router

package
v1.2.0 Latest Latest
Warning

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

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

README

Router

The router package adapts gateway requests to the pure route planner and the attempt executor. It does not own model capabilities, prices, context limits, or provider offerings. Starmap owns those facts through one immutable routable snapshot.

Routing behavior

  • A single model selects matching provider offerings from the snapshot.
  • A models array sets explicit model order and fallback order.
  • openrouter/auto lets the planner consider any routable model in the snapshot. In a mixed array, explicit models stay ahead of the automatic fallback set.
  • Provider order, only, and ignore rules are request policy.
  • Account model and provider restrictions are hard constraints.
  • Measured latency, Starmap price facts, required capabilities, context size, and provider affinity determine the stable order within one model rank.
  • The executor owns the total retry and fallback budget for streaming and non-streaming calls.

Ownership

  • internal/catalog publishes the generation-consistent Starmap snapshot.
  • internal/routing plans immutable attempts without network or mutable state.
  • internal/availability owns runtime offering health.
  • internal/execution runs the plan within one budget.
  • internal/providers/connectors adapts each provider transport.

Run the package contracts with:

go test ./internal/router ./internal/routing

Documentation

Overview

Package router provides model routing and fallback capabilities for the Starport gateway.

Package router provides model routing and fallback capabilities for the Starport gateway. It implements OpenRouter-compatible routing with provider preferences, health tracking, latency-based routing, cost optimization, and sticky sessions for conversation continuity.

Index

Constants

View Source
const (
	// AutoModelID is the special model ID for automatic routing
	AutoModelID = "openrouter/auto"
)

Variables

View Source
var (
	// ErrNoModelsAvailable is returned when no models can be used
	ErrNoModelsAvailable = errors.New("no models available for routing")
	// ErrAllModelsFailed is returned when all models in the chain failed
	ErrAllModelsFailed = errors.New("all models failed")
)

Functions

This section is empty.

Types

type APIKeyConfig

type APIKeyConfig struct {
	// Allowed providers for this API key
	AllowedProviders []string

	// Allowed models for this API key
	AllowedModels []string

	// Model-specific overrides
	ModelOverrides map[string]string

	// Rate limit tier
	RateLimitTier string

	// CredentialStrategy is the effective credential order for this request,
	// already resolved against the account's governing strategy by the caller.
	CredentialStrategy keyring.Strategy

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

	// BYOKProviders gates which providers the BYOK credential source may
	// serve for this request. Nil allows every provider, an empty list
	// allows none, and a non-empty list allows only its members. The caller
	// resolves it from the account's BYOK policy so this package never
	// learns the account vocabulary.
	BYOKProviders *[]string
}

APIKeyConfig contains API key specific routing configuration

type Config

type Config struct {
	// Latency tracking configuration
	LatencyAlpha      float64 // EMA smoothing factor (0-1)
	LatencyWindowSize int     // Samples before EMA kicks in

	// Cost optimization
	EnableCostOptimization bool

	// Sticky sessions
	EnableStickySessions bool
	SessionTTL           time.Duration

	// Execution owns the one total retry and fallback budget.
	Execution execution.Config

	// Availability owns exact offering circuit state.
	Availability availability.Config
}

Config contains configuration for the router

type EMALatencyTracker

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

EMALatencyTracker implements LatencyTracker using exponential moving average

func (*EMALatencyTracker) GetAllLatencies

func (t *EMALatencyTracker) GetAllLatencies() map[string]time.Duration

GetAllLatencies returns latencies for all tracked providers

func (*EMALatencyTracker) GetLatency

func (t *EMALatencyTracker) GetLatency(provider string) time.Duration

GetLatency returns the current EMA latency for a provider

func (*EMALatencyTracker) RecordLatency

func (t *EMALatencyTracker) RecordLatency(provider string, latency time.Duration)

RecordLatency records a latency measurement for a provider

func (*EMALatencyTracker) Reset

func (t *EMALatencyTracker) Reset()

Reset clears all latency data

type EmbeddingRequest added in v1.0.2

type EmbeddingRequest struct {
	*connectors.EmbeddingsRequest
	APIKeyConfig *APIKeyConfig
	AccountID    string
}

EmbeddingRequest contains one provider-neutral embedding request plus account routing and credential policy.

type EmbeddingResponse added in v1.0.2

type EmbeddingResponse struct {
	Response         inference.EmbeddingResponse
	ModelUsed        string
	ProviderUsed     string
	CredentialSource string
	Attempts         int
	Metadata         *Metadata
	CatalogSnapshot  *runtimecatalog.RoutableSnapshot
}

EmbeddingResponse wraps one embedding result with route evidence.

type ImagesRequest added in v1.1.0

type ImagesRequest = MediaRequest[inference.ImagesRequest]

ImagesRequest routes one image generation or image edit.

type ImagesResponse added in v1.1.0

type ImagesResponse = MediaResponse[inference.ImagesResponse]

ImagesResponse is one image result with route evidence.

type LatencyTracker

type LatencyTracker interface {
	// RecordLatency records a latency measurement for a provider
	RecordLatency(provider string, latency time.Duration)

	// GetLatency returns the current EMA latency for a provider
	GetLatency(provider string) time.Duration

	// GetAllLatencies returns latencies for all tracked providers
	GetAllLatencies() map[string]time.Duration

	// Reset clears all latency data
	Reset()
}

LatencyTracker tracks provider latencies using exponential moving average (EMA)

func NewLatencyTracker

func NewLatencyTracker(alpha float64, windowSize int) LatencyTracker

NewLatencyTracker creates a new EMA-based latency tracker

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 and document recognition are not media, and a media name on the answer they return would say they were.

type Metadata

type Metadata struct {
	// Models that were tried
	ModelsAttempted []ModelAttempt `json:"models_attempted"`

	// Total routing time
	RoutingDuration time.Duration `json:"routing_duration_ms"`

	// Reason for final model selection
	SelectionReason string `json:"selection_reason"`
}

Metadata contains detailed routing information

type ModelAttempt

type ModelAttempt struct {
	Model    string        `json:"model"`
	Provider string        `json:"provider"`
	Error    string        `json:"error,omitempty"`
	Duration time.Duration `json:"duration_ms"`
	Status   string        `json:"status"` // "success", "failed", "skipped"
}

ModelAttempt records an attempt to use a specific model

type ModelRouter

type ModelRouter interface {
	// SelectModel chooses the best model based on the request and routing strategy
	// Returns the selected model ID and the connector to use
	SelectModel(ctx context.Context, req *Request) (modelID string, connector connectors.Connector, err error)

	// RouteWithFallback attempts to route a request through multiple models with fallback logic
	// Returns the response and which model was actually used
	RouteWithFallback(ctx context.Context, req *Request) (*Response, error)

	// RouteStream executes the same immutable route plan and budget for streaming.
	RouteStream(ctx context.Context, req *Request) (execution.ManagedStream, error)

	// RouteEmbeddings executes one embedding request through the same route,
	// credential, availability, and total-attempt policies as chat requests.
	RouteEmbeddings(ctx context.Context, req *EmbeddingRequest) (*EmbeddingResponse, error)

	// RouteRerank scores one document list against a query through the same
	// route, credential, availability, and total-attempt policies as chat.
	RouteRerank(ctx context.Context, req *RerankRequest) (*RerankResponse, error)

	// RouteModerations classifies one input list against a provider's harm
	// categories through the same policies as chat.
	RouteModerations(ctx context.Context, req *ModerationRequest) (*ModerationResponse, error)

	// RouteImages executes one image generation or image edit request.
	RouteImages(ctx context.Context, req *ImagesRequest) (*ImagesResponse, error)

	// RouteSpeech executes one text-to-speech request.
	RouteSpeech(ctx context.Context, req *SpeechRequest) (*SpeechResponse, error)

	// RouteTranscription executes one speech-to-text request, in the spoken
	// language or translated into English.
	RouteTranscription(ctx context.Context, req *TranscriptionRequest) (*TranscriptionResponse, error)

	// RouteVideoSubmit starts one video generation at a provider that serves
	// it, and answers with the provider's own job identifier.
	RouteVideoSubmit(ctx context.Context, req *VideoSubmitRequest) (*VideoJobResponse, error)

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

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

	// RouteVideoContent reads the finished output of one accepted job from the
	// provider that produced it.
	RouteVideoContent(ctx context.Context, req *VideoAssetRequest) (*VideoAssetResponse, error)

	// RouteDocumentRecognition reads the text off a document whose pages carry
	// none. The gateway orders this read on a caller's behalf inside a chat
	// turn, so it is the one route no HTTP path reaches.
	RouteDocumentRecognition(ctx context.Context, req *RecognitionRequest) (*RecognitionResponse, error)
}

ModelRouter handles model selection, fallback logic, and provider routing

func New

func New(registry connectors.Registry, opts ...Option) ModelRouter

New creates a new model router with all features enabled by default

type ModerationRequest added in v1.2.0

type ModerationRequest = OperationRequest[inference.ModerationRequest]

ModerationRequest routes one moderation call.

type ModerationResponse added in v1.2.0

type ModerationResponse = OperationResponse[inference.ModerationResponse]

ModerationResponse is one classified input list with route evidence.

type OperationRequest added in v1.1.0

type OperationRequest[Request any] struct {
	Request      Request
	APIKeyConfig *APIKeyConfig
	AccountID    string
}

OperationRequest carries one canonical request plus the account routing and credential policy every operation reads.

type OperationResponse added in v1.1.0

type OperationResponse[Response any] struct {
	Response         Response
	ModelUsed        string
	ProviderUsed     string
	CredentialSource string
	Attempts         int
	Metadata         *Metadata
	CatalogSnapshot  *runtimecatalog.RoutableSnapshot
}

OperationResponse is one operation result with the same route evidence a chat or an embedding answer carries.

type OperatorCredentialGate added in v1.0.3

type OperatorCredentialGate interface {
	OperatorMaterialReady(providerID string, materialVersion string) bool
}

OperatorCredentialGate admits one exact resolved operator material version.

type Option

type Option func(*modelRouter)

Option configures the transitional router composition.

func WithAvailability

func WithAvailability(tracker *availability.Tracker) Option

WithAvailability supplies the one runtime offering availability owner.

func WithCatalog

func WithCatalog(catalogPlane *runtimecatalog.ControlPlane) Option

WithCatalog supplies the shared generation-consistent routable snapshot.

func WithOperatorCredentialGate added in v1.0.3

func WithOperatorCredentialGate(gate OperatorCredentialGate) Option

WithOperatorCredentialGate prevents a provider-proved bad material version from being retried before its lifecycle owner supplies a replacement.

func WithOutcomePublisher added in v1.0.3

func WithOutcomePublisher(publisher execution.OutcomePublisher) Option

WithOutcomePublisher supplies the safe provider invocation outcome sink.

func WithSharedHealthStore added in v1.2.0

func WithSharedHealthStore(store availability.KVStore) Option

WithSharedHealthStore supplies the distributed store that replicas share. The latency tracker publishes its snapshots there and reads peer measurements back. Without it every measurement stays process-local.

func WithStoredCredentials added in v1.1.0

func WithStoredCredentials(resolver StoredCredentialResolver) Option

WithStoredCredentials supplies the stored inference credential planes: the operator's shared credentials and every account's own.

type ProviderPreferences

type ProviderPreferences struct {
	// Try providers in this order
	Order []string `json:"order,omitempty"`

	// Only use these providers
	Only []string `json:"only,omitempty"`

	// Never use these providers
	Ignore []string `json:"ignore,omitempty"`

	// Allow fallback to other providers not in order list
	AllowFallbacks bool `json:"allow_fallbacks,omitempty"`

	// Sort selects the route ordering: "price", "latency", "throughput",
	// or "spread". Starport measures latency, not throughput, so
	// "throughput" routes by measured latency. "spread" balances traffic
	// across 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 controls which providers can be used

type RecognitionRequest added in v1.1.0

type RecognitionRequest = MediaRequest[inference.RecognitionRequest]

RecognitionRequest routes one document read.

type RecognitionResponse added in v1.1.0

type RecognitionResponse = MediaResponse[inference.RecognitionResponse]

RecognitionResponse is one document's recognized pages with route evidence.

type Request

type Request struct {
	// Original chat request
	*connectors.ChatRequest

	// Models to try in order (fallback chain)
	Models []string

	// Provider routing preferences
	ProviderPreferences *ProviderPreferences

	// API key configuration (for provider restrictions)
	APIKeyConfig *APIKeyConfig

	// AccountID selects the account whose BYOK credential record the request
	// may read. It is never a gateway API key ID.
	AccountID string

	// Request metadata for routing decisions
	Metadata *RequestMetadata

	// PrepareAttempt optionally adjusts the provider request for the selected
	// model immediately before invoking the connector.
	PrepareAttempt func(route routing.Route, req *connectors.ChatRequest) *connectors.ChatRequest
}

Request contains the original request plus routing preferences

type RequestMetadata

type RequestMetadata struct {
	// Estimated tokens in the request
	EstimatedTokens int

	// Required features (e.g., "vision", "function_calling")
	RequiredFeatures []string

	// RequiredModalities names the media the request carries, such as
	// "audio". A model that does not read one of them cannot answer the
	// request, so the planner drops it rather than letting the provider
	// refuse the call.
	RequiredModalities []string

	// Conversation ID for sticky routing
	ConversationID string

	// User preferences
	UserPreferences map[string]any
}

RequestMetadata contains information for routing decisions

type RerankRequest added in v1.1.0

type RerankRequest = OperationRequest[inference.RerankRequest]

RerankRequest routes one rerank call.

type RerankResponse added in v1.1.0

type RerankResponse = OperationResponse[inference.RerankResponse]

RerankResponse is one ranked document list with route evidence.

type Response

type Response struct {
	// The actual response from the model
	*connectors.ChatResponse

	// Which model was actually used
	ModelUsed string `json:"model_used"`

	// Provider that handled the request
	ProviderUsed string `json:"provider_used"`

	// CredentialSource names which credential plane paid for the attempt
	// that answered: the operator's environment, the operator's shared
	// credential, the account's own BYOK, or no credential at all.
	CredentialSource string `json:"credential_source,omitempty"`

	// Number of attempts made
	Attempts int `json:"attempts"`

	// Routing metadata
	Metadata *Metadata `json:"metadata,omitempty"`

	// CatalogSnapshot is the exact leased runtime generation that produced the
	// response.
	CatalogSnapshot *runtimecatalog.RoutableSnapshot `json:"-"`
}

Response wraps the chat response with routing metadata

type SharedLatencyTracker added in v1.2.0

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

SharedLatencyTracker layers peer latency snapshots from the distributed store over a process-local tracker. A local measurement wins; peers fill in the providers this replica has not called yet, so a fresh replica starts with the fleet's view.

func NewSharedLatencyTracker added in v1.2.0

func NewSharedLatencyTracker(local LatencyTracker, store availability.KVStore) *SharedLatencyTracker

NewSharedLatencyTracker wraps a local tracker with distributed publication.

func (*SharedLatencyTracker) GetAllLatencies added in v1.2.0

func (t *SharedLatencyTracker) GetAllLatencies() map[string]time.Duration

GetAllLatencies merges peer snapshots under the local measurements.

func (*SharedLatencyTracker) GetLatency added in v1.2.0

func (t *SharedLatencyTracker) GetLatency(provider string) time.Duration

GetLatency returns the local measurement, or the freshest peer measurement when this replica holds none.

func (*SharedLatencyTracker) RecordLatency added in v1.2.0

func (t *SharedLatencyTracker) RecordLatency(provider string, latency time.Duration)

RecordLatency records the measurement locally and publishes the replica snapshot at most once per refresh interval.

func (*SharedLatencyTracker) Reset added in v1.2.0

func (t *SharedLatencyTracker) Reset()

Reset clears the local measurements and the merged peer view.

type SpeechRequest added in v1.1.0

type SpeechRequest = MediaRequest[inference.SpeechRequest]

SpeechRequest routes one text-to-speech call.

type SpeechResponse added in v1.1.0

type SpeechResponse = MediaResponse[inference.SpeechResponse]

SpeechResponse is one speech result with route evidence.

type StickyProviderSessionManager

type StickyProviderSessionManager interface {
	// GetProvider returns the provider for a conversation, if any
	GetProvider(conversationID string) (string, bool)

	// SetProvider sets the provider for a conversation
	SetProvider(conversationID string, provider string)

	// RemoveSession removes a conversation's sticky session
	RemoveSession(conversationID string)

	// CleanupExpired removes expired sessions
	CleanupExpired()
}

StickyProviderSessionManager manages conversation-to-provider mappings

func NewStickyProviderSessionManager

func NewStickyProviderSessionManager(ttl time.Duration) StickyProviderSessionManager

NewStickyProviderSessionManager creates a new sticky session manager

type StoredCredentialResolver added in v1.1.0

type StoredCredentialResolver interface {
	ResolveStoredMaterial(context.Context, string, catalogs.Provider) (credentials.Material, error)
	ResolveSharedMaterial(context.Context, string, catalogs.Provider) (credentials.Material, error)
}

StoredCredentialResolver resolves stored credentials against the provider contract retained by the request's runtime generation. The two stored planes have different shapes, so each has its own method: the account plane reads one exact scoped record, and the shared plane picks the first shared credential the account may spend.

type StreamEvidence added in v1.1.0

type StreamEvidence interface {
	ProviderUsed() string
	CredentialSourceUsed() string
	AttemptCount() int
	RoutingDuration() time.Duration
	CatalogSnapshot() *runtimecatalog.RoutableSnapshot
}

StreamEvidence exposes route evidence from a managed stream for usage accounting.

type TranscriptionRequest added in v1.1.0

type TranscriptionRequest = MediaRequest[inference.TranscriptionRequest]

TranscriptionRequest routes one speech-to-text call.

type TranscriptionResponse added in v1.1.0

type TranscriptionResponse = MediaResponse[inference.TranscriptionResponse]

TranscriptionResponse is one transcript with route evidence.

type VideoAssetReference added in v1.1.0

type VideoAssetReference struct {
	VideoJobReference
	// MaxBytes is the largest asset the caller will store. It travels with the
	// request rather than living here, because the half that stores the bytes
	// is the half that decides what it is willing to store.
	MaxBytes int64
}

VideoAssetReference names the finished output of one accepted job and states the bound the record store is willing to hold.

type VideoAssetRequest added in v1.1.0

type VideoAssetRequest = MediaRequest[VideoAssetReference]

VideoAssetRequest routes one read of a finished job's asset.

type VideoAssetResponse added in v1.1.0

type VideoAssetResponse = MediaResponse[connectors.JobAsset]

VideoAssetResponse is one finished asset with route evidence.

type VideoJobReference added in v1.1.0

type VideoJobReference struct {
	// Provider is who accepted the job. Planning is pinned to it.
	Provider string
	// Model is the catalog model the job runs. The plan needs it to find the
	// offering that names the endpoint.
	Model string
	// ProviderJobID is the provider's own identifier. It reaches this package
	// from the job record and travels no further than the request body.
	ProviderJobID string
}

VideoJobReference names one job a provider already accepted.

type VideoJobRequest added in v1.1.0

type VideoJobRequest = MediaRequest[VideoJobReference]

VideoJobRequest routes one poll or one cancel of an accepted job.

type VideoJobResponse added in v1.1.0

type VideoJobResponse = MediaResponse[connectors.ProviderJob]

VideoJobResponse is one provider job answer with route evidence.

type VideoSubmitRequest added in v1.1.0

type VideoSubmitRequest = MediaRequest[inference.VideoJobRequest]

VideoSubmitRequest routes one video generation submission.

Jump to

Keyboard shortcuts

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