---
slug: 52-llm-provider-native
title: Config de proveedor
description: Configurá el proveedor LLM de Synsema — Anthropic, OpenAI, MiniMax, DeepSeek, cualquier server local compatible con OpenAI, o un GGUF embebido corriendo dentro del proceso — vía .env, con la clave nunca expuesta al programa.
example_ids: []
---

# 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)

```bash
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](/es/0.6.x/50-llm-primitives)). 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):

```bash
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:

```bash
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

```bash
# 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](/es/0.6.x/53-llm-api-direct)**.
