z7aOT7(vO1{?%YmQjcRb*tbMvkhfwpd4Q$gb
zjmDB(Zc6Gglgn_RU~sn_iEC*KbW;t*WL4|TBqG6u?7x89ZSO(|Dt7ED`=YMtYiAL<
zgj`oVm5ds(ZjBRdAxb<7Sd>R!BP~SHC2VQ!5+}V1^IO9BrTWh#|4EqBK?KE=!A916
zt8WB3k5b~oltyF8B$u|p4MHy<{1Os0L(c=@a!YsG>Y=YA1H+)BK=3f){812C0D@@bD>i7Efo6^@<^P!9~97}SJ<^@N-FJI
zExLc@La(EaXlGBfOV^UBN%7@+c6{cUdlCt;-#08#ZlSgi`@Eq{F>v}bR@T_ywa>C$
zqa2oKEU(6@Q_P;NfZ_p*EJQ^ok!#oKCVRJW6`}0o#&5$#)6uk4{taNs@ncOxq#W<6
z2!YmycRwQ1-@>vzvWPpQQ8g8hMroWynk(3j=Aufwmm#0sX&n;16xHJ^fwyrgNyE7A
zWUn Tuple[bool, List[str]]:
- """
- 扫描 SQL 中单引号字符串(含 T-SQL N'…'),若字面量内出现 CJK 则判失败。
+def _cjk_text_in_string_literal(text: str) -> bool:
+ return bool(_CJK_IN_STRING_RE.search(text))
- 跳过 ``--`` 行注释与 ``/* */`` 块注释内的文本,避免误报。
+
+def _node_in_subtree(root, target) -> bool:
+ if root is None:
+ return False
+ for n in root.walk():
+ if n is target:
+ return True
+ return False
+
+
+def _cjk_string_in_forbidden_context(node, exp) -> bool:
+ """
+ 禁止含 CJK 的字面量出现在「比对/过滤」语境:WHERE、HAVING、JOIN ON、
+ 以及 CASE 分支的 WHEN 条件(含简单 CASE 的 WHEN 值),
+ 但允许出现在 SELECT 投影、CASE 的 THEN/ELSE 结果等纯展示位置。
+ """
+ if node.find_ancestor(exp.Where):
+ return True
+ if node.find_ancestor(exp.Having):
+ return True
+ join = node.find_ancestor(exp.Join)
+ if join is not None:
+ on = join.args.get("on")
+ if on is not None and _node_in_subtree(on, node):
+ return True
+
+ case = node.find_ancestor(exp.Case)
+ while case is not None:
+ default = case.args.get("default")
+ if default is not None and _node_in_subtree(default, node):
+ return False
+ for br in case.args.get("ifs") or []:
+ then_expr = br.args.get("true")
+ cond = br.this
+ if then_expr is not None and _node_in_subtree(then_expr, node):
+ return False
+ if cond is not None and _node_in_subtree(cond, node):
+ return True
+ case = case.find_ancestor(exp.Case)
+
+ return False
+
+
+def _check_no_cjk_legacy_text_scan(sql: str) -> Tuple[bool, List[str]]:
+ """
+ 解析失败时的回退:扫描单引号字符串(含 N'…'),字面量内出现 CJK 即失败。
+ 跳过 ``--`` 行注释与 ``/* */`` 块注释内的文本。
"""
errors: List[str] = []
i = 0
@@ -27,7 +72,6 @@ def check_no_cjk_in_sql_string_literals(sql: str) -> Tuple[bool, List[str]]:
in_block_comment = False
def _read_single_quoted_string(start: int) -> Tuple[str, int]:
- """从 start 指向的 opening `'` 之后开始读,返回 (内容, 闭合引号后下标)。"""
j = start
parts: List[str] = []
while j < n:
@@ -66,10 +110,9 @@ def check_no_cjk_in_sql_string_literals(sql: str) -> Tuple[bool, List[str]]:
i += 2
continue
- # N' 或 n' 前缀的 Unicode 字面量
if i + 1 < n and sql[i] in "Nn" and sql[i + 1] == "'":
body, i = _read_single_quoted_string(i + 2)
- if _CJK_IN_STRING_RE.search(body):
+ if _cjk_text_in_string_literal(body):
prev = body[:48] + ("…" if len(body) > 48 else "")
errors.append(
"SQL 字符串字面量中含中文或与业务中文直接作为比对值(禁止)。"
@@ -79,7 +122,7 @@ def check_no_cjk_in_sql_string_literals(sql: str) -> Tuple[bool, List[str]]:
if sql[i] == "'":
body, i = _read_single_quoted_string(i + 1)
- if _CJK_IN_STRING_RE.search(body):
+ if _cjk_text_in_string_literal(body):
prev = body[:48] + ("…" if len(body) > 48 else "")
errors.append(
"SQL 字符串字面量中含中文或与业务中文直接作为比对值(禁止)。"
@@ -92,6 +135,41 @@ def check_no_cjk_in_sql_string_literals(sql: str) -> Tuple[bool, List[str]]:
return len(errors) == 0, errors
+def check_no_cjk_in_sql_string_literals(sql: str, dialect: str = "tsql") -> Tuple[bool, List[str]]:
+ """
+ 禁止在「过滤/比对」语境使用含中日韩字符的字符串字面量(含 T-SQL ``N'…'``)。
+
+ 允许在 SELECT 投影、CASE 的 THEN/ELSE 结果等展示用字面量中使用中文标签。
+ 解析失败时回退为全文扫描(与旧版一致,偏严)。
+ """
+ from sqlglot import parse_one, exp
+
+ errors: List[str] = []
+
+ try:
+ parsed = parse_one(sql, dialect=dialect)
+ except Exception as e:
+ logger.debug("CJK 校验回退为全文扫描(SQL 解析失败): %s", e)
+ return _check_no_cjk_legacy_text_scan(sql)
+
+ for node in parsed.walk():
+ text: Optional[str] = None
+ if isinstance(node, exp.Literal) and node.is_string:
+ text = str(node.this)
+ elif isinstance(node, exp.National):
+ text = str(node.this)
+ if text is None or not _cjk_text_in_string_literal(text):
+ continue
+ if _cjk_string_in_forbidden_context(node, exp):
+ prev = text[:48] + ("…" if len(text) > 48 else "")
+ errors.append(
+ "SQL 在 WHERE/HAVING/JOIN ON 或 CASE/WHEN 条件中出现含中文的字符串字面量(禁止)。"
+ f"片段近似: …'{prev}'… — 请改用 Schema 注释中的代码/枚举,或通过维表 JOIN 用键列过滤。"
+ )
+
+ return len(errors) == 0, errors
+
+
# 危险操作关键词(除非明确允许)
DANGEROUS_KEYWORDS = [
"DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "TRUNCATE",
diff --git a/data/embeddings/chroma_fewshot/9756e6ea-6d51-4e24-a490-4a58f0a585b9/length.bin b/data/embeddings/chroma_fewshot/9756e6ea-6d51-4e24-a490-4a58f0a585b9/length.bin
index d1fa5fe9b7b0ce3292b49c25b0aa11a055477be1..16406af4df89423e2b5130dcedff88eddfd90e78 100644
GIT binary patch
literal 4000
zcmb7FOK=ob6n)b()6)|=B%}dJNM`s&z<>!#AQK1@KAB8rl88aR1u{WE1xrg@Koy{l
zpJc|ek}6$5X%!21x^v~iFIZ)j-+qlcpkRjy%nl||VXEX=O32)jw^EvBaOFS4tIU#0a5`MpBxEoQLS
zXr1Dt>}SbW&d1mENj}P6rxoYF_VK~|G4`g9*V)_N`AYthkDs3ZwvX4@@7{Tp
zKYr{>A0Ny=d;Q~q^TGQ6@bSU=S!8G;`*;{1q
zk$p?{J=u3;HW?@Tk?aSupUBE&H^{D&Z40s5`iXQgEmpQR)reI>IB*VG*BE68-coj28)9t1=J0M
zn45q$SI+eSZ2>}thk&+9*+|hF0#;=}_;!hRNFI2n#C;ME7l%b!;{FQnW`J);SH*NU
zLc8&d2(x&@Wai@O%jYhgDU$L;=|H|C6W&KJ_|yS+^#ati2sP1e
zhMLy%y3;JIy^#vd6ZzaAQlA=5$o`
zS_NC~n9F5TS2*ShHyTDKbaaAtGjw9JliX}uJ>2LK8hN?!7b7yNdnC#RL!+T<`Y6xk
zKRo;5*~!$!qnA%zI#o`z^F(_&k#Z6#yP=!My6s$^>!TvC8sWx>(CD)Z2a$=%-|n+gj5lwT7*A8>7vl+~#jTAYd=0|Wt}1-qwnn)zDm41+
z!asSxfnD#STiXr_>voWoo4cHO8ruZRnh$Q5#voA{DLcEe%uf+DuY
z(VA=_JK4sQ{r0?!=#9&sZIeWQq5ryJxlCJFBf^n7U2IouPO_A!imlhhpknt(mai5|V`^7Ou9G`aLgd(1h
zM0HtDD&nbW#7RY*k_5dGsX2^#qw{s~v|`VIEk_nQk%h$b1*0OYHR|H5VlODR(TOxl
zc0aMrx_CjcJ8I_A~)axqUFvg;264i$zb5AtRE?fD0)*gm^H
d4M=^Ka*mRmxjOx&sGkUZbJ?BpO?=H1{s9ZZT%rH~
literal 4000
zcmXRowdChCMh1q4oEiT?Gy{VK2Z%huIUOt>!_dOO1ms7sfkYV?ws=G6h!YUH@MSlc
z!3^PIu#tqR!sLgV;=|y`5Wo<^;148q8O#_=8JvKmH-kBY1yGcMfd#6L5yD^<1@hBp
zurp8k)VeD4++|s?SHWsPzGGm3@nJL<0}oi70qlDy6G%aW0WQD_Wf?J;Fc>fxGFSrn
zFf-taMj4|aFd71*Aut*OqaiRF0;3@?8UmvsFvvncQb{9*fsdhKkU4AAIin#kqC)@x
DbKM(I
diff --git a/logs/text2sql_api.log b/logs/text2sql_api.log
index 0a01ac8..a02e2bd 100644
--- a/logs/text2sql_api.log
+++ b/logs/text2sql_api.log
@@ -1,68 +1,135 @@
-2026-04-16 16:22:05 INFO [utils.repo_logging] repo_logging.py:79 configure_text2sql_api_logging() | 日志文件: C:\Users\24019\Desktop\backman-camel\logs\text2sql_api.log
-2026-04-16 16:22:10 INFO [__main__] api_server.py:85 () | [OK] 已加载配置文件: C:\Users\24019\Desktop\backman-camel\.env
-2026-04-16 16:22:10 INFO [__main__] api_server.py:1108 () | 启动服务: http://0.0.0.0:8041
-2026-04-16 16:22:10 INFO [__main__] api_server.py:1109 () | API文档: http://0.0.0.0:8041/docs
-2026-04-16 16:22:10 INFO [uvicorn.error] server.py:92 _serve() | Started server process [33080]
-2026-04-16 16:22:10 INFO [uvicorn.error] on.py:48 startup() | Waiting for application startup.
-2026-04-16 16:22:10 INFO [__main__] api_server.py:799 lifespan() | ============================================================
-2026-04-16 16:22:10 INFO [__main__] api_server.py:800 lifespan() | Text2SQL API Server 启动中...
-2026-04-16 16:22:10 INFO [__main__] api_server.py:801 lifespan() | ============================================================
-2026-04-16 16:22:10 INFO [main] main.py:126 setup_environment() | [OK] 环境检查通过
-2026-04-16 16:22:10 INFO [main] main.py:127 setup_environment() | - Schema: data\schemas\G3SB_MCDataDictionary_table_structure.json
-2026-04-16 16:22:10 INFO [main] main.py:128 setup_environment() | - LLM: openai
-2026-04-16 16:22:10 INFO [main] main.py:154 load_schema() | 加载Schema: ./data/schemas/G3SB_MCDataDictionary_table_structure.json
-2026-04-16 16:22:10 INFO [main] main.py:156 load_schema() | 表注释(meta): ./data/schemas/G3SB_MCDataDictionary_table_meta.json
-2026-04-16 16:22:10 INFO [schema.loader] loader.py:107 load_from_json() | [OK] 加载Schema完成(G3SB schemas): G3SB_MCDataDictionary_table_structure, 共2516张表
-2026-04-16 16:22:10 INFO [main] main.py:163 load_schema() | [OK] Schema加载完成: G3SB_MCDataDictionary_table_structure, 共2516张表, 59196个字段
-2026-04-16 16:22:14 INFO [llm.openai_client] openai_client.py:55 __init__() | [OK] OpenAIClient初始化: model=gpt-4o-mini, base_url=http://113.192.49.54:9080/v1
-2026-04-16 16:22:17 INFO [utils.embedding] embedding.py:144 __init__() | 使用 OpenAI Embedding API:model=text-embedding-ada-002,base_url=http://113.192.49.54:9080/v1,max_batch=100
-2026-04-16 16:22:19 INFO [utils.fewshot_chroma_store] fewshot_chroma_store.py:86 __init__() | [OK] FewShotChromaStore: 磁盘 data\embeddings\chroma_fewshot collection=fewshot_samples count=50
-2026-04-16 16:22:19 INFO [utils.fewshot_selector] fewshot_selector.py:128 _init_chroma_mode() | [Few-shot] 已从向量库加载(Chroma 50 条,data\embeddings\chroma_fewshot)
-2026-04-16 16:22:19 INFO [utils.fewshot_selector] fewshot_selector.py:159 _init_chroma_mode() | [OK] Few-shot 使用 Chroma(50 条)
-2026-04-16 16:22:19 INFO [agents.orchestrator] orchestrator.py:116 __init__() | Few-shot已启用: top_k=3, min_rating=7
-2026-04-16 16:22:19 INFO [agents.orchestrator] orchestrator.py:127 __init__() | [OK] Text2SQLOrchestrator初始化完成: max_retry=2, use_vector_search=True, fewshot=on, nl→zh_norm=on
-2026-04-16 16:22:19 INFO [__main__] api_server.py:132 get_orchestrator() | [OK] Orchestrator 初始化完成
-2026-04-16 16:22:19 INFO [__main__] api_server.py:805 lifespan() | [OK] 服务已就绪
-2026-04-16 16:22:19 INFO [uvicorn.error] on.py:62 startup() | Application startup complete.
-2026-04-16 16:22:19 INFO [uvicorn.error] server.py:224 _log_started_message() | Uvicorn running on http://0.0.0.0:8041 (Press CTRL+C to quit)
-2026-04-16 16:22:20 INFO [uvicorn.access] httptools_impl.py:483 send() | 127.0.0.1:14651 - "POST /g3sb/api/nl/chat/stream HTTP/1.1" 200
-2026-04-16 16:22:20 INFO [__main__] api_server.py:669 _chat_stream_events() | [API/stream] 开始: user_id='anonymous' visitor_biz_id=None session_id=None service_code=None model='gpt-4o-mini' lang_code='auto' msg_chars=15 preview='所有客户账户之间的股票转移记录' dialog_context_chars=0 last_turn_was_data_query=False
-2026-04-16 16:22:23 INFO [llm.openai_client] openai_client.py:55 __init__() | [OK] OpenAIClient初始化: model=gpt-4o-mini, base_url=http://113.192.49.54:9080/v1
-2026-04-16 16:22:23 INFO [utils.dialog_classifier] dialog_classifier.py:261 classify_dialog() | [dialog] intent=text2sql (hybrid fast: query hint) preview='所有客户账户之间的股票转移记录'
-2026-04-16 16:22:23 INFO [__main__] api_server.py:712 _chat_stream_events() | [GEN/API/stream] dialect=tsql top_k=20 dialog_context_chars=0 question_len=15 preview='所有客户账户之间的股票转移记录'
-2026-04-16 16:22:26 INFO [llm.openai_client] openai_client.py:55 __init__() | [OK] OpenAIClient初始化: model=gpt-4o-mini, base_url=http://113.192.49.54:9080/v1
-2026-04-16 16:22:29 INFO [agents.orchestrator] orchestrator.py:803 generate() | [GEN] 问句已归一中文:所有客户账户的股票转移记录
-2026-04-16 16:22:29 INFO [agents.orchestrator] orchestrator.py:816 generate() | [GEN] 开始生成SQL: question_chars=13 preview='所有客户账户的股票转移记录' dialog_context_chars=0
-2026-04-16 16:22:29 INFO [agents.orchestrator] orchestrator.py:831 generate() | 尝试 #1
-2026-04-16 16:22:29 INFO [schema.indexer] indexer.py:82 __init__() | [OK] 初始化SchemaIndexer(Chroma磁盘 path=data\embeddings\chroma): collection=schema_tables, count=2516
-2026-04-16 16:22:29 INFO [schema.indexer] indexer.py:106 ensure_index_for_schema() | Schema 向量索引已就绪(2516 张表),跳过向量化
-2026-04-16 16:22:29 INFO [agents.orchestrator] orchestrator.py:188 _coarse_filter() | [Orchestrator] 开始向量检索: query_chars=13 query_preview='所有客户账户的股票转移记录'
-2026-04-16 16:22:30 INFO [utils.embedding] embedding.py:163 _set_dim_from_vector() | [OK] Embedding 向量维度:1536
-2026-04-16 16:22:31 INFO [schema.indexer] indexer.py:244 search() | Schema 向量检索: query_chars=13 命中=20(阈值=0.1)top=[('VSBHKRpt0430', 0.8264), ('VSBHKRpt0431', 0.8188), ('TSBTransferInstruction', 0.8147), ('VSBTransferInstruction', 0.8134), ('VSBHKRpt0397', 0.8126), ('VSBHKRpt0672', 0.8117), ('VSBHKRpt0090C', 0.81), ('VSBHKRpt0570', 0.8084), ('VSBHKRpt1030A', 0.8077), ('VSBRpt0999E', 0.8065), ('VSBRpt0999C', 0.8064), ('TSBAccountEntitlementRelease', 0.8063), ('VSBRpt0999A', 0.8059), ('TSBAccountInstrumentMovement', 0.8038), ('VSBTransferInstructionGenerationByAccountContract', 0.8033)]
-2026-04-16 16:22:31 INFO [agents.orchestrator] orchestrator.py:204 _coarse_filter() | [Orchestrator] 向量粗筛: 命中=20 张(阈值内),表名+分: [('VSBHKRpt0430', 0.8264), ('VSBHKRpt0431', 0.8188), ('TSBTransferInstruction', 0.8147), ('VSBTransferInstruction', 0.8134), ('VSBHKRpt0397', 0.8126), ('VSBHKRpt0672', 0.8117), ('VSBHKRpt0090C', 0.81), ('VSBHKRpt0570', 0.8084), ('VSBHKRpt1030A', 0.8077), ('VSBRpt0999E', 0.8065), ('VSBRpt0999C', 0.8064), ('TSBAccountEntitlementRelease', 0.8063), ('VSBRpt0999A', 0.8059), ('TSBAccountInstrumentMovement', 0.8038), ('VSBTransferInstructionGenerationByAccountContract', 0.8033), ('VSBRpt0060', 0.8021), ('XCGatewayStockReconciliationReport', 0.802), ('TSBAccountEntitlementHold', 0.8013), ('WSBBatchLocationTransferDetail', 0.8012), ('VCAccountCashMovement', 0.8012)]
-2026-04-16 16:22:35 INFO [agents.orchestrator] orchestrator.py:258 _llm_select_tables() | LLM精筛选中表:['VSBHKRpt0430', 'VSBHKRpt0431', 'TSBTransferInstruction', 'VSBTransferInstruction', 'TSBAccountInstrumentMovement'] | reasoning_chars=192 reasoning_preview='问题涉及客户账户的股票转移记录,VSBHKRpt0430和VSBHKRpt0431提供了客户股票的日常转移报告和账户工具移动报告,TSBTransferInstruction和VSBTransferInstruction包含账户转移指令的详细信息,TSBAccountInstrumentMovement记录账户工具的移动交易。这些表共同涵盖了股票转移的各个方面,确保了信息的完整性。'
-2026-04-16 16:22:35 INFO [agents.orchestrator] orchestrator.py:859 generate() | 选中表:['VSBHKRpt0430', 'VSBHKRpt0431', 'TSBTransferInstruction', 'VSBTransferInstruction', 'TSBAccountInstrumentMovement'],扩展后:['TSBAccountInstrumentMovement', 'TSBTransferInstruction', 'VSBHKRpt0431', 'VSBHKRpt0430', 'VSBTransferInstruction']
-2026-04-16 16:22:37 INFO [utils.fewshot_selector] fewshot_selector.py:209 select_best_with_score() | [Few-shot] best_with_score: qid=Q2 score=0.9575 preview='列出今日所有客户账户之间的股票转移记录。'
-2026-04-16 16:22:37 INFO [agents.orchestrator] orchestrator.py:432 _generate_sql_golden_adapt() | [GEN] 黄金 few-shot 条件适配: qid=Q2 score=0.9575
-2026-04-16 16:22:41 INFO [agents.orchestrator] orchestrator.py:450 _generate_sql_golden_adapt() | 生成的SQL(黄金适配,chars=494):
--- 查询所有客户账户间股票转移记录
--- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移
-SELECT
- m.MovementID,
- m.AccountID AS FromAccountID,
- m.TransferToAccountID,
- m.InstrumentID,
- i.Name AS InstrumentSymbol,
- m.MovementType,
- m.Quantity AS TransferQuantity,
- m.ValueDate AS TransferDate
-FROM TSBAccountInstrumentMovement m
-LEFT JOIN MCInstrument i ON m.InstrumentID = i.InstrumentID
-WHERE m.MovementType = 'T'
- AND m.ValueDate = CAST(GETDATE() AS DATE)
-ORDER BY m.ValueDate;
-2026-04-16 16:22:41 INFO [db.engine] engine.py:45 get_engine() | SQLAlchemy engine initialized from database_url
-2026-04-16 16:22:41 INFO [db.dbhub_tools] dbhub_tools.py:627 _execute_sql() | _execute_sql 执行语句数=1 readonly=True max_rows=1
-2026-04-16 16:22:44 INFO [agents.orchestrator] orchestrator.py:754 _validate_sql() | [validate] 程序+探针+LLM 汇总: valid=True err_count=0 warn_count=0 db_execution_status=0 sql_chars=494
-2026-04-16 16:22:44 INFO [agents.orchestrator] orchestrator.py:921 generate() | [OK] SQL生成与验证通过(1次尝试)
-2026-04-16 16:22:44 INFO [__main__] api_server.py:765 _chat_stream_events() | [API/stream] Text2SQL 完成: valid=True attempts=1 tables_used=['TSBAccountInstrumentMovement', 'TSBTransferInstruction', 'VSBHKRpt0431', 'VSBHKRpt0430', 'VSBTransferInstruction'] sql_chars=494 sql_head="-- 查询所有客户账户间股票转移记录\n-- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移\nSELECT \n m.MovementID,\n m.AccountID AS FromAccountID,\n m.TransferToAccountID,\n m.InstrumentID,\n i.Name AS InstrumentSymbol,\n m.MovementType,\n m.Quantity AS TransferQuantity,\n m.ValueDate AS TransferDate\nFROM TSBAccountInstrumentMovement m\nLEFT JOIN MCInstrument i ON m.InstrumentID = i.InstrumentID\nWHERE m.MovementType = 'T'\n AND m.ValueDate = CAST(GETDATE() AS DATE)\nORDER BY m.ValueDate;"
+2026-04-17 10:48:50 INFO [utils.repo_logging] repo_logging.py:79 configure_text2sql_api_logging() | 日志文件: C:\Users\24019\Desktop\backman-camel\logs\text2sql_api.log
+2026-04-17 10:48:55 INFO [__main__] api_server.py:86 () | [OK] 已加载配置文件: C:\Users\24019\Desktop\backman-camel\.env
+2026-04-17 10:48:55 INFO [__main__] api_server.py:1299 () | 启动服务: http://0.0.0.0:8041
+2026-04-17 10:48:55 INFO [__main__] api_server.py:1300 () | API文档: http://0.0.0.0:8041/docs
+2026-04-17 10:48:55 INFO [uvicorn.error] server.py:92 _serve() | Started server process [38600]
+2026-04-17 10:48:55 INFO [uvicorn.error] on.py:48 startup() | Waiting for application startup.
+2026-04-17 10:48:55 INFO [__main__] api_server.py:969 lifespan() | ============================================================
+2026-04-17 10:48:55 INFO [__main__] api_server.py:970 lifespan() | Text2SQL API Server 启动中...
+2026-04-17 10:48:55 INFO [__main__] api_server.py:971 lifespan() | ============================================================
+2026-04-17 10:48:55 INFO [main] main.py:126 setup_environment() | [OK] 环境检查通过
+2026-04-17 10:48:55 INFO [main] main.py:127 setup_environment() | - Schema: data\schemas\G3SB_MCDataDictionary_table_structure.json
+2026-04-17 10:48:55 INFO [main] main.py:128 setup_environment() | - LLM: openai
+2026-04-17 10:48:55 INFO [main] main.py:154 load_schema() | 加载Schema: ./data/schemas/G3SB_MCDataDictionary_table_structure.json
+2026-04-17 10:48:55 INFO [main] main.py:156 load_schema() | 表注释(meta): ./data/schemas/G3SB_MCDataDictionary_table_meta.json
+2026-04-17 10:48:55 INFO [schema.loader] loader.py:107 load_from_json() | [OK] 加载Schema完成(G3SB schemas): G3SB_MCDataDictionary_table_structure, 共2516张表
+2026-04-17 10:48:55 INFO [main] main.py:163 load_schema() | [OK] Schema加载完成: G3SB_MCDataDictionary_table_structure, 共2516张表, 59196个字段
+2026-04-17 10:48:59 INFO [llm.openai_client] openai_client.py:55 __init__() | [OK] OpenAIClient初始化: model=gpt-4o-mini, base_url=http://113.192.49.54:9080/v1
+2026-04-17 10:49:01 INFO [utils.embedding] embedding.py:144 __init__() | 使用 OpenAI Embedding API:model=text-embedding-ada-002,base_url=http://113.192.49.54:9080/v1,max_batch=100
+2026-04-17 10:49:03 INFO [utils.fewshot_chroma_store] fewshot_chroma_store.py:86 __init__() | [OK] FewShotChromaStore: 磁盘 data\embeddings\chroma_fewshot collection=fewshot_samples count=50
+2026-04-17 10:49:03 INFO [utils.fewshot_selector] fewshot_selector.py:128 _init_chroma_mode() | [Few-shot] 已从向量库加载(Chroma 50 条,data\embeddings\chroma_fewshot)
+2026-04-17 10:49:03 INFO [utils.fewshot_selector] fewshot_selector.py:159 _init_chroma_mode() | [OK] Few-shot 使用 Chroma(50 条)
+2026-04-17 10:49:03 INFO [agents.orchestrator] orchestrator.py:116 __init__() | Few-shot已启用: top_k=3, min_rating=7
+2026-04-17 10:49:03 INFO [agents.orchestrator] orchestrator.py:127 __init__() | [OK] Text2SQLOrchestrator初始化完成: max_retry=2, use_vector_search=True, fewshot=on, nl→zh_norm=on
+2026-04-17 10:49:03 INFO [__main__] api_server.py:133 get_orchestrator() | [OK] Orchestrator 初始化完成
+2026-04-17 10:49:03 INFO [__main__] api_server.py:975 lifespan() | [OK] 服务已就绪
+2026-04-17 10:49:03 INFO [uvicorn.error] on.py:62 startup() | Application startup complete.
+2026-04-17 10:49:03 INFO [uvicorn.error] server.py:224 _log_started_message() | Uvicorn running on http://0.0.0.0:8041 (Press CTRL+C to quit)
+2026-04-17 10:49:16 INFO [uvicorn.access] httptools_impl.py:483 send() | 127.0.0.1:29292 - "POST /g3sb/api/nl/chat/stream HTTP/1.1" 200
+2026-04-17 10:49:16 INFO [__main__] api_server.py:735 _chat_stream_events() | [API/stream] 开始: user_id='anonymous' visitor_biz_id=None session_id=None service_code=None model='gpt-4o-mini' lang_code='auto' msg_chars=13 preview='查看目前余额最高的账号名称' dialog_context_chars=0 last_turn_was_data_query=False
+2026-04-17 10:49:19 INFO [llm.openai_client] openai_client.py:55 __init__() | [OK] OpenAIClient初始化: model=gpt-4o-mini, base_url=http://113.192.49.54:9080/v1
+2026-04-17 10:49:19 INFO [utils.dialog_classifier] dialog_classifier.py:261 classify_dialog() | [dialog] intent=text2sql (hybrid fast: query hint) preview='查看目前余额最高的账号名称'
+2026-04-17 10:49:19 INFO [__main__] api_server.py:801 _chat_stream_events() | [GEN/API/stream] dialect=tsql top_k=20 dialog_context_chars=0 question_len=13 preview='查看目前余额最高的账号名称'
+2026-04-17 10:49:22 INFO [llm.openai_client] openai_client.py:55 __init__() | [OK] OpenAIClient初始化: model=gpt-4o-mini, base_url=http://113.192.49.54:9080/v1
+2026-04-17 10:49:24 INFO [agents.orchestrator] orchestrator.py:827 generate() | [GEN] 问句已归一中文:查看余额最高的账号名称
+2026-04-17 10:49:24 INFO [agents.orchestrator] orchestrator.py:840 generate() | [GEN] 开始生成SQL: question_chars=11 preview='查看余额最高的账号名称' dialog_context_chars=0
+2026-04-17 10:49:24 INFO [agents.orchestrator] orchestrator.py:855 generate() | 尝试 #1
+2026-04-17 10:49:24 INFO [schema.indexer] indexer.py:82 __init__() | [OK] 初始化SchemaIndexer(Chroma磁盘 path=data\embeddings\chroma): collection=schema_tables, count=2516
+2026-04-17 10:49:24 INFO [schema.indexer] indexer.py:106 ensure_index_for_schema() | Schema 向量索引已就绪(2516 张表),跳过向量化
+2026-04-17 10:49:24 INFO [agents.orchestrator] orchestrator.py:188 _coarse_filter() | [Orchestrator] 开始向量检索: query_chars=11 query_preview='查看余额最高的账号名称'
+2026-04-17 10:49:27 INFO [utils.embedding] embedding.py:163 _set_dim_from_vector() | [OK] Embedding 向量维度:1536
+2026-04-17 10:49:27 INFO [schema.indexer] indexer.py:244 search() | Schema 向量检索: query_chars=11 命中=20(阈值=0.1)top=[('VSBFrontOfficeBalanceAccountCash', 0.7854), ('VSBRpt9009', 0.7824), ('BCAccountCash', 0.7824), ('VXVNSyncBalanceAccountCash', 0.7812), ('VCAccountOnlineCashWithdrawOrTransferredAmount', 0.781), ('VCAccountBalance', 0.7798), ('VXPhilipsB2BInterfaceAccountCashBalance', 0.779), ('VCBAccountCash', 0.7788), ('VSBAccountContractOnlineAvailableCashInAdvanceAmount', 0.7775), ('VCAccountDailyCreditLimit', 0.7765), ('VSBAyersFrontOfficeBalanceAccountCash', 0.776), ('VSBRpt0060', 0.7756), ('VSBRpt0055D_CIBalance', 0.7745), ('VSBRpt9008', 0.7745), ('VSBVNFrontOfficeMasterAccount', 0.7745)]
+2026-04-17 10:49:27 INFO [agents.orchestrator] orchestrator.py:204 _coarse_filter() | [Orchestrator] 向量粗筛: 命中=20 张(阈值内),表名+分: [('VSBFrontOfficeBalanceAccountCash', 0.7854), ('VSBRpt9009', 0.7824), ('BCAccountCash', 0.7824), ('VXVNSyncBalanceAccountCash', 0.7812), ('VCAccountOnlineCashWithdrawOrTransferredAmount', 0.781), ('VCAccountBalance', 0.7798), ('VXPhilipsB2BInterfaceAccountCashBalance', 0.779), ('VCBAccountCash', 0.7788), ('VSBAccountContractOnlineAvailableCashInAdvanceAmount', 0.7775), ('VCAccountDailyCreditLimit', 0.7765), ('VSBAyersFrontOfficeBalanceAccountCash', 0.776), ('VSBRpt0060', 0.7756), ('VSBRpt0055D_CIBalance', 0.7745), ('VSBRpt9008', 0.7745), ('VSBVNFrontOfficeMasterAccount', 0.7745), ('VSBRpt0061', 0.7744), ('VSBRpt0055N_CIBalance', 0.7743), ('VCBAccountCashForBatchCashWithdrawal', 0.7739), ('VSBRPT0290I', 0.7737), ('VCBAccountSummary', 0.7736)]
+2026-04-17 10:49:31 INFO [agents.orchestrator] orchestrator.py:258 _llm_select_tables() | LLM精筛选中表:['BCAccountCash', 'VCAccountBalance', 'VCBAccountCash'] | reasoning_chars=101 reasoning_preview='问题涉及查看余额,BCAccountCash存储账户现金余额信息,VCAccountBalance提供账户余额视图,VCBAccountCash是账户现金视图,这些表能够提供账户名称及其对应的余额信息。'
+2026-04-17 10:49:31 INFO [agents.orchestrator] orchestrator.py:320 _prioritize_vc_user_accessible_function() | 已优先纳入目录视图 VCUserAccessibleFunction(置于选表前列),当前选表:['VCUserAccessibleFunction', 'BCAccountCash', 'VCAccountBalance', 'VCBAccountCash']
+2026-04-17 10:49:31 INFO [agents.orchestrator] orchestrator.py:894 generate() | 选中表:['VCUserAccessibleFunction', 'BCAccountCash', 'VCAccountBalance', 'VCBAccountCash'],扩展后:['VCBAccountCash', 'BCAccountCash', 'VCAccountBalance', 'VCUserAccessibleFunction']
+2026-04-17 10:49:32 INFO [utils.fewshot_selector] fewshot_selector.py:209 select_best_with_score() | [Few-shot] best_with_score: qid=Q17 score=0.8266 preview='哪些客户已超过其信用额度?'
+2026-04-17 10:49:34 INFO [utils.fewshot_selector] fewshot_selector.py:357 _select_chroma() | Few-shot(Chroma)选择: 问题='查看余额最高的账号名称...' → 选中3个示例 (top_k=3, min_rating=7)
+2026-04-17 10:49:34 INFO [agents.orchestrator] orchestrator.py:568 _generate_sql() | 已注入 3 个 few-shot 示例: qid=['Q17', 'Q27', 'Q23'] question_zh_preview=['哪些客户已超过其信用额度?', '列出所有超过3天的暂记账户分录。', '列出所有逾期未付保证金利息的账户。']
+2026-04-17 10:49:37 INFO [agents.orchestrator] orchestrator.py:635 _generate_sql() | 生成的SQL(chars=178):
+SELECT
+ a.AccountID,
+ a.AccountName AS AccountName,
+ a.AvailableBalance,
+ a.MarketValue,
+ a.LedgerBalance
+FROM VCAccountBalance a
+ORDER BY a.AvailableBalance DESC;
+2026-04-17 10:49:37 INFO [db.engine] engine.py:45 get_engine() | SQLAlchemy engine initialized from database_url
+2026-04-17 10:49:37 INFO [db.dbhub_tools] dbhub_tools.py:627 _execute_sql() | _execute_sql 执行语句数=1 readonly=True max_rows=1
+2026-04-17 10:49:38 INFO [agents.orchestrator] orchestrator.py:778 _validate_sql() | [validate] 程序+探针+LLM 汇总: valid=True err_count=0 warn_count=0 db_execution_status=1 sql_chars=178
+2026-04-17 10:49:38 INFO [agents.orchestrator] orchestrator.py:956 generate() | [OK] SQL生成与验证通过(1次尝试)
+2026-04-17 10:49:41 INFO [__main__] api_server.py:916 _chat_stream_events() | [API/stream] Text2SQL 完成: valid=True attempts=1 tables_used=['VCBAccountCash', 'BCAccountCash', 'VCAccountBalance', 'VCUserAccessibleFunction'] sql_chars=178 sql_head='SELECT\n a.AccountID,\n a.AccountName AS AccountName,\n a.AvailableBalance,\n a.MarketValue,\n a.LedgerBalance\nFROM VCAccountBalance a\nORDER BY a.AvailableBalance DESC;'
+2026-04-17 10:53:01 INFO [uvicorn.access] httptools_impl.py:483 send() | 127.0.0.1:24658 - "POST /g3sb/api/nl/chat/stream HTTP/1.1" 200
+2026-04-17 10:53:01 INFO [__main__] api_server.py:735 _chat_stream_events() | [API/stream] 开始: user_id='anonymous' visitor_biz_id=None session_id=None service_code=None model='gpt-4o-mini' lang_code='auto' msg_chars=23 preview="查询账户'M050013'持有的所有股票及数量" dialog_context_chars=0 last_turn_was_data_query=False
+2026-04-17 10:53:04 INFO [llm.openai_client] openai_client.py:55 __init__() | [OK] OpenAIClient初始化: model=gpt-4o-mini, base_url=http://113.192.49.54:9080/v1
+2026-04-17 10:53:04 INFO [utils.dialog_classifier] dialog_classifier.py:261 classify_dialog() | [dialog] intent=text2sql (hybrid fast: query hint) preview="查询账户'M050013'持有的所有股票及数量"
+2026-04-17 10:53:04 INFO [__main__] api_server.py:801 _chat_stream_events() | [GEN/API/stream] dialect=tsql top_k=20 dialog_context_chars=0 question_len=23 preview="查询账户'M050013'持有的所有股票及数量"
+2026-04-17 10:53:07 INFO [llm.openai_client] openai_client.py:55 __init__() | [OK] OpenAIClient初始化: model=gpt-4o-mini, base_url=http://113.192.49.54:9080/v1
+2026-04-17 10:53:08 INFO [agents.orchestrator] orchestrator.py:827 generate() | [GEN] 问句已归一中文:查询账户'M050013'持有的所有股票及其数量
+2026-04-17 10:53:08 INFO [agents.orchestrator] orchestrator.py:840 generate() | [GEN] 开始生成SQL: question_chars=24 preview="查询账户'M050013'持有的所有股票及其数量" dialog_context_chars=0
+2026-04-17 10:53:08 INFO [agents.orchestrator] orchestrator.py:855 generate() | 尝试 #1
+2026-04-17 10:53:09 INFO [schema.indexer] indexer.py:106 ensure_index_for_schema() | Schema 向量索引已就绪(2516 张表),跳过向量化
+2026-04-17 10:53:09 INFO [agents.orchestrator] orchestrator.py:188 _coarse_filter() | [Orchestrator] 开始向量检索: query_chars=24 query_preview="查询账户'M050013'持有的所有股票及其数量"
+2026-04-17 10:53:10 INFO [schema.indexer] indexer.py:244 search() | Schema 向量检索: query_chars=24 命中=20(阈值=0.1)top=[('VSBHKRpt0695A', 0.8153), ('VSBHKRpt0365A', 0.8114), ('VSBHKRpt1330', 0.809), ('VSBHKRPT0156', 0.8061), ('VSBHKRpt0672', 0.8053), ('VSBHKRpt0390D', 0.8051), ('VSBHKRPT0590', 0.8046), ('VSBRpt0055U_AccountInformation', 0.8038), ('VSBHKRpt4000A', 0.8028), ('VSBHKRptMSTATInvestmentPortfolioNoHoldings', 0.8026), ('VSBHKRpt1150', 0.8022), ('VSBRpt0055M_AccountInformation', 0.8019), ('VSBRpt0055U_Stock', 0.8017), ('VSBRpt0302V_AccountInformation', 0.8017), ('TSBYellowFormShareAllotment', 0.8016)]
+2026-04-17 10:53:10 INFO [agents.orchestrator] orchestrator.py:204 _coarse_filter() | [Orchestrator] 向量粗筛: 命中=20 张(阈值内),表名+分: [('VSBHKRpt0695A', 0.8153), ('VSBHKRpt0365A', 0.8114), ('VSBHKRpt1330', 0.809), ('VSBHKRPT0156', 0.8061), ('VSBHKRpt0672', 0.8053), ('VSBHKRpt0390D', 0.8051), ('VSBHKRPT0590', 0.8046), ('VSBRpt0055U_AccountInformation', 0.8038), ('VSBHKRpt4000A', 0.8028), ('VSBHKRptMSTATInvestmentPortfolioNoHoldings', 0.8026), ('VSBHKRpt1150', 0.8022), ('VSBRpt0055M_AccountInformation', 0.8019), ('VSBRpt0055U_Stock', 0.8017), ('VSBRpt0302V_AccountInformation', 0.8017), ('TSBYellowFormShareAllotment', 0.8016), ('VSBHKRpt0450B', 0.8012), ('VSBHKRpt0390', 0.8009), ('VSBRpt0060', 0.8006), ('TSBAccountEntitlement', 0.8006), ('VSBHKRPT0157', 0.8004)]
+2026-04-17 10:53:14 INFO [agents.orchestrator] orchestrator.py:258 _llm_select_tables() | LLM精筛选中表:['VSBHKRpt0390D', 'VSBHKRpt0390', 'VSBHKRpt0672'] | reasoning_chars=119 reasoning_preview="用户查询账户'M050013'持有的所有股票及其数量,VSBHKRpt0390D和VSBHKRpt0390均为账户持仓相关表,包含账户及其持有的股票信息,VSBHKRpt0672提供账户的基本信息,三者可以通过账户ID关联,满足查询需求。"
+2026-04-17 10:53:14 INFO [agents.orchestrator] orchestrator.py:320 _prioritize_vc_user_accessible_function() | 已优先纳入目录视图 VCUserAccessibleFunction(置于选表前列),当前选表:['VCUserAccessibleFunction', 'VSBHKRpt0390D', 'VSBHKRpt0390', 'VSBHKRpt0672']
+2026-04-17 10:53:14 INFO [agents.orchestrator] orchestrator.py:894 generate() | 选中表:['VCUserAccessibleFunction', 'VSBHKRpt0390D', 'VSBHKRpt0390', 'VSBHKRpt0672'],扩展后:['VSBHKRpt0390D', 'VSBHKRpt0390', 'VSBHKRpt0672', 'VCUserAccessibleFunction']
+2026-04-17 10:53:15 INFO [utils.fewshot_selector] fewshot_selector.py:209 select_best_with_score() | [Few-shot] best_with_score: qid=Q2 score=0.8459 preview='列出今日所有客户账户之间的股票转移记录。'
+2026-04-17 10:53:16 INFO [utils.fewshot_selector] fewshot_selector.py:357 _select_chroma() | Few-shot(Chroma)选择: 问题='查询账户'M050013'持有的所有股票及其数量...' → 选中3个示例 (top_k=3, min_rating=7)
+2026-04-17 10:53:16 INFO [agents.orchestrator] orchestrator.py:568 _generate_sql() | 已注入 3 个 few-shot 示例: qid=['Q2', 'Q32', 'Q40'] question_zh_preview=['列出今日所有客户账户之间的股票转移记录。', '各市场客户不同资产(如股票、债券、基金、虚拟资产)的总持仓是多少?', '列出今日执行的所有股票转移/移动。']
+2026-04-17 10:53:18 INFO [agents.orchestrator] orchestrator.py:635 _generate_sql() | 生成的SQL(chars=169):
+SELECT
+ h.AccountID,
+ h.InstrumentID,
+ h.InstrumentName AS InstrumentSymbol,
+ h.HoldQuantity AS HoldingQty
+FROM VSBHKRpt0390 h
+WHERE h.AccountID = 'M050013';
+2026-04-17 10:53:18 INFO [db.dbhub_tools] dbhub_tools.py:627 _execute_sql() | _execute_sql 执行语句数=1 readonly=True max_rows=1
+2026-04-17 10:53:18 INFO [agents.orchestrator] orchestrator.py:778 _validate_sql() | [validate] 程序+探针+LLM 汇总: valid=True err_count=0 warn_count=0 db_execution_status=1 sql_chars=169
+2026-04-17 10:53:18 INFO [agents.orchestrator] orchestrator.py:956 generate() | [OK] SQL生成与验证通过(1次尝试)
+2026-04-17 10:53:20 INFO [__main__] api_server.py:916 _chat_stream_events() | [API/stream] Text2SQL 完成: valid=True attempts=1 tables_used=['VSBHKRpt0390D', 'VSBHKRpt0390', 'VSBHKRpt0672', 'VCUserAccessibleFunction'] sql_chars=169 sql_head="SELECT\n h.AccountID,\n h.InstrumentID,\n h.InstrumentName AS InstrumentSymbol,\n h.HoldQuantity AS HoldingQty\nFROM VSBHKRpt0390 h\nWHERE h.AccountID = 'M050013';"
+2026-04-17 15:07:22 INFO [uvicorn.error] server.py:272 shutdown() | Shutting down
+2026-04-17 15:07:23 INFO [uvicorn.error] server.py:102 _serve() | Finished server process [38600]
+2026-04-17 15:07:23 ERROR [uvicorn.error] on.py:134 send() | Traceback (most recent call last):
+ File "D:\conda\Lib\asyncio\runners.py", line 194, in run
+ return runner.run(main)
+ ^^^^^^^^^^^^^^^^
+ File "D:\conda\Lib\asyncio\runners.py", line 118, in run
+ return self._loop.run_until_complete(task)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ File "D:\conda\Lib\asyncio\base_events.py", line 674, in run_until_complete
+ self.run_forever()
+ File "D:\conda\Lib\asyncio\windows_events.py", line 322, in run_forever
+ super().run_forever()
+ File "D:\conda\Lib\asyncio\base_events.py", line 641, in run_forever
+ self._run_once()
+ File "D:\conda\Lib\asyncio\base_events.py", line 1986, in _run_once
+ handle._run()
+ File "D:\conda\Lib\asyncio\events.py", line 88, in _run
+ self._context.run(self._callback, *self._args)
+ File "C:\Users\24019\AppData\Roaming\Python\Python312\site-packages\uvicorn\server.py", line 78, in serve
+ with self.capture_signals():
+ ^^^^^^^^^^^^^^^^^^^^^^
+ File "D:\conda\Lib\contextlib.py", line 144, in __exit__
+ next(self.gen)
+ File "C:\Users\24019\AppData\Roaming\Python\Python312\site-packages\uvicorn\server.py", line 339, in capture_signals
+ signal.raise_signal(captured_signal)
+ File "D:\conda\Lib\asyncio\runners.py", line 157, in _on_sigint
+ raise KeyboardInterrupt()
+KeyboardInterrupt
+
+During handling of the above exception, another exception occurred:
+
+Traceback (most recent call last):
+ File "C:\Users\24019\AppData\Roaming\Python\Python312\site-packages\starlette\routing.py", line 645, in lifespan
+ await receive()
+ File "C:\Users\24019\AppData\Roaming\Python\Python312\site-packages\uvicorn\lifespan\on.py", line 137, in receive
+ return await self.receive_queue.get()
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ File "D:\conda\Lib\asyncio\queues.py", line 158, in get
+ await getter
+asyncio.exceptions.CancelledError
+