Documentation
¶
Overview ¶
Package llm provides a transport-agnostic declaration for an LLM completion contract: a system prompt plus typed input/output codecs.
It follows the same declare → register → handle pattern as api/rest, api/events, api/reqreply, and api/mcp. Call is the llm analogue of [reqreply.Route]: a typed request/response declaration, but for a completion request to a large language model instead of a network peer. Call is protocol-agnostic — it does not know about OpenAI, Azure, or any specific provider; adapters/openai (or a future provider-specific adapter) supplies the wire format and implements [ports.IOAdapter].
Usage ¶
// Declare once — no HTTP method, no topic, just a system prompt and codecs.
var Summarize = llm.NewCall[Article, Summary]("summarize", articleCodec, summaryCodec,
llm.SystemPromptFile("prompts/summarize.md"),
llm.CallMeta{Description: "Summarizes a news article in exactly three sentences."},
)
// Register with a Builder to get a CallHandle and an LLMSpec catalog.
builder := llm.NewBuilder(llm.Info{Name: "My Service", Version: "1.0.0"})
handle, err := Summarize.Register(builder)
// The handle is protocol-agnostic — pass it to adapters/openai.CallAdapter
// (or any future provider adapter) via a ports.IOPort:
domain.Summarize.Bind(ctx, openai.CallAdapter(httpClient, handle, openai.CallAdapterOptions{
Model: "gpt-4o-mini", APIKey: os.Getenv("OPENAI_API_KEY"),
}))
Standalone use — no shared Builder ¶
Use Call.ClientHandle instead of Call.Register when a Call is used standalone, with no shared spec accumulation:
handle, err := Summarize.ClientHandle()
The one-struct, one-call promise ¶
An LLM completion has no path/topic/header/query var-boundary concept — CallHandle.EncodeRequest renders exactly one Req value into the user-turn content, and CallHandle.DecodeResponse parses exactly one raw completion back into exactly one Resp value, running every codex.Codec.Refine constraint declared on the response codec. The response is ALSO constrained at the API level via CallHandle.ResponseSchema (OpenAI-style "strict structured outputs") — belt-and-suspenders validation: the JSON Schema constrains the shape at generation time, codex.Refine catches what a bare schema cannot express (cross-field invariants, custom constraints).
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder accumulates CallSpec entries as Call values are registered against it — same role as [rest.Builder]/[events.Builder]/[reqreply.Builder]/ [mcp.Builder], minus OpenAPI/AsyncAPI-specific spec assembly: there is no path/topic template to validate, so LLMSpec is a flat catalog with no document-rendering step of its own. render/openaitools consumes LLMSpec directly.
Create a Builder with NewBuilder, register calls via Call.Register, and call Builder.LLMSpec to produce the catalog.
func NewBuilder ¶
NewBuilder returns a Builder initialised with the given Info.
type Call ¶
type Call[Req, Resp any] struct { // contains filtered or unexported fields }
Call declares an LLM completion contract: a system prompt plus typed input/output codecs. Call is protocol-agnostic — it does not know about OpenAI, Azure, or any specific provider; adapters/openai (or a future provider-specific adapter) supplies the wire format.
NewCall is infallible — it only captures the spec. Validation (including reading a SystemPromptFile path) runs at Call.Register/Call.ClientHandle time.
var Summarize = llm.NewCall[Article, Summary]("summarize", articleCodec, summaryCodec,
llm.SystemPromptFile("prompts/summarize.md"),
llm.CallMeta{Description: "Summarizes a news article in exactly three sentences."},
)
func NewCall ¶
func NewCall[Req, Resp any]( name string, reqCodec codex.Codec[Req], respCodec codex.Codec[Resp], opts ...CallOpt, ) Call[Req, Resp]
NewCall creates a Call spec from a name, codecs, and variadic opts. name is used for observability, error context, and spec generation (render/openaitools, future prompt-catalog docs).
NewCall is a free function (not a method) because Go requires type parameters on free functions, not on method receivers.
Example ¶
package main
import (
"github.com/DaniDeer/go-codex/api/llm"
"github.com/DaniDeer/go-codex/codex"
"github.com/DaniDeer/go-codex/validate"
)
type article struct{ Title, Body string }
type summary struct{ ThreeSentences string }
var articleCodec = codex.Struct[article](
codex.RequiredField("title", codex.String(),
func(a article) string { return a.Title },
func(a *article, v string) { a.Title = v }),
codex.RequiredField("body", codex.String(),
func(a article) string { return a.Body },
func(a *article, v string) { a.Body = v }),
)
var summaryCodec = codex.Struct[summary](
codex.RequiredField("threeSentences", codex.String().Refine(validate.NonEmptyString),
func(s summary) string { return s.ThreeSentences },
func(s *summary, v string) { s.ThreeSentences = v }),
)
func main() {
call := llm.NewCall[article, summary]("summarize", articleCodec, summaryCodec,
llm.SystemPrompt("You summarize articles in exactly three sentences."),
)
handle, err := call.ClientHandle()
if err != nil {
panic(err)
}
_ = handle
}
Output:
func (Call[Req, Resp]) ClientHandle ¶
func (c Call[Req, Resp]) ClientHandle() (*CallHandle[Req, Resp], error)
ClientHandle builds a CallHandle without registering against a Builder — for a Call used standalone, with no shared spec accumulation. Mirrors [rest.Route.ClientHandle]/[reqreply.Route.ClientHandle].
Returns SystemPromptFileError if SystemPromptFile was used and its path cannot be read.
func (Call[Req, Resp]) Register ¶
func (c Call[Req, Resp]) Register(b *Builder) (*CallHandle[Req, Resp], error)
Register validates the Call, resolves the system prompt (reading SystemPromptFile if used), renders request/response JSON Schemas, adds a CallSpec entry to b, and returns a CallHandle. Mirrors [rest.Route.Register]/[events.Channel.Register]/[reqreply.Route.Register].
Returns an error if the Call's name is empty or already registered with b. Returns SystemPromptFileError if SystemPromptFile was used and its path cannot be read.
type CallHandle ¶
type CallHandle[Req, Resp any] struct { // Name is the Call's name, as passed to [NewCall]. Name string // SystemPrompt is the fully resolved system prompt (the file has already // been read if [SystemPromptFile] was used). SystemPrompt string // EncodeRequest renders req into the LLM's user-turn content string — // the default JSON encoding, or the [UserMessage] override. EncodeRequest func(req Req) (string, error) // ResponseSchema is the JSON Schema derived from the response codec — // passed to the provider as response_format/json_schema by the adapter. ResponseSchema json.RawMessage // DecodeResponse parses the LLM's raw completion content (already // constrained by ResponseSchema at the API level) through the response // codec — applying every Refine constraint exactly like any other // go-codex boundary. Errors are wrapped as [ResponseDecodeError]. DecodeResponse func(raw []byte) (Resp, error) }
CallHandle is the protocol-agnostic runtime object returned by Call.Register/Call.ClientHandle. adapters/openai (or any future provider adapter) uses it; CallHandle itself never touches HTTP.
CallHandle mirrors [rest.RouteHandle]/[events.ChannelHandle]/ [reqreply.RouteHandle]/[mcp.ToolHandle]: it is a value that callers pass around and store. No magic, no global state.
type CallMeta ¶
type CallMeta struct {
// Description is a human-readable purpose, surfaced in render/openaitools
// and any future prompt-catalog rendering.
Description string
Tags []string
}
CallMeta holds documentation metadata — mirrors RouteMeta/ChannelMeta/ ToolMeta's role in the other API families. Implements CallOpt.
type CallOpt ¶
type CallOpt interface {
// contains filtered or unexported methods
}
CallOpt is the sealed interface for variadic NewCall options.
The following types implement CallOpt:
- SystemPromptOpt (returned by SystemPrompt) — system prompt text
- SystemPromptFileOpt (returned by SystemPromptFile) — system prompt loaded from a file
- UserMessageOpt (returned by UserMessage) — custom request-encoding function
- IncludeRequestSchemaOpt (returned by IncludeRequestSchema) — append the input schema to the prompt
- CallMeta — documentation metadata (Description, Tags)
type CallSpec ¶
type CallSpec struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Tags []string `json:"tags,omitempty"`
// SystemPrompt is included verbatim — useful for a human-readable prompt
// catalog. NOTE: this means an LLMSpec-derived renderer handed to a
// DIFFERENT LLM (e.g. render/openaitools.FromLLMSpec's agent-calls-agent
// use) could leak prompt text into that other agent's context. Revisit
// if this proves undesirable in practice — flagged as an open design
// decision in docs/roadmap/llm-integration.md.
SystemPrompt string `json:"systemPrompt"`
RequestSchema json.RawMessage `json:"requestSchema,omitempty"`
ResponseSchema json.RawMessage `json:"responseSchema,omitempty"`
}
CallSpec is the spec entry for one declared Call in LLMSpec.
type IncludeRequestSchemaOpt ¶
type IncludeRequestSchemaOpt struct{}
IncludeRequestSchemaOpt is returned by IncludeRequestSchema. Implements CallOpt.
func IncludeRequestSchema ¶
func IncludeRequestSchema() IncludeRequestSchemaOpt
IncludeRequestSchema appends the input codec's JSON Schema to the system prompt (as a fenced code block) — useful when the raw JSON alone is ambiguous. Default false (keeps prompts lean; the model already receives the concrete data, not just its shape).
type Info ¶
Info identifies the LLM call catalog for spec/documentation purposes — the llm-family analogue of [rest.Info]/[events.Info]/[reqreply.Info]/[mcp.Info].
type LLMSpec ¶
type LLMSpec struct {
Name string `json:"name"`
Version string `json:"version"`
Calls []CallSpec `json:"calls,omitempty"`
}
LLMSpec is the static catalog of all declared Call contracts — the llm-family analogue of [mcp.MCPSpec], REST's OpenAPI document, and events' AsyncAPI document. Feeds render/openaitools (and any future prompt-catalog renderer).
type ResponseDecodeError ¶
type ResponseDecodeError struct {
// Name is the Call's name, as passed to [NewCall].
Name string
// Raw is the raw completion content that failed to decode.
Raw []byte
// Err is the underlying codec Decode error.
Err error
}
ResponseDecodeError is returned by CallHandle.DecodeResponse when the raw LLM completion content fails to decode/validate through the response codec — a JSON syntax error, a type mismatch, or a Refine constraint failure.
This is the error adapters/openai's MaxRetries loop inspects to build the re-prompt message: the failure means the provider's own structured-outputs enforcement did not (or could not) guarantee a codec-valid response — belt-and-suspenders local validation caught what the JSON Schema alone could not (e.g. cross-field Refine constraints).
func (ResponseDecodeError) Error ¶
func (e ResponseDecodeError) Error() string
func (ResponseDecodeError) LogValue ¶
func (e ResponseDecodeError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type SystemPromptFileError ¶
type SystemPromptFileError struct {
// Path is the file path passed to [SystemPromptFile].
Path string
// Err is the underlying os.ReadFile error.
Err error
}
SystemPromptFileError is returned by Call.Register/Call.ClientHandle when SystemPromptFile's path cannot be read.
func (SystemPromptFileError) Error ¶
func (e SystemPromptFileError) Error() string
func (SystemPromptFileError) LogValue ¶
func (e SystemPromptFileError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type SystemPromptFileOpt ¶
type SystemPromptFileOpt struct {
// contains filtered or unexported fields
}
SystemPromptFileOpt is returned by SystemPromptFile. Implements CallOpt.
func SystemPromptFile ¶
func SystemPromptFile(path string) SystemPromptFileOpt
SystemPromptFile loads the system prompt from a file (e.g. a Markdown file) at Call.Register/Call.ClientHandle time. Register/ClientHandle fails with SystemPromptFileError if the file cannot be read — same fallibility precedent as rest.WithPathConstraints/reqreply topic validation running at registration time, not per-call.
type SystemPromptOpt ¶
type SystemPromptOpt struct {
// contains filtered or unexported fields
}
SystemPromptOpt is returned by SystemPrompt. Implements CallOpt.
func SystemPrompt ¶
func SystemPrompt(text string) SystemPromptOpt
SystemPrompt sets the system prompt text directly.
type UserMessageOpt ¶
type UserMessageOpt[Req any] struct { // contains filtered or unexported fields }
UserMessageOpt is returned by UserMessage. Implements CallOpt.
func UserMessage ¶
func UserMessage[Req any](fn func(Req) (string, error)) UserMessageOpt[Req]
UserMessage overrides how Req is rendered into the LLM's user-turn content. Default: JSON-encode the request codec's output verbatim (equivalent to format.JSON(reqCodec).Marshal), no extra wrapping text.