diff --git a/.env b/.env index 51a1bd1..4c48eb6 100644 --- a/.env +++ b/.env @@ -13,8 +13,8 @@ MODEL_PRIMARY=deepseek-chat OPENAI_API_KEY=sk-proj-FEb2ChHZK5Llm0tBkmNIT3BlbkFJbu0b0zQpTW8762yD6HDv OPENAI_BASE_URL=http://113.192.49.54:9080/v1 -OPENAI_MODEL=gpt-5.4 -# OPENAI_CHAT_MODEL=gpt-4o-mini +OPENAI_MODEL=gpt-4o-mini +# OPENAI_CHAT_MODEL= # 模型配置 # 主生成模型 diff --git a/__pycache__/api_server.cpython-312.pyc b/__pycache__/api_server.cpython-312.pyc index 0b55948..b190960 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 ffecb31..bb1311b 100644 --- a/api_server.py +++ b/api_server.py @@ -275,6 +275,36 @@ def _conversation_nl_dict(reply: str) -> Dict[str, Any]: } +def _effective_llm_route_from_request(request: NLChatRequest) -> Dict[str, Any]: + """ + 计算“本次请求实际使用的 LLM 路由信息”(用于前端联调回显)。 + 注意:此处仅回显路由结果,不包含密钥等敏感信息。 + """ + model = _normalize_model_label(request.model) + sc = _infer_service_code(request.service_code, model) + + sc2 = sc + if sc2 == "deepseek" and not _has_deepseek_key(): + if _has_openai_key(): + sc2 = "openai" + if sc2 == "openai" and not _has_openai_key(): + if _has_deepseek_key(): + sc2 = "deepseek" + + # 与 _maybe_override_orch_llm 保持一致:网关不兼容时丢弃 model 覆盖,让下游走默认模型 + effective_model = model + if effective_model is not None: + if (sc2 or "").strip().lower() == "openai" and "deepseek" in effective_model.strip().lower(): + effective_model = None + elif (sc2 or "").strip().lower() == "deepseek" and effective_model.strip().lower().startswith("gpt"): + effective_model = None + + return { + "service_code": sc2, + "model": effective_model, + } + + def _normalize_lang_code(code: Optional[str]) -> str: raw = (code or "auto").strip() if not raw: @@ -670,6 +700,7 @@ async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]: async for pkt in _sse_stream_text_chunks("chat", reply): yield pkt data_dict = _conversation_nl_dict(reply) + 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) return @@ -760,6 +791,7 @@ async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]: pass data_dict = _nl_dict_from_generation(result) + 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) @@ -846,6 +878,7 @@ async def nl_chat(request: NLChatRequest): reply = _localized_conversation_reply(lang) logger.info("[API/chat] 对话意图: conversation(跳过 Text2SQL)") data_dict = _conversation_nl_dict(reply) + data_dict["llm"] = _effective_llm_route_from_request(request) await _append_session_if_needed(request, text, data_dict) return {"code": 200, "msg": "success", "data": data_dict} @@ -866,6 +899,7 @@ async def nl_chat(request: NLChatRequest): pass data_dict = _nl_dict_from_generation(result) + data_dict["llm"] = _effective_llm_route_from_request(request) response_data = NLChatSuccessData.model_validate(data_dict) msg = "success" if result.valid else "partial" if result.valid: diff --git a/logs/text2sql_api.log b/logs/text2sql_api.log index 7874c56..087ce80 100644 --- a/logs/text2sql_api.log +++ b/logs/text2sql_api.log @@ -105,3 +105,71 @@ ORDER BY m.ValueDate; 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..loop(<_OverlappedF...210', 55004))>) at D:\conda\Lib\asyncio\proactor_events.py:843 +handle: .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”我还无法判断你的查询需求。'