llmpool

package
v0.3.15 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 7, 2026 License: AGPL-3.0 Imports: 12 Imported by: 0

Documentation

Overview

Package llmpool implements LLM failover ("轮询"): a Provider decorator that walks an ordered chain of LLM profiles and moves to the next one when the current one can't serve the request — out of credit, revoked key, rate-limited past the SDK's own retries, or down.

The chain is built by the server from llm_profiles (active profile first, then by priority), so the whole process shares ONE chain and ONE circuit-breaker registry: when a task discovers a profile is out of credit, every other task skips it immediately.

Index

Constants

View Source
const RankActive = int(^uint(0)>>1) - 1

RankActive is the Rank the caller gives the chain head (the active profile, or an explicitly bound one) so it always outranks any configured priority.

Variables

View Source
var ErrExhausted = errors.New("LLM 轮询:所有配置均不可用")

ErrExhausted is returned when every member of the chain failed.

Functions

This section is empty.

Types

type Member

type Member struct {
	ID       int64  // llm_profiles.id
	Name     string // profile name, for logs / UI
	Model    string
	Format   string // "anthropic" | "openai"
	Priority int    // the profile's configured priority (display only)
	Active   bool   // is_default (display only)
	// Rank is the ordering key the caller assigned: higher goes first, and members
	// sharing a Rank take turns leading (load-spreading across duplicate keys).
	// The caller encodes "active profile heads the chain" as a Rank above every
	// user-settable priority, so this type needs no policy of its own.
	Rank int
	// WindowTokens is the profile's context window in tokens. A member whose
	// window can't hold the request is skipped rather than made to fail on it.
	WindowTokens int
	Prov         llm.Provider
}

Member is one LLM profile in the chain, already built into a provider (wrapped with the recorder by the caller, so a failed attempt is still recorded under its own profile name).

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool is an llm.Provider that fails over across an ordered chain of members. It is safe for concurrent use: members are immutable after construction and all mutable state lives in the shared Registry.

func New

func New(members []*Member, health *Registry) *Pool

New builds a Pool over members (already in chain order). Returns nil when the chain is empty. A single-member chain is still a valid Pool — it just behaves exactly like the bare provider.

func (*Pool) Complete

func (p *Pool) Complete(ctx context.Context, req llm.CompletionRequest) (llm.Message, string, llm.Usage, error)

Complete implements llm.Provider with failover for non-streaming calls. A non-streaming request is atomic — it never delivers partial output — so every failover-eligible failure lands in the safe window and the next member can be tried without risk of duplicated output. Mirrors Stream's health-tripping and chain-exhaustion behavior.

func (*Pool) Head

func (p *Pool) Head() *Member

Head returns the first member of the chain.

func (*Pool) Members

func (p *Pool) Members() []*Member

Members returns the chain in order (read-only).

func (*Pool) Stream

Stream implements llm.Provider with failover.

The one hard rule: a member may only be abandoned BEFORE it has yielded any event. Once text or a tool_use has reached the caller, re-sending the same request to another model would duplicate output and corrupt the conversation history — so a mid-stream failure is surfaced as-is and left to the agent harness's resume logic. Fortunately the failures this exists for (402 no credit, 401 bad key, 429, 5xx) all surface during request establishment, before the body is read, so they always land in the safe window.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds the circuit-breaker state of every profile, keyed by profile id. It is process-wide and outlives any single Pool instance, so rebuilding the pool (saving an unrelated profile, toggling a setting) never clears what we've learned about which backends are broken.

func NewRegistry

func NewRegistry(persist func(id int64, st State), forget func(id int64)) *Registry

NewRegistry builds an empty registry. persist/forget may be nil.

func (*Registry) Get

func (r *Registry) Get(id int64) State

Get returns a copy of one profile's state.

func (*Registry) IsOpen

func (r *Registry) IsOpen(id int64) bool

IsOpen reports whether a profile is in its cooling-off window.

func (*Registry) Pass

func (r *Registry) Pass(id int64)

Pass records a successful call: clears the failure counters so a profile that recovers is fully trusted again (and drops the persisted row).

func (*Registry) Reset

func (r *Registry) Reset(id int64)

Reset clears one profile's state — the UI's "立即恢复" action.

func (*Registry) Restore

func (r *Registry) Restore(id int64, st State)

Restore seeds state loaded from the DB at startup (bypasses persistence).

func (*Registry) SetPolicy

func (r *Registry) SetPolicy(softTrip int, cooldown time.Duration)

SetPolicy overrides the breaker's two knobs. softTrip: how many consecutive transient failures trip it (0 = default softTripAfter; negative = transient failures never trip it, leaving only the deterministic ones). cooldown: a fixed cooling-off window (0 = the 1min/5min/30min ladder). Safe to call at any time.

func (*Registry) Snapshot

func (r *Registry) Snapshot() map[int64]State

Snapshot returns a copy of all tracked state, for the status API.

func (*Registry) Trip

func (r *Registry) Trip(id int64, errMsg string, hard bool) (tripped bool)

Trip records a failed call. hard=true marks a deterministic failure (no credit, invalid key, missing model) which opens the breaker immediately; hard=false is a transient one (429 / 5xx / network) that needs softTripAfter in a row. Returns true when this call is what opened the breaker, so the caller can log it once.

type State

type State struct {
	Fails     int       // consecutive failures; reset by a success
	Trips     int       // total trips, indexes the backoff ladder
	OpenUntil time.Time // zero / past = closed (usable)
	LastError string
	LastAt    time.Time
}

State is one profile's circuit-breaker state.

func (State) Open

func (s State) Open() bool

Open reports whether the breaker is currently open (profile should be skipped).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL