From 7023742880684169274772188412ff725e7685ec Mon Sep 17 00:00:00 2001 From: ymq Date: Sun, 30 Aug 2026 15:07:34 +0800 Subject: [PATCH] =?UTF-8?q?fix(llm):=20=E6=A8=A1=E5=9E=8B=E4=B8=8D?= =?UTF-8?q?=E5=8F=AF=E7=94=A8=E6=8A=A5=E7=9C=9F=E5=AE=9E=E9=94=99=E8=AF=AF?= =?UTF-8?q?+name/model=5Fid=E5=8F=8C=E5=8C=B9=E9=85=8D+org=E8=AF=AD?= =?UTF-8?q?=E4=B9=89=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _no_llm_error: 错误写明模型名/原因/行动指引,禁止笼统No LLM API configured - _get_model_config: name/model_id 双匹配(与gateway解析语义一致),org过滤含系统级共享 - agent_loop_v2 run(): LLM调用异常捕获→yield error事件,前端显示真实错误不再卡死思考中 --- ... for v in w: walk(v)\nwalk(d)\n\\\"\"" | 0 pipeline_service/agent_loop_v2.py | 9 +++++- pipeline_service/llm_bridge.py | 32 ++++++++++++++++--- 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 ", i.get(script,)[:80])\n for v in w.values(): walk(v)\n elif isinstance(w, list):\n for v in w: walk(v)\nwalk(d)\n\\\"\"" diff --git "a/, i.get(script,)[:80])\n for v in w.values(): walk(v)\n elif isinstance(w, list):\n for v in w: walk(v)\nwalk(d)\n\\\"\"" "b/, i.get(script,)[:80])\n for v in w.values(): walk(v)\n elif isinstance(w, list):\n for v in w: walk(v)\nwalk(d)\n\\\"\"" new file mode 100644 index 0000000..e69de29 diff --git a/pipeline_service/agent_loop_v2.py b/pipeline_service/agent_loop_v2.py index 7bb768f..8c16617 100644 --- a/pipeline_service/agent_loop_v2.py +++ b/pipeline_service/agent_loop_v2.py @@ -198,7 +198,14 @@ class AgentExecutor: await self._maybe_compress() # 5b. LLM 调用(native function calling,返回 dict) - resp = await self._call_llm() + # 模型不可用等配置错误必须真实报给用户(error 事件), + # 绝不能让异常打断流——前端会停在"思考中..."干等。 + try: + resp = await self._call_llm() + except Exception as e: + logger.error(f"run: LLM 调用失败 turn={turn+1}: {e}") + yield json.dumps({"type": "error", "message": str(e)}, ensure_ascii=False) + "\n" + return # 5c. 原生 function calling:优先处理 tool_calls native_calls = (resp.get("tool_calls") or []) if isinstance(resp, dict) else [] diff --git a/pipeline_service/llm_bridge.py b/pipeline_service/llm_bridge.py index f333856..53ed366 100644 --- a/pipeline_service/llm_bridge.py +++ b/pipeline_service/llm_bridge.py @@ -50,10 +50,14 @@ async def _get_model_config(model_name: str = None, org_id: str = None) -> dict: dbname = "pipeline" async with db.sqlorContext(dbname) as sor: if model_name: - sql = "SELECT api_base, api_key, model_id FROM llm WHERE name=${name}$ AND status='active'" + # name/model_id 双匹配:与 gateway._resolve_and_persist_model 语义一致—— + # 调用方传 llm.name 或 API 模型名(model_id)都能解析,避免语义漂移。 + sql = ("SELECT api_base, api_key, model_id FROM llm " + "WHERE status='active' AND (name=${name}$ OR model_id=${name}$)") params = {"name": model_name} if org_id: - sql += " AND org_id=${org}$" + # 与 gateway 一致:本机构模型 + 系统级共享(org_id 为空/0) + sql += " AND (org_id=${org}$ OR org_id='' OR org_id='0')" params["org"] = org_id sql += " LIMIT 1" recs = await sor.sqlExe(sql, params) @@ -80,6 +84,24 @@ async def _get_model_config(model_name: str = None, org_id: str = None) -> dict: return {} +def _no_llm_error(model_name=None, org_id=None) -> ValueError: + """模型不可用的错误必须真实可行动:写明模型名与原因,前端原样展示、运维据此处理。 + + 禁止笼统的 'No LLM API configured'——用户看到它只会卡住干等。 + """ + if model_name: + return ValueError( + f"模型「{model_name}」不可用:llm 表中未找到可用配置" + f"(name/model_id 不匹配、status 非 active" + + (f",或不属于当前机构 org={org_id}" if org_id else "") + + ")。请在模型下拉里改选可用模型,或联系管理员在模型管理中补齐配置。" + ) + return ValueError( + "没有可用模型:当前会话未指定模型,且 llm 表无启用模型。" + "请在模型下拉中选择模型,或联系管理员配置模型。" + ) + + # 瞬时错误重试:超时/连接错误/限流/服务端5xx 均重试;配置错误(无key)、鉴权/参数4xx 不重试 _RETRYABLE_STATUS = (429, 500, 502, 503, 504) _LLM_MAX_ATTEMPTS = 3 @@ -168,7 +190,7 @@ async def llm_call(prompt: str, model: str = None, temperature: float = 0.7, org model_id = model or os.environ.get("LLM_MODEL", "gpt-4o-mini") if not api_key: - raise ValueError("No LLM API configured. Please add a model in the llm table or set LLM_API_KEY env var.") + raise _no_llm_error(model, org_id) import aiohttp @@ -207,7 +229,7 @@ async def llm_call_msgs(messages: list, model: str = None, temperature: float = model_id = model or os.environ.get("LLM_MODEL", "gpt-4o-mini") if not api_key: - raise ValueError("No LLM API configured") + raise _no_llm_error(model, org_id) headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} payload = {"model": model_id, "messages": messages, "temperature": temperature} @@ -237,7 +259,7 @@ async def llm_call_msgs_native(messages: list, tools: list = None, model: str = model_id = model or os.environ.get("LLM_MODEL", "gpt-4o-mini") if not api_key: - raise ValueError("No LLM API configured") + raise _no_llm_error(model, org_id) headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} payload = {"model": model_id, "messages": messages, "temperature": temperature}