baml
BAMLは、静的に型付けされた表現指向の言語です — snake_caseメソッド、name: type,フィールド、enum、インターフェース、ジェネリクス、クロージャ、オプショナルチェイニング、${...}補間のあるバックティック文字列、任意の値に対する.to_string()、実際の標準ライブラリを備えたTypeScriptのようなものです。そして、LLM呼び出しのための宣言型DSL(function … { client: prompt: }、test)があり、それはそれに脱糖化されるため、モデルの構造化出力は単なる型付き戻り値になります。
CLIがドキュメントです。baml describeで発見してください:
brew install baml # CLIバイナリ: `baml`
baml init # 新規プロジェクト (baml.toml + baml_src/)
baml help <command> # CLIオプションと例
baml describe baml.json # 任意のモジュール/型/メソッド/シグネチャ/キーワードのリファレンス
baml describe Array --budget 120 # (Array, String, Map, assert, match, patterns, spawn, python, ...)
# 最後に「N more lines」と表示されたら `--budget <N>` で再実行
baml check # プロジェクトのコンパイルチェック
baml run -e 'expr' # 式の評価; 高速フィードバック + 構文チェック
baml test --list && baml test # すべてのtest/testsetブロックを実行
baml fmt baml_src/main.baml # プロジェクトのフォーマットを正規化
baml describe <name> は標準ライブラリ関数の完全なソース本文を出力します — 動作を確認する最速の方法であり、assertのような組み込み関数(ディスク上のファイルなし)にアクセスする唯一の方法です。純粋関数はテスト/クライアントを必要としません — baml run -e 'add(2, 3)' でチェックできます。
すでに以下でデモされているAPIを、エラーが発生しない限り説明しないでください。例に基づいて開始し、さらにエラーが発生した場合や実際の標準ライブラリの詳細が必要な場合に使用してください。
基本的にはJavaScript/TypeScriptのように動作し、非常に似た構文を持ちますが、BAMLはより健全で厳格です。
ベストプラクティスと情報
- LLM関数 = 型付き戻り値。 戻り値の型がモデルが生成しなければならないスキーマです(
class、enum、リテラル共用体、string[]、T?)。構造化出力は単なる型付き値です — 通常のコードに渡してください。 - プロンプトは
${...}補間のあるバックティック文字列です。prompt:… ${arg} …と書き、構造化戻り値には常に${ctx.output_format()}を注入してください。\/\${でエスケープし、追加のバックティックでネストします。 - クライアントは値であり、設定ブロックではありません。
client Fast = openai.ResponsesClient.new(model = "…", api_key = env.OPENAI_API_KEY);— 古いclient<llm> Name { provider: …, options: {…} }ブロックは削除されました。ai.Clientを実装するものはすべて動作します(openai.ResponsesClient、openai.ChatClient、anthropic.Client、…; コンストラクタパラメータはプロバイダによって異なります)。api_keyとbase_urlはai.Credentialを受け入れます: リテラル文字列、プロバイダデフォルトにはnull、またはリクエスト時に解決される遅延バインディングのenv.NAME参照。ラッピングによって信頼性を構成します:ai.clients.Retry.new(inner = c, max_attempts = 3)とai.clients.RoundRobin.new(members = […])は.newを持ちますが、ai.clients.Fallback { members: […] }はそうではありません — クラスリテラルとして構築してください。次に、関数内でclient: Fastを使用するか、ショートハンドのclient: "openai/gpt-4o-mini"を使用します。baml describe openai/baml describe ai.Credential/baml describe ai.clients。 - フィールド属性でスキーマを整形します。
@description("…")は${ctx.output_format()}でモデルが見る///ヒントを追加します;@alias("name")は出力されるJSONキーの名前を変更します。連鎖:tags: string[] @alias("labels") @description("…")。 - 純粋なコードをテストし、モデルはテストしないでください。 リテラルデータを使用してオーケストレーション/後処理をユニットテストするには
assert.*を使用します。test内でライブクライアントを使用してLLM関数を呼び出すと、実際のリクエストが行われます。プロンプト、リクエスト、および解析をオフラインでテストするには、F@spec(args)またはスクリプト化されたクライアントを使用します(AI関数の実行を参照)。 - 強制変換ではなく、補間で文字列を構築します。
score=${n}は任意の値を文字列化します(暗黙の.to_string()); 文字列単体が必要な場合は.to_string()を呼び出します。+は両側がすでに文字列である必要があります("n=" + 5はコンパイルされません)。 - 特定のエラーには
catch、すべてのエラーにはcatch_all。expr catch (e) { baml.errors.ParseError => fallback }は特定のエラーを処理します;expr catch_all (e) { _ => fallback }は網羅的です — ワークフローのトップ/エントリポイント用。エラーは暗黙的に伝播します; 呼び出し元は再宣言する必要はありません。発生させるにはthrow baml.errors.InvalidArgument { message: "…" }を使用します(エラータイプは組み込みのbaml.errors.*クラスです —InvalidArgument/ParseError/Io/Timeout/…;baml describe baml.errors); 失敗する可能性のあるシグネチャには-> T throws ErrTypeで注釈を付けます。通常の制御フローでは、スローよりも型付きの結果共用体(type R = Ok | Err)を優先します。 - インターフェース = 共有動作 + 動的ディスパッチ。
interface I { function m(self) -> T throws never }; 各メソッドは自身のthrows句を宣言します。本体を持つメソッドはデフォルト実装であり、実装者はそれをオーバーライドできます。クラスはimplements I { … }を介してオプトインします;I(またはI[])と型付けされた値は、実行時に実装者にディスパッチします。インターフェースは関連型とジェネリック境界も宣言できます。baml describe interfaces。 requires、継承ではありません。 BAMLには継承がありません: クラスはクラスを拡張できません。interface Pet requires Animal, Named { … }は、それらのインターフェースをPetの前提条件にします。Petのデフォルト本体はそれらのメソッドを呼び出すことができ、Petと型付けされた値はそれらを公開します。すべてのPet実装者は、独自のimplements Animalおよびimplements Namedブロックも記述します(そうしないとE0125)。requiresのダイヤモンドはコンパイルされ、共有インターフェースは1回実装されます; 循環はエラーです。1つのクラス上の2つのインターフェースが同じメソッド名を宣言する場合、修飾されていない呼び出しはエラーです(E0121); 代わりにobj.as<I>.m()を呼び出します。- パターンマッチング。
match (v) { … }は値/型に対して; アームはpattern => expr— リテラル、let x: T(バインド+絞り込み)、クラス分解T { f: let y }、またはパターンA | B、ガード… if cond、_; 網羅的でなければなりません。また、v is T→ bool(絞り込み)およびif let x: T = v { … } else { … }もあります。baml describe patterns。 - 絞り込み — ローカルのみ。
v is T、v != null/v == null、および真偽テストは、vが ローカル(letまたはパラメータ)であり、クロージャでキャプチャされていない場合にのみvを絞り込みます: ガードされたif/whileブランチ内、早期終了後(if (!(v is T)) { return … })、および&&(左辺が真)と||(左辺が偽)の右辺 —v is T && v.f == 1、s == null || s.length() == 0。絞り込まれない: キャプチャされたローカル、フィールドパス(h.v is T && h.v.f→E0007)、およびインデックスパス(xs[0] is T)。タスクはステートメント間でインターリーブできるため、別のタスクまたはエイリアスがテストと読み取りの間にフィールドを変更する可能性があります。フィールド書き込みの場合は、最初にlet v = h.v;とするか、if let v: T = h.v { … }、またはmatch (h.v) { let v: T => … }(それぞれフィールドを1回読み取ります)を使用します。ローカルへの代入は、その型でファクトを置き換えます。baml describe is。 - 並行性 = グリーンスレッド。
spawn { … }はFutureを返します;awaitで収集します。baml.future.all/all_settled/race/anyで多数を組み合わせます。all_settledは各入力に対してSuccess<T>、Failure<E>、またはPanickedを返します; 入力のキャンセルはPanicked結果です。with句で spawn を構成します:spawn with limit, token { … }— リストされた各値は、spawn が構築する計画を左から右に変換するbaml.spawn.Modifierです。baml.spawn.Limit.new(n)は並行性を制限します(超過した spawn はキューに入ります)、baml.spawn.CancelTokenは協調的にキャンセルします、baml.spawn.Root.new()はタスクをその spawner のキャンセルから切り離します。baml describe spawn/baml describe baml.future。 - リソース安全性 —
defer、cleanup、catch (e, ctx)。defer { … }はスコープ終了時にブロックを実行します。LIFO、すべてのパス(return / throw / fall-through)で — Goのように。function cleanup(self) -> voidという名前のクラスメソッドはファイナライザです: 呼び出し、defer、またはGCが回収するかどうかにかかわらず、インスタンスごとに最大1回実行されます。catch (e, ctx)はエラーとともにErrorContextをバインドします — 処理中にスローされたエラーはそれに連鎖するため、ctx.root_cause()/ctx.causeは元の障害までさかのぼり、ctx.to_string()はチェーン全体をレンダリングします(Python__context__スタイル)。while let PATTERN = expr { … }はパターンが失敗するまでループします(例:T?を返す.pop()のドレイン)。 - Python / TS から BAML を呼び出す。
baml.tomlで[generator.<name>]を宣言し、baml generateを実行してから、型付けされたbaml_sdkをインポートします。インストール+使用法:baml describe python/baml describe typescript/baml describe baml_sdk。 - インデックス付けよりも安全なアクセス。 添え字は、欠落しているインデックス/キーでパニックを起こします;
.at(i)/.get(k)(→T?)を使用し、?.で辿り、??でデフォルト値を指定します(括弧で囲みます:(m.get(k) ?? 0) + 1)。 - Stdlibメソッドは snake_case で、値に対して呼び出します。 新しいものを返すもの、その場で変更するもの、両方を行うものもあります(
sort_by_keyはレシーバをソートし、それを返します)— ドキュメントを読むにはbaml describe <word/type/identifier/keyword/etc>。 - クラスフィールド
name: type,;Type { field: val }で構築。 メソッドは裸のselfを取ります; ファクトリは自由関数です。フィールドは変更可能です(TSのように):obj.field = vとobj.field += nが機能し、selfメソッドはその場で変更できます — 副作用メソッドはvoidを返します。クラスは参照型です:find/at(i)/添え字はライブエイリアスを返し、コピーではないため、結果を変更すると配列内のその要素が変更されます(xs.find(p)?.n += 1はxsを更新します)、また関数に渡されたクラスは呼び出し先によって変更される可能性があります。構造体更新スプレッドがサポートされています:User { ...u, tier: Tier.Free }。空のクラスは有効です(class Marker {})— 共用体バリアントとして便利です。Enumはプレーンなバリアントです — メソッドも関連データもありません(E.A.foo()はコンパイルされません); 動作はmatchする自由関数に配置します。enum E { A, B }、E.Aでアクセス。 - ブロックは式です — 最後の式が値です(
;なし); 早期終了にはreturn x;を使用します。副作用のみの関数はvoidを返します — これはnull、ユニット型の別のスペルです — そして、最終式を省略するか、return;とすることができます。そのような本体の最終式として値が残されている場合はエラーになり、破棄されません:;で終了します(xs.push(x);—pushは新しい長さを返します)。for (let x in xs)は VALUES を反復します;while (cond) { … }はループします。クロージャ(x) -> { ... }はコンテキストからパラメータ/戻り値を推論します(あいまいな場合のみ(x: T) -> Rで注釈;->は必須です)。.map/.filterは配列を直接返します(.collect()は不要)。空のマップには型が必要です:let m: map<string, int> = {};。 - 三項演算子なし —
if/elseが式です。cond ? a : bはありません;if (cond) { a } else { b }は値を返す式であるため、直接代入します:let label = if (x > 3) { "big" } else { "small" };。各ブランチはブロックであり、その最後の式が値です(returnは不要)。else ifで連鎖し、if let PATTERN = expr { … } else { … }と組み合わせてバインド&絞り込みを行います。 - 条件は真偽値を使用します。
if、while、マッチガード、&&、||、および!は任意の値を受け入れます。偽の値はfalse、null、数値ゼロ、空文字列、空の配列/マップ、空のバイトです; その他はすべて真です。&&と||は依然としてboolを返し、オペランド値ではありません; ショートサーキットします。真のオプショナルローカルは、採用されたブランチ内および&&の右辺で絞り込みます(s && s.length() > 0)。 - 配列は JS ライクなメソッドセットを持ちます —
map/filter/filter_map/reduce/find/some/every/flat_map/slice/concat/join/includes/length()、さらにインプレースのpush/pop/shift/unshift/sort_by/sort_by_key。ほとんどのクロージャはthrows可能です。baml describe Arrayで詳細が得られます。 - ローカル let バインディングは再代入可能です(x = x + 1)— mut キーワードはありません(RustではなくTSのletです); const もありません。
- 引数: デフォルトは
=、キーワード呼び出しは=(決して:ではない)。 シグネチャでデフォルトを宣言します:function f(a: int, b: int = 10);f(1)またはf(1, b = 2)で呼び出します。デフォルト値のあるパラメータは名前で渡す必要があります —f(1, 2)はエラーです(defaulted parameter 'b' must be passed by name)。任意のパラメータ(必須であっても)は名前で渡すことができ(f(a = 1, b = 2))、中間のデフォルトをスキップして後続のパラメータを設定できます(f(1, c = 9))。キーワード構文はname = valueです;name: valueは解析されません(:は型/フィールド用です)。T?は引数をオプショナルにしません — TSb?: Tとは異なり、b: T?パラメータは依然として必須です(nullを渡さなければなりません。そうしないとexpected N argument(s), got …); 省略可能にするには= nullを追加します。組み込み関数もこれに従います:baml.http.fetch(url, timeout = baml.time.Duration.from_seconds(10))。 - TS との違い(静かな落とし穴): 算術演算は型駆動型であり、TSスタイルではありません。
int / intは切り捨て整数除算です(285 / 100 == 2、2.85ではありません)、%は剰余です(285 % 100 == 85); これは問題なくコンパイルされ、単に静かに間違った数値を与えるため、最も価値のある落とし穴です。浮動小数点数を混在させると浮動小数点除算になります(285 / 100.0 == 2.85、285.0 / 100 == 2.85); 混合されたint/float演算はfloatに昇格します(5 + 2.0 == 7.0)。.to_float()はありません —n * 1.0で int を変換します(または浮動小数点数で除算します)。intの結果は代入時にfloatに自動変換されません(let x: float = 285 / 100はコンパイルエラーです)。+は数値のみ: 文字列の連結は両側がすでにstringである必要があります("n=" + 5はコンパイルされません —${...}補間を使用します)。比較(==、<、…、構造的==)および&&/||/!は TS ライクです。 - テスト: 単独の
test "name" { ... }(ラッパーなし);testsetはグループ化のみ。アサーション(6つだけ):assert.equal/approx_equal/is_true/is_type/not_null/contains。assert.equalは構造的に比較します(クラス/配列/マップ全体で深く)— そしてプレーンな==も同様で、bool 形式です。assert.equalは浮動小数点数に対して正確です; 計算されたものにはassert.approx_equal(actual, expected, eps)を使用します。最後のアサーション: 末尾の;は不要。正規IDは、トップレベルのテストではroot::TestName、testset 内ではroot::Testset::TestNameです。baml test root::Testset::TestNameで1つ実行します。 - ファイルは絶対的または現在のファイルを基準として解決されます (./ または ../ なし) —
source("other.baml")は、compileディレクトリを基準とした相対パス get で、source("../../other.baml")も同様ですが、現在のファイルを基準とした解決はサポートされていません。compileディレクトリは、ルート構成ファイル (baml.toml、baml.json) が存在するディレクトリです。 class Maybe { inner: int? }— 空のフィールドは null として読み戻されます。 これは型の安全性に影響します —m.innerはint?として解決され、絞り込みまたは??が必要です。記述 JSON では"inner": nullのようになります。@default(value)または@default_from_context属性を使用して、欠落しているフィールドにデフォルト値を適用します。forとbreak:for (let x in xs)は値を反復します (for (let i in 10)はrangeを生成しません — 代わりにfor (let x in xs)または明示的なwhile/rangeループを使用します)。breakはループを終了し、オプションで値を返します (break val)。continueは次の反復にスキップします。typeエイリアス:type UserID = int;は新しい型を作成せず、単に同義語です (UserIDとintは交換可能)。type R = Ok | Err(判別共用体) はパターンマッチングに便利です。- カスタム
baml.json— スキーマ定義 (ドット区切り): BAML 型/クラス/enum/関数/テストが他のツールにどのように公開されるかを定義します (format:baml.JsonFormat.Json/JsonWithSchema/JsonWithDescription/Baml; デフォルトはJson)。type User = baml.Class<"User", json { format: Baml }>。 .bamlファイル内のbaml describeはインラインで解決され、ファイルに書き込まれません。- BAML スキーマはフォルダ構造を必要としません — フラットな
baml_src/で十分です。
AI 関数の実行とクライアントの設定
LLM 呼び出しは function ブロック内で定義されます。最も単純な形式は次のとおりです:
function ClassifyIntent(input: string) -> Intent {
client: "openai/gpt-4o-mini"
prompt: #"
Classify the following input into one of the intents.
Input: {{ input }}
{{ ctx.output_format() }}
"#
}
または、特定のクライアントインスタンスを使用します:
client FastModel = openai.ResponsesClient.new(
model = "gpt-4o-mini",
api_key = env.OPENAI_API_KEY
)
function Summarize(text: string) -> Summary {
client: FastModel
prompt: #"Summarize: {{ text }}
{{ ctx.output_format() }}"#
}
利用可能なクライアント (AI SDK)
- openai.ResponsesClient — OpenAI Responses API
- openai.ChatClient — OpenAI Chat Completions API
- anthropic.Client — Anthropic API
- google_ai.Client — Google AI (Gemini)
- aws.client — AWS Bedrock
クライアントの構成
// リトライラッパー
client Reliable = ai.clients.Retry.new(
inner = FastModel,
max_attempts = 3
)
// ラウンドロビン (複数モデル)
client Balanced = ai.clients.RoundRobin.new(
members = [
openai.ResponsesClient.new(model = "gpt-4o-mini"),
anthropic.Client.new(model = "claude-3-haiku-20240307")
]
)
// フォールバック (クラスリテラル)
client Fallback = ai.clients.Fallback {
members: [FastModel, SlowModel]
}
BAML スキーマの定義
class User {
id: int
name: string
email: string?
tags: string[] @alias("labels") @description("User-assigned tags")
}
enum Tier {
Free
Pro
Enterprise
}
enum Intent {
Purchase
Support
General
}
BAML コードのテスト
test "test_classify_purchase" {
functions [ClassifyIntent]
args {
input := "I want to buy a subscription"
}
}
testset "intent_tests" {
test "support_query" {
functions [ClassifyIntent]
args { input := "How do I reset my password?" }
}
test "general_chat" {
functions [ClassifyIntent]
args { input := "Tell me a joke" }
}
}
インストールとコード生成
# CLI のインストール
brew install baml
# pip (Python SDK)
pip install baml
# npm (TypeScript SDK)
npm install @boundaryml/baml
baml.toml:
[generator.python]
output = "baml_sdk"
[generator.typescript]
output = "baml_sdk"
生成:
baml generate
さらなる情報
- すべてのコマンドとフラグのドキュメントを表示:
baml describe baml.json - 特定の標準ライブラリ型のドキュメントを表示:
baml describe Array、baml describe String、baml describe Map - スキーマ定義の詳細:
baml describe baml.Class、baml describe baml.JsonFormat - エラーハンドリング:
baml describe baml.errors - パターンマッチング:
baml describe patterns - スポーンと並行性:
baml describe spawn、baml describe baml.future