// Package ai provides abstraction for AI model providers package ai import ( "context" "encoding/json" "errors" "strings" ) // Model provides an interface for interacting with AI model providers type Model interface { // Init initializes the model with options Init(...Option) error // Options returns the model options Options() Options // Generate generates a response from the model Generate(ctx context.Context, req *Request, opts ...GenerateOption) (*Response, error) // Stream generates a streaming response (for future implementation) Stream(ctx context.Context, req *Request, opts ...GenerateOption) (Stream, error) // String returns the name of the provider String() string } // Tool represents a tool/function that can be called by the model type Tool struct { Name string // LLM-safe name (e.g., "greeter_Greeter_Hello") OriginalName string // Original name (e.g., "greeter.Greeter.Hello") Description string Properties map[string]any // JSON schema for tool parameters } // Request represents a request to generate content from a model type Request struct { // Prompt is the user's message/prompt Prompt string // SystemPrompt is the system instruction for the model SystemPrompt string // Tools available for the model to use Tools []Tool // Messages for continuing a conversation (optional). // Use ai.History to accumulate these across turns. Messages []Message } // Message represents a conversation message type Message struct { Role string // "user", "assistant", "system", "tool" Content any // Can be string or structured content } // Usage describes token counts returned by model providers. type Usage struct { InputTokens int `json:"input_tokens,omitempty"` OutputTokens int `json:"output_tokens,omitempty"` TotalTokens int `json:"total_tokens,omitempty"` } // Response represents the response from a model type Response struct { // Reply is the text response from the model Reply string // ToolCalls are tool calls requested by the model ToolCalls []ToolCall // Answer is the final answer after tool execution (if tools were used) Answer string // Usage contains provider token usage when available. Usage Usage } // ToolCall represents a request to call a tool and its result type ToolCall struct { ID string // Tool call ID (for correlation) Name string // Tool name Input map[string]any // Tool input arguments Result string // Tool execution result (populated after execution) Error string // Tool execution error (populated after execution) } // Scan decodes the call's Input into v (a pointer to a struct or map), // the same way a codec decodes an RPC request body. Use it when a tool // wants typed arguments instead of the raw map: // // var args struct{ Query string `json:"query"` } // if err := call.Scan(&args); err != nil { ... } func (c ToolCall) Scan(v any) error { b, err := json.Marshal(c.Input) if err != nil { return err } return json.Unmarshal(b, v) } // ToolResult represents the result of a tool execution type ToolResult struct { ID string // Tool call ID (for correlation) Value any // Structured result (optional) Content string // Tool execution result (JSON string), shown to the model Attempts int `json:"attempts,omitempty"` // Tool execution attempts, set when retried. // Refused names the reason a guardrail blocked the call before it ran // ("max_steps", "loop", "approval"); empty when the call executed. A // tool wrapper can switch on it to build reliability tooling — react to // a detected loop, audit refusals — without parsing the message. Refused string `json:"refused,omitempty"` } // Refusal reason codes set on ToolResult.Refused by the agent's guardrails. const ( RefusedMaxSteps = "max_steps" RefusedLoop = "loop" RefusedApproval = "approval" // RefusedSpendBudget means an agent refused a paid tool before execution // because the configured per-run x402 spend budget would be exceeded. RefusedSpendBudget = "spend_budget" ) // RunInfo describes the agent run a tool call belongs to. The agent // attaches it to the context passed to a ToolHandler, so a wrapper can // correlate calls within a run and across delegation without coupling to // the agent package. Flows also attach their name and current step so // tools and agents called from a workflow can be tied back to the // services → agents → workflows lifecycle that invoked them. Per-call // detail (tool name, id) is on the ToolCall. Attempt and MaxAttempts are // set while a model Generate call is in progress, so tools and wrappers can // tell which provider attempt produced the call and whether it is part of a // retry budget. They are zero when no model-attempt context is known. type RunInfo struct { RunID string // correlation id for this agent or flow run ParentID string // the run that delegated to this one, if any Agent string // the agent's name Flow string // the flow's name, when the call is part of a workflow Step string // the flow step currently executing, when known Attempt int // current model Generate attempt, starting at 1 when known MaxAttempts int // configured model Generate attempt budget when known VerificationFeedback string // feedback from the previous failed verifier attempt, when retrying a flow step Dispatch string // how the run was dispatched (direct, broker, schedule, resume) when known Trigger string // external trigger or schedule label that started the run, when known Spent int64 // cumulative paid x402 spend in this run, in the asset's smallest unit ToolSpend int64 // paid x402 spend attributed to the current tool call, in the asset's smallest unit } type runInfoKey struct{} // WithRunInfo attaches run info to ctx. func WithRunInfo(ctx context.Context, r RunInfo) context.Context { return context.WithValue(ctx, runInfoKey{}, r) } // RunInfoFrom returns the run info attached to ctx, and whether it was set. func RunInfoFrom(ctx context.Context) (RunInfo, bool) { r, ok := ctx.Value(runInfoKey{}).(RunInfo) return r, ok } // ErrStreamingUnsupported is returned by providers that implement the Model // interface but do not yet support token streaming. Use errors.Is so callers // can distinguish an unsupported capability from transient provider failures. var ErrStreamingUnsupported = errors.New("ai: streaming unsupported") // Stream is the interface for streaming responses. type Stream interface { // Recv receives the next chunk of the response Recv() (*Response, error) // Close closes the stream Close() error } // ToolHandler executes a tool call and returns its result. It mirrors a // go-micro RPC handler — context first, a request in, a result out — so // the same mental model carries over from services to tools. type ToolHandler func(ctx context.Context, call ToolCall) ToolResult // ToolWrapper wraps a ToolHandler to add behavior around execution — // logging, metrics, retries, guardrails. It is the tool-side analog of // client.CallWrapper and server.HandlerWrapper: a wrapper takes the next // handler and returns a new one, and code before the next(...) call runs // before the tool, code after runs after. type ToolWrapper func(ToolHandler) ToolHandler // NewFunc creates a new Model instance type NewFunc func(...Option) Model var providers = make(map[string]NewFunc) // Register registers a model provider func Register(name string, fn NewFunc) { providers[name] = fn } // New creates a new Model instance based on the provider name func New(provider string, opts ...Option) Model { if fn, ok := providers[provider]; ok { return fn(opts...) } // Default to first registered provider if len(providers) > 0 { for _, fn := range providers { return fn(opts...) } } return nil } // AutoDetectProvider attempts to detect the provider from the base URL func AutoDetectProvider(baseURL string) string { if baseURL == "" { return "openai" } switch { case strings.Contains(baseURL, "anthropic"): return "anthropic" case strings.Contains(baseURL, "atlascloud"): return "atlascloud" case strings.Contains(baseURL, "googleapis.com"), strings.Contains(baseURL, "google"): return "gemini" case strings.Contains(baseURL, "groq"): return "groq" case strings.Contains(baseURL, "minimax"): return "minimax" case strings.Contains(baseURL, "mistral"): return "mistral" case strings.Contains(baseURL, "together"): return "together" default: return "openai" } } // DefaultModel is a default model instance var DefaultModel Model // Generate generates a response using the default model. func Generate(ctx context.Context, req *Request, opts ...GenerateOption) (*Response, error) { if DefaultModel == nil { return nil, nil } return DefaultModel.Generate(ctx, req, opts...) }