proxy

package
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Sep 11, 2026 License: AGPL-3.0 Imports: 36 Imported by: 0

Documentation

Overview

Package proxy provides a high-performance LLM request proxy with support for multiple providers, intelligent routing, caching, and extensible middleware.

Package proxy provides the core business logic for LLM request proxying

Index

Constants

View Source
const (
	// CacheStatusKey is the context key for cache status
	CacheStatusKey contextKey = "X-Cache"

	// CacheStatusHit indicates a cache hit
	CacheStatusHit = "HIT"
	// CacheStatusMiss indicates a cache miss
	CacheStatusMiss = "MISS"
)
View Source
const OverheadHeader = "x-starport-overhead-ms"

OverheadHeader carries the gateway-added latency of one proxied response in whole milliseconds. The value excludes upstream provider time; on a stream it covers the work before the first byte reaches the client.

View Source
const PresetReferencePrefix = "@preset/"

PresetReferencePrefix selects a stored preset through the model field, matching OpenRouter's "@preset/<name>" reference.

Variables

View Source
var (
	// ErrNoValidModel indicates no valid model was specified
	ErrNoValidModel = errors.New("no valid model specified")

	// ErrNoAvailableProvider indicates no provider is available for the request
	ErrNoAvailableProvider = errors.New("no available provider for model")

	// ErrInvalidRequest indicates the request is malformed
	ErrInvalidRequest = errors.New("invalid request")

	// ErrProviderUnavailable indicates the provider is temporarily unavailable
	ErrProviderUnavailable = errors.New("provider temporarily unavailable")

	// ErrRateLimitExceeded indicates rate limit has been exceeded
	ErrRateLimitExceeded = errors.New("rate limit exceeded")

	// ErrInsufficientQuota indicates the user has insufficient quota
	ErrInsufficientQuota = errors.New("insufficient quota")

	// ErrStreamingNotSupported indicates streaming is not supported for this request
	ErrStreamingNotSupported = errors.New("streaming not supported")

	// ErrEmbeddingsNotSupported indicates embeddings are not supported by the provider
	ErrEmbeddingsNotSupported = errors.New("embeddings not supported by provider")

	// ErrPresetNotFound indicates a request referenced an unknown preset
	ErrPresetNotFound = errors.New("preset not found")
	// ErrStoredFileNotFound indicates a request referenced a stored file this
	// account does not hold. A file another account holds reads the same way,
	// because a different answer would report which identifiers exist.
	ErrStoredFileNotFound = errors.New("stored file not found")
)

Common proxy errors

Functions

func ExtractProviderFromModel

func ExtractProviderFromModel(modelID string) (provider, model string)

ExtractProviderFromModel extracts a provider-scoped model ID.

func OverheadMS added in v1.1.0

func OverheadMS(ctx context.Context) (int64, bool)

OverheadMS reports the gateway-added milliseconds measured so far. The second result is false when the request never started a timer.

func StartOverhead added in v1.1.0

func StartOverhead(ctx context.Context) context.Context

StartOverhead begins overhead measurement for one request. The HTTP layer calls it once before dispatch; attempt callbacks mark upstream waits on the same timer through the context.

func TransformChatRequest

func TransformChatRequest(req *ChatCompletionRequest) (*connectors.ChatRequest, error)

TransformChatRequest converts the canonical use-case request at the provider boundary.

func TransformEmbeddingsRequest

func TransformEmbeddingsRequest(req *EmbeddingsRequest) *connectors.EmbeddingsRequest

TransformEmbeddingsRequest converts a proxy EmbeddingsRequest to a connector EmbeddingsRequest

func ValidateChatCompletionRequest

func ValidateChatCompletionRequest(req *ChatCompletionRequest) error

ValidateChatCompletionRequest validates one canonical gateway chat request.

func ValidateEmbeddingsRequest

func ValidateEmbeddingsRequest(req *EmbeddingsRequest) error

ValidateEmbeddingsRequest validates one canonical embedding request.

func ValidateImagesRequest added in v1.1.0

func ValidateImagesRequest(req *ImagesRequest) error

ValidateImagesRequest checks one image generation or image edit request.

func ValidateModerationRequest added in v1.2.0

func ValidateModerationRequest(req *ModerationRequest) error

ValidateModerationRequest checks one canonical moderation request. Like the rerank validator, it repeats the emptiness checks the codec performed so the guarantee belongs to the operation rather than to one wire format.

func ValidateRerankRequest added in v1.1.0

func ValidateRerankRequest(req *RerankRequest) error

ValidateRerankRequest checks one canonical rerank request. The codec already refuses an empty query and an empty document list, so this guard covers the caller the codec does not cover: a gateway-internal one that builds the request itself.

func ValidateSpeechRequest added in v1.1.0

func ValidateSpeechRequest(req *SpeechRequest) error

ValidateSpeechRequest checks one text-to-speech request.

func ValidateTranscriptionRequest added in v1.1.0

func ValidateTranscriptionRequest(req *TranscriptionRequest) error

ValidateTranscriptionRequest checks one speech-to-text request.

func ValidateVideoAssetReference added in v1.1.0

func ValidateVideoAssetReference(req *VideoAssetRequest) error

ValidateVideoAssetReference checks one read of a finished job's asset. It adds the stored bound to the reference checks: a read with no bound would let the provider's answer size this deployment's storage.

func ValidateVideoJobReference added in v1.1.0

func ValidateVideoJobReference(req *VideoJobRequest) error

ValidateVideoJobReference checks one poll or cancel of an accepted job. A reference with no provider would plan a route to whichever provider the catalog ranked first, and that provider never issued the identifier.

func ValidateVideoJobRequest added in v1.1.0

func ValidateVideoJobRequest(req *VideoSubmitRequest) error

ValidateVideoJobRequest checks one video generation submission.

Types

type APIKeyRoutingConfig

type APIKeyRoutingConfig struct {
	AllowedProviders   []string
	AllowedModels      []string
	ModelOverrides     map[string]string
	RateLimitTier      string
	CredentialStrategy keyring.Strategy

	// Access carries the account's paired provider and model grants. Nil
	// grants every provider and model.
	Access []routing.ProviderAccess

	// BYOKProviders gates which providers the BYOK credential source may
	// serve: nil allows every provider, an empty list none, a non-empty
	// list only its members. It is the account's BYOK policy resolved to
	// plain data, so this package never learns the account vocabulary.
	BYOKProviders *[]string
}

APIKeyRoutingConfig contains API-key scoped routing restrictions.

type AuthorInfo added in v1.1.0

type AuthorInfo = view.AuthorInfo

AuthorInfo represents one catalog author or organization.

type AuthorsResponse added in v1.1.0

type AuthorsResponse struct {
	Authors []AuthorInfo `json:"authors"`

	// Internal fields (not serialized)
	CacheStatus string `json:"-"`
}

AuthorsResponse represents catalog author information

type CacheConfig

type CacheConfig struct {
	// Enable caching for different endpoints
	EnableChatCache      bool `env:"ENABLE_CHAT_CACHE,default=true"`
	EnableEmbeddingCache bool `env:"ENABLE_EMBEDDING_CACHE,default=true"`
	EnableModelCache     bool `env:"ENABLE_MODEL_CACHE,default=true"`
	EnableProviderCache  bool `env:"ENABLE_PROVIDER_CACHE,default=true"`
	// Skip cache for specific models or patterns
	SkipCacheModels []string `env:"SKIP_CACHE_MODELS"`
	// Force cache refresh header
	CacheControlHeader string `env:"CACHE_CONTROL_HEADER,default=X-Cache-Control"`

	// EnableSemanticCache turns on the similarity layer beside the exact
	// identity. Each request still opts in per call, and the layer only
	// runs where the exact cache already deemed the request cacheable.
	// Composition sets these fields from the deployment configuration, so
	// they carry no env tags.
	EnableSemanticCache bool
	// SemanticThreshold is the minimum cosine similarity that answers.
	// Zero takes the cache package default.
	SemanticThreshold float64
	// SemanticMaxEntries bounds the vectors one similarity scope holds.
	// Zero takes the cache package default.
	SemanticMaxEntries int
	// SemanticEmbedder embeds the canonical prompt text through the
	// gateway's own embeddings path. Nil keeps the layer off.
	SemanticEmbedder SemanticEmbedder
}

CacheConfig defines caching behavior

type CacheCost

type CacheCost struct {
	WriteTokens float64 `json:"write_tokens"` // Cost of writing tokens to cache
	ReadTokens  float64 `json:"read_tokens"`  // Cost of reading tokens from cache
	TotalCost   float64 `json:"total_cost"`   // Total cost including cache operations
}

CacheCost represents the cost of cache operations

type CacheManager

type CacheManager interface {
	GetModel(ctx context.Context, key string) (any, bool, error)
	SetModel(ctx context.Context, key string, value any) error
	GetResponse(ctx context.Context, key string) ([]byte, bool, error)
	SetResponse(ctx context.Context, key string, response []byte) error
}

CacheManager interface defines the cache operations used by the proxy

type CacheMiddleware

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

CacheMiddleware provides caching functionality for proxy services.

func (*CacheMiddleware) Wrap

func (m *CacheMiddleware) Wrap(next Proxy) Proxy

Wrap wraps the service with caching functionality.

type CacheSimilarityProvider added in v1.2.0

type CacheSimilarityProvider interface {
	GetCacheSimilarity() float64
}

CacheSimilarityProvider reports the cosine similarity a semantic cache hit served under. A stream that does not implement it, or answers zero, served exactly or not from cache at all.

type CacheStatusProvider

type CacheStatusProvider interface {
	GetCacheStatus() string
	GetCacheAge() int // Returns cache age in seconds, or 0 if not cached
}

CacheStatusProvider is an interface to check cache status on streams

type ChatCompletionRequest

type ChatCompletionRequest struct {
	Request  inference.ChatRequest
	Route    string
	Provider *ProviderPreferences
	// Preset names a stored preset selected by the request body. A
	// "@preset/<name>" model reference selects one the same way and wins
	// over this field.
	Preset string

	// Internal fields
	APIKey string `json:"-"`
	// AccountID is the account the request runs under. Credential selection,
	// response cache scope, and account limits read this.
	AccountID string `json:"-"`
	// KeyID is the gateway API key that authenticated the request. Usage
	// attribution and per-key limits read this. Many keys share one account, so
	// the two values are distinct and neither substitutes for the other.
	KeyID string `json:"-"`
	// TeamID is the team the serving key is attributed to, or empty for a
	// teamless key. Usage attribution and the team budget read this.
	TeamID       string               `json:"-"`
	APIKeyConfig *APIKeyRoutingConfig `json:"-"`
	RequestID    string               `json:"-"`
	Protocol     string               `json:"-"` // Protocol surface that received the request
	// BatchID names the batch this request runs inside, or is empty for an
	// online request. The usage record carries it, so an operator can read
	// what one batch spent.
	BatchID string `json:"-"`
	// SemanticCache opts this request into the similarity layer, when the
	// deployment enables one. The X-Semantic-Cache header states it.
	SemanticCache bool `json:"-"`
}

ChatCompletionRequest is a canonical chat request plus gateway policy and identity.

type ChatCompletionResponse

type ChatCompletionResponse struct {
	Response inference.ChatResponse

	// ExtractionCached reports that every document this turn attached came
	// back from the extraction cache. It is separate from CacheStatus below,
	// which reports the response cache: a turn can read its attachments from
	// the cache and still call the model, and that is the common case for a
	// conversation about one document.
	ExtractionCached bool `json:"-"`

	// The rest of the document read, as the usage record reports it: which
	// engine ran, how many pages it read each way, which model recognized
	// them, what that cost in integer nano-USD, and how long it took.
	ExtractionEngine   string        `json:"-"`
	ExtractionPages    int           `json:"-"`
	RecognizedPages    int           `json:"-"`
	NativePages        int           `json:"-"`
	ExtractionOffering string        `json:"-"`
	ExtractionNanoUSD  int64         `json:"-"`
	ExtractionUnpriced bool          `json:"-"`
	ExtractionDuration time.Duration `json:"-"`

	// Internal fields (not serialized)
	CacheStatus string     `json:"-"`
	CacheAge    int        `json:"-"` // Seconds since cached
	ETag        string     `json:"-"` // Entity tag for response
	CacheCost   *CacheCost `json:"-"` // Cache pricing information
	// CacheSimilarity is the cosine similarity a semantic hit served
	// under, or zero for an exact hit or a miss. The X-Cache-Similarity
	// header reports it beside the shared cache status vocabulary.
	CacheSimilarity float64 `json:"-"`

	// Route evidence (not serialized)
	ProviderUsed     string                           `json:"-"`
	CredentialSource string                           `json:"-"`
	Attempts         int                              `json:"-"`
	RoutingDuration  time.Duration                    `json:"-"`
	CatalogSnapshot  *runtimecatalog.RoutableSnapshot `json:"-"`

	// GuardrailVerdict is the strongest verdict the guardrail pipeline
	// answered over this turn: allow, redact, or refuse. Empty means no
	// guardrail ran. The usage record carries it; the wire never does.
	GuardrailVerdict string `json:"-"`
}

ChatCompletionResponse is a canonical result plus gateway response metadata.

func TransformChatResponse

func TransformChatResponse(resp *connectors.ChatResponse, modelUsed string) (*ChatCompletionResponse, error)

TransformChatResponse converts a connector ChatResponse to a proxy ChatCompletionResponse

type ChatCompletionStreamResponse

type ChatCompletionStreamResponse interface {
	// Read returns the next canonical event or io.EOF when done.
	Read() (*inference.StreamEvent, error)

	// Close releases any resources
	Close() error
}

ChatCompletionStreamResponse represents a streaming response

type Config

type Config struct {
	// Registry provides access to LLM provider connectors
	Registry connectors.LeasingRegistry

	// Router handles intelligent model selection and failover
	Router router.ModelRouter

	// CacheManager handles response caching (optional)
	CacheManager CacheManager

	// CacheConfig configures caching behavior (optional)
	CacheConfig *CacheConfig

	// TokenEstimator synthesizes estimated usage for streams that end
	// without provider-reported usage (optional)
	TokenEstimator *tokenize.Estimator

	// FileResolver reads the bytes behind a stored document reference
	// (optional). A deployment without one refuses a request that names a
	// file rather than sending the model an empty document.
	FileResolver FileResolver

	// DocumentExtractor is the native parser engine, with the page and time
	// bounds this deployment sets (optional). A deployment without one reads
	// documents under the engine's own default bounds.
	DocumentExtractor *document.Extractor

	// DocumentCache holds one document's text for reuse inside a window
	// (optional). A deployment without one reads every attachment on every
	// turn, which is correct and pays the page price each time.
	DocumentCache *document.Cache
}

Config holds the configuration for creating a new proxy service.

type CredentialFieldInfo added in v1.1.0

type CredentialFieldInfo = view.CredentialFieldInfo

CredentialFieldInfo is the catalog-declared inference credential field a caller supplies for BYOK. It carries no secret values.

type EmbeddingsRequest

type EmbeddingsRequest struct {
	Request inference.EmbeddingRequest

	// Internal fields
	APIKey string `json:"-"`
	// AccountID is the account the request runs under. Credential selection,
	// response cache scope, and account limits read this.
	AccountID string `json:"-"`
	// KeyID is the gateway API key that authenticated the request. Usage
	// attribution and per-key limits read this. Many keys share one account, so
	// the two values are distinct and neither substitutes for the other.
	KeyID string `json:"-"`
	// TeamID is the team the serving key is attributed to, or empty for a
	// teamless key. Usage attribution and the team budget read this.
	TeamID       string               `json:"-"`
	APIKeyConfig *APIKeyRoutingConfig `json:"-"`
	RequestID    string               `json:"-"`
	Protocol     string               `json:"-"` // Protocol surface that received the request
	// BatchID names the batch this request runs inside, or is empty for an
	// online request. The usage record carries it, so an operator can read
	// what one batch spent.
	BatchID string `json:"-"`
}

EmbeddingsRequest is a canonical embedding request plus gateway identity.

type EmbeddingsResponse

type EmbeddingsResponse struct {
	Response inference.EmbeddingResponse

	// Internal fields (not serialized)
	CacheStatus string     `json:"-"`
	CacheAge    int        `json:"-"` // Seconds since cached
	ETag        string     `json:"-"` // Entity tag for response
	CacheCost   *CacheCost `json:"-"` // Cache pricing information

	// Route evidence (not serialized)
	ModelUsed        string                           `json:"-"`
	ProviderUsed     string                           `json:"-"`
	CredentialSource string                           `json:"-"`
	Attempts         int                              `json:"-"`
	RoutingDuration  time.Duration                    `json:"-"`
	CatalogSnapshot  *runtimecatalog.RoutableSnapshot `json:"-"`
}

EmbeddingsResponse is a canonical embedding result plus gateway metadata.

type EndpointInfo

type EndpointInfo = view.EndpointInfo

EndpointInfo represents an endpoint that can serve a model

type FileResolver added in v1.1.0

type FileResolver interface {
	ResolveDocument(ctx context.Context, account, id string) (StoredDocument, bool, error)
}

FileResolver reads one stored document for one account.

This package names the port rather than importing the file service, so the concept that owns a stored file keeps its own vocabulary and the proxy keeps the one shape it needs.

The account argument is not advisory. A resolver reports an identifier belonging to another account as absent, exactly as it reports an unknown identifier, because an answer that distinguished the two would tell one account which identifiers another account holds.

type GatewayEmbedder added in v1.2.0

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

GatewayEmbedder adapts the gateway's own embeddings surface to the SemanticEmbedder seam. The embedding rides the account's own identity: credential selection, usage capture, and limits treat it as the account's own embedding request. The gateway is late-bound because composition finishes after the cache middleware is built.

func NewGatewayEmbedder added in v1.2.0

func NewGatewayEmbedder(model string, gateway func() Proxy) *GatewayEmbedder

NewGatewayEmbedder builds the adapter around a catalog embedding model and a late-bound gateway.

func (*GatewayEmbedder) Embed added in v1.2.0

func (e *GatewayEmbedder) Embed(ctx context.Context, identity SemanticEmbedIdentity, text string) ([]float32, error)

Embed implements SemanticEmbedder over the gateway's embeddings path.

type GatewayModerator added in v1.2.0

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

GatewayModerator adapts the gateway's own moderation surface to the guardrail Moderator seam. The classification rides the account's own routing: it runs under the calling request's identity, so credential selection, usage capture, and limits treat it as the account's own moderation request. The gateway is late-bound because composition finishes after the guardrail pipeline is built.

func NewGatewayModerator added in v1.2.0

func NewGatewayModerator(model string, gateway func() Proxy) *GatewayModerator

NewGatewayModerator builds the adapter around a catalog moderation model and a late-bound gateway.

func (*GatewayModerator) Moderate added in v1.2.0

func (m *GatewayModerator) Moderate(ctx context.Context, text string) ([]guardrails.CategoryScore, error)

Moderate implements guardrails.Moderator.

type GuardrailVerdictProvider added in v1.2.0

type GuardrailVerdictProvider interface {
	GuardrailVerdict() string
}

GuardrailVerdictProvider exposes the strongest verdict a stream saw, so the usage middleware outside this one can record it at stream end.

type Guardrails added in v1.2.0

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

Guardrails is a proxy middleware that runs the account's guardrail pipeline over canonical text: the request messages before planning, and the answer before the caller reads it. Composition skips this middleware entirely when no check is configured, so an unconfigured deployment pays nothing here.

func NewGuardrails added in v1.2.0

func NewGuardrails(policy guardrails.Policy) *Guardrails

NewGuardrails creates the middleware around one policy.

func (*Guardrails) Wrap added in v1.2.0

func (g *Guardrails) Wrap(next Proxy) Proxy

Wrap implements Middleware.

type ImagesRequest added in v1.1.0

type ImagesRequest = OperationRequest[inference.ImagesRequest]

ImagesRequest is one gateway image generation or image edit request.

type ImagesResponse added in v1.1.0

type ImagesResponse = OperationResponse[inference.ImagesResponse]

ImagesResponse is one gateway image result.

type JobAccountant added in v1.1.0

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

JobAccountant prices one finished job and writes its single usage record.

It lives here rather than in internal/jobs because pricing reads a Starmap offering, and internal/jobs owns job state and nothing else. It lives here rather than in the composition root because this package already holds the catalog-to-cost rules that every other operation uses, and a video priced by a second copy of them would drift from the rest of the bill.

It is deliberately not a method on Proxy. Nothing on the request path calls it: a job settles when a poll, a cancel, or the sweep reaches its terminal state, which may be long after the submitting request returned.

func NewJobAccountant added in v1.1.0

func NewJobAccountant(
	snapshots func() *runtimecatalog.RoutableSnapshot,
	recorder UsageRecorder,
) *JobAccountant

NewJobAccountant returns an accountant over one catalog reader and one record store. Either may be absent, which is what a deployment with usage recording switched off gets: the job still settles and still frees its slot.

func (*JobAccountant) RecordJob added in v1.1.0

func (a *JobAccountant) RecordJob(ctx context.Context, entry jobs.AccountingEntry) error

RecordJob writes the one usage record a terminal job draws.

A failed job and a cancelled job draw a record with no cost rather than no record. The work is a real event in the account's history, and a spend report that showed only the jobs that succeeded would answer "what did this account do" with a shorter list than the truth. Their cost is zero, which is what CostReasonNoUsage already means everywhere else.

type MediaRequest added in v1.1.0

type MediaRequest[Request any] = OperationRequest[Request]

MediaRequest routes one canonical media request. The shared carrier is OperationRequest.

type MediaResponse added in v1.1.0

type MediaResponse[Response any] = OperationResponse[Response]

MediaResponse is one media result. The shared carrier is OperationResponse: reranking is not media, and a media name on the answer it returns would say it was.

type Middleware

type Middleware interface {
	// Wrap wraps the given proxy with the middleware functionality
	Wrap(Proxy) Proxy
}

Middleware wraps a Proxy with additional functionality. The caching, preset-resolution, and usage-capture seams each implement it, and the composition root layers them around the core proxy.

func NewCacheMiddleware

func NewCacheMiddleware(manager CacheManager, config *CacheConfig, runtime runtimeGenerationSource) Middleware

NewCacheMiddleware creates a new cache middleware.

type ModelArchitecture

type ModelArchitecture = view.ModelArchitecture

ModelArchitecture describes protocol-facing model capabilities.

type ModelAuthorInfo added in v1.1.0

type ModelAuthorInfo = view.ModelAuthorInfo

ModelAuthorInfo names one catalog author of a model.

type ModelEndpointsResponse

type ModelEndpointsResponse struct {
	Model     string         `json:"model"`
	Endpoints []EndpointInfo `json:"endpoints"`
}

ModelEndpointsResponse represents available endpoints for a model

type ModelInfo

type ModelInfo = view.ModelInfo

ModelInfo represents model information

type ModelLineageInfo added in v1.1.0

type ModelLineageInfo = view.ModelLineageInfo

ModelLineageInfo describes canonical model-family relationships.

type ModelOfferingInfo added in v1.1.0

type ModelOfferingInfo = view.ModelOfferingInfo

ModelOfferingInfo is one provider's routable offering of a model.

type ModelPricing

type ModelPricing = view.ModelPricing

ModelPricing represents model pricing information

type ModelsResponse

type ModelsResponse struct {
	Object string      `json:"object"`
	Data   []ModelInfo `json:"data"`

	// Internal fields (not serialized)
	CacheStatus string `json:"-"`
}

ModelsResponse represents a list of available models

type ModerationRequest added in v1.2.0

type ModerationRequest = OperationRequest[inference.ModerationRequest]

ModerationRequest is one gateway moderation request.

type ModerationResponse added in v1.2.0

type ModerationResponse = OperationResponse[inference.ModerationResponse]

ModerationResponse is one gateway classified input list. It is not cached for the same reason a rerank answer is not: the result reads by position in one caller's own input list.

type OfferingPricingInfo added in v1.1.0

type OfferingPricingInfo = view.OfferingPricingInfo

OfferingPricingInfo carries every token price dimension of one offering.

type OperationRequest added in v1.1.0

type OperationRequest[Request any] struct {
	Request Request

	APIKey string `json:"-"`
	// AccountID is the account the request runs under. Credential selection
	// and account limits read this.
	AccountID string `json:"-"`
	// KeyID is the gateway API key that authenticated the request. Usage
	// attribution and per-key limits read this.
	KeyID string `json:"-"`
	// TeamID is the team the serving key is attributed to, or empty for a
	// teamless key. Usage attribution and the team budget read this.
	TeamID       string               `json:"-"`
	APIKeyConfig *APIKeyRoutingConfig `json:"-"`
	RequestID    string               `json:"-"`
	Protocol     string               `json:"-"`
}

OperationRequest is one canonical request plus gateway identity. Every operation past chat carries the identity a chat request carries, so one type states it once.

type OperationResponse added in v1.1.0

type OperationResponse[Response any] struct {
	Response Response

	ModelUsed        string                           `json:"-"`
	ProviderUsed     string                           `json:"-"`
	CredentialSource string                           `json:"-"`
	Attempts         int                              `json:"-"`
	RoutingDuration  time.Duration                    `json:"-"`
	CatalogSnapshot  *runtimecatalog.RoutableSnapshot `json:"-"`

	// Cost is what this turn cost, once the accounting middleware has priced it
	// against the snapshot that routed it. It is nil until then, and it stays
	// nil on a turn the catalog could not price. A protocol that names a cost on
	// its usage block reads this field, so the number the caller sees and the
	// number the account is billed come from one derivation.
	Cost *usage.Cost `json:"-"`
}

OperationResponse is one canonical result plus gateway route evidence. It carries no cache fields. A media answer is not cached, because an image and an audio file are large and a caller that repeats a prompt expects a new rendering rather than the previous one. A rerank answer is not cached either, because its result names positions in one caller's own document list and a second caller's list holds different text at those positions.

type Option

type Option func(*Config)

Option configures the proxy service.

func WithCache

func WithCache(manager CacheManager, config *CacheConfig) Option

WithCache enables caching with the specified cache manager and configuration.

func WithDocumentCache added in v1.1.0

func WithDocumentCache(cache *document.Cache) Option

WithDocumentCache reuses one document's text across the turns of a conversation that keeps resending it.

func WithDocumentExtractor added in v1.1.0

func WithDocumentExtractor(extractor *document.Extractor) Option

WithDocumentExtractor sets the bounds the native parser engine reads under.

func WithFiles added in v1.1.0

func WithFiles(resolver FileResolver) Option

WithFiles lets a chat request name a document this gateway already stores instead of carrying its bytes.

func WithTokenEstimator added in v1.1.0

func WithTokenEstimator(estimator *tokenize.Estimator) Option

WithTokenEstimator guarantees every chat stream ends with a usage event: when the provider reports none, the estimator synthesizes estimated counts.

type PresetResolver added in v1.1.0

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

PresetResolver is a proxy middleware that resolves preset references on chat requests and merges the stored configuration into them. Fields the request supplies win over preset fields; an unknown preset fails the request with ErrPresetNotFound before any routing happens.

func NewPresetResolver added in v1.1.0

func NewPresetResolver(source PresetSource) *PresetResolver

NewPresetResolver creates the resolution middleware around one source.

func (*PresetResolver) Wrap added in v1.1.0

func (r *PresetResolver) Wrap(next Proxy) Proxy

Wrap implements Middleware.

type PresetSource added in v1.1.0

type PresetSource interface {
	Get(ctx context.Context, name string) (presets.Record, error)
	GetRevision(ctx context.Context, name string, revision uint64) (presets.Record, error)
}

PresetSource resolves one stored preset by name, or one pinned revision of it. The concept-owned repository in internal/presets satisfies it.

type ProviderError

type ProviderError struct {
	Provider string
	Code     string
	Message  string
	Err      error
}

ProviderError represents an error from a provider

func (*ProviderError) Error

func (e *ProviderError) Error() string

func (*ProviderError) Unwrap

func (e *ProviderError) Unwrap() error

type ProviderInfo

type ProviderInfo = view.ProviderInfo

ProviderInfo represents provider metadata

type ProviderPolicyInfo added in v1.1.0

type ProviderPolicyInfo = view.ProviderPolicyInfo

ProviderPolicyInfo summarizes the provider's published data policies.

type ProviderPreferences

type ProviderPreferences struct {
	Order         []string `json:"order,omitempty"`
	Ignore        []string `json:"ignore,omitempty"`
	Only          []string `json:"only,omitempty"`
	AllowFallback bool     `json:"allow_fallback,omitempty"`

	// Sort selects route ordering: "price", "latency", "throughput"
	// (routed by measured latency), or "spread" (weighted balance inside
	// the leading ranking band). Empty keeps the server default.
	Sort string `json:"sort,omitempty"`

	// MaxPromptPricePer1M and MaxCompletionPricePer1M cap the accepted route
	// price in USD per million tokens. Zero means no cap.
	MaxPromptPricePer1M     float64 `json:"max_prompt_price_per_1m,omitempty"`
	MaxCompletionPricePer1M float64 `json:"max_completion_price_per_1m,omitempty"`
}

ProviderPreferences represents provider routing preferences

type ProvidersResponse

type ProvidersResponse struct {
	Providers []ProviderInfo `json:"providers"`

	// Internal fields (not serialized)
	CacheStatus string `json:"-"`
}

ProvidersResponse represents provider information

type Proxy

type Proxy interface {
	// ProcessChatCompletion handles chat completion requests with routing and processing
	ProcessChatCompletion(ctx context.Context, req *ChatCompletionRequest) (*ChatCompletionResponse, error)

	// ProcessChatCompletionStream handles streaming chat completion requests
	ProcessChatCompletionStream(ctx context.Context, req *ChatCompletionRequest) (ChatCompletionStreamResponse, error)

	// ProcessEmbeddings handles embedding generation requests
	ProcessEmbeddings(ctx context.Context, req *EmbeddingsRequest) (*EmbeddingsResponse, error)

	// ProcessRerank scores one document list against a query and answers with
	// the caller's own document positions in ranked order.
	ProcessRerank(ctx context.Context, req *RerankRequest) (*RerankResponse, error)

	// ProcessModerations classifies each input against a provider's harm
	// categories and answers with a verdict and a score per category.
	ProcessModerations(ctx context.Context, req *ModerationRequest) (*ModerationResponse, error)

	// ProcessImages handles image generation and image edit requests.
	ProcessImages(ctx context.Context, req *ImagesRequest) (*ImagesResponse, error)

	// ProcessSpeech handles text-to-speech requests.
	ProcessSpeech(ctx context.Context, req *SpeechRequest) (*SpeechResponse, error)

	// ProcessTranscription handles speech-to-text requests, in the spoken
	// language or translated into English.
	ProcessTranscription(ctx context.Context, req *TranscriptionRequest) (*TranscriptionResponse, error)

	// SubmitVideoJob starts one video generation and answers with the
	// provider's own job identifier. Nothing above this interface stores that
	// identifier except the job record, which never hands it back out.
	SubmitVideoJob(ctx context.Context, req *VideoSubmitRequest) (*VideoJobAnswer, error)

	// PollVideoJob asks the provider that accepted a job where it got to.
	PollVideoJob(ctx context.Context, req *VideoJobRequest) (*VideoJobAnswer, error)

	// CancelVideoJob asks the provider that accepted a job to stop it.
	CancelVideoJob(ctx context.Context, req *VideoJobRequest) (*VideoJobAnswer, error)

	// FetchVideoAsset reads the finished output of one accepted job.
	FetchVideoAsset(ctx context.Context, req *VideoAssetRequest) (*VideoAsset, error)

	// VideoJobRunner returns the provider side of one caller's video jobs,
	// bound to the gateway identity the request carries. The record store
	// drives a job through this value and names no transport itself.
	VideoJobRunner(req *VideoSubmitRequest) jobs.Runner

	// ListModels returns available models based on routing configuration
	ListModels(ctx context.Context) (*ModelsResponse, error)

	// ListProviders returns available provider information
	ListProviders(ctx context.Context) (*ProvidersResponse, error)

	// ListAuthors returns catalog author information
	ListAuthors(ctx context.Context) (*AuthorsResponse, error)

	// GetAuthor returns one catalog author. It reports a not_found
	// provider error for an unknown author ID.
	GetAuthor(ctx context.Context, authorID string) (*AuthorInfo, error)

	// GetModelEndpoints returns provider endpoints for a specific model
	GetModelEndpoints(ctx context.Context, modelID string) (*ModelEndpointsResponse, error)

	// or author. It reports a not_found provider error when the catalog
	// carries no bytes for this kind and ID.
	GetLogo(ctx context.Context, kind view.LogoKind, id string) ([]byte, error)
}

Proxy defines the core proxy interface for LLM request handling. This interface serves three primary purposes: 1. Testing - allows easy mocking in unit tests 2. Middleware composition - enables wrapping with caching, logging, metrics, etc. 3. Dependency injection - provides clean separation for HTTP handlers

The proxy handles routing requests to appropriate LLM providers, managing fallbacks, and transforming between different API formats.

func New

func New(registry connectors.LeasingRegistry, router router.ModelRouter, opts ...Option) Proxy

New creates a new proxy service with the given registry and router. Additional functionality can be added using options.

Example:

// Basic proxy
proxy := proxy.New(registry, router)

// Proxy with caching
proxy := proxy.New(registry, router,
    proxy.WithCache(cacheManager, cacheConfig),
)

type RerankRequest added in v1.1.0

type RerankRequest = OperationRequest[inference.RerankRequest]

RerankRequest is one gateway rerank request.

type RerankResponse added in v1.1.0

type RerankResponse = OperationResponse[inference.RerankResponse]

RerankResponse is one gateway ranked document list.

type RoutingError

type RoutingError struct {
	Model  string
	Reason string
	Err    error
}

RoutingError represents an error during model routing

func (*RoutingError) Error

func (e *RoutingError) Error() string

func (*RoutingError) Unwrap

func (e *RoutingError) Unwrap() error

type SemanticEmbedIdentity added in v1.2.0

type SemanticEmbedIdentity struct {
	AccountID string
	KeyID     string
	TeamID    string
	RequestID string
	Protocol  string
}

SemanticEmbedIdentity carries the gateway identity a cache embedding call runs under, so the call pays and meters like the account's own request.

type SemanticEmbedder added in v1.2.0

type SemanticEmbedder interface {
	Embed(ctx context.Context, identity SemanticEmbedIdentity, text string) ([]float32, error)
}

SemanticEmbedder turns canonical prompt text into one vector.

type SpeechRequest added in v1.1.0

type SpeechRequest = OperationRequest[inference.SpeechRequest]

SpeechRequest is one gateway text-to-speech request.

type SpeechResponse added in v1.1.0

type SpeechResponse = OperationResponse[inference.SpeechResponse]

SpeechResponse is one gateway speech result.

type StoredDocument added in v1.1.0

type StoredDocument struct {
	Filename string
	Data     []byte
}

StoredDocument is one stored file's bytes and the name it was stored under.

type StreamUnwrapper added in v1.1.0

type StreamUnwrapper interface {
	Unwrap() ChatCompletionStreamResponse
}

StreamUnwrapper exposes the inner stream of a decorating stream wrapper so cross-cutting middleware can reach route evidence on the routed stream.

type TopProviderInfo

type TopProviderInfo = view.TopProviderInfo

TopProviderInfo describes the selected representative offering limits.

type TranscriptionRequest added in v1.1.0

type TranscriptionRequest = OperationRequest[inference.TranscriptionRequest]

TranscriptionRequest is one gateway speech-to-text request.

type TranscriptionResponse added in v1.1.0

type TranscriptionResponse = OperationResponse[inference.TranscriptionResponse]

TranscriptionResponse is one gateway transcript.

type UsageCapture added in v1.1.0

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

UsageCapture is a proxy middleware that records one usage.Record per completed inference request. Writes are asynchronous, bounded, and best-effort: capture failure never fails or delays a request.

func NewUsageCapture added in v1.1.0

func NewUsageCapture(recorder UsageRecorder, observers ...UsageObserver) *UsageCapture

NewUsageCapture creates the capture middleware around one recorder. Each observer sees every captured record synchronously, so an observer must be cheap; counter arithmetic is, a network call is not.

func (*UsageCapture) Flush added in v1.1.0

func (c *UsageCapture) Flush()

Flush waits for every in-flight record write to finish.

func (*UsageCapture) Wrap added in v1.1.0

func (c *UsageCapture) Wrap(next Proxy) Proxy

Wrap implements Middleware.

type UsageObserver added in v1.2.0

type UsageObserver interface {
	ObserveUsage(record usage.Record)
}

UsageObserver sees every record this middleware captures, before the asynchronous write. The telemetry seam satisfies it, which is how one choke point feeds both the activity store and the scrape.

type UsageRecorder added in v1.1.0

type UsageRecorder interface {
	Put(ctx context.Context, record usage.Record) error
}

UsageRecorder persists one usage record. The concept-owned repository in internal/usage satisfies it.

type ValidationError

type ValidationError struct {
	Field   string
	Message string
}

ValidationError represents a request validation error

func (*ValidationError) Error

func (e *ValidationError) Error() string

type VideoAsset added in v1.1.0

type VideoAsset struct {
	ContentType  string
	Bytes        []byte
	ModelUsed    string
	ProviderUsed string
}

VideoAsset is one finished output with the route evidence that names who served it. The bytes arrive whole: the caller stores them, and the bound it sent is what makes holding them safe.

type VideoAssetReference added in v1.1.0

type VideoAssetReference = router.VideoAssetReference

VideoAssetReference names the finished output of one accepted job.

type VideoAssetRequest added in v1.1.0

type VideoAssetRequest = MediaRequest[VideoAssetReference]

VideoAssetRequest is one gateway read of a finished job's asset.

type VideoJobAnswer added in v1.1.0

type VideoJobAnswer struct {
	ProviderJobID string
	State         jobs.JobState
	Reason        string
	ModelUsed     string
	ProviderUsed  string
}

VideoJobAnswer is what a provider reported about one job, with the route evidence that names who reported it.

type VideoJobReference added in v1.1.0

type VideoJobReference = router.VideoJobReference

VideoJobReference names one job a provider already accepted. It is the router type rather than a copy of it, because a copy would be one more place a provider job identifier has to be carried by hand.

type VideoJobRequest added in v1.1.0

type VideoJobRequest = MediaRequest[VideoJobReference]

VideoJobRequest is one gateway poll or cancel of an accepted job.

type VideoJobRunner added in v1.1.0

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

VideoJobRunner is the provider side of one caller's video jobs.

It exists so internal/jobs can start, poll, and stop work without importing a provider transport or a credential policy. The record hands it a handle for one call, it spends the handle on one request, and it answers in the vocabulary the record keeps.

func (*VideoJobRunner) Cancel added in v1.1.0

func (r *VideoJobRunner) Cancel(ctx context.Context, handle jobs.Handle) (jobs.Report, error)

Cancel asks the provider to stop the accepted job.

func (*VideoJobRunner) Fetch added in v1.1.0

func (r *VideoJobRunner) Fetch(
	ctx context.Context,
	handle jobs.Handle,
	maxBytes int64,
) (jobs.Asset, error)

Fetch reads the finished output of the accepted job.

maxBytes arrives as an argument rather than as a setting this side reads. The record store is the half that holds the bytes, so it is the half that decides how large an asset it is willing to hold.

func (*VideoJobRunner) Poll added in v1.1.0

func (r *VideoJobRunner) Poll(ctx context.Context, handle jobs.Handle) (jobs.Report, error)

Poll reports where the accepted job got to.

func (*VideoJobRunner) Submit added in v1.1.0

func (r *VideoJobRunner) Submit(ctx context.Context) (jobs.Acceptance, error)

Submit starts the work this runner was built for.

type VideoSubmitRequest added in v1.1.0

type VideoSubmitRequest = MediaRequest[inference.VideoJobRequest]

VideoSubmitRequest is one gateway video generation submission.

Jump to

Keyboard shortcuts

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