Community생산성 & 협업github.com

baml - 정적 타입 LLM 언어

정적 타입, 표현 지향 BAML 언어에 대한 스킬입니다. TypeScript와 유사한 구문에 snake_case 메서드, first-class LLM 함수를 제공하며, AI 워크플로우, 에이전트, 평가(eval) 구축에 유용합니다.

baml - 정적 타입 LLM 언어란 무엇인가요?

BAML은 정적 타입과 표현 지향(expression-oriented) 설계를 채택한 언어로, TypeScript와 유사한 구문에 `snake_case` 메서드, `name: type,` 필드, 열거형(enum), 인터페이스, 제네릭, 클로저, 선택적 체이닝(optional chaining), 백틱 문자열 보간(`${...}`), `.to_string()` 등 실용적인 표준 라이브러리를 제공합니다. 특히 LLM 호출을 위한 선언적 DSL(`function … { client: prompt: }`, `test`)을 언어 자체에 내장하여, 모델의 구조화된 출력이 단순히 타입이 있는 반환값이 되도록 설계되었습니다. CLI 자체가 문서 역할을 하며, `baml describe <name>` 명령어로 모든 표준 라이브러리 함수의 전체 소스 코드를 즉시 확인할 수 있습니다. 이는 임베디드 내장 함수(`assert` 등)에도 동일하게 적용됩니다. BAML은 Claude Code, Cursor, Codex 등 다양한 AI 에이전트 플랫폼에서 사용할 수 있으며, `baml run -e '표현식'`으로 빠른 피드백을 받고 `baml test`로 테스트를 실행할 수 있습니다. 핵심 철학은 '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은 정적 타입, 표현 지향 언어입니다 — 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?는 인수를 선택적으로 만들지 않습니다 — TS b?: 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, contains
  • baml.http.fetch(url, …), baml.time, baml.json, baml.errors.*
  • 표준 라이브러리 탐색: baml describe

요약: BAML은 구조화된 LLM 출력을 위한 언어입니다. TS 같은 구문에, first-class LLM 함수와 철저한 타입 시스템을 갖추었습니다. baml describe가 당신의 안내자입니다.

관련 스킬