fix(agent): 跨上游超时窗调参——主调用单次超时330s→180s(实测最长响应<60s留3倍余量,失败快速返回);会话级重试2→3次+退避15s(3×180+2×15≈9.5分钟跨度可跨分钟级超时窗);llm_call_msgs_native补timeout参数透传_http_chat;回退文本路径同接180s

This commit is contained in:
ymq 2026-09-11 16:39:50 +08:00
parent 458736d71e
commit 0964dd2864
2 changed files with 19 additions and 7 deletions

View File

@ -108,6 +108,15 @@ class AgentExecutor:
_CONTINUE_GATE_MIN_CHARS = 400
_CONTINUE_GATE_MAX = 2
# 主调用单次超时2026-09-11端点缺省 330s 太长——上游超时窗内一次失败
# 要烧 330s×3 重试。实测最长成功响应 3212 token <60s180s 留 3 倍余量足够,
# 失败快速返回交给会话级重试跨越窗口。
_MAIN_TIMEOUT = 180
# 会话级重试次数(含首次共 3 次尝试)+ 退避秒数:上游超时窗实测分钟级、
# 间歇出现3 次尝试 × 180s + 2×15s 退避 ≈ 9.5 分钟跨度,可跨越多数窗口。
_MAIN_ATTEMPTS = 3
_MAIN_RETRY_BACKOFF = 15
def __init__(
self,
config, # AgentConfig (from pipeline_core)
@ -332,7 +341,7 @@ class AgentExecutor:
# 重试前向用户透出进度,避免"卡死"观感。
resp = None
_last_err = None
for _attempt in range(2):
for _attempt in range(self._MAIN_ATTEMPTS):
try:
resp = await self._call_llm()
break
@ -341,14 +350,14 @@ class AgentExecutor:
_transient = any(k in type(e).__name__ or k in str(e)
for k in ("Timeout", "ClientError", "ServerDisconnected",
"ConnectionReset"))
if not _transient or _attempt >= 1:
if not _transient or _attempt >= self._MAIN_ATTEMPTS - 1:
break
logger.warning(f"run: LLM 瞬时失败 turn={turn+1} 重试: {e}")
logger.warning(f"run: LLM 瞬时失败 turn={turn+1} 重试{_attempt+1}: {e}")
yield json.dumps({
"type": "progress",
"message": "模型上游瞬时超时,正在重试…\n",
}, ensure_ascii=False) + "\n"
await asyncio.sleep(3)
await asyncio.sleep(self._MAIN_RETRY_BACKOFF)
if resp is None:
logger.error(f"run: LLM 调用失败 turn={turn+1}: {_last_err}")
yield json.dumps({"type": "error", "message": str(_last_err)}, ensure_ascii=False) + "\n"
@ -868,6 +877,7 @@ class AgentExecutor:
temperature=self.config.temperature,
org_id=self.org_id,
session_id=self.session_id,
timeout=self._MAIN_TIMEOUT,
)
except Exception as e:
# 原生视觉降级2026-09-10模型不支持多模态 content 数组时
@ -893,13 +903,14 @@ class AgentExecutor:
temperature=self.config.temperature,
org_id=self.org_id,
session_id=self.session_id,
timeout=self._MAIN_TIMEOUT,
)
except Exception as e:
if self._degrade_images_if_needed(e):
content = await llm_call_msgs(
self._msgs, model=self.model_name,
temperature=self.config.temperature, org_id=self.org_id,
session_id=self.session_id)
session_id=self.session_id, timeout=self._MAIN_TIMEOUT)
else:
raise
return {"content": content or "", "tool_calls": []}

View File

@ -194,7 +194,8 @@ async def llm_call_msgs(messages: list, model: str = None, temperature: float =
async def llm_call_msgs_native(messages: list, tools: list = None, model: str = None,
temperature: float = 0.7, org_id: str = None,
user_id: str = None, purpose: str = '',
project_id: str = '', session_id: str = '') -> dict:
project_id: str = '', session_id: str = '',
timeout: int = 0) -> dict:
"""Native function calling. 传入 tools JSON schema返回 message dict。
Returns:
@ -212,7 +213,7 @@ async def llm_call_msgs_native(messages: list, tools: list = None, model: str =
if session_id:
payload["_session_id"] = session_id
data = await _http_chat(payload, org_id or '0', user_id or '', model or '',
project_id=project_id or '')
timeout=timeout, project_id=project_id or '')
try:
msg = data["choices"][0]["message"]
except (KeyError, IndexError, TypeError) as e: