Refactor _chat_stream_events in api_server.py to standardize SSE response format by removing additional status codes and consolidating output into a unified JSON structure. Update logging to enhance clarity on streaming events and improve error handling. Adjust documentation to reflect changes in event data structure.

This commit is contained in:
陈辅元
2026-04-16 16:26:58 +08:00
parent 70c8e90f2e
commit f492e29749
4 changed files with 59 additions and 169 deletions
Binary file not shown.
+7 -10
View File
@@ -659,7 +659,9 @@ async def _run_generate(
async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]: async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]:
if not request.message or not request.message.strip(): if not request.message or not request.message.strip():
lang = _normalize_lang_code(request.lang_code) lang = _normalize_lang_code(request.lang_code)
yield _sse_data({"code": 400, "msg": _localized_empty_input_reply(lang), "data": None}) yield _sse_data(
{"stage": "sql_gen", "stream_kind": "content", "content": _localized_empty_input_reply(lang)}
)
return return
text = request.message.strip() text = request.message.strip()
orch = get_orchestrator() orch = get_orchestrator()
@@ -696,16 +698,13 @@ async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]:
len(reply), len(reply),
reply[:500] + ("…" if len(reply) > 500 else ""), reply[:500] + ("…" if len(reply) > 500 else ""),
) )
yield _sse_data({"stage": "orchestrator", "stream_kind": "content", "content": "CHAT"}) async for pkt in _sse_stream_text_chunks("sql_gen", reply):
async for pkt in _sse_stream_text_chunks("chat", reply):
yield pkt yield pkt
data_dict = _conversation_nl_dict(reply) data_dict = _conversation_nl_dict(reply)
data_dict["llm"] = _effective_llm_route_from_request(request) data_dict["llm"] = _effective_llm_route_from_request(request)
yield _sse_data({"code": 200, "msg": "success", "data": data_dict})
await _append_session_if_needed(request, text, data_dict) await _append_session_if_needed(request, text, data_dict)
return return
yield _sse_data({"stage": "orchestrator", "stream_kind": "content", "content": "DATA_QUERY"})
dialect = resolve_sql_dialect(os.getenv("TEXT2SQL_DIALECT", "sqlserver")) dialect = resolve_sql_dialect(os.getenv("TEXT2SQL_DIALECT", "sqlserver"))
dc_ctx = (dialog_block or "").strip() or None dc_ctx = (dialog_block or "").strip() or None
top_k = 20 top_k = 20
@@ -759,7 +758,7 @@ async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]:
result = holder["result"] result = holder["result"]
except Exception as e: except Exception as e:
logger.error(f"[API/stream] 生成异常: {e}", exc_info=True) logger.error(f"[API/stream] 生成异常: {e}", exc_info=True)
yield _sse_data({"code": 500, "msg": str(e), "data": None}) yield _sse_data({"stage": "sql_gen", "stream_kind": "content", "content": str(e)})
return return
sql_out = (result.sql or "").strip() sql_out = (result.sql or "").strip()
@@ -792,8 +791,6 @@ async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]:
data_dict = _nl_dict_from_generation(result) data_dict = _nl_dict_from_generation(result)
data_dict["llm"] = _effective_llm_route_from_request(request) data_dict["llm"] = _effective_llm_route_from_request(request)
msg = "success" if result.valid else "partial"
yield _sse_data({"code": 200, "msg": msg, "data": data_dict})
await _append_session_if_needed(request, text, data_dict) await _append_session_if_needed(request, text, data_dict)
@@ -919,8 +916,8 @@ async def nl_chat(request: NLChatRequest):
async def nl_chat_stream(request: NLChatRequest): async def nl_chat_stream(request: NLChatRequest):
""" """
自然语言对话流式接口(SSE)。 自然语言对话流式接口(SSE)。
事件体为 JSON:分片 `data: {"stage","stream_kind","content"}`(例:sql_gen 时 事件体为 JSON:仅发送分片 `data: {"stage":"sql_gen","stream_kind":"content","content":"..."}`,
`stream_kind` 为 `content`)或结束包 `{code, msg, data}`。 流结束表示完成;不再发送额外的结束包。
""" """
return StreamingResponse( return StreamingResponse(
_chat_stream_events(request), _chat_stream_events(request),
+52 -159
View File
@@ -1,50 +1,50 @@
2026-04-16 13:35:24 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: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 13:35:29 INFO [__main__] api_server.py:85 <module>() | [OK] 已加载配置文件: C:\Users\24019\Desktop\backman-camel\.env 2026-04-16 16:22:10 INFO [__main__] api_server.py:85 <module>() | [OK] 已加载配置文件: C:\Users\24019\Desktop\backman-camel\.env
2026-04-16 13:35:29 INFO [__main__] api_server.py:1120 <module>() | 启动服务: http://0.0.0.0:8041 2026-04-16 16:22:10 INFO [__main__] api_server.py:1108 <module>() | 启动服务: http://0.0.0.0:8041
2026-04-16 13:35:29 INFO [__main__] api_server.py:1121 <module>() | API文档: http://0.0.0.0:8041/docs 2026-04-16 16:22:10 INFO [__main__] api_server.py:1109 <module>() | API文档: http://0.0.0.0:8041/docs
2026-04-16 13:35:29 INFO [uvicorn.error] server.py:92 _serve() | Started server process [37328] 2026-04-16 16:22:10 INFO [uvicorn.error] server.py:92 _serve() | Started server process [33080]
2026-04-16 13:35:29 INFO [uvicorn.error] on.py:48 startup() | Waiting for application startup. 2026-04-16 16:22:10 INFO [uvicorn.error] on.py:48 startup() | Waiting for application startup.
2026-04-16 13:35:29 INFO [__main__] api_server.py:811 lifespan() | ============================================================ 2026-04-16 16:22:10 INFO [__main__] api_server.py:799 lifespan() | ============================================================
2026-04-16 13:35:29 INFO [__main__] api_server.py:812 lifespan() | Text2SQL API Server 启动中... 2026-04-16 16:22:10 INFO [__main__] api_server.py:800 lifespan() | Text2SQL API Server 启动中...
2026-04-16 13:35:29 INFO [__main__] api_server.py:813 lifespan() | ============================================================ 2026-04-16 16:22:10 INFO [__main__] api_server.py:801 lifespan() | ============================================================
2026-04-16 13:35:29 INFO [main] main.py:126 setup_environment() | [OK] 环境检查通过 2026-04-16 16:22:10 INFO [main] main.py:126 setup_environment() | [OK] 环境检查通过
2026-04-16 13:35:29 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:127 setup_environment() | - Schema: data\schemas\G3SB_MCDataDictionary_table_structure.json
2026-04-16 13:35:29 INFO [main] main.py:128 setup_environment() | - LLM: openai 2026-04-16 16:22:10 INFO [main] main.py:128 setup_environment() | - LLM: openai
2026-04-16 13:35:29 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:154 load_schema() | 加载Schema: ./data/schemas/G3SB_MCDataDictionary_table_structure.json
2026-04-16 13:35:29 INFO [main] main.py:156 load_schema() | 表注释(meta): ./data/schemas/G3SB_MCDataDictionary_table_meta.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 13:35:30 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 [schema.loader] loader.py:107 load_from_json() | [OK] 加载Schema完成(G3SB schemas): G3SB_MCDataDictionary_table_structure, 共2516张表
2026-04-16 13:35:30 INFO [main] main.py:163 load_schema() | [OK] Schema加载完成: G3SB_MCDataDictionary_table_structure, 共2516张表, 59196个字段 2026-04-16 16:22:10 INFO [main] main.py:163 load_schema() | [OK] Schema加载完成: G3SB_MCDataDictionary_table_structure, 共2516张表, 59196个字段
2026-04-16 13:35:33 INFO [llm.openai_client] openai_client.py:55 __init__() | [OK] OpenAIClient初始化: model=gpt-5.4, base_url=http://113.192.49.54:9080/v1 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 13:35:35 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: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 13:35:37 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_chroma_store] fewshot_chroma_store.py:86 __init__() | [OK] FewShotChromaStore: 磁盘 data\embeddings\chroma_fewshot collection=fewshot_samples count=50
2026-04-16 13:35:37 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:128 _init_chroma_mode() | [Few-shot] 已从向量库加载(Chroma 50 条,data\embeddings\chroma_fewshot)
2026-04-16 13:35:37 INFO [utils.fewshot_selector] fewshot_selector.py:159 _init_chroma_mode() | [OK] Few-shot 使用 Chroma(50 条) 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 13:35:37 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:116 __init__() | Few-shot已启用: top_k=3, min_rating=7
2026-04-16 13:35:37 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 [agents.orchestrator] orchestrator.py:127 __init__() | [OK] Text2SQLOrchestrator初始化完成: max_retry=2, use_vector_search=True, fewshot=on, nl→zh_norm=on
2026-04-16 13:35:37 INFO [__main__] api_server.py:132 get_orchestrator() | [OK] Orchestrator 初始化完成 2026-04-16 16:22:19 INFO [__main__] api_server.py:132 get_orchestrator() | [OK] Orchestrator 初始化完成
2026-04-16 13:35:37 INFO [__main__] api_server.py:817 lifespan() | [OK] 服务已就绪 2026-04-16 16:22:19 INFO [__main__] api_server.py:805 lifespan() | [OK] 服务已就绪
2026-04-16 13:35:37 INFO [uvicorn.error] on.py:62 startup() | Application startup complete. 2026-04-16 16:22:19 INFO [uvicorn.error] on.py:62 startup() | Application startup complete.
2026-04-16 13:35:37 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: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 13:35:52 INFO [uvicorn.access] httptools_impl.py:483 send() | 127.0.0.1:23615 - "POST /g3sb/api/nl/chat/stream HTTP/1.1" 200 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 13:35:52 INFO [__main__] api_server.py:668 _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: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 13:35:55 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 [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 13:35:55 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 [utils.dialog_classifier] dialog_classifier.py:261 classify_dialog() | [dialog] intent=text2sql (hybrid fast: query hint) preview='所有客户账户之间的股票转移记录'
2026-04-16 13:35:55 INFO [__main__] api_server.py:715 _chat_stream_events() | [GEN/API/stream] dialect=tsql top_k=20 dialog_context_chars=0 question_len=15 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 13:35:57 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: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 13:36:00 INFO [agents.orchestrator] orchestrator.py:803 generate() | [GEN] 问句已归一中文:所有客户账户的股票转移记录 2026-04-16 16:22:29 INFO [agents.orchestrator] orchestrator.py:803 generate() | [GEN] 问句已归一中文:所有客户账户的股票转移记录
2026-04-16 13:36:00 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:816 generate() | [GEN] 开始生成SQL: question_chars=13 preview='所有客户账户的股票转移记录' dialog_context_chars=0
2026-04-16 13:36:00 INFO [agents.orchestrator] orchestrator.py:831 generate() | 尝试 #1 2026-04-16 16:22:29 INFO [agents.orchestrator] orchestrator.py:831 generate() | 尝试 #1
2026-04-16 13:36:00 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:82 __init__() | [OK] 初始化SchemaIndexer(Chroma磁盘 path=data\embeddings\chroma): collection=schema_tables, count=2516
2026-04-16 13:36:00 INFO [schema.indexer] indexer.py:106 ensure_index_for_schema() | Schema 向量索引已就绪(2516 张表),跳过向量化 2026-04-16 16:22:29 INFO [schema.indexer] indexer.py:106 ensure_index_for_schema() | Schema 向量索引已就绪(2516 张表),跳过向量化
2026-04-16 13:36:00 INFO [agents.orchestrator] orchestrator.py:188 _coarse_filter() | [Orchestrator] 开始向量检索: query_chars=13 query_preview='所有客户账户的股票转移记录' 2026-04-16 16:22:29 INFO [agents.orchestrator] orchestrator.py:188 _coarse_filter() | [Orchestrator] 开始向量检索: query_chars=13 query_preview='所有客户账户的股票转移记录'
2026-04-16 13:36:02 INFO [utils.embedding] embedding.py:163 _set_dim_from_vector() | [OK] Embedding 向量维度:1536 2026-04-16 16:22:30 INFO [utils.embedding] embedding.py:163 _set_dim_from_vector() | [OK] Embedding 向量维度:1536
2026-04-16 13:36:03 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 [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 13:36:03 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: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 13:36:07 INFO [agents.orchestrator] orchestrator.py:258 _llm_select_tables() | LLM精筛选中表:['VSBHKRpt0430', 'VSBHKRpt0431', 'TSBTransferInstruction', 'VSBTransferInstruction', 'TSBAccountInstrumentMovement'] | reasoning_chars=181 reasoning_preview='问题涉及客户账户的股票转移记录,VSBHKRpt0430和VSBHKRpt0431提供了客户股票的日常转移报告和账户工具移动报告,TSBTransferInstruction和VSBTransferInstruction则记录了账户转移指令,TSBAccountInstrumentMovement包含账户工具的移动交易信息,这些表共同覆盖了股票转移的各个方面。' 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 13:36:07 INFO [agents.orchestrator] orchestrator.py:859 generate() | 选中表:['VSBHKRpt0430', 'VSBHKRpt0431', 'TSBTransferInstruction', 'VSBTransferInstruction', 'TSBAccountInstrumentMovement'],扩展后:['TSBTransferInstruction', 'VSBTransferInstruction', 'VSBHKRpt0431', 'VSBHKRpt0430', '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 13:36:08 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 [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 13:36:08 INFO [agents.orchestrator] orchestrator.py:432 _generate_sql_golden_adapt() | [GEN] 黄金 few-shot 条件适配: qid=Q2 score=0.9575 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 13:36:12 INFO [agents.orchestrator] orchestrator.py:450 _generate_sql_golden_adapt() | 生成的SQL(黄金适配,chars=494): 2026-04-16 16:22:41 INFO [agents.orchestrator] orchestrator.py:450 _generate_sql_golden_adapt() | 生成的SQL(黄金适配,chars=494):
-- 查询所有客户账户间股票转移记录 -- 查询所有客户账户间股票转移记录
-- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移 -- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移
SELECT SELECT
@@ -61,115 +61,8 @@ LEFT JOIN MCInstrument i ON m.InstrumentID = i.InstrumentID
WHERE m.MovementType = 'T' WHERE m.MovementType = 'T'
AND m.ValueDate = CAST(GETDATE() AS DATE) AND m.ValueDate = CAST(GETDATE() AS DATE)
ORDER BY m.ValueDate; ORDER BY m.ValueDate;
2026-04-16 13:36:12 INFO [db.engine] engine.py:45 get_engine() | SQLAlchemy engine initialized from database_url 2026-04-16 16:22:41 INFO [db.engine] engine.py:45 get_engine() | SQLAlchemy engine initialized from database_url
2026-04-16 13:36:12 INFO [db.dbhub_tools] dbhub_tools.py:627 _execute_sql() | _execute_sql 执行语句数=1 readonly=True max_rows=1 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 13:36:15 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:754 _validate_sql() | [validate] 程序+探针+LLM 汇总: valid=True err_count=0 warn_count=0 db_execution_status=0 sql_chars=494
2026-04-16 13:36:15 INFO [agents.orchestrator] orchestrator.py:921 generate() | [OK] SQL生成与验证通过(1次尝试) 2026-04-16 16:22:44 INFO [agents.orchestrator] orchestrator.py:921 generate() | [OK] SQL生成与验证通过(1次尝试)
2026-04-16 13:36:15 INFO [__main__] api_server.py:776 _chat_stream_events() | [API/stream] Text2SQL 完成: valid=True attempts=1 tables_used=['TSBTransferInstruction', 'VSBTransferInstruction', 'VSBHKRpt0431', 'VSBHKRpt0430', 'TSBAccountInstrumentMovement'] 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-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-16 13:47:46 INFO [uvicorn.access] httptools_impl.py:483 send() | 127.0.0.1:27791 - "POST /g3sb/api/nl/chat/stream HTTP/1.1" 200
2026-04-16 13:47:46 INFO [__main__] api_server.py:668 _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 13:47:49 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 13:47:49 INFO [utils.dialog_classifier] dialog_classifier.py:261 classify_dialog() | [dialog] intent=text2sql (hybrid fast: query hint) preview='所有客户账户之间的股票转移记录'
2026-04-16 13:47:49 INFO [__main__] api_server.py:715 _chat_stream_events() | [GEN/API/stream] dialect=tsql top_k=20 dialog_context_chars=0 question_len=15 preview='所有客户账户之间的股票转移记录'
2026-04-16 13:47:52 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 13:47:54 INFO [agents.orchestrator] orchestrator.py:803 generate() | [GEN] 问句已归一中文:所有客户账户的股票转移记录
2026-04-16 13:47:54 INFO [agents.orchestrator] orchestrator.py:816 generate() | [GEN] 开始生成SQL: question_chars=13 preview='所有客户账户的股票转移记录' dialog_context_chars=0
2026-04-16 13:47:54 INFO [agents.orchestrator] orchestrator.py:831 generate() | 尝试 #1
2026-04-16 13:47:54 INFO [schema.indexer] indexer.py:106 ensure_index_for_schema() | Schema 向量索引已就绪(2516 张表),跳过向量化
2026-04-16 13:47:54 INFO [agents.orchestrator] orchestrator.py:188 _coarse_filter() | [Orchestrator] 开始向量检索: query_chars=13 query_preview='所有客户账户的股票转移记录'
2026-04-16 13:47:56 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 13:47:56 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 13:48:00 INFO [agents.orchestrator] orchestrator.py:258 _llm_select_tables() | LLM精筛选中表:['VSBHKRpt0430', 'VSBHKRpt0431', 'TSBTransferInstruction', 'VSBTransferInstruction', 'TSBAccountInstrumentMovement'] | reasoning_chars=182 reasoning_preview='问题涉及客户账户的股票转移记录,VSBHKRpt0430和VSBHKRpt0431提供了客户股票的日常转移报告和账户工具移动报告,TSBTransferInstruction和VSBTransferInstruction包含账户转移指令的详细信息,TSBAccountInstrumentMovement记录账户工具的移动交易。这些表共同涵盖了股票转移的相关信息。'
2026-04-16 13:48:00 INFO [agents.orchestrator] orchestrator.py:859 generate() | 选中表:['VSBHKRpt0430', 'VSBHKRpt0431', 'TSBTransferInstruction', 'VSBTransferInstruction', 'TSBAccountInstrumentMovement'],扩展后:['TSBTransferInstruction', 'VSBTransferInstruction', 'VSBHKRpt0431', 'VSBHKRpt0430', 'TSBAccountInstrumentMovement']
2026-04-16 13:48:01 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 13:48:01 INFO [agents.orchestrator] orchestrator.py:432 _generate_sql_golden_adapt() | [GEN] 黄金 few-shot 条件适配: qid=Q2 score=0.9575
2026-04-16 13:48:06 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 13:48:06 INFO [db.dbhub_tools] dbhub_tools.py:627 _execute_sql() | _execute_sql 执行语句数=1 readonly=True max_rows=1
2026-04-16 13:48:08 INFO [openai._base_client] _base_client.py:1111 _sleep_for_retry() | Retrying request to /chat/completions in 0.481978 seconds
2026-04-16 13:48:13 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 13:48:13 INFO [agents.orchestrator] orchestrator.py:921 generate() | [OK] SQL生成与验证通过(1次尝试)
2026-04-16 13:48:13 INFO [__main__] api_server.py:776 _chat_stream_events() | [API/stream] Text2SQL 完成: valid=True attempts=1 tables_used=['TSBTransferInstruction', 'VSBTransferInstruction', 'VSBHKRpt0431', 'VSBHKRpt0430', 'TSBAccountInstrumentMovement'] 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-16 14:40:07 INFO [uvicorn.access] httptools_impl.py:483 send() | 192.168.3.210:53090 - "POST /g3sb/api/nl/chat/stream HTTP/1.1" 200
2026-04-16 14:50:35 INFO [__main__] api_server.py:668 _chat_stream_events() | [API/stream] 开始: user_id='ACCOUNT' visitor_biz_id=None session_id='a68b69c0-d945-4553-869f-3538d42900f9' service_code='openai' model=None lang_code='zh' msg_chars=3 preview='123' dialog_context_chars=0 last_turn_was_data_query=False
2026-04-16 14:50:38 INFO [llm.openai_client] openai_client.py:55 __init__() | [OK] OpenAIClient初始化: model=gpt-5.4, base_url=http://113.192.49.54:9080/v1
2026-04-16 14:50:38 INFO [uvicorn.error] server.py:272 shutdown() | Shutting down
2026-04-16 14:50:38 ERROR [asyncio] base_events.py:1820 default_exception_handler() | Exception in callback BaseProactorEventLoop._start_serving.<locals>.loop(<_OverlappedF...210', 55004))>) at D:\conda\Lib\asyncio\proactor_events.py:843
handle: <Handle BaseProactorEventLoop._start_serving.<locals>.loop(<_OverlappedF...210', 55004))>) at D:\conda\Lib\asyncio\proactor_events.py:843>
Traceback (most recent call last):
File "D:\conda\Lib\asyncio\events.py", line 88, in _run
self._context.run(self._callback, *self._args)
File "D:\conda\Lib\asyncio\proactor_events.py", line 858, in loop
self._make_socket_transport(
File "D:\conda\Lib\asyncio\proactor_events.py", line 647, in _make_socket_transport
return _ProactorSocketTransport(self, sock, protocol, waiter,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\conda\Lib\asyncio\proactor_events.py", line 613, in __init__
super().__init__(loop, sock, protocol, waiter, extra, server)
File "D:\conda\Lib\asyncio\proactor_events.py", line 189, in __init__
super().__init__(loop, sock, protocol, waiter, extra, server)
File "D:\conda\Lib\asyncio\proactor_events.py", line 335, in __init__
super().__init__(*args, **kw)
File "D:\conda\Lib\asyncio\proactor_events.py", line 66, in __init__
self._server._attach()
File "D:\conda\Lib\asyncio\base_events.py", line 296, in _attach
assert self._sockets is not None
^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
2026-04-16 14:50:38 INFO [uvicorn.error] server.py:102 _serve() | Finished server process [37328]
2026-04-16 14:50:38 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
2026-04-16 14:50:43 INFO [utils.dialog_classifier] dialog_classifier.py:211 _classify_dialog_llm() | [dialog] intent=conversation (LLM) reply_chars=51 reply_preview='请具体说明你想查询什么数据,例如时间范围、品种、账户或指标。仅输入“123”我还无法判断你的查询需求。'