Update environment configuration to switch OpenAI model from gpt-5.4 to gpt-4o-mini. Introduce _effective_llm_route_from_request function in api_server.py to determine the effective LLM routing based on service keys and model compatibility. Update chat event handling to include LLM routing information in response data. Enhance logging for better traceability of API interactions.

This commit is contained in:
陈辅元
2026-04-16 15:49:03 +08:00
parent be15b512a4
commit 70c8e90f2e
4 changed files with 104 additions and 2 deletions
+2 -2
View File
@@ -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=
# 模型配置
# 主生成模型
Binary file not shown.
+34
View File
@@ -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:
+68
View File
@@ -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.<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”我还无法判断你的查询需求。'