---
name: baml-structured-output
description: "Define typed LLM functions in BAML's DSL, generate clients, and get robust structured output via Schema-Aligned Parsing. Production BAML (BoundaryML): define LLM functions in the .baml DSL with classes, enums, and unions; run the VSCode playground and baml-cli generate to emit type-safe Python/TS/Go/Ruby clients via baml_client. Covers Schema-Aligned Parsing to recover malformed/partial JSON, @description and @alias prompt hints, client<llm> provider blocks with fallback and retry policies, streaming partial types, test blocks, and BAML-vs-Instructor tradeoffs."
license: MIT
---

# BAML Structured Output

BAML (BoundaryML) is a domain-specific language for defining LLM functions with real types, then generating idiomatic client code for your app. This skill helps you author `.baml` files, run the generator, and rely on Schema-Aligned Parsing (SAP) so model outputs deserialize into your types even when the raw response is imperfect JSON. It's for engineers who are tired of brittle `json.loads` + retry loops and `try/except` wrappers around extraction, classification, and agent-tool prompts.

## When to use it

- You need an LLM call to return a structured object (extraction, classification, entity tagging, routing) and want a typed result instead of a dict.
- You're hitting parse failures on models that emit markdown fences, trailing commas, unquoted keys, or partial JSON during streaming.
- You want one prompt definition consumed across Python, TypeScript, Go, and Ruby services.
- You want to iterate on prompts with live token/output preview before wiring them into code.

## How it works

You write functions in the BAML DSL — `class` for objects, `enum` for closed sets, and union types (`int | string`, `Cat | Dog`) for variants. A `function` block declares input args, a return type, the `client` (model), and a `prompt` using the `{{ ctx.output_format }}` and `{{ _.role("user") }}` helpers. Run `baml-cli generate` (or let the VSCode extension regenerate on save) to emit a `baml_client` package with fully typed wrappers. At call time, BAML's **Schema-Aligned Parsing** coerces the model's text toward your declared schema rather than demanding strict JSON, so it recovers fenced blocks, fixes minor syntax, and fills partials during streaming. Annotate fields with `@description` to steer the model and `@alias` to map prompt-facing names to your code names.

## Usage example

```rust
// baml_src/resume.baml
class Resume {
  name string
  skills string[] @description("technical skills only")
  years_experience int @alias("yoe")
}

function ExtractResume(text: string) -> Resume {
  client "openai/gpt-4o"
  prompt #"
    Extract the resume.
    {{ ctx.output_format }}
    {{ _.role("user") }} {{ text }}
  "#
}
```

```python
# after: baml-cli generate
from baml_client import b
resume = b.ExtractResume(raw_text)   # typed Resume, parsed via SAP
print(resume.skills, resume.years_experience)
```

## Tips & gotchas

- Don't hand-edit `baml_client/` — it's regenerated; change the `.baml` source instead.
- Use `b.stream.ExtractResume(...)` for partial typed objects as tokens arrive; SAP keeps each partial valid.
- Prefer `enum` over free `string` for categories — the model is constrained and parsing is more reliable.
- Add `test` blocks in `.baml` and run them from the VSCode playground to catch prompt regressions early.

## FAQ

**Does this force JSON mode or function-calling?** No. SAP parses ordinary text output, so it works on models without native structured-output APIs.

**Can I use my own model/proxy?** Yes — define a `client<llm>` with the provider and `base_url`, including Anthropic, OpenAI, Ollama, or any OpenAI-compatible endpoint.

**What happens if the output truly can't match?** BAML raises a typed parsing error rather than returning a malformed object, so failures are explicit.
