title: Getting started description: Your first AI chat end to end: activate a provider, resolve a credential, send a Chat message, then take a structured Ask response straight into a Go struct. tags: [tutorial, getting-started, credentials, structured-output]
Getting started¶
This tutorial walks you through your first AI chat, end to end: activate a
provider, resolve a credential, send a Chat message, and then ask for a
structured Ask response unmarshalled straight into a Go struct. Allow about
fifteen minutes, plus whatever the two API calls cost you. Both are small.
You will do everything in one self-contained Go program so you can watch each moving part. It uses Anthropic Claude; the same shape works for every provider, and you would swap only the blank import, the provider constant, and the credential env var.
Prerequisites¶
- Go 1.27 or newer.
- An Anthropic API key exported as
ANTHROPIC_API_KEY. - A new module to experiment in:
mkdir chat-tutorial && cd chat-tutorial
go mod init example.test/chat-tutorial
go get gitlab.com/phpboyscout/go/chat-anthropic
Install the provider module, not the core, and let it bring the core with
it. The two are released as a matched pair, and asking for the core separately
can select a version newer than the provider was built against, which does not
compile. Version compatibility
has the detail; go mod tidy in the next step promotes the core to a direct
requirement at the version already chosen, which is what you want.
Step 1: activate a provider¶
Providers live in separate modules and are switched on by a blank import:
the same pattern as database drivers or image/* decoders. Importing
chat-anthropic for its init() side effect registers the claude provider
with the core:
import (
"gitlab.com/phpboyscout/go/chat"
_ "gitlab.com/phpboyscout/go/chat-anthropic" // registers ProviderClaude
)
Without this import, chat.New with ProviderClaude returns
unsupported provider: claude. The SDK-free claude-local provider is the only
one that needs no import, because it ships in the core.
Step 2: construct a client¶
chat.New takes package-owned Settings: a Config describing provider
behaviour, and an optional *slog.Logger. Leave Token empty and the client
falls back to the well-known ANTHROPIC_API_KEY environment variable.
client, err := chat.New(ctx, chat.Settings{
Config: chat.Config{
Provider: chat.ProviderClaude,
SystemPrompt: "You are a concise assistant.",
},
})
If no Model is set, Claude defaults to claude-opus-5.
Step 3: send a message¶
Chat sends a prompt and returns the text reply. Message history is retained on
the client, so follow-up calls continue the same conversation.
reply, err := client.Chat(ctx, "Name three uses for a CLI tool, one line each.")
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
Step 4: ask for structured output¶
Ask unmarshals the model's answer directly into a Go value. Build a
ResponseSchema from your struct with chat.GenerateSchema, set it on the
Config, and the provider is forced to return JSON matching that shape.
type Summary struct {
Title string `json:"title"`
Points []string `json:"points"`
}
structured, err := chat.New(ctx, chat.Settings{
Config: chat.Config{
Provider: chat.ProviderClaude,
ResponseSchema: chat.GenerateSchema[Summary](),
SchemaName: "summary",
},
})
if err != nil {
log.Fatal(err)
}
var out Summary
if err := structured.Ask(ctx, "Summarise the Go language in a title and three points.", &out); err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n%v\n", out.Title, out.Points)
The complete program¶
package main
import (
"context"
"fmt"
"log"
"gitlab.com/phpboyscout/go/chat"
_ "gitlab.com/phpboyscout/go/chat-anthropic"
)
type Summary struct {
Title string `json:"title"`
Points []string `json:"points"`
}
func main() {
ctx := context.Background()
client, err := chat.New(ctx, chat.Settings{
Config: chat.Config{
Provider: chat.ProviderClaude,
SystemPrompt: "You are a concise assistant.",
},
})
if err != nil {
log.Fatal(err)
}
reply, err := client.Chat(ctx, "Name three uses for a CLI tool, one line each.")
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
structured, err := chat.New(ctx, chat.Settings{
Config: chat.Config{
Provider: chat.ProviderClaude,
ResponseSchema: chat.GenerateSchema[Summary](),
SchemaName: "summary",
},
})
if err != nil {
log.Fatal(err)
}
var out Summary
if err := structured.Ask(ctx, "Summarise the Go language in a title and three points.", &out); err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n%v\n", out.Title, out.Points)
}
Tidy the module (this promotes the core to a direct requirement at the version the provider already pinned) then run it:
Check it worked¶
The program prints a one-line-per-use answer, then a title and three bullet
points parsed into the Summary struct, something like:
Automating repetitive tasks
Scripting deployments
Querying a service without a browser
A concise tour of Go
[Compiled and statically typed Built-in concurrency with goroutines Standard library covers most needs]
The wording will differ every run; the shape will not.
Fix it when it does not¶
| What you see | What it means |
|---|---|
unsupported provider: claude |
the blank import from step 1 is missing |
Anthropic API key is required but not provided |
ANTHROPIC_API_KEY is not exported in the shell you ran from (this fails at chat.New, before any request) |
invalid operation: … is not an interface at build time |
the core and provider modules are at different minors (see version compatibility) |
Next steps¶
- Swap providers or point at a local model: Choose & configure a provider.
- Give the model tools it can call: Call tools (the ReAct loop).
- Stream long replies token by token: Stream responses.
- Look up a field, a default or an error: Configuration fields.
- Understand the loop and the seams: Architecture.