Config de proveedor
El proveedor lo selecciona y configura el runtime, no el programa — el .syn nunca nombra un host ni una clave, así que no puede redirigir la llamada ni filtrar la clave. Cada knob resuelve environ del proceso > .env > default.
.env (gitignoreado)
SYNSEMA_LLM_PROVIDER=deepseek
DEEPSEEK_API_KEY=sk-...
La clave llega al runtime sin entrar al environ del proceso — ningún proceso hijo ni programa puede leerla (el programa necesitaría require secret, y aun así la ve redactada). No hace falta export/source.
Knobs
Env var / entrada .env | Propósito | Default |
|---|---|---|
ANTHROPIC_API_KEY / OPENAI_API_KEY / MINIMAX_API_KEY / DEEPSEEK_API_KEY | API key; su presencia auto-selecciona el proveedor | — (offline si falta) |
SYNSEMA_LLM_PROVIDER | Forzar anthropic / openai / minimax / deepseek | auto |
SYNSEMA_LLM_MODEL | Id de modelo | claude-sonnet-4-6 / gpt-4o / MiniMax-M3 / deepseek-chat |
SYNSEMA_LLM_MAX_TOKENS | Tope de salida | 4096 |
SYNSEMA_LLM_BASE_URL | Base del endpoint (apuntar a un server local/compatible) | oficial |
SYNSEMA_LLM_TIMEOUT | Timeout HTTP en segundos de los providers de red. Con el transporte streaming (default) mide silencio entre bytes — cada chunk lo renueva — así que una generación de minutos fluye y un host muerto sigue fallando rápido. Inválido/≤0 → default | 60 |
SYNSEMA_LLM_HTTP_STREAM | Transporte SSE interno de los providers de red (las ops del lenguaje siguen devolviendo texto completo; llm_stream emite chunks reales). 0/false → camino no-stream clásico, escape para proxies raros | 1 (on) |
SYNSEMA_LLM_BUDGET | Presupuesto de tokens LLM por proceso (input + output, todas las ops). Al tope, toda op degrada al marker [llm budget exceeded: used N of M tokens] — sin error, sin llamada de red, un aviso por stderr. El consumo se lee con llm_usage() (ver Primitivas LLM). Inválido/0 → sin presupuesto, con aviso | — (ilimitado) |
También podés forzar el proveedor por corrida: synsema run app.syn --provider anthropic (flag > env > .env).
El timeout ya no limita las generaciones largas. Los providers de red piden la respuesta a la API en modo streaming y la rearman internamente, así que el read-timeout detecta *conexiones muertas* (60s de silencio real) en vez de limitar la duración total de la generación. Verificado en vivo: una generación de 177s / 68 KB (MiniMax-M3, modelo razonador) completa donde la ventana fija de 60s la mataba. Con SYNSEMA_LLM_HTTP_STREAM=0 el timeout limita la llamada completa, como antes. Local / on-prem (100% privado)
Cualquier server compatible con OpenAI (Ollama, LM Studio, vLLM):
SYNSEMA_LLM_PROVIDER=openai
SYNSEMA_LLM_BASE_URL=http://localhost:11434/v1 # Ollama
SYNSEMA_LLM_MODEL=llama3.1
OPENAI_API_KEY=ollama # cualquier valor no vacío
Proveedor local embebido (local) — GGUF dentro del proceso, cero red
Con un binario compilado con la feature llm-local, el runtime corre un GGUF cuantizado dentro del proceso de Synsema (candle, CPU): sin server, sin API key, sin socket — el único proveedor que funciona bajo un deny net total. Siempre explícito, jamás auto-seleccionado:
SYNSEMA_LLM_PROVIDER=local
SYNSEMA_LLM_MODEL=/models/qwen2.5-0.5b-instruct-q4_k_m.gguf # ruta al .gguf (obligatoria)
| Knob | Propósito | Default |
|---|---|---|
SYNSEMA_LLM_CTX | Ventana de contexto (capada al límite del propio GGUF) | 4096 |
SYNSEMA_LLM_THREADS | Threads de CPU para la inferencia | todos los cores |
SYNSEMA_LLM_TEMPERATURE | 0 = greedy/determinista; >0 = muestreo (seed fija) | 0 |
SYNSEMA_LLM_MAX_CONCURRENT | Instancias máximas del modelo; 1 serializa llamadas concurrentes bajo serve | 1 |
SYNSEMA_LLM_STREAM_BUFFER | Chunks en vuelo entre la generación y la emisión de llm_stream | 32 |
SYNSEMA_LLM_MAX_TOKENS aplica como siempre. Arquitecturas soportadas: llama, qwen2, qwen3 — es el string general.architecture del header del GGUF, no la marca del modelo; cualquier otra falla con un [local error: …] claro. Muchas familias populares se convierten a GGUF declarando una de esas tres, así que la cobertura real es más amplia de lo que sugieren los tres nombres. Familias verificadas en vivo (sonda = cargar + pregunta + respuesta coherente + corte limpio):
| Modelo (GGUF sondeado) | declara | Verificado |
|---|---|---|
| Qwen2.5 Instruct 0.5B/3B | qwen2 | ✅ |
| Qwen3 0.6B | qwen3 | ✅ modelo razonador — emite <think>…</think> crudo; presupuestá SYNSEMA_LLM_MAX_TOKENS para eso |
| Qwen3 4B Instruct-2507 | qwen3 | ✅ (GGUF de 2.5 GB, corrió en 8 GB de RAM). La 4B Thinking-2507 también corre pero piensa miles de tokens por respuesta — inviable en CPU; preferí la variante Instruct |
| Mistral 7B Instruct v0.3 | llama | ✅ su template [INST] se detecta solo |
| Llama 3.2 1B Instruct | llama | ✅ template llama3 detectado solo |
| SmolLM2 135M Instruct | llama | ✅ chatml |
| DeepSeek-R1-Distill-Qwen 1.5B | qwen2 | ✅ corre en modo plain; filtrá los tags <think> en tu código |
| TinyLlama 1.1B Chat | llama | ⚠️ carga y corre, pero su template zephyr no se reconoce → cae a plain y se comporta como modelo base |
Una arch soportada carga y corre; la usabilidad como chat además necesita un template reconocido (chatml / llama3 / [INST]; si no, fallback plain). Los GGUF de gemma/phi/glm se rechazan a propósito: candle todavía no expone un reset público del KV-cache para ellos, y el aislamiento entre requests va primero.
Tip — los modelos bajados con ollama son blobs GGUF planos (y su CDN es mucho más rápido que bajar de HF con una sola conexión): ollama pull llama3.2:1b, y apuntá SYNSEMA_LLM_MODEL al blob en ~/.ollama/models/blobs/sha256-… (el digest está en el manifest bajo ~/.ollama/models/manifests/…; ollama no necesita estar corriendo).
En un binario sin la feature, SYNSEMA_LLM_PROVIDER=local avisa por stderr y queda offline — jamás degrada en silencio a otro proveedor.
Compilalo — y compilalo rápido
# build simple (funciona en cualquier CPU, pero los kernels AVX2 de candle quedan APAGADOS — prefill lento):
cargo install --path crates/synsema-cli --features llm-local --force
# prefill ~3× más rápido: candle elige sus kernels cuantizados AVX2 en tiempo de COMPILACIÓN,
# y el target x86-64 default de Rust no los habilita:
RUSTFLAGS="-C target-cpu=native" cargo install --path crates/synsema-cli --features llm-local --force
Medido (Qwen2.5 Q4_K_M): generación ~13 tok/s (0.5B) / ~5 tok/s (3B); prefill con el flag ~35 tok/s (0.5B) — pensado para prompts cortos (cientos de tokens, no miles). La carga del modelo se paga una vez por proceso (~6s / ~19s): bajo serve, la primera request carga y el resto reusa. RAM: ~1GB (0.5B) / ~2.4GB (3B). native ata el binario a la CPU de esa máquina — ideal para tu propio VPS; para un binario que distribuís, usá -C target-cpu=x86-64-v3 (AVX2+FMA, CPUs x86 desde ~2015).
---
El egress al host configurado es parte de require llm — no un grant net aparte (el proveedor embebido no necesita egress en absoluto). Offline (sin clave), las ops devuelven placeholders; ramificá con llm_available(). Para saltarte las ops integradas y pegarle a la API vos mismo, mirá API del proveedor directa.