Go Quick Start
The Go AI SDK is a powerful toolkit designed to help developers build AI-powered applications using Go.
In this quickstart tutorial, you'll build a simple agent with a streaming chat interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the SDK in your own projects.
Prerequisites
To follow this quickstart, you'll need:
- Go 1.26+ installed on your local development machine
- An API key from a supported provider (OpenAI, Anthropic, Google, etc.)
If you haven't obtained your API key, sign up at your chosen provider's website.
Setup Your Application
Start by creating a new directory and initializing a Go module:
mkdir my-ai-app
cd my-ai-app
go mod init my-ai-app
Install Dependencies
Install the Go AI SDK:
go get github.com/digitallysavvy/go-ai
The Go AI SDK is designed to be a unified interface to interact with any large language model. This means that you can change model and providers with just one line of code! Learn more about available providers.
Configure Your API Key
Create a .env file in your project's root directory and add your API key:
touch .env
Edit the .env file:
OPENAI_API_KEY=sk-...
# or
ANTHROPIC_API_KEY=sk-ant-...
# or
GOOGLE_GENERATIVE_AI_API_KEY=...
Replace the placeholder with your actual API key.
Create Your Application
Create a main.go file in the root of your project:
package main
import (
"bufio"
"context"
"fmt"
"log"
"os"
"strings"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
ctx := context.Background()
// Initialize provider and model
prov := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, err := prov.LanguageModel("gpt-4")
if err != nil {
log.Fatal(err)
}
// Initialize conversation history
messages := []types.Message{}
// Create a reader for user input
reader := bufio.NewReader(os.Stdin)
fmt.Println("AI Chat Agent (type 'exit' to quit)")
fmt.Println("=====================================")
for {
// Get user input
fmt.Print("You: ")
userInput, _ := reader.ReadString('\n')
userInput = strings.TrimSpace(userInput)
if userInput == "exit" {
break
}
// Add user message to history
messages = append(messages, types.Message{
Role: types.RoleUser,
Content: []types.ContentPart{
types.TextContent{Text: userInput},
},
})
// Stream the response
stream, err := ai.StreamText(ctx, ai.StreamTextOptions{
Model: model,
Messages: messages,
})
if err != nil {
log.Printf("Error: %v\n", err)
continue
}
// Print assistant response
fmt.Print("\nAssistant: ")
var fullResponse strings.Builder
for chunk := range stream.Chunks() {
if chunk.Type == provider.ChunkTypeText {
fmt.Print(chunk.Text)
fullResponse.WriteString(chunk.Text)
}
}
fmt.Print("\n\n")
// Check for errors
if err := stream.Err(); err != nil {
log.Printf("Stream error: %v\n", err)
continue
}
// Add assistant response to history
messages = append(messages, types.Message{
Role: types.RoleAssistant,
Content: []types.ContentPart{
types.TextContent{Text: fullResponse.String()},
},
})
}
}
Let's take a look at what is happening in this code:
- Initialize the provider and model - Create an OpenAI provider instance and get a language model
- Set up conversation history - Initialize a slice to store the conversation messages
- Create an input reader - Use
bufio.Readerto read user input from the terminal - Main loop:
- Prompt for and capture user input
- Add user input to the
messagesslice as a user message - Call
ai.StreamTextwith the model and conversation history - Stream and print the AI response in real-time
- Add the assistant's response to the
messagesslice for conversation context
Running Your Application
To start your application:
go run main.go
You should see a prompt in your terminal. Test it out by entering a message and see the AI agent respond in real-time! The Go AI SDK makes it fast and easy to build AI chat interfaces.
Choosing a Provider
You can easily switch between providers by changing just a few lines:
OpenAI
import "github.com/digitallysavvy/go-ai/pkg/providers/openai"
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
Anthropic
import "github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
provider := anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
})
model, _ := provider.LanguageModel("claude-sonnet-4-5")
Google
import "github.com/digitallysavvy/go-ai/pkg/providers/google"
provider := google.New(google.Config{
APIKey: os.Getenv("GOOGLE_GENERATIVE_AI_API_KEY"),
})
model, _ := provider.LanguageModel("gemini-1.5-flash")
The rest of your code remains exactly the same!
Enhance Your Agent with Tools
While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where tools come in.
Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response.
Let's enhance your agent by adding a simple weather tool.
Update Your Application
Modify your main.go file to include the weather tool:
package main
import (
"bufio"
"context"
"fmt"
"log"
"math/rand"
"os"
"strings"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
ctx := context.Background()
prov := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, err := prov.LanguageModel("gpt-4")
if err != nil {
log.Fatal(err)
}
messages := []types.Message{}
reader := bufio.NewReader(os.Stdin)
fmt.Println("AI Chat Agent (type 'exit' to quit)")
fmt.Println("=====================================")
for {
fmt.Print("You: ")
userInput, _ := reader.ReadString('\n')
userInput = strings.TrimSpace(userInput)
if userInput == "exit" {
break
}
messages = append(messages, types.Message{
Role: types.RoleUser,
Content: []types.ContentPart{
types.TextContent{Text: userInput},
},
})
// Define tools
tools := []types.Tool{
{
Name: "getWeather",
Description: "Get the weather in a location (fahrenheit)",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{
"type": "string",
"description": "The location to get the weather for",
},
},
"required": []string{"location"},
},
Execute: func(ctx context.Context, params map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
location := params["location"].(string)
temperature := rand.Intn(59) + 32 // Random temp between 32-90°F
return map[string]interface{}{
"location": location,
"temperature": temperature,
}, nil
},
},
}
stream, err := ai.StreamText(ctx, ai.StreamTextOptions{
Model: model,
Messages: messages,
Tools: tools,
StopWhen: []ai.StopCondition{ai.IsStepCount(5)},
})
if err != nil {
log.Printf("Error: %v\n", err)
continue
}
fmt.Print("\nAssistant: ")
var fullResponse strings.Builder
for chunk := range stream.Chunks() {
if chunk.Type == provider.ChunkTypeText {
fmt.Print(chunk.Text)
fullResponse.WriteString(chunk.Text)
}
}
fmt.Print("\n\n")
if err := stream.Err(); err != nil {
log.Printf("Stream error: %v\n", err)
continue
}
messages = append(messages, types.Message{
Role: types.RoleAssistant,
Content: []types.ContentPart{
types.TextContent{Text: fullResponse.String()},
},
})
}
}
In this updated code:
- Import
math/rand- For generating random temperatures - Define tools - Create a
toolsmap with agetWeathertool that:- Has a description that helps the agent understand when to use it
- Defines parameters using JSON Schema format, specifying it requires a
locationstring - Includes an
Executefunction that simulates getting weather data (returns a random temperature) - Is an asynchronous function where you could fetch real data from an external API
- Pass tools to StreamText - Include the tools in the streaming options
Now your agent can "fetch" weather information for any location. Try asking something like "What's the weather in New York?"
Enabling Multi-Step Tool Calls
You may have noticed that while the agent calls the weather tool, it might not always use the results to answer your question. This is because you need to enable multi-step reasoning.
Update Your Application
Modify your code to enable multi-step generations:
package main
import (
"bufio"
"context"
"fmt"
"log"
"math/rand"
"os"
"strings"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
ctx := context.Background()
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, err := provider.LanguageModel("gpt-4")
if err != nil {
log.Fatal(err)
}
messages := []types.Message{}
reader := bufio.NewReader(os.Stdin)
fmt.Println("AI Chat Agent (type 'exit' to quit)")
fmt.Println("=====================================")
for {
fmt.Print("You: ")
userInput, _ := reader.ReadString('\n')
userInput = strings.TrimSpace(userInput)
if userInput == "exit" {
break
}
messages = append(messages, types.Message{
Role: types.RoleUser,
Content: []types.ContentPart{
types.TextContent{Text: userInput},
},
})
tools := []types.Tool{
{
Name: "getWeather",
Description: "Get the weather in a location (fahrenheit)",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{
"type": "string",
"description": "The location to get the weather for",
},
},
"required": []string{"location"},
},
Execute: func(ctx context.Context, params map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
location := params["location"].(string)
temperature := rand.Intn(59) + 32
return map[string]interface{}{
"location": location,
"temperature": temperature,
}, nil
},
},
}
// Generate with tool calling (synchronous for multi-step)
maxSteps := 5
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Messages: messages,
Tools: tools,
MaxSteps: &maxSteps, // Allow up to 5 steps for tool calling
})
if err != nil {
log.Printf("Error: %v\n", err)
continue
}
fmt.Printf("\nAssistant: %s\n\n", result.Text)
// Add the full result to conversation history
messages = append(messages, types.Message{
Role: types.RoleAssistant,
Content: []types.ContentPart{
types.TextContent{Text: result.Text},
},
})
// Log tool calls if any
if len(result.ToolCalls) > 0 {
fmt.Printf("(Used %d tool(s))\n\n", len(result.ToolCalls))
}
}
}
Key changes:
- Use
GenerateTextinstead ofStreamText- This allows automatic multi-step execution - Set
MaxSteps: 5- Allow up to 5 steps for the agent to use tools and generate responses - Print full response - Display the final text after all tool calls are complete
Now when you ask about the weather, the agent will:
- Call the weather tool to get the temperature
- Use that information to provide a natural language response
Adding a Second Tool
Let's add temperature conversion to demonstrate multi-step tool usage:
tools := []types.Tool{
{
Name: "getWeather",
Description: "Get the weather in a location (fahrenheit)",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{
"type": "string",
"description": "The location to get the weather for",
},
},
"required": []string{"location"},
},
Execute: func(ctx context.Context, params map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
location := params["location"].(string)
temperature := rand.Intn(59) + 32
return map[string]interface{}{
"location": location,
"temperature": temperature,
}, nil
},
},
{
Name: "convertFahrenheitToCelsius",
Description: "Convert a temperature from fahrenheit to celsius",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"temperature": map[string]interface{}{
"type": "number",
"description": "The temperature in fahrenheit to convert",
},
},
"required": []string{"temperature"},
},
Execute: func(ctx context.Context, params map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
// Handle both float64 and int
var fahrenheit float64
switch v := params["temperature"].(type) {
case float64:
fahrenheit = v
case int:
fahrenheit = float64(v)
}
celsius := (fahrenheit - 32) * 5 / 9
return map[string]interface{}{
"celsius": int(celsius),
}, nil
},
},
}
Now when you ask "What's the weather in New York in celsius?", the agent will:
- Call the
getWeathertool - Call the
convertFahrenheitToCelsiustool - Provide a natural language response with the temperature in Celsius
This demonstrates how tools can expand your agent's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems.
Where to Next?
You've built an AI agent using the Go AI SDK! From here, you have several paths to explore:
- Foundations - Learn about core concepts like providers, prompts, and streaming
- AI SDK Core - Explore the complete API reference
- Agents - Build more sophisticated autonomous agents
- Advanced Topics - Learn about production patterns like caching, rate limiting, and backpressure
- Examples - Check out more example applications
Production Tips
When moving to production, consider:
- Error Handling - Implement proper error handling and retry logic
- Context Timeouts - Set appropriate timeouts for your use case
- Rate Limiting - Respect provider rate limits
- Monitoring - Add telemetry and logging
- Cost Management - Monitor token usage and implement caching
Example with timeout and better error handling:
import providererrors "github.com/digitallysavvy/go-ai/pkg/provider/errors"
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
result, err := ai.GenerateText(ctx, options)
if err != nil {
var rateLimitErr *providererrors.RateLimitError
var providerErr *providererrors.ProviderError
switch {
case errors.As(err, &rateLimitErr):
log.Printf("Rate limited: %v", rateLimitErr)
// Wait and retry, respecting rateLimitErr.RetryAfterSeconds if set
case errors.As(err, &providerErr):
log.Printf("Provider error (%d): %v", providerErr.StatusCode, providerErr)
// Retry on 5xx, fail fast on 4xx
case providererrors.IsValidationError(err):
log.Printf("Invalid input: %v", err)
// Fix the request
default:
log.Printf("Unknown error: %v", err)
}
return
}