Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

145 lines
5.9 KiB
Go

// Package llm — provider abstraction.
//
// Provider isolates every LLM operation (the agent tool-loop and the
// three search-assist passes) from where inference actually runs.
// Nine implementations live under internal/llm/provider/: a llama.cpp
// `local` provider (CGO, `-tags llama`), six pure-Go HTTP providers
// (`anthropic`, `openai`, `ollama`, `gemini`, `bedrock` — SigV4-signed,
// no AWS SDK — and `deepseek`), and two subprocess CLI providers,
// `claudecli` and `codex`, that shell out to the user's `claude` /
// `codex` binary (reusing their existing Claude Code / Codex sign-in —
// no API key needed). They are swapped via the `llm.provider` config
// key — see Config.
//
// The whole surface is a single method, Complete: one structured
// single-turn call. The agent loop is just repeated Complete calls
// with a growing Messages slice; the assist passes are one Complete
// call each. Keeping the interface to one method is what lets the
// HTTP providers stay small and the build-tag split stay contained to
// the `local` package.
package llm
import "context"
// Role identifies who produced a Message in a provider conversation.
type Role string
const (
RoleSystem Role = "system"
RoleUser Role = "user"
RoleAssistant Role = "assistant"
RoleTool Role = "tool"
)
// Message is one turn in a provider conversation. Content is the plain
// text payload — for a RoleAssistant message that represents a tool
// call it holds the raw tool-call JSON; for a RoleTool message it
// holds the tool's observation and ToolName names the tool that
// produced it.
//
// The conversation is provider-neutral: the `local` provider flattens
// it through a llama chat template, the HTTP providers map it onto
// their native messages array. Tool calls are carried as plain text
// (the "emulated" protocol the local model already uses), not via any
// provider's native tool-use wire format — that keeps a single
// Message shape working across all four providers.
type Message struct {
Role Role
Content string
ToolName string // set on RoleTool messages: which tool produced Content
}
// ToolSpec describes one callable tool to a provider. When a request
// carries Shape == ShapeToolCall the provider constrains the emitted
// "tool" field to exactly these names.
type ToolSpec struct {
Name string
Description string
}
// JSONShape names the structured-output schema a provider must enforce
// on a completion. ShapeFreeform applies no constraint; every other
// value corresponds to a concrete JSON object shape (see
// JSONSchemaFor) that the provider guarantees the response conforms
// to — via a GBNF grammar (local) or a json-schema / forced-tool
// mechanism (HTTP providers).
type JSONShape int
const (
ShapeFreeform JSONShape = iota // no structured constraint
ShapeExpandTerms // {"terms":[<string>...]}
ShapeRerankOrder // {"order":[<string>...]}
ShapeVerifyKeep // {"keep":[<string>...]}
ShapeToolCall // {"tool":<enum>,"args":{...}}
)
// CompletionRequest is one single-turn request to a Provider. The
// provider flattens Messages into its native wire format, applies the
// structured-output mechanism implied by Shape, and returns the raw
// model text.
type CompletionRequest struct {
// Messages is the conversation so far, oldest first. The last
// message is whatever the model should respond to.
Messages []Message
// MaxTokens caps generation length. 0 lets the provider pick a
// sensible default.
MaxTokens int
// Shape is the structured-output contract for the response.
Shape JSONShape
// Tools is consulted only when Shape == ShapeToolCall: the provider
// constrains the emitted "tool" field to these names.
Tools []ToolSpec
}
// TokenUsage carries the token accounting for a single completion: the
// prompt (input) tokens billed, the generated (output) tokens, and the
// prompt-cache read / write tokens when the provider reports them. A
// provider that does not surface usage leaves every field zero.
type TokenUsage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
CacheReadTokens int `json:"cache_read_tokens"`
CacheWriteTokens int `json:"cache_write_tokens"`
}
// Add folds u' into u in place, accumulating each token field. Used by
// the agent loop to sum usage across the tool-calling steps of one run.
func (u *TokenUsage) Add(other TokenUsage) {
u.InputTokens += other.InputTokens
u.OutputTokens += other.OutputTokens
u.CacheReadTokens += other.CacheReadTokens
u.CacheWriteTokens += other.CacheWriteTokens
}
// IsZero reports whether no usage was recorded — the signal a provider
// could not report token counts (subprocess / not-yet-decoded HTTP).
func (u TokenUsage) IsZero() bool {
return u.InputTokens == 0 && u.OutputTokens == 0 &&
u.CacheReadTokens == 0 && u.CacheWriteTokens == 0
}
// CompletionResponse is a Provider's single-turn output. Text is the
// raw model text — JSON conforming to the requested Shape when Shape
// is not ShapeFreeform. Usage carries the provider's token accounting
// when it reports one (zero otherwise).
type CompletionResponse struct {
Text string
Usage TokenUsage
}
// Provider is a single-turn LLM completion backend. The agent loop and
// the search-assist passes are both built on repeated Complete calls.
type Provider interface {
// Name returns the provider's short identifier — one of "local",
// "anthropic", "openai", "ollama", "claudecli", "codex", "gemini",
// "bedrock", "deepseek". Used to pick the prompt tier (see
// PromptProfile) and for diagnostics.
Name() string
// Complete runs one single-turn completion, honouring req.Shape
// with whatever structured-output mechanism the provider has.
Complete(ctx context.Context, req CompletionRequest) (CompletionResponse, error)
// Close releases any held resources (model weights, idle HTTP
// connections). Safe to call multiple times.
Close() error
}