feat(brain): add Gemini as brain_answer's third LLM tier
llm.Router was primary-then-fallback (berget -> iguana ollama). Both have failed together at least once in production (berget 5xx outage + a cold fallback model exceeding the shared timeout budget), leaving brain_answer/brain_classify fully down. Router gains an optional Tertiary *Client, tried only if both Primary and Fallback fail. Nil-safe — existing 2-tier deployments (no BRAIN_LLM_TERTIARY_URL) behave exactly as before. main.go wires it from BRAIN_LLM_TERTIARY_URL/MODEL + GEMINI_API_KEY, pointed at Google's OpenAI-compatible Gemini endpoint (generativelanguage.googleapis.com/v1beta/openai/) — llm.Client already speaks OpenAI-shaped chat-completions, so no new client type needed. Tests: TestRouter_FallsBackToTertiaryWhenPrimaryAndFallbackFail, TestRouter_TertiaryNotCalledWhenFallbackSucceeds, TestRouter_AllThreeFail, TestRouter_NilTertiary (2-tier backward-compat). Full suite green (both modules, -race), golangci-lint clean. Deployment env vars (infra repo, separate commit — GitOps, TBD there too): BRAIN_LLM_TERTIARY_URL, BRAIN_LLM_TERTIARY_MODEL, GEMINI_API_KEY secretRef. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,14 +5,18 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Router calls Primary first; on any error falls back to Fallback.
|
||||
// Fallback may be nil, in which case primary errors are returned directly.
|
||||
// Router calls Primary first; on any error falls back to Fallback, then to
|
||||
// Tertiary. Fallback and Tertiary may each be nil — a nil tier is skipped and
|
||||
// the chain returns the errors accumulated so far.
|
||||
type Router struct {
|
||||
Primary *Client
|
||||
Fallback *Client
|
||||
Tertiary *Client
|
||||
}
|
||||
|
||||
// Complete implements pipeline.CompleteFunc, routing through Primary then Fallback.
|
||||
// Complete implements pipeline.CompleteFunc, routing through Primary, then
|
||||
// Fallback, then Tertiary — trying each configured tier in order and
|
||||
// returning the first success.
|
||||
func (r *Router) Complete(ctx context.Context, system, user string) (string, error) {
|
||||
out, err := r.Primary.Complete(ctx, system, user)
|
||||
if err == nil {
|
||||
@@ -22,8 +26,15 @@ func (r *Router) Complete(ctx context.Context, system, user string) (string, err
|
||||
return "", fmt.Errorf("primary llm: %w", err)
|
||||
}
|
||||
out, err2 := r.Fallback.Complete(ctx, system, user)
|
||||
if err2 != nil {
|
||||
if err2 == nil {
|
||||
return out, nil
|
||||
}
|
||||
if r.Tertiary == nil {
|
||||
return "", fmt.Errorf("primary llm: %w; fallback llm: %v", err, err2)
|
||||
}
|
||||
out, err3 := r.Tertiary.Complete(ctx, system, user)
|
||||
if err3 != nil {
|
||||
return "", fmt.Errorf("primary llm: %w; fallback llm: %v; tertiary llm: %v", err, err2, err3)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user