LLM observability for Go developers. Zero external services. 3 lines of code.
Every team building LLM-powered apps in Python or TypeScript has access to mature observability tools β Langfuse, LangSmith, Helicone. Go developers have nothing equivalent.
llmobserve is a zero-dependency, embeddable Go package that gives you complete LLM observability by wrapping your existing provider client. Every API call is automatically captured β prompts, responses, latency, token usage, cost, and errors β stored locally in SQLite and viewable in a lightweight browser dashboard.
No external services. No API keys. No infrastructure. Just import and go.
- Drop-in instrumentation β wrap your existing client in one line, nothing else changes
- Automatic trace capture β prompts, responses, latency, tokens, cost, errors
- Local SQLite storage β data never leaves your machine
- Async writes β zero added latency on your LLM calls
- Browser dashboard β view, filter, and search traces at
http://localhost:7890 - Project grouping β organise traces by feature, service, or use case
- Session tracking β group related calls from one conversation
- Error log β filtered view of all failed LLM calls with full context
| Provider | Status | Notes |
|---|---|---|
| OpenAI | β Full | Chat completions |
| Ollama | β Full | All local models |
| Anthropic | π Coming soon | β |
| Gemini | π Coming soon | β |
go get github.com/llmobserve/llmobserveRequires Go 1.21+
package main
import (
"context"
"fmt"
"log"
ollama "github.com/ollama/ollama/api"
"github.com/llmobserve/llmobserve"
)
func main() {
ctx := context.Background()
// 1 β init llmobserve with dashboard enabled
scope, err := llmobserve.New(llmobserve.Config{
StoragePath: "./traces",
DevMode: true, // starts dashboard at http://localhost:7890
})
if err != nil {
log.Fatal(err)
}
defer scope.Close()
// 2 β wrap your existing Ollama client
ollamaClient, _ := ollama.ClientFromEnvironment()
client := scope.WrapOllama(ollamaClient, "my-project")
// 3 β use exactly as before β everything is traced automatically
err = client.Chat(ctx, &ollama.ChatRequest{
Model: "llama3",
Messages: []ollama.Message{
{Role: "user", Content: "Why is Go great for backend services?"},
},
}, func(resp ollama.ChatResponse) error {
fmt.Print(resp.Message.Content)
return nil
})
if err != nil {
log.Fatal(err)
}
// open http://localhost:7890 to see the trace
}// 1 β init llmobserve
scope, err := llmobserve.New(llmobserve.Config{
StoragePath: "./traces",
DevMode: true,
})
defer scope.Close()
// 2 β wrap your existing OpenAI client
client := scope.WrapOpenAI(openai.NewClient(), "my-project")
// 3 β use exactly as before
resp, err := client.CreateChatCompletion(ctx, openai.ChatCompletionNewParams{
Model: openai.ChatModelGPT4o,
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("Explain distributed tracing in one paragraph."),
},
})Enable the dashboard by setting DevMode: true in your config. It starts automatically at http://localhost:7890.
scope, err := llmobserve.New(llmobserve.Config{
StoragePath: "./traces",
DevMode: true, // dashboard starts at http://localhost:7890
})Overview β summary stats (total requests, avg latency, total cost, error count, total tokens) and a recent traces list.
Traces β paginated, filterable table of all traces. Filter by provider, model, or error status. Click any row to see full detail.
Trace Detail β full prompt conversation with roles, complete response, token breakdown, latency, cost, tags, and error info.
Error Log β filtered view of all failed LLM calls with error messages and context.
Production note:
DevMode: false(the default) does not start the dashboard. Your production binary has zero HTTP overhead unless you explicitly enable it.
scope, err := llmobserve.New(llmobserve.Config{
StoragePath: "./traces", // where SQLite db is created. Default: ./llmobserve
DevMode: true, // starts dashboard at http://localhost:7890. Default: false
})| Field | Type | Default | Description |
|---|---|---|---|
StoragePath |
string |
./llmobserve |
Directory where llmobserve.db is created |
DevMode |
bool |
false |
Starts the browser dashboard automatically |
Every LLM call produces one trace record:
| Field | Description |
|---|---|
trace_id |
Auto-generated UUID |
project_id |
Your project grouping |
session_id |
Optional β group related calls |
provider |
openai / ollama |
model |
Exact model name (e.g. gpt-4o, llama3) |
prompt |
Full message history with roles |
response |
Complete LLM response text |
request_timestamp |
When the call was made (unix ms) |
response_timestamp |
When the response arrived (unix ms) |
input_tokens |
Tokens consumed in prompt |
output_tokens |
Tokens generated in response |
cost_usd |
Calculated cost in USD (best-effort, 0.0 for local models) |
tags |
Your custom metadata (e.g. env=prod) |
error |
Error code and message if the call failed |
Projects organise traces by feature, service, or use case. A project is created automatically the first time you wrap a client with a given project ID.
// traces for your chatbot feature
chatClient := scope.WrapOpenAI(openaiClient, "chatbot")
// traces for your summarizer feature
summaryClient := scope.WrapOpenAI(openaiClient, "summarizer")
// traces for a different environment
prodClient := scope.WrapOllama(ollamaClient, "prod-assistant")Each project appears as a separate entry in the dashboard's project selector.
Group multiple related LLM calls from one user conversation using a session ID:
trace := models.NewTrace(projectID, "openai", "gpt-4o")
trace.SessionID = "user-session-abc123"Filter by session in the dashboard to follow a full conversation thread.
Use the builder pattern to construct filters programmatically:
filter := models.NewTraceFilter().
WithProvider("openai").
WithModel("gpt-4o").
WithDateRange(from, to).
WithHasError(true).
Build()Your Code
β
βΌ
Provider Wrapper (Decorator + Adapter)
β intercepts call, extracts data, builds Trace
βΌ
Tracer (async)
β buffered channel β never blocks your LLM call
βΌ
Storage (Strategy)
β SQLite β persistent, queryable, zero external DB
βΌ
./traces/llmobserve.db
β
βΌ
Dashboard (optional)
HTTP server at :7890 β embedded static files, no Node.js
- No cloud backend or SaaS
- No data ever leaves your machine
- No streaming support (coming in v2)
- No real-time alerting
- Single instance only β each app instance has its own SQLite database
- No distributed tracing β traces from multiple app instances are not aggregated
- Persistent storage required β in containers, mount
/path/to/tracesas a volume
- Core tracer with async writes
- SQLite storage
- OpenAI provider
- Ollama provider
- Browser dashboard (Overview, Traces, Trace Detail, Error Log)
- Anthropic provider
- Gemini provider
- JSON flat file storage backend
- Streaming support
- Cost explorer with pricing table
- Prompt versioning
- OpenTelemetry export
- Distributed tracing are planned for v0.2.0+
Contributions welcome. Please open an issue first to discuss what you'd like to change.
MIT β see LICENSE
llmobserve β Open Source | github.com/llmobserve/llmobserve