BAML スキル

BAML スクリプト作成のためのミニマルなスキル。静的に型付けされた表現指向言語で、ファーストクラスのLLM関数、TypeScript風の構文、snake_caseメソッドを備え、AIワークフロー、エージェント、評価の構築に適しています。

BAML スキル とは?

このスキルは、AIエージェントのワークフローと構造化出力の構築に特化した静的に型付けされた言語であるBAMLの包括的なリファレンスを提供します。BAMLはTypeScriptに似た構文とsnake_caseメソッドを持ちながら、LLM呼び出しのための宣言型DSLを導入し、モデルの構造化出力を単なる型付き戻り値として扱えるようにします。主な特徴としては、型安全なLLM関数定義、バックティック文字列と`${...}`を用いたプロンプト補間、インターフェースとパターンマッチングによる洗練された制御フロー、そして`baml describe`コマンドによる完全なCLIベースのドキュメンテーションがあります。このスキルは、AIエージェントとワークフローを迅速にプロトタイピングし構築したい開発者向けに設計されており、Python(`pip install baml`)とTypeScript/JavaScript(`npm install @boundaryml/baml`)の両方で利用可能で、Structured OutputやJSONモードをネイティブでサポートする主要なLLMプロバイダーとシームレスに統合できます。

対応✓Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/xyzcharlize/hyperframes-genie/tree/HEAD/.agents/skills/baml-core

Installed? Explore more 生産性&コラボレーション skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

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? は引数をオプショナルにしません — TS b?: 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

関連スキル