Skip to content

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.

You will create:

  • A SQLite MessageStore for conversation history.
  • A Gemini-backed Agent.
  • An HTTPSession that runs a single user message.
  • A custom Go tool the model can call.
  • Go 1.24 or newer.
  • A provider API key.
  • Basic familiarity with Go modules.

For this page, set Gemini:

Terminal window
export GEMINI_API_KEY=...

Provider env vars:

ProviderEnv var
GeminiGEMINI_API_KEY
OpenRouterOPENROUTER_API_KEY
GroqGROQ_API_KEY
CerebrasCEREBRAS_API_KEY
AnthropicANTHROPIC_API_KEY

Create a new app:

Terminal window
mkdir godantic-demo
cd godantic-demo
go mod init example.com/godantic-demo
go get github.com/Desarso/godantic

If you are working from a local checkout of this repository, use a replace directive instead:

replace github.com/Desarso/godantic => ./godantic

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:

Terminal window
go run .

You should see a short text response. A local chat.sqlite file is created and contains the conversation history.

The important line is:

response, err := session.RunSingleInteraction(message)

That one call does several things:

  1. Saves the user message.
  2. Fetches existing conversation history.
  3. Calls the configured model.
  4. Saves the model response.
  5. 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.

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.

Only the model constructor changes.

agent := godantic.Create_Agent(
godantic.NewOpenRouterModel("openai/gpt-4o-mini"),
[]models.FunctionDeclaration{weatherTool},
)

Then set:

Terminal window
export OPENROUTER_API_KEY=...

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.

ProblemFix
Empty provider responseConfirm the provider API key is set in the process environment.
Tool not calledMake the tool description specific and ask the model to use tools when useful.
Multi-argument tool receives wrong valuesKeep Parameters.Required in the same order as the Go function parameters.
History looks wrongUse sessions instead of direct agent.Run unless you are manually handling history.
SQLite locked under loadUse Postgres for multi-process or high-concurrency deployments.
  • 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.