9.1 KiB
Claude API — Go
Note: The Go SDK supports the Claude API and beta tool use with `BetaToolRunner`. Agent SDK is not yet available for Go.
Installation
```bash go get github.com/anthropics/anthropic-sdk-go ```
Client Initialization
```go import ( "github.com/anthropics/anthropic-sdk-go" "github.com/anthropics/anthropic-sdk-go/option" )
// Default (uses ANTHROPIC_API_KEY env var) client := anthropic.NewClient()
// Explicit API key client := anthropic.NewClient( option.WithAPIKey("your-api-key"), ) ```
Basic Message Request
```go response, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{ Model: anthropic.ModelClaudeOpus4_6, MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("What is the capital of France?")), }, }) if err != nil { log.Fatal(err) } for _, block := range response.Content { switch variant := block.AsAny().(type) { case anthropic.TextBlock: fmt.Println(variant.Text) } } ```
Streaming
```go stream := client.Messages.NewStreaming(context.Background(), anthropic.MessageNewParams{ Model: anthropic.ModelClaudeOpus4_6, MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("Write a haiku")), }, })
for stream.Next() { event := stream.Current() switch eventVariant := event.AsAny().(type) { case anthropic.ContentBlockDeltaEvent: switch deltaVariant := eventVariant.Delta.AsAny().(type) { case anthropic.TextDelta: fmt.Print(deltaVariant.Text) } } } if err := stream.Err(); err != nil { log.Fatal(err) } ```
Tool Use
Tool Runner (Beta — Recommended)
Beta: The Go SDK provides `BetaToolRunner` for automatic tool use loops via the `toolrunner` package.
```go import ( "context" "fmt" "log"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/toolrunner"
)
// Define tool input with jsonschema tags for automatic schema generation type GetWeatherInput struct { City string `json:"city" jsonschema:"required,description=The city name"` }
// Create a tool with automatic schema generation from struct tags weatherTool, err := toolrunner.NewBetaToolFromJSONSchema( "get_weather", "Get current weather for a city", func(ctx context.Context, input GetWeatherInput) (anthropic.BetaToolResultBlockParamContentUnion, error) { return anthropic.BetaToolResultBlockParamContentUnion{ OfText: &anthropic.BetaTextBlockParam{ Text: fmt.Sprintf("The weather in %s is sunny, 72°F", input.City), }, }, nil }, ) if err != nil { log.Fatal(err) }
// Create a tool runner that handles the conversation loop automatically runner := client.Beta.Messages.NewToolRunner( []anthropic.BetaTool{weatherTool}, anthropic.BetaToolRunnerParams{ BetaMessageNewParams: anthropic.BetaMessageNewParams{ Model: anthropic.ModelClaudeOpus4_6, MaxTokens: 1024, Messages: []anthropic.BetaMessageParam{ anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("What's the weather in Paris?")), }, }, MaxIterations: 5, }, )
// Run until Claude produces a final response message, err := runner.RunToCompletion(context.Background()) if err != nil { log.Fatal(err) } fmt.Println(message.Content[0].Text) ```
Key features of the Go tool runner:
- Automatic schema generation from Go structs via `jsonschema` tags
- `RunToCompletion()` for simple one-shot usage
- `All()` iterator for processing each message in the conversation
- `NextMessage()` for step-by-step iteration
- Streaming variant via `NewToolRunnerStreaming()` with `AllStreaming()`
Manual Loop
For fine-grained control over the agentic loop, define tools with `ToolParam`, check `StopReason`, execute tools yourself, and feed `tool_result` blocks back. This is the pattern when you need to intercept, validate, or log tool calls.
Derived from `anthropic-sdk-go/examples/tools/main.go`.
```go package main
import ( "context" "encoding/json" "fmt" "log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() { client := anthropic.NewClient()
// 1. Define tools. ToolParam.InputSchema uses a map, no struct tags needed.
addTool := anthropic.ToolParam{
Name: "add",
Description: anthropic.String("Add two integers"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"a": map[string]any{"type": "integer"},
"b": map[string]any{"type": "integer"},
},
},
}
// ToolParam must be wrapped in ToolUnionParam for the Tools slice
tools := []anthropic.ToolUnionParam{{OfTool: &addTool}}
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is 2 + 3?")),
}
for {
resp, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 1024,
Messages: messages,
Tools: tools,
})
if err != nil {
log.Fatal(err)
}
// 2. Append the assistant response to history BEFORE processing tool calls.
// resp.ToParam() converts Message → MessageParam in one call.
messages = append(messages, resp.ToParam())
// 3. Walk content blocks. ContentBlockUnion is a flattened struct;
// use block.AsAny().(type) to switch on the actual variant.
toolResults := []anthropic.ContentBlockParamUnion{}
for _, block := range resp.Content {
switch variant := block.AsAny().(type) {
case anthropic.TextBlock:
fmt.Println(variant.Text)
case anthropic.ToolUseBlock:
// 4. Parse the tool input. Use variant.JSON.Input.Raw() to get the
// raw JSON — block.Input is json.RawMessage, not the parsed value.
var in struct {
A int \`json:"a"\`
B int \`json:"b"\`
}
if err := json.Unmarshal([]byte(variant.JSON.Input.Raw()), &in); err != nil {
log.Fatal(err)
}
result := fmt.Sprintf("%d", in.A+in.B)
// 5. NewToolResultBlock(toolUseID, content, isError) builds the
// ContentBlockParamUnion for you. block.ID is the tool_use_id.
toolResults = append(toolResults,
anthropic.NewToolResultBlock(block.ID, result, false))
}
}
// 6. Exit when Claude stops asking for tools
if resp.StopReason != anthropic.StopReasonToolUse {
break
}
// 7. Tool results go in a user message (variadic: all results in one turn)
messages = append(messages, anthropic.NewUserMessage(toolResults...))
}
} ```
Key API surface:
| Symbol | Purpose |
|---|---|
| `resp.ToParam()` | Convert `Message` response → `MessageParam` for history |
| `block.AsAny().(type)` | Type-switch on `ContentBlockUnion` variants |
| `variant.JSON.Input.Raw()` | Raw JSON string of tool input (for `json.Unmarshal`) |
| `anthropic.NewToolResultBlock(id, content, isError)` | Build `tool_result` block |
| `anthropic.NewUserMessage(blocks...)` | Wrap tool results as a user turn |
| `anthropic.StopReasonToolUse` | `StopReason` constant to check loop termination |
| `anthropic.ToolUnionParam{OfTool: &t}` | Wrap `ToolParam` in the union for `Tools:` |
Extended Thinking
Enable Claude's internal reasoning by setting `Thinking` in `MessageNewParams`. The response will contain `ThinkingBlock` content before the final `TextBlock`.
Derived from `anthropic-sdk-go/message.go:6316` (`ThinkingConfigParamOfEnabled`).
```go resp, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{ Model: anthropic.ModelClaudeSonnet4_6, MaxTokens: 16000, // must be > budget_tokens // ThinkingConfigParamOfEnabled(budgetTokens) is the helper constructor. // budgetTokens must be >= 1024 and < MaxTokens. Thinking: anthropic.ThinkingConfigParamOfEnabled(5000), Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("How many r's in strawberry?")), }, }) if err != nil { log.Fatal(err) }
// Thinking blocks come before text blocks in Content for _, block := range resp.Content { switch variant := block.AsAny().(type) { case anthropic.ThinkingBlock: fmt.Println("[thinking]", variant.Thinking) case anthropic.TextBlock: fmt.Println("[response]", variant.Text) } } ```
To disable: `anthropic.NewThinkingConfigDisabledParam()`. For adaptive thinking (model decides budget): `anthropic.NewThinkingConfigAdaptiveParam()`.