From a42e876ef832f94e42fe6a9238911346113407b3 Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Fri, 10 Jul 2026 14:41:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(call=5Fprovider=5Fwith=5Fretry):=20?= =?UTF-8?q?=E5=8F=AA=E6=9C=89=20primary=20provider=20=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E5=A4=B1=E8=B4=A5=E5=90=8E=EF=BC=8C=E6=89=8D?= =?UTF-8?q?=E4=BC=9A=E5=87=BA=E7=8E=B0=E7=AC=AC=E4=BA=8C=E7=BB=84=20fallba?= =?UTF-8?q?ck=20=E7=9A=84=20Provider=20=E9=80=89=E6=8B=A9=E6=97=A5?= =?UTF-8?q?=E5=BF=97=EF=BC=8C=E5=87=8F=E5=B0=91=E4=B8=8D=E5=BF=85=E8=A6=81?= =?UTF-8?q?=E7=9A=84=E6=97=A5=E5=BF=97=E5=86=85=E5=AE=B9=20(e53016e)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../analysis/analyzers/base_analyzer.py | 30 +++++++++++++------ .../analysis/utils/llm_utils.py | 30 +++++++++++-------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/infrastructure/analysis/analyzers/base_analyzer.py b/src/infrastructure/analysis/analyzers/base_analyzer.py index c5fa5a9..7036d17 100644 --- a/src/infrastructure/analysis/analyzers/base_analyzer.py +++ b/src/infrastructure/analysis/analyzers/base_analyzer.py @@ -158,20 +158,23 @@ class BaseAnalyzer(ABC, Generic[TDataObject, TInputData]): self, provider_id_key: str | None, umo: str | None, + provider_id: str | None = None, ) -> float | None: """ 尝试从当前将要调用的 Provider 配置中解析基础 temperature。 """ - provider_id = await get_provider_id_with_fallback( - self.context, - self.config_manager, - provider_id_key, - umo, - ) - if not provider_id: + pid = provider_id + if not pid: + pid = await get_provider_id_with_fallback( + self.context, + self.config_manager, + provider_id_key, + umo, + ) + if not pid: return None - provider = self.context.get_provider_by_id(provider_id=provider_id) + provider = self.context.get_provider_by_id(provider_id=pid) if provider is None: return None @@ -377,8 +380,16 @@ class BaseAnalyzer(ABC, Generic[TDataObject, TInputData]): # 2. 调用LLM(使用配置的 provider) provider_id_key = self.get_provider_id_key() + + # Resolve provider ID once; pass downstream to avoid duplicate resolve logs + resolved_provider_id = None + if provider_id_key: + resolved_provider_id = await get_provider_id_with_fallback( + self.context, self.config_manager, provider_id_key, umo + ) + base_temperature = await self._resolve_provider_temperature( - provider_id_key, umo + provider_id_key, umo, provider_id=resolved_provider_id ) # 获取人格设定 @@ -401,6 +412,7 @@ class BaseAnalyzer(ABC, Generic[TDataObject, TInputData]): prompt=prompt, umo=umo, provider_id_key=provider_id_key, + provider_id=resolved_provider_id, system_prompt=system_prompt, response_format=self.get_response_format(), ) diff --git a/src/infrastructure/analysis/utils/llm_utils.py b/src/infrastructure/analysis/utils/llm_utils.py index 75155d4..5480887 100644 --- a/src/infrastructure/analysis/utils/llm_utils.py +++ b/src/infrastructure/analysis/utils/llm_utils.py @@ -243,6 +243,7 @@ async def call_provider_with_retry( prompt: str, umo: str | None = None, provider_id_key: str | None = None, + provider_id: str | None = None, system_prompt: str | None = None, response_format: JSONObject | None = None, extra_generate_kwargs: dict[str, JSONValue] | None = None, @@ -273,20 +274,14 @@ async def call_provider_with_retry( attempt_queue = [] # 尝试获取指定的 Provider - specific_provider_id = await get_provider_id_with_fallback( - context, config_manager, provider_id_key, umo - ) + specific_provider_id = provider_id + if not specific_provider_id: + specific_provider_id = await get_provider_id_with_fallback( + context, config_manager, provider_id_key, umo + ) if specific_provider_id: attempt_queue.extend([(specific_provider_id, False)] * retries) - # 尝试获取降级/默认的 Provider (传入 None 走主模型/会话模型路径) - if provider_id_key is not None: - fallback_provider_id = await get_provider_id_with_fallback( - context, config_manager, None, umo - ) - if fallback_provider_id and fallback_provider_id != specific_provider_id: - attempt_queue.extend([(fallback_provider_id, True)] * retries) - if not attempt_queue: logger.error("无可用 Provider,无法调用 llm_generate") return None @@ -331,10 +326,11 @@ async def call_provider_with_retry( # 记录上一次尝试的 Provider ID,用于判断是否发生切换 previous_pid = None + # 惰性降级标记:仅在 primary provider 重试用尽后才 resolve fallback + needs_fallback = provider_id_key is not None for i, (current_pid, is_fallback) in enumerate(attempt_queue): attempt_num = i + 1 - is_last_attempt = i == len(attempt_queue) - 1 # 修复状态污染:如果切换了全新的 Provider,必须重置 response_format 约束 if current_pid != previous_pid: @@ -375,7 +371,17 @@ async def call_provider_with_retry( last_exc = inner_e logger.warning(f"{prefix}请求失败: {last_exc}") + # 惰性降级:仅当所有 primary provider 的重试都耗尽后才 resolve 并注入 fallback + if not is_fallback and i == retries - 1 and needs_fallback: + fallback_provider_id = await get_provider_id_with_fallback( + context, config_manager, None, umo + ) + if fallback_provider_id and fallback_provider_id != specific_provider_id: + for _ in range(retries): + attempt_queue.append((fallback_provider_id, True)) + + is_last_attempt = i == len(attempt_queue) - 1 if not is_last_attempt: # Exponential backoff with jitter: backoff * (2 ^ (attempt_num - 1)) + random jitter sleep_time = backoff * (2 ** (attempt_num - 1)) + random.uniform(0, 1)