Nola vs BAML
What BAML is
Section titled “What BAML is”BAML is a domain-specific language for LLM functions: you declare classes and functions with typed inputs and outputs in .baml files, write the prompt next to the signature, and baml-cli generate emits a client (TypeScript, Python, Ruby and others) whose calls return parsed, validated values. The return type renders the output format into the prompt (ctx.output_format), and a VS Code extension with a playground and test runner comes with it.
The same task, side by side
Section titled “The same task, side by side”Classify a support message into a category plus order ids, then write a reply that depends on the category.
BAML — two files: the .baml source and the TypeScript that calls the generated client.
class Triage { category "billing" | "refund" | "fraud" | "other" orderIds string[]}
function ClassifyMessage(message: string) -> Triage { client "openai/gpt-5-mini" prompt #" {{ _.role("system") }} You are a support classifier. {{ ctx.output_format }} {{ _.role("user") }} {{ message }} "#}
function WriteReply(category: string, message: string) -> string { client "openai/gpt-5-mini" prompt #" {{ _.role("system") }} You are a support agent. {{ _.role("user") }} Reply to this {{ category }} case: {{ message }} "#}// not-checked — triage.ts, after `npx baml-cli generate`import { b } from "./baml_client";
const triage = await b.ClassifyMessage(message);const reply = await b.WriteReply(triage.category, message);Nola — one file, no generated client:
export type Triage = { category: "billing" | "refund" | "fraud" | "other"; orderIds: string[];};
export infer function classifyMessage(.message: string) { const triage = ask ..`triage the customer message`<Triage>; const reply = ask ..`a reply for a ${triage.category} case`<string>; return { ...triage, reply };}Where BAML is stronger
Section titled “Where BAML is stronger”- Multi-language clients. One
.bamldefinition serves TypeScript, Python, Ruby and more; Nola is TypeScript only. - Tooling around the prompt. The playground, the built-in
testblocks and the mature structured-output parser are a complete workflow for iterating on prompts. - Maturity. BAML is established and widely used; Nola is 0.1.x.
Where Nola is stronger
Section titled “Where Nola is stronger”- No second language, no codegen step.
Triageis the TypeScript type you already have — usable from plain TS, refactorable with the editor, nogeneratebetween an edit and its effect. In BAML,Triageis a BAML class that TypeScript sees only afterbaml-cli generate. - Context belongs to the function. Both asks in
classifyMessagesee.message; the reply is chained with a${}. In BAML each function is one prompt-to-parse round trip, so the follow-up is a second function you hand the category and the message to again. - Plain TypeScript orchestration. Loops, branches,
await, call intents over your own functions — all in the same file, type-checked together bynola check.
When to pick which
Section titled “When to pick which”- You need clients in several languages, or a prompt playground with test cases, today → BAML.
- Your stack is TypeScript and you want the typed call layer to be part of the language, with the editor seeing every ask → Nola.
- Both are honest about one thing: the output type is the contract, and the prompt lives next to it.
Next: Nola vs LangGraph