Typed, validated LLM output for Go. The Go equivalent of Python's instructor / pydantic-ai — define a struct, get back a typed value.
structllm is a Go library for structured output from LLMs. You write a Go
struct; structllm turns it into a JSON Schema, sends it to the model through
the provider's native structured-output mechanism, repairs and validates the
response, retries on validation failure, and gives you back a typed T.
It works with the three most common Go LLM destinations out of the box:
- OpenAI — via
response_format(Structured Outputs / JSON Schema) - Anthropic Claude — via tool use
- Ollama — via the
formatparameter
Built on JSON Schema, native function calling / tool use, and Go generics — no DSL, no codegen, no runtime dependency on Python.
go get github.com/mohsenm4/structllmRequires Go 1.25 or later.
type Article struct {
Title string `json:"title"`
Tags []string `json:"tags"`
Sentiment string `json:"sentiment" enum:"positive,negative,neutral"`
}
client := anthropic.New(os.Getenv("ANTHROPIC_API_KEY"))
article, err := structllm.Generate[Article](ctx, client, structllm.Request{
Model: "claude-sonnet-4-6",
Prompt: "Analyze: " + text,
})
// article.Title, article.Tags, article.Sentiment — typed, validated, readyUnder the hood:
- A JSON Schema is generated from the struct via reflection + struct tags.
- The schema is sent to the provider using its native structured-output mode
(OpenAI
response_format, Anthropic tool use, Ollamaformat). - The response is parsed, repaired (markdown fences, trailing commas, truncation), and validated against the schema.
- On validation failure the error is sent back to the model with a retry — up
to
Request.MaxAttemptstimes (default 3).
import (
"github.com/mohsenm4/structllm/provider/anthropic"
"github.com/mohsenm4/structllm/provider/openai"
"github.com/mohsenm4/structllm/provider/ollama"
)
anthropicClient := anthropic.New(os.Getenv("ANTHROPIC_API_KEY"))
openaiClient := openai.New(os.Getenv("OPENAI_API_KEY"))
ollamaClient, _ := ollama.FromEnvironment() // reads OLLAMA_HOSTAll three satisfy structllm.Provider — swap them freely. Any custom backend
that implements the one-method Provider
interface works too.
| Tag | Effect |
|---|---|
json:"name" |
field name in the schema (standard) |
json:",omitempty" or *T |
field is optional (not in required) |
description:"..." |
adds a description to help the model |
enum:"a,b,c" |
restrict the value to a fixed set |
For fields where the source text may not contain the answer:
type Quote struct {
Text string `json:"text"`
Author structllm.Maybe[string] `json:"author"`
}
quote, _ := structllm.Generate[Quote](ctx, client, req)
if name, ok := quote.Author.Get(); ok {
fmt.Println("found author:", name)
} else {
fmt.Println("no author:", quote.Author.Reason)
}This avoids the common failure mode where the model invents an answer because the schema demanded one.
Ask the model to think step-by-step before committing to an answer:
result, err := structllm.GenerateWithReasoning[Answer](ctx, client, req)
fmt.Println(result.Reasoning) // the model's thinking
fmt.Println(result.Answer) // typed answerThe reasoning field is declared first in the schema so the model writes its thinking before the final answer.
If the response fails schema validation, structllm re-prompts the model with
its previous attempt and the validation error. Capped by Request.MaxAttempts
(default 3; set to 1 to disable).
The pieces are also exported for direct use:
| Package | Use |
|---|---|
schema |
Generate[T]() — JSON Schema from a Go type |
repair |
JSON([]byte) — clean up markdown fences, trailing commas, truncated output |
validate |
JSON(schema, data) — validate JSON against a JSON Schema |
Runnable programs are in examples/:
examples/basic— extract typed data from a stringexamples/maybe— handle missing fields gracefullyexamples/reasoning— chain-of-thought before the answer
Each example needs ANTHROPIC_API_KEY set in the environment:
go run ./examples/basic| structllm | raw provider SDK | LangChain-style framework | |
|---|---|---|---|
| Typed output | ✅ Generic T |
❌ map[string]any / strings |
any |
| Schema from struct tags | ✅ | ❌ Hand-write JSON Schema | |
| Validation + repair | ✅ Built-in | ❌ Roll your own | |
| Retry with feedback | ✅ | ❌ | |
| Multi-provider | ✅ OpenAI / Claude / Ollama | ❌ One per SDK | ✅ |
| Dependencies | Minimal | Minimal | Heavy |
v0.1.0 — usable. Not yet battle-tested in production; please open issues.
- Streaming partial output (
v0.2) - More providers (Gemini, Mistral, Groq)
- Function/tool calling beyond structured output
See CONTRIBUTING.md. Bug reports, feature requests, and PRs welcome.
- instructor — the original, Python
- BAML — schema-as-DSL approach
- pydantic-ai — agent + typed output
MIT — see LICENSE.
Keywords: golang llm structured output, go openai structured output, go anthropic claude tool use, go ollama json format, go json schema from struct, go instructor alternative, go pydantic alternative, typed llm response golang, go function calling library.