Documentation
¶
Overview ¶
Package llmkit — the SHARED OpenAI-compatible LLM client every consumer that talks to an OpenAI-style endpoint uses: the generic pipeline engine's agent stages (candy/plugin-pipeline) and the `vision:` check verb (candy/plugin-vision), which validates a screenshot by sending it to the same endpoint.
ONE home (R3 + the boundary law): the client was plugin-local in plugin-pipeline; it belongs in the SDK so any plugin speaks the endpoint identically — the same streaming/accumulator/idle-bound/vision machinery, the same parameter mapping, the same no-env-bleed guarantee. The CUE vocabulary it maps (spec.LLMSpec/spec.LLMParams) lives in the contract module.
Built on the OFFICIAL github.com/openai/openai-go/v3 SDK. The SDK owns the wire format, the SSE decoder, the chunk accumulator, tool-call assembly and the typed request params; this package owns the things the SDK does not:
- the IDLE bound (a per-chunk watchdog that cancels the request) — NOT a whole-generation deadline, so a slow-but-progressing model is never cut off while a silent provider fails in bounded time;
- ollama's NON-STANDARD `reasoning` delta, which the SDK accumulator drops, read per-chunk from the raw JSON so a reasoning-only 200 is reported as a NAMED empty completion rather than a bare one;
- the empty-completion guard (a 200 with neither content nor tool calls is an ERROR, never a silent empty turn);
- the no-env-bleed guarantee: the client is built from the given options ONLY, never via openai.NewClient (which reads OPENAI_API_KEY/ OPENAI_BASE_URL/OPENAI_CUSTOM_HEADERS and would let a stray operator env hijack a lane).
Index ¶
- Constants
- func ChatVision(ctx context.Context, cfg Config, prompt string, images []string) (string, error)
- func FunctionTool(name, description string, parameters map[string]any) openai.ChatCompletionToolUnionParam
- func ImageDataURL(mime string, data []byte) string
- func MergeParams(base, over spec.LLMParams) spec.LLMParams
- func ParseDuration(s string) (time.Duration, bool)
- func Strptr(s string) *string
- func System(s string) openai.ChatCompletionMessageParamUnion
- func Text(s string) openai.ChatCompletionMessageParamUnion
- func ToSDKMessages(msgs []Message) []openai.ChatCompletionMessageParamUnion
- func ToSDKToolCalls(calls []ToolCall) []openai.ChatCompletionMessageToolCallUnionParam
- func VisionParts(prompt string, images []string) []openai.ChatCompletionContentPartUnionParam
- type Config
- type EmptyCompletionError
- type Message
- type StallError
- type ToolCall
- type Usage
Constants ¶
const ( DefaultBaseURL = "http://localhost:11434/v1" DefaultModel = "deepseek-v4.1-flash:cloud" DefaultIdleTimeout = 3 * time.Minute )
Defaults keep a consumer runnable with no authored config — the local ollama server is the built-in default layer.
Variables ¶
This section is empty.
Functions ¶
func ChatVision ¶
ChatVision sends one multimodal completion: `prompt` plus the given images (base64 data URLs), returning the assistant's text. This is the primitive the `vision:` check verb drives — the same idle bound, accumulator and empty-completion guard as Chat.
A successful Chat with no tools always carries content (the empty-completion guard rejects the alternative), so the deref below cannot silently turn a failure into "".
func FunctionTool ¶
func FunctionTool(name, description string, parameters map[string]any) openai.ChatCompletionToolUnionParam
FunctionTool renders one function tool from a name/description/JSON-schema parameters object. An empty parameters map yields a tool with no argument schema.
func ImageDataURL ¶
ImageDataURL renders raw image bytes as an OpenAI image_url data URL. Ollama's OpenAI layer accepts a base64 data URL ONLY (remote image URLs are explicitly unsupported), so this is the canonical way to hand an image to the endpoint.
func MergeParams ¶
MergeParams overlays `over` onto `base` FIELD-WISE (over wins per set field), merging the open maps. This is what makes a step-level override change one knob without discarding the entity's other parameters.
func ParseDuration ¶
ParseDuration parses a non-empty, positive Go duration; false otherwise.
func System ¶
func System(s string) openai.ChatCompletionMessageParamUnion
System builds a system message.
func Text ¶
func Text(s string) openai.ChatCompletionMessageParamUnion
Text builds a user text message.
func ToSDKMessages ¶
func ToSDKMessages(msgs []Message) []openai.ChatCompletionMessageParamUnion
ToSDKMessages converts a caller transcript to the SDK message union.
func ToSDKToolCalls ¶
func ToSDKToolCalls(calls []ToolCall) []openai.ChatCompletionMessageToolCallUnionParam
ToSDKToolCalls converts caller tool-call rows to the SDK param union.
func VisionParts ¶
func VisionParts(prompt string, images []string) []openai.ChatCompletionContentPartUnionParam
VisionParts builds a multimodal user message: the prompt text plus one image per URL (a data URL from ImageDataURL, or a remote URL which a full OpenAI endpoint accepts even though ollama's compat layer does not).
Types ¶
type Config ¶
type Config struct {
BaseURL string
Model string
APIKey string
Organization string
Project string
Timeout time.Duration
IdleTimeout time.Duration
MaxRetries int
Headers map[string]string
Params spec.LLMParams
}
Config is the RESOLVED endpoint + request config for one call. Build it with Default(), overlay authored blocks with Apply, then overlay the operator env with FromEnv — the same field-wise precedence the pipeline documents (env > step > entity > default).
func Default ¶
func Default() Config
Default returns the built-in config layer, NORMALIZED so its `Params` reflect the same defaults every call applies (include_usage in particular).
func (Config) Apply ¶
Apply overlays an authored #LLMSpec FIELD-WISE: a layer fills only what the higher layer left unset, and `params`/`headers`/`extra` merge rather than replace, so a later layer can tighten one knob without wiping its siblings.
type EmptyCompletionError ¶ added in v0.2026263.1023
type EmptyCompletionError struct {
ReasoningBytes int
FinishReason string
// Reasoning is the FULL reasoning text produced before the empty completion.
// It is the diagnostic a runaway RCA needs: a byte count cannot distinguish a
// model re-deriving one point (a loop) from one deliberating a genuinely long
// chain. Kept on the typed error so no caller can drop it.
Reasoning string
}
EmptyCompletionError is the DETERMINISTIC "the provider returned a completion with no content and no tool calls" failure. It is exported and typed so a caller can tell it apart from a transport/idle failure: it is NOT retryable with the same parameters (a `finish_reason: length` outcome is a budget decision, not a flake), and it carries the diagnostic fields an RCA needs — how much reasoning was produced and the provider's own stop reason.
func (*EmptyCompletionError) Error ¶ added in v0.2026263.1023
func (e *EmptyCompletionError) Error() string
type Message ¶
type Message struct {
Role string
Content *string
ToolCalls []ToolCall
ToolCallID string
Reasoning string
// FinishReason is the provider's completion stop reason for the turn
// ("stop", "length", "tool_calls", "content_filter", …). It is surfaced so a
// caller can tell a TRUNCATED turn ("length") from a natural stop — the
// distinction the unbounded-generation RCA turns on. Empty when the provider
// reported none.
FinishReason string
// Usage is the provider's token accounting for the turn when it reports one
// (llmkit requests it via stream_options.include_usage). It carries the
// reasoning-token count that makes an unbounded reasoning generation
// measurable rather than inferred. nil when the provider reported no usage.
Usage *Usage
}
Message is the caller's transcript row. It is deliberately NOT a wire type (the SDK owns the wire): Content is a *string so "no content" (a pure tool-call turn) is distinguishable from "".
Reasoning carries ollama's NON-STANDARD `reasoning` output for the turn when the provider emitted one. It is SURFACED here rather than only used for the empty-completion diagnostic, so a caller (and a test) can observe that the non-standard field was actually read — the client captures it from the raw JSON because the OpenAI schema has no slot for it.
func Chat ¶
func Chat(ctx context.Context, cfg Config, msgs []openai.ChatCompletionMessageParamUnion, tools []openai.ChatCompletionToolUnionParam) (Message, error)
Chat issues ONE streaming chat-completions call and returns the assembled assistant turn.
IDLE BOUND, NOT A WHOLE-GENERATION DEADLINE. Streaming is load-bearing: the request rides a cancellable child of the caller's ctx and a watchdog CANCELS that child when no chunk arrives for IdleTimeout. Cancelling the ctx makes the transport tear the connection down, which unblocks the in-flight body read with an error — so a silent provider fails in bounded time while a slow-but-progressing one is never cut off. (resp.Body.Close() does NOT work here: net/http holds a mutex across the blocking read, so a Close from the watchdog merely queues behind it.)
An empty completion (no content, no tool calls) is an ERROR, never a silent empty turn — and the diagnostic NAMES ollama's non-standard `reasoning` field when the model produced ONLY thinking, which is the real-world shape of this failure.
type StallError ¶ added in v0.2026263.1023
type StallError struct {
Idle time.Duration
ContentBytes int
ReasoningBytes int
ToolCalls int
// Reasoning is the FULL reasoning text produced before the stream stalled.
Reasoning string
}
StallError is the typed "the stream stopped producing chunks for the idle bound" failure — the provider-unanswered class. It is TYPED (not a string to match) so a caller can classify it without coupling to the message wording, and it carries the diagnostic counters (how much content/reasoning/tool activity preceded the silence) that an RCA needs.
func (*StallError) Error ¶ added in v0.2026263.1023
func (e *StallError) Error() string
type ToolCall ¶
ToolCall is one assembled tool call.
func FromSDKTools ¶
func FromSDKTools(calls []openai.ChatCompletionMessageToolCallUnion) []ToolCall
FromSDKTools converts assembled SDK tool calls into caller rows.