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.
This commit is contained in:
+6
-6
@@ -639,33 +639,33 @@
|
|||||||
|
|
||||||
## 1. 改动概览
|
## 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`)。
|
- **涉及模块**:`api_server.py`(`/g3sb/api/nl/chat/stream`、`_iter_sql_gen_content_pieces`、`_sse_stream_text_chunks`)。
|
||||||
- **改动类型**:行为调整(默认分片粒度默认更贴近前端的 `char`;可配置回 `delta`)。
|
- **改动类型**:行为调整(默认分片粒度为 `delta`:不做二次切分;如确需更细粒度可配置 `char`)。
|
||||||
|
|
||||||
## 2. 方法级改动
|
## 2. 方法级改动
|
||||||
|
|
||||||
| 位置 | 变更 |
|
| 位置 | 变更 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `NLChatRequest` | 新增可选 `sql_stream_granularity`(`sqlStreamGranularity`);明确 `streaming_throttle` 为相邻 content 间隔毫秒。 |
|
| `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` 支持相同节流。 |
|
| `_chat_stream_events` | sql_gen 循环按粒度拆分后对每条 `data` 可选 `asyncio.sleep(throttle)`;寒暄分支 `_sse_stream_text_chunks` 支持相同节流。 |
|
||||||
|
|
||||||
## 3. 调用方与影响范围
|
## 3. 调用方与影响范围
|
||||||
|
|
||||||
- **调用方**:仅 SSE 流式客户端;非流式 `/g3sb/api/nl/chat` 不变。
|
- **调用方**:仅 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. 配置变更
|
## 4. 配置变更
|
||||||
|
|
||||||
| 环境变量 | 含义 | 默认 |
|
| 环境变量 | 含义 | 默认 |
|
||||||
|----------|------|------|
|
|----------|------|------|
|
||||||
| `SSE_SQL_GEN_SPLIT` | `char`:逐 Unicode 标量多条 SSE;`delta`:与 LLM 增量一致 | `char`(未设置 env 时由代码默认) |
|
| `SSE_SQL_GEN_SPLIT` | `delta`:与 LLM 增量一致;`char`:逐 Unicode 标量多条 SSE | `delta`(未设置 env 时由代码默认) |
|
||||||
|
|
||||||
## 5. 风险与回滚
|
## 5. 风险与回滚
|
||||||
|
|
||||||
- **风险级别**:低。`char` 模式下 SSE 条数增加,带宽与前端拼接次数上升。
|
- **风险级别**:低。`char` 模式下 SSE 条数增加,带宽与前端拼接次数上升。
|
||||||
- **回滚**:设 `SSE_SQL_GEN_SPLIT=delta` 或请求传 `sqlStreamGranularity: "delta"`。
|
- **回滚**:设 `SSE_SQL_GEN_SPLIT=delta` 或请求传 `sqlStreamGranularity: "delta"`(回到默认:不切分)。
|
||||||
|
|
||||||
**回滚方式是否简单**:是。
|
**回滚方式是否简单**:是。
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
+5
-48
@@ -150,15 +150,6 @@ class NLChatRequest(BaseModel):
|
|||||||
session_id: Optional[str] = Field(None, description="会话ID")
|
session_id: Optional[str] = Field(None, description="会话ID")
|
||||||
visitor_biz_id: Optional[str] = Field(None, description="访客业务ID")
|
visitor_biz_id: Optional[str] = Field(None, description="访客业务ID")
|
||||||
user_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):
|
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")
|
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 } 累加
|
# 与前端 chatStore onDelta 一致:多次 { stage, stream_kind: content, content } 累加
|
||||||
_DEFAULT_SSE_CHUNK_CHARS = int(os.getenv("SSE_STREAM_CHUNK_CHARS", "64"))
|
_DEFAULT_SSE_CHUNK_CHARS = int(os.getenv("SSE_STREAM_CHUNK_CHARS", "64"))
|
||||||
|
|
||||||
@@ -280,21 +254,16 @@ async def _sse_stream_text_chunks(
|
|||||||
content: str,
|
content: str,
|
||||||
*,
|
*,
|
||||||
chunk_size: Optional[int] = None,
|
chunk_size: Optional[int] = None,
|
||||||
throttle_ms: Optional[int] = None,
|
|
||||||
) -> AsyncIterator[bytes]:
|
) -> AsyncIterator[bytes]:
|
||||||
"""将长文本拆成多段 SSE,便于浏览器逐段渲染(流式)。"""
|
"""将长文本拆成多段 SSE,便于浏览器逐段渲染(流式)。"""
|
||||||
if not content:
|
if not content:
|
||||||
return
|
return
|
||||||
size = max(8, chunk_size or _DEFAULT_SSE_CHUNK_CHARS)
|
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):
|
for i in range(0, len(content), size):
|
||||||
yield _sse_data(
|
yield _sse_data(
|
||||||
{"stage": stage, "stream_kind": "content", "content": content[i : i + size]}
|
{"stage": stage, "stream_kind": "content", "content": content[i : i + size]}
|
||||||
)
|
)
|
||||||
if delay:
|
await asyncio.sleep(0)
|
||||||
await asyncio.sleep(delay)
|
|
||||||
else:
|
|
||||||
await asyncio.sleep(0)
|
|
||||||
|
|
||||||
|
|
||||||
def _conversation_nl_dict(reply: str) -> Dict[str, Any]:
|
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 ""),
|
reply[:500] + ("…" if len(reply) > 500 else ""),
|
||||||
)
|
)
|
||||||
yield _sse_data({"stage": "orchestrator", "stream_kind": "content", "content": "CHAT"})
|
yield _sse_data({"stage": "orchestrator", "stream_kind": "content", "content": "CHAT"})
|
||||||
async for pkt in _sse_stream_text_chunks(
|
async for pkt in _sse_stream_text_chunks("chat", reply):
|
||||||
"chat", reply, throttle_ms=request.streaming_throttle
|
|
||||||
):
|
|
||||||
yield pkt
|
yield pkt
|
||||||
data_dict = _conversation_nl_dict(reply)
|
data_dict = _conversation_nl_dict(reply)
|
||||||
yield _sse_data({"code": 200, "msg": "success", "data": data_dict})
|
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)
|
loop.call_soon_threadsafe(chunk_queue.put_nowait, None)
|
||||||
|
|
||||||
gen_task = asyncio.create_task(asyncio.to_thread(_run_generate_sync))
|
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:
|
while True:
|
||||||
piece = await chunk_queue.get()
|
piece = await chunk_queue.get()
|
||||||
if piece is None:
|
if piece is None:
|
||||||
break
|
break
|
||||||
for frag in _iter_sql_gen_content_pieces(piece, mode=gran):
|
# 不做二次切分:直接按 LLM 原生 delta 逐条透传给前端
|
||||||
|
if piece:
|
||||||
yield _sse_data(
|
yield _sse_data(
|
||||||
{
|
{
|
||||||
"stage": "sql_gen",
|
"stage": "sql_gen",
|
||||||
"stream_kind": "content",
|
"stream_kind": "content",
|
||||||
"content": frag,
|
"content": piece,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if sql_chunk_delay:
|
|
||||||
await asyncio.sleep(sql_chunk_delay)
|
|
||||||
await gen_task
|
await gen_task
|
||||||
if holder.get("error"):
|
if holder.get("error"):
|
||||||
raise holder["error"]
|
raise holder["error"]
|
||||||
@@ -928,8 +887,6 @@ async def nl_chat_stream(request: NLChatRequest):
|
|||||||
自然语言对话流式接口(SSE)。
|
自然语言对话流式接口(SSE)。
|
||||||
事件体为 JSON:分片 `data: {"stage","stream_kind","content"}`(例:sql_gen 时
|
事件体为 JSON:分片 `data: {"stage","stream_kind","content"}`(例:sql_gen 时
|
||||||
`stream_kind` 为 `content`)或结束包 `{code, msg, data}`。
|
`stream_kind` 为 `content`)或结束包 `{code, msg, data}`。
|
||||||
可选:`sql_stream_granularity` / `sqlStreamGranularity`(delta|char,未传则 `SSE_SQL_GEN_SPLIT`,默认 char)、
|
|
||||||
`streaming_throttle`(相邻 content 分片间隔毫秒)。
|
|
||||||
"""
|
"""
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
_chat_stream_events(request),
|
_chat_stream_events(request),
|
||||||
|
|||||||
Binary file not shown.
@@ -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: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 <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 13:35:29 INFO [__main__] api_server.py:1121 <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 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;"
|
||||||
|
|||||||
Reference in New Issue
Block a user