Documentation
¶
Overview ¶
Package ai is the high-level, provider-agnostic API for go-ai-sdk: text generation, structured output, tool calling, streaming, embeddings, and media generation, built entirely on the interfaces in package provider.
Every entry point takes a context.Context and an Opts struct naming a provider.LanguageModel (or EmbeddingModel/ImageModel/SpeechModel/ TranscriptionModel), and returns a typed result plus an error — nothing here reaches into a specific providers/* package directly, so any provider.LanguageModel, including one wrapped by a middleware such as ExtractReasoningMiddleware or TelemetryMiddleware, works uniformly:
result, err := ai.GenerateText(ctx, ai.GenerateTextOpts{
Model: model, // e.g. anthropic.New().Model("claude-sonnet-5")
Prompt: "Why is the sky blue? Answer in one sentence.",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Text)
GenerateText/StreamText drive a multi-step tool-calling loop (see GenerateTextOpts.Tools, StopWhen, PrepareStep); GenerateObject[T]/ StreamObject[T] decode model output into a caller-supplied Go type T, whose JSON Schema is derived by reflection instead of a schema library; Embed/EmbedMany wrap provider.EmbeddingModel with batching and retries. StreamText's *TextStream and StreamObject's *ObjectStream expose their parts as an iter.Seq, consumed with a plain for range (Go's range-over-func iterators, package iter in the standard library).
See the package README and docs/ for the full guide set, and docs/architecture.md for how this package relates to provider and providers/*.
Index ¶
- Constants
- Variables
- func AddToolInputExamplesMiddleware(model provider.LanguageModel) provider.LanguageModel
- func ChainMiddleware(model provider.LanguageModel, ...) provider.LanguageModel
- func CosineSimilarity(a, b []float64) (float64, error)
- func DefaultSettingsMiddleware(model provider.LanguageModel, defaults provider.Call) provider.LanguageModel
- func DeleteFile(ctx context.Context, opts DeleteFileOpts) error
- func ExtractJSONMiddleware(model provider.LanguageModel) provider.LanguageModel
- func ExtractReasoningMiddleware(model provider.LanguageModel, opts ExtractReasoningOpts) provider.LanguageModel
- func HasToolCall(names ...string) func([]Step) bool
- func LoopFinished() func([]Step) bool
- func OutputAs[T any](r *GenerateTextResult) (T, error)
- func SimulateStreamingMiddleware(model provider.LanguageModel) provider.LanguageModel
- func SmoothStream(parts iter.Seq[provider.StreamPart], opts SmoothOpts) iter.Seq[provider.StreamPart]
- func StepCountIs(n int) func([]Step) bool
- func StreamTranscribe(ctx context.Context, opts StreamTranscribeOpts) (provider.TranscriptionStream, error)
- func TelemetryMiddleware(model provider.LanguageModel, t Telemetry) provider.LanguageModel
- func UploadFile(ctx context.Context, opts UploadFileOpts) (*provider.FileInfo, error)
- func WrapImageModel(m provider.ImageModel, wrap func(provider.ImageModel) provider.ImageModel) provider.ImageModel
- func WrapModel(m provider.LanguageModel, ...) provider.LanguageModel
- type APICallError
- type ApprovalDecision
- type ApprovalRequest
- type ApprovalRequirer
- type DeleteFileOpts
- type EmbedManyOpts
- type EmbedManyResult
- type EmbedOpts
- type EmbedResult
- type EmbeddingModelProvider
- type ExtractReasoningOpts
- type GenerateImageOpts
- type GenerateImageResult
- type GenerateObjectOpts
- type GenerateObjectResult
- type GenerateSpeechOpts
- type GenerateSpeechResult
- type GenerateTextOpts
- type GenerateTextResult
- type GenerateVideoOpts
- type GenerateVideoResult
- type ImageModelProvider
- type InvalidToolArgumentsError
- type LanguageModelProvider
- type ModelCallEnd
- type NoObjectGeneratedError
- type NoSuchToolError
- type ObjectStream
- type Output
- type RankedDocument
- type Registry
- func (r *Registry) EmbeddingModel(id string) (provider.EmbeddingModel, error)
- func (r *Registry) ImageModel(id string) (provider.ImageModel, error)
- func (r *Registry) LanguageModel(id string) (provider.LanguageModel, error)
- func (r *Registry) Register(name string, p any)
- func (r *Registry) RerankingModel(id string) (provider.RerankingModel, error)
- func (r *Registry) SpeechModel(id string) (provider.SpeechModel, error)
- func (r *Registry) TranscriptionModel(id string) (provider.TranscriptionModel, error)
- func (r *Registry) VideoModel(id string) (provider.VideoModel, error)
- type RerankOpts
- type RerankResult
- type RerankingModelProvider
- type RetryError
- type RuntimeContext
- type SmoothOpts
- type SpanInfo
- type SpeechModelProvider
- type Step
- type StepPlan
- type StreamTranscribeOpts
- type Telemetry
- type TextStream
- func (s *TextStream) Close() error
- func (s *TextStream) Err() error
- func (s *TextStream) FinishReason() provider.FinishReason
- func (s *TextStream) Messages() []provider.Message
- func (s *TextStream) Output() (any, error)
- func (s *TextStream) Parts() iter.Seq[provider.StreamPart]
- func (s *TextStream) PendingApprovals() []ApprovalRequest
- func (s *TextStream) ReasoningText() string
- func (s *TextStream) Sources() []provider.SourcePart
- func (s *TextStream) Steps() []Step
- func (s *TextStream) Text() string
- func (s *TextStream) Usage() provider.Usage
- type Timeout
- type TimeoutError
- type Tool
- type ToolApprovalDeniedError
- type ToolCallRecord
- type ToolExecutionError
- type ToolInputCallbacks
- type ToolOption
- type ToolResultContent
- type ToolResultRecord
- type TranscribeOpts
- type TranscribeResult
- type TranscriptionModelProvider
- type TranslateOpts
- type TranslateResult
- type UploadFileOpts
- type VideoModelProvider
Examples ¶
Constants ¶
const ( ChunkingWord = "word" ChunkingLine = "line" )
ChunkingWord and ChunkingLine are the recognized values for SmoothOpts.Chunking. Any other value (including the empty string) falls back to word chunking.
Variables ¶
var ErrAudioRequired = errors.New("ai: audio is required")
ErrAudioRequired is returned when Audio is empty in Transcribe options.
var ErrDataRequired = errors.New("ai: data is required")
ErrDataRequired is returned when Data is empty in UploadFileOpts.
var ErrDocumentsRequired = errors.New("ai: documents is required")
ErrDocumentsRequired is returned when Documents is empty in RerankOpts.
var ErrFilenameRequired = errors.New("ai: filename is required")
ErrFilenameRequired is returned when Filename is empty in UploadFileOpts.
var ErrIDRequired = errors.New("ai: id is required")
ErrIDRequired is returned when ID is empty in DeleteFileOpts.
var ErrModelRequired = errors.New("ai: model is required")
ErrModelRequired is returned when Model is nil in Embed or EmbedMany options.
var ErrOutputRequiresJSONOrNoTools = errors.New("ai: output: model has no native JSON mode and tools are in use; structured output modes require one or the other")
ErrOutputRequiresJSONOrNoTools is returned by GenerateText when GenerateTextOpts.Output has a schema, the model has no native JSON mode (Capabilities().NativeJSON is false), AND user Tools are set. Structured output without native JSON support requires a single injected tool call forced via ToolChoice, which cannot coexist with the caller's own tools.
var ErrOutputWithStreamText = errors.New("ai: Output is not supported by StreamText (GenerateText only)")
ErrOutputWithStreamText was returned by StreamText when GenerateTextOpts.Output was set, back when structured output modes were GenerateText-only. StreamText now supports every Output mode (see TextStream.Output and GenerateTextOpts.OnPartialOutput), so nothing returns this error any more; it is kept only so existing errors.Is checks still compile.
Deprecated: no longer returned.
var ErrPromptRequired = errors.New("ai: prompt is required")
ErrPromptRequired is returned when Prompt is empty in GenerateImage options.
var ErrQueryRequired = errors.New("ai: query is required")
ErrQueryRequired is returned when Query is empty in RerankOpts.
var ErrStoreRequired = errors.New("ai: store is required")
ErrStoreRequired is returned when Store is nil in UploadFileOpts or DeleteFileOpts.
var ErrTextRequired = errors.New("ai: text is required")
ErrTextRequired is returned when Text is empty in GenerateSpeech options.
Functions ¶
func AddToolInputExamplesMiddleware ¶ added in v0.2.0
func AddToolInputExamplesMiddleware(model provider.LanguageModel) provider.LanguageModel
AddToolInputExamplesMiddleware wraps model so that every outgoing Call's tools have their InputExamples folded into their Description as text, then cleared. For each tool with a non-empty InputExamples, it appends "\n\nExample inputs:\n" followed by each example's compact JSON on its own line to the tool's Description, then clears InputExamples so a provider with native support for the field (e.g. Anthropic's input_examples) doesn't also receive — and double-count — the same examples via its wire field. This mirrors the AI SDK v6 middleware that serializes examples into description text for providers without native support.
The wrapped Call's Tools is always a fresh slice; the caller's original Tools slice (and its ToolDef values) are never mutated, so calling this middleware repeatedly (or wrapping an already-wrapped model) is idempotent per call — each invocation starts again from the original Description plus the original InputExamples supplied by the caller.
func ChainMiddleware ¶ added in v0.3.0
func ChainMiddleware(model provider.LanguageModel, middlewares ...func(provider.LanguageModel) provider.LanguageModel) provider.LanguageModel
ChainMiddleware wraps model in middlewares, first-listed outermost: ChainMiddleware(m, a, b) == a(b(m)), so a sees every call first — matching the Vercel AI SDK's wrapLanguageModel middleware-array order. Middleware constructors that take extra configuration (ExtractReasoningMiddleware, DefaultSettingsMiddleware, TelemetryMiddleware) are adapted with a closure: func(m provider.LanguageModel) provider.LanguageModel { return ExtractReasoningMiddleware(m, opts) }. A nil middleware panics (programmer error, same contract as the individual constructors' nil-model panic); calling with no middlewares returns model unchanged.
func CosineSimilarity ¶
CosineSimilarity returns the cosine similarity of two equal-length vectors: dot(a, b) / (||a|| * ||b||). It errors if a and b differ in length, or if either vector has zero magnitude (cosine similarity is undefined for a zero vector).
Example ¶
package main
import (
"fmt"
"log"
"github.com/azrtydxb/go-ai-sdk/ai"
)
func main() {
same, err := ai.CosineSimilarity([]float64{1, 0, 0}, []float64{1, 0, 0})
if err != nil {
log.Fatal(err)
}
orthogonal, err := ai.CosineSimilarity([]float64{1, 0, 0}, []float64{0, 1, 0})
if err != nil {
log.Fatal(err)
}
fmt.Printf("identical: %.1f\n", same)
fmt.Printf("orthogonal: %.1f\n", orthogonal)
}
Output: identical: 1.0 orthogonal: 0.0
func DefaultSettingsMiddleware ¶
func DefaultSettingsMiddleware(model provider.LanguageModel, defaults provider.Call) provider.LanguageModel
DefaultSettingsMiddleware wraps model so that every call's zero-valued fields are filled in from defaults before being sent to the underlying model: Temperature/TopP/MaxTokens/TopK/PresencePenalty/FrequencyPenalty/ Seed/Reasoning (nil pointers), StopSequences (empty slice), Headers (merged per header key, with per-call keys winning over the matching default key — same semantics as ProviderOptions below, applied one level shallower since Headers has no namespace level), and ProviderOptions (merged per provider-name namespace, with per-call entries winning over the matching default entries). All other Call fields (Messages, Tools, ToolChoice, ResponseFormat) are passed through unmodified — per-call values always win because only zero-valued fields are ever replaced/merged in the caller's favor.
func DeleteFile ¶ added in v0.2.0
func DeleteFile(ctx context.Context, opts DeleteFileOpts) error
DeleteFile deletes a previously-uploaded file from the given provider.FileStore. It wraps the call in retry logic (default maxRetries = 2).
func ExtractJSONMiddleware ¶ added in v0.2.0
func ExtractJSONMiddleware(model provider.LanguageModel) provider.LanguageModel
ExtractJSONMiddleware wraps model so that markdown code fences around its text output (a common way models wrap JSON they were asked to produce "raw", e.g. "```json\n{...}\n```") are stripped before the text reaches the caller.
Generate reuses GenerateObject's non-native-JSON decoding rule exactly (see stripFences): the response text is trimmed, and a leading "```" (or "```json") plus a trailing "```" are removed together — only when BOTH are present at their respective ends of the (trimmed) text. Text that isn't fenced at both ends passes through unchanged, including text with fence lines embedded in the middle of otherwise-unfenced prose.
Stream strips fences incrementally, mirroring that whole-text rule as closely as streaming allows — and, unlike Generate, with no notion of "lines": a fence marker is recognized purely by the literal three-byte "```" sequence and its position relative to the start/end of the stream, exactly like stripFences looks at the start/end of the (trimmed) string rather than at line boundaries.
- An opening fence is resolved once, at the very start of the stream: any leading whitespace is buffered, and if the first non-whitespace bytes are "```", that marker is stripped immediately, followed by an exact, case-sensitive "json" tag if present (mirroring stripFences' literal strings.TrimPrefix(t, "json") — "```JSON"/"```javascript" are NOT recognized as a tag and are left as literal text, same as Generate), followed by any further leading whitespace (mirroring the final strings.TrimSpace in stripFences). None of this requires a newline anywhere — "```json{...}```" with no newlines at all is handled the same as the more common "```json\n{...}\n```".
- A closing fence is only ever looked for when an opening fence WAS resolved at the start of the stream — stripFences strips only when BOTH ends are fenced, so a stream that never opened with "```" passes through entirely verbatim, even if it happens to end in a "```" marker.
- A closing fence is only a CANDIDATE until it's known to terminate the stream: found via the same technique the "```" is searched for anywhere in the remaining body (not just at a line start), it (and any whitespace-only bytes after it) is buffered, not emitted, until either non-whitespace content arrives (the candidate wasn't terminal after all — flush it verbatim as ordinary text, then resume scanning for the next "```" from that point) or the stream ends with nothing but whitespace having followed the candidate (it WAS the terminal closing fence — discard it, buffered whitespace included).
- Any other "```" occurring in the body — one that isn't part of the resolved opening and doesn't turn out to be the terminal closing fence — passes through unchanged, same as prose-embedded fences under Generate's rule.
One divergence from Generate's whole-text rule is unavoidable in streaming: Generate requires BOTH a leading and a trailing fence before stripping either (a leading fence alone is left untouched, since the text might not actually be fenced). Stream cannot wait indefinitely to find out whether a closing fence will ever arrive, so it strips a resolved opening fence unconditionally, even if the stream then ends without a matching closing fence — a truncated stream ends up with its opening fence stripped and no closing fence to strip (there being none).
A fence marker split across any number of deltas is still recognized correctly in both directions, since the relevant undecided/pending bytes carry over between feeds.
func ExtractReasoningMiddleware ¶
func ExtractReasoningMiddleware(model provider.LanguageModel, opts ExtractReasoningOpts) provider.LanguageModel
ExtractReasoningMiddleware wraps model so that <tagName>...</tagName> spans embedded in its text output are pulled out and re-emitted as reasoning content (provider.ReasoningPart in Generate responses, provider.ReasoningDelta/ReasoningEnd in streams) instead of ordinary text. This is useful for models that signal "thinking" with an inline tag in the text stream rather than a dedicated reasoning content type (e.g. some DeepSeek-compatible endpoints using "<think>...</think>").
The stream path is fully incremental: it never buffers more than the longest unresolved prefix of the tag currently being watched for (open tag while outside reasoning, close tag while inside), so text/reasoning content flows to the caller as it arrives rather than being held back pending a later determination. Tag markers split across stream deltas (e.g. "<th" then "ink>") are still recognized correctly since the unresolved prefix carries over between feeds.
func HasToolCall ¶ added in v0.2.0
HasToolCall returns a StopWhen function that stops the tool loop when the LAST completed step called any of the named tools. With no names given, it stops when the last step called any tool at all (regardless of name). An empty steps slice (impossible in normal use, since StopWhen is only ever consulted after at least one step has completed) reports false.
func LoopFinished ¶ added in v0.2.0
LoopFinished returns a StopWhen function that stops the tool loop when the LAST completed step made no tool calls — the same condition that already ends the loop naturally (see StopWhen's doc comment: a no-tool-call step always ends the loop, independent of what any StopWhen function returns). It is provided mainly for composing with other conditions inside a custom StopWhen closure (e.g. "stop at 10 steps OR when the loop would finish naturally, whichever comes first" — though for that particular example the natural-end behavior already makes the LoopFinished half redundant; it earns its keep in less trivial compositions).
func OutputAs ¶ added in v0.2.0
func OutputAs[T any](r *GenerateTextResult) (T, error)
OutputAs extracts the decoded output as T from a GenerateTextResult produced with GenerateTextOpts.Output set. It returns a descriptive error (never a panic) when r has no decoded Output, or when its dynamic type doesn't match T.
func SimulateStreamingMiddleware ¶
func SimulateStreamingMiddleware(model provider.LanguageModel) provider.LanguageModel
SimulateStreamingMiddleware makes model's Stream method call Generate instead, then replay the resulting Response as a synthetic single-chunk stream. This lets code written against the streaming API work uniformly with models/providers that only support non-streaming Generate.
The synthetic stream emits, in order: for each ReasoningPart in the response, a ReasoningDelta carrying its text followed by a ReasoningEnd carrying the part itself; then a TextDelta for each TextPart; then a ToolCallEnd for each tool call; then a single FinishPart carrying the response's FinishReason and Usage.
func SmoothStream ¶
func SmoothStream(parts iter.Seq[provider.StreamPart], opts SmoothOpts) iter.Seq[provider.StreamPart]
SmoothStream wraps parts, re-chunking its TextDeltas into smaller, more evenly sized deltas — "word" mode splits on whitespace boundaries, "line" mode splits on newlines — and sleeping Opts.Delay between each part it emits. This is purely a presentation aid for driving a UI at a steady cadence; it does not change the total text content, only how it is broken into deltas over time.
Chunk shape: each emitted chunk is the content unit PLUS its trailing delimiter, e.g. word mode emits "hello " (word + trailing whitespace, not "hello" then " " separately) and line mode emits "first line\n" (line + trailing newline). A word or line split across multiple input TextDeltas is buffered and coalesced: nothing is emitted for a partial word/line until its trailing delimiter (or the stream's end) arrives.
Only TextDelta parts are re-chunked. Every other StreamPart — including ReasoningDelta, which is passed through completely UNTOUCHED (never re-chunked, even though it is also free-form text) — flushes any currently-buffered text first (as a TextDelta), then is yielded unchanged. Any text still buffered when the inner sequence ends is flushed as a final TextDelta before SmoothStream itself ends.
Callers typically apply SmoothStream to the iter.Seq[provider.StreamPart] obtained from a TextStream, downstream of anything CallOpts.OnChunk observed: OnChunk always sees the provider's original, unsmoothed parts, never these re-chunked ones.
func StepCountIs ¶
StepCountIs returns a StopWhen function that stops the tool loop once at least n steps have completed.
Example ¶
ExampleStepCountIs stops the tool-calling loop on a condition rather than letting it run to MaxSteps. HasToolCall and LoopFinished compose the same way.
package main
import (
"context"
"fmt"
"log"
"github.com/azrtydxb/go-ai-sdk/ai"
"github.com/azrtydxb/go-ai-sdk/ai/aitest"
"github.com/azrtydxb/go-ai-sdk/provider"
)
func main() {
type pingArgs struct{}
ping := ai.NewTool("ping", "Ping.", func(_ context.Context, _ pingArgs) (any, error) {
return "pong", nil
})
toolCall := func() *provider.Response {
return &provider.Response{
Content: []provider.ContentPart{provider.ToolCallPart{
ID: "c", Name: "ping", Args: []byte(`{}`),
}},
FinishReason: provider.FinishToolCalls,
}
}
// The model would keep calling the tool forever; StopWhen cuts it off.
model := &aitest.MockModel{Responses: []*provider.Response{
toolCall(), toolCall(), toolCall(), toolCall(),
}}
res, err := ai.GenerateText(context.Background(), ai.GenerateTextOpts{
Model: model,
Prompt: "Ping repeatedly.",
Tools: []ai.Tool{ping},
MaxSteps: 10,
StopWhen: ai.StepCountIs(2),
})
if err != nil {
log.Fatal(err)
}
fmt.Println("steps:", len(res.Steps))
}
Output: steps: 2
func StreamTranscribe ¶ added in v0.2.0
func StreamTranscribe(ctx context.Context, opts StreamTranscribeOpts) (provider.TranscriptionStream, error)
StreamTranscribe opens a live bidirectional transcription session against opts.Model. Unlike Transcribe, there is no retry: a live connection failing mid-stream cannot be transparently retried.
func TelemetryMiddleware ¶
func TelemetryMiddleware(model provider.LanguageModel, t Telemetry) provider.LanguageModel
TelemetryMiddleware wraps model so that every Generate and Stream call reports a span to t: OnSpanStart when the call begins, OnSpanEnd when it ends.
Generate emits exactly one span per call, ending when Generate returns (with Usage/FinishReason on success, Err on failure).
Stream emits one span per call that ends when the stream's FinishPart is observed during iteration (with Usage/FinishReason taken from it), or — if no FinishPart is ever observed — once Parts() iteration ends for any other reason: a mid-stream error (StreamResponse.Err(), recorded as Err), the consumer abandoning iteration early, or Close being called before either of those happens. In every case the span ends with whatever is known at that point; nothing is buffered or invented to make an abandoned/errored stream look complete. A failure to start the stream (model.Stream returning a non-nil error) ends the span immediately with that Err, and no StreamResponse is ever wrapped or returned.
func UploadFile ¶ added in v0.2.0
UploadFile uploads a file to the given provider.FileStore. It wraps the call in retry logic (default maxRetries = 2). The returned *provider.FileInfo's ID can be referenced from a later prompt via provider.FilePart.FileID.
func WrapImageModel ¶ added in v0.2.0
func WrapImageModel(m provider.ImageModel, wrap func(provider.ImageModel) provider.ImageModel) provider.ImageModel
WrapImageModel applies wrap to m, returning the wrapped model. It is the provider.ImageModel counterpart to WrapModel — a one-line naming hook for middleware that decorates a provider.ImageModel before it is passed to GenerateImage.
func WrapModel ¶
func WrapModel(m provider.LanguageModel, wrap func(provider.LanguageModel) provider.LanguageModel) provider.LanguageModel
WrapModel applies wrap to m, returning the wrapped model. It is a one-line hook for middleware that decorates a provider.LanguageModel (e.g. logging, caching, retries) before it is passed to GenerateText.
Types ¶
type APICallError ¶
type APICallError struct {
StatusCode int
URL string
ResponseBody string
Retryable bool
Message string
}
APICallError represents an error from an AI provider API call.
func NewAPICallError ¶
func NewAPICallError(statusCode int, url, body, message string) *APICallError
NewAPICallError creates a new APICallError with Retryable set based on status code. Retryable is true for status codes: 429, 408, or >= 500.
func (*APICallError) Error ¶
func (e *APICallError) Error() string
Error implements the error interface.
func (*APICallError) IsRetryable ¶
func (e *APICallError) IsRetryable() bool
IsRetryable implements the retry.Retryable interface.
type ApprovalDecision ¶ added in v0.2.0
type ApprovalDecision struct {
ToolCallID string
Approved bool
Reason string // included in the denial tool result sent to the model
}
ApprovalDecision is a resolved approval outcome for one tool call, matched by ToolCallID: supplied out-of-band via GenerateTextOpts.Approvals on a resume call, or returned inline by GenerateTextOpts.ApproveToolCall.
type ApprovalRequest ¶ added in v0.2.0
type ApprovalRequest struct {
StepIndex int
Call ToolCallRecord
}
ApprovalRequest describes one tool call awaiting an approval decision: passed to GenerateTextOpts.ApproveToolCall, and reported (for calls left undecided) on GenerateTextResult.PendingApprovals.
type ApprovalRequirer ¶ added in v0.2.0
type ApprovalRequirer interface {
ApprovalRequired(ctx context.Context, args json.RawMessage) bool
}
ApprovalRequirer is implemented by Tools whose calls need approval before execution. RequireApproval wraps any Tool to add it. GenerateText and StreamText check every tool call's underlying Tool for this interface (after unwrapping via RequireApproval's wrapper, since that's what's actually stored in GenerateTextOpts.Tools) and resolve a decision for each one that needs approval — see GenerateTextOpts.ApproveToolCall and Approvals for how a decision is reached, and PendingApprovals for what happens when none is available.
type DeleteFileOpts ¶ added in v0.2.0
type DeleteFileOpts struct {
Store provider.FileStore // required
ID string // required
MaxRetries *int
}
DeleteFileOpts options for the DeleteFile function.
type EmbedManyOpts ¶
type EmbedManyOpts struct {
Model provider.EmbeddingModel
Values []string
MaxRetries *int
// ProviderOptions follows provider.Call.ProviderOptions' merge
// semantics. It only has an effect when Model implements
// provider.EmbeddingModelWithOptions; it is silently ignored otherwise.
ProviderOptions map[string]any
// Headers carries extra HTTP headers to send with the request; threaded
// through to provider.EmbeddingCall.Headers unchanged — see that
// field's doc for precedence (it never overrides the provider's auth
// header) and which request paths implement it. It only has an effect
// when Model implements provider.EmbeddingModelWithOptions; it is
// silently ignored otherwise.
Headers map[string]string
// Concurrency bounds how many batches may be in flight at once. 0 or 1
// (the default) processes batches strictly sequentially, identical to
// pre-Concurrency behavior. A value greater than 1 fans batches out over
// a worker pool of at most Concurrency goroutines: each batch is still
// retried independently via the same retry.Do + translateRetryErr path
// as the sequential mode, but batches run concurrently and results are
// reassembled index-aligned (Embeddings stays aligned with Values;
// Usage is summed across all batches regardless of completion order).
//
// On the first batch failure, the context used for all other batches is
// cancelled via context.WithCancel: in-flight batches are allowed to
// drain (EmbedMany waits for every dispatched batch to return before
// returning itself) but no new batches are dispatched once cancellation
// is observed. EmbedMany returns the first error encountered (in
// completion order, not batch order), translated the same way the
// sequential path translates it.
//
// Callback contract under concurrency: OnEmbedStart/OnEmbedEnd still
// fire exactly once per batch, but from worker goroutines, in
// completion order rather than batch order — callers that set these
// callbacks with Concurrency > 1 MUST make them goroutine-safe (e.g.
// guard shared state with a mutex or use atomics). In sequential mode
// (0 or 1) the existing in-order, single-goroutine guarantee holds
// verbatim.
Concurrency int
// OnEmbedStart, when non-nil, fires once per underlying provider call —
// once per batch — before the first attempt of that batch. See
// Concurrency's doc for the ordering/goroutine-safety contract this
// callback must satisfy when Concurrency > 1.
OnEmbedStart func(values []string)
// OnEmbedEnd, when non-nil, fires once per batch after the final
// attempt of that batch (success or retry exhaustion). err, when
// non-nil, is the SAME error EmbedMany itself returns for that failure
// (retry exhaustion translated to *RetryError). resp is nil on error.
// See Concurrency's doc for the ordering/goroutine-safety contract this
// callback must satisfy when Concurrency > 1.
OnEmbedEnd func(resp *provider.EmbeddingResponse, err error)
}
EmbedManyOpts options for the EmbedMany function.
type EmbedManyResult ¶
type EmbedManyResult struct {
Embeddings [][]float64 // index-aligned with Values
Usage provider.Usage
}
EmbedManyResult is the outcome of an EmbedMany call.
func EmbedMany ¶
func EmbedMany(ctx context.Context, opts EmbedManyOpts) (*EmbedManyResult, error)
EmbedMany embeds multiple string values using the provided model. It splits Values into chunks of model.MaxBatchSize(), calls sequentially (each retried), reassembles in order, and sums usage. If Values is empty, returns empty result without calling the model.
Example ¶
ExampleEmbedMany embeds a slice of values, batching them according to the model's MaxBatchSize and preserving input order in the result.
package main
import (
"context"
"fmt"
"log"
"github.com/azrtydxb/go-ai-sdk/ai"
"github.com/azrtydxb/go-ai-sdk/ai/aitest"
)
func main() {
model := &aitest.MockEmbedder{Dim: 3, BatchSize: 2}
res, err := ai.EmbedMany(context.Background(), ai.EmbedManyOpts{
Model: model,
Values: []string{"alpha", "beta", "gamma"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println("embeddings:", len(res.Embeddings))
fmt.Println("provider batches:", len(model.RecordedBatches()))
}
Output: embeddings: 3 provider batches: 2
type EmbedOpts ¶
type EmbedOpts struct {
Model provider.EmbeddingModel
Value string
MaxRetries *int
// ProviderOptions follows provider.Call.ProviderOptions' merge
// semantics. It only has an effect when Model implements
// provider.EmbeddingModelWithOptions; it is silently ignored otherwise.
ProviderOptions map[string]any
// Headers carries extra HTTP headers to send with the request; threaded
// through to provider.EmbeddingCall.Headers unchanged — see that
// field's doc for precedence (it never overrides the provider's auth
// header) and which request paths implement it. It only has an effect
// when Model implements provider.EmbeddingModelWithOptions; it is
// silently ignored otherwise.
Headers map[string]string
// OnEmbedStart, when non-nil, fires once before the first attempt of
// the underlying provider call.
OnEmbedStart func(values []string)
// OnEmbedEnd, when non-nil, fires once after the final attempt (success
// or retry exhaustion). err, when non-nil, is the SAME error Embed
// itself returns (retry exhaustion translated to *RetryError). resp is
// nil on error.
OnEmbedEnd func(resp *provider.EmbeddingResponse, err error)
}
EmbedOpts options for the Embed function.
type EmbedResult ¶
EmbedResult is the outcome of an Embed call.
func Embed ¶
func Embed(ctx context.Context, opts EmbedOpts) (*EmbedResult, error)
Embed embeds a single string value using the provided model. It wraps the call in retry logic (default maxRetries = 2).
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/azrtydxb/go-ai-sdk/ai"
"github.com/azrtydxb/go-ai-sdk/ai/aitest"
)
func main() {
// In real code: openai.New().EmbeddingModel("text-embedding-3-small")
model := &aitest.MockEmbedder{Dim: 3}
res, err := ai.Embed(context.Background(), ai.EmbedOpts{
Model: model,
Value: "go-ai-sdk",
})
if err != nil {
log.Fatal(err)
}
fmt.Println("dimensions:", len(res.Embedding))
}
Output: dimensions: 3
type EmbeddingModelProvider ¶
type EmbeddingModelProvider interface {
EmbeddingModel(id string) provider.EmbeddingModel
}
EmbeddingModelProvider is implemented by provider packages that can construct a provider.EmbeddingModel for a given model ID.
type ExtractReasoningOpts ¶
type ExtractReasoningOpts struct {
// TagName is the tag name without angle brackets, e.g. "think".
// Required.
TagName string
// StartWithReasoning indicates the model omits the opening tag and
// begins its response already "inside" the reasoning span, relying
// solely on the closing tag to mark the transition to normal output
// (e.g. a raw "Let me work through this... </think> The answer is
// 4."). When true, content from the very start of the call/stream is
// treated as reasoning until the closing tag is seen (or, if it never
// arrives, for the whole response). When false (the default), an
// orphan closing tag with no matching opener is inert: it passes
// through as ordinary text verbatim.
StartWithReasoning bool
}
ExtractReasoningOpts configures ExtractReasoningMiddleware.
type GenerateImageOpts ¶
type GenerateImageOpts struct {
Model provider.ImageModel // required
Prompt string // required
N int
Size string
AspectRatio string
Seed *int64
MaxRetries *int
ProviderOptions map[string]any
// Headers carries extra HTTP headers to send with the request; threaded
// through to provider.ImageCall.Headers unchanged — see that field's
// doc for precedence (it never overrides the provider's auth header)
// and which request paths implement it.
Headers map[string]string
// OnImageStart, when non-nil, fires once before the first attempt of
// the underlying provider call.
OnImageStart func(call provider.ImageCall)
// OnImageEnd, when non-nil, fires once after the final attempt (success
// or retry exhaustion). err, when non-nil, is the SAME error
// GenerateImage itself returns (retry exhaustion translated to
// *RetryError). resp is nil on error.
OnImageEnd func(resp *provider.ImageResponse, err error)
}
GenerateImageOpts options for the GenerateImage function.
type GenerateImageResult ¶
type GenerateImageResult struct {
Image provider.GeneratedImage // first image
Images []provider.GeneratedImage
}
GenerateImageResult is the outcome of a GenerateImage call.
func GenerateImage ¶
func GenerateImage(ctx context.Context, opts GenerateImageOpts) (*GenerateImageResult, error)
GenerateImage generates one or more images from a text prompt using the provided model. It wraps the call in retry logic (default maxRetries = 2).
type GenerateObjectOpts ¶
type GenerateObjectOpts struct {
Model provider.LanguageModel // required
System string // optional; prepended as system message
Prompt string // exactly one of Prompt/Messages
Messages []provider.Message
SchemaName string // optional; default "output"
SchemaDescription string
MaxRetries *int // default 2
MaxTokens *int
Temperature *float64
}
GenerateObjectOpts configures a GenerateObject or StreamObject call.
type GenerateObjectResult ¶
type GenerateObjectResult[T any] struct { Object T RawText string Usage provider.Usage FinishReason provider.FinishReason }
GenerateObjectResult is the outcome of a GenerateObject call.
func GenerateObject ¶
func GenerateObject[T any](ctx context.Context, opts GenerateObjectOpts) (*GenerateObjectResult[T], error)
GenerateObject calls opts.Model (through retry) once, and decodes its output into a T according to the schema for T, using native JSON mode or forced tool-call mode depending on opts.Model.Capabilities().NativeJSON.
Example ¶
ExampleGenerateObject decodes the model's output into a caller-supplied Go type. The JSON Schema sent to the provider is derived from T by reflection, including the json struct tags.
package main
import (
"context"
"fmt"
"log"
"github.com/azrtydxb/go-ai-sdk/ai"
"github.com/azrtydxb/go-ai-sdk/ai/aitest"
"github.com/azrtydxb/go-ai-sdk/provider"
)
func main() {
type Forecast struct {
City string `json:"city"`
Temp int `json:"temp"`
}
model := &aitest.MockModel{
Caps: provider.Capabilities{NativeJSON: true},
Responses: []*provider.Response{{
Content: []provider.ContentPart{provider.TextPart{Text: `{"city":"Ghent","temp":21}`}},
FinishReason: provider.FinishStop,
}},
}
res, err := ai.GenerateObject[Forecast](context.Background(), ai.GenerateObjectOpts{
Model: model,
Prompt: "Forecast for Ghent, in celsius.",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s: %d°C\n", res.Object.City, res.Object.Temp)
}
Output: Ghent: 21°C
type GenerateSpeechOpts ¶
type GenerateSpeechOpts struct {
Model provider.SpeechModel
Text string
Voice string
OutputFormat string
Speed *float64
Language string
MaxRetries *int
ProviderOptions map[string]any
// Headers carries extra HTTP headers to send with the request; threaded
// through to provider.SpeechCall.Headers unchanged — see that field's
// doc for precedence (it never overrides the provider's auth header)
// and which request paths implement it.
Headers map[string]string
// OnSpeechStart, when non-nil, fires once before the first attempt of
// the underlying provider call.
OnSpeechStart func(call provider.SpeechCall)
// OnSpeechEnd, when non-nil, fires once after the final attempt
// (success or retry exhaustion). err, when non-nil, is the SAME error
// GenerateSpeech itself returns (retry exhaustion translated to
// *RetryError). resp is nil on error.
OnSpeechEnd func(resp *provider.SpeechResponse, err error)
}
GenerateSpeechOpts options for the GenerateSpeech function.
type GenerateSpeechResult ¶
GenerateSpeechResult is the outcome of a GenerateSpeech call.
func GenerateSpeech ¶
func GenerateSpeech(ctx context.Context, opts GenerateSpeechOpts) (*GenerateSpeechResult, error)
GenerateSpeech synthesizes speech audio from text using the provided model. It wraps the call in retry logic (default maxRetries = 2).
type GenerateTextOpts ¶
type GenerateTextOpts struct {
Model provider.LanguageModel // required
System string // optional; prepended as system message
Prompt string // exactly one of Prompt/Messages
// Messages, when its LAST message is an assistant message containing
// ToolCallParts (necessarily unanswered, since it's the last message —
// no RoleTool message follows it), resumes a previously-suspended tool
// loop: both GenerateText and StreamText run that batch first (approval
// rules applied against Approvals/ApproveToolCall, same as any other
// batch) before making any model call, append the RoleTool results
// message, and proceed with the loop as usual. If that batch is itself
// still pending (no decision available for some call), the run
// suspends again immediately — see PendingApprovals — without ever
// calling the model.
Messages []provider.Message
Tools []Tool
ToolChoice *provider.ToolChoice
// Output selects a structured-output mode (object/array/choice/json) for
// this call. Both GenerateText and StreamText honor it: in StreamText the
// decoded value comes from TextStream.Output (and GenerateTextResult.
// Output on the OnFinish result) once iteration completes, with
// intermediate values delivered via OnPartialOutput. When Output
// has a schema and Model.Capabilities().NativeJSON is true, it is
// enforced via Call.ResponseFormat; when NativeJSON is false, it falls
// back to a single forced tool call the same way GenerateObject does —
// which requires Tools to be empty (ErrOutputRequiresJSONOrNoTools
// otherwise). See Output's doc and OutputObject/OutputArray/
// OutputChoice/OutputJSON for the available modes, and OutputAs to
// extract the decoded GenerateTextResult.Output as a concrete type. In the
// tool-mode fallback, if the model emits the output tool more than once in
// one response, only the first matching call is decoded and answered.
Output Output
// SchemaDescription describes the expected output schema; used as the
// injected output tool's Description in the tool-mode fallback. It has
// no effect in native-JSON mode (ResponseFormat carries no description).
SchemaDescription string
MaxSteps int // default 1; if 0 and StopWhen is set, defaults to 16
MaxRetries *int // default 2
MaxTokens *int
Temperature *float64
TopP *float64
StopSequences []string
// TopK, PresencePenalty, FrequencyPenalty, and Seed are threaded through
// to the identically-named provider.Call fields unchanged — see those
// fields' docs for per-provider support and wire-name mapping.
TopK *int
PresencePenalty *float64
FrequencyPenalty *float64
Seed *int64
// Reasoning is the unified reasoning/thinking request option. It is
// threaded through to the identically-named provider.Call field
// unchanged — see that field's doc for per-provider wire mapping.
// ProviderOptions still merge last and win on wire-key collision.
Reasoning *provider.ReasoningConfig
// Headers carries extra HTTP headers to send with the request; threaded
// through to provider.Call.Headers unchanged — see that field's doc for
// precedence (it never overrides the provider's auth header) and which
// request paths implement it.
Headers map[string]string
// Timeout, when set, bounds the run more finely than ctx alone — see
// Timeout's doc for the Total/Step/Chunk semantics and how an
// SDK-imposed bound (→ *TimeoutError via OnError) is distinguished from
// the caller's own ctx being canceled or exceeding its own deadline (→
// unchanged ctx-error/OnAbort path). Nil means no additional bound
// beyond whatever ctx the caller passes in.
Timeout *Timeout
// StopWhen, when set, decides after each completed step whether to stop
// the tool loop (return true = stop). It is evaluated after EVERY step,
// whether or not that step requested tool calls — this is what makes
// LoopFinished (which reports true on a no-tool-call step) meaningful to
// compose with other conditions inside a custom StopWhen. That said, a
// step with no tool calls always ends the loop naturally regardless of
// what StopWhen returns for it — StopWhen cannot make the loop continue
// past a step the model didn't request further tool calls in. MaxSteps
// still applies as a hard cap regardless of StopWhen: if MaxSteps is 0
// (unset) and StopWhen is non-nil, the hard cap defaults to 16 instead
// of the usual default of 1.
//
// One exception: the step that completes Output's tool-mode fallback
// (see Output's doc) ends the loop unconditionally and does NOT
// evaluate StopWhen at all — that step's forced tool call is the
// structured output itself, so the loop must end there regardless of
// what a StopWhen closure would otherwise decide.
StopWhen func(steps []Step) bool
// PrepareStep, when set, is called before each model call with the
// zero-based step index and the StepPlan about to be used: Call is the
// Call about to be sent, and Model is the model that will make the
// call (opts.Model on step 0, or whatever an earlier PrepareStep call
// last swapped to). Returning (plan, true) uses the returned StepPlan
// for that step instead; returning (_, false) leaves the planned step
// unchanged.
//
// Setting StepPlan.Model swaps the model used for that step's call —
// and every step after it, until PrepareStep swaps again (StepPlan.Model
// persists rather than applying to a single step). This is a deliberate
// divergence from a strictly per-step swap: it composes more simply (a
// swap made at step N doesn't need to be re-asserted at every later step
// to "stick") and matches the common use case of routing to a different
// model partway through a run (e.g. a cheaper model once a plan has been
// established) rather than alternating models step by step. A model swapped
// in via PrepareStep is not re-checked against Output's NativeJSON capability
// requirement — the output strategy is fixed from opts.Model at entry; swapping
// to a model without native JSON mid-loop leaves the schema unenforced on that
// provider.
PrepareStep func(stepIndex int, plan StepPlan) (StepPlan, bool)
// OnStepFinish, when set, is called after each step completes
// (including the final step) in both GenerateText and StreamText, with
// the finished Step. Errors are not returned from the callback.
//
// Caveat for StreamText: the callback fires only once a step's Parts()
// iteration has run to completion. If the consumer stops ranging over
// Parts() before that (e.g. breaking out of the loop right after
// observing that step's FinishPart), OnStepFinish does not fire for
// that step, even though FinishPart itself was already delivered.
OnStepFinish func(step Step)
// OnChunk, when set, is called with each provider.StreamPart before it
// is yielded to the consumer of StreamText's TextStream.Parts(). It has
// no effect on GenerateText, which has no stream of parts to observe.
// It observes the raw parts the provider produced: if the result is
// wrapped with SmoothStream, that re-chunking happens downstream of
// OnChunk, so OnChunk still sees the provider's original, unsmoothed
// parts rather than the re-chunked ones the consumer ultimately reads.
//
// Exception: in Output's tool-mode fallback, the forced output tool's
// call/result parts are suppressed from Parts() entirely (they're an
// encoding detail, not real content the consumer should see) but are
// still observed by OnChunk before that suppression happens.
OnChunk func(part provider.StreamPart)
// OnPartialOutput, when set together with Output, is called during
// StreamText with each successfully repair-parsed, distinct intermediate
// value of the structured output as it streams in — the same
// repair-then-unmarshal snapshots ObjectStream.Partials yields, delivered
// as a callback because TextStream's iterator is already spoken for by
// the unified stream parts. The dynamic type of v is the mode's own: T
// for OutputObject[T], []T for OutputArray[T], any (map/slice/scalar) for
// OutputJSON.
//
// It taps the accumulating text of the current step (native-JSON and
// schemaless modes) or the accumulating arguments of the forced output
// tool call (the tool-mode fallback — see Output). Consecutive snapshots
// that are reflect.DeepEqual are collapsed, so it fires only when the
// partial value actually changed; the last value it reports for a step
// therefore equals that step's final decoded output. Values may be
// incomplete in every sense (missing fields, truncated strings, a
// half-formed final element) — treat them as previews, not as the result.
//
// It never fires for OutputChoice (a choice is atomic: intermediate
// prefixes name no valid choice), and is a no-op when Output is nil or in
// GenerateText, which has no intermediate states to report.
//
// In the tool-mode fallback, partials are reported as the forced call's
// arguments stream in — before the tool loop validates that the call
// named the injected output tool. If it named a different tool instead,
// that mismatch is only rejected at step end, so one or more partials
// may already have been delivered before TextStream.Err() (and
// TextStream.Output(), which returns the same error) reports it.
OnPartialOutput func(v any)
// OnFinish, when set, is called once with the call's result after it
// completes successfully: in GenerateText, right before it returns,
// with the same *GenerateTextResult that is returned; in StreamText, at
// the natural end of TextStream.Parts() iteration (the tool loop
// stopped because a step had no tool calls, MaxSteps was reached, or
// StopWhen returned true) — never on a step that ended in an error, nor
// if the consumer abandons iteration (stops ranging) before the stream
// ends naturally. The StreamText case builds a fresh *GenerateTextResult
// from the same accumulated step/usage/message state exposed by
// TextStream's Steps/Usage/Messages accessors, so it is equivalent in
// shape to what GenerateText would return for the same underlying model
// script.
OnFinish func(result *GenerateTextResult)
// OnError, when set, is called with a call's terminal error. In
// StreamText this covers errors that end TextStream.Parts() iteration
// abnormally — a mid-stream provider error (TextStream.Err()) or a
// tool-loop error (e.g. an unknown tool, or a subsequent step's stream
// failing to start) — but not a failure to start the very first stream,
// which is reported solely via the error StreamText itself returns. In
// GenerateText, the function's returned error already fully signals
// failure to the caller; OnError additionally fires with that same
// error for symmetry with StreamText, so code that wires up both APIs
// through one callback doesn't have to special-case GenerateText.
//
// OnError is not invoked for argument-validation errors (nil model,
// prompt/messages misuse) — those are reported solely via the returned
// error, in both APIs. This mirrors StreamText, which never reaches a
// call site capable of invoking OnError when that validation fails
// (buildCall runs before the first model call, so there's no started
// call for OnError to describe); GenerateText applies the same
// exclusion for consistency, even though it could technically fire
// OnError there.
OnError func(err error)
// OnAbort, when set, is consulted only by StreamText (GenerateText has
// no notion of an abandoned or mid-flight iteration, so it never calls
// OnAbort). It fires exactly once per TextStream, before the stream's
// internal Close, in either of two cases:
//
// - the consumer abandons TextStream.Parts() early — stops ranging
// over it (e.g. via break) before the tool loop would otherwise have
// ended naturally or with an error;
// - the context passed to StreamText is canceled (or its deadline
// exceeded), causing ANY step of the tool loop to terminate with an
// error — not just a step's stream itself (stream.Err()), but
// equally a between-steps failure while ctx is canceled: tool
// execution (runToolCalls), or building/starting the next step's
// call (buildCall/startStream). Wherever in the loop the
// cancellation is observed, the outcome is the same: OnAbort fires
// instead of OnError for that termination.
//
// OnAbort never fires on natural completion (StopWhen/MaxSteps/no more
// tool calls) — that case is OnFinish's — nor does it fire together with
// OnError for the same event: a ctx-cancellation-caused termination
// anywhere in the loop fires OnAbort only, while any other error (a real
// provider or tool failure, not caused by ctx) fires OnError only.
// Abandoning iteration early is likewise never accompanied by an error —
// Err() reports nil in that case, same as before this field existed —
// so only OnAbort fires for it.
OnAbort func()
// ProviderOptions carries provider-specific escape-hatch parameters. It
// is threaded through to provider.Call.ProviderOptions unchanged — see
// that field's doc for the keying and merge semantics.
ProviderOptions map[string]any
// ActiveTools, when non-nil, limits which of Tools are OFFERED to the
// model (ToolDefs in the Call built by buildCall — PrepareStep, which
// runs afterward, sees the already-filtered call and may replace
// Call.Tools itself). Execution is restricted the same way: a call
// (whether from the model directly or a corrected call returned by
// RepairToolCall) that names a tool outside the active set is treated
// as unknown — a *NoSuchToolError — even though that tool is present in
// Tools. A nil ActiveTools means all of Tools are active; a non-nil,
// possibly empty, slice replaces the active set entirely.
ActiveTools []string
// RepairToolCall is invoked when a tool call fails to validate — an
// unknown tool name (not in the active set), or an
// *InvalidToolArgumentsError from the tool's Execute. It may return a
// corrected call (retried ONCE per original call) or false to give up,
// in which case the original error's normal semantics apply (a
// *NoSuchToolError aborts the batch; an *InvalidToolArgumentsError is
// recorded on the corresponding ToolResultRecord.Err). Repair runs
// before either of those normal-path outcomes. If the repaired call
// fails again — still unknown, or Execute fails again — RepairToolCall
// is not invoked a second time for that original call.
//
// Repair × approval ordering: a bad-args repair is re-checked against
// ApprovalRequirer using the REPAIRED call's tool and args before it
// executes — not the decision (if any) that let the ORIGINAL call reach
// execution. Repair may rename the call to a different tool that
// requires approval, or change the args of an already-approved
// approval-requiring call (the approval covered the original args, not
// the repaired ones); either way, there is no suspension possible
// mid-execution, so a repaired call that now requires approval is
// recorded as a denial (&ToolApprovalDeniedError{ToolName, Reason:
// "approval required for repaired call"} on ToolResultRecord.Err)
// rather than executed or silently allowed through.
RepairToolCall func(ctx context.Context, call ToolCallRecord, toolErr error) (ToolCallRecord, bool)
// OnModelCallStart fires immediately before each underlying model
// request (once per step, in both GenerateText and StreamText), with
// the step index (0-based) and the exact provider.Call about to be
// sent. It fires exactly once per step, outside the retry loop — before
// the FIRST attempt, regardless of how many retries that step's call
// ends up needing.
OnModelCallStart func(stepIndex int, call provider.Call)
// OnModelCallEnd fires when the model request for a step completes —
// exactly once, after the FINAL attempt (success or retry exhaustion),
// pairing with OnModelCallStart.
//
// In GenerateText, Response is the provider response (nil on error),
// and Err — when non-nil — is the SAME error GenerateText itself
// returns for that failure (retry exhaustion is translated to
// *RetryError before this callback sees it, never the raw
// retry-internal error).
//
// In StreamText, Response is always nil (there is no single Response
// value for a stream); Usage/FinishReason carry what the step's
// FinishPart reported (zero values if the step's stream errored before
// a FinishPart arrived). On StreamText's abort path — see OnAbort: the
// consumer abandoning iteration early, or ctx cancellation causing the
// termination — OnModelCallEnd does NOT fire for that step; OnAbort
// covers it instead, so every step's Start/End pair either both fire or
// (on the abort path) neither Err-bearing End fires alone without an
// abort signal.
OnModelCallEnd func(end ModelCallEnd)
// OnToolExecutionStart fires before each tool Execute (after the call
// has been validated/repaired into a known tool, before the tool
// runs). OnToolExecutionEnd fires after, with the result record and the
// raw execution error (nil on success; note the loop also reports tool
// errors through the result record's Err field, which mirrors this
// argument). Both fire exactly once per tool call record: when
// RepairToolCall retries a failed Execute, that whole
// execute-and-maybe-repair sequence counts as one execution, not two —
// there is one Start/End pair per original tool call, not one per
// attempt. Neither callback fires for a call that never reaches
// Execute at all (e.g. one that aborts the whole batch as an unknown
// tool with no successful repair).
//
// If RepairToolCall's bad-args repair path (see its doc) changes the
// call's ID or Name, the pair does NOT agree on those fields:
// OnToolExecutionStart still fires with the ORIGINAL (pre-repair) ID
// and Name (it fires before Execute is first attempted, when only the
// original call is known), while OnToolExecutionEnd's ToolResultRecord
// carries the REPAIRED ID and Name (the ones actually executed and
// recorded). Correlate the pair by call order within the step, not by
// ID, if RepairToolCall may rename calls.
OnToolExecutionStart func(stepIndex int, call ToolCallRecord)
OnToolExecutionEnd func(stepIndex int, result ToolResultRecord, err error)
// RuntimeContext, when set, is installed on the ctx passed to Tool.
// Execute, ApprovalRequirer.ApprovalRequired, and ApproveToolCall for
// this run — retrieve it with RuntimeContextFrom. It is installed once,
// before the tool loop begins (both loops), so every step and every
// resumed batch sees the same value.
RuntimeContext RuntimeContext
// ApproveToolCall decides approval-needing calls inline. Called once per
// call whose Tool implements ApprovalRequirer and reports true from
// ApprovalRequired, UNLESS Approvals already supplies a decision for
// that call's ToolCallID (Approvals is checked first). Return
// (decision, true) to decide; (zero, false) to leave the call pending,
// which suspends the loop (see PendingApprovals). The ctx passed in
// carries the same RuntimeContext installed for the run.
ApproveToolCall func(ctx context.Context, req ApprovalRequest) (ApprovalDecision, bool)
// Approvals supplies out-of-band decisions on a resume call, matched by
// ToolCallID against the unanswered assistant tool-call batch at the end
// of Messages (see Messages's resume semantics). It is consulted ONLY
// for that resume batch — not for any approval-needing call arising
// later within the same run, which must instead be decided via
// ApproveToolCall (or left to suspend). This scoping exists because
// Approvals matches by ToolCallID alone, and some providers (e.g.
// geminicompat) synthesize deterministic IDs from a call's name and
// index; a later batch's call can legitimately reuse an ID an earlier
// Approvals entry already answered, and consulting Approvals for it too
// would auto-approve a call no one actually decided.
Approvals []ApprovalDecision
}
GenerateTextOpts configures a GenerateText call.
type GenerateTextResult ¶
type GenerateTextResult struct {
Text string // last step's text
ReasoningText string // last step's reasoning text
Sources []provider.SourcePart // last step's sources
Steps []Step
ToolCalls []ToolCallRecord // last step's
ToolResults []ToolResultRecord // last step's
FinishReason provider.FinishReason
Usage provider.Usage // summed over steps
Messages []provider.Message // full final conversation incl. tool msgs
// Output holds the decoded value when GenerateTextOpts.Output was set:
// a T for OutputObject[T], a []T for OutputArray[T], a string for
// OutputChoice, or an arbitrary JSON value (map[string]any / []any /
// ...) for OutputJSON. Nil when Output was not set. Extract it with
// OutputAs[T].
//
// In the tool-mode fallback (Model.Capabilities().NativeJSON false),
// the model is forced to call a single injected output-schema tool;
// that call is never a real tool call as far as the rest of the
// result is concerned. FinishReason is never tool-calls for it (the
// underlying response's finish reason is kept when it's something
// else, e.g. length; tool-calls itself is mapped to FinishStop), and
// ToolCalls is empty on both the final Step and GenerateTextResult —
// the forced call is scrubbed from both, not left dangling. Messages
// ends with the assistant message carrying that tool call followed by
// a synthetic RoleTool message answering it (a single ToolResultPart
// whose Result is the call's own args JSON as a string), so the
// returned transcript is always well-formed for a round-trip resend —
// no provider's wire format is left with an unanswered tool call.
//
// A suspended run (PendingApprovals non-empty) has no Output — decoding
// is skipped entirely for it, even if Output was set, since the
// suspended step's Text has nothing to do with the output schema (the
// batch never executed). Output is decoded on the RESUMED run that
// actually completes.
//
// StreamText fills this field identically on the result it hands
// OnFinish, with one divergence: a decode failure fails the whole
// GenerateText call, whereas a stream has already delivered its parts
// by then — so it leaves this nil and reports the
// *NoObjectGeneratedError from TextStream.Output instead.
Output any
// PendingApprovals is non-empty when the tool loop suspended because
// some call(s) in a batch needed approval (see ApprovalRequirer) and no
// decision was available for them (checked against Approvals, then
// ApproveToolCall). Batch atomicity: when this is non-empty, NO tool in
// that batch executed, even ones that needed no approval or were
// already decided. One entry per approval-needing call without a
// decision, in call order. Messages ends with the assistant tool-call
// message (round-trippable — resend it, with Approvals set to answer
// these calls, to resume). Not an error: OnFinish still fires, and
// FinishReason is the step's real finish reason (tool-calls).
//
// When a RESUMED batch (Messages ending in an unanswered assistant
// tool-call message) is itself still pending, the run suspends before
// any model call: Steps is empty, Messages is unchanged, and
// FinishReason is tool-calls.
//
// A suspended run has no Output (see that field's doc), even when
// GenerateTextOpts.Output was set — decoding is deferred to the resumed
// run that completes.
PendingApprovals []ApprovalRequest
}
GenerateTextResult is the outcome of a GenerateText call.
func GenerateText ¶
func GenerateText(ctx context.Context, opts GenerateTextOpts) (*GenerateTextResult, error)
GenerateText calls opts.Model (through retry), running a multi-step tool-calling loop when the model requests tool calls.
After a response whose FinishReason is tool-calls (or that contains ToolCallParts), if an unknown tool is requested, GenerateText returns a *NoSuchToolError. Otherwise it executes all tool calls sequentially in response order, appends the assistant message and a single RoleTool message with all tool results, and calls the model again. This repeats while tool calls occur and len(Steps) < MaxSteps (default 1). Usage is summed across steps.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/azrtydxb/go-ai-sdk/ai"
"github.com/azrtydxb/go-ai-sdk/ai/aitest"
"github.com/azrtydxb/go-ai-sdk/provider"
)
func main() {
// A real program would use a provider model here instead:
// model := anthropic.New().Model("claude-sonnet-5")
model := &aitest.MockModel{Responses: []*provider.Response{{
Content: []provider.ContentPart{provider.TextPart{Text: "Rayleigh scattering."}},
FinishReason: provider.FinishStop,
Usage: provider.Usage{InputTokens: 12, OutputTokens: 4, TotalTokens: 16},
}}}
res, err := ai.GenerateText(context.Background(), ai.GenerateTextOpts{
Model: model,
System: "Answer in one sentence.",
Prompt: "Why is the sky blue?",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Text)
fmt.Println("tokens:", res.Usage.TotalTokens)
}
Output: Rayleigh scattering. tokens: 16
type GenerateVideoOpts ¶ added in v0.2.0
type GenerateVideoOpts struct {
Model provider.VideoModel // required
Prompt string // required
AspectRatio string
Resolution string
DurationSec float64
MaxRetries *int
ProviderOptions map[string]any
// Headers carries extra HTTP headers to send with the request; threaded
// through to provider.VideoCall.Headers unchanged — see that field's
// doc for precedence (it never overrides the provider's auth header)
// and which request paths implement it.
Headers map[string]string
// OnVideoStart, when non-nil, fires once before the first attempt of
// the underlying provider call (before job submission, for job-based
// providers).
OnVideoStart func(call provider.VideoCall)
// OnVideoEnd, when non-nil, fires once after the final attempt resolves
// (success or retry exhaustion; after the final poll, for job-based
// providers). err, when non-nil, is the SAME error GenerateVideo itself
// returns (retry exhaustion translated to *RetryError). resp is nil on
// error.
OnVideoEnd func(resp *provider.VideoResponse, err error)
}
GenerateVideoOpts options for the GenerateVideo function.
type GenerateVideoResult ¶ added in v0.2.0
type GenerateVideoResult struct {
Video provider.GeneratedVideo // first video
Videos []provider.GeneratedVideo
}
GenerateVideoResult is the outcome of a GenerateVideo call.
func GenerateVideo ¶ added in v0.2.0
func GenerateVideo(ctx context.Context, opts GenerateVideoOpts) (*GenerateVideoResult, error)
GenerateVideo generates one or more videos from a text prompt using the provided model. It wraps the call in retry logic (default maxRetries = 2).
type ImageModelProvider ¶
type ImageModelProvider interface {
ImageModel(id string) provider.ImageModel
}
ImageModelProvider is implemented by provider packages that can construct a provider.ImageModel for a given model ID.
type InvalidToolArgumentsError ¶
type InvalidToolArgumentsError struct {
ToolName string
Args json.RawMessage
Cause error
}
InvalidToolArgumentsError is returned when tool arguments are invalid.
func (*InvalidToolArgumentsError) Error ¶
func (e *InvalidToolArgumentsError) Error() string
Error implements the error interface.
func (*InvalidToolArgumentsError) Unwrap ¶
func (e *InvalidToolArgumentsError) Unwrap() error
Unwrap implements the error unwrapping interface.
type LanguageModelProvider ¶
type LanguageModelProvider interface {
Model(id string) provider.LanguageModel
}
LanguageModelProvider is implemented by provider packages that can construct a provider.LanguageModel for a given model ID.
type ModelCallEnd ¶ added in v0.2.0
type ModelCallEnd struct {
StepIndex int
Response *provider.Response // GenerateText only
Usage provider.Usage
FinishReason provider.FinishReason
Err error
}
ModelCallEnd is the argument to GenerateTextOpts.OnModelCallEnd — see that field's doc for how its fields are populated differently between GenerateText and StreamText.
type NoObjectGeneratedError ¶
NoObjectGeneratedError is returned when an LLM fails to generate a valid object.
func (*NoObjectGeneratedError) Error ¶
func (e *NoObjectGeneratedError) Error() string
Error implements the error interface.
func (*NoObjectGeneratedError) Unwrap ¶
func (e *NoObjectGeneratedError) Unwrap() error
Unwrap implements the error unwrapping interface.
type NoSuchToolError ¶
type NoSuchToolError struct {
ToolName string
}
NoSuchToolError is returned when a tool is not found.
func (*NoSuchToolError) Error ¶
func (e *NoSuchToolError) Error() string
Error implements the error interface.
type ObjectStream ¶
type ObjectStream[T any] struct { // contains filtered or unexported fields }
ObjectStream is the result of StreamObject: a single-use iterator over snapshots of T decoded from the model's incrementally streamed output, plus accumulated results available after iteration completes.
func StreamObject ¶
func StreamObject[T any](ctx context.Context, opts GenerateObjectOpts) (*ObjectStream[T], error)
StreamObject starts the model stream (retried like StreamText) and returns an *ObjectStream. A non-nil error means the stream could not start.
func (*ObjectStream[T]) Close ¶
func (s *ObjectStream[T]) Close() error
Close releases the underlying provider stream, if one is still open. It is idempotent and safe to call at any point: before Partials() has ever been ranged over (the caller decided not to consume the stream, so the HTTP body would otherwise leak), after Partials() has been fully iterated or abandoned (Partials() already closes the stream itself in both cases, so Close() is then a no-op), or mid-iteration. Close is not safe for concurrent use with Parts().
func (*ObjectStream[T]) Err ¶
func (s *ObjectStream[T]) Err() error
Err returns the error, if any, that ended iteration abnormally: a *RetryError from stream start failures are returned by StreamObject itself, so Err reflects only the underlying provider stream's mid-stream error.
func (*ObjectStream[T]) Final ¶
func (s *ObjectStream[T]) Final() (T, error)
Final returns the last valid decode of the complete accumulated stream text (fences stripped, not partialjson-repaired — the finished stream is expected to be complete JSON). Valid only after Partials() has been iterated to completion. Returns a *NoObjectGeneratedError if: the accumulated text never decoded successfully; Partials() was abandoned (the caller stopped ranging over it) before the stream finished; or Partials() was never called at all. Final never silently reports a zero-value T as success in any of those cases.
func (*ObjectStream[T]) Partials ¶
func (s *ObjectStream[T]) Partials() iter.Seq[T]
Partials yields a new T snapshot each time the accumulated JSON — text deltas in native JSON mode, or the forced tool call's argument deltas in tool mode — repaired via partialjson.Repair, unmarshals successfully into T and differs (via reflect.DeepEqual) from the previously yielded snapshot. A leading markdown code fence is tolerated (stripped before the repair), so models that wrap their JSON in fences still yield snapshots. Iteration is single-use: calling Partials() again after exhausting (or abandoning) it yields nothing. The underlying provider stream is closed when iteration ends, including on early abandonment.
func (*ObjectStream[T]) Usage ¶
func (s *ObjectStream[T]) Usage() provider.Usage
Usage returns the usage reported by the stream's FinishPart.
type Output ¶ added in v0.2.0
type Output interface {
// contains filtered or unexported methods
}
Output selects a structured-output mode for GenerateText. Construct one with OutputObject, OutputArray, OutputChoice, or OutputJSON; the zero value (nil field) means plain text.
func OutputArray ¶ added in v0.2.0
OutputArray selects a structured-output mode that decodes the model's response into a []T. The requested schema wraps the per-element schema (schema.For[T]()) in an object with a single "elements" array property — most providers' schema-constrained JSON modes require a top-level object, not a bare array.
func OutputChoice ¶ added in v0.2.0
OutputChoice selects a structured-output mode that decodes the model's response into one of choices, enforced via a JSON schema enum. Calling it with zero choices is a configuration error: schema() returns an error (surfaced from GenerateText up front, before any model call) rather than building an unsatisfiable {"enum":[]} schema. The enum constraint is not necessarily enforced by the model itself (tool-mode providers don't validate arguments against the injected tool's schema), so decode also checks membership: a result outside choices returns a *NoObjectGeneratedError rather than returning it silently.
func OutputJSON ¶ added in v0.2.0
func OutputJSON() Output
OutputJSON selects a schemaless structured-output mode that decodes the model's response as arbitrary JSON (map[string]any, []any, string, float64, bool, or nil, per encoding/json's default unmarshal-into-any rules). ResponseFormat.Type is set to "json" with no Schema, regardless of Model.Capabilities().NativeJSON — providers that can't honor a schemaless JSON response format just return text, which is decoded the same way.
func OutputObject ¶ added in v0.2.0
OutputObject selects structured-output mode that decodes the model's response into a T, constrained by schema.For[T]().
type RankedDocument ¶ added in v0.2.0
RankedDocument mirrors provider.RankedDocument plus the resolved document text.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps provider names to provider values (e.g. *openai.Provider, *anthropic.Provider) and resolves "provider:model" IDs into concrete provider.LanguageModel / EmbeddingModel / ImageModel / SpeechModel / TranscriptionModel values, type-asserting the registered provider against the matching capability interface at lookup time.
func (*Registry) EmbeddingModel ¶
func (r *Registry) EmbeddingModel(id string) (provider.EmbeddingModel, error)
EmbeddingModel resolves id ("provider:model") into a provider.EmbeddingModel.
func (*Registry) ImageModel ¶
func (r *Registry) ImageModel(id string) (provider.ImageModel, error)
ImageModel resolves id ("provider:model") into a provider.ImageModel.
func (*Registry) LanguageModel ¶
func (r *Registry) LanguageModel(id string) (provider.LanguageModel, error)
LanguageModel resolves id ("provider:model") into a provider.LanguageModel.
func (*Registry) Register ¶
Register stores p under name. p is typically a provider package's *Provider value (e.g. openai.New()); its capabilities (which of LanguageModelProvider, EmbeddingModelProvider, etc. it implements) are checked lazily at lookup time, so p need not implement every capability interface.
func (*Registry) RerankingModel ¶ added in v0.2.0
func (r *Registry) RerankingModel(id string) (provider.RerankingModel, error)
RerankingModel resolves id ("provider:model") into a provider.RerankingModel.
func (*Registry) SpeechModel ¶
func (r *Registry) SpeechModel(id string) (provider.SpeechModel, error)
SpeechModel resolves id ("provider:model") into a provider.SpeechModel.
func (*Registry) TranscriptionModel ¶
func (r *Registry) TranscriptionModel(id string) (provider.TranscriptionModel, error)
TranscriptionModel resolves id ("provider:model") into a provider.TranscriptionModel.
func (*Registry) VideoModel ¶ added in v0.2.0
func (r *Registry) VideoModel(id string) (provider.VideoModel, error)
VideoModel resolves id ("provider:model") into a provider.VideoModel.
type RerankOpts ¶ added in v0.2.0
type RerankOpts struct {
Model provider.RerankingModel // required
Query string // required
Documents []string // required, non-empty
TopN int
MaxRetries *int
ProviderOptions map[string]any
// Headers carries extra HTTP headers to send with the request; threaded
// through to provider.RerankCall.Headers unchanged — see that field's
// doc for precedence (it never overrides the provider's auth header)
// and which request paths implement it.
Headers map[string]string
// OnRerankStart, when non-nil, fires once before the first attempt.
OnRerankStart func(query string, documents []string)
// OnRerankEnd, when non-nil, fires once after the final attempt
// (success or exhausted error). err, when non-nil, is the SAME error
// Rerank itself returns for that failure (retry exhaustion translated
// to *RetryError, never the raw retry-internal error). resp is nil on
// error.
OnRerankEnd func(resp *provider.RerankResponse, err error)
}
RerankOpts options for the Rerank function.
type RerankResult ¶ added in v0.2.0
type RerankResult struct {
Results []RankedDocument
Usage provider.Usage
}
RerankResult is the outcome of a Rerank call.
func Rerank ¶ added in v0.2.0
func Rerank(ctx context.Context, opts RerankOpts) (*RerankResult, error)
Rerank ranks opts.Documents by relevance to opts.Query using the provided model. It wraps the call in retry logic (default maxRetries = 2).
type RerankingModelProvider ¶ added in v0.2.0
type RerankingModelProvider interface {
RerankingModel(id string) provider.RerankingModel
}
RerankingModelProvider is implemented by provider packages that can construct a provider.RerankingModel for a given model ID.
type RetryError ¶
RetryError is returned when retries are exhausted.
func (*RetryError) Error ¶
func (e *RetryError) Error() string
Error implements the error interface.
func (*RetryError) Unwrap ¶
func (e *RetryError) Unwrap() error
Unwrap implements the error unwrapping interface.
type RuntimeContext ¶ added in v0.2.0
RuntimeContext is an arbitrary bag of application values made available to tools during execution via RuntimeContextFrom. Set GenerateTextOpts. RuntimeContext to have it installed on the ctx passed to Tool.Execute (and, per RequireApproval, to ApprovalRequired and GenerateTextOpts. ApproveToolCall) for the duration of that GenerateText/StreamText call. It is installed once, before the tool loop begins — both loops install the SAME RuntimeContext value for every step and every resumed batch.
RuntimeContext is not synchronized. The tool loop executes tool calls sequentially, so reads and writes from tool Execute functions are safe without locking — but a tool that spawns its own goroutines and touches the map from them must provide its own synchronization.
func RuntimeContextFrom ¶ added in v0.2.0
func RuntimeContextFrom(ctx context.Context) RuntimeContext
RuntimeContextFrom returns the RuntimeContext installed for this tool loop, or nil when none was configured (GenerateTextOpts.RuntimeContext was nil/unset, or ctx is unrelated to any GenerateText/StreamText call).
type SmoothOpts ¶
type SmoothOpts struct {
// Chunking selects how TextDeltas are re-chunked: ChunkingWord (default,
// used when empty) or ChunkingLine. Any unrecognized value falls back
// to word chunking.
Chunking string
// Delay is slept after every part SmoothStream emits (both re-chunked
// text deltas and passed-through parts). Zero means no delay at all —
// unlike Vercel AI SDK's smoothStream, which defaults to a 10ms delay,
// this implementation applies NO implicit default: callers that want a
// delay must set it explicitly. This divergence keeps behavior
// predictable and keeps tests (which use Delay: 0) fast and
// deterministic.
Delay time.Duration
}
SmoothOpts configures SmoothStream.
type SpanInfo ¶
type SpanInfo struct {
CorrelationID string // stable id shared by the start/end pair for one call
Operation string // "generate" | "stream"
ModelID string
ProviderName string
StartTime time.Time
EndTime time.Time // zero on Start
Usage provider.Usage // zero on Start
FinishReason provider.FinishReason
Err error
}
SpanInfo describes a single Generate or Stream call observed by TelemetryMiddleware. Passed to Telemetry.OnSpanStart with only CorrelationID/Operation/ModelID/ProviderName/StartTime populated (EndTime is the zero time, Usage is the zero value, FinishReason is empty, Err is nil), and to Telemetry.OnSpanEnd fully populated: EndTime always set, and either Usage/FinishReason (on success) or Err (on failure) set — never both. CorrelationID is identical on the OnSpanStart and OnSpanEnd (or stream-end) SpanInfo for one call, so a bridge (e.g. to OTel) can pair them up without relying on StartTime, which can collide.
type SpeechModelProvider ¶
type SpeechModelProvider interface {
SpeechModel(id string) provider.SpeechModel
}
SpeechModelProvider is implemented by provider packages that can construct a provider.SpeechModel for a given model ID.
type Step ¶
type Step struct {
Text string
ReasoningText string // concatenated ReasoningParts of this step's Response
Sources []provider.SourcePart // this step's Response.SourceParts()
ToolCalls []ToolCallRecord
ToolResults []ToolResultRecord
FinishReason provider.FinishReason
Usage provider.Usage
Response *provider.Response
}
Step captures the result of a single model call within a GenerateText run.
type StepPlan ¶
type StepPlan struct {
Call provider.Call
// Model is the model that will make this step's call. On the way in,
// it is always the model currently active for the loop (opts.Model, or
// whatever a prior PrepareStep call swapped to). On the way out, a nil
// Model means keep the current model; a non-nil Model swaps to it for
// this step and every step after, until PrepareStep swaps again — see
// GenerateTextOpts.PrepareStep for why the swap persists.
Model provider.LanguageModel
}
StepPlan is the input/output of GenerateTextOpts.PrepareStep: the Call about to be sent for a step, and the LanguageModel that will send it.
type StreamTranscribeOpts ¶ added in v0.2.0
type StreamTranscribeOpts struct {
Model provider.StreamingTranscriptionModel
MediaType string
Language string
SampleRate int
ProviderOptions map[string]any
}
StreamTranscribeOpts are the options for StreamTranscribe.
type Telemetry ¶
type Telemetry interface {
OnSpanStart(ctx context.Context, info SpanInfo)
OnSpanEnd(info SpanInfo)
}
Telemetry receives span events from TelemetryMiddleware. Implementations must be safe for concurrent use, since a middleware-wrapped model may be called concurrently. Adapt to OTel (or any other tracing system) by implementing this interface with a tracer: start a span in OnSpanStart, stash it (e.g. keyed by SpanInfo.CorrelationID or via a field on a per-implementation wrapper), and end it in OnSpanEnd, looking it back up by CorrelationID.
OnSpanStart receives the ctx of the underlying model call, so an implementation can read a parent span out of ctx (e.g. via OTel's trace.SpanFromContext) and attach the new span as its child. The SDK does not use any ctx OnSpanStart might derive or return; the provider call is a leaf, so the signature is ctx-in only.
type TextStream ¶
type TextStream struct {
// contains filtered or unexported fields
}
TextStream is the result of StreamText: a single-use iterator over the unified stream parts of a (possibly multi-step) tool-calling loop, plus accumulated results available after iteration completes.
func StreamText ¶
func StreamText(ctx context.Context, opts GenerateTextOpts) (*TextStream, error)
StreamText starts the first model call (retried like GenerateText) and returns a *TextStream. A non-nil error means the stream could not start.
Example ¶
ExampleStreamText consumes a stream as it arrives. Parts is an iter.Seq, so it is consumed with a plain for range; check Err after the loop, since a mid-stream failure can only be reported once iteration ends.
package main
import (
"context"
"fmt"
"log"
"github.com/azrtydxb/go-ai-sdk/ai"
"github.com/azrtydxb/go-ai-sdk/ai/aitest"
"github.com/azrtydxb/go-ai-sdk/provider"
)
func main() {
model := &aitest.MockModel{Streams: [][]provider.StreamPart{{
provider.TextDelta{Text: "Hel"},
provider.TextDelta{Text: "lo, "},
provider.TextDelta{Text: "world"},
provider.FinishPart{Reason: provider.FinishStop, Usage: provider.Usage{TotalTokens: 8}},
}}}
stream, err := ai.StreamText(context.Background(), ai.GenerateTextOpts{
Model: model,
Prompt: "Say hello.",
})
if err != nil {
log.Fatal(err)
}
for part := range stream.Parts() {
if delta, ok := part.(provider.TextDelta); ok {
fmt.Printf("delta: %q\n", delta.Text)
}
}
// Err reports any error that ended the stream early.
if err := stream.Err(); err != nil {
log.Fatal(err)
}
fmt.Println("accumulated:", stream.Text())
}
Output: delta: "Hel" delta: "lo, " delta: "world" accumulated: Hello, world
func (*TextStream) Close ¶
func (s *TextStream) Close() error
Close releases the underlying provider stream, if one is still open. It is idempotent and safe to call at any point: before Parts() has ever been ranged over (the caller decided not to consume the stream, so the HTTP body would otherwise leak), after Parts() has been fully iterated or abandoned (Parts() already closes the stream itself in both cases, so Close() is then a no-op), or mid-iteration. Close is not safe for concurrent use with Parts().
func (*TextStream) Err ¶
func (s *TextStream) Err() error
Err returns the error, if any, that ended iteration abnormally: a *RetryError if a subsequent step's stream could not start, a *NoSuchToolError if an unknown tool was requested, or the underlying provider stream's mid-stream error.
func (*TextStream) FinishReason ¶
func (s *TextStream) FinishReason() provider.FinishReason
FinishReason returns the last step's finish reason.
func (*TextStream) Messages ¶
func (s *TextStream) Messages() []provider.Message
Messages returns the full final conversation so far, including any assistant and tool messages appended by completed steps of the tool loop — the same semantics as GenerateTextResult.Messages. Valid after Parts() has been iterated (fully or partially); before that it is just the initial request messages.
func (*TextStream) Output ¶ added in v0.3.0
func (s *TextStream) Output() (any, error)
Output returns the decoded structured output of a stream started with GenerateTextOpts.Output set, and is valid once Parts() iteration has completed: it decodes the final step's accumulated text through the same path GenerateText uses (stripFences, then the mode's decode), so it reports the same *NoObjectGeneratedError for text the mode can't parse.
That decode failure surfaces HERE and only here — Err() stays nil for it. The parts of a stream have already been delivered to the consumer by the time the final text can be decoded at all, so retroactively failing the stream would contradict what it already yielded.
If the stream instead ended abnormally (Err() is non-nil — e.g. a wrong tool name in Output's tool-mode fallback, an unknown tool, or a mid-stream provider error), Output() returns that same error rather than decoding whatever partial/unrelated text happened to accumulate: decoding s.lastText in that case would typically just report an unrelated empty- text *NoObjectGeneratedError and mask the real cause.
It returns nil, nil when Output was not set, when Parts() has not been ranged over at all, and likewise for a stream that suspended on pending approvals (see PendingApprovals): the suspended step's text is unrelated to the output schema — mirroring the decode GenerateText skips in the same situation. Repeated calls return the same decoded value; the decode itself runs at most once.
func (*TextStream) Parts ¶
func (s *TextStream) Parts() iter.Seq[provider.StreamPart]
Parts yields unified parts across ALL steps of the tool loop: TextDelta, ToolCallDelta, ToolCallEnd, and one FinishPart per step. Between steps it executes any requested tools and starts the next model stream. Iteration is single-use: calling Parts() again after exhausting (or abandoning) it yields nothing.
func (*TextStream) PendingApprovals ¶ added in v0.2.0
func (s *TextStream) PendingApprovals() []ApprovalRequest
PendingApprovals returns the same value as GenerateTextResult. PendingApprovals would carry, for a stream that suspended because some tool call(s) needed approval and none was available — see ApprovalRequirer and GenerateTextOpts.ApproveToolCall/Approvals. Nil when the stream never suspended. Valid after Parts() has been iterated (fully or partially, including the immediate-suspension case where Parts() yields nothing at all because the resumed batch itself was pending).
func (*TextStream) ReasoningText ¶
func (s *TextStream) ReasoningText() string
ReasoningText returns the accumulated reasoning text of the final step.
func (*TextStream) Sources ¶
func (s *TextStream) Sources() []provider.SourcePart
Sources returns the SourceParts accumulated (via SourceEvent stream parts) during the final step.
func (*TextStream) Steps ¶
func (s *TextStream) Steps() []Step
Steps returns the steps executed so far. If iteration stopped because of a *NoSuchToolError (an unknown tool was requested), the step in which that happened is still appended, with its ToolCalls populated but ToolResults nil (execution never ran) — check Err() to detect this case rather than assuming every step in Steps() completed successfully.
func (*TextStream) Text ¶
func (s *TextStream) Text() string
Text returns the accumulated text of the final step.
func (*TextStream) Usage ¶
func (s *TextStream) Usage() provider.Usage
Usage returns the summed usage across all steps.
type Timeout ¶ added in v0.2.0
type Timeout struct {
Total time.Duration // whole run (all steps); a derived context.WithTimeout at entry
Step time.Duration // each individual model call/step; a derived per-step context.WithTimeout
// Chunk (StreamText only) is the max gap between yielded
// provider.StreamParts before the stream is aborted with a
// *TimeoutError{Dimension: "chunk"}. It is implemented with a timer that
// resets on every yielded part; there is an inherent, rare TOCTOU window
// in that design — a part arriving at (almost) the same instant the
// timer fires can still lose the race and surface as a chunk timeout
// even though the stream wasn't really stalled. This is a property of
// any timer-based watchdog, not a bug to be designed away here.
Chunk time.Duration
}
Timeout bounds a GenerateText/StreamText run more finely than a single context deadline. Zero fields mean "no bound" for that dimension.
Total, Step, and Chunk are SDK-imposed bounds, layered on top of (never replacing) whatever ctx the caller passes to GenerateText/StreamText: the earlier of the caller's own deadline and Total wins automatically, since Total is implemented as a further context.WithTimeout derived from the caller's ctx.
The critical distinction Timeout makes is WHICH side caused a run to end:
- If one of Total/Step/Chunk elapses first, the run ends with a *TimeoutError (Dimension "total"/"step"/"chunk") delivered via OnError — this is an SDK-imposed limit, i.e. an error, not a user abort.
- If the caller's own ctx is canceled or reaches its own deadline first, the run ends exactly as it always has: the ctx error from GenerateText's return value, or OnAbort in StreamText. Timeout never changes that path.
This is detected by deriving each bound's context with context.WithTimeoutCause using a distinct sentinel cause per dimension, so context.Cause on the context actually used for a call reveals whether an SDK bound fired (matches a sentinel) or the caller's own ctx did (does not) — never by racing wall-clock time against the caller's deadline.
type TimeoutError ¶ added in v0.2.0
type TimeoutError struct {
Dimension string // "total", "step", or "chunk"
Limit time.Duration // the Timeout bound that elapsed
}
TimeoutError is returned when one of Timeout's SDK-imposed bounds (Total, Step, or Chunk) elapses before a GenerateText or StreamText run completes. It is distinct from the caller's own ctx being canceled or exceeding its own deadline: THAT case is reported exactly as it always was (the ctx error from GenerateText's return value; OnAbort in StreamText) — see Timeout's doc for the precise distinction and how it's detected.
func (*TimeoutError) Error ¶ added in v0.2.0
func (e *TimeoutError) Error() string
Error implements the error interface.
type Tool ¶
type Tool interface {
Name() string
Description() string
Schema() json.RawMessage
Execute(ctx context.Context, args json.RawMessage) (any, error)
// Strict reports whether the tool requests provider-enforced schema
// conformance for its arguments (see provider.ToolDef.Strict). Most
// tools return false.
Strict() bool
// InputExamples returns example argument payloads for the tool, each a
// complete JSON object matching Schema (see provider.ToolDef.InputExamples).
// Most tools return nil.
InputExamples() []json.RawMessage
// InputCallbacks returns the tool's input-streaming lifecycle hooks (see
// ToolInputCallbacks and WithToolInputCallbacks). Most tools return a
// zero-value ToolInputCallbacks (all fields nil); GenerateText and
// StreamText nil-check every field before invoking it.
InputCallbacks() ToolInputCallbacks
}
Tool represents an executable tool that can be called by an LLM.
func NewTool ¶
func NewTool[Args any](name, description string, fn func(context.Context, Args) (any, error), opts ...ToolOption) Tool
NewTool creates a new Tool with a typed handler function. The Args type parameter is a struct that defines the tool's input schema. NewTool derives the schema from Args at construction; it panics on schema error (treating schema derivation as a programmer error, similar to regexp.MustCompile).
When Execute is called, it unmarshals the args strictly (using json.Decoder with DisallowUnknownFields). On unmarshal failure, it returns *InvalidToolArgumentsError. If the function returns an error, Execute wraps it in *ToolExecutionError.
Optional ToolOptions (WithToolStrict, WithToolInputExamples) configure the resulting tool's Strict/InputExamples values.
Example ¶
ExampleNewTool shows the multi-step tool-calling loop: the model asks for a tool, GenerateText executes it, feeds the result back, and the model answers. Both round trips are visible in Steps.
package main
import (
"context"
"fmt"
"log"
"github.com/azrtydxb/go-ai-sdk/ai"
"github.com/azrtydxb/go-ai-sdk/ai/aitest"
"github.com/azrtydxb/go-ai-sdk/provider"
)
func main() {
type weatherArgs struct {
City string `json:"city"`
}
weather := ai.NewTool("get_weather", "Look up the current weather in a city.",
func(_ context.Context, args weatherArgs) (any, error) {
return "sunny, 21°C", nil
})
model := &aitest.MockModel{Responses: []*provider.Response{
// Step 1: the model calls the tool.
{
Content: []provider.ContentPart{provider.ToolCallPart{
ID: "call_1", Name: "get_weather", Args: []byte(`{"city":"Ghent"}`),
}},
FinishReason: provider.FinishToolCalls,
},
// Step 2: given the tool result, the model answers.
{
Content: []provider.ContentPart{provider.TextPart{Text: "It's sunny in Ghent."}},
FinishReason: provider.FinishStop,
},
}}
res, err := ai.GenerateText(context.Background(), ai.GenerateTextOpts{
Model: model,
Prompt: "What's the weather in Ghent?",
Tools: []ai.Tool{weather},
MaxSteps: 5,
})
if err != nil {
log.Fatal(err)
}
fmt.Println("steps:", len(res.Steps))
fmt.Println("tool result:", res.Steps[0].ToolResults[0].Result)
fmt.Println(res.Text)
}
Output: steps: 2 tool result: sunny, 21°C It's sunny in Ghent.
func RequireApproval ¶ added in v0.2.0
RequireApproval wraps t so every call requires approval; with a non-nil when func (only the first is used; it's variadic purely so the argument can be omitted), only calls for which when returns true do. The ctx passed to when carries the same RuntimeContext installed for the run (see RuntimeContextFrom).
type ToolApprovalDeniedError ¶ added in v0.2.0
ToolApprovalDeniedError is recorded on a ToolResultRecord.Err (never returned/raised directly) when a tool call needing approval was denied — see GenerateTextOpts.ApproveToolCall and Approvals.
func (*ToolApprovalDeniedError) Error ¶ added in v0.2.0
func (e *ToolApprovalDeniedError) Error() string
Error implements the error interface. Reason is omitted from the message when empty.
type ToolCallRecord ¶
type ToolCallRecord struct {
ID string
Name string
Args json.RawMessage
}
ToolCallRecord records a tool call made by the model during a step.
type ToolExecutionError ¶
type ToolExecutionError struct {
ToolName string
Cause error
// Stack is the goroutine stack captured at the point a tool panic was
// recovered (see (*tool).Execute), for callers that want to log it
// themselves. It is nil for an ordinary (non-panic) tool error. It is
// deliberately NOT included in Error()'s output: that string can end up
// in a provider prompt (generate_text.go's toolResultValue sends it back
// to the model as the tool result), and a raw stack trace there would
// leak local file paths and goroutine internals into the conversation.
Stack []byte
}
ToolExecutionError is returned when tool execution fails.
func (*ToolExecutionError) Error ¶
func (e *ToolExecutionError) Error() string
Error implements the error interface.
func (*ToolExecutionError) Unwrap ¶
func (e *ToolExecutionError) Unwrap() error
Unwrap implements the error unwrapping interface.
type ToolInputCallbacks ¶ added in v0.2.0
type ToolInputCallbacks struct {
// OnInputStart fires once per toolCallID, when the first argument delta
// for that call arrives. Stream-only: never fires from GenerateText.
OnInputStart func(ctx context.Context, toolCallID string)
// OnInputDelta fires once per argument delta (including the first),
// carrying that fragment's raw args-JSON text. The concatenation of
// every delta for a given toolCallID equals the call's fully assembled
// arguments. Stream-only: never fires from GenerateText.
OnInputDelta func(ctx context.Context, toolCallID string, delta string)
// OnInputAvailable fires once per tool call, with its fully assembled
// arguments, immediately before that call is executed. Fires from both
// GenerateText and StreamText.
OnInputAvailable func(ctx context.Context, toolCallID string, input json.RawMessage)
}
ToolInputCallbacks are per-tool lifecycle hooks fired as a tool call's arguments become available, mirroring the Vercel AI SDK's v6 onInputStart/onInputDelta/onInputAvailable. Attach via WithToolInputCallbacks.
StreamText fires OnInputStart the first time an argument delta for a given toolCallID arrives, OnInputDelta for every argument delta thereafter (including that first one) with the raw args-JSON text fragment, and OnInputAvailable once the call's arguments are fully assembled — before that call is executed. GenerateText (no streaming, so no deltas exist) fires only OnInputAvailable, immediately before Execute. All three are nil-checked and invoked synchronously on the consuming goroutine, and none of them fire for the Output tool-mode synthetic call (see GenerateTextOpts.Output), since that call is never executed via Tool. Execute.
type ToolOption ¶ added in v0.2.0
type ToolOption func(*toolOptions)
ToolOption configures optional NewTool behavior (strict mode, input examples).
func WithToolInputCallbacks ¶ added in v0.2.0
func WithToolInputCallbacks(cb ToolInputCallbacks) ToolOption
WithToolInputCallbacks attaches per-tool input-streaming lifecycle hooks (see ToolInputCallbacks) to the resulting tool.
func WithToolInputExamples ¶ added in v0.2.0
func WithToolInputExamples[Args any](examples ...Args) ToolOption
WithToolInputExamples attaches example argument values to the tool. Each example is marshaled to JSON at construction time; it panics on marshal failure (treating this as a programmer error, similar to schema derivation).
func WithToolStrict ¶ added in v0.2.0
func WithToolStrict() ToolOption
WithToolStrict marks the tool as requesting provider-enforced schema conformance (see provider.ToolDef.Strict).
type ToolResultContent ¶ added in v0.2.0
type ToolResultContent struct {
Text string
Images []provider.GeneratedImage
}
ToolResultContent is a multi-modal tool result: a Tool's Execute method may return a ToolResultContent (or *ToolResultContent) instead of a plain value when it wants to attach one or more images alongside (or instead of) text — e.g. a screenshot tool, an image-generation tool, or a chart renderer.
Provider support for the Images half is uneven, since not every wire format has an image slot in a tool result:
- anthropic serializes it natively: the tool_result content block's "content" becomes an array — one {"type":"text"} block (only when Text is non-empty) followed by one {"type":"image","source":{...}} block per entry in Images.
- bedrock (Converse) likewise serializes it natively: the toolResult block's "content" array gets a {"text":...} entry (only when Text is non-empty) followed by one {"image":{...}} entry per entry in Images.
- openaicompat, geminicompat, cohere, and mistral have no image slot in their tool-result wire formats; these providers project ToolResultContent down to its Text field only — Images is silently dropped for them. Prefer text-describable results (or a separate, provider-agnostic mechanism such as attaching an image to a subsequent user message) if a script must run identically across these providers and one of the image-capable ones.
A Tool that never needs images can simply return a plain string (or any other JSON-marshalable value) from Execute, as before — ToolResultContent is opt-in, only for tools that want to attach images.
type ToolResultRecord ¶
type ToolResultRecord struct {
ToolCallID string
Name string
Result any
Err error // tool execution error, recorded not raised (see Task 7)
}
ToolResultRecord records the outcome of executing a tool call.
type TranscribeOpts ¶
type TranscribeOpts struct {
Model provider.TranscriptionModel
Audio []byte
MediaType string
Language string
Prompt string
MaxRetries *int
ProviderOptions map[string]any
// Headers carries extra HTTP headers to send with the request; threaded
// through to provider.TranscriptionCall.Headers unchanged — see that
// field's doc for precedence (it never overrides the provider's auth
// header) and which request paths implement it.
Headers map[string]string
// OnTranscribeStart, when non-nil, fires once before the first attempt
// of the underlying provider call.
OnTranscribeStart func(call provider.TranscriptionCall)
// OnTranscribeEnd, when non-nil, fires once after the final attempt
// (success or retry exhaustion). err, when non-nil, is the SAME error
// Transcribe itself returns (retry exhaustion translated to
// *RetryError). resp is nil on error.
OnTranscribeEnd func(resp *provider.TranscriptionResponse, err error)
}
TranscribeOpts options for the Transcribe function.
type TranscribeResult ¶
type TranscribeResult struct {
Text string
Segments []provider.TranscriptSegment
Language string
DurationSec float64
}
TranscribeResult is the outcome of a Transcribe call.
func Transcribe ¶
func Transcribe(ctx context.Context, opts TranscribeOpts) (*TranscribeResult, error)
Transcribe transcribes audio into text using the provided model. It wraps the call in retry logic (default maxRetries = 2).
type TranscriptionModelProvider ¶
type TranscriptionModelProvider interface {
TranscriptionModel(id string) provider.TranscriptionModel
}
TranscriptionModelProvider is implemented by provider packages that can construct a provider.TranscriptionModel for a given model ID.
type TranslateOpts ¶ added in v0.2.0
type TranslateOpts struct {
Model provider.TranslationModel
Audio []byte
MediaType string
Prompt string
MaxRetries *int
ProviderOptions map[string]any
// Headers carries extra HTTP headers to send with the request; threaded
// through to provider.TranslationCall.Headers unchanged — see that
// field's doc for precedence (it never overrides the provider's auth
// header) and which request paths implement it.
Headers map[string]string
// OnTranslateStart, when non-nil, fires once before the first attempt of
// the underlying provider call.
OnTranslateStart func(call provider.TranslationCall)
// OnTranslateEnd, when non-nil, fires once after the final attempt
// (success or retry exhaustion). err, when non-nil, is the SAME error
// Translate itself returns (retry exhaustion translated to
// *RetryError). resp is nil on error.
OnTranslateEnd func(resp *provider.TranslationResponse, err error)
}
TranslateOpts options for the Translate function.
type TranslateResult ¶ added in v0.2.0
type TranslateResult struct {
Text string // English translation
Language string // detected source language, "" if not reported
DurationSec float64
}
TranslateResult is the outcome of a Translate call.
func Translate ¶ added in v0.2.0
func Translate(ctx context.Context, opts TranslateOpts) (*TranslateResult, error)
Translate translates audio in any supported source language into English text using the provided model. It wraps the call in retry logic (default maxRetries = 2).
type UploadFileOpts ¶ added in v0.2.0
type UploadFileOpts struct {
Store provider.FileStore // required
Data []byte // required
Filename string // required
MediaType string
Purpose string
MaxRetries *int
ProviderOptions map[string]any
// Headers carries extra HTTP headers to send with the request; threaded
// through to provider.FileUploadCall.Headers unchanged — see that
// field's doc for precedence (it never overrides the provider's auth
// header) and which request paths implement it.
Headers map[string]string
}
UploadFileOpts options for the UploadFile function.
type VideoModelProvider ¶ added in v0.2.0
type VideoModelProvider interface {
VideoModel(id string) provider.VideoModel
}
VideoModelProvider is implemented by provider packages that can construct a provider.VideoModel for a given model ID.
Source Files
¶
- approval.go
- doc.go
- embed.go
- errors.go
- generate_image.go
- generate_object.go
- generate_speech.go
- generate_text.go
- generate_video.go
- middleware.go
- middleware_json.go
- options.go
- output.go
- partial_tracker.go
- registry.go
- rerank.go
- runtime_context.go
- similarity.go
- smooth.go
- stream_object.go
- stream_text.go
- stream_transcribe.go
- telemetry.go
- timeout.go
- tool.go
- tool_result_content.go
- transcribe.go
- translate.go
- upload_file.go