Documentation
¶
Overview ¶
Package provider implements the real model execution layer behind the Model Gateway decision chain: an HTTP executor covering two wire dialects.
The default dialect is the OpenAI-compatible /chat/completions spoken by vLLM, Qwen, DeepSeek, GLM and any compatible endpoint. The Anthropic Messages dialect (/v1/messages) is selected per provider with Config.Protocol. Both dialects share the same guarantees: streaming, bounded retries, a per-provider circuit breaker, exact usage extraction and provider request-id capture.
Credentials never cross this boundary in either direction: the API key is resolved from the configured environment variable per request, is attached only to the outbound authentication header of the selected dialect, and is stripped from every error message and telemetry surface.
Index ¶
- Constants
- Variables
- func UsageKnownZero(err error) bool
- type CapabilityProfile
- type CircuitAcquire
- type CircuitBreaker
- type CircuitPermit
- type CircuitRecord
- type Config
- type Executor
- func (e *Executor) Complete(ctx context.Context, invocation Invocation) (Result, error)
- func (e *Executor) Health(ctx context.Context) error
- func (e *Executor) Name() string
- func (e *Executor) Stream(ctx context.Context, invocation Invocation, onDelta func(string)) (Result, error)
- func (e *Executor) StreamObserved(ctx context.Context, invocation Invocation, observer StreamObserver) (Result, error)
- func (e *Executor) Tokenizer() string
- func (e *Executor) WithCircuitBreaker(shared CircuitBreaker) *Executor
- type Invocation
- type Message
- type Registry
- func (r *Registry) Names() []string
- func (r *Registry) Register(config Config) error
- func (r *Registry) RegisterRoute(route Route) error
- func (r *Registry) Resolve(name string) (*Executor, error)
- func (r *Registry) ResolveModel(modelRef string) (*Executor, string, error)
- func (r *Registry) UseCircuitBreaker(breaker CircuitBreaker)
- type Result
- type Route
- type StreamObserver
- type ToolCall
- type ToolDefinition
Constants ¶
const ( // ProtocolOpenAI is the /chat/completions dialect spoken by OpenAI // itself and by vLLM, Qwen, DeepSeek, GLM and other compatible // endpoints. It is the default. ProtocolOpenAI = "openai" // ProtocolAnthropic is the Anthropic Messages dialect: /v1/messages // with a top-level system prompt, tool_use/tool_result content blocks // and typed server-sent events. ProtocolAnthropic = "anthropic" )
Protocol identifiers accepted in provider configuration.
Variables ¶
var ( // reached after the bounded retry budget, or one whose circuit breaker // is open (fast fail). ErrProviderUnavailable = errors.New("model provider unavailable") // ErrProviderRejected reports a non-retryable provider refusal (4xx other // than 408/429): authentication, permission, malformed request or model. ErrProviderRejected = errors.New("model provider rejected the request") // ErrStreamAborted reports a stream that failed after content had already // been delivered; it is never retried, because a retry would duplicate // billed output. ErrStreamAborted = errors.New("model provider stream aborted after delivery") // ErrCircuitOpen is returned by a shared breaker backend when another // gateway instance has opened or is probing the provider circuit. ErrCircuitOpen = errors.New("model provider circuit breaker open") )
Sentinel errors the invoker maps onto ledger outcomes.
Functions ¶
func UsageKnownZero ¶
UsageKnownZero reports whether an executor error proves the provider did not accept billable generation (for example a definitive 4xx or 429). Every ambiguous transport/5xx/partial-stream outcome returns false.
Types ¶
type CapabilityProfile ¶
type CapabilityProfile struct {
StreamUsage *bool `json:"streamUsage,omitempty"`
ToolCalling *bool `json:"toolCalling,omitempty"`
JSONSchema *bool `json:"jsonSchema,omitempty"`
Reasoning *bool `json:"reasoning,omitempty"`
Usage *bool `json:"usage,omitempty"`
RequestID *bool `json:"requestId,omitempty"`
MaxTokensField string `json:"maxTokensField,omitempty"` // max_tokens|max_completion_tokens
}
CapabilityProfile makes OpenAI-compatible dialect differences explicit. Pointer booleans distinguish an omitted field (safe default) from false.
type CircuitAcquire ¶
type CircuitBreaker ¶
type CircuitBreaker interface {
AcquireCircuit(context.Context, CircuitAcquire) (CircuitPermit, error)
RecordCircuit(context.Context, CircuitRecord) error
}
CircuitBreaker is the optional distributed provider-health coordination surface. PostgreSQL implements it so every gateway instance observes the same open/half-open state; a nil breaker retains the process-local fallback.
type CircuitPermit ¶
type CircuitPermit struct{ ProbeToken string }
type CircuitRecord ¶
type Config ¶
type Config struct {
Name string `json:"name"`
BaseURL string `json:"baseUrl"`
APIKeyEnv string `json:"apiKeyEnv"`
APIKey string `json:"-"` // programmatic (tests); file loads must use APIKeyEnv
Timeout time.Duration `json:"-"`
TimeoutMs int64 `json:"timeoutMs"`
MaxAttempts int `json:"maxAttempts"`
BreakerOpens int `json:"breakerOpens"`
BreakerCoolMs int64 `json:"breakerCooldownMs"`
MaxConcurrent int `json:"maxConcurrent"`
SupportsIdempotency bool `json:"supportsIdempotency"`
IdempotencyHeader string `json:"idempotencyHeader"`
RetryAmbiguous bool `json:"retryAmbiguous"`
HealthPath string `json:"healthPath"`
HealthMethod string `json:"healthMethod"`
Capabilities CapabilityProfile `json:"capabilities,omitempty"`
// Tokenizer names the token estimator used for this provider's budget
// reservations: "heuristic" (default, script-aware) or "conservative"
// (uncharacterized tokenizers). Provider-reported usage remains the
// authoritative settlement.
Tokenizer string `json:"tokenizer,omitempty"`
// Protocol selects the wire dialect: "openai" (the default,
// /chat/completions) or "anthropic" (/v1/messages). An empty value
// keeps the OpenAI-compatible behaviour, so existing configurations do
// not change meaning.
Protocol string `json:"protocol,omitempty"`
// ExtraHeaders are added to every outbound request before the
// dialect's own headers, so the authentication header of the selected
// dialect always wins. They carry provider hints (for example the
// OpenRouter attribution headers) and never hold credentials.
ExtraHeaders map[string]string `json:"extraHeaders,omitempty"`
}
Config is one provider endpoint. APIKeyEnv names the environment variable the key is resolved from on every call; the key itself is never stored.
type Executor ¶
type Executor struct {
// contains filtered or unexported fields
}
Executor executes invocations against one provider endpoint.
func NewExecutor ¶
NewExecutor builds the executor for one provider endpoint.
func (*Executor) Health ¶
Health probes the endpoint with a short-lived request against the provider's model list. It is the half-open probe of the circuit breaker as well as the readiness signal.
func (*Executor) Stream ¶
func (e *Executor) Stream(ctx context.Context, invocation Invocation, onDelta func(string)) (Result, error)
Stream performs one streaming invocation, delivering content deltas to onDelta as they arrive. Retries happen only before the first delta is delivered; after that, a failure aborts the stream without retry so no output is duplicated.
func (*Executor) StreamObserved ¶
func (e *Executor) StreamObserved(ctx context.Context, invocation Invocation, observer StreamObserver) (Result, error)
StreamObserved performs one streaming invocation with complete generation metering and retry-delivery semantics.
func (*Executor) Tokenizer ¶
Tokenizer returns the configured token-estimator name for budget reservations on this provider.
func (*Executor) WithCircuitBreaker ¶
func (e *Executor) WithCircuitBreaker(shared CircuitBreaker) *Executor
WithCircuitBreaker installs a distributed breaker backend.
type Invocation ¶
type Invocation struct {
ModelName string `json:"model"`
Messages []Message `json:"messages"`
Temperature *float64 `json:"temperature,omitempty"`
MaxOutputTokens int32 `json:"max_tokens,omitempty"`
Stream bool `json:"stream"`
Tools []ToolDefinition `json:"tools,omitempty"`
IdempotencyKey string `json:"-"`
TraceID string `json:"-"`
TaskID string `json:"-"`
AttemptID string `json:"-"`
ModelCallID string `json:"-"`
}
Invocation is one provider request.
type Message ¶
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
ToolCallID string `json:"tool_call_id,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
}
Message is one chat turn. Role is system|user|assistant|tool; ToolCalls carries OpenAI-style function calls on assistant turns; ToolCallID binds a tool-role turn to its call.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry resolves provider names (the first path segment of a model reference, e.g. "deepseek" in deepseek/deepseek-chat) to their executors. An empty registry fails closed: every reference resolves to an explicit "no execution endpoint configured" error rather than silently bypassing the Model Gateway.
func LoadRegistryFile ¶
LoadRegistryFile reads a provider configuration file. An empty path yields an empty (fail-closed) registry.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry builds an empty registry. client may be nil (a default HTTP client is used per executor).
func (*Registry) Register ¶
Register adds one provider endpoint configuration. A provider name is declared once: registering an existing name fails instead of silently replacing the endpoint (a duplicate in static configuration is a mistake, not a rotation).
func (*Registry) RegisterRoute ¶
RegisterRoute adds one explicit logical-model route. A modelRef is routed once: re-declaring it fails instead of silently overriding the previous route.
func (*Registry) ResolveModel ¶
ResolveModel resolves an exact logical route, falling back to the legacy provider/model convention for backwards compatibility.
func (*Registry) UseCircuitBreaker ¶
func (r *Registry) UseCircuitBreaker(breaker CircuitBreaker)
UseCircuitBreaker applies one distributed breaker to current and future provider executors.
type Result ¶
type Result struct {
Content string `json:"content"`
ToolCalls []ToolCall `json:"toolCalls,omitempty"`
FinishReason string `json:"finishReason"`
InputTokens int64 `json:"inputTokens"`
OutputTokens int64 `json:"outputTokens"`
ProviderRequestID string `json:"providerRequestId"`
UsageReported bool `json:"usageReported"`
}
Result is the outcome of one invocation. Usage is the exact provider-reported token counts (never estimated by the caller).
type Route ¶
type Route struct {
ModelRef string `json:"modelRef"`
Provider string `json:"provider"`
Model string `json:"model"`
}
Route separates a tenant-visible logical model reference from the provider endpoint and wire model selected by operations.
type StreamObserver ¶
type StreamObserver struct {
OnContent func(string)
OnGenerated func(string)
OnThinking func(string)
}
StreamObserver separates user-visible content from all provider-generated bytes. Budget meters observe content, tool names and tool arguments while callers only receive textual content.
OnThinking receives the provider's reasoning channel separately: the Anthropic thinking blocks and the reasoning fields of OpenAI-compatible endpoints. Reasoning is provider-generated text that is not part of the answer, so it is metered like any other generated byte but never delivered as content.
type ToolCall ¶
type ToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
Arguments string `json:"arguments"`
}
ToolCall is one function call the model requested.
type ToolDefinition ¶
type ToolDefinition struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters json.RawMessage `json:"parameters"`
}
ToolDefinition declares one callable tool to the model. The internal shape stays flat; encodeInvocation converts it into the OpenAI-compatible function-tool wire shape ({"type":"function","function":{…}}) at the request boundary.