Documentation
¶
Index ¶
- Variables
- func EffortNames() []string
- func EffortString(level int) string
- func NormalizeErr(err error) error
- func ParseEffort(name string) int
- func RenderContentBlocksAsText(blocks []tools.ContentBlock) string
- func ToolSchema(t tools.Tool) json.RawMessage
- type APIConfig
- type Chunk
- type ChunkFunc
- type ChunkKind
- type ChunkSink
- type Client
- type ClientFactory
- type LLMParams
- type Message
- type Option
- func UnsetTemperature() Option
- func UnsetTopK() Option
- func UnsetTopP() Option
- func WithEffort(e int) Option
- func WithHTTPClient(c *http.Client) Option
- func WithMaxTokens(v int) Option
- func WithStopSequences(seqs ...string) Option
- func WithSystem(s string) Option
- func WithTemperature(v float64) Option
- func WithTopK(v int) Option
- func WithTopP(v float64) Option
- type Registry
- func (r *Registry) Build(name, model string, api APIConfig, opts []Option) (Client, error)
- func (r *Registry) Has(name string) bool
- func (r *Registry) MustRegister(name string, factory ClientFactory)
- func (r *Registry) Names() []string
- func (r *Registry) Register(name string, factory ClientFactory) error
- type Response
- type Role
- type ToolResult
- type Usage
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInterrupted = errors.New("llm: interrupted")
ErrInterrupted signals that the caller cancelled the request — typically the user pressing ESC in the TUI. Clients return this (wrapped) instead of the raw context.Canceled so callers can match without importing context.
Use errors.Is(err, llm.ErrInterrupted) to detect.
Functions ¶
func EffortNames ¶
func EffortNames() []string
EffortNames returns the sorted list of valid effort level names.
func EffortString ¶
EffortString converts an int level back to its name. Returns "medium" for unknowns.
func NormalizeErr ¶
NormalizeErr maps context cancellation to ErrInterrupted and leaves every other error untouched. Provider clients call this on transport-layer errors so the agent loop and TUI can treat user-initiated cancellation uniformly.
func ParseEffort ¶
ParseEffort converts a lowercase effort name to its int value. Returns 0 when the name is unknown.
func RenderContentBlocksAsText ¶
func RenderContentBlocksAsText(blocks []tools.ContentBlock) string
RenderContentBlocksAsText converts typed content blocks to a text-only representation for providers that do not support multimodal tool results. Image blocks are rendered as [Image: <mime>, <bytes>B] metadata stubs.
func ToolSchema ¶
func ToolSchema(t tools.Tool) json.RawMessage
ToolSchema returns the JSON schema for a tool's input, or a permissive default.
Types ¶
type APIConfig ¶
APIConfig is the per-provider credentials struct llm.Client factories receive. Aliased to pkg/config.APIConfig so the canonical definition can live in pkg/config (which pkg/llm and pkg/tools both depend on without forming a cycle).
type Chunk ¶
Chunk is one delta emitted during a streaming completion. Delta is the incremental text the provider just produced — never the cumulative buffer. Consumers append it to their own in-flight block.
type ChunkFunc ¶
type ChunkFunc func(Chunk)
ChunkFunc adapts a plain function into a ChunkSink. Convenient for tests and small inline consumers.
type ChunkKind ¶
type ChunkKind int
ChunkKind discriminates what kind of text a streaming delta carries. Providers translate their wire-level signal (text_delta, reasoning_content, thinking_delta, message.content, ...) into one of these values so the agent and UI never need to import a provider package.
type ChunkSink ¶
type ChunkSink interface {
OnChunk(Chunk)
}
ChunkSink consumes deltas as they arrive. Providers call OnChunk from the goroutine that owns the streaming read; calls are serialized by the provider, never invoked concurrently for the same Stream call.
Implementations should be fast and non-blocking. The agent's adapter (see internal/agent/stream.go) forwards each chunk to the event sink with the usual emit mutex, which is sufficient.
type Client ¶
type Client interface {
Name() string
Model() string
// SupportsDeferLoading reports whether this provider natively supports
// defer_loading (Anthropic, OpenAI). When false, the agent MUST NOT
// mutate the tools array between turns — doing so would change the
// prefix and invalidate prompt caching for providers that lack a
// server-side defer_loading / tool_reference mechanism.
SupportsDeferLoading() bool
Complete(ctx context.Context, messages []Message, tools []tools.Tool) (Response, error)
// Stream is the chunk-by-chunk variant of Complete. Implementations push
// each text/thinking delta through sink as it arrives, then return the
// fully assembled Response (content, thinking, signature, tool calls,
// usage). Cancellation rules are identical to Complete.
//
// A provider that has no native streaming endpoint MAY fall back to a
// buffered Complete and emit a single Chunk per kind at the end — this
// keeps the contract uniform at the cost of no progressive output.
Stream(ctx context.Context, messages []Message, tools []tools.Tool, sink ChunkSink) (Response, error)
// Apply tunes request parameters at runtime. Same options accepted by
// NewXxx — see WithSystem, WithTemperature, etc.
Apply(opts ...Option)
}
Client abstracts the LLM provider so the agent loop never imports a concrete SDK.
Cancellation contract: Complete MUST honor ctx and abort the in-flight request promptly when ctx is cancelled. On cancellation the implementation must return an error matching ErrInterrupted (via errors.Is). The TUI binds ESC to ctx cancellation, so this contract is what makes user interrupts work end-to-end.
type ClientFactory ¶
ClientFactory builds one llm.Client instance for the given provider credentials, model id, and option list. Each registered provider supplies a ClientFactory that wraps its own New() constructor.
Factories may return an error when the provided APIConfig is invalid (e.g. missing API key for a cloud provider). Returning nil for both Client and err is a programmer error.
type LLMParams ¶
type LLMParams struct {
Temperature *float64
TopP *float64
TopK *int
MaxTokens int
StopSequences []string
System string
Effort int // from 1~n (every model provider should adapt their own impl)
// HTTPClient overrides the transport used to talk to the provider.
// nil → http.DefaultClient.
HTTPClient *http.Client
}
LLMParams holds tunable request parameters shared across providers. Pointer fields preserve the "explicitly unset" semantic so each client can omit them and fall back to the upstream API's default.
type Message ¶
type Message struct {
Role Role
Content string
Thinking string
ThinkingSignature string
ToolCalls []*tools.Call
ToolResults []*ToolResult
}
Message is one turn of the conversation passed to and from the LLM.
ToolCalls is set on RoleAssistant turns when the model wants to invoke one or more tools. ToolResults is set on RoleTool turns and carries the result of each call, paired by ID with the corresponding ToolCall. A single RoleTool message carries every result for the preceding assistant turn — Anthropic mandates that fan-in, and the OpenAI-style converters fan it back out into per-call messages on the wire.
Thinking is provider-specific reasoning text. The TUI may render it, and providers that require it MUST echo it back in subsequent requests:
- DeepSeek: reasoning_content
- Anthropic: thinking blocks (with ThinkingSignature) — required whenever tool_use follows a thinking block, or the API errors 400.
ThinkingSignature is the opaque crypto signature Anthropic ships with each thinking block. Carry it round-trip; treat as a black box.
type Option ¶
type Option func(*LLMParams)
Option mutates LLMParams. Options are accepted by every client constructor and by the per-client Apply method, so the same knobs work at init and at runtime.
func UnsetTemperature ¶
func UnsetTemperature() Option
func WithEffort ¶
func WithHTTPClient ¶
func WithMaxTokens ¶
func WithStopSequences ¶
func WithSystem ¶
func WithTemperature ¶
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps provider names to ClientFactories. The agent loop resolves a (providerName, model) pair through Registry.Build at agent construction; downstream apps register additional providers before the first call to Build by writing to DefaultRegistry().
Registry is safe for concurrent use. Register fails on duplicate names — silently overwriting would let a typo route a model id to the wrong implementation. Use MustRegister at init time when a duplicate is a programming bug.
func DefaultRegistry ¶
func DefaultRegistry() *Registry
DefaultRegistry returns the process-wide registry. Pre-population is the caller's responsibility — import pkg/llm/builtins for its side effect to register the bundled providers (anthropic, deepseek, ollama):
import _ "github.com/johnny1110/evva/pkg/llm/builtins"
Or register a single provider explicitly:
llm.DefaultRegistry().MustRegister(claude.ProviderName, claude.Factory)
This keeps DefaultRegistry's content explicit and lets downstream apps opt into exactly the providers they want.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns an empty registry. Most callers want DefaultRegistry instead, which is pre-populated with the built-in providers.
func (*Registry) Build ¶
Build resolves the named provider's factory and invokes it. Returns an error for unregistered names — there is no silent fallback.
func (*Registry) MustRegister ¶
func (r *Registry) MustRegister(name string, factory ClientFactory)
MustRegister wraps Register and panics on error. Use only at init time where a duplicate or nil factory is a programmer bug.
func (*Registry) Names ¶
Names returns every registered provider name, sorted lexicographically. Useful for diagnostics, tests, and /model picker enumeration.
func (*Registry) Register ¶
func (r *Registry) Register(name string, factory ClientFactory) error
Register associates a factory with a provider name. Returns an error if name is empty, factory is nil, or name is already registered.
Example ¶
ExampleRegistry_Register demonstrates the canonical "add a custom LLM provider" pattern. Pick a unique name, supply a ClientFactory that wraps your provider's constructor, and the agent layer can then build clients for that provider by name through the same machinery the bundled providers use.
package main
import (
"context"
"fmt"
"github.com/johnny1110/evva/pkg/llm"
"github.com/johnny1110/evva/pkg/tools"
)
// nullClient is a deterministic stand-in for examples. Replace with a
// real provider in production downstream code (or import
// `_ "github.com/johnny1110/evva/pkg/llm/builtins"` for the bundled
// Anthropic / DeepSeek / Ollama clients).
type nullClient struct{ model string }
func (n *nullClient) Name() string { return "null" }
func (n *nullClient) Model() string { return n.model }
func (*nullClient) SupportsDeferLoading() bool { return false }
func (n *nullClient) Complete(context.Context, []llm.Message, []tools.Tool) (llm.Response, error) {
return llm.Response{Content: "ok"}, nil
}
func (n *nullClient) Stream(ctx context.Context, m []llm.Message, t []tools.Tool, _ llm.ChunkSink) (llm.Response, error) {
return n.Complete(ctx, m, t)
}
func (*nullClient) Apply(...llm.Option) {}
// ExampleRegistry_Register demonstrates the canonical "add a custom
// LLM provider" pattern. Pick a unique name, supply a ClientFactory
// that wraps your provider's constructor, and the agent layer can
// then build clients for that provider by name through the same
// machinery the bundled providers use.
func main() {
if err := llm.DefaultRegistry().Register("null-example",
func(_ llm.APIConfig, model string, _ ...llm.Option) (llm.Client, error) {
return &nullClient{model: model}, nil
},
); err != nil {
fmt.Println("register error:", err)
return
}
client, err := llm.DefaultRegistry().Build("null-example", "v0",
llm.APIConfig{}, nil)
if err != nil {
fmt.Println("build error:", err)
return
}
fmt.Println("provider:", client.Name(), "model:", client.Model())
}
Output: provider: null model: v0
type Response ¶
type Response struct {
Content string
Thinking string
ThinkingSignature string
ToolCalls []*tools.Call
Usage Usage
}
Response is what the LLM returns on each completion turn.
ToolCalls is non-empty when the model wants to invoke tools instead of (or in addition to) replying with text. Each call carries the provider's id in Call.ID — the agent echoes that back in the matching ToolResult.
Thinking carries any provider-specific reasoning trace; empty for providers that don't expose one. See Message.Thinking for the round-trip caveat.
type ToolResult ¶
type ToolResult struct {
ID string
Content string
IsError bool
ContentBlocks []tools.ContentBlock
}
ToolResult pairs a tool call's id with the result the agent dispatched. Lives on RoleTool messages so one message can carry N results from a parallel-dispatched assistant turn.
type Usage ¶
type Usage struct {
InputTokens int
OutputTokens int
CacheReadTokens int
CacheCreationTokens int
ReasoningTokens int
}
Usage reports token counts from a single LLM call. Zero values mean "unknown / not reported" — fields are populated only when the provider returns them, so accumulating zero across turns is safe.
CacheReadTokens / CacheCreationTokens are populated by Anthropic when prompt caching is in play and left zero by every other provider. ReasoningTokens is the share of output spent on hidden reasoning (DeepSeek reasoning_content, OpenAI o1-style chains).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package builtins registers evva's bundled LLM providers (Anthropic, DeepSeek, OpenAI, Ollama) into pkg/llm.DefaultRegistry().
|
Package builtins registers evva's bundled LLM providers (Anthropic, DeepSeek, OpenAI, Ollama) into pkg/llm.DefaultRegistry(). |