The layers
ai — Talk to a Model
One call in, one message out: providers, models, messages, tools, and streaming without an agent loop
- Making your first model call with pi-go
- Deciding whether you need the agent loop at all
- Building a mental model of the types every other layer reuses
You can make one model call with pkg/ai and keep full control over the prompt.
You build an ai.Prompt. A bound ai.LanguageModel sends it to its provider.
One stream returns the live events and the final ai.Message.
model := ai.NewLanguageModel(modelInfo, provider)
stream := model.StreamText(ctx, ai.Prompt{ System: "Answer in one sentence.", Messages: []ai.Message{ai.UserMessage("Why is Go good for agents?")},}, ai.WithMaxTokens(200))
for event, err := range stream.Events() { // text_delta, thinking_delta, tool_delta, ...}
msg, err := stream.Wait()This layer makes one request and returns one response. It runs no turns, keeps no history, and finds no credentials. Those jobs belong to the layers above.
A message is a list of content blocks
for _, block := range msg.Content { if text, ok := ai.AsContent[ai.Text](block); ok { fmt.Println(text.Text) } // ai.Thinking, ai.Image, ai.File, ai.ToolCall}UserMessage, UserImageMessage, AssistantMessage, and ToolResultMessage
build the roles. Every layer above stores these same values.
You run the tools at this layer
weather := ai.DefineTool( "weather", "Reports the current temperature for a city.", func(ctx context.Context, in Query) (Report, error) { /* ... */ },)
prompt := ai.Prompt{ Messages: history, Tools: []ai.ToolInfo{weather.Info()},}DefineTool derives the JSON Schema from the Go types. DefineParallelTool marks
a tool that is safe to run beside other tools. DefineServerTool names a tool that
the provider hosts and runs itself.
The model returns tool calls. Your code runs them, appends a
ai.ToolResultMessage for each one, and calls the model again. pkg/agent
exists to own this loop for you.
Options ride with the request
model.StreamText(ctx, prompt, ai.WithTemperature(0.2), ai.WithMaxTokens(1024), ai.WithThinking(ai.ThinkingHigh), ai.WithToolChoice(ai.ToolChoiceRequired), ai.WithCacheRetention(ai.CacheRetentionLong),)Each provider maps these values to its own API fields. Prompt caching is on by default where a provider supports it.
Usage arrives on the final message
msg.Usage.Input // prompt tokensmsg.Usage.CacheRead // tokens served from the cachemsg.Usage.Reasoning // thinking tokensmsg.Usage.Cost.Output // USD, when the adapter knows the priceUsage.Add sums two values category by category. The layers above use it to
accumulate across turns and runs.
Go deeper
- GoDoc for
pkg/ai - Messages and Content
- Models and Providers
- Options, Tools, and Prompt Caching
- Usage