Community生產力與協作github.com

xyzcharlize/hyperframes-genie

極簡 BAML 技能。BAML 是一種靜態型別、表達式導向的語言,擁有 TypeScript 風格的 snake_case 方法,適合建構 AI 工作流程、代理和評估。

hyperframes-genie 是什麼?

這個技能提供 BAML 語言的完整支援,一種靜態型別、表達式導向的程式語言,專為結構化 LLM 輸出與 AI 工作流程設計。BAML 結合 TypeScript-like 語法與 snake_case 方法,以及用於 LLM 呼叫的宣告式 DSL,使得模型回傳的結構化輸出可直接作為型別值使用。此技能支援多種平台,包括 Claude Code、Cursor、Codex 等,並透過 CLI 工具 `baml` 提供完整的開發體驗,例如專案初始化、型別檢查、表達式求值與格式化。它涵蓋了從基本的類型系統(類別、列舉、介面、泛型)到進階功能(綠色執行緒並發、資源安全、模式匹配、錯誤處理)的所有核心語法與標準庫,並內建對 Python 和 TypeScript 生成 SDK 的支援。開發者可以透過 `baml describe` 指令探索任何模組、類型或方法的完整原始碼,快速驗證行為,是建構可靠 AI 代理和評估管線的強大基礎。

相容平台✓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 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

baml

BAML 是一種靜態型別、表達式導向的語言——TypeScript 擁有 snake_case 方法、name: type, 欄位、列舉、介面、泛型、閉包、可選鏈、帶 ${...} 插值的反引號字串、所有值上的 .to_string() 方法、以及真正的標準庫。此外,它還包含一個用於 LLM 呼叫的宣告式 DSL(function … { client: prompt: }、test),該 DSL 會脫糖轉換為 BAML 本身,因此模型的結構化輸出只是一個型別化的回傳值。

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 行」?重新執行並加上 `--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: […] } 沒有 .new——將其作為類別字面量建構。然後在函式中使用 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 可以編譯,共享的介面只實作一次;迴圈則會報錯。當一個類別上的兩個介面宣告了同名方法時,未限定的呼叫會報錯(E0121);請改用 obj.as<I>.m() 來呼叫。
  • 模式匹配。 match (v) { … } 用於值 / 型別;分支為 pattern => expr——字面量、let x: T(繫結 + 收窄)、類別解構 T { f: let y }、或模式 A | B、守衛 … if cond、_;必須窮舉。還有 v is T → 布林值(同時收窄)和 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 => … }(每個都只讀取欄位一次)。對區域變數的賦值會用賦予的型別取代該事實。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 { … }——列出的每個值都是一個 baml.spawn.Modifier,它會從左到右轉換 spawn 建構的計畫。baml.spawn.Limit.new(n) 限制並發數(過多的 spawn 會排隊),baml.spawn.CancelToken 協作式取消,baml.spawn.Root.new() 將任務與其產生者的取消機制分離。baml describe spawn / baml describe baml.future。
  • 資源安全——defer、cleanup、catch (e, ctx)。 defer { … } 在作用域退出時執行一個區塊,後進先出,且在所有路徑上(回傳 / 拋出 / 貫穿)都會執行——類似 Go。一個名為 function cleanup(self) -> void 的類別方法是終結器:無論你呼叫它、defer 它、還是 GC 回收它,它每個實例最多執行一次。catch (e, ctx) 將一個 ErrorContext 與錯誤一同繫結——一個在處理另一個錯誤時拋出的錯誤會鏈接到它,因此 ctx.root_cause() / ctx.cause 可以追溯到原始失敗,且 ctx.to_string() 會呈現整個鏈(類似 Python 的 __context__ 風格)。while let PATTERN = expr { … } 會一直迴圈直到模式匹配失敗(例如,耗盡一個回傳 T? 的 .pop())。
  • 從 Python / TypeScript 呼叫 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)。
  • 標準庫方法為 snake_case,在值上呼叫。 有些回傳新值,有些原地修改,少數兩者兼做(sort_by_key 會排序接收者並回傳它)——若要閱讀文件,請執行 baml describe <word/type/identifier/keyword/etc>。
  • 類別欄位 name: type,;使用 Type { field: val } 建構。 方法接受裸 self;工廠是自由函式。欄位是可變的(類似 TypeScript):obj.field = v 和 obj.field += n 有效,且 self 方法可以原地修改——一個有副作用的 method 回傳 void。類別是參考型別:find / at(i) / 下標回傳一個活別名,而非複本,因此修改結果會修改陣列內的該元素(xs.find(p)?.n += 1 會更新 xs),且傳遞給函式的類別可以被呼叫者修改。結構更新展開是受支援的:User { ...u, tier: Tier.Free }。空類別是合法的(class Marker {})——適合作為聯集變體。列舉是純變體——沒有方法,沒有關聯資料(E.A.foo() 無法編譯);將行為放在使用 match 的自由函式中。enum E { A, B },存取 E.A。
  • 區塊是表達式——最後一個表達式是值(無 ;);使用 return x; 進行提前退出。一個僅有副作用的函式回傳 void——null(單位型別)的另一種寫法——且可以省略其最終表達式或 return;。將值留在這種本體的最終表達式位置是錯誤,不會被丟棄:請以 ; 結尾(xs.push(x);——push 回傳新的長度)。for (let x in xs) 迭代值;while (cond) { … } 迴圈。閉包 (x) -> { ... } 從上下文中推斷參數 / 回傳型別(僅在不明確時標註 (x: T) -> R;-> 是必需的)。.map / .filter 直接回傳陣列(無需 .collect())。空 map 需要型別: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、數字零、空字串、空陣列 / map、空位元組;其他所有值都是真值。&& 和 || 仍回傳 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 關鍵字(它是 TypeScript 的 let,而非 Rust);也沒有 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? 並不會讓參數變成可選——與 TypeScript 的 b?: T 不同,一個 b: T? 參數仍然是必需的(你必須傳遞 null,否則會出現 expected N argument(s), got …);加上 = null 讓它變成可省略。內建函式遵循此規則:baml.http.fetch(url, timeout = baml.time.Duration.from_seconds(10))。
  • 與 TypeScript 的差異(無聲陷阱): 算術是型別驅動的,而非 TypeScript 風格。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 結果在賦值時不會自動強制轉型為 float(let x: float = 285 / 100 是編譯錯誤)。+ 僅限數值:字串串接需要兩邊都是 string("n=" + 5 無法編譯——請使用 ${...} 插值)。比較(==、<、……、結構化 ==)和 && / || / ! 類似 TypeScript。
  • 測試: 獨立的 test "name" { ... }(無包裝器);testset 僅用於分組。斷言(僅 6 個):assert.equal / approx_equal / is_true / is_type / not_null / contains。assert.equal 進行結構化比較(深度比較,跨類別 / 陣列 / map)——純 == 也是如此,它是布林形式。assert.equal 對浮點數是精確的;對於計算出的浮點數,請使用 assert.approx_equal(actual, expected, eps)。最後一個斷言:無尾隨 ;。標準 ID 為頂層測試的 root::TestName 和 testset 內測試的 root::Testset::TestName。使用 baml test <id> 執行一個測試,使用 baml test 執行全部。baml test --verbose / baml test --format json。baml describe tests。
  • 載入外部資料(JSON / CSV): let items = baml.open.json("data.json");(→ json_value,一個遞迴型別——存取前先鍵入或 match)。要載入為型別化的類別,使用 let items: T = baml.open.json_as<T>("data.json");(如果型別不匹配則在執行時期恐慌)。CSV:let rows = baml.open.csv("data.csv")(→ map<string, string>[],每列一個 map,標題作為鍵)。baml describe baml.open。
  • 語言伺服器: 在 baml_src/ 中編輯時,在編輯器中執行 baml lsp。baml describe lsp。baml.json 是編譯器的內部格式——直接編輯它(如果你敢)然後執行 baml check。
  • 組態:baml.toml 在根目錄。專案在這裡定義。baml describe baml.toml。

執行 AI 函式

一個 BAML LLM 函式看起來像這樣:

// baml_src/main.baml

function ClassifyIntent(input: string) -> Intent {
  client: "openai/gpt-4o-mini"
  prompt: #"
    Classify the user's intent into one of the following categories:
    - Search
    - Bookmark
    - Navigate
    - Help
    - Other
    User input: {{input}}
    {{ctx.output_format()}}
  "#
}

class Intent {
  category: string
  confidence: float
}

你可以在 BAML 程式碼中建立一個腳本化用戶端進行測試——無需真實 API 金鑰:

test "test intent classification with fake client" {
  // 請勿在生產用戶端上使用 F@——它會繞過真實 LLM
  // 而是使用 F@spec 來測試提示詞產生和解析
  let result = [email protected](input: "find my saved recipes")
  assert.equal(result.category, "Search")
  assert.equal(result.confidence, 0.95)
}

然後從 Python / TypeScript 生成用戶端 SDK:

baml generate
# app.py
from baml_sdk import baml_client

client = baml_client.BamlClient()
result = client.ClassifyIntent(input="go to settings")
print(result.category, result.confidence)

更多資源

  • 使用 baml describe <name> 探索任何型別、函式、關鍵字或標準庫模組
  • 在專案中執行 baml describe baml.toml 查看完整的組態參考
  • 使用 baml check 驗證你的 .baml 檔案
  • 使用 baml run -e '<expr>' 測試表達式
  • 在 baml_src/ 中編輯時使用 baml lsp 啟用編輯器支援

相關技能