provider

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultRetryBudget = 10

DefaultRetryBudget is the per-task ceiling applied when neither the task nor the configuration overrides it. With three internal retries per provider call (the default DoWithRetry config), ten attempts cover at least three back-to-back provider invocations before the task is declared failed — enough headroom for normal flake recovery, not enough to drain a budget on a single deeply-stuck task.

View Source
const MaxErrorBodyBytes int64 = 64 << 10

MaxErrorBodyBytes caps error-path body reads. Error responses are echoed into user-facing error messages; we do not want to embed megabytes of stack traces or HTML into a wrapped error.

View Source
const MaxResponseBytes int64 = 32 << 20

MaxResponseBytes caps a fully-buffered provider response body. The largest realistic AI completion (max output tokens × ~4 bytes/char + thinking + JSON overhead) sits in the low single-digit MB range; 32 MiB is generous enough that legitimate responses are never refused while bounding the damage from a misbehaving server (or proxy injecting a huge HTML error page) that would otherwise OOM the process via io.ReadAll.

View Source
const MaxStreamLineBytes = 4 << 20

MaxStreamLineBytes caps a single line in a streamed response (SSE event or NDJSON record). bufio.Scanner's default ceiling is 64 KiB; legitimate provider events can exceed that — Anthropic occasionally emits a single thinking_delta of several hundred KiB, and OpenAI's reasoning summaries can do the same. 4 MiB leaves comfortable headroom while still bounding the damage from a misbehaving server that emits one huge line.

Variables

View Source
var EffortLevels = []string{"low", "medium", "high", "xhigh", "max"}

EffortLevels are the valid reasoning-effort levels accepted by Options.Effort, ordered from least to most effort. They mirror the claude CLI's --effort flag values.

View Source
var ErrCircuitOpen = errors.New("provider: circuit breaker is open")

ErrCircuitOpen is returned by DoWithRetry when the breaker rejects a request.

View Source
var ErrResponseTooLarge = errors.New("provider: response body exceeds maximum allowed size")

ErrResponseTooLarge is returned by ReadResponseBody when the body exceeds the supplied cap.

View Source
var ErrRetryBudgetExhausted = errors.New("provider: retry budget exhausted")

ErrRetryBudgetExhausted is returned by DoWithRetry when the per-task retry budget bound to ctx has been fully consumed. It is a distinct failure mode from a plain provider error: the upstream may be perfectly healthy, but the calling task has spent its allotment of provider attempts and the orchestrator must surface this as task-level failure rather than retrying further.

Callers branch on it with errors.Is(err, ErrRetryBudgetExhausted).

Functions

func Available

func Available() string

Available returns a comma-separated list of registered providers.

func BodyErrorStatus

func BodyErrorStatus(code int) int

BodyErrorStatus maps an HTTP status to the status a DoWithRetry closure should report when reading or parsing the response body fails. A 2xx status would be classified as a non-retryable client success, but a body failure after a 2xx is a network-level transport error — report 0 so it is retried and counted against the circuit breaker.

func DoWithRetry

func DoWithRetry(ctx context.Context, cfg RetryConfig, fn func() (statusCode int, err error)) error

DoWithRetry calls fn up to cfg.MaxAttempts times, sleeping with exponential backoff between attempts. fn returns the HTTP status code (0 for non-HTTP errors) and an error. Retries stop when fn returns nil, the context is done, or the error comes from a non-retryable HTTP status.

When ctx carries a request ID (via pkg/reqid) it is included in the returned error wrappers so a single failure can be traced from the orchestrator log → provider error → HTTP-level audit trail without having to correlate timestamps. Errors that already wrap the request ID (the inner fn might do its own wrapping) are not double-tagged: only the outermost retry/breaker error gets the prefix.

func IsRetryBudgetExhausted

func IsRetryBudgetExhausted(err error) bool

IsRetryBudgetExhausted reports whether err is, or wraps, ErrRetryBudgetExhausted. Provided as a small convenience so callers do not have to import errors and remember to use errors.Is.

func IsRetryableStatus

func IsRetryableStatus(code int) bool

IsRetryableStatus returns true for HTTP status codes worth retrying. 429 = rate limited, 500/502/503/504 = transient server errors.

func NewHTTPClient

func NewHTTPClient() *http.Client

NewHTTPClient returns an *http.Client tuned for long-lived AI provider calls.

Why a shared helper: the bare zero-value http.Client used historically had no transport timeouts, so a hung TCP connection (silently dead peer, network blip) could block a Complete() call for up to two hours waiting on the OS keepalive default. Even with context cancellation, the goroutine is stuck inside the syscall until the read returns.

We deliberately do NOT set Client.Timeout because that would cap the entire request including streaming response body reads — a long Anthropic SSE completion legitimately takes minutes and must not be cut off mid-token. Instead we set transport-level timeouts that catch the failure modes that matter: TCP connect hang, TLS handshake hang, server stalling before the first response byte, and dead peers (via TCP keepalive).

func NewHTTPTransport

func NewHTTPTransport() *http.Transport

NewHTTPTransport returns an *http.Transport with the same timeout policy as NewHTTPClient. Exposed separately so callers that need a custom Client (e.g. to set a Client.Timeout for a non-streaming request) can reuse the transport.

func NewStreamScanner

func NewStreamScanner(r io.Reader) *bufio.Scanner

NewStreamScanner returns a *bufio.Scanner configured to read SSE / NDJSON streamed responses. It uses a 64 KiB initial buffer (cheap) and grows up to MaxStreamLineBytes per line. A line that exceeds the cap surfaces as bufio.ErrTooLong via Scanner.Err() — callers should wrap that error with provider context so users can tell streaming-cap-exceeded apart from a transport failure.

func ReadResponseBody

func ReadResponseBody(r io.Reader, maxBytes int64) ([]byte, error)

ReadResponseBody reads up to maxBytes from r and returns the buffer. If the body is larger than maxBytes it returns ErrResponseTooLarge with the limit included so the user-facing error is actionable.

Implementation note: we read maxBytes+1 through io.LimitReader; if the final length is greater than maxBytes the cap was exceeded. This avoids a false positive when the body is exactly maxBytes long.

func ReadResponseBodyTruncated

func ReadResponseBodyTruncated(r io.Reader, maxBytes int64) (data []byte, truncated bool, err error)

ReadResponseBodyTruncated reads at most maxBytes from r and reports whether the underlying body was longer. Unlike ReadResponseBody, oversize is not an error — the caller wants whatever diagnostic content is available (typical use: error-path bodies that get embedded in a wrapped error message). The returned buffer is at most maxBytes long.

func Register

func Register(name string, factory ProviderFactory)

Register adds a provider factory to the global registry.

func RegisterAuditDecorator

func RegisterAuditDecorator(d func(Provider) Provider)

RegisterAuditDecorator installs the audit-log wrapper used by Build. Idempotent: calling it twice replaces the prior decorator. Pass nil to disable audit logging (useful in tests that don't want a state.db).

func RegisterBreaker

func RegisterBreaker(key string, b *CircuitBreaker)

RegisterBreaker installs a custom-configured breaker under the given key. Replaces any existing entry. Useful in tests and for tuning per provider.

func RegisteredNames

func RegisteredNames() []string

RegisteredNames returns the names of all registered providers as a slice.

func ResetAllBreakers

func ResetAllBreakers()

ResetAllBreakers wipes the breaker registry. Test helper.

func RetryBudgetExhaustedTotal

func RetryBudgetExhaustedTotal() int64

RetryBudgetExhaustedTotal returns the number of Take calls that have returned ErrRetryBudgetExhausted since process start. Used by the metrics layer to expose retry_budget_exhausted_total without having to plumb a callback through every provider implementation.

func SetHTTPMiddleware

func SetHTTPMiddleware(fn func(http.RoundTripper) http.RoundTripper) func(http.RoundTripper) http.RoundTripper

SetHTTPMiddleware installs (or removes, when fn == nil) the wrapper applied to every transport returned by NewHTTPClient. Returns the previous wrapper.

Concurrency: SetHTTPMiddleware may be called at any time, including from inside an init() before any provider is constructed. Already-built http.Client instances are NOT retroactively wrapped — only future NewHTTPClient calls pick up the change. In practice every provider builds its client in NewProvider() at startup, after init() has run.

func ValidEffort

func ValidEffort(v string) bool

ValidEffort reports whether v is a valid Options.Effort value. The empty string is valid and means "provider default".

func WithRetryBudget

func WithRetryBudget(ctx context.Context, b *RetryBudget) context.Context

WithRetryBudget returns a copy of ctx that carries b. When b is nil the original ctx is returned unchanged, so installing a missing budget at an outer layer does not clobber an inner one.

func WrapErrWithRequestID

func WrapErrWithRequestID(ctx context.Context, err error) error

WrapErrWithRequestID is the public helper for provider implementations (anthropic, openai, ollama, claudecode) to tag a direct error with the request ID carried by ctx. Streaming and one-shot paths that don't go through DoWithRetry use this so every error returned to the caller is traceable end-to-end.

Returns err unchanged when ctx carries no ID or err is nil.

Types

type BackgroundActivity

type BackgroundActivity struct {
	// Detected is how many processes were still running once the harness had
	// exited and a short grace window had passed.
	Detected int
	// Commands names what was running, for operator diagnosis. Bounded, and
	// truncated to the kernel's 15-character process names.
	Commands []string
	// Waited is how long cloop blocked waiting for the work to finish.
	Waited time.Duration
	// Drained is true when the background work finished on its own within the
	// budget. The task's output can then be trusted: the work it started is
	// genuinely complete.
	Drained bool
	// Terminated is how many processes were killed after outliving the
	// budget. Zero when Drained, or when the operator opted to keep orphans.
	Terminated int
}

BackgroundActivity describes processes an agent harness left running when it exited, and what cloop did about them.

This exists because an agent that starts a long job in the background and then reports success is indistinguishable, from its output alone, from one that actually finished the job. The distinction matters whenever a later task consumes the result: a training run still writing its checkpoint looks exactly like a finished one to the task that reads the model file.

func (*BackgroundActivity) Incomplete

func (b *BackgroundActivity) Incomplete() bool

Incomplete reports whether background work outlived its budget, meaning the task that started it has not actually finished.

func (*BackgroundActivity) Summary

func (b *BackgroundActivity) Summary() string

Summary renders the activity as one line for annotations, logs and the UI.

type BackgroundPolicyConfig

type BackgroundPolicyConfig struct {
	Disabled     bool
	GraceSeconds int
	WaitMinutes  int
	KeepOrphans  bool
}

BackgroundPolicyConfig carries pkg/config's background settings to the claudecode factory in units the factory can apply directly.

A plain struct here rather than an import of pkg/config because the dependency runs the other way round: the provider tree is imported by pkg/config's consumers, and a leaf provider must not be made to depend on the configuration package to compile.

type BreakerConfig

type BreakerConfig struct {
	// FailureThreshold is the number of failures within FailureWindow that
	// trigger a transition from closed to open. Default: 5.
	FailureThreshold int
	// FailureWindow is the rolling window in which failures are counted.
	// Default: 60s.
	FailureWindow time.Duration
	// Cooldown is the duration the breaker stays open before transitioning
	// to half-open and admitting a probe request. Default: 30s.
	Cooldown time.Duration
}

BreakerConfig configures a CircuitBreaker.

type BreakerStatus

type BreakerStatus struct {
	Key               string        `json:"key"`
	State             string        `json:"state"`
	ConsecutiveFails  int           `json:"consecutive_failures"`
	RecentFailures    int           `json:"recent_failures"`
	OpenedAt          time.Time     `json:"opened_at,omitempty"`
	CooldownRemaining time.Duration `json:"cooldown_remaining"`
	LastFailure       time.Time     `json:"last_failure,omitempty"`
	LastError         string        `json:"last_error,omitempty"`
}

BreakerStatus is a point-in-time snapshot of a breaker for surfacing in the UI or metrics layer. Safe to copy across goroutines.

func AllBreakerStatuses

func AllBreakerStatuses() []BreakerStatus

AllBreakerStatuses returns a snapshot of every registered breaker, sorted by key. The UI/metrics layer can render this directly.

type CircuitBreaker

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

CircuitBreaker tracks failures for one logical endpoint (typically a single upstream provider) and short-circuits new requests when the upstream is failing repeatedly. The zero value is not usable; call NewCircuitBreaker.

func GetBreaker

func GetBreaker(key string) *CircuitBreaker

GetBreaker returns the singleton breaker for the given key, creating one with default config if needed. Safe for concurrent use.

func NewCircuitBreaker

func NewCircuitBreaker(key string, cfg BreakerConfig) *CircuitBreaker

NewCircuitBreaker creates a breaker for the given key (used in Status).

func (*CircuitBreaker) Allow

func (b *CircuitBreaker) Allow() bool

Allow checks whether a request should be admitted. It mutates the breaker's state to handle cooldown expiry (open → half-open) and probe gating. Callers MUST follow Allow()==true with exactly one of RecordSuccess or RecordFailure when the request completes.

func (*CircuitBreaker) Config

func (b *CircuitBreaker) Config() BreakerConfig

Config returns the effective configuration (with defaults applied).

func (*CircuitBreaker) RecordFailure

func (b *CircuitBreaker) RecordFailure(err error)

RecordFailure marks the most recent admitted request as failed. May transition the breaker from closed → open (after threshold failures within the rolling window) or half-open → open (probe failed).

func (*CircuitBreaker) RecordSuccess

func (b *CircuitBreaker) RecordSuccess()

RecordSuccess marks the most recent admitted request as successful. In half-open state this closes the breaker and clears all failure history.

func (*CircuitBreaker) Reset

func (b *CircuitBreaker) Reset()

Reset returns the breaker to closed state and clears all history. Intended for tests and explicit operator action.

func (*CircuitBreaker) SetClock

func (b *CircuitBreaker) SetClock(now func() time.Time)

SetClock overrides the breaker's time source. Test-only — not concurrency safe with in-flight Allow/RecordSuccess/RecordFailure calls.

func (*CircuitBreaker) Status

func (b *CircuitBreaker) Status() BreakerStatus

Status returns a point-in-time snapshot for observation. Side-effect free except that it expires an open breaker into half-open if the cooldown has passed and there is no in-flight probe (mirrors what Allow would do).

type CircuitState

type CircuitState int

CircuitState is the operational state of a circuit breaker.

const (
	// CircuitClosed means requests flow through normally.
	CircuitClosed CircuitState = iota
	// CircuitOpen means requests are rejected without being attempted.
	CircuitOpen
	// CircuitHalfOpen means a single probe request is allowed through to test recovery.
	CircuitHalfOpen
)

func (CircuitState) String

func (s CircuitState) String() string

type Options

type Options struct {
	Model        string
	MaxTokens    int
	Timeout      time.Duration
	SystemPrompt string
	WorkDir      string
	// OnToken, if set, is called for each token chunk as it streams in.
	// When set, the provider should use streaming mode.
	// The full output is still returned in Result.Output.
	OnToken func(token string)

	// OnBackgroundWait, if set, is called once when the provider starts
	// waiting for work an agent harness left running after it exited. The
	// activity passed in is provisional: Waited and Drained are not yet known.
	//
	// It exists so a caller can surface the wait as it happens. Without it a
	// task blocked for twenty minutes on a training run the agent forgot to
	// wait for is indistinguishable, from the outside, from a task that is
	// simply slow — which is the confusion this whole mechanism exists to end.
	OnBackgroundWait func(activity BackgroundActivity)

	// Inference parameters — nil means "use provider default".
	// Temperature controls randomness (0 = deterministic, higher = more creative).
	// TopP is nucleus sampling threshold (0–1).
	// FrequencyPenalty reduces repetition (OpenAI only, 0–2).
	Temperature      *float64
	TopP             *float64
	FrequencyPenalty *float64

	// ExtendedThinking enables reasoning/thinking mode.
	// For Anthropic: sends the "thinking" block with budget_tokens=ThinkingBudget.
	// For OpenAI o1/o3/o4-mini: sets reasoning_effort based on ThinkingBudget.
	ExtendedThinking bool

	// ThinkingBudget is the token budget for reasoning/thinking content.
	// Anthropic: budget_tokens for the thinking block (default 8000).
	// OpenAI: maps to reasoning_effort ("low"/<4000, "medium"/<12000, "high"/>=12000).
	ThinkingBudget int

	// Effort is the model reasoning-effort level for providers that support
	// it. Valid values: "low", "medium", "high", "xhigh", "max"; empty means
	// provider default. Currently honored by claudecode (passed as the claude
	// CLI's --effort flag); other providers ignore it.
	Effort string
}

Options for a completion request.

type Provider

type Provider interface {
	Complete(ctx context.Context, prompt string, opts Options) (*Result, error)
	Name() string
	DefaultModel() string
}

Provider is the interface all AI backends must implement.

func Build

func Build(cfg ProviderConfig) (Provider, error)

Build creates a provider by name using the global registry.

The returned provider is wrapped (innermost → outermost) in:

real provider → WithPanicSafety → WithRequestIDTracing → audit decorator

so a panic inside the underlying SDK becomes an ordinary error, every error returned to the caller is tagged with the request ID carried in the call context, and every call (success or failure) lands in the per-project audit log. This is the single chokepoint every cloop command goes through, so wrapping here gives the behaviour to all 80+ Complete call sites without touching them.

The audit decorator runs OUTSIDE request-ID tagging so it sees the final tagged error message; that message is what ends up in the audit row. The decorator is best-effort and can never block the call.

func WithPanicSafety

func WithPanicSafety(p Provider) Provider

WithPanicSafety wraps p so that a panic raised inside p.Complete is converted into an ordinary error rather than crashing the host process.

Most cloop commands call p.Complete from a single foreground goroutine (e.g. the long-running `cloop run` orchestrator, ad-hoc commands like `cloop scope`/`cloop critique`/`cloop ask`). A nil-pointer deref inside a third-party SDK or a bad JSON cast inside a provider's response parser would otherwise tear the entire process down — losing every queued task, every cached state mutation that hadn't been flushed, and the WS server.

The Build factory below applies this wrapper unconditionally, so every provider returned to the rest of the codebase is already panic-safe. Per-package safeComplete helpers in pkg/orchestrator and pkg/multiagent remain in place as defense-in-depth; with this wrapper they should now be unreachable on the panic branch but still correct.

On panic, the full stack trace is logged to stderr at the recovery point and the returned error matches the existing "provider panic in <name>" shape so callers that already match against that string keep working.

func WithRedaction added in v0.0.2

func WithRedaction(p Provider, set *redact.Set) Provider

WithRedaction returns p with credential scrubbing applied, or p unchanged when there is nothing to scrub — which is the ordinary case for a project with no grants, and must cost nothing.

func WithRequestIDTracing

func WithRequestIDTracing(p Provider) Provider

WithRequestIDTracing decorates p so that every error returned from p.Complete is automatically tagged with the request ID carried by ctx (via pkg/reqid). Successful results pass through unchanged.

The wrapper sits OUTSIDE retry/breaker logic — retries internal to the provider already use DoWithRetry, which tags its own outermost error. What this layer adds: error paths that bypass retry (streaming-only providers, claudecode subprocess execution, request-construction failures) also receive the tag, so a future log aggregation never sees an untagged error from a request that did flow through the middleware.

In addition, when ctx carries no request ID at all the wrapper mints a fresh one (via reqid.EnsureContext) and binds it to the context handed down to the inner provider. This guarantees every provider call in the process is traceable, even when invoked from a CLI command that did not go through the HTTP middleware (e.g. `cloop run` from a shell).

The wrapper is idempotent: applying it twice has the same effect as applying it once. The factory.go Build pipeline composes it with WithPanicSafety so callers get both behaviours by default.

type ProviderConfig

type ProviderConfig struct {
	// Which provider to use
	Name string

	// Anthropic settings
	AnthropicAPIKey  string
	AnthropicBaseURL string

	// OpenAI settings
	OpenAIAPIKey  string
	OpenAIBaseURL string

	// Ollama settings
	OllamaBaseURL string

	// Mock settings
	MockResponsesFile string

	// ClaudeCodeBackground tunes the handling of work the claude CLI leaves
	// running after it exits (Task 20205). The zero value detects it, waits
	// for it, and terminates whatever outlives the wait.
	ClaudeCodeBackground BackgroundPolicyConfig
}

ProviderConfig holds settings for all providers, used by the factory.

type ProviderFactory

type ProviderFactory func(cfg ProviderConfig) (Provider, error)

ProviderFactory is a function that creates a Provider from a ProviderConfig.

type Result

type Result struct {
	Output       string
	Duration     time.Duration
	Provider     string
	Model        string
	InputTokens  int // tokens in the prompt/input
	OutputTokens int // tokens in the completion/output
	// ThinkingTokens is the number of tokens used for reasoning/thinking content
	// (Anthropic extended thinking, OpenAI reasoning tokens). Estimated from output.
	ThinkingTokens int

	// Background reports work the harness left running after it exited, for
	// providers that spawn one. nil means none was detected — which is also
	// what the HTTP providers report, since they spawn no processes at all.
	//
	// A non-nil value with Drained false means the output above cannot be
	// trusted as a finished result: the harness described work it had only
	// started. Callers that gate other work on this one must treat that as
	// incomplete rather than as success.
	Background *BackgroundActivity
}

Result from a completion request.

type RetryBudget

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

RetryBudget caps the total number of provider attempts a single logical task is allowed to consume across all DoWithRetry calls made from goroutines that share its context. A "task" here is anything the orchestrator scopes a budget to — typically one entry in pm.Plan.Tasks, but sub-agent fan-out, consensus voting, and heal retries also share the same budget so a misbehaving task cannot escape the cap by spawning helpers.

All methods are safe for concurrent use. Multiple goroutines may share a single *RetryBudget; the counter is updated with sync/atomic so parallel sub-calls cannot race past the limit.

The zero value is NOT usable; call NewRetryBudget. A nil *RetryBudget is treated as "no budget configured" — Take returns nil so existing call sites that do not wire a budget keep working unchanged.

func NewRetryBudget

func NewRetryBudget(limit int) *RetryBudget

NewRetryBudget returns a budget allowing up to limit total provider attempts. limit values <= 0 are clamped to DefaultRetryBudget so a hand-constructed Config{Task.RetryBudget: 0} or a forgotten override never produces a no-cap budget.

func RetryBudgetFromContext

func RetryBudgetFromContext(ctx context.Context) *RetryBudget

RetryBudgetFromContext returns the budget bound to ctx, or nil when none is present. Callers MUST treat a nil return as "no budget configured" and proceed normally.

func (*RetryBudget) Limit

func (b *RetryBudget) Limit() int

Limit returns the maximum number of attempts the budget allows. A nil receiver returns 0 ("no budget configured").

func (*RetryBudget) Remaining

func (b *RetryBudget) Remaining() int

Remaining returns the number of attempts still available, never negative. A nil receiver returns 0.

func (*RetryBudget) Take

func (b *RetryBudget) Take() error

Take consumes one attempt from the budget. It returns nil while the budget has room and ErrRetryBudgetExhausted (without actually rolling back the counter) once the limit has been crossed.

A nil receiver returns nil — call sites that have not wired a budget behave exactly as they did before this type existed. Concurrent calls are safe: even if N goroutines invoke Take simultaneously, at most limit of them will observe a nil return.

func (*RetryBudget) Used

func (b *RetryBudget) Used() int

Used returns the number of attempts that have been consumed so far. May exceed Limit() if Take has been called past exhaustion (the over- budget calls return ErrRetryBudgetExhausted but the counter still advances so audit logs show the true demand).

type RetryConfig

type RetryConfig struct {
	MaxAttempts  int           // total attempts including the first (default: 3)
	InitialDelay time.Duration // base delay before the first retry (default: 1s)
	MaxDelay     time.Duration // cap on delay between retries (default: 30s)

	// BreakerKey, if non-empty, gates the call through a circuit breaker
	// looked up from the global registry (or created with default config).
	// When the breaker is open the call short-circuits with ErrCircuitOpen
	// without consuming an attempt or contacting the upstream.
	BreakerKey string
}

RetryConfig controls retry behavior for provider HTTP requests.

Directories

Path Synopsis
Package anthropic provides direct access to the Anthropic Messages API.
Package anthropic provides direct access to the Anthropic Messages API.
Package cached provides a CachedProvider decorator that wraps any Provider and transparently caches responses to avoid redundant identical API calls.
Package cached provides a CachedProvider decorator that wraps any Provider and transparently caches responses to avoid redundant identical API calls.
Package claudecode wraps the claude CLI binary as a provider.
Package claudecode wraps the claude CLI binary as a provider.
Package fallback provides a Provider that tries a list of providers in order, returning the first successful result.
Package fallback provides a Provider that tries a list of providers in order, returning the first successful result.
Package mock provides a deterministic offline provider for CI and testing.
Package mock provides a deterministic offline provider for CI and testing.
Package ollama provides access to locally running Ollama models.
Package ollama provides access to locally running Ollama models.
Package openai provides access to the OpenAI Chat Completions API and any OpenAI-compatible API (e.g.
Package openai provides access to the OpenAI Chat Completions API and any OpenAI-compatible API (e.g.

Jump to

Keyboard shortcuts

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