a1

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 10 Imported by: 0

README

a1

Grade-A1 AI calls for Go.

A thin, opinionated layer over the official Anthropic Go SDK - plain-text completions and schema-constrained JSON extraction.

Go Reference Go Version


a1 wraps the Anthropic Go SDK for the two calls apps actually make: plain-text completions and schema-constrained JSON extraction.

It is deliberately not a framework. Models stay plain id strings, schemas stay plain maps, the SDK stays one import away. a1 only owns the glue you keep rewriting between projects:

  • Metering - every billed call reports model, tokens, and duration through one gate; the default is a grep-able log line (grep 💥 = everything that cost money)
  • Stop-reason handling - refusals and max_tokens truncation become typed errors (ErrRefused, ErrTruncated) instead of silently wrong text
  • Response assembly - text blocks are concatenated (answers get split across blocks in the wild)
  • Content retry - transient empty/truncated/garbled responses are retried; API errors are not (the SDK already retries those)

Install

go get github.com/amberpixels/a1

Plain Text

c := a1.NewClient(os.Getenv("ANTHROPIC_API_KEY"))

text, meta, err := c.Text(ctx, a1.Request{
    Task:      "compose-message", // names the call site in metering logs
    Model:     "claude-haiku-4-5",
    System:    "You write Telegram notifications. Plain text only.",
    Prompt:    prompt,
    MaxTokens: 1024,
})
// meta: Model, StopReason, InputTokens, OutputTokens, Duration

Structured JSON

JSON[T] uses Anthropic's native structured outputs - the response is schema-guaranteed, then unmarshaled into your type. Keep property descriptions rich: the model reads them like prompt text.

type Guess struct {
    Place      string `json:"place"`
    Confidence string `json:"confidence"`
}

schema := a1.Obj(map[string]any{
    "place":      a1.Prop("A single geocodable place in the local language."),
    "confidence": a1.Enum("How sure the answer is.", "high", "medium", "low"),
})

guess, meta, err := a1.JSON[Guess](ctx, c, a1.Request{
    Task:     "geo-guess",
    Model:    "claude-opus-4-8",
    System:   systemPrompt,
    Prompt:   prompt,
    Thinking: true, // adaptive thinking; silently skipped on models without it
    Attempts: 2,    // retry transient empty/garbled responses
}, schema)

a1.Obj requires every property by default (structured outputs reject optional-by-absence anyway) and always sets additionalProperties: false.

Metering

The default meter logs 💥 tokens burned (Anthropic) via slog. Plug in your own logger or metrics:

c := a1.NewClient(key, a1.WithMeter(func(ctx context.Context, task string, m a1.Meta) {
    myLogger.Info("💥 tokens burned", "task", task, "model", m.Model,
        "in", m.InputTokens, "out", m.OutputTokens)
}))

The meter fires on every billed call, including failed attempts that still returned a response - it tracks spend, not success.

Errors

text, _, err := c.Text(ctx, req)
switch {
case errors.Is(err, a1.ErrRefused):    // model/classifier declined - don't retry
case errors.Is(err, a1.ErrTruncated):  // raise MaxTokens
case errors.Is(err, a1.ErrEmpty):      // transient; raise Attempts
case err != nil:                       // SDK error (auth, rate limit, network...)
}

Testing Your Integration

WithSDKOptions accepts raw SDK options - point the client at an httptest server and script responses (see a1_test.go for a ready-made pattern):

c := a1.NewClient("test-key", a1.WithSDKOptions(option.WithBaseURL(srv.URL)))

Feedback

a1 is a solo, opinionated project - but if you stumbled upon it and have ideas, questions, or bug reports, an issue is always welcome :)

License

MIT © amberpixels

Documentation

Overview

Package a1 is a thin, opinionated layer over the official Anthropic Go SDK for the calls amberpixels apps actually make: plain-text completions and schema-constrained JSON extraction.

It is deliberately not a framework. The SDK stays visible (models are plain id strings, schemas are plain maps) — a1 only owns the glue every app kept rewriting:

  • usage metering: every billed call reports Meta (model, tokens, duration) through one gate — by default the grep-able "💥 tokens burned" log line
  • stop-reason handling: refusal and max_tokens truncation become typed errors instead of silently wrong text
  • response assembly: text blocks are concatenated (answers have been observed split across blocks or followed by empty ones)
  • retry: transient empty/truncated/garbled responses — a failure class the SDK's transport retries don't cover — are retried when Request.Attempts allows; API errors are never retried here (the SDK already does that)

Index

Constants

View Source
const DefaultMaxTokens = 4096

DefaultMaxTokens bounds a completion when Request.MaxTokens is unset.

Variables

View Source
var (
	// ErrRefused: the model (or a safety classifier) declined the request —
	// stop_reason "refusal". Not retryable; retrying the same prompt refuses
	// again.
	ErrRefused = errors.New("a1: request refused")
	// ErrTruncated: the response hit MaxTokens before finishing — stop_reason
	// "max_tokens". Retryable (a rerun usually stays in budget), but a
	// persistent truncation means MaxTokens is too low for the task.
	ErrTruncated = errors.New("a1: response truncated (max_tokens)")
	// ErrEmpty: the response carried no text. Observed rarely in bulk runs;
	// retryable.
	ErrEmpty = errors.New("a1: empty response")
)

Typed outcomes of a completed API call. API/transport errors from the SDK are returned as-is (see the SDK's typed error values for those).

Functions

func Cost added in v0.0.2

func Cost(model string, inputTokens, outputTokens int64) float64

Cost estimates the USD list-price cost of one call. It is an estimate for budgeting/metering (no batch or cache discounts), not a billing statement.

func Enum

func Enum(description string, values ...string) map[string]any

Enum builds a string property constrained to the given values.

func Obj

func Obj(props map[string]any, required ...string) map[string]any

Obj builds an object schema from its properties. required lists the mandatory property names; when omitted, every property is required (the common case — and structured outputs reject optional-by-absence anyway). additionalProperties is always false, as structured outputs require.

func Prop

func Prop(description string) map[string]any

Prop builds a string property with a description.

func SupportsAdaptiveThinking

func SupportsAdaptiveThinking(model string) bool

SupportsAdaptiveThinking reports whether a model accepts the adaptive thinking config. Haiku-tier and pre-4.x models reject it.

Types

type Budget added in v0.0.2

type Budget struct {
	Default float64
	Min     float64
	Max     float64
}

Budget declares a spend policy in code: a default plus the operator-tunable range. Configuration can customize the value within [Min, Max]; anything else — unset, zero, negative, out of range — resolves to Default. Unlimited is deliberately not expressible: a missing or bogus config value must never disable a spend guard.

The unit is whatever the declaring app counts in (USD/day is the common case); Budget only owns the clamping semantics, not the counting.

func (Budget) Resolve added in v0.0.2

func (b Budget) Resolve(configured float64) float64

Resolve maps an operator-configured value to the effective one: the value itself when it lies within [Min, Max], Default otherwise.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client wraps the Anthropic SDK client with metering.

func NewClient

func NewClient(apiKey string, opts ...Option) *Client

NewClient builds a metered client for the given API key.

func (*Client) Text

func (c *Client) Text(ctx context.Context, req Request) (string, Meta, error)

Text runs one plain-text completion and returns the assistant's text.

type Meta

type Meta struct {
	Model        string
	StopReason   string
	InputTokens  int64
	OutputTokens int64
	// CostUSD is the estimated list-price cost of the call (see Cost).
	CostUSD  float64
	Duration time.Duration
}

Meta is the usage/latency record of one billed API call.

func JSON

func JSON[T any](ctx context.Context, c *Client, req Request, schema map[string]any) (*T, Meta, error)

JSON runs one schema-constrained completion (structured outputs) and unmarshals the response into T. The schema is a plain JSON-schema map — build it with Obj/Prop/Enum or by hand; keep property descriptions rich, they steer the model as much as the prompt does.

type Meter

type Meter func(ctx context.Context, task string, m Meta)

Meter observes every billed call (including failed attempts that still returned a response). task is Request.Task.

type Option

type Option func(*config)

Option configures a Client.

func WithMeter

func WithMeter(m Meter) Option

WithMeter replaces the default metering log line with a custom observer (e.g. the app's own logger, or a metrics counter).

func WithSDKOptions

func WithSDKOptions(opts ...option.RequestOption) Option

WithSDKOptions appends raw SDK request options to the underlying client — base URL overrides, custom HTTP clients, extra headers.

type Request

type Request struct {
	// Task names the call site for metering/logs (e.g. "geo-guess",
	// "compose-message").
	Task string
	// Model is the Anthropic model id. Required.
	Model string
	// System is the system prompt; empty means none.
	System string
	// Prompt is the user message.
	Prompt string
	// MaxTokens bounds the completion; 0 means DefaultMaxTokens.
	MaxTokens int64
	// Thinking enables adaptive thinking on models that support it (it is
	// silently skipped on models that don't, e.g. Haiku 4.5).
	Thinking bool
	// Attempts is how many times a transient content failure (empty,
	// truncated, or unparseable response) is tried before giving up.
	// 0 or 1 means no retry. API/transport errors are never retried here.
	Attempts int
}

Request describes one completion call. Model is a plain Anthropic model id (e.g. "claude-haiku-4-5" or an anthropic.ModelClaude* constant).

Jump to

Keyboard shortcuts

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