你的 AI Agent 剛和使用者進行了 30 分鐘的對話。他們討論了專案需求、分享了偏好、做出了決定。接著,使用者輸入 /new 來開始一個新的對話。
Agent 試圖將該對話整合到長期記憶中。LLM 呼叫失敗了。可能是 Rate limit、Timeout,或者是模型回傳了純文字而非呼叫所需的 tool。
記憶消失了。三十分鐘的上下文煙消雲散。
這種情況發生的頻率比你想像的要高。我們在 LemonClaw 實例中追蹤了這個問題:在任何單一模型上,記憶整合(memory consolidation)的失敗率約為 15%。對於一個理應是「隱形基礎設施」的功能來說,這是不可接受的。
如果你正在構建產品介面而不僅僅是記憶子系統,請將此頁面與 一鍵式聊天機器人指南 以及 自託管 LemonClaw 指南 搭配閱讀。只有當 Agent 真正存在於一個可用的應用程式中時,記憶的持久性才有意義。
其他框架如何處理這個問題(其實他們沒處理)
大多數 AI Agent 框架將記憶整合視為一個簡單的 LLM 呼叫。如果成功了,那很好;如果失敗了,記憶就丟失了。
LemonClaw 的前身框架對整合和對話使用相同的模型。一次 Claude Sonnet 呼叫花費 $0.003 且耗時 8 秒以上,僅僅是為了總結一段使用者永遠不會看到的對話。當該呼叫失敗(Rate limit、Timeout、模型錯誤)時,框架只會記錄一條警告然後繼續。使用者的上下文就此消失。
nanobot 是另一個受歡迎的框架,也採用相同的架構。單一模型、單次嘗試、沒有 fallback。整合功能甚至沒有 Timeout 設定。緩慢的上游(常見的 Cloudflare 524 錯誤)會阻塞整個對話,直到連線中斷。
這些框架都沒有將整合與主模型分離,也沒有針對記憶操作的 fallback 邏輯。它們無法區分「API 呼叫失敗」與「API 呼叫成功但模型沒有執行要求的操作」。
這些並非極端案例。在任何單一模型上都有 15% 的失敗率,這意味著一個每天執行 100 次整合的框架會丟失 15 次記憶。一週下來,就有 105 個對話會讓 Agent 忘得一乾二淨。
問題比重試邏輯更深層
最顯而易見的修復方法是使用指數退避(exponential backoff)進行重試。我們以前也有這個機制。它能很好地處理暫時性的 HTTP 錯誤:
# Retry loop: 1s → 2s → 4s backoff
for attempt in range(3):
try:
response = await acompletion(**kwargs)
return await self._collect_stream(response)
except (RateLimitError, APIConnectionError) as e:
await asyncio.sleep(RETRY_DELAYS[attempt])
這種方式能捕捉到 429 錯誤和網路波動。但有兩種失敗模式會成為漏網之魚:
失敗模式 1:模型無法進行 tool calling。某些模型,特別是在快速推理引擎上運行的較小模型,偶爾會在複雜的 prompt 下無法生成有效的 function calls。API 回傳 200,但內部包裹著 ServiceUnavailableError 或 MidStreamFallbackError。你的重試邏輯看到異常,重試同一個模型,結果得到相同的錯誤。
失敗模式 2:模型「成功」了但沒有呼叫 tool。LLM 回傳了一個完全有效的響應。HTTP 200。沒有錯誤。但它沒有使用結構化數據呼叫 save_memory,而是寫了一段純文字總結。你的重試引擎認為這是成功的。整合功能檢查 tool calls,發現沒有,然後就放棄了。
第二種失敗模式更為隱蔽。傳輸層(transport layer)認為一切正常,但業務層(business layer)知道並非如此。對於一個不理解你的 tool schema 的模型,再多的 HTTP 層級重試也無濟於事。
雙層 Fallback 架構
我們透過在不同層級運行的兩個獨立 fallback 迴圈解決了這個問題:
使用者發送 /new
│
▼
consolidate() ─── 業務層 Fallback
│ 「模型呼叫 save_memory 了嗎?」
│ 否 → 嘗試鏈中的下一個模型
│
▼
_chat_with_retry() ─── 傳輸層 Fallback
│ HTTP 錯誤 → 指數退避
│ 重試耗盡 → 遍歷 fallback 鏈
│
▼
MODEL_MAP fallback 鏈:
llama-3.3-70b ─$0.59/M─→ qwen3-32b ─$0.29/M─→ llama-4-scout ─$0.11/M─→ gpt-4.1-mini ─→ claude-haiku
(394 TPS) (662 TPS) (594 TPS) (可靠) (最後手段)
第一層處理傳輸失敗。第二層處理業務邏輯失敗。Fallback 鏈在兩層之間共享,並在中央目錄中定義一次。
這與「重試同一個模型」的方法有本質上的不同。當一個模型無法呼叫 tool 時,用相同的 prompt 重試它很少有幫助。切換到另一個具有不同權重和不同 tool calling 行為的模型則有效得多。
模型目錄:單一事實來源
我們目錄中的每個模型都有一個可選的 fallback 欄位,指向下一個要嘗試的模型:
@dataclass(frozen=True)
class ModelEntry:
id: str
label: str
tier: str
description: str
fallback: str | None = None
hidden: bool = False # 對使用者隱藏 /model 列表
MODEL_CATALOG = [
# 使用者可見模型 (使用者可以切換的 16 個模型)
ModelEntry("claude-sonnet-4-6", "Claude Sonnet 4.6", "standard",
"Recommended", fallback="claude-sonnet-4-5"),
ModelEntry("gpt-4.1-mini", "GPT-4.1 Mini", "economy",
"Stable tool calling", fallback="claude-haiku-4-5"),
# 隱藏的整合模型 (僅限內部使用)
ModelEntry("llama-3.3-70b-versatile", "Llama 3.3 70B (Groq)", "economy",
"394 TPS", fallback="qwen3-32b", hidden=True),
ModelEntry("qwen3-32b", "Qwen3 32B (Groq)", "economy",
"662 TPS", fallback="llama-4-scout-17b-16e-instruct", hidden=True),
# ...
]
hidden=True 標記可以讓內部模型不出現在面向使用者的 /model 命令列表中,但仍能參與 fallback 鏈。使用者看到 16 個可以切換的模型,而系統實際上使用了 19 個。這三個隱藏模型專門用於記憶整合等後台任務,在這些任務中,速度和成本比對話品質更重要。
這個目錄是所有模型路由的單一事實來源。在 fallback 鏈中添加新模型只需添加一行代碼。無需同步配置文件,無需更新環境變數,也無需修改部署腳本。
傳輸層:具備循環檢測的鏈式 Fallback
重試引擎使用 visited 集合來遍歷 fallback 鏈,以防止無限迴圈:
async def _chat_with_retry(self, kwargs, original_model):
# 第一階段:在主模型上進行指數退避
for attempt in range(3):
try:
response = await acompletion(**kwargs)
return await self._collect_stream(response)
except (RateLimitError, APIConnectionError, APIError) as e:
await asyncio.sleep(RETRY_DELAYS[attempt])
except AuthenticationError:
return LLMResponse(content="API key invalid.", finish_reason="error")
# 第二階段:遍歷 fallback 鏈
visited = {original_model}
current = original_model
while True:
entry = MODEL_MAP.get(current)
if not entry or not entry.fallback or entry.fallback in visited:
break
current = entry.fallback
visited.add(current)
# 為此模型解析正確的 gateway
gw = self._resolve_gateway_for_model(current)
resolved = self._resolve_model(current, gateway=gw)
fb_kwargs = {**kwargs, "model": resolved}
# 為目標模型的協議修正 api_base
if gw and gw.default_api_base:
fb_kwargs["api_base"] = gw.default_api_base
try:
response = await acompletion(**fb_kwargs)
return await self._collect_stream(response)
except Exception:
continue # 嘗試鏈中的下一個
return LLMResponse(content="Service unavailable.", finish_reason="error")
visited 集合至關重要。如果沒有它,像 A→B→A 這樣的鏈條將會永遠循環。有了它,引擎會精確地嘗試每個模型一次。
Gateway 解析也很重要。不同的模型需要不同的 API 格式。Claude 模型透過 Anthropic 格式的 gateway 路由(沒有 /v1 字尾)。GPT 模型透過 OpenAI 相容的 gateway 路由(帶有 /v1)。Groq 模型則使用另一個 endpoint。Fallback 引擎會為鏈中的每個模型解析正確的 gateway,防止出現將 Anthropic 請求發送到 OpenAI endpoint 等協議不匹配的情況。
這是大多數框架完全忽略的細節。它們假設所有模型都使用相同的協議。在生產環境中,面對跨越 4 種不同 API 格式的 19 個模型,這個假設會立即破產。
業務層:Tool Call 驗證
整合功能在頂層添加了自己的 fallback 迴圈:
async def consolidate(self, session, provider, model, **kwargs):
visited = set()
current_model = model
while current_model and current_model not in visited and len(visited) <= 3:
visited.add(current_model)
response = await asyncio.wait_for(
provider.chat(messages=messages, tools=SAVE_MEMORY_TOOL, model=current_model),
timeout=30,
)
if response.has_tool_calls:
# 成功:提取並儲存記憶
args = response.tool_calls[0].arguments
self.write_long_term(args["memory_update"])
self.append_history(args["history_entry"])
return True
# 模型沒有呼叫 tool — 嘗試鏈中的下一個模型
entry = MODEL_MAP.get(current_model)
next_model = entry.fallback if entry else None
if next_model and next_model not in visited:
current_model = next_model
continue
return False # 沒有更多 fallback
return False
這捕捉了 _chat_with_retry 回傳成功響應(HTTP 200,內容有效)但模型未使用 tool 的情況。整合功能會檢查 has_tool_calls,如果缺失,則移動到鏈中的下一個模型。
Timeout 包裝器(asyncio.wait_for)也會觸發 fallback。如果模型耗時超過 30 秒(在慢速上游的 Cloudflare 524 錯誤中很常見),該函數會捕捉 TimeoutError 並嘗試下一個模型,而不是無限期地阻塞使用者的對話。
為什麼選擇 Groq 進行整合
記憶整合是一項後台任務。使用者看不到輸出,他們只需要它能運作。這使其成為快速、廉價模型的絕佳候選者。
大多數框架對所有任務都使用相同的昂貴模型。如果你使用 Claude Sonnet 進行對話,你也同樣在使用 Claude Sonnet 進行記憶整合。對於一項產出人類永遠不會閱讀的任務來說,這意味著每百萬 input tokens 花費 $3,且每次整合耗時 8 秒以上。
我們將整合與對話模型完全解耦。對話使用使用者選擇的任何模型,而整合則使用專門的 Groq 託管模型鏈:
| 模型 | 速度 | 輸入成本 | 輸出成本 |
|---|---|---|---|
| llama-3.3-70b-versatile | 394 TPS | $0.59/M | $0.79/M |
| qwen3-32b | 662 TPS | $0.29/M | $0.59/M |
| llama-4-scout-17b-16e | 594 TPS | $0.11/M | $0.34/M |
| gpt-4.1-mini (先前版本) | ~150 TPS | $0.40/M | $1.60/M |
主模型(llama-3.3-70b)在大約 5 秒內就能整合一個包含 60 條訊息的對話。之前的預設模型(gpt-4.1-mini)則需要 8 秒以上。每次整合的成本從約 $0.003 降至約 $0.001。
權衡之處在於:Groq 模型在複雜 prompt 下的 tool calling 可靠性較低。這正是雙層 fallback 存在的原因。當 llama-3.3-70b 無法呼叫 tool 時,qwen3-32b 會接手。如果這也失敗了,llama-4-scout 會嘗試。如果所有三個 Groq 模型都失敗了,gpt-4.1-mini 會以近乎 100% 的 tool calling 可靠性來處理它。
在生產環境中,我們看到主模型的成功率約為 85%。只有不到 2% 的整合會觸及 gpt-4.1-mini。總體失敗率實際上為零。
生產環境結果
我們將此方案部署到兩個 LemonClaw 實例,並使用真實的 Telegram 對話進行測試。
第一次部署(僅限單層 fallback):
Memory consolidation (archive_all): 56 messages
llama-3.3-70b-versatile → "Failed to call a function"
Falling back → qwen3-32b
qwen3-32b: LLM did not call save_memory, skipping
→ "Memory archival failed, session not cleared."
傳輸層捕捉到了第一次失敗並進行了 fallback。但 qwen3-32b 回傳了文字而沒有呼叫 tool。單層 fallback 無法處理這種情況。這正是所有其他框架會默默丟失記憶的典型場景。
第二次部署(雙層 fallback):
Memory consolidation (archive_all): 60 messages
model=llama-3.3-70b-versatile → success
Memory consolidation done: 60 messages remaining
相同的模型,相同的訊息量。這次第一次嘗試就成功了。Tool calling 失敗的間歇性特質,正是為什麼你需要一個 fallback 鏈而不是單一備份模型的原因。
當主模型確實失敗時,鏈條會捕捉到它:
llama-3.3-70b → tool call failed
→ consolidate() fallback → qwen3-32b
→ qwen3-32b didn't call tool
→ consolidate() fallback → llama-4-scout
→ llama-4-scout didn't call tool
→ consolidate() fallback → gpt-4.1-mini
→ gpt-4.1-mini called save_memory ✓
Memory consolidation done
四個模型嘗試過,記憶已儲存。使用者看到「新對話已開始」,完全不知道發生了這一切。
架構差距
LemonClaw 的記憶系統與其他替代方案的功能對比:
| 功能 | 典型的 AI Agent 框架 | LemonClaw |
|---|---|---|
| 整合模型 | 與對話相同 (昂貴、緩慢) | 獨立模型鏈,Groq 加速 |
| 失敗處理 | 記錄警告,丟失記憶 | 雙層 fallback,深度達 5 個模型 |
| 傳輸層 Fallback | 重試同一個模型 3 次 | 跨不同模型的鏈式 fallback |
| 業務邏輯 Fallback | 無 | Tool call 驗證 + 模型切換 |
| Timeout 保護 | 無 (Cloudflare 524 阻塞對話) | asyncio.wait_for(timeout=30) + fallback |
| 對話截斷 | 無 (上下文無限增長) | 整合後截斷舊訊息 |
| 歷史搜尋 | 無 | HISTORY.md 滾動視窗,可 grep 搜尋 |
| 內部模型 | 不支援 | hidden=True 僅限系統模型 |
| 循環預防 | 不需要 (無鏈條) | visited 集合防止 A→B→A 循環 |
| Gateway 解析 | 假設單一 API 格式 | 具備協議檢測的每模型 gateway |
表格中的每一行都代表了我們親身經歷過,或在其他框架的 issue 追蹤器中觀察到的生產環境失敗案例。雙層 fallback、隱藏模型目錄、每模型 gateway 解析、Timeout 觸發的 fallback:這些在 nanobot 或我們研究過的任何其他開源 Agent 框架中都不存在。
我們的經驗教訓
「請求成功」不等於「任務成功」。通用的重試引擎運作在 HTTP 層級。它們無法知道一個帶有有效 JSON 的 200 響應實際上是失敗的,因為模型沒有使用你要求的 tool。業務關鍵型操作需要自己的成功標準和 fallback 邏輯。
小模型的失敗方式與大模型不同。大模型(GPT-4.1, Claude Sonnet)在被要求時幾乎總是會呼叫 tool。快速推理引擎上的小模型有時會生成看起來有效但完全忽略 tool schema 的響應。這不是你能透過 prompt engineering 修復的 bug,而是一個需要架構性緩解的功能差距。
使用生產數據而非合成數據進行測試。我們最初使用 6 條合成訊息進行的測試在每個模型上都通過了。而包含 tool call 歷史、時間戳記和混合語言的真實 60 條訊息對話,在三個 Groq 模型中的兩個上都失敗了。真實數據的複雜性會暴露乾淨的測試數據永遠無法發現的失敗模式。
這也是為什麼 AI API Rate Limiting 指南 在這裡很重要。記憶系統不僅僅需要一個「更好的模型」,它還需要傳輸策略、業務邏輯成功檢查,以及一個不會在供應商發生普通故障時崩潰的 fallback 階梯。
LemonClaw 是一個開源的 AI Agent 框架,內建多模型路由、持久化記憶以及 10 多個聊天平台整合。這裡描述的整個雙層 fallback 系統都包含在開源版本中。在你的伺服器上運行它:github.com/hedging8563/lemonclaw
需要透過一個 API key 存取 300 多個 AI 模型嗎?lemondata.cc 提供對 OpenAI、Anthropic、Google、DeepSeek、Groq 等模型的統一存取。
