xyzcharlize/hyperframes-genie

Habilidad BAML mínima. BAML es un lenguaje estáticamente tipado orientado a expresiones con funciones LLM de primera clase, similar a TypeScript con métodos en snake_case. Útil para construir flujos de trabajo de IA, agentes y evaluaciones.

¿Qué es hyperframes-genie?

BAML es un lenguaje de programación estáticamente tipado y orientado a expresiones, diseñado específicamente para construir flujos de trabajo de IA, agentes y sistemas de evaluación. Combina una sintaxis similar a TypeScript (con métodos en snake_case, campos `name: type,`, etc.) con una DSL declarativa para llamadas a modelos de lenguaje (LLM), lo que permite que la salida estructurada de un modelo sea simplemente un valor tipado retornado. La habilidad incluye un CLI completo (`baml describe` para documentación, `baml init` para nuevos proyectos, `baml test` para ejecutar pruebas) y un rico conjunto de características: inferencia de tipos, pattern matching, concurrencia con green threads (`spawn`/`await`), gestión de errores con `catch`/`catch_all`, interfaces con despacho dinámico, y más. Es compatible con múltiples proveedores de IA (OpenAI, Anthropic, etc.) mediante clientes configurables, y puede integrarse con Python y TypeScript a través de un SDK generado automáticamente.

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

Installed? Explore more Productividad y colaboración skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

baml

BAML es un lenguaje de programación estáticamente tipado y orientado a expresiones: TypeScript con métodos en snake_case, campos name: type,, enums, interfaces, genéricos, clausuras, encadenamiento opcional, cadenas con backticks e interpolación ${...}, .to_string() en cualquier valor y una stdlib completa. También incluye un DSL declarativo para llamadas a LLM (function … { client: prompt: }, test) que se desugariza en él, por lo que la salida estructurada de un modelo es simplemente un valor de retorno tipado.

El CLI es la documentación. Descúbrelo con baml describe:

brew install baml                # Binario CLI: `baml`

baml init                        # Nuevo proyecto (baml.toml + baml_src/)
baml help <comando>              # Opciones del CLI y ejemplos
baml describe baml.json          # Referencia para cualquier módulo/tipo/método/firma/palabra clave
baml describe Array --budget 120 #   (Array, String, Map, assert, match, patterns, spawn, python, ...)
                                 #   termina con "N more lines"? vuelve a ejecutar con `--budget <N>`
baml check                       # Verificación de compilación del proyecto
baml run -e 'expr'               # Evaluar una expresión; retroalimentación rápida + verificación de sintaxis
baml test --list && baml test    # Ejecutar cada bloque test/testset
baml fmt baml_src/main.baml      # Canonicalizar el formato del proyecto

baml describe <nombre> imprime el cuerpo fuente completo de las funciones de la stdlib, la forma más rápida de verificar el comportamiento y la única ruta para funciones integradas como assert (sin archivo en disco). Las funciones puras no necesitan test/cliente: verifícalas con baml run -e 'add(2, 3)'.

No describas APIs ya demostradas a continuación a menos que encuentres errores. Puedes comenzar basándote en los ejemplos y usarlo si encuentras más errores o deseas detalles reales de la stdlib.

En su mayor parte, se comporta como JavaScript/TypeScript, con una sintaxis muy similar, pero BAML es más sólido/estricto.

Mejores prácticas e información

  • Función LLM = retorno tipado. EL TIPO DE RETORNO es el esquema que el modelo debe producir (class, enum, unión literal, string[], T?). La salida estructurada es solo un valor tipado: entrégala al código ordinario.
  • Los prompts son cadenas con backticks e interpolación ${...}. Escribe prompt: … ${arg} …, y siempre inyecta ${ctx.output_format()} para un retorno estructurado. Escapa con \`` / ${`; anida con backticks adicionales.
  • Los clientes son valores, no bloques de configuración. client Fast = openai.ResponsesClient.new(model = "…", api_key = env.OPENAI_API_KEY); — el bloque antiguo client<llm> Name { provider: …, options: {…} } está eliminado. Cualquier cosa que implemente ai.Client funciona (openai.ResponsesClient, openai.ChatClient, anthropic.Client, …; los parámetros del constructor varían según el proveedor). api_key y base_url aceptan ai.Credential: una cadena literal, null para el valor predeterminado del proveedor, o una referencia env.NAME enlazada tardíamente y resuelta en el momento de la solicitud. Compón confiabilidad mediante envoltura: ai.clients.Retry.new(inner = c, max_attempts = 3) y ai.clients.RoundRobin.new(members = […]) tienen .new, pero ai.clients.Fallback { members: […] } no — construye como un literal de clase. Luego usa client: Fast en la función, o la abreviatura client: "openai/gpt-4o-mini". baml describe openai / baml describe ai.Credential / baml describe ai.clients.
  • Da forma al esquema con atributos de campo. @description("…") agrega una pista /// que el modelo ve en ${ctx.output_format()}; @alias("name") renombra la clave JSON emitida. Encadena: tags: string[] @alias("labels") @description("…").
  • Prueba el código puro, no el modelo. Prueba unitaria de orquestación/post-procesamiento en datos literales con assert.*. Llamar a una función LLM en un test con un cliente real realiza una solicitud real. Para probar prompts, solicitudes y análisis sin conexión, usa F@spec(args) o un cliente con script (ver Ejecución de funciones de IA).
  • Construye cadenas con interpolación, no coerción. score=${n} stringifica cualquier valor (.to_string() implícito); llama a .to_string() solo para la cadena. + requiere que ambos lados ya sean cadenas ("n=" + 5 no compilará).
  • catch para algunos, catch_all para todos. expr catch (e) { baml.errors.ParseError => fallback } maneja un error específico; expr catch_all (e) { _ => fallback } es exhaustivo, para la parte superior/punto de entrada de un flujo de trabajo. Los errores se propagan implícitamente; los llamadores no necesitan redeclararlos. Lanza con throw baml.errors.InvalidArgument { message: "…" } (los tipos de error son las clases integradas baml.errors.* — InvalidArgument/ParseError/Io/Timeout/…; baml describe baml.errors); anota una firma fallible con -> T throws ErrType. Prefiere una unión de resultado tipada (type R = Ok | Err) sobre lanzar para flujo de control ordinario.
  • Interfaces = comportamiento compartido + despacho dinámico. interface I { function m(self) -> T throws never }; cada método declara su cláusula throws. Un método con cuerpo es una implementación predeterminada, que un implementador puede anular. Una clase opta mediante implements I { … }; un valor tipado como I (o I[]) despacha al implementador en tiempo de ejecución. Las interfaces también pueden declarar tipos asociados y límites genéricos. baml describe interfaces.
  • requires, no herencia. BAML no tiene herencia: una clase no puede extender otra clase. interface Pet requires Animal, Named { … } convierte esas interfases en requisitos previos de Pet. Los cuerpos predeterminados de Pet pueden llamar a sus métodos, y un valor tipado como Pet los expone. Cada implementador de Pet también escribe sus propios bloques implements Animal e implements Named (de lo contrario, E0125). Un diamante de requires compila, y la interfaz compartida se implementa una vez; un ciclo es un error. Cuando dos interfaces en una clase declaran el mismo nombre de método, una llamada sin calificar es un error (E0121); llama obj.as<I>.m() en su lugar.
  • Pattern matching. match (v) { … } sobre valores/tipos; los brazos son patrón => expr — literales, let x: T (vincular + estrechar), deestructuración de clase T { f: let y }, patrones OR A | B, guardas … if cond, _; debe ser exhaustivo. También v is T → bool (estrecha) y if let x: T = v { … } else { … }. baml describe patterns.
  • Estrechamiento — solo locales. v is T, v != null / v == null y una prueba de veracidad estrecha v solo cuando v es un local (un let o un parámetro) que ninguna clausura captura: en la rama if / while con guarda, después de una salida temprana (if (!(v is T)) { return … }), y en el lado derecho de && (lado izquierdo verdadero) y || (lado izquierdo falso) — v is T && v.f == 1, s == null || s.length() == 0. NO se estrecha: un local capturado, una ruta de campo (h.v is T && h.v.f → E0007) y una ruta de índice (xs[0] is T). Las tareas pueden intercalarse entre declaraciones, por lo que otra tarea o un alias puede cambiar un campo entre la prueba y la lectura. Para una escritura de campo, escribe let v = h.v; primero, o if let v: T = h.v { … }, o match (h.v) { let v: T => … } (cada una lee el campo una vez). Una asignación al local reemplaza el hecho con el tipo asignado. baml describe is.
  • Concurrencia = green threads. spawn { … } devuelve un Future; await lo recoge. Combina muchos con baml.future.all / all_settled / race / any. all_settled devuelve Success<T>, Failure<E> o Panicked para cada entrada; la cancelación de entrada es un resultado Panicked. Configura un spawn con una cláusula with: spawn with limit, token { … } — cada valor listado es un baml.spawn.Modifier que transforma el plan que el spawn construye, de izquierda a derecha. baml.spawn.Limit.new(n) limita la concurrencia (los spawns excedentes se ponen en cola), un baml.spawn.CancelToken cancela cooperativamente, baml.spawn.Root.new() desvincula la tarea de la cancelación de su generador. baml describe spawn / baml describe baml.future.
  • Seguridad de recursos — defer, cleanup, catch (e, ctx). defer { … } ejecuta un bloque al salir del ámbito, LIFO, en cada ruta (return/throw/fall-through) — como Go. Un método de clase llamado function cleanup(self) -> void es un finalizador: se ejecuta como máximo una vez por instancia, ya sea que lo llames, lo defer o el GC lo reclame. catch (e, ctx) vincula un ErrorContext junto con el error: un error lanzado mientras se maneja otro se encadena a él, por lo que ctx.root_cause() / ctx.cause retroceden hasta el fallo original y ctx.to_string() renderiza toda la cadena (estilo __context__ de Python). while let PATTERN = expr { … } itera hasta que el patrón falla (por ejemplo, drenar un .pop() que devuelve T?).
  • Llama a BAML desde Python/TS. Declara un [generator.<name>] en baml.toml, ejecuta baml generate, luego importa el baml_sdk tipado. Instalación + uso: baml describe python / baml describe typescript / baml describe baml_sdk.
  • Acceso seguro sobre indexación. El subíndice entra en pánico en un índice/clave faltante; usa .at(i)/.get(k) (→ T?), pasa con ?., valor predeterminado con ?? (parentiza: (m.get(k) ?? 0) + 1).
  • Los métodos de la stdlib son snake_case, llamados sobre un valor. Algunos devuelven nuevo, otros mutan en el lugar, algunos hacen ambas cosas (sort_by_key ordena el receptor y lo devuelve) — para leer la documentación, baml describe <palabra/tipo/identificador/palabra clave/etc>.
  • Campos de clase name: type,; construcción Type { field: val }. Los métodos toman un self simple; las fábricas son funciones libres. Los campos son mutables (como TS): obj.field = v y obj.field += n funcionan, y un método self puede mutar en el lugar — un método con efectos secundarios devuelve void. Las clases son tipos de referencia: find/at(i)/subíndice devuelven un alias en vivo, no una copia, por lo que mutar el resultado muta ese elemento dentro del array (xs.find(p)?.n += 1 actualiza xs), y una clase pasada a una función puede ser mutada por el que llama. Se admite la propagación de actualización de struct: User { ...u, tier: Tier.Free }. Las clases vacías son legales (class Marker {}) — útiles como variantes de unión. Los enums son variantes simples — sin métodos, sin datos asociados (E.A.foo() no compilará); coloca el comportamiento en funciones libres que hagan match. enum E { A, B }, acceso E.A.
  • Los bloques son expresiones — la última expresión es el valor (sin ;); return x; para salida temprana. Una función solo de efectos secundarios devuelve void — otra forma de escribir null, el tipo unidad — y puede omitir su expresión final o return;. Un valor dejado como expresión final de dicho cuerpo es un error, no se descarta: termínalo con ; (xs.push(x); — push devuelve la nueva longitud). for (let x in xs) itera VALORES; while (cond) { … } itera. Las clausuras (x) -> { ... } infieren parámetro/retorno del contexto (anota (x: T) -> R solo cuando es ambiguo; -> es obligatorio). .map/.filter devuelven arrays directamente (sin .collect()). Un mapa vacío necesita un tipo: let m: map<string, int> = {};.
  • Sin ternario — if/else es la expresión. No hay cond ? a : b; if (cond) { a } else { b } es una expresión que devuelve un valor, así que asígnalo directamente: let label = if (x > 3) { "big" } else { "small" };. Cada rama es un bloque cuya última expresión es su valor (sin return). Encadena con else if, y combina con if let PATTERN = expr { … } else { … } para vincular y estrechar.
  • Las condiciones usan veracidad. if, while, guardas de match, &&, || y ! aceptan cualquier valor. Los valores falsos son false, null, cero numérico, cadenas vacías, arrays/mapas vacíos y bytes vacíos; todo lo demás es verdadero. && y || aún devuelven bool, no un valor de operando; cortocircuitan. Un optional local verdadero se estrecha en la rama tomada y en el lado derecho de && (s && s.length() > 0).
  • Los arrays tienen un conjunto de métodos similar a JS — map/filter/filter_map/reduce/find/some/every/flat_map/slice/concat/join/includes/length(), más push/pop/shift/unshift/sort_by/sort_by_key en el lugar. La mayoría toman clausuras que pueden throws. baml describe Array da más información.
  • Las vinculaciones locales con let son reasignables (x = x + 1) — no hay palabra clave mut (es TS let, no Rust); tampoco hay const.
  • Args: valores predeterminados con =, llamadas por palabra clave con = (nunca :). Declara un valor predeterminado en la firma: function f(a: int, b: int = 10); llama f(1) o f(1, b = 2). Un parámetro con valor predeterminado debe pasarse por nombre — f(1, 2) es un error (el parámetro con valor predeterminado 'b' debe pasarse por nombre). Cualquier parámetro (incluso obligatorio) puede pasarse por nombre (f(a = 1, b = 2)), y puedes omitir un valor predeterminado del medio para establecer uno posterior (f(1, c = 9)). La sintaxis de palabra clave es nombre = valor; nombre: valor no se analizará (: es para tipos/campos). T? NO hace que un argumento sea opcional — a diferencia de TS b?: T, un parámetro b: T? sigue siendo obligatorio (debes pasar null, de lo contrario se esperaban N argumento(s), se obtuvo …); agrega = null para hacerlo omisible. Las funciones integradas siguen esto: baml.http.fetch(url, timeout = baml.time.Duration.from_seconds(10)).
  • Donde diverge de TS (las trampas silenciosas): la aritmética está basada en tipos, no al estilo TS. int / int es división entera truncante (285 / 100 == 2, NO 2.85) y % es el resto (285 % 100 == 85); esto compila bien y solo da un número incorrectamente silencioso, por lo que es la trampa de mayor valor. Mezcla un float para obtener división flotante (285 / 100.0 == 2.85, 285.0 / 100 == 2.85); cualquier operación mixta int/float promueve a float (5 + 2.0 == 7.0). No hay .to_float() — convierte un int con n * 1.0 (o divide por un float). Un resultado int no se autocoerciona a float en la asignación (let x: float = 285 / 100 es un error de compilación). + es solo numérico: la concatenación de cadenas requiere que ambos lados ya sean string ("n=" + 5 no compilará — usa interpolación ${...}). Las comparaciones (==, <, …, == estructural) y &&/||/! son similares a TS.
  • Tests: test "nombre" { ... } solo (sin envoltorio); testset solo AGRUPA. Aserciones (solo 6): assert.equal/approx_equal/is_true/is_type/not_null/contains. assert.equal compara estructuralmente (profundo, entre clases/arrays/mapas) — y también lo hace == simple, que es la forma booleana. assert.equal es exacto en floats; usa assert.approx_equal(actual, esperado, eps) para los calculados. Última aserción: sin ; final. Los IDs canónicos son root::NombreTest para un test de nivel superior y root::Testset::NombreTest dentro de un testset. Ejecuta uno con baml test root::NombreTest.
  • Ejecución de funciones de IA. Construir una solicitud sin ejecutarla: add devuelve un baml.llm.PendingCall. Pasarlo a baml.llm.run lo ejecuta una vez; a baml.llm.run_stream para iterar tokens incrementalmente. Una entrada de datos es un mapa de argumentos nombrados desde el que BAML lee (SSE o stdin). baml.llm.PendingCall { … } también es un valor construible manualmente. baml describe llm.
  • CLI, no lib. BAML es un lenguaje; todo (tests, generación de código, documentación) sale de baml y baml.toml. La lib libbaml es la implementación del lenguaje, no una API pública para programación de aplicaciones (excepto los SDK generados para Python/TS). Solo el binario baml es compatible.
  • Los comentarios son de una sola línea. // comienza un comentario. No hay comentarios de bloque (/* */); usa // para todo, incluso para bloques de documentación (ver @description).

Tabla de referencia rápida

CaracterísticaSintaxis / Notas
Función LLMfunction name(input: T) -> R { client: …, prompt: … }
Cliente LLMclient Name = openai.ResponsesClient.new(model = "…")
PromptCadena con backticks: prompt: `… ${arg} … ${ctx.output_format()}`
Atributos de campo@description("…") / @alias("json_name")
Tipo opcionalT? — null o valor
Pattern matchingmatch (v) { patrón => expr, … }
Concurrenciaspawn { … } → Future; await fut; baml.future.all([…])
Manejo de errorescatch (e) { Tipo => fallback }; catch_all
Pruebastest "nombre" { … } con assert.*
Generación de códigobaml generate después de configurar [generator]
Atajo de clienteclient: "openai/gpt-4o-mini"

Skills relacionados