From 0964dd2864c7bafe829dbcbfdcfd225005d5b058 Mon Sep 17 00:00:00 2001 From: ymq Date: Fri, 11 Sep 2026 16:39:50 +0800 Subject: [PATCH] =?UTF-8?q?fix(agent):=20=E8=B7=A8=E4=B8=8A=E6=B8=B8?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E7=AA=97=E8=B0=83=E5=8F=82=E2=80=94=E2=80=94?= =?UTF-8?q?=E4=B8=BB=E8=B0=83=E7=94=A8=E5=8D=95=E6=AC=A1=E8=B6=85=E6=97=B6?= =?UTF-8?q?330s=E2=86=92180s(=E5=AE=9E=E6=B5=8B=E6=9C=80=E9=95=BF=E5=93=8D?= =?UTF-8?q?=E5=BA=94<60s=E7=95=993=E5=80=8D=E4=BD=99=E9=87=8F,=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E5=BF=AB=E9=80=9F=E8=BF=94=E5=9B=9E);=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E7=BA=A7=E9=87=8D=E8=AF=952=E2=86=923=E6=AC=A1+?= =?UTF-8?q?=E9=80=80=E9=81=BF15s(3=C3=97180+2=C3=9715=E2=89=889.5=E5=88=86?= =?UTF-8?q?=E9=92=9F=E8=B7=A8=E5=BA=A6=E5=8F=AF=E8=B7=A8=E5=88=86=E9=92=9F?= =?UTF-8?q?=E7=BA=A7=E8=B6=85=E6=97=B6=E7=AA=97);llm=5Fcall=5Fmsgs=5Fnativ?= =?UTF-8?q?e=E8=A1=A5timeout=E5=8F=82=E6=95=B0=E9=80=8F=E4=BC=A0=5Fhttp=5F?= =?UTF-8?q?chat;=E5=9B=9E=E9=80=80=E6=96=87=E6=9C=AC=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=90=8C=E6=8E=A5180s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pipeline_service/agent_loop_v2.py | 21 ++++++++++++++++----- pipeline_service/llm_bridge.py | 5 +++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pipeline_service/agent_loop_v2.py b/pipeline_service/agent_loop_v2.py index 550c2db..fae85c3 100644 --- a/pipeline_service/agent_loop_v2.py +++ b/pipeline_service/agent_loop_v2.py @@ -108,6 +108,15 @@ class AgentExecutor: _CONTINUE_GATE_MIN_CHARS = 400 _CONTINUE_GATE_MAX = 2 + # 主调用单次超时(2026-09-11):端点缺省 330s 太长——上游超时窗内一次失败 + # 要烧 330s×3 重试。实测最长成功响应 3212 token <60s,180s 留 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": []} diff --git a/pipeline_service/llm_bridge.py b/pipeline_service/llm_bridge.py index 548abed..f1ba140 100644 --- a/pipeline_service/llm_bridge.py +++ b/pipeline_service/llm_bridge.py @@ -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: