Documentation
¶
Overview ¶
Package llm is the model provider boundary.
One interface, several backends. §10.1 requires this rather than a direct Anthropic wrapper: token-mode budgeting is justified partly by users on self-hosted models, and hard-wiring one vendor would make that claim false.
Every call returns Usage, because the budget ledger settles against it. A provider that cannot report tokens cannot be used in a metered session — see §4.2 for the delegated mode where that is true and what replaces the budget there.
Index ¶
- Constants
- Variables
- func EstimateTokens(chars int) int64
- func IsLoopback(baseURL string) bool
- func Reachable(ctx context.Context, baseURL string, client *http.Client) bool
- func Retryable(err error) bool
- type APIError
- type Chunk
- type ChunkOptions
- type Config
- type CredentialSource
- type Detected
- type Kind
- type LocalEndpoint
- type MapReduce
- type MapResult
- type Message
- type Provider
- type Request
- type Response
- type Role
- type SourcedProvider
- type Tier
- type Usage
- type Vendor
Constants ¶
const ( DefaultAnthropicStrong = "claude-opus-5" DefaultAnthropicCheap = "claude-haiku-4-5" )
Defaults for the Anthropic backend. Opus 5 for reasoning, Haiku 4.5 for the per-chunk extraction work that runs an order of magnitude more often.
const (
MaxReasoningAllowance = 16384
)
Reasoning allowance bounds.
MaxReasoningAllowance caps what one model may be given for thinking: a model that has not produced an answer in this many tokens is not about to, and a token budget that grows without a ceiling is not a budget.
firstReasoningAllowance is what a first empty response jumps to, and it is measured rather than chosen. qwen3:4b asked for the JSON `{"ok":true}` — the smallest useful prompt there is — spent 1,924 completion tokens on its chain in one run and more than 2,128 in the next, on the same prompt. A model that reasons at all reasons in thousands, and stepping there 512 tokens at a time means paying for four wasted calls to learn what one can.
maxReasoningAttempts bounds the paid discovery at three provider calls.
Variables ¶
var ( // ErrRateLimited is transient; the executor backs off and retries (§9.5). ErrRateLimited = errors.New("llm: rate limited") // never succeeds. ErrUnauthorized = errors.New("llm: unauthorized") // ErrQuotaExceeded is fatal for this session. ErrQuotaExceeded = errors.New("llm: quota exceeded") // ErrOverloaded is transient. ErrOverloaded = errors.New("llm: provider overloaded") // ErrNoUsageReported means the provider returned a completion without // token counts. Treated as an error rather than a zero charge: a silent // zero would make the budget ceiling unenforceable for that provider. ErrNoUsageReported = errors.New("llm: provider reported no token usage") // ErrContextTooLong means the request exceeded the model's window. The // chunker's job is to prevent this; when it happens anyway the actor // re-splits rather than failing the lead. ErrContextTooLong = errors.New("llm: context too long") // ErrEmptyOutput means the provider returned a successful response with no // content. // // The case that produces it in practice is a reasoning model: qwen3, gemma4 // and others emit a `reasoning` field that mole does not read, and it is // charged against the same output budget. Ask for too few tokens and the // whole allowance goes to reasoning, leaving content empty with // finish_reason "length" — a successful call that returned nothing. // // Worth its own sentinel because the symptom is otherwise a JSON parse // failure, which sends whoever reads the log looking at the prompt instead // of at MaxTokens. ErrEmptyOutput = errors.New("llm: provider returned empty content") )
Functions ¶
func EstimateTokens ¶
EstimateTokens approximates a token count from characters.
Deliberately rough and deliberately high: it is used to decide how many chunks fit a sub-budget BEFORE any call is made, and over-estimating stops early while under-estimating overspends. Actual charges always come from the provider's reported usage, never from this.
func IsLoopback ¶
IsLoopback reports whether a base URL points at this machine.
func Reachable ¶
Reachable probes an OpenAI-compatible endpoint.
Only meaningful for local runtimes, where the check is free and instant. A hosted provider cannot be verified without spending money, so `doctor` reports those as configured-but-unverified and points at `config test-llm` rather than printing a green tick it has not earned.
Types ¶
type APIError ¶
type APIError struct {
Provider string
Status int
Body string
// contains filtered or unexported fields
}
APIError carries provider detail alongside a sentinel.
type Chunk ¶
Chunk is one contiguous span of the source.
func Split ¶
func Split(text string, opts ChunkOptions) []Chunk
Split divides text into chunks.
Offsets index the ORIGINAL string, so a quote found in a chunk can be located in the source without re-searching — which is what keeps quote verification exact for documents that were never held in one context.
type ChunkOptions ¶
type ChunkOptions struct {
// MaxChars is the target ceiling per chunk. Characters rather than tokens
// because the tokenizer differs per provider and an estimate that runs
// long produces a request rejection rather than a slightly larger bill.
MaxChars int
// OverlapChars repeats the tail of the previous chunk. A claim near a
// boundary is otherwise visible in neither chunk with full context.
OverlapChars int
// MinChars avoids emitting a final sliver that costs a model call and
// carries nothing.
MinChars int
}
ChunkOptions tune splitting.
func DefaultChunkOptions ¶
func DefaultChunkOptions() ChunkOptions
DefaultChunkOptions targets roughly 6k tokens of input per chunk on typical English prose, leaving generous room under any current context window.
type Config ¶
type Config struct {
Kind Kind
// APIKey may be empty for Anthropic, in which case the SDK's own
// credential chain resolves it — an env var, then an `ant auth login`
// profile. That is what lets a machine with a profile need no
// configuration at all (§4.2).
APIKey string
// BaseURL overrides the endpoint. Required for OpenAI-compatible backends.
BaseURL string
// StrongModel and CheapModel resolve the two tiers.
StrongModel string
CheapModel string
MaxRetries int
Timeout time.Duration
}
Config selects and configures a provider.
type CredentialSource ¶
type CredentialSource string
CredentialSource records how a provider resolved its credential, so `doctor` can answer "which one is this actually using" without guessing.
const ( CredentialConfig CredentialSource = "config" CredentialEnv CredentialSource = "env" CredentialChain CredentialSource = "sdk credential chain" CredentialNotNeeded CredentialSource = "none required" )
func SourceOf ¶
func SourceOf(p Provider) CredentialSource
SourceOf reports how a provider authenticated, or CredentialConfig when the provider does not track it.
type Detected ¶
type Detected struct {
Config Config
// Reason is shown to the user, so it has to name what was found rather
// than just asserting success.
Reason string
// Free is true for local runtimes, where token-mode budgeting is the only
// meaningful ceiling because no money changes hands.
Free bool
}
Detected describes a provider found without configuration.
type LocalEndpoint ¶
type LocalEndpoint struct {
Name string
BaseURL string
// Probe is the path that lists models; a 200 means something is listening
// and speaking the right dialect.
Probe string
}
LocalEndpoint is a runtime worth probing on localhost.
func LocalEndpoints ¶
func LocalEndpoints() []LocalEndpoint
LocalEndpoints are the runtimes checked, in order.
type MapReduce ¶
type MapReduce struct {
Chunks []Chunk
// Truncated is set when the sub-budget could not cover every chunk.
Truncated bool
// Skipped counts chunks dropped for budget.
Skipped int
}
MapReduce is the plan for summarizing a document that does not fit.
It is a plan rather than an executor because the actor owns the budget: the actor asks how many chunks it can afford, runs that many, and records Truncated rather than silently dropping content or blowing the reservation (§4.1).
func Plan ¶
func Plan(text string, maxInputTokens int64, opts ChunkOptions) MapReduce
Plan splits text and decides how much of it the sub-budget can cover.
maxInputTokens is the sub-budget derived from the lead's reservation. When the document exceeds it, the highest-value chunks are kept — which here means the earliest, since article structure front-loads the substance and a truncation that kept the middle would read as incoherent.
type Provider ¶
type Provider interface {
Complete(ctx context.Context, req Request) (*Response, error)
// Name identifies the backend for logs and diagnostics.
Name() string
// ModelFor reports which model a tier resolves to, so cost estimates and
// `doctor` can name it without making a call.
ModelFor(tier Tier) string
}
Provider is a model backend.
type Request ¶
type Request struct {
// Tier picks the model when Model is empty.
Tier Tier
// Model overrides the tier's default.
Model string
System string
Messages []Message
MaxTokens int
// Effort maps to the provider's reasoning-depth control where it has one.
Effort string
// Thinking asks for extended reasoning. Off for extraction work, where it
// adds latency and tokens without improving a mechanical task.
Thinking bool
}
Request is one completion.
type Response ¶
type Response struct {
Text string
Model string
StopReason string
Usage Usage
Elapsed time.Duration
// Reasoning is a reasoning model's chain of thought, kept separate from Text
// and never concatenated into it: it is not the answer, it is not quotable
// evidence, and §11.5 would let a model cite its own reasoning as a source if
// the two were joined.
//
// Carried rather than discarded so a caller can log what a model spent its
// allowance on when the answer came back thin.
Reasoning string
// ReasoningTokens is what the chain cost, where the provider reports it or it
// can be attributed. NOT part of Usage: providers count reasoning inside
// completion_tokens, so adding it to the ledger's output count would charge
// the same tokens twice.
ReasoningTokens int64
// Attempts is how many provider calls produced this response. More than one
// means the first was spent entirely on reasoning and the ceiling was raised
// — Usage covers every attempt, because the tokens were spent either way.
Attempts int
// Refused is set when the provider's safety classifiers declined the
// request. It arrives as a successful HTTP response, so code that reads
// Text without checking this gets an empty string and no error.
Refused bool
// RefusalCategory carries the provider's reason where one is given.
RefusalCategory string
}
Response is one completion result.
type SourcedProvider ¶
type SourcedProvider interface {
Provider
CredentialSource() CredentialSource
}
SourcedProvider is a Provider that can report how it authenticated.
type Tier ¶
type Tier string
Tier selects how much capability a call needs.
Splitting the two is a real cost lever: chunk mining runs once per chunk and is mostly extraction, while planning and synthesis run once per lead or session and carry the reasoning. Using one model for both either overpays on the many calls or underperforms on the few that matter.
type Usage ¶
type Usage struct {
InputTokens int64
OutputTokens int64
CacheReadTokens int64
CacheWriteTokens int64
}
Usage is the token accounting a provider reports.
This is not optional. The ledger charges against these numbers, and a provider that returns zeros silently makes the session's ceiling unenforceable — which is why Complete rejects a response with no usage on a non-empty completion.
type Vendor ¶
type Vendor struct {
Name string
Kind Kind
BaseURL string
// Example models, so an error message can be acted on rather than
// researched. Not defaults: picking a model for someone silently is how a
// session runs against something they did not choose.
Models string
}
Vendor describes where a key came from and what it needs to work.
func VendorFromKey ¶
VendorFromKey identifies the provider a key belongs to.
This exists to catch one specific false green: llm.provider defaults to Anthropic when unset, so a Groq or OpenAI key set on its own produces a doctor line reading "✓ claude-opus-5 via config" and then an auth failure at the first model call — potentially many leads into a paid session.
Prefixes are a convention, not a guarantee, so an unrecognized key is not an error. Only a key that clearly belongs to a DIFFERENT vendor than the one configured is worth refusing.