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 937744a..0b55948 100644 Binary files a/__pycache__/api_server.cpython-312.pyc and b/__pycache__/api_server.cpython-312.pyc differ diff --git a/api_server.py b/api_server.py index 031f1a4..ffecb31 100644 --- a/api_server.py +++ b/api_server.py @@ -150,15 +150,6 @@ class NLChatRequest(BaseModel): session_id: Optional[str] = Field(None, description="会话ID") visitor_biz_id: Optional[str] = Field(None, description="访客业务ID") user_id: Optional[str] = Field(None, description="用户ID") - streaming_throttle: Optional[int] = Field( - None, - description="流式节流:相邻 content 分片之间的间隔毫秒数(0/None 表示不延迟)", - ) - sql_stream_granularity: Optional[str] = Field( - None, - validation_alias=AliasChoices("sql_stream_granularity", "sqlStreamGranularity"), - description="SQL 生成流式分片:delta | char;不传则使用环境变量 SSE_SQL_GEN_SPLIT(默认 char)", - ) class IntentPayload(BaseModel): @@ -254,23 +245,6 @@ def _sse_data(obj: Dict[str, Any]) -> 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 824aa08..35d3e3f 100644 Binary files a/data/embeddings/chroma_fewshot/9756e6ea-6d51-4e24-a490-4a58f0a585b9/length.bin and b/data/embeddings/chroma_fewshot/9756e6ea-6d51-4e24-a490-4a58f0a585b9/length.bin differ diff --git a/logs/text2sql_api.log b/logs/text2sql_api.log index b4d1ded..7874c56 100644 --- a/logs/text2sql_api.log +++ b/logs/text2sql_api.log @@ -1 +1,107 @@ 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 13:35:29 INFO [__main__] api_server.py:85 () | [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;"