godantic is a small Go framework for building useful LLM agents without turning your app into a pile of provider glue.
Bring a model, a message store, and optional tools. godantic gives you the agent loop, streaming, tool calls, history persistence, and HTTP/WebSocket session helpers.
Documentation: https://desarso.github.io/godantic/
Current version line: v0.x. See Versioning for the release policy.
Most assistant backends end up solving the same problems: provider adapters, function calling, streaming, tool result feedback, chat history, WebSocket plumbing, and persistence. godantic keeps those concerns in one Go module with simple interfaces you can replace when your app needs something custom.
- One
Modelinterface across Gemini, OpenRouter, Groq, Cerebras, Anthropic, and custom providers. - Request/response, streaming, SSE, and WebSocket session helpers.
- Tool calling with JSON-schema declarations backed by ordinary Go functions.
- SQLite and PostgreSQL conversation stores through a replaceable
MessageStoreinterface. - Built-in tools for search, web fetch, file operations, shell execution, image analysis, workflows, skill files, TypeScript execution, and image generation.
- Optional trace persistence for WebSocket tool execution.
The version is stored in VERSION. Release helpers live in the root Makefile:
make test
make docs-build
make releasemake release validates the tree, creates a vX.Y.Z tag, pushes main and the tag, and creates a GitHub release using gh.
go get github.com/Desarso/godanticFor local development from a parent app, use a Go replace directive:
replace github.com/Desarso/godantic => ./godanticSet the API key for the provider you use:
| Provider | Default env var |
|---|---|
| Gemini | GEMINI_API_KEY |
| OpenRouter | OPENROUTER_API_KEY |
| Groq | GROQ_API_KEY |
| Cerebras | CEREBRAS_API_KEY |
| Anthropic | ANTHROPIC_API_KEY |
Some built-in tools require their own keys:
| Tool | Env var |
|---|---|
common_tools.Brave_Search |
BRAVE_API_KEY |
common_tools.Search |
PERPLEXITY_API_KEY |
WebSocket TTS support uses optional ElevenLabs env vars such as ELEVEN_LABS_API_KEY, ELEVEN_LABS_TTS_VOICE_ID, and ELEVEN_LABS_TTS_MODEL_ID.
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)
}
agent := godantic.Create_Agent(
godantic.NewGeminiModel("gemini-2.0-flash"),
nil,
)
session := godantic.NewHTTPSession("conversation-1", &agent, store)
userMessage := models.User_Message{
Role: "user",
Content: models.Content{Parts: []models.User_Part{{Text: "Say hello in one sentence."}}},
}
response, err := session.RunSingleInteraction(userMessage)
if err != nil {
log.Fatal(err)
}
for _, part := range response.Parts {
if part.Text != nil {
fmt.Println(*part.Text)
}
}
}Run it with:
GEMINI_API_KEY=... go run .Use the helper constructors for common providers:
agent := godantic.Create_Agent(godantic.NewGeminiModel("gemini-2.0-flash"), tools)
agent := godantic.Create_Agent(godantic.NewOpenRouterModel("openai/gpt-4o-mini"), tools)
agent := godantic.Create_Agent(godantic.NewGroqModel("llama-3.1-70b-versatile"), tools)
agent := godantic.Create_Agent(godantic.NewCerebrasModel("llama-3.3-70b"), tools)
agent := godantic.Create_Agent(godantic.NewAnthropicModel("claude-sonnet-4-20250514"), tools)Provider-specific option helpers are also available:
temp := 0.2
maxTokens := 2048
model := godantic.NewOpenRouterModelWithOptions(
"anthropic/claude-sonnet-4",
&temp,
&maxTokens,
"https://example.com",
"Example App",
)OpenRouter, Groq, Cerebras, and Anthropic model structs also support custom BaseURL and APIKeyEnv fields for compatible gateways.
Use HTTPSession for request/response APIs, SSE endpoints, jobs, or tests.
session := godantic.NewHTTPSession("conversation-1", &agent, store)
response, err := session.RunSingleInteraction(userMessage)
stream, errs := session.RunStreamInteraction(userMessage)
history, err := session.GetChatHistory()For clients that send the full request shape, use the Model_Request methods:
req := models.Model_Request{User_Message: &userMessage}
response, err := session.RunSingleInteractionWithRequest(req)Implement SSEWriter and call RunSSEInteraction:
type Writer struct{}
func (Writer) WriteSSE(data string) error { return nil }
func (Writer) WriteSSEError(err error) error { return nil }
func (Writer) Flush() {}
err := session.RunSSEInteraction(userMessage, Writer{}, ctx)Use AgentSession when you have a *websocket.Conn and want the built-in WebSocket protocol, tool approval flow, frontend tool support, trace streaming, and optional TTS handling.
session := godantic.NewAgentSession(
"session-1",
"user-1",
conn,
&agent,
store,
memoryManager,
)
if traceStore != nil {
session.SetTraceStore(traceStore)
}
err := session.RunInteraction(req)memoryManager can be nil. If provided, it must implement the session memory interface in sessions/types.go.
SQLite is the easiest default:
store, err := stores.NewSQLiteStoreSimple("chat.sqlite")PostgreSQL is available by DSN or config:
store, err := stores.NewPostgresStoreSimple("host=localhost user=app password=secret dbname=chat port=5432 sslmode=disable")You can provide your own persistence by implementing stores.MessageStore.
Tools are models.FunctionDeclaration values. Each declaration contains:
Name: the tool name exposed to the model.Description: when the model should call it.Parameters: JSON schema for arguments.Callable: a Go function that returns(string, error).
The callable can take no parameters, one parameter, or multiple typed parameters. Agent.ExecuteTool maps model-provided arguments into the Go function and returns a JSON string result.
For the standard local-agent tools, use common_tools.DefaultTools():
import "github.com/Desarso/godantic/common_tools"
tools := common_tools.DefaultTools()
agent := godantic.Create_Agent(godantic.NewOpenRouterModel("openai/gpt-4o-mini"), tools)For schema-backed built-ins, use Create_Tools with functions that have cached schemas in schemas/cached_schemas:
tools, err := godantic.Create_Tools([]interface{}{
common_tools.Brave_Search,
common_tools.Web_Fetch,
common_tools.Execute_TypeScript,
})
if err != nil {
log.Fatal(err)
}For application tools, the most portable path is to construct models.FunctionDeclaration directly:
func GetWeather(city string) (string, error) {
return "Sunny in " + city, nil
}
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",
},
},
Required: []string{"city"},
},
Callable: GetWeather,
}
agent := godantic.Create_Agent(godantic.NewGeminiModel("gemini-2.0-flash"), []models.FunctionDeclaration{weatherTool})For multi-argument functions, keep Required in the same order as the Go function parameters.
WSConfig is a convenience builder for apps that wire controllers from config:
config := godantic.NewWSConfig().
WithOpenRouter("openai/gpt-4o-mini").
WithSystemPrompt("You are concise and helpful.").
WithSQLiteStore("chat.sqlite").
WithTools([]interface{}{common_tools.Brave_Search}).
WithTemperature(0.2).
WithMaxTokens(2048)Then create tools and an agent:
tools, err := godantic.Create_Tools(config.Tools)
if err != nil {
log.Fatal(err)
}
agent := godantic.Create_Agent_From_Config(config, tools)A normal user request is a models.Model_Request with a User_Message:
req := models.Model_Request{
User_Message: &models.User_Message{
Role: "user",
Content: models.Content{Parts: []models.User_Part{{Text: "What can you do?"}}},
},
}Tool follow-up requests use Tool_Results:
req := models.Model_Request{
Tool_Results: &[]models.Tool_Result{{
Tool_ID: "call_123",
Tool_Name: "get_weather",
Tool_Output: `{"result":"Sunny"}`,
}},
}Responses are models.Model_Response values containing parts. A part may be text, a function call, thinking/reasoning content, or provider-specific metadata.
Run the module tests with:
go test ./...Apache-2.0. See LICENSE.