Synsema docsENES

Primitivas LLM

El LLM es el motor de razonamiento, intercambiable como un driver de base de datos. Cuatro operaciones, gateadas por la capacidad llm (auto-otorgada en run, exigida bajo serve):

llm.syn
-- Doc example: LLM operations. The four ops return TEXT and need a real provider,
-- so the doctest verifies the offline/online check; the ops are shown in `summarize`.
intent: "doc example: LLM operations"
require llm

task summarize(report)
    when llm_available()
        give reason about report with context = "be concise"   -- needs a provider
    otherwise
        give "(LLM offline)"

print("summarize(\"a report\") → " + summarize("a report"))    -- offline → (LLM offline)

test "llm_available() is a bool — branch on it instead of guessing"
    assert_eq(type_of(llm_available()), "bool")

-- llm_stream(prompt, context, on_chunk): on_chunk receives each fragment as it is
-- produced; the call returns the full text. Offline it returns the placeholder
-- WITHOUT ever invoking on_chunk (placeholders are not answers).
task on_tok(t)
    print("chunk: " + t)

test "llm_stream: offline → placeholder, on_chunk untouched; online → text"
    when llm_available()
        assert_eq(type_of(llm_stream("Reply with one word: pong", "", on_tok)), "text")
    otherwise
        assert_eq(llm_stream("q", "", on_tok), "[no llm provider]")

Las cuatro operaciones

require llm

let analysis be analyze sales_data for "trends and anomalies"
let action   be decide between ["refund", "replace", "escalate"] given ticket
let email    be generate "response email" given complaint with tone = "empathetic"
let insight  be reason about problem with context = background_data

Llevan un sujeto: reason about X, decide between [...] given X, analyze X for "...", generate "..." given X. (Un reason "literal" pelado también funciona.)

Decisiones validadas

El resultado de un decide between [...] debe ser exactamente una de las opciones. Synsema

lo hace cumplir en capas: en los providers de red la elección se fuerza a nivel API (una tool

interna cuyo schema restringe la respuesta a tus opciones — cero reintentos en el caso común);

la respuesta se normaliza (mayúsculas, puntuación, contención por palabra completa: `"La

respuesta es AZUL"AZUL`); si aún no matchea, un reintento con feedback; y recién

entonces avisa (stderr, una vez) y devuelve la respuesta cruda — la cadena nunca se rompe, pero

nunca en silencio.

Modo offline

Sin un proveedor configurado, las ops devuelven placeholders descriptivos (p. ej. decide"[decision pending]"), así los programas siguen corriendo — la cadena nunca se rompe. Pero nunca es en silencio: la primera op LLM que cae a placeholder imprime un aviso único por stderr apuntándote a synsema llm status para el diagnóstico exacto (qué variable falta, y dónde). Ramificá según llm_available() en vez de adivinar:

when llm_available()
    let s be reason "Summarize: " + text
otherwise
    let s be "(LLM offline)"

Metering y presupuesto — llm_usage(), SYNSEMA_LLM_BUDGET

Toda llamada real al provider se mide. llm_usage() → número de tokens LLM (input + output) consumidos por este proceso hasta ahora — introspección, sin capability (como llm_available()); 0 offline o antes de la primera llamada. Un contador por proceso, compartido también bajo serve. Es monotónico: las ventanas de tiempo son política de tu programa, no estado del runtime.

El host puede fijar un presupuesto duro de tokens con SYNSEMA_LLM_BUDGET (entero positivo, resuelto environ del proceso > .env; inválido o 0 → sin presupuesto, con aviso). Al llegar al tope, toda op LLM degrada al marker [llm budget exceeded: used N of M tokens] — sin error, sin llamada de red, un solo aviso por stderr por proceso. Una op LLM jamás rompe la cadena del agente (misma filosofía que los placeholders offline), así que el código que deba frenar al agotarse el presupuesto ramifica sobre el marker o sobre llm_usage():

require llm
let antes be llm_usage()
let respuesta be reason about pregunta
when contains(respuesta, "llm budget exceeded")
    give "(presupuesto agotado — no reintento)"
print("esta llamada costó " + text(llm_usage() - antes) + " tokens")

Streaming (llm_stream)

llm_stream(prompt, context, on_chunk) genera con el proveedor configurado, invocando la task on_chunk con cada fragmento de texto a medida que se produce, y devuelve el texto completo. Mismo gate llm. Los proveedores de red streamean los text-deltas SSE de la API como chunks reales; el proveedor local embebido streamea token a token. (Con SYNSEMA_LLM_HTTP_STREAM=0 un proveedor de red cae a un chunk — la respuesta entera.)

require llm

task on_tok(t)
    print(t)

let full be llm_stream("Explain CSP in one line", "", on_tok)

Offline devuelve "[no llm provider]" sin invocar on_chunk. Bajo una ruta SSE de serve compone con send — definí la task emisora dentro del bloque stream (send es un statement, no una función, y solo parsea ahí):

serve on 8080
    route "GET /chat"
        stream
            task emit(tok)
                send tok
            let full be llm_stream("Count to ten in words", "", emit)
            send full as "full"

Si on_chunk falla (p. ej. el send de un cliente desconectado), la generación corta temprano y el error propaga — recuperable con try/recover.

Configurá un proveedor en Config de proveedor; dejá que el modelo elija tools en Tool calling.