Documentation
¶
Overview ¶
Package virtualmodel defines the protocol-agnostic primitives for virtual models. Concrete provider-specific implementations and registries live in the anthropic and openai sub-packages — this top-level package contains only the base interface and shared value types.
These primitives back the production /virtual/v1/* endpoint (onboarding, demos, dry-runs without a real upstream provider) and are also reused as an in-process LLM substitute by test packages such as protocoltest and servertest. The production endpoint is the package's primary surface; test consumers are secondary and use GenericRegistry directly rather than RegisterDefaults — see README.md "Positioning & registration discipline".
Index ¶
- Constants
- func EmitChunks(ctx context.Context, chunks []string, perChunkDelay time.Duration, ...) error
- func ResolveChunkDelay(totalDelay time.Duration, chunkCount int) time.Duration
- func ToolCallDisplayContent(args map[string]interface{}) string
- type BaseMockModel
- type EmitGate
- type ErrorCategory
- type ErrorInjectingModel
- type ErrorInjection
- type ErrorStage
- type ExhaustPolicy
- type GenericRegistry
- func (r *GenericRegistry[T]) Clear()
- func (r *GenericRegistry[T]) Get(id string) T
- func (r *GenericRegistry[T]) Has(id string) bool
- func (r *GenericRegistry[T]) List() []T
- func (r *GenericRegistry[T]) ListModels() []Model
- func (r *GenericRegistry[T]) Register(vm T) error
- func (r *GenericRegistry[T]) Unregister(id string)
- type MidStreamMode
- type MockUsage
- type Model
- type ResolvedStep
- type Sequence
- type SequenceConfig
- type SequenceStep
- type SharedMockSpec
- type StepOption
- type ToolCallConfig
- type VirtualModel
- type VirtualModelType
Constants ¶
const DefaultMockDescription = "A virtual model that returns fixed responses for testing"
DefaultMockDescription is the description returned by mock virtual models when their config does not provide an explicit one.
const DefaultMockOwnedBy = "tingly-box-virtual"
DefaultMockOwnedBy is the OwnedBy value reported by every built-in mock virtual model in the OpenAI-compatible models list.
const DefaultStreamChunkDelay = 50 * time.Millisecond
DefaultStreamChunkDelay is the per-chunk sleep used by stream helpers when no explicit total simulated delay is configured.
const FallbackSequenceContent = "Sequenced virtual response."
FallbackSequenceContent is the module-level fallback body for a success step that sets neither its own Content nor SequenceConfig.DefaultContent. It keeps a bare Step(200) useful out of the box. Distinct from SequenceConfig's DefaultContent — that one is per-model, this one is the last resort when a model configures no default at all.
Variables ¶
This section is empty.
Functions ¶
func EmitChunks ¶
func EmitChunks(ctx context.Context, chunks []string, perChunkDelay time.Duration, emit func(index int, chunk string) bool) error
EmitChunks returns the context error if cancellation happens while waiting or if the emit callback stops accepting chunks.
func ResolveChunkDelay ¶
ResolveChunkDelay computes the per-chunk sleep duration for a stream.
- if totalDelay > 0 and chunkCount > 0 → totalDelay / chunkCount
- otherwise → DefaultStreamChunkDelay
This is the shared latency-distribution rule used by all mock streams across protocols.
func ToolCallDisplayContent ¶
ToolCallDisplayContent extracts display text from tool call arguments. It checks for "message" and "question" keys, returning the first non-empty value found.
Types ¶
type BaseMockModel ¶
type BaseMockModel struct {
ID string
Name string
Description string
Type VirtualModelType
Delay time.Duration
}
BaseMockModel is the protocol-neutral half of an in-memory mock virtual model. Protocol-specific mocks embed it and only need to add their own Handle*/Handle*Stream methods. All identity / metadata methods of the VirtualModel interface live here.
func (*BaseMockModel) GetDescription ¶
func (b *BaseMockModel) GetDescription() string
func (*BaseMockModel) GetID ¶
func (b *BaseMockModel) GetID() string
func (*BaseMockModel) GetName ¶
func (b *BaseMockModel) GetName() string
func (*BaseMockModel) GetType ¶
func (b *BaseMockModel) GetType() VirtualModelType
func (*BaseMockModel) SimulatedDelay ¶
func (b *BaseMockModel) SimulatedDelay() time.Duration
func (*BaseMockModel) ToModel ¶
func (b *BaseMockModel) ToModel() Model
ToModel returns the OpenAI-compatible models-list entry for this mock.
type EmitGate ¶ added in v0.260604.1
type EmitGate struct {
// contains filtered or unexported fields
}
EmitGate is a counting gate used by mock streams to honor mid-stream injection. After Allow() has returned true `cutoff` times, all subsequent Allow() calls return false. Callers should bail out of the stream loop the first time Allow() returns false to avoid emitting events past the cutoff.
A cutoff of <= 0 disables the gate (Allow() always returns true).
func NewEmitGate ¶ added in v0.260604.1
NewEmitGate constructs a gate. Pass the return value of MidStreamCutoff (or -1 to disable).
type ErrorCategory ¶ added in v0.260611.1
type ErrorCategory string
ErrorCategory categorizes error models for filtering and catalog purposes.
const ( ErrorCategoryRateLimit ErrorCategory = "rate_limit" // Rate limiting (429) ErrorCategoryUpstream ErrorCategory = "upstream" // Upstream server errors (5xx) ErrorCategoryTimeout ErrorCategory = "timeout" // Request timeout ErrorCategoryOverloaded ErrorCategory = "overloaded" // Service overloaded ErrorCategoryInvalid ErrorCategory = "invalid" // Invalid request ErrorCategoryAuth ErrorCategory = "auth" // Authentication/authorization ErrorCategoryNetwork ErrorCategory = "network" // Network connectivity ErrorCategoryMalformed ErrorCategory = "malformed" // Malformed response )
type ErrorInjectingModel ¶ added in v0.260604.1
type ErrorInjectingModel interface {
ErrorInjection() *ErrorInjection
}
ErrorInjectingModel is the optional interface a mock virtual model implements to declare a synthetic failure. The virtualserver handler type-asserts to this interface before dispatching the request.
type ErrorInjection ¶ added in v0.260604.1
type ErrorInjection struct {
Stage ErrorStage
// PreContent fields: HTTP status (defaults to 500 if zero) and an
// optional message that the handler renders into the protocol-specific
// error envelope (OpenAI: error.message; Anthropic: error.error.message).
// Type defaults to "api_error" if empty.
Status int
Message string
Type string
// MidStream fields: number of stream events to emit before tripping
// (default 1; values <= 0 are treated as 1), and how to terminate.
AfterEvents int
MidStreamMode MidStreamMode
}
ErrorInjection describes a synthetic failure that a mock virtual model should simulate. Attach one to MockScenario.Error (or MockModelConfig.Error) and the virtualserver handler honors it without any real upstream involvement.
Two stages are supported, and they exercise different gateway paths:
- PreContent: HTTP status + error body, no streaming started. The gateway's firstChunkGate stays buffered → failover retries.
- MidStream: handler writes a 200 + Content-Type + AfterEvents real events, then either closes the TCP connection or emits a final SSE error event. firstChunkGate is already committed → failover MUST NOT retry.
func ExtractErrorInjection ¶ added in v0.260604.1
func ExtractErrorInjection(vm any) *ErrorInjection
ExtractErrorInjection reports the model's error injection configuration, or nil if the model does not implement ErrorInjectingModel or has none set.
type ErrorStage ¶ added in v0.260604.1
type ErrorStage int
ErrorStage selects when in the request lifecycle a mock model's error injection fires.
const ( // ErrorStagePreContent is a failure before any response body is written: // the handler returns the configured HTTP status with an error JSON envelope // and never invokes the model's Handle* methods past that point. This is // the case that priority-routing failover MUST retry — the gate stays // buffered, the orchestrator sees a retryable status and discards. ErrorStagePreContent ErrorStage = iota // ErrorStageMidStream is a failure after the handler has already started // streaming. The model emits AfterEvents real stream events, then the // handler applies MidStreamMode: it either hijacks and closes the TCP // connection (truncated stream from the client's POV) or emits a single // SSE error event before stopping. This is the case that priority-routing // failover MUST NOT retry — the gate has committed, bytes left the process. ErrorStageMidStream )
type ExhaustPolicy ¶ added in v0.260723.1
type ExhaustPolicy string
ExhaustPolicy selects what a sequence serves once every step has been consumed once. The zero value loops, which is the common default.
const ( // ExhaustLoop wraps back to the first step and repeats the program // indefinitely. This is the zero value / default. ExhaustLoop ExhaustPolicy = "" // ExhaustClamp keeps serving the last step forever once the program is // exhausted (e.g. 200, 503 → 200, 503, 503, 503, …). ExhaustClamp ExhaustPolicy = "clamp" // ExhaustFail serves a terminal pre-content error (HTTP 410, type // "sequence_exhausted") for every request after the program is exhausted, // modelling an upstream whose scripted run is over. ExhaustFail ExhaustPolicy = "fail" )
type GenericRegistry ¶
type GenericRegistry[T VirtualModel] struct { // contains filtered or unexported fields }
GenericRegistry is a thread-safe registry of virtual models indexed by ID, parameterised by a protocol-specific VirtualModel sub-interface T. Each protocol sub-package (anthropic, openai, ...) instantiates its own Registry alias so models cannot leak across protocols.
func NewGenericRegistry ¶
func NewGenericRegistry[T VirtualModel]() *GenericRegistry[T]
NewGenericRegistry creates an empty registry.
func (*GenericRegistry[T]) Clear ¶
func (r *GenericRegistry[T]) Clear()
Clear removes all registered models.
func (*GenericRegistry[T]) Get ¶
func (r *GenericRegistry[T]) Get(id string) T
Get returns the virtual model for id, or the zero value of T if not registered.
func (*GenericRegistry[T]) Has ¶
func (r *GenericRegistry[T]) Has(id string) bool
Has reports whether a model with the given ID is registered.
func (*GenericRegistry[T]) List ¶
func (r *GenericRegistry[T]) List() []T
List returns all registered virtual models.
func (*GenericRegistry[T]) ListModels ¶
func (r *GenericRegistry[T]) ListModels() []Model
ListModels returns all registered models in the OpenAI-compatible Model format.
func (*GenericRegistry[T]) Register ¶
func (r *GenericRegistry[T]) Register(vm T) error
Register adds a virtual model. Returns an error if the ID is already taken.
func (*GenericRegistry[T]) Unregister ¶
func (r *GenericRegistry[T]) Unregister(id string)
Unregister removes a virtual model by ID. No-op if not present.
type MidStreamMode ¶ added in v0.260604.1
type MidStreamMode int
MidStreamMode selects how a mid-stream failure terminates the stream after AfterEvents events have been emitted to the wire.
const ( // MidStreamModeConnectionClose hijacks the underlying TCP connection and // closes it. The client sees an EOF / abrupt disconnect mid-stream — // exactly the shape an unstable upstream produces when it dies during a // long response. MidStreamModeConnectionClose MidStreamMode = iota // MidStreamModeErrorEvent emits one final protocol-specific error event // (SSE "event: error" frame) before returning. The stream is well-formed // up to that point; the client sees an in-band error. MidStreamModeErrorEvent // MidStreamModeCleanEOF ends the HTTP response cleanly (proper chunked // terminator) after AfterEvents events, without any terminal protocol // event. The client's reader sees a well-formed body that simply stops — // the shape an upstream reverse proxy or gateway produces when it times // out an idle stream, and the shape behind "stream closed before // response.completed" reports (#1384). MidStreamModeCleanEOF )
type MockUsage ¶ added in v0.260604.1
type MockUsage struct {
PromptTokens int64 // input_tokens / prompt_tokens
CompletionTokens int64 // output_tokens / completion_tokens
CachedInputTokens int64 // OpenAI prompt_tokens_details.cached_tokens / Anthropic cache_read_input_tokens
// CacheWriteTokens renders as Anthropic cache_creation_input_tokens or
// OpenAI prompt_tokens_details.cache_write_tokens (gpt-5.6+) — the same
// premium-rate write cost under two wire names.
CacheWriteTokens int64
ReasoningTokens int64 // OpenAI completion_tokens_details.reasoning_tokens
}
MockUsage carries deterministic token-usage values that a mock model emits over its streaming wire format. All fields are optional; a zero value means "do not advertise this dimension". Used so streaming converters / observers can be tested for completeness (cache and reasoning tokens, not just plain prompt/completion).
type Model ¶
type Model struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
OwnedBy string `json:"owned_by"`
}
Model represents a virtual model in the models list (OpenAI-compatible format).
type ResolvedStep ¶ added in v0.260723.1
type ResolvedStep struct {
Content string
Error *ErrorInjection // nil for success steps
}
ResolvedStep is the concrete outcome of advancing a Sequence: either a success (Error == nil, use Content) or a pre-content failure (Error set).
func (ResolvedStep) HTTPStatus ¶ added in v0.260723.1
func (r ResolvedStep) HTTPStatus() int
HTTPStatus reports the HTTP status this step serves: 200 for a success step, or the configured status for an error step. Derived from Error rather than stored separately, so there is exactly one source of truth for an error step's status.
type Sequence ¶ added in v0.260723.1
type Sequence struct {
// contains filtered or unexported fields
}
Sequence is the protocol-neutral engine behind the per-protocol SequenceModel wrappers. It owns the expanded step program and an atomic cursor so concurrent requests each grab a distinct, monotonically advancing step without locking.
func NewSequence ¶ added in v0.260723.1
func NewSequence(cfg SequenceConfig) *Sequence
NewSequence flattens cfg.Steps (expanding Repeat) and pre-resolves each step so Next() is allocation-free on the hot path. A config with no steps yields a single success step backed by DefaultContent, so the model is always usable.
func (*Sequence) Len ¶ added in v0.260723.1
Len reports the number of (post-expansion) steps in the program.
func (*Sequence) Next ¶ added in v0.260723.1
func (s *Sequence) Next() ResolvedStep
Next atomically advances the cursor and returns the step for this request. It is safe for concurrent use; each caller observes a distinct cursor value. Behaviour past the end of the program is governed by OnExhaust.
type SequenceConfig ¶ added in v0.260723.1
type SequenceConfig struct {
ID string `json:"id" yaml:"id"`
Name string `json:"name" yaml:"name"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Delay time.Duration `json:"delay,omitempty" yaml:"delay,omitempty"`
// DefaultContent backs any success step that does not set its own Content.
DefaultContent string `json:"default_content,omitempty" yaml:"default_content,omitempty"`
// Steps is the response program. Each step is expanded by its Repeat
// count at construction time.
Steps []SequenceStep `json:"steps,omitempty" yaml:"steps,omitempty"`
// OnExhaust selects the behaviour after the program is consumed once:
// ExhaustLoop (default) wraps around, ExhaustClamp repeats the last step,
// ExhaustFail serves a terminal error.
OnExhaust ExhaustPolicy `json:"on_exhaust,omitempty" yaml:"on_exhaust,omitempty"`
}
SequenceConfig describes a SequenceModel: an ordered program of steps that is walked one step per request. By default the program loops (wraps back to the first step) so the model is reusable across an unbounded number of requests; set OnExhaust to change what happens once it is consumed.
Tagged like SequenceStep even though nothing in the codebase currently (de)serializes it: both types describe the same potential external surface (a sequence loaded from a config file or management API), and tagging them together keeps that door open without committing to it yet.
func DefaultSequenceConfigs ¶ added in v0.260723.1
func DefaultSequenceConfigs() []SequenceConfig
DefaultSequenceConfigs returns the user-facing demo sequence(s) registered into BOTH default registries by each protocol's RegisterDefaults. A demo sequence is genuinely useful for onboarding / dry-runs: it lets users see how the gateway reacts to an intermittently rate-limited upstream without configuring a real provider.
type SequenceStep ¶ added in v0.260723.1
type SequenceStep struct {
// Status is the HTTP status this step serves. 0 and 200 both mean
// "success" (return Content); anything else is served as a pre-content
// error with that status code.
Status int `json:"status" yaml:"status"`
// Content is the response body for a success step. When empty the
// SequenceConfig.DefaultContent is used. Ignored for error steps.
Content string `json:"content,omitempty" yaml:"content,omitempty"`
// ErrorMessage and ErrorType override the error envelope for an error
// step. When empty they are derived from Status (see defaultErrorMeta).
// Ignored for success steps.
ErrorMessage string `json:"error_message,omitempty" yaml:"error_message,omitempty"`
ErrorType string `json:"error_type,omitempty" yaml:"error_type,omitempty"`
// Repeat serves this step Repeat consecutive times before advancing to
// the next one. Values <= 0 are treated as 1. Useful for compact configs
// like "succeed 5×, then fail once".
Repeat int `json:"repeat,omitempty" yaml:"repeat,omitempty"`
}
SequenceStep is one entry in a SequenceModel's response program. A step is either a success (Status 0 or 200 → a normal content response) or a pre-content failure (any other status → the configured HTTP error envelope).
This lets a single virtual model reproduce the real-world behaviour of a flaky upstream — e.g. "200, 200, 429, 200" — so failover / retry / backoff logic can be exercised deterministically without standing up a real or ad-hoc test provider.
func Step ¶ added in v0.260723.1
func Step(status int, opts ...StepOption) SequenceStep
Step builds a SequenceStep from a status code plus optional overrides. Status is the only required input — content for a success step and the error type/message for a failure step are filled from defaults at build time (module FallbackSequenceContent / defaultErrorMeta), so Step(429) and Step(200) are both immediately usable.
func Steps ¶ added in v0.260723.1
func Steps(statuses ...int) []SequenceStep
Steps builds a program from a bare list of status codes — the common case, e.g. Steps(200, 200, 429). Each step takes default content / error metadata.
type SharedMockSpec ¶
type SharedMockSpec struct {
// Error metadata (only meaningful when Error != nil)
}
SharedMockSpec describes a built-in mock that is identical across protocols (anthropic + openai). Each protocol's RegisterDefaults converts a SharedMockSpec into its own protocol-specific MockModelConfig.
Entries returned by SharedDefaultMocks are user-facing demo defaults: they are mounted into the production /virtual/v1/* endpoint and visible to end users via the virtual provider. Test-only fixtures must NOT be added here; tests should build their own GenericRegistry rather than pollute the production defaults set.
func ExtendedErrorSpecs ¶ added in v0.260611.1
func ExtendedErrorSpecs() []SharedMockSpec
ExtendedErrorSpecs returns additional error scenarios for testing. These are opt-in - register via RegisterExtendedErrorMocks in per-protocol packages.
func SharedDefaultMocks ¶
func SharedDefaultMocks() []SharedMockSpec
SharedDefaultMocks returns the mocks registered by BOTH the Anthropic and OpenAI default registries. Per-protocol unique entries (e.g. virtual-claude-3 for Anthropic, virtual-gpt-4 for OpenAI, the compact transforms) live in their respective sub-packages.
func StreamTestMockSpecs ¶ added in v0.260604.1
func StreamTestMockSpecs() []SharedMockSpec
StreamTestMockSpecs returns deterministic stream-test fixtures (static + tool variants) that advertise the full usage shape — prompt, completion, cached input, cache-creation input, and reasoning tokens. These are opt-in (NOT in SharedDefaultMocks): consumers wire them into their own registry via RegisterStreamTestMocks helpers in the per-protocol sub-packages.
type StepOption ¶ added in v0.260723.1
type StepOption func(*SequenceStep)
StepOption customizes a SequenceStep built by Step. Only Status is required; these options override the otherwise-defaulted fields for the uncommon cases.
func WithContent ¶ added in v0.260723.1
func WithContent(content string) StepOption
WithContent sets a success step's response body (overrides the config/module default content).
func WithErrorMessage ¶ added in v0.260723.1
func WithErrorMessage(message string) StepOption
WithErrorMessage overrides an error step's message (otherwise derived from Status).
func WithErrorType ¶ added in v0.260723.1
func WithErrorType(typ string) StepOption
WithErrorType overrides an error step's type (otherwise derived from Status).
func WithRepeat ¶ added in v0.260723.1
func WithRepeat(n int) StepOption
WithRepeat serves the step n consecutive times before advancing.
type ToolCallConfig ¶
type ToolCallConfig struct {
Name string `json:"name" yaml:"name"`
Arguments map[string]interface{} `json:"arguments" yaml:"arguments"`
}
ToolCallConfig defines a tool call to be returned by the virtual model.
type VirtualModel ¶
type VirtualModel interface {
GetID() string
GetName() string
GetDescription() string
GetType() VirtualModelType
SimulatedDelay() time.Duration
ToModel() Model
}
VirtualModel is the base interface common to all virtual model types. Provider-specific extensions are defined in the anthropic and openai sub-packages, each adding the Handle methods for that protocol.
type VirtualModelType ¶
type VirtualModelType string
VirtualModelType represents the type/category of a virtual model.
const ( // VirtualModelTypeStatic represents static mock models that return fixed responses. VirtualModelTypeStatic VirtualModelType = "static" // VirtualModelTypeProxy represents proxy/transform models that modify requests before forwarding. VirtualModelTypeProxy VirtualModelType = "proxy" // VirtualModelTypeTool represents tool models that return tool_use blocks. VirtualModelTypeTool VirtualModelType = "tool" // VirtualModelTypeSequence represents sequence models that walk a configured // program of per-request outcomes (e.g. 200, 200, 429) to simulate a flaky // upstream provider. VirtualModelTypeSequence VirtualModelType = "sequence" )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package anthropic provides Anthropic-protocol virtual models.
|
Package anthropic provides Anthropic-protocol virtual models. |
|
Package benchmark is the vmodel benchmark: a shared, real-world mock-provider foundation.
|
Package benchmark is the vmodel benchmark: a shared, real-world mock-provider foundation. |
|
check
Package check holds the protocol-neutral, reusable check logic for the vmodel benchmark: the RoundTripResult view of a single gateway round trip and the named Assertion library that operates on it.
|
Package check holds the protocol-neutral, reusable check logic for the vmodel benchmark: the RoundTripResult view of a single gateway round trip and the named Assertion library that operates on it. |
|
examples/client
command
Stand-alone benchmark client driver: starts an in-process LocalServer (vmodel-backed) and drives it with the BenchmarkClient against both the OpenAI Chat and Anthropic Messages routes, printing a metrics summary.
|
Stand-alone benchmark client driver: starts an in-process LocalServer (vmodel-backed) and drives it with the BenchmarkClient against both the OpenAI Chat and Anthropic Messages routes, printing a metrics summary. |
|
examples/server
command
Stand-alone benchmark mock server: starts a local HTTP server backed by the production virtualmodel registries (with their default mock models pre-registered) so external benchmark drivers can hit a realistic vmodel surface over loopback.
|
Stand-alone benchmark mock server: starts a local HTTP server backed by the production virtualmodel registries (with their default mock models pre-registered) so external benchmark drivers can hit a realistic vmodel surface over loopback. |
|
scenario
Package scenario holds the reusable mock-provider fixtures for the vmodel benchmark: named Scenarios, each carrying per-format MockResponseBuilders and a set of check.Assertions.
|
Package scenario holds the reusable mock-provider fixtures for the vmodel benchmark: named Scenarios, each carrying per-format MockResponseBuilders and a set of check.Assertions. |
|
Package vmodelclient provides in-process implementations of the client interfaces backed by vmodel registries.
|
Package vmodelclient provides in-process implementations of the client interfaces backed by vmodel registries. |
|
Package openai provides OpenAI-protocol virtual models.
|
Package openai provides OpenAI-protocol virtual models. |
|
Package virtualserver provides the HTTP handler for virtual model endpoints.
|
Package virtualserver provides the HTTP handler for virtual model endpoints. |