promptr

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: MIT Imports: 2 Imported by: 0

README

promptr

CI Go Reference

Typed prompts for Go. promptr makes a language model's output typed and reliable — the way BAML does, but as idiomatic, dependency-free Go.

Declare your prompts and their input/output types in a small .promptr schema; promptr compiles it to ordinary Go functions you call. Each generated function renders the prompt, calls a model through a tiny Provider interface you wire up (no vendor SDK in the core), and turns the loose reply into your typed value — retrying with the parse error fed back if the first attempt won't fit.

Its core is schema-aligned parsing: instead of forcing a model into a rigid JSON mode (which empirically degrades answer quality), you let it write naturally and a tolerant, schema-driven parser coerces the result into your Go types — recovering from prose preambles, Markdown fences, trailing commas, single quotes, fuzzy enum spellings, and truncated objects that real model output is full of.

.promptr schema ──promptr generate──▶ typed Go function
                                            │ renders prompt (schema baked in)
                                            ▼
                                    your Provider (any model)
                                            │ loose text reply
                                            ▼
                            coerce ──▶ typed value  (retry on misfit)

The pipeline in one example

// ticket.promptr
enum Severity { LOW HIGH CRITICAL }

class Ticket {
  title    string
  severity Severity
  tags     string[]
  due_days int?
}

client GPT4o { provider "openai" model "gpt-4o" }

function ExtractTicket(text: string) -> Ticket {
  client GPT4o
  prompt #"
    Extract a support ticket from the message.
    {{ ctx.output_schema }}      // compiler injects a schema description of Ticket
    Message: {{ text }}
  "#
}
promptr generate ./...     # -> ticket.promptr.go
ticket, err := ExtractTicket(ctx, provider, "my server is down!!")
// ticket.Severity == SeverityCRITICAL, even if the model wrote "critical priority"

The generated function bakes a human-readable schema of Ticket into the prompt (BAML's "show the model the shape you want" trick), then coerces the reply and re-asks on a parse miss — all in a few lines you can read.

coerce — the schema-aligned parser (usable on its own)

Don't want the DSL? Use the kernel directly on the structs you already have:

import "github.com/zkrebbekx/promptr/coerce"

ticket, err := coerce.Into[Ticket](modelText) // modelText: fences, prose and all

coerce.Into[T] digests input that encoding/json would reject outright:

the model emitted encoding/json coerce
```json { ... } ``` wrapped in prose ✓ extracts the payload
{ title: 'x', tags: [1,2,], } (unquoted key, single quotes, trailing comma)
{"due_days": "7"} into an int field ✓ coerces "7"7
{"amount": "$1,200.50"} into a float64 ✓ → 1200.5
{"severity": "high priority"} into an enum ✓ fuzzy-matches → HIGH
{"title": "x", "tags": ["a", (truncated) ✓ recovers what parsed

Nested structs, *T optionals, maps, snake/camel/case-insensitive keys, and a single value where a list was expected are all handled. Bare prose where a struct was expected returns a *coerce.Error — the signal the runtime retries on.

Discriminated unions
u := coerce.NewUnion(Search{}, Escalate{})
act, err := coerce.ResolveInto[Action](modelText, u) // best-fit by shape or "type" discriminator
Streaming
for p := range coerce.Stream[Ticket](tokenChan) {
    render(p.Value)        // a progressively-completed Ticket
    if p.Complete { break }
}

Providers

The core imports no LLM SDK. A Provider is one method:

type Provider interface {
    Complete(ctx context.Context, messages []Message) (string, error)
}
  • providers/fake — deterministic scripted replies for tests and the playground.
  • providers/anthropic — a real adapter over the Anthropic Messages API, built on net/http alone. Import it only if you want it.

Wiring any other model is a dozen lines of net/http.

Install

# library
go get github.com/zkrebbekx/promptr

# CLI compiler
go install github.com/zkrebbekx/promptr/cmd/promptr@latest

Also published as a container image (ghcr.io/zkrebbekx/promptr) and a Homebrew cask.

Playground

A WebAssembly playground (DSL → Go, and paste-messy-output → repaired value) runs entirely client-side: playground/, deployed to GitHub Pages.

Develop

make test     # go test ./...
make race     # -race
make fuzz     # fuzz the tolerant parser
make lint
go generate ./...   # regenerate examples

License

MIT

Documentation

Overview

Package promptr is the runtime that generated .promptr code calls into: a minimal, provider-agnostic Provider interface plus the parse-with-repair loop that turns a model's loose reply into a typed Go value via the coerce kernel.

The compiler (cmd/promptr) turns each `function` in a .promptr file into a Go function whose body is a single call to Extract — so the generated code stays thin and readable, and all the retry/coercion logic lives here, tested once.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Extract

func Extract[T any](ctx context.Context, p Provider, prompt string, opts Options) (T, error)

Extract runs prompt against the provider and coerces the reply into T. If the reply will not coerce, it re-asks — appending the unparseable reply and the parse error so the model can correct itself — up to Options.Attempts times.

This is the function every generated .promptr function calls.

Types

type Message

type Message struct {
	Role    string
	Content string
}

Message is one turn in a chat-style exchange. Roles follow the usual "system" / "user" / "assistant" convention; a Provider maps them to whatever its backend expects.

type Options

type Options struct {
	// Attempts is the maximum number of model calls (default 2): the first
	// try plus repair re-asks when the reply will not coerce into T.
	Attempts int
	// System, when non-empty, is prepended as a system message.
	System string
}

Options tunes an Extract call.

type Provider

type Provider interface {
	Complete(ctx context.Context, messages []Message) (string, error)
}

Provider is the single seam between promptr and a language model. Implement it with net/http against any chat API — the core imports no vendor SDK. A deterministic fake lives in providers/fake for tests and the playground.

Directories

Path Synopsis
cmd
promptr command
Command promptr compiles .promptr schema files into Go.
Command promptr compiles .promptr schema files into Go.
Package codegen turns a parsed .promptr File into idiomatic Go source.
Package codegen turns a parsed .promptr File into idiomatic Go source.
Package coerce is a schema-aligned parser: it turns the loose, near-JSON text that language models actually emit into typed Go values.
Package coerce is a schema-aligned parser: it turns the loose, near-JSON text that language models actually emit into typed Go values.
Package dsl lexes and parses the .promptr schema language into an AST.
Package dsl lexes and parses the .promptr schema language into an AST.
examples
ticket
Package ticket is an end-to-end promptr example: a .promptr schema compiled to Go (ticket.promptr.go) and exercised against the fake provider in ticket_test.go.
Package ticket is an end-to-end promptr example: a .promptr schema compiled to Go (ticket.promptr.go) and exercised against the fake provider in ticket_test.go.
providers
anthropic
Package anthropic is a promptr.Provider backed by the Anthropic Messages API, built on net/http alone — no vendor SDK, in keeping with promptr's zero-SDK core.
Package anthropic is a promptr.Provider backed by the Anthropic Messages API, built on net/http alone — no vendor SDK, in keeping with promptr's zero-SDK core.
fake
Package fake is a deterministic promptr.Provider for tests, examples and the playground — no network, no API key.
Package fake is a deterministic promptr.Provider for tests, examples and the playground — no network, no API key.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL