BAML - Habilidades de Linguagem BAML

Habilidade mínima para BAML. BAML é uma linguagem estaticamente tipada, orientada a expressões com funções LLM de primeira classe — semelhante a TypeScript, métodos snake_case, etc. Útil para construir workflows de IA, agentes e avaliações.

O que é BAML - Habilidades de Linguagem BAML?

Esta habilidade fornece uma referência abrangente e as melhores práticas para a linguagem BAML, uma linguagem estaticamente tipada e orientada a expressões, projetada para definir e executar chamadas a modelos de linguagem de grande escala (LLMs). A habilidade atua como documentação interativa através do comando `baml describe`, que expõe o corpo fonte completo das funções da stdlib e a sintaxe da linguagem. Ela suporta diversas plataformas, incluindo CLI, e se integra com Python e TypeScript através de SDKs gerados. A habilidade explica como definir funções LLM com tipos de retorno que atuam como esquemas de saída estruturada, utilizando sintaxe de interpolação de strings com crases para prompts, e configurando clientes LLM como valores, com suporte a wrappers de confiabilidade como Retry e RoundRobin. Além disso, cobre tópicos avançados como pattern matching, concorrência com green threads, gerenciamento de recursos com `defer` e `cleanup`, e a diferença crucial entre BAML e TypeScript, como a divisão inteira truncante e a ausência de operador ternário.

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

Installed? Explore more Produtividade e Colaboração skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

baml

BAML é uma linguagem estaticamente tipada e orientada a expressões — TypeScript com métodos snake_case, campos nome: tipo,, enums, interfaces, genéricos, closures, encadeamento opcional, strings com crases e interpolação ${...}, .para_string() em qualquer valor, uma stdlib real. E uma DSL declarativa para chamadas de LLM (function … { client: prompt: }, test) que se dessugara nela, de modo que a saída estruturada de um modelo é apenas um valor de retorno tipado.

A CLI é a documentação. Descubra via baml describe:

brew install baml                # Binário CLI: `baml`

baml init                        # Novo projeto (baml.toml + baml_src/)
baml help <comando>              # Opções e exemplos da CLI
baml describe baml.json          # Referência para qualquer módulo/tipo/método/assinatura/palavra-chave
baml describe Array --budget 120 # (Array, String, Map, assert, match, patterns, spawn, python, ...)
                                 # Termina com "N mais linhas"? execute novamente com `--budget <N>`
baml check                       # Verificação de compilação do projeto
baml run -e 'expr'               # Avalia uma expressão; feedback rápido + verificação de sintaxe
baml test --list && baml test    # Executa todos os blocos de teste/testset
baml fmt baml_src/main.baml      # Canonicaliza a formatação do projeto

baml describe <nome> imprime o corpo fonte completo das funções da stdlib — a maneira mais rápida de verificar o comportamento e o único caminho para builtins embutidos como assert (sem arquivo em disco). Funções puras não precisam de teste/cliente — verifique-as com baml run -e 'add(2, 3)'.

Não descreva APIs já demonstradas abaixo, a menos que encontre alguns erros. Você pode começar com base nos exemplos e usá-lo se encontrar mais erros ou se quiser detalhes reais da stdlib.

Na maior parte, comporta-se como JavaScript/TypeScript, com sintaxe muito semelhante — mas BAML é mais sólido/rigoroso.

Melhores práticas e informações

  • Função LLM = retorno tipado. O TIPO DE RETORNO é o esquema que o modelo deve produzir (class, enum, união literal, string[], T?). Saída estruturada é apenas um valor tipado — entregue-o a código comum.
  • Prompts são strings com crases com interpolação ${...}. Escreva prompt: … ${arg} …, e sempre injete ${ctx.formato_saida()} para um retorno estruturado. Escape com \ / \${; aninhe com crases extras.
  • Clientes são valores, não blocos de configuração. client Rapido = openai.ResponsesClient.new(model = "…", api_key = env.OPENAI_API_KEY); — o bloco antigo client<llm> Nome { provider: …, options: {…} } foi removido. Qualquer coisa que implemente ai.Client funciona (openai.ResponsesClient, openai.ChatClient, anthropic.Client, …; parâmetros do construtor diferem por provedor). api_key e base_url aceitam ai.Credential: uma string literal, null para o padrão do provedor, ou uma referência env.NOME de ligação tardia resolvida no momento da requisição. Compõe confiabilidade encapsulando: ai.clients.Retry.new(inner = c, max_attempts = 3) e ai.clients.RoundRobin.new(members = […]) têm .new, mas ai.clients.Fallback { members: […] } não tem — construa-o como um literal de classe. Em seguida, use client: Rapido na função, ou a abreviação client: "openai/gpt-4o-mini". baml describe openai / baml describe ai.Credential / baml describe ai.clients.
  • Molde o esquema com atributos de campo. @description("…") adiciona uma dica /// que o modelo vê em ${ctx.formato_saida()}; @alias("nome") renomeia a chave JSON emitida. Encadeie: tags: string[] @alias("rotulos") @description("…").
  • Teste o código puro, não o modelo. Teste unitário de orquestração/pós-processamento em dados literais com assert.*. Chamar uma função LLM em um test com um cliente ativo faz uma requisição real. Para testar prompts, requisições e análise offline, use F@spec(args) ou um cliente com script (veja Executando funções de IA).
  • Construa strings com interpolação, não coerção. score=${n} transforma qualquer valor em string (.para_string() implícito); chame .para_string() apenas para a string. + precisa que ambos os lados já sejam strings ("n=" + 5 não compila).
  • catch para alguns, catch_all para todos. expr catch (e) { baml.errors.ParseError => fallback } lida com um erro específico; expr catch_all (e) { _ => fallback } é exaustivo — para um topo de workflow / ponto de entrada. Erros propagam-se implicitamente; os chamadores não precisam redeclarar. Levante com throw baml.errors.InvalidArgument { message: "…" } (os tipos de erro são as classes baml.errors.* embutidas — InvalidArgument/ParseError/Io/Timeout/…; baml describe baml.errors); anote uma assinatura falível com -> T throws ErrType. Prefira uma união de resultado tipada (type R = Ok | Err) em vez de lançar para fluxo de controle comum.
  • Interfaces = comportamento compartilhado + despacho dinâmico. interface I { function m(self) -> T throws never }; cada método declara sua cláusula throws. Um método com corpo é uma implementação padrão, que um implementador pode sobrescrever. Uma classe opta via implements I { … }; um valor tipado como I (ou I[]) despacha para o implementador em tempo de execução. Interfaces também podem declarar tipos associados e limites genéricos. baml describe interfaces.
  • requires, não herança. BAML não tem herança: uma classe não pode estender uma classe. interface Pet requires Animal, Named { … } torna essas interfaces pré-requisitos de Pet. Os corpos padrão de Pet podem chamar seus métodos, e um valor tipado como Pet os expõe. Cada implementador de Pet também escreve seus próprios blocos implements Animal e implements Named (senão E0125). Um diamante de requires compila, e a interface compartilhada é implementada uma vez; um ciclo é um erro. Quando duas interfaces em uma classe declaram o mesmo nome de método, uma chamada não qualificada é um erro (E0121); chame obj.como<I>.m() em vez disso.
  • Correspondência de padrões. match (v) { … } sobre valores/tipos; os braços são padrão => expr — literais, let x: T (vincular + restringir), desestruturação de classe T { f: let y }, ou-padrões A | B, guardas … if cond, _; deve ser exaustivo. Também v is T → bool (restringe) e if let x: T = v { … } else { … }. baml describe patterns.
  • Restrição — apenas locais. v is T, v != null / v == null e um teste de veracidade restringem v apenas quando v é um local (um let ou um parâmetro) que nenhum closure captura: no ramo if / while guardado, após uma saída antecipada (if (!(v is T)) { return … }), e no lado direito de && (lado esquerdo verdadeiro) e || (lado esquerdo falso) — v is T && v.campo == 1, s == null || s.comprimento() == 0. NÃO restringido: um local capturado, um caminho de campo (h.v is T && h.v.campo → E0007) e um caminho de índice (xs[0] is T). Tarefas podem intercalar entre declarações, então outra tarefa ou um alias pode alterar um campo entre o teste e a leitura. Para um campo escreva let v = h.v; primeiro, ou if let v: T = h.v { … }, ou match (h.v) { let v: T => … } (cada um lê o campo uma vez). Uma atribuição ao local substitui o fato pelo tipo atribuído. baml describe is.
  • Concorrência = threads verdes. spawn { … } retorna um Future; await o coleta. Combine muitos com baml.future.all / all_settled / race / any. all_settled retorna Success<T>, Failure<E>, ou Panicked para cada entrada; o cancelamento de entrada é um resultado Panicked. Configure um spawn com uma cláusula with: spawn with limite, token { … } — cada valor listado é um baml.spawn.Modifier que transforma o plano que o spawn constrói, da esquerda para a direita. baml.spawn.Limit.new(n) limita a concorrência (excessos de spawn enfileiram), um baml.spawn.CancelToken cancela cooperativamente, baml.spawn.Root.new() desanexa a tarefa do cancelamento de seu spawner. baml describe spawn / baml describe baml.future.
  • Segurança de recursos — defer, cleanup, catch (e, ctx). defer { … } executa um bloco na saída do escopo, LIFO, em todo caminho (retorno / lançamento / passagem) — como Go. Um método de classe chamado function cleanup(self) -> void é um finalizador: executa no máximo uma vez por instância, seja chamando-o, usando defer, ou o GC o reivindicando. catch (e, ctx) vincula um ErrorContext junto com o erro — um erro lançado ao lidar com outro encadeia-se nele, então ctx.causa_raiz() / ctx.causa percorre de volta até a falha original e ctx.para_string() renderiza toda a cadeia (estilo Python __context__). while let PADRAO = expr { … } itera até que o padrão falhe (por exemplo, drenando um .pop() que retorna T?).
  • Chame BAML de Python / TS. Declare um [generator.<nome>] em baml.toml, execute baml generate, depois importe o baml_sdk tipado. Instalação + uso: baml describe python / baml describe typescript / baml describe baml_sdk.
  • Acesso seguro em vez de indexação. Subscrito entra em pânico em um índice/chave ausente; use .at(i)/.get(k) (→ T?), acesse com ?., padrão com ?? (parentetize: (m.get(k) ?? 0) + 1).
  • Métodos stdlib são snake_case, chamados em um valor. Alguns retornam novos, alguns mutam no local, alguns fazem ambos (sort_by_key classifica o receptor e o retorna) — para ler a documentação, baml describe <palavra/tipo/identificador/palavra-chave/etc>.
  • Campos de classe nome: tipo,; construa Tipo { campo: valor }. Métodos recebem um self simples; fábricas são funções livres. Campos são mutáveis (como TS): obj.campo = v e obj.campo += n funcionam, e um método self pode mutar no local — um método de efeito colateral retorna void. Classes são tipos de referência: find/at(i)/subscrito retornam um alias ativo, não uma cópia, então mutar o resultado muta aquele elemento dentro do array (xs.find(p)?.n += 1 atualiza xs), e uma classe passada para uma função pode ser mutada pelo chamado. Propagação de atualização de struct é suportada: User { ...u, nivel: Nivel.Gratuito }. Classes vazias são legais (class Marcador {}) — útil como variantes de união. Enums são variantes simples — sem métodos, sem dados associados (E.A.foo() não compila); coloque comportamento em funções livres que usam match. enum E { A, B }, acesse E.A.
  • Blocos são expressões — a última expressão é o valor (sem ;); return x; para saída antecipada. Uma função apenas de efeito colateral retorna void — outra grafia de null, o tipo unidade — e pode omitir sua expressão final ou return;. Um valor deixado como a expressão final de tal corpo é um erro, não descartado: termine-o com ; (xs.push(x); — push retorna o novo comprimento). for (let x in xs) itera VALORES; while (cond) { … } itera. Closures (x) -> { ... } inferem parâmetro/retorno do contexto (anote (x: T) -> R apenas quando ambíguo; o -> é obrigatório). .map/.filter retornam arrays diretamente (sem .collect()). Mapa vazio precisa de um tipo: let m: map<string, int> = {};.
  • Sem ternário — if/else é a expressão. Não há cond ? a : b; if (cond) { a } else { b } é uma expressão que retorna um valor, então atribua diretamente: let rotulo = if (x > 3) { "grande" } else { "pequeno" };. Cada ramo é um bloco cuja última expressão é seu valor (sem return). Encadeie com else if, e emparelhe com if let PADRAO = expr { … } else { … } para vincular e restringir.
  • Condições usam veracidade. if, while, guardas de match, &&, ||, e ! aceitam qualquer valor. Valores falsos são false, null, zero numérico, strings vazias, arrays/mapas vazios e bytes vazios; todo o resto é verdadeiro. && e || ainda retornam bool, não um valor de operando; eles fazem curto-circuito. Um local opcional verdadeiro restringe no ramo tomado e no lado direito de && (s && s.comprimento() > 0).
  • Arrays têm um conjunto de métodos semelhante a JS — map/filter/filter_map/reduce/find/some/every/flat_map/slice/concat/join/includes/comprimento(), mais push/pop/shift/unshift/sort_by/sort_by_key no local. A maioria recebe closures que podem usar throws. baml describe Array fornece mais informações.
  • Ligações locais let são reassignáveis (x = x + 1) — sem palavra-chave mut (é TS let, não Rust); também não há const.
  • Args: padrões com =, chamadas de palavra-chave com = (nunca :). Declare um padrão na assinatura: function f(a: int, b: int = 10); chame f(1) ou f(1, b = 2). Um parâmetro com padrão deve ser passado por nome — f(1, 2) é um erro (parâmetro com padrão 'b' deve ser passado por nome). Qualquer parâmetro (mesmo obrigatório) pode ser passado por nome (f(a = 1, b = 2)), e você pode pular um padrão do meio para definir um posterior (f(1, c = 9)). A sintaxe de palavra-chave é nome = valor; nome: valor não será analisado (: é para tipos/campos). T? NÃO torna um argumento opcional — ao contrário de TS b?: T, um parâmetro b: T? ainda é obrigatório (você deve passar null, senão esperava N argumento(s), obteve …); adicione = null para torná-lo omitível. Builtins seguem isso: baml.http.fetch(url, timeout = baml.time.Duration.from_seconds(10)).
  • Onde diverge do TS (as armadilhas silenciosas): aritmética é orientada a tipos, não estilo TS. int / int é divisão inteira truncada (285 / 100 == 2, NÃO 2.85) e % é o resto (285 % 100 == 85); isso compila bem e apenas dá um número silenciosamente errado, então é a pegadinha de maior valor. Misture um float para obter divisão float (285 / 100.0 == 2.85, 285.0 / 100 == 2.85); qualquer operação mista int/float promove para float (5 + 2.0 == 7.0). Não há .para_float() — converta um int com n * 1.0 (ou divida por um float). Um resultado int não coagula automaticamente para float na atribuição (let x: float = 285 / 100 é um erro de compilação). + é apenas numérico: concatenação de string precisa que ambos os lados já sejam string ("n=" + 5 não compila — use interpolação ${...}). Comparações (==, <, …, == estrutural) e &&/||/! são semelhantes a TS.
  • Testes: test "nome" { ... } isolado (sem wrapper); testset apenas AGRUPA. Afirmações (apenas 6): assert.equal/approx_equal/is_true/is_type/not_null/contains. assert.equal compara estruturalmente (profundo, entre classes/arrays/mapas) — e o mesmo faz == simples, que é a forma bool. assert.equal é exato em floats; use assert.approx_equal(real, esperado, eps) para os computados. Última afirmação: sem ; no final. IDs canônicos são root::NomeTeste para um teste de nível superior e root::Testset::NomeTeste dentro de um testset. Execute um com baml test root::NomeTeste.
  • Saída formatada do modelo: para controle preciso sobre o JSON gerado, use @asType(…) em um campo — funciona como @alias mas para o tipo: @asType("string") força o campo a ser serializado como string (útil para IDs grandes int@asType("string")). baml describe asType.
  • Declarações de tipo (type alias). type Id = int @asType("string"); type Lista = array<int>. Útil para abstrações e para controlar a serialização.
  • Documentação embutida: além de @description nos campos, você pode usar comentários /// para documentar módulos, classes e funções — eles aparecem nas saídas de baml describe.
  • Importações: import "caminho/para/arquivo" — sem módulos explícitos; o nome do arquivo (sem extensão) torna-se o namespace. Use imports para organizar funções, tipos e clientes em diferentes arquivos.
  • baml.toml: arquivo de configuração do projeto. Define o nome do projeto, geradores e outras opções. Veja baml describe baml.toml para detalhes completos.
  • Ambiente: variáveis de ambiente são acessíveis via env.NOME. Use-as para chaves de API, URLs base, etc. baml describe env.
  • Erros comuns (E + número): o compilador BAML emite códigos de erro com mensagens descritivas. baml describe E0121, por exemplo, explica o erro e como corrigi-lo.

Executando funções de IA

Para testar e executar funções de IA sem gerar SDK, use baml run:

baml run -e 'minhaFuncaoIA(args)'
baml run -e 'minhaFuncaoIA(args)' --client Rapido

Para teste offline (sem chamada de modelo real), crie um cliente script:

client Scriptado = openai.ResponsesClient.new(
  api_key = "fake",
  base_url = "http://localhost:8080/v1"  # Servidor mock
);

function minhaFuncaoIA(args: Args) -> Saida {
  client Scriptado
  prompt: `...`
}

Ou use F@spec(args) para injetar saída diretamente:

test "teste offline" {
  let saida = minhaFuncaoIA@spec(args: { ... })  # Injeta especificação diretamente
  assert.equal(saida, esperado)
}

Exemplo completo

# baml_src/main.baml

class Entrada {
  texto: string,
}

enum Sentimento {
  Positivo,
  Negativo,
  Neutro,
}

function classificar_sentimento(input: Entrada) -> Sentimento {
  client "openai/gpt-4o-mini"
  prompt: `
    Classifique o sentimento do seguinte texto.
    ${ctx.formato_saida()}
    Texto: ${input.texto}
  `
}

test "classificar positivo" {
  let resultado = classificar_sentimento(Entrada { texto: "Estou muito feliz!" })
  assert.equal(resultado, Sentimento.Positivo)
}

Habilidades Relacionadas