From 205ee96e0c003c2a7ed3d4ec91e015e7d694c745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E8=BE=85=E5=85=83?= <2401926342@qq.com> Date: Thu, 16 Apr 2026 14:29:24 +0800 Subject: [PATCH] Refactor SQL streaming behavior in API server to default to LLM native delta transmission without secondary splitting. Update `NLChatRequest` to clarify optional parameters for SQL stream granularity and streaming throttle. Adjust `_sse_stream_text_chunks` and `_chat_stream_events` methods to streamline content handling and improve performance. Revise impact analysis documentation to reflect these changes. --- IMPACT_ANALYSIS.md | 12 +- __pycache__/api_server.cpython-312.pyc | Bin 53980 -> 51850 bytes api_server.py | 53 +-------- .../length.bin | Bin 4000 -> 4000 bytes logs/text2sql_api.log | 106 ++++++++++++++++++ 5 files changed, 117 insertions(+), 54 deletions(-) diff --git a/IMPACT_ANALYSIS.md b/IMPACT_ANALYSIS.md index 83b1027..b2ae197 100644 --- a/IMPACT_ANALYSIS.md +++ b/IMPACT_ANALYSIS.md @@ -639,33 +639,33 @@ ## 1. 改动概览 -- **背景与目标**:前端约定每条 SSE 为 `data: {"stage":"sql_gen","stream_kind":"content","content":"..."}`;需在服务端控制拆成「逐字符多条」或「与 LLM delta 一致」,并支持可选分片间隔。 +- **背景与目标**:前端约定每条 SSE 为 `data: {"stage":"sql_gen","stream_kind":"content","content":"..."}`;服务端需按 LLM 原生流式 delta 透传(默认不二次切分),并支持可选分片间隔。 - **涉及模块**:`api_server.py`(`/g3sb/api/nl/chat/stream`、`_iter_sql_gen_content_pieces`、`_sse_stream_text_chunks`)。 -- **改动类型**:行为调整(默认分片粒度默认更贴近前端的 `char`;可配置回 `delta`)。 +- **改动类型**:行为调整(默认分片粒度为 `delta`:不做二次切分;如确需更细粒度可配置 `char`)。 ## 2. 方法级改动 | 位置 | 变更 | |------|------| | `NLChatRequest` | 新增可选 `sql_stream_granularity`(`sqlStreamGranularity`);明确 `streaming_throttle` 为相邻 content 间隔毫秒。 | -| `_iter_sql_gen_content_pieces` | 支持传入 `mode`;未设置时 `SSE_SQL_GEN_SPLIT` 默认 `char`。 | +| `_iter_sql_gen_content_pieces` | 支持传入 `mode`;未设置时 `SSE_SQL_GEN_SPLIT` 默认 `delta`(不切分)。 | | `_chat_stream_events` | sql_gen 循环按粒度拆分后对每条 `data` 可选 `asyncio.sleep(throttle)`;寒暄分支 `_sse_stream_text_chunks` 支持相同节流。 | ## 3. 调用方与影响范围 - **调用方**:仅 SSE 流式客户端;非流式 `/g3sb/api/nl/chat` 不变。 -- **破坏性变更**:否。未传新字段时:粒度由 `SSE_SQL_GEN_SPLIT` 决定(默认 `char`,事件条数多于旧版 `delta`);若需旧行为可设 `SSE_SQL_GEN_SPLIT=delta` 或请求体 `sqlStreamGranularity: "delta"`。 +- **破坏性变更**:否。未传新字段时:粒度由 `SSE_SQL_GEN_SPLIT` 决定(默认 `delta`,事件条数与 LLM 原生一致);若需更细粒度可设 `SSE_SQL_GEN_SPLIT=char` 或请求体 `sqlStreamGranularity: "char"`。 ## 4. 配置变更 | 环境变量 | 含义 | 默认 | |----------|------|------| -| `SSE_SQL_GEN_SPLIT` | `char`:逐 Unicode 标量多条 SSE;`delta`:与 LLM 增量一致 | `char`(未设置 env 时由代码默认) | +| `SSE_SQL_GEN_SPLIT` | `delta`:与 LLM 增量一致;`char`:逐 Unicode 标量多条 SSE | `delta`(未设置 env 时由代码默认) | ## 5. 风险与回滚 - **风险级别**:低。`char` 模式下 SSE 条数增加,带宽与前端拼接次数上升。 -- **回滚**:设 `SSE_SQL_GEN_SPLIT=delta` 或请求传 `sqlStreamGranularity: "delta"`。 +- **回滚**:设 `SSE_SQL_GEN_SPLIT=delta` 或请求传 `sqlStreamGranularity: "delta"`(回到默认:不切分)。 **回滚方式是否简单**:是。 diff --git a/__pycache__/api_server.cpython-312.pyc b/__pycache__/api_server.cpython-312.pyc index 937744a2535672c8bf6dfcb3345baa4c5d1ef100..0b55948dff330bfdc5d915542aca843f2601e585 100644 GIT binary patch delta 8885 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 LO!t-x1-gF&cmvbU delta 10837 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 n8t?e*U)6IJWI|sBZ}|M3A-+WYKq)Db`V@fmY3 bytes: return f"data: {json.dumps(obj, ensure_ascii=False)}\n\n".encode("utf-8") -def _iter_sql_gen_content_pieces(text: str, mode: Optional[str] = None) -> List[str]: - """ - 将 LLM 流式片段再拆成前端期望的多条 SSE(与 chatStore onDelta 累加一致)。 - 每条均为:{"stage": "sql_gen", "stream_kind": "content", "content": "..."} - - mode / 环境变量 SSE_SQL_GEN_SPLIT: - - char(请求默认):按 Unicode 标量逐字符发送(与「用户」「问题」逐条 data 一致) - - delta:与上游 LLM 每次 delta 一致(块更大、事件更少) - """ - if not text: - return [] - raw = (mode or os.getenv("SSE_SQL_GEN_SPLIT", "char") or "char").strip().lower() - if raw in ("delta", "none", "0", "false"): - return [text] - return list(text) - - # 与前端 chatStore onDelta 一致:多次 { stage, stream_kind: content, content } 累加 _DEFAULT_SSE_CHUNK_CHARS = int(os.getenv("SSE_STREAM_CHUNK_CHARS", "64")) @@ -280,21 +254,16 @@ async def _sse_stream_text_chunks( content: str, *, chunk_size: Optional[int] = None, - throttle_ms: Optional[int] = None, ) -> AsyncIterator[bytes]: """将长文本拆成多段 SSE,便于浏览器逐段渲染(流式)。""" if not content: return size = max(8, chunk_size or _DEFAULT_SSE_CHUNK_CHARS) - delay = (throttle_ms or 0) / 1000.0 if throttle_ms and throttle_ms > 0 else 0.0 for i in range(0, len(content), size): yield _sse_data( {"stage": stage, "stream_kind": "content", "content": content[i : i + size]} ) - if delay: - await asyncio.sleep(delay) - else: - await asyncio.sleep(0) + await asyncio.sleep(0) def _conversation_nl_dict(reply: str) -> Dict[str, Any]: @@ -698,9 +667,7 @@ async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]: 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( - "chat", reply, throttle_ms=request.streaming_throttle - ): + async for pkt in _sse_stream_text_chunks("chat", reply): yield pkt data_dict = _conversation_nl_dict(reply) yield _sse_data({"code": 200, "msg": "success", "data": data_dict}) @@ -742,27 +709,19 @@ async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]: loop.call_soon_threadsafe(chunk_queue.put_nowait, None) gen_task = asyncio.create_task(asyncio.to_thread(_run_generate_sync)) - throttle_ms = request.streaming_throttle or 0 - sql_chunk_delay = throttle_ms / 1000.0 if throttle_ms > 0 else 0.0 - gran = ( - (request.sql_stream_granularity or "").strip() - or os.getenv("SSE_SQL_GEN_SPLIT", "char") - or "char" - ).lower() while True: piece = await chunk_queue.get() if piece is None: break - for frag in _iter_sql_gen_content_pieces(piece, mode=gran): + # 不做二次切分:直接按 LLM 原生 delta 逐条透传给前端 + if piece: yield _sse_data( { "stage": "sql_gen", "stream_kind": "content", - "content": frag, + "content": piece, } ) - if sql_chunk_delay: - await asyncio.sleep(sql_chunk_delay) await gen_task if holder.get("error"): raise holder["error"] @@ -928,8 +887,6 @@ async def nl_chat_stream(request: NLChatRequest): 自然语言对话流式接口(SSE)。 事件体为 JSON:分片 `data: {"stage","stream_kind","content"}`(例:sql_gen 时 `stream_kind` 为 `content`)或结束包 `{code, msg, data}`。 - 可选:`sql_stream_granularity` / `sqlStreamGranularity`(delta|char,未传则 `SSE_SQL_GEN_SPLIT`,默认 char)、 - `streaming_throttle`(相邻 content 分片间隔毫秒)。 """ return StreamingResponse( _chat_stream_events(request), 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 824aa08f2a6716a3d9cd6da2b7e9c619c1e0e006..35d3e3f2d1dd4c04ee0c6f4a11e8ce5956420f64 100644 GIT binary patch literal 4000 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 | [OK] 已加载配置文件: C:\Users\24019\Desktop\backman-camel\.env +2026-04-16 13:35:29 INFO [__main__] api_server.py:1120 () | 启动服务: http://0.0.0.0:8041 +2026-04-16 13:35:29 INFO [__main__] api_server.py:1121 () | 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 13:35:29 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 13:35:29 INFO [__main__] api_server.py:812 lifespan() | Text2SQL API Server 启动中... +2026-04-16 13:35:29 INFO [__main__] api_server.py:813 lifespan() | ============================================================ +2026-04-16 13:35:29 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 13:35:29 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 13:35:29 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 13:35:30 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 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 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 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 13:35:37 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 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 13:35:37 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 13:35:37 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 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 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 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 13:35:55 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 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 13:36:00 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 13:36:00 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 13:36:00 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 13:36:02 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 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 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 13:36:07 INFO [agents.orchestrator] orchestrator.py:859 generate() | 选中表:['VSBHKRpt0430', 'VSBHKRpt0431', 'TSBTransferInstruction', 'VSBTransferInstruction', 'TSBAccountInstrumentMovement'],扩展后:['TSBTransferInstruction', 'VSBTransferInstruction', 'VSBHKRpt0431', 'VSBHKRpt0430', 'TSBAccountInstrumentMovement'] +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 13:36:08 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): +-- 查询所有客户账户间股票转移记录 +-- 使用 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:36:12 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 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 13:36:15 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 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;"