Getting Started
This guide builds a minimal assistant that stores history in SQLite and calls Gemini. After it works, you can swap the model, add tools, or move to WebSockets without changing the overall shape.
What You Will Build
Section titled “What You Will Build”You will create:
- A SQLite
MessageStorefor conversation history. - A Gemini-backed
Agent. - An
HTTPSessionthat runs a single user message. - A custom Go tool the model can call.
Prerequisites
Section titled “Prerequisites”- Go 1.24 or newer.
- A provider API key.
- Basic familiarity with Go modules.
For this page, set Gemini:
export GEMINI_API_KEY=...Provider env vars:
| Provider | Env var |
|---|---|
| Gemini | GEMINI_API_KEY |
| OpenRouter | OPENROUTER_API_KEY |
| Groq | GROQ_API_KEY |
| Cerebras | CEREBRAS_API_KEY |
| Anthropic | ANTHROPIC_API_KEY |
Install
Section titled “Install”Create a new app:
mkdir godantic-democd godantic-demogo mod init example.com/godantic-demogo get github.com/Desarso/godanticIf you are working from a local checkout of this repository, use a replace directive instead:
replace github.com/Desarso/godantic => ./godanticMinimal Chat
Section titled “Minimal Chat”Create main.go:
package main
import ( "fmt" "log"
"github.com/Desarso/godantic" "github.com/Desarso/godantic/models" "github.com/Desarso/godantic/stores")
func main() { store, err := stores.NewSQLiteStoreSimple("chat.sqlite") if err != nil { log.Fatal(err) } defer store.Close()
agent := godantic.Create_Agent( godantic.NewGeminiModel("gemini-2.0-flash"), nil, )
session := godantic.NewHTTPSession("conversation-1", &agent, store)
message := models.User_Message{ Role: "user", Content: models.Content{Parts: []models.User_Part{{ Text: "Explain godantic in one sentence.", }}}, }
response, err := session.RunSingleInteraction(message) if err != nil { log.Fatal(err) }
for _, part := range response.Parts { if part.Text != nil { fmt.Println(*part.Text) } }}Run it:
go run .You should see a short text response. A local chat.sqlite file is created and contains the conversation history.
What Happened
Section titled “What Happened”The important line is:
response, err := session.RunSingleInteraction(message)That one call does several things:
- Saves the user message.
- Fetches existing conversation history.
- Calls the configured model.
- Saves the model response.
- Returns provider-independent response parts.
The provider is only one piece. The same session pattern works with OpenRouter, Groq, Cerebras, Anthropic, or your own Model implementation.
Add Your First Tool
Section titled “Add Your First Tool”Tools are Go functions that the model can request. Each tool has a JSON schema so the model knows how to call it.
Add this function:
func GetWeather(city string) (string, error) { return `{"forecast":"sunny","city":"` + city + `"}`, nil}Create a declaration:
weatherTool := models.FunctionDeclaration{ Name: "get_weather", Description: "Get the current weather for a city.", Parameters: models.Parameters{ Type: "object", Properties: map[string]interface{}{ "city": map[string]interface{}{ "type": "string", "description": "City name, for example Toronto or Chicago", }, }, Required: []string{"city"}, }, Callable: GetWeather,}Pass it to the agent:
agent := godantic.Create_Agent( godantic.NewGeminiModel("gemini-2.0-flash"), []models.FunctionDeclaration{weatherTool},)Ask a tool-friendly question:
Text: "What is the weather in Toronto? Use tools if useful.",Depending on the provider and prompt, the model may call get_weather, receive the result, and produce a final answer.
Switch Providers
Section titled “Switch Providers”Only the model constructor changes.
agent := godantic.Create_Agent( godantic.NewOpenRouterModel("openai/gpt-4o-mini"), []models.FunctionDeclaration{weatherTool},)Then set:
export OPENROUTER_API_KEY=...Use Streaming
Section titled “Use Streaming”Streaming returns two channels: one for chunks and one for errors.
stream, errs := session.RunStreamInteraction(message)
for stream != nil || errs != nil { select { case chunk, ok := <-stream: if !ok { stream = nil continue } for _, part := range chunk.Parts { if part.Text != nil { fmt.Print(*part.Text) } } case err, ok := <-errs: if ok && err != nil { log.Fatal(err) } errs = nil }}Use streaming when your UI should start rendering before the full response is complete.
Common Mistakes
Section titled “Common Mistakes”| Problem | Fix |
|---|---|
| Empty provider response | Confirm the provider API key is set in the process environment. |
| Tool not called | Make the tool description specific and ask the model to use tools when useful. |
| Multi-argument tool receives wrong values | Keep Parameters.Required in the same order as the Go function parameters. |
| History looks wrong | Use sessions instead of direct agent.Run unless you are manually handling history. |
| SQLite locked under load | Use Postgres for multi-process or high-concurrency deployments. |
Next Steps
Section titled “Next Steps”- Read Architecture to understand the data flow.
- Read Models to choose or implement a provider.
- Read Tools to design safe app tools.
- Read Sessions before building HTTP, SSE, or WebSocket endpoints.