Help us shape the future of Go! Take the 2026 Go Developer Survey

middleware

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: Apache-2.0 Imports: 8 Imported by: 1

Documentation

Overview

Package middleware provides composable func(Handler) Handler batteries for the model call — the "LiteLLM for Go" layer at the semantic level (handlers see messages, tool calls, and token usage, not raw bytes). Attach with agent.Agent.Use.

a := agent.New(model, store, tools...).Use(
	middleware.Retry(3),
	middleware.TokenBudget(100_000),
)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Cost

func Cost(m *CostMeter, r Rates) agent.Middleware

Cost is a model middleware that records token usage and computes running cost after each successful model call. The caller reads accumulated values via m.

func Hedge

func Hedge(delay time.Duration, backups ...agent.Model) agent.Middleware

Hedge races the model call against one or more backup models and returns the FIRST successful response, cancelling the rest. It cuts tail latency (a slow primary no longer blocks the turn, you take whichever target answers first) and adds provider-outage failover (a down or throttled primary does not stall the run). It is safe here in a way it is not in a bare service: the agent loop journals only the winning response, at most once, and the losing calls are cancelled before they can touch state, so the durable record stays exactly-once and replays deterministically regardless of which target won the race.

Scheduling:

  • The primary (the wrapped model, i.e. the one passed to agent.New) fires immediately.
  • If delay > 0, the backups fire only after the primary has been outstanding for delay, so you pay for a backup generation only when the primary is actually slow. A delay of 0 fires every target at once (pure redundancy, always pays for all of them).
  • If a target returns an ERROR while backups are still pending, the backups fire immediately (fast failover), rather than waiting out the remaining delay on a fast failure.

The first target to return a nil-error result wins; every other in-flight call is cancelled via a derived context. If every target fails, Hedge returns the joined error. With no backups it is a pass-through, so it is safe to wire unconditionally and add backups later.

Hedge only races the model generation. It does not duplicate tool calls or any other side effect: those run in the agent loop, above this middleware, under the at-most-once journal and the Safety layer. Note it hedges latency and availability, not correctness: taking the first answer is not the same as requiring agreement across models (that is a governance-quorum concern, not this).

// Primary is Anthropic; if it is quiet for 800ms, also try OpenAI and take the first.
a := agent.New(anthropicModel, store, tools...).
    Use(middleware.Hedge(800*time.Millisecond, openaiModel))

func RateLimit

func RateLimit(r *RateLimiter) agent.Middleware

RateLimit throttles model calls through the shared limiter, blocking (respecting context cancellation) until a token is available. Proactive throttling complements Retry's reactive Retry-After backoff: it keeps you under the provider's ceiling instead of bouncing off it.

func Retry

func Retry(n int, opts ...RetryOption) agent.Middleware

Retry retries the model call up to n additional times on error, respecting context cancellation. Between attempts it sleeps with exponential backoff (full jitter, base×2^attempt, capped at max). When the error is *agent.RateLimited it sleeps for min(RetryAfter, max) instead.

Default backoff: base=200ms, max=10s. Override with WithBackoff.

func Retryable

func Retryable(err error) bool

Retryable is a ready-made classifier for WithRetryIf: it retries transient failures and fails fast on terminal ones. It retries *agent.RateLimited, a per-attempt timeout (context.DeadlineExceeded), and *agent.APIError with a 5xx or 408 status; it does not retry parent cancellation (context.Canceled) or 4xx API errors (auth, validation). Errors it cannot classify (e.g. raw network errors) are retried, since those are usually transient.

agent.New(model, store, tools...).Use(middleware.Retry(3, middleware.WithRetryIf(middleware.Retryable)))

func TokenBudget

func TokenBudget(max int) agent.Middleware

TokenBudget aborts the run once cumulative tokens (input+output) across the run's model calls exceed max. The cap is a HARD ceiling: the call that would exceed it is refused. State is per-middleware-instance, so it accumulates across the loop's turns.

func ToolCache

func ToolCache() agent.ToolMiddleware

ToolCache memoizes successful tool results by (name, args) in a process-local map and short-circuits a repeat call — the tool never runs on a hit. It demonstrates the short-circuit power of tool middleware.

Use ONLY for pure / read-only tools whose output depends solely on their arguments: a cached result skips the real call entirely. Errors are never cached. This is an in-memory, unbounded, per-instance cache — it is NOT the durable journal (which already dedupes each tool-use ID at-most-once); it dedupes DISTINCT calls with identical arguments within one process.

func ToolLog

func ToolLog(logf func(format string, args ...any)) agent.ToolMiddleware

ToolLog logs one line per tool call — name, duration, and ok/error — via logf (pass log.Printf, t.Logf, or a structured logger's Printf-shaped method). Attach with agent.Agent.UseTool.

a := agent.New(model, store, tools...).UseTool(middleware.ToolLog(log.Printf))

func ToolRateLimit

func ToolRateLimit(r *RateLimiter) agent.ToolMiddleware

ToolRateLimit throttles tool calls through the shared limiter.

func ToolRetry

func ToolRetry(n int, opts ...RetryOption) agent.ToolMiddleware

ToolRetry retries a failing tool call up to n additional times with exponential backoff + jitter (honoring an *agent.RateLimited RetryAfter), respecting context cancellation. Reuses RetryOption/WithBackoff from the model-side Retry.

Default backoff: base=200ms, max=10s. Override with WithBackoff.

a := agent.New(model, store, tools...).UseTool(middleware.ToolRetry(3))

Types

type CostMeter

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

CostMeter accumulates token usage and computed cost across model calls. It is safe for concurrent use.

func (*CostMeter) Total

func (m *CostMeter) Total() float64

Total returns the accumulated USD cost so far.

func (*CostMeter) Usage

func (m *CostMeter) Usage() agent.Usage

Usage returns the accumulated token usage so far.

type RateLimiter

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

RateLimiter is a token-bucket limiter shared across calls: it allows one token every `interval` with a burst up to `burst`. It is dependency-free (no background goroutine): tokens accrue lazily from elapsed time. Safe for concurrent use. Share one limiter across model and tool middleware to cap a whole agent, or use separate limiters per surface.

func NewRateLimiter

func NewRateLimiter(interval time.Duration, burst int) *RateLimiter

NewRateLimiter allows one call every `interval` (so rate = 1/interval), bursting up to `burst` calls. A burst < 1 is treated as 1. Starts full so the first `burst` calls proceed immediately.

type Rates

type Rates struct {
	InputPer1M      float64
	OutputPer1M     float64
	CacheReadPer1M  float64
	CacheWritePer1M float64
}

Rates holds the billing rates for a model in USD per 1,000,000 tokens.

func (Rates) Cost

func (r Rates) Cost(u agent.Usage) float64

Cost returns the USD cost of the token usage u at these rates.

type RetryOption

type RetryOption func(*retryConfig)

RetryOption configures optional behavior of Retry.

func WithBackoff

func WithBackoff(base, max time.Duration) RetryOption

WithBackoff sets the initial backoff duration and the maximum cap. Between retries the sleep doubles each attempt (full jitter applied) and is capped at max. If the error is *agent.RateLimited the Retry-After hint overrides the computed backoff (capped at max).

func WithRetryIf

func WithRetryIf(pred func(error) bool) RetryOption

WithRetryIf classifies which errors are worth retrying: pred returns true to retry, false to fail fast. Use it to stop burning attempts (and tokens) on terminal errors such as HTTP 4xx auth/validation failures, while still retrying transient ones. The default (nil) retries every error. Context cancellation always stops the loop regardless. See Retryable for a ready-made classifier.

func WithTimeout

func WithTimeout(d time.Duration) RetryOption

WithTimeout bounds each individual attempt with its own deadline: the call gets a context that is cancelled after d, so a hung model or tool call fails that attempt (and is retried) instead of blocking the run forever. The parent context still governs overall cancellation. 0 (default) means no per-attempt timeout.

Jump to

Keyboard shortcuts

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