Synsemadocsv0.6.xENES

LLM

API del proveedor directa

Las ops integradas (reason/decide/…) cubren la mayoría de los casos y te mantienen neutral respecto al proveedor. Pero cuando necesitás un parámetro que las ops no exponen (streaming, visión, un endpoint específico, tu propia lógica de reintentos), llamá la API HTTP del proveedor vos mismo. Es un request HTTP normal — así que está gateado por net(host) y usa un secret para auth (ver Secretos).

llm-api-direct.syn
-- Doc example: calling a provider's HTTP API directly (instead of the built-in ops).
-- It's a normal HTTP request, so it is gated by net(host) and uses secret/bearer for auth.
intent: "doc example: provider API directly"
require net("api.anthropic.com")
require secret("ANTHROPIC_API_KEY")

task call_undeclared_provider()
    -- api.openai.com is NOT declared → the request is denied at the capability check
    give http_post("https://api.openai.com/v1/chat/completions", {}, {"x-api-key": secret("ANTHROPIC_API_KEY")})

test "a direct provider call is gated by net(host) like any HTTP request"
    assert_error(call_undeclared_provider)

Anthropic (Messages API) — el header es x-api-key, no Bearer§

require net("api.anthropic.com")
require secret("ANTHROPIC_API_KEY")

let r be http_post("https://api.anthropic.com/v1/messages",
    json_encode({"model": "claude-sonnet-4-6", "max_tokens": 256,
                 "messages": [{"role": "user", "content": "Say hi"}]}),
    {"x-api-key": secret("ANTHROPIC_API_KEY"),        -- raw secret in a CUSTOM header
     "anthropic-version": "2023-06-01",
     "Content-Type": "application/json"})
let reply be json of r                                 -- parsed for you when the API answers JSON (engine v0.6.20+; json_decode(body of r) before)
let answer be reply["content"][0]["text"]

OpenAI / DeepSeek — Authorization: Bearer§

require net("api.openai.com")
require secret("OPENAI_API_KEY")

let r be http_post("https://api.openai.com/v1/chat/completions",
    json_encode({"model": "gpt-4o", "messages": [{"role": "user", "content": "Say hi"}]}),
    {"Authorization": bearer(secret("OPENAI_API_KEY")), "Content-Type": "application/json"})

La clave es un secret: se materializa solo en el socket y se redacta en logs/errores. Cuándo usar esto vs. las ops integradas: las ops para portabilidad y decisiones validadas; la API directa para features específicas del proveedor. Perdés la neutralidad de vendor y el contexto/validación automáticos.

Qué registra el engine (v0.6.29+). La respuesta del proveedor es una entrada del programa, como cualquier respuesta HTTP: lineage() la lista con source http_post, what = solo el host (api.anthropic.com — nunca el path, la query ni una credencial) y el sha256 del body, y receipt() la publica — ver Observabilidad § Linaje. Dos reglas HTTP de la misma versión aplican acá también: un puerto explícito es parte del permiso (require net("http://localhost:11434") cubre solo ese puerto; net("localhost") cubre cualquiera), y una URL con user:pass@ manda Authorization: Basic salvo que pases tu propio Authorization — ver Cliente HTTP.