Skip to content

Nola vs BAML

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.

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.

baml_src/triage.baml
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:

classify.tsi
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 };
}
  • Multi-language clients. One .baml definition serves TypeScript, Python, Ruby and more; Nola is TypeScript only.
  • Tooling around the prompt. The playground, the built-in test blocks 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.
  • No second language, no codegen step. Triage is the TypeScript type you already have — usable from plain TS, refactorable with the editor, no generate between an edit and its effect. In BAML, Triage is a BAML class that TypeScript sees only after baml-cli generate.
  • Context belongs to the function. Both asks in classifyMessage see .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 by nola check.
  • 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