Nola vs BAML
What BAML is
Section titled “What BAML is”BAML is a language for defining LLM functions in .baml files. You declare inputs, output types, and prompts, then generate a client for your application. The client parses responses into the declared types. The ctx.output_format macro adds output-format instructions to a prompt. See BAML’s prompting guide.
Both BAML and Nola return typed values. The main difference in this example is where the output type lives: a BAML class with a generated client, or a TypeScript type used by a Nola extractor.
The same task, side by side
Section titled “The same task, side by side”Both examples classify a support message into a category and order IDs, then write a reply using that category. These snippets focus on the two calls; provider credentials and project setup are omitted.
BAML: define two functions in .baml, configure a TypeScript generator, and call 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: define the type and both asks in a .tsi module.
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”- Clients in several languages. A BAML definition can generate clients for TypeScript, Python, Ruby, and other targets. This is useful when services in different languages share prompts and types. See the generator reference.
- Prompt testing. BAML’s VS Code extension provides a playground and runs
testblocks alongside function definitions. See Testing functions.
Where Nola is stronger
Section titled “Where Nola is stronger”- Reusing TypeScript types. Nola derives the schema from
Triageduring compilation. If the domain types already belong to a TypeScript application, they can remain there. BAML’s generated types can instead serve as the application’s source of truth. - Shared context. Marking
.messagemakes it available to both asks. In the BAML example, the TypeScript caller passes the message to each function explicitly. - Keeping steps together. Nola lets you put asks, branches, loops, and ordinary function calls in the same
.tsifunction and check them withnola check. BAML callers can also use TypeScript control flow around generated functions.
Schema derivation happens at compile time; validation of a model response happens at runtime. Neither approach makes the meaning of a model’s answer a compile-time guarantee.
When to pick which
Section titled “When to pick which”BAML fits a project that needs clients in several languages or prefers a dedicated prompt language and testing workflow.
Nola fits a TypeScript project that wants to use its existing types for extraction and keep model calls next to application logic. It requires adopting .tsi files and the Nola toolchain.
Next: Nola vs LangGraph