Documentation
¶
Overview ¶
Package model defines the abstraction every reasoning provider in Korvun talks through. A Model speaks in role-tagged conversation messages (system / user / assistant), not in envelope.Envelope — the Brain layer (internal/brain, Stage 7) is responsible for translating between the two domains, the same way the channel adapters translate native transport formats to and from Envelope.
See ADR-0009 for the design rationale.
Phase 4.1 ships the synchronous Generate path only. Streaming arrives later as a sibling StreamingModel interface; provider support is opt-in by satisfying that interface in addition to Model.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNilRequest is returned by Generate when called with a // nil *Request. Adapter wrappers may also surface this. ErrNilRequest = errors.New("model: request is nil") // ErrEmptyModel is returned when Request.Model is the empty // string. Every provider needs a concrete model identifier. ErrEmptyModel = errors.New("model: request model name is empty") // ErrEmptyMessages is returned when Request.Messages is empty. // A Model with no conversation has nothing to answer. ErrEmptyMessages = errors.New("model: request has no messages") // ErrInvalidRole is returned when a Message carries a Role // value outside the recognised RoleSystem / RoleUser / // RoleAssistant range. ErrInvalidRole = errors.New("model: message has invalid role") // ErrEmptyContent is returned when a Message has Role set but // Content empty. An empty turn carries no information. ErrEmptyContent = errors.New("model: message content is empty") // when the underlying transport fails to deliver the request // (network error, server down, etc.). Wraps the underlying // cause for errors.Is / errors.As inspection. ErrProviderUnavailable = errors.New("model: provider unavailable") // ErrProviderResponse is returned by adapter implementations // when the underlying provider responded but the payload could // not be parsed or the status code was non-2xx. Wraps the // underlying cause. ErrProviderResponse = errors.New("model: provider returned a bad response") // ErrAuthInvalid is returned by adapter implementations when the // underlying provider rejected the call for missing or invalid // credentials (typically HTTP 401 or 403 on cloud APIs). Distinct // from ErrProviderUnavailable because a retry will not help — the // operator must fix the credentials before the call can succeed. // (ADR-0010 §1 / §4.) ErrAuthInvalid = errors.New("model: provider authentication failed") // ErrToolsUnsupported is returned by a ToolCallingModel adapter when the // underlying MODEL refuses the tools protocol itself — verified raw // against ollama 0.30.8 + gemma3:270m (2026-08-15, deterministic 3/3): // HTTP 400 {"error":"...does not support tools"}. Distinct from // ErrProviderResponse because the caller can DEGRADE: the same model // answers the plain chat lane, so the agent falls back to the // prompt-protocol lane instead of failing the message (RT-3). ErrToolsUnsupported = errors.New("model: model does not support native tool calling") // ErrRateLimited is returned by adapter implementations when the // underlying provider rejected the call for exceeding a quota // (typically HTTP 429 on cloud APIs). Recoverable by waiting; a // fan-out / retry policy upstream of the adapter (Phase 4.3) can // inspect *RateLimitError via errors.As to recover the optional // RetryAfter hint. (ADR-0010 §1 / §4.) ErrRateLimited = errors.New("model: provider rate-limited the caller") )
Sentinel errors every Model adapter returns at the upstream validation seam. Callers match with errors.Is rather than string comparison.
Functions ¶
func ParseRetryAfter ¶ added in v0.2.0
ParseRetryAfter reads a Retry-After header value as an integer number of seconds and returns it as a time.Duration. Empty or unparseable values return zero — the consumer interprets zero as "no hint given" (e.g. HTTP 429 without a usable retry-after).
It parses the seconds form only. The HTTP spec also allows an HTTP-date form, which is deliberately NOT handled here: neither the Ollama nor the Groq adapter — the two current consumers — emits it today, so an HTTP-date value yields zero. A future provider that uses the date form would require extending this function.
This is the single source of truth shared by internal/model/ollama and internal/model/groq (ADR-0031 sub-phase 3, decision D2); the body is the former groq.parseRetryAfter verbatim.
func ValidateRequest ¶
ValidateRequest checks the universal upstream invariants every adapter expects: non-nil request, non-empty Model, at least one Message, each Message with a recognised Role and non-empty Content. Adapters call this first thing inside Generate so validation errors look the same across providers.
Types ¶
type Message ¶
type Message struct {
Role Role
Content string
// ToolCalls carries the model's native tool requests on an assistant
// turn; ToolName labels a RoleTool result turn (ADR-0042 §2). Both are
// ADDITIVE and zero outside the native lane: existing adapters build
// their wire structs from Role/Content only, so old-lane requests are
// byte-identical — the prohibition on widening model.Model stands, the
// DTO merely grew.
ToolCalls []ToolCall
ToolName string
// ToolCallID correlates a RoleTool result turn with the ToolCall.ID it
// answers, on wires that thread ids (ADR-0044 SP-B FR-GWB-1). ADDITIVE:
// zero on the ollama lane, threaded by the agent loop when present.
ToolCallID string
}
Message is one turn of the conversation handed to a Model. Role labels who authored it; Content is the plain text payload. Phase 4.1 does not model multimodal content (images, tool calls, vision) — those grow as additive fields or sibling Part types when a real consumer needs them.
type Model ¶
type Model interface {
// Generate produces the assistant reply for the given request.
Generate(ctx context.Context, req *Request) (*Response, error)
// Name returns the canonical provider name (e.g. "ollama"),
// used for error wrapping and (later) by the Registry.
Name() string
}
Model is the contract every reasoning provider implements. Generate produces an assistant Message from the given conversation; implementations MUST propagate ctx to the underlying HTTP request so cancellation works end-to-end.
Streaming is intentionally not on this interface — providers that support it additionally satisfy StreamingModel (declared in a later phase) so non-streaming callers do not pay any streaming cognitive cost. See ADR-0009 §2.
type RateLimitError ¶
RateLimitError is the concrete error type returned when a provider signals a rate-limit hit. Provider identifies the source (handy when a fan-out sees several errors from different providers); RetryAfter is the suggested wait, zero when the provider did not advise one (e.g. HTTP 429 without a retry-after header).
Wraps ErrRateLimited so errors.Is(err, ErrRateLimited) keeps working without callers having to know the concrete type; errors.As(err, &rle) recovers the metadata. Same pattern as net.OpError, os.PathError, etc.
func (*RateLimitError) Error ¶
func (e *RateLimitError) Error() string
Error implements the error interface. Includes the provider and, when present, the retry-after hint, so a log line is self- describing without forcing the caller to type-assert.
func (*RateLimitError) Unwrap ¶
func (e *RateLimitError) Unwrap() error
Unwrap returns the wrapped ErrRateLimited sentinel so errors.Is(err, ErrRateLimited) succeeds on a *RateLimitError.
type Request ¶
Request is the input to Model.Generate. Model names the provider-side model identifier (e.g. "llama3.2" for Ollama, "gpt-4o" for OpenAI); Messages is the ordered conversation, with system prompts first by convention. The Model adapter does not enforce ordering — that is a Brain concern.
type Response ¶
Response is what Model.Generate returns. Message is the assistant turn (Role == RoleAssistant on success); Provider and ModelName label the source so a fan-out result (Phase 4.3) keeps its attribution without a side channel.
type Role ¶
type Role int
Role classifies a Message by its author in the LLM-style conversation. The zero value is intentionally invalid so an uninitialised Message never silently masquerades as one of the real roles.
Recognised roles. Numeric order is irrelevant to providers (they see the lowercase string from Role.String), so additions are safe at any position.
const RoleTool Role = RoleAssistant + 1
RoleTool labels a tool-result turn in the native lane (the verified wire role "tool"). It never appears in prompt-protocol conversations.
type ToolCall ¶ added in v0.7.0
type ToolCall struct {
// ID is the provider's call identifier, used by wires that correlate
// tool results by id (the OpenAI-compatible lane, ADR-0044 SP-B
// FR-GWB-1). ADDITIVE: zero for providers without ids (ollama neither
// populates nor reads it — its wire has no id field).
ID string
// Name is the requested tool's name.
Name string
// Arguments is the raw JSON object the provider returned. Under the
// uniform v1 schema the payload is Arguments["args"] (a string); the
// lane re-serializes anything else verbatim for the tool to parse.
Arguments map[string]any
}
ToolCall is one native tool request returned by the model (ADR-0042 §1).
type ToolCallingModel ¶ added in v0.7.0
type ToolCallingModel interface {
Model
// GenerateWithTools is Generate plus a tool catalog: the reply may carry
// Message.ToolCalls (the model wants tools) or plain Content (the final
// answer). Implementations honor the same statelessness and concurrency
// contract Generate carries.
GenerateWithTools(ctx context.Context, req *Request, tools []ToolSpec) (*Response, error)
}
ToolCallingModel is the native-lane capability interface (ADR-0042 §1, ADR-0021 §3.4): providers with structured tool calling ALSO satisfy it. The AgentBrain discovers it by type assertion and prefers it; models without it keep the prompt-protocol lane, unchanged.
type ToolParamSpec ¶ added in v0.7.0
ToolParamSpec is one structured field of a ToolSpec (all strings in v1).
type ToolSpec ¶ added in v0.7.0
type ToolSpec struct {
// Name is the tool's protocol name.
Name string
// Description is the capability line advertised to the model.
Description string
// Params, when non-empty, declares the tool's structured string fields
// (the ParamTool surface — the 2026-08-09 demo lesson). Empty keeps
// the uniform {"args": string} schema.
Params []ToolParamSpec
}
ToolSpec advertises one tool to a native tool-calling model (ADR-0042 §1). The v1 parameter schema is UNIFORM — a single string argument "args", documented by Description (each built-in already explains its format there) — so the tool.Tool seam's Execute(ctx, args string) contract stays untouched. Richer per-tool schemas are an additive future extension.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package fanout coordinates the parallel dispatch of a single model.Request to N model.Model implementations and collects every outcome.
|
Package fanout coordinates the parallel dispatch of a single model.Request to N model.Model implementations and collects every outcome. |
|
Package groq implements internal/model.Model against the Groq cloud API.
|
Package groq implements internal/model.Model against the Groq cloud API. |
|
Package ollama implements internal/model.Model against a local Ollama server.
|
Package ollama implements internal/model.Model against a local Ollama server. |
|
Package openaicompat implements internal/model.Model against ANY OpenAI-compatible chat-completions endpoint — cloud or local — selected by config alone (ADR-0044, the universal model gateway).
|
Package openaicompat implements internal/model.Model against ANY OpenAI-compatible chat-completions endpoint — cloud or local — selected by config alone (ADR-0044, the universal model gateway). |
|
Package retry provides a per-instance decorator over model.Model that owns the per-attempt deadline for every dispatch shape and retries ONLY genuinely transient post-load errors (ADR-0031 Decisions 4 and 5).
|
Package retry provides a per-instance decorator over model.Model that owns the per-attempt deadline for every dispatch shape and retries ONLY genuinely transient post-load errors (ADR-0031 Decisions 4 and 5). |
|
Package sequential coordinates the SERIAL dispatch of a single model.Request to N model.Model implementations, stopping at the first success.
|
Package sequential coordinates the SERIAL dispatch of a single model.Request to N model.Model implementations, stopping at the first success. |