fix(llm): 模型不可用报真实错误+name/model_id双匹配+org语义对齐

- _no_llm_error: 错误写明模型名/原因/行动指引,禁止笼统No LLM API configured
- _get_model_config: name/model_id 双匹配(与gateway解析语义一致),org过滤含系统级共享
- agent_loop_v2 run(): LLM调用异常捕获→yield error事件,前端显示真实错误不再卡死思考中
This commit is contained in:
ymq 2026-08-30 15:07:34 +08:00
parent 519b4bfcf4
commit 7023742880
3 changed files with 35 additions and 6 deletions

View File

@ -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 []

View File

@ -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}