|
|
@@ -22,12 +22,40 @@ Usage:
|
|
|
"""
|
|
|
from __future__ import annotations
|
|
|
|
|
|
+import logging
|
|
|
import time
|
|
|
from typing import Any, Callable, List, Optional
|
|
|
|
|
|
from lambdagent.core import Term, Context
|
|
|
from lambdagent.providers.base import LLMProvider, ProviderError, ChatMessage, ChatResponse
|
|
|
|
|
|
+logger = logging.getLogger("lambdagent.conversation")
|
|
|
+
|
|
|
+# 可重试 provider 错误(如 claude-code resume 首字节卡死 / 瞬时 API 抖动)的总尝试次数。
|
|
|
+# 不重试则一次卡死就把这步退化成 [X_ERROR] 字符串、run 失败。provider 在 stall 时已
|
|
|
+# 清 session_id → 重试时退化成 fresh first-turn(小 prompt,通常很快)→ 恢复而非死。
|
|
|
+_PROVIDER_RETRY_ATTEMPTS = 3
|
|
|
+
|
|
|
+
|
|
|
+def _provider_call_with_retry(fn, what: str):
|
|
|
+ """调用 provider,对 retryable ProviderError 重试。非 retryable 立即上抛;
|
|
|
+ 重试耗尽后把最后一次错误上抛,由调用方退化成 [X_ERROR] 字符串。"""
|
|
|
+ last: Optional[ProviderError] = None
|
|
|
+ for attempt in range(1, _PROVIDER_RETRY_ATTEMPTS + 1):
|
|
|
+ try:
|
|
|
+ return fn()
|
|
|
+ except ProviderError as e:
|
|
|
+ last = e
|
|
|
+ if not getattr(e, "retryable", False) or attempt >= _PROVIDER_RETRY_ATTEMPTS:
|
|
|
+ raise
|
|
|
+ logger.warning(
|
|
|
+ "provider retryable error on %s (attempt %d/%d) — 重试: %s",
|
|
|
+ what, attempt, _PROVIDER_RETRY_ATTEMPTS, str(e)[:200],
|
|
|
+ )
|
|
|
+ time.sleep(1.0)
|
|
|
+ assert last is not None
|
|
|
+ raise last
|
|
|
+
|
|
|
|
|
|
class ConversationLam(Term):
|
|
|
"""
|
|
|
@@ -109,7 +137,8 @@ class ConversationLam(Term):
|
|
|
# Call provider
|
|
|
tokens_used = 0
|
|
|
try:
|
|
|
- response = self.provider.chat(managed)
|
|
|
+ response = _provider_call_with_retry(
|
|
|
+ lambda: self.provider.chat(managed), "chat")
|
|
|
except ProviderError as e:
|
|
|
response = f"[{e.provider.upper()}_ERROR] {e}"
|
|
|
ctx.record_provider_error(e.provider, str(e)) # AUDIT ②: 结构化上报
|
|
|
@@ -144,12 +173,13 @@ class ConversationLam(Term):
|
|
|
|
|
|
# Call provider via typed interface
|
|
|
try:
|
|
|
- response = self.provider.chat_typed(
|
|
|
- messages=messages,
|
|
|
- model=self._model,
|
|
|
- temperature=self._temperature,
|
|
|
- max_tokens=self._max_tokens,
|
|
|
- )
|
|
|
+ response = _provider_call_with_retry(
|
|
|
+ lambda: self.provider.chat_typed(
|
|
|
+ messages=messages,
|
|
|
+ model=self._model,
|
|
|
+ temperature=self._temperature,
|
|
|
+ max_tokens=self._max_tokens,
|
|
|
+ ), "chat_typed")
|
|
|
except ProviderError as e:
|
|
|
response = ChatResponse(
|
|
|
text=f"[{e.provider.upper()}_ERROR] {e}",
|