# Send 失敗與 fallback

模型呼叫逾時、被限流、額度或 context 用盡時的處理，以及 fallback 如何挑下一個模型。

## Send 失敗處理

`Agent.Send()` 失敗採逐級升高處理，而非盲目重試：

| 失敗類型 | 行為 |
|---|---|
| Timeout | 於同一模型最多重試 `MAX_RETRY_TIMES`（3）次——v1.1.2 起共 4 次嘗試，先前為 3 次——間隔 `SEND_TIMEOUT_RETRY_INTERVAL`（15 秒） |
| Rate limit（HTTP 429 或 rate-limit 訊息） | 註冊 30 分鐘 cooldown，於同一模型在 5 秒、10 秒、15 秒後重試；之後切換到下一個 fallback |
| 額度耗盡（HTTP 402 / 403 或額度 / 帳務訊息） | 註冊 30 分鐘 cooldown，立即切換到下一個 fallback |
| Reasoning 等級被拒（錯誤訊息指出 thinking / reasoning / effort 不支援或無效） | v1.3.0 起於同一模型改用相鄰等級重試——`medium` 以上往下、低於 `medium` 往上，之後沿同一方向——最多 `MAX_RETRY_TIMES`（3）次；footer 與 done 事件記錄實際使用的等級 |
| Context 長度超出 | v1.3.0 起從錯誤訊息學習模型實際上限（取其中不小於 4,096 的最小數字，依模型保存至程序結束，之後的壓縮門檻也改用此值），將歷史裁到原大小的 80 %（`compact.TrimRatio`）後重送；v1.3.1 起先由 decision model 移除需求已用不到的工具步驟，再刪最舊的對話。只有在無可裁剪時才中止。v1.1.1 起「先壓縮歷史」的步驟移除於 v1.3.0 |
| 串流中無回應 | v1.2.3 起由 `go-llm-router` 監看串流（`WatchIdle`）：每 30 秒探測一次，探測沒收到新資料後每 10 秒複查（只有 keep-alive 或無法解析內容的資料不算），連續 3 次失敗、或串流陷入重複輸出時，以 `[Timeout]` 錯誤結束串流，並依上方 Timeout 規則處理。Agenvoy 自有的 watchdog（`UNRESPONSIVE_PROBE_INTERVAL`）移除於 v1.2.3 |
| 其他錯誤 | 切換到下一個 fallback 模型；無可用 fallback 時中止 |

Fallback 候選來自 `exec/model.Route`（v1.2.1 前為 `ResolveAgent`）依優先順序建立的清單，自 v1.1.0 起不含 `pass` tier 模型。`exec/model.Next`（v1.2.1 前為 `nextAgent`）會略過與失敗模型同供應商的模型（略過 context window 容不下輸入之模型的行為移除於 v1.2.1），對每個候選做 health check（`HEALTH_CHECK_TIMEOUT`，10 秒），並重建清單直到跑完 `MAX_RETRY_TIMES`（3）輪重試——v1.1.2 起共 4 輪，先前為 3 輪。綁定特定模型（`auto` 以外）的 session 不會 fallback。

切換模型會清空 `ToolHistories`（或套用 `compact.RawToolFallback`）、重置去重表並歸零計數器——新模型不會繼承失敗模型的殘留狀態。

## Timeout 層級

| 層級 | 值 | 攔截的問題 |
|---|---|---|
| Provider HTTP client | 由 `go-llm-router` 各 provider 內部設定 | 傳輸層卡住 |
| `AGENT_SEND_TIMEOUT_SEC` | `config.json` 的 `limits.agent_send_timeout_seconds`，預設 `600`；v1.1.2 起小於預設的值會被忽略 | exec 層以 `context.WithTimeout` 設下的上限 |
| 串流閒置檢查（`go-llm-router` `WatchIdle`） | 每 30 秒探測；探測失敗後每 10 秒複查；失敗 3 次即以逾時結束串流 | 串流卡住、只送 keep-alive 或不斷重複卻未報錯 |
| Health check | 10 秒 | 各 fallback 候選的存活探測 |
