baml
BAML은 정적 타입, 표현 지향 언어입니다 — TypeScript에 snake_case 메서드, name: type, 필드, 열거형, 인터페이스, 제네릭, 클로저, 선택적 체이닝, ${...} 보간이 가능한 백틱 문자열, 모든 값에 대한 .to_string(), 실제 표준 라이브러리를 더한 것입니다. 그리고 LLM 호출을 위한 선언적 DSL(function … { client: prompt: }, test)을 포함하며, 이는 언어 내부 구조로 디슈거(desugar)되어 모델의 구조화된 출력이 단순히 타입이 있는 반환값이 됩니다.
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의 다이아몬드는 컴파일되며, 공유 인터페이스는 한 번 구현됩니다; 순환은 오류입니다. 하나의 클래스에 두 인터페이스가 동일한 메서드 이름을 선언하면, 정규화되지 않은 호출은 오류입니다(E0121); 대신obj.as<I>.m()을 호출하세요.- 패턴 매칭.
match (v) { … }는 값/타입에 대해 사용합니다; arm은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 => … }(각각 필드를 한 번 읽음)를 사용하세요. 로컬에 할당하면 해당 타입으로 팩트가 대체됩니다.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가 회수하든 인스턴스당 최대 한 번 실행됩니다.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 {}) — 유니온 변형으로 유용합니다. 열거형은 일반 변형입니다 — 메서드나 연관 데이터 없음(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, 숫자 0, 빈 문자열, 빈 배열/맵, 빈 바이트입니다; 그 외 모든 것은 참입니다.&&와||는 여전히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 키워드 없음(TS let, Rust 아님); const도 없습니다.
- 인자:
=로 기본값,=로 키워드 호출(:절대 사용 안 함). 시그니처에서 기본값을 선언하세요:function f(a: int, b: int = 10);f(1)또는f(1, b = 2)로 호출하세요. 기본값 매개변수는 이름으로 전달해야 합니다 —f(1, 2)는 오류입니다('b' 기본값 매개변수는 이름으로 전달해야 함). 모든 매개변수(필수도 포함)는 이름으로 전달할 수 있으며(f(a = 1, b = 2)), 중간 기본값을 건너뛰고 이후 값을 설정할 수 있습니다(f(1, c = 9)). 키워드 구문은name = value입니다;name: value는 파싱되지 않습니다(:는 타입/필드용).T?는 인수를 선택적으로 만들지 않습니다 — TSb?: T와 달리,b: T?매개변수는 여전히 필수입니다(null을 전달해야 하며, 그렇지 않으면N 인수가 필요, …개 받음오류 발생); 생략 가능하게 하려면= null을 추가하세요. 내장 함수는 다음을 따릅니다:baml.http.fetch(url, timeout = baml.time.Duration.from_seconds(10)). - TS와 다른 점(조용한 함정): 산술은 타입 중심이며 TS 스타일이 아닙니다.
int / int는 잘린 정수 나눗셈입니다(285 / 100 == 2,2.85가 아님) 그리고%는 나머지입니다(285 % 100 == 85); 이것은 잘 컴파일되고 조용히 잘못된 숫자를 제공하므로 가장 가치 있는 함정입니다. float를 혼합하여 float 나눗셈을 얻으세요(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로 나누기)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은 float에 대해 정확합니다; 계산된 float에는assert.approx_equal(actual, expected, eps)를 사용하세요. 마지막 단언: 뒤에;없음. 표준 ID는 최상위 테스트의 경우root::TestName, testset 내부 테스트의 경우root::Testset::TestName입니다. 하나 실행:baml test.
AI 함수 실행
LLM 함수는 baml run -e 'myFunction(args)'로 평가됩니다(라이브 클라이언트 필요). 오프라인 테스트의 경우 F@spec(args)를 사용하여 client: spec 함수를 호출하세요 — 실제 요청을 보내지 않고 프롬프트 렌더링 및 출력 파싱을 테스트합니다. baml describe spec.
내장 함수 선택
assert.equal,approx_equal,is_true,is_type,not_null,containsbaml.http.fetch(url, …),baml.time,baml.json,baml.errors.*- 표준 라이브러리 탐색:
baml describe
요약: BAML은 구조화된 LLM 출력을 위한 언어입니다. TS 같은 구문에, first-class LLM 함수와 철저한 타입 시스템을 갖추었습니다. baml describe가 당신의 안내자입니다.