synsema
ES Español

Cómo usar Jev desde Synsema — decisiones calibradas en un bloque judge

Jev es el modelo System One de TypeSafe: responde con probabilidades calibradas y tipadas en vez de texto. En Synsema es una primitiva del lenguaje — `require judge`, un bloque, una llamada, tres verbos — con degradación honesta cuando no está.

Un modelo System One no escribe. Le pasás un estado y preguntas tipadas, y responde con probabilidades: ¿es esto cierto? (0,93), ¿cuál de estas? (billing, y la distribución), ¿dónde en esta escala? (nivel 2 de 3, y qué tan concentrada está la respuesta). Jev, de TypeSafe, es el primero, y desde v0.6.25 Synsema le habla con un bloque del lenguaje en vez de un cliente HTTP y un parser.

El motivo de que sea sintaxis y no una biblioteca: un juicio es un valor con forma, y esa forma es sobre la que ramifica el resto de tu código.

El bloque§

require judge

let ticket be {"subject": "Payouts failing", "text": "I want my money back NOW or I'm cancelling"}

let v be judge ticket
    refund: whether "The customer is asking for money back"
    team:   choose "Which team should handle this?" between {
                "billing":   "Payments, invoicing, refunds",
                "technical": "Bugs, outages, integrations"
            } or nothing
    anger:  rate "How frustrated is the customer?" across ["Calm", "Frustrated", "Very angry"]

when v.team.available and confidence of v.team >= 0.8
    print("route to " + v.team.choice)
otherwise
    approve "Route this ticket to " + text(v.team.choice) + "?"

Un estado, tres preguntas, una sola llamada. Ese es todo el sentido de que el bloque sea la única forma: el estado se ingiere una vez y las preguntas se evalúan contra él en paralelo. Medido contra jev-1.13.0, un bloque de ocho preguntas fue 7,6× más rápido y usó 4,9× menos tokens de entrada que ocho llamadas sueltas, y la latencia se mantuvo plana — unos 0,8 s con una pregunta o con cuarenta. No hay atajo de una sola pregunta a propósito: los agentes de programación tienden a hacer una llamada por pregunta, y el lenguaje no los deja.

Tres verbos, tres distribuciones§

VerboPreguntaDistribuciónQué leés
whether "…"¿es cierta esta afirmación?Bernoulliprobability
choose "…" between {…} [or nothing]¿cuál de estas?categóricachoice, probabilities, confidence
rate "…" across […]¿dónde en esta escala ordenada?ordinalscore, level, levels, probabilities, confidence

Las preposiciones son distintas a propósito: between significa opciones sin orden, across significa niveles ordenados. Escribir rate … between es un error de carga que nombra el arreglo.

El resultado es un mapa plano, id → respuesta, así que una pregunta llamada usage no choca con nada:

v.refund.probability    -- 0..1 (un `whether` no tiene confianza aparte)
v.team.choice           -- uno de TUS ids, byte por byte, o nothing
v.team.probabilities    -- {"billing": 0.93, "technical": 0.07, "none": 0.0}
v.team.confidence       -- qué tan concentrada está la distribución
v.anger.score           -- 0..n-1, ponderado por probabilidad: 1,4 es reparto entre el 2º y el 3º
v.anger.level           -- "Frustrated"

Las opciones aceptan una lista (el ítem es el id y la descripción) o un mapa (id: descripción) — usá el mapa cuando las descripciones son largas, porque v.anger.probabilities["Visiblemente molesto, amenaza con cancelar"] no es forma de vivir. En choose, el modelo lee tus ids de opción, así que poneles nombres con sentido; los ids de las preguntas no se envían.

or nothing, y la falla que lo hizo existir§

Un choose tiene que elegir. Un mensaje que preguntaba por los horarios de atención, con solo billing y technical como opciones, volvió technical con 0,69 — una respuesta equivocada que pasa derecho por una compuerta de 0,5. or nothing agrega una opción de escape al cable; cuando gana, choice es nothing y la masa queda en probabilities.none. En esos mismos casos respondió none con 1,00 y 0,98, y no costó nada en los casos claros (billing se quedó en 0,97).

Escribilo siempre que el estado pueda no encajar en ninguna opción. rate no tiene escape — una escala ordenada no tiene niveles fuera de sí misma —, así que protegé un rate con un whether que pregunte si el estado aplica.

Cuando no está, lo dice§

Esta es la parte que decide si podés ponerlo en producción. Sin clave, pasado el presupuesto o tras una falla de red, cada respuesta vuelve con available: false, confidence: 0 y su valor principal en nothing:

[synsema] notice: judge is OFFLINE — every `judge` answer is returning available: false with
confidence 0 and its main value (probability/choice/score) as nothing, not a real judgment.
The program keeps running; a confidence gate sends these to the human path by itself.
offline: available=false  probability=nothing  team.confidence=0.0

Una oración inventada se ve en tu salida. Una probabilidad inventada no, y se multiplica hasta volverse plata — así que este bloque nunca devuelve una. Y fijate qué hace la degradación con el código que ya escribiste: la confianza 0 está por debajo de cualquier compuerta, así que el ticket se va solo por la rama humana. Un programa que se salteó la compuerta y compara directo falla fuerte (Unsupported operation: nothing > number) en vez de tomar la rama equivocada en silencio.

Su propia capacidad§

require judge      -- clasificar
require llm        -- generar

llm no otorga judge y judge no otorga llm. Clasificar y generar son derechos distintos, y el techo que los separa es real: --cap-set judge es un programa que puede medir y no puede escribir — no puede exfiltrar por texto libre y no se lo puede convencer de generar. En lo demás se comporta como llm: se otorga solo en un run común, es obligatoria bajo serve, se vacía en sandbox, se deniega bajo --deterministic (es E/S de red) y está offline dentro de un guest wasm. La clave nunca entra al programa y el host lo fija el runtime, así que un .syn no puede redirigir la llamada.

El slot paralelo, y un preflight§

El juez se configura al lado del LLM, no adentro: TYPESAFE_API_KEY, SYNSEMA_JUDGE_PROVIDER, SYNSEMA_JUDGE_MODEL, SYNSEMA_JUDGE_BASE_URL, SYNSEMA_JUDGE_TIMEOUT, SYNSEMA_JUDGE_BUDGET — todas escritas en .env.example por synsema init. El juez decide, el LLM escribe, y tener los dos cableados es la configuración normal.

Desde v0.6.26 el CLI responde qué quedó resuelto y por qué, sin tocar la red:

$ synsema judge status
Key         TYPESAFE_API_KEY             ✗ FALTA
Model       jev-latest                   (SYNSEMA_JUDGE_MODEL, default)
Base URL    https://api.typesafe.ai      (SYNSEMA_JUDGE_BASE_URL, default)
Timeout     60s                          (SYNSEMA_JUDGE_TIMEOUT, default)
Budget      (sin techo)                  (SYNSEMA_JUDGE_BUDGET, default)
decide      LLM (default)                (SYNSEMA_JUDGE_DECIDE)

Sale 0 si está vivo y 1 si está offline, así que synsema judge status && synsema serve app.syn es una compuerta de despliegue. La clave se informa por presencia, nunca por valor.

El verificador no gasta tokens§

synsema check conoce los límites de la API y rechaza un bloque que sería un 400 en producción:

$ synsema check bad.syn
Error: bad.syn:4: judge 'team': choose needs at least 2 options (got 1);
with one option the model can only agree

Y advierte — nunca falla — sobre lo que corre pero engaña:

warning: judge 'quiet' asks in the negative — the model reads negations literally and
P(not A) is not 1 − P(A) (measured 0.37 + 0.78); ask in the positive and negate in code
warning: judge 'total' asks for arithmetic or counting over the state — the model recognises
the shape of an answer, it does not calculate (a six-line total came out wrong at 0.32);
compute in Synsema and judge the result

Las rutas entre comillas invertidas — ` ticket.messages[0].text `, el modismo del proveedor para apuntar a un elemento — se resuelven contra el estado antes de la llamada, así que una ruta que no existe es una advertencia con el arreglo en vez de una respuesta de 0,31 salida de la nada.

Tests sin clave, y decide de regalo§

SYNSEMA_JUDGE_PROVIDER=mock cablea un proveedor determinista — whether → 0,5, choose → la primera opción, rate → el nivel del medio — así que el mismo bloque corre en CI sin red y sin cuenta. Con una clave real, afirmá el ganador y rangos (v.team.choice == "billing", v.refund.probability > 0.9), nunca números exactos: el mismo pedido dos veces se mueve unas centésimas (0,72 → 0,69).

Y si tu programa ya usa decide between […] given x, la v0.6.26 agrega SYNSEMA_JUDGE_DECIDE=1: cada decide del proceso lo responde el juez como un choose calibrado — una de tus opciones byte por byte, sin normalización ni reintento — sin cambiar una línea del programa. Es opt-in porque cambia qué modelo responde, necesita require judge, y cae al camino del LLM cuando el juez no está disponible.

Dónde seguir§

La página del manual es Judge, el modelo es Jev, y la entrada compañera acá es System One vs System Two — cuándo una probabilidad es la respuesta correcta y cuándo seguís queriendo una oración.

Instalalo y probalo en cinco minutos: curl -fsSL https://synsema.org/install.sh | sh, y después SYNSEMA_JUDGE_PROVIDER=mock synsema run triage.syn.