feat(chat): LLM failover chain + show which model answered #83
Reference in New Issue
Block a user
Delete Branch "feat/llm-failover-chain"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Chat now fails over across an ordered LLM chain instead of failing when the pinned model is down/drifted, and shows which model actually answered.
Why
You saw
vllm-current ✗ HTTP 400(drifted off the served model) with anthropic available, and expected failover. Chat only failed over on transport errors, not 4xx, and there was no fallback target.Changes
LlmdeclaresextraConfig.fallbacks: string[]; the dispatcher builds primary-pool → fallback-pool(s) and tries them in order (resolveCandidatesWithFallbacks).runOneInferenceadvances on a non-2xx status (e.g. a drifted-model 400) or an empty/invalid completion — not just thrown transport errors. Streaming fails over pre-first-chunk.ChatResult+ the SSEfinalframe carry{llm, model, failedOver}; the CLI printsmodel: <llm> (<model>)each turn and⚠ failed over → answered by …on a fallback.Tests
3 failover unit tests (primary 400 → fallback answers + model reported; primary answers → failedOver=false; all fail → clear error). mcpd 948 + CLI 508 green; tsc + lint clean. docs/reliability.md updated.
Follow-up (live, this session): provision the homelab chain vllm-current → anthropic-fallback (anthropic key as an mcpd secret + provider wiring).
🤖 Generated with Claude Code
Chat is LLM-essential but not model-specific — instead of failing when the pinned model is down/drifted, it now fails over across an ordered chain and reports which model actually answered. - Ordered fallback: an Llm declares `extraConfig.fallbacks: string[]`; the dispatcher builds primary-pool → fallback-pool(s) candidates and tries them in order (resolveCandidatesWithFallbacks). - Fail over on real failures, not just transport: runOneInference now advances on a non-2xx status (e.g. a 400 from a drifted model) or an empty/invalid completion, not only thrown transport errors. Streaming fails over pre-first-chunk (already threw on 4xx). - Transparency: ChatResult + the SSE `final` frame carry {llm, model, failedOver}; the CLI prints `model: <llm> (<model>)` each turn and `⚠ failed over → answered by …` when a fallback was used. - Exhaustion names the last model + upstream body (not "no choice"). Tests: 3 failover unit tests (primary 400 → fallback answers + model reported; primary answers → failedOver=false; all fail → clear aggregated error). mcpd 948 + CLI 508 green; tsc + lint clean. docs/reliability.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>