Modelos personalizados
Añade proveedores y modelos personalizados (Ollama, vLLM, LM Studio, proxies) mediante ~/.pi/agent/models.json.
Tabla de contenidos
Sección titulada «Tabla de contenidos»- Ejemplo mínimo
- Ejemplo completo
- API compatibles
- Configuración del proveedor
- Configuración del modelo
- Sobrescribir proveedores integrados
- Sobrescrituras por modelo
- Compatibilidad con Anthropic Messages
- Compatibilidad con OpenAI
Ejemplo mínimo
Sección titulada «Ejemplo mínimo»Para modelos locales (Ollama, LM Studio, vLLM), solo se requiere id por modelo:
{ "providers": { "ollama": { "baseUrl": "http://localhost:11434/v1", "api": "openai-completions", "apiKey": "ollama", "models": [ { "id": "llama3.1:8b" }, { "id": "qwen2.5-coder:7b" } ] } }}apiKey es obligatorio, pero Ollama lo ignora, así que cualquier valor sirve.
Algunos servidores compatibles con OpenAI no entienden el rol developer usado para modelos con capacidad de razonamiento. Para esos proveedores, establece compat.supportsDeveloperRole en false para que pi envíe el prompt del sistema como mensaje system. Si el servidor tampoco admite reasoning_effort, establece también compat.supportsReasoningEffort en false.
Puedes configurar compat a nivel de proveedor para aplicarlo a todos los modelos, o a nivel de modelo para sobrescribir uno concreto. Esto es habitual en Ollama, vLLM, SGLang y servidores similares compatibles con OpenAI.
{ "providers": { "ollama": { "baseUrl": "http://localhost:11434/v1", "api": "openai-completions", "apiKey": "ollama", "compat": { "supportsDeveloperRole": false, "supportsReasoningEffort": false }, "models": [ { "id": "gpt-oss:20b", "reasoning": true } ] } }}Ejemplo completo
Sección titulada «Ejemplo completo»Sobrescribe los valores predeterminados cuando necesites valores específicos:
{ "providers": { "ollama": { "baseUrl": "http://localhost:11434/v1", "api": "openai-completions", "apiKey": "ollama", "models": [ { "id": "llama3.1:8b", "name": "Llama 3.1 8B (Local)", "reasoning": false, "input": ["text"], "contextWindow": 128000, "maxTokens": 32000, "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } } ] } }}El archivo se recarga cada vez que abres /model. Edita durante la sesión; no hace falta reiniciar.
Ejemplo de Google AI Studio
Sección titulada «Ejemplo de Google AI Studio»Usa google-generative-ai con un baseUrl para añadir modelos de Google AI Studio, incluidas entradas personalizadas de Gemma 4:
{ "providers": { "my-google": { "baseUrl": "https://generativelanguage.googleapis.com/v1beta", "api": "google-generative-ai", "apiKey": "$GEMINI_API_KEY", "models": [ { "id": "gemma-4-31b-it", "name": "Gemma 4 31B", "input": ["text", "image"], "contextWindow": 262144, "reasoning": true } ] } }}baseUrl es obligatorio al añadir modelos personalizados al tipo de API google-generative-ai.
API compatibles
Sección titulada «API compatibles»| API | Descripción |
|---|---|
openai-completions | OpenAI Chat Completions (mayor compatibilidad) |
openai-responses | OpenAI Responses API |
anthropic-messages | Anthropic Messages API |
google-generative-ai | Google Generative AI |
Establece api a nivel de proveedor (predeterminado para todos los modelos) o a nivel de modelo (sobrescritura por modelo).
Configuración del proveedor
Sección titulada «Configuración del proveedor»| Campo | Descripción |
|---|---|
baseUrl | URL del endpoint de la API |
api | Tipo de API (ver arriba) |
apiKey | Clave API (ver resolución de valores abajo) |
headers | Cabeceras personalizadas (ver resolución de valores abajo) |
authHeader | Establece true para añadir automáticamente Authorization: Bearer <apiKey> |
models | Array de configuraciones de modelos |
modelOverrides | Sobrescrituras por modelo para modelos integrados en este proveedor |
Resolución de valores
Sección titulada «Resolución de valores»Los campos apiKey y headers admiten ejecución de comandos, interpolación de entorno y literales:
- Comando shell:
"!command"al inicio ejecuta todo el valor como comando y usa stdout"apiKey": "!security find-generic-password -ws 'anthropic'""apiKey": "!op read 'op://vault/item/credential'" - Interpolación de entorno:
"$ENV_VAR"o"${ENV_VAR}"usa el valor de la variable nombrada. La interpolación funciona dentro de literales más grandes."apiKey": "$MY_API_KEY""apiKey": "${KEY_PREFIX}_${KEY_SUFFIX}"$FOO_BARes la variableFOO_BAR; usa${FOO}_BARcuandoBARes texto literal. Las variables de entorno faltantes dejan el valor sin resolver. - Escapes:
"$$"emite un"$"literal;"$!"emite un"!"literal sin activar la ejecución del comando."apiKey": "$$literal-dollar-prefix""apiKey": "$!literal-bang-prefix" - Valor literal: Se usa directamente. Cadenas en mayúsculas como
MY_API_KEYson literales; usa$MY_API_KEYpara variables de entorno."apiKey": "sk-..."
Para models.json, los comandos shell se resuelven en el momento de la solicitud. pi no aplica intencionalmente TTL integrado, reutilización obsoleta ni lógica de recuperación para comandos arbitrarios. Diferentes comandos necesitan distintas estrategias de caché y fallo, y pi no puede inferir la correcta.
Si tu comando es lento, costoso, tiene límite de tasa o debe seguir usando un valor anterior en fallos transitorios, envuélvelo en tu propio script o comando que implemente el comportamiento de caché o TTL que desees.
Las comprobaciones de disponibilidad de /model usan la presencia de auth configurada y no ejecutan comandos shell.
Cabeceras personalizadas
Sección titulada «Cabeceras personalizadas»{ "providers": { "custom-proxy": { "baseUrl": "https://proxy.example.com/v1", "apiKey": "$MY_API_KEY", "api": "anthropic-messages", "headers": { "x-portkey-api-key": "$PORTKEY_API_KEY", "x-secret": "!op read 'op://vault/item/secret'" }, "models": [...] } }}Configuración del modelo
Sección titulada «Configuración del modelo»| Campo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|
id | Sí | — | Identificador del modelo (se pasa a la API) |
name | No | id | Etiqueta legible del modelo. Se usa para coincidencias (patrones --model) y se muestra como texto de detalle secundario del modelo. |
api | No | api del proveedor | Sobrescribe la API del proveedor para este modelo |
reasoning | No | false | Admite pensamiento extendido |
thinkingLevelMap | No | omitido | Mapea niveles de pensamiento de pi a valores del proveedor y marca niveles no admitidos (ver abajo) |
input | No | ["text"] | Tipos de entrada: ["text"] o ["text", "image"] |
contextWindow | No | 128000 | Tamaño de la ventana de contexto en tokens |
maxTokens | No | 16384 | Máximo de tokens de salida |
cost | No | todos ceros | {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0} (por millón de tokens) |
compat | No | compat del proveedor | Sobrescrituras de compatibilidad del proveedor. Se fusiona con compat a nivel de proveedor cuando ambos están definidos. |
Comportamiento actual:
/model,--list-modelsy el pie interactivo muestran entradas poridde modelo.- El
nameconfigurado se usa para coincidencias de modelos y texto de detalle secundario. No reemplaza el id del modelo en el pie/barra de estado.
Mapa de niveles de pensamiento
Sección titulada «Mapa de niveles de pensamiento»Usa thinkingLevelMap en un modelo para describir controles de pensamiento específicos del modelo. Las claves son niveles de pensamiento de pi: off, minimal, low, medium, high, xhigh.
Los valores son triestado:
| Valor | Significado |
|---|---|
| omitido | El nivel está admitido y usa el mapeo predeterminado del proveedor |
| string | El nivel está admitido y este valor se envía al proveedor |
null | El nivel no está admitido y se oculta/omite/limita |
Ejemplo para un modelo que solo admite razonamiento off, high y max:
{ "id": "deepseek-v4-pro", "reasoning": true, "thinkingLevelMap": { "minimal": null, "low": null, "medium": null, "high": "high", "xhigh": "max" }}Ejemplo para un modelo en el que el pensamiento no se puede desactivar:
{ "id": "always-thinking-model", "reasoning": true, "thinkingLevelMap": { "off": null }}Migración: las configuraciones antiguas que usaban compat.reasoningEffortMap deben mover ese mapeo a thinkingLevelMap a nivel de modelo. Usa null para niveles que no deben aparecer en la UI.
Sobrescribir proveedores integrados
Sección titulada «Sobrescribir proveedores integrados»Enruta un proveedor integrado a través de un proxy sin redefinir modelos:
{ "providers": { "anthropic": { "baseUrl": "https://my-proxy.example.com/v1" } }}Todos los modelos Anthropic integrados siguen disponibles. La auth OAuth o de clave API existente sigue funcionando.
Para fusionar modelos personalizados en un proveedor integrado, incluye el array models:
{ "providers": { "anthropic": { "baseUrl": "https://my-proxy.example.com/v1", "apiKey": "$ANTHROPIC_API_KEY", "api": "anthropic-messages", "models": [...] } }}Semántica de fusión:
- Se conservan los modelos integrados.
- Los modelos personalizados se actualizan por
iddentro del proveedor. - Si el
idde un modelo personalizado coincide con el de un modelo integrado, el personalizado reemplaza al integrado. - Si el
iddel modelo personalizado es nuevo, se añade junto a los modelos integrados.
Sobrescrituras por modelo
Sección titulada «Sobrescrituras por modelo»Usa modelOverrides para personalizar modelos integrados concretos sin reemplazar la lista completa de modelos del proveedor.
{ "providers": { "openrouter": { "modelOverrides": { "anthropic/claude-sonnet-4": { "name": "Claude Sonnet 4 (Bedrock Route)", "compat": { "openRouterRouting": { "only": ["amazon-bedrock"] } } } } } }}modelOverrides admite estos campos por modelo: name, reasoning, input, cost (parcial), contextWindow, maxTokens, headers, compat.
Notas de comportamiento:
modelOverridesse aplican a modelos de proveedores integrados.- Los ID de modelo desconocidos se ignoran.
- Puedes combinar
baseUrl/headersa nivel de proveedor conmodelOverrides. - Sobrescribir
namecambia solo la coincidencia de modelos y el texto de detalle secundario; el pie y las listas principales de modelos siguen mostrando eliddel modelo. - Si también se define
modelspara un proveedor, los modelos personalizados se fusionan después de las sobrescrituras integradas. Un modelo personalizado con el mismoidreemplaza la entrada del modelo integrado sobrescrito.
Compatibilidad con Anthropic Messages
Sección titulada «Compatibilidad con Anthropic Messages»Para proveedores o proxies que usan api: "anthropic-messages", usa compat para controlar la compatibilidad de solicitudes específica de Anthropic.
Por defecto pi envía eager_input_streaming: true por herramienta. Si un proxy o backend compatible con Anthropic rechaza ese campo, establece supportsEagerToolInputStreaming en false. Pi omitirá tools[].eager_input_streaming y enviará la cabecera beta heredada fine-grained-tool-streaming-2025-05-14 en solicitudes con herramientas habilitadas.
Algunos modelos Anthropic requieren pensamiento adaptativo (thinking.type: "adaptive" más output_config.effort) en lugar del payload de pensamiento heredado basado en presupuesto. Los modelos integrados lo configuran automáticamente. Para proveedores personalizados o alias que enrutan a esos modelos, establece forceAdaptiveThinking en true.
Algunos proveedores compatibles con Anthropic emiten bloques de pensamiento con firmas vacías y aún los esperan en la reproducción. Establece allowEmptySignature en true solo para esos proveedores; Anthropic real rechaza firmas de pensamiento vacías.
{ "providers": { "anthropic-proxy": { "baseUrl": "https://proxy.example.com", "api": "anthropic-messages", "apiKey": "$ANTHROPIC_PROXY_KEY", "compat": { "supportsEagerToolInputStreaming": false, "supportsLongCacheRetention": true, "forceAdaptiveThinking": true, "allowEmptySignature": true }, "models": [ { "id": "claude-opus-4-7", "reasoning": true, "input": ["text", "image"] } ] } }}| Campo | Descripción |
|---|---|
supportsEagerToolInputStreaming | Si el proveedor acepta eager_input_streaming por herramienta. Predeterminado: true. Establece en false para omitir ese campo y usar la cabecera beta heredada de streaming fino de herramientas en solicitudes con herramientas. |
supportsLongCacheRetention | Si el proveedor acepta retención larga de caché de Anthropic (cache_control.ttl: "1h") cuando la retención de caché es long. Predeterminado: true. |
sendSessionAffinityHeaders | Si enviar x-session-affinity desde el id de sesión cuando la caché está habilitada. Predeterminado: autodetectado para proveedores conocidos. |
supportsCacheControlOnTools | Si el proveedor acepta marcadores cache_control al estilo Anthropic en definiciones de herramientas. Predeterminado: true. |
forceAdaptiveThinking | Si enviar pensamiento adaptativo (thinking.type: "adaptive" más output_config.effort) para este modelo. Los modelos adaptativos integrados lo configuran automáticamente. Predeterminado: false. |
allowEmptySignature | Si reproducir firmas de pensamiento vacías como signature: "" en lugar de convertir el pensamiento a texto. Predeterminado: false. |
Compatibilidad con OpenAI
Sección titulada «Compatibilidad con OpenAI»Para proveedores con compatibilidad parcial con OpenAI, usa el campo compat.
compata nivel de proveedor aplica valores predeterminados a todos los modelos bajo ese proveedor.compata nivel de modelo sobrescribe los valores a nivel de proveedor para ese modelo.
{ "providers": { "local-llm": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "compat": { "supportsUsageInStreaming": false, "maxTokensField": "max_tokens" }, "models": [...] } }}| Campo | Descripción |
|---|---|
supportsStore | El proveedor admite el campo store |
supportsDeveloperRole | Usar rol developer frente a system |
supportsReasoningEffort | Soporte del parámetro reasoning_effort |
supportsUsageInStreaming | Admite stream_options: { include_usage: true } (predeterminado: true) |
maxTokensField | Usar max_completion_tokens o max_tokens |
requiresToolResultName | Incluir name en mensajes de resultado de herramienta |
requiresAssistantAfterToolResult | Insertar un mensaje assistant antes de un mensaje user tras resultados de herramienta |
requiresThinkingAsText | Convertir bloques de pensamiento a texto plano |
requiresReasoningContentOnAssistantMessages | Incluir reasoning_content vacío en todos los mensajes assistant reproducidos cuando el razonamiento está habilitado |
thinkingFormat | Usar parámetros de pensamiento reasoning_effort, openrouter, deepseek, together, zai, qwen o qwen-chat-template |
cacheControlFormat | Usar marcadores cache_control al estilo Anthropic en el prompt del sistema, la última definición de herramienta y el último contenido de texto user/assistant. Actualmente solo se admite anthropic. |
supportsStrictMode | Incluir el campo strict en definiciones de herramientas |
supportsLongCacheRetention | Si el proveedor acepta retención larga de caché cuando la retención es long: prompt_cache_retention: "24h" para caché de prompts de OpenAI, o cache_control.ttl: "1h" cuando cacheControlFormat es anthropic. Predeterminado: true. |
openRouterRouting | Preferencias de enrutamiento de proveedor de OpenRouter. Este objeto se envía tal cual en el campo provider de la solicitud a la API de OpenRouter. |
vercelGatewayRouting | Configuración de enrutamiento de Vercel AI Gateway para selección de proveedor (only, order) |
openrouter usa reasoning: { effort }. together usa reasoning: { enabled } y también reasoning_effort cuando supportsReasoningEffort está habilitado. qwen usa enable_thinking de nivel superior. Usa qwen-chat-template para servidores locales compatibles con Qwen que requieren chat_template_kwargs.enable_thinking.
cacheControlFormat: "anthropic" es para proveedores compatibles con OpenAI que exponen caché de prompts al estilo Anthropic mediante marcadores cache_control en contenido de texto y definiciones de herramientas.
Ejemplo:
{ "providers": { "openrouter": { "baseUrl": "https://openrouter.ai/api/v1", "apiKey": "$OPENROUTER_API_KEY", "api": "openai-completions", "models": [ { "id": "openrouter/anthropic/claude-3.5-sonnet", "name": "OpenRouter Claude 3.5 Sonnet", "compat": { "openRouterRouting": { "allow_fallbacks": true, "require_parameters": false, "data_collection": "deny", "zdr": true, "enforce_distillable_text": false, "order": ["anthropic", "amazon-bedrock", "google-vertex"], "only": ["anthropic", "amazon-bedrock"], "ignore": ["gmicloud", "friendli"], "quantizations": ["fp16", "bf16"], "sort": { "by": "price", "partition": "model" }, "max_price": { "prompt": 10, "completion": 20 }, "preferred_min_throughput": { "p50": 100, "p90": 50 }, "preferred_max_latency": { "p50": 1, "p90": 3, "p99": 5 } } } } ] } }}Ejemplo de Vercel AI Gateway:
{ "providers": { "vercel-ai-gateway": { "baseUrl": "https://ai-gateway.vercel.sh/v1", "apiKey": "$AI_GATEWAY_API_KEY", "api": "openai-completions", "models": [ { "id": "moonshotai/kimi-k2.5", "name": "Kimi K2.5 (Fireworks via Vercel)", "reasoning": true, "input": ["text", "image"], "cost": { "input": 0.6, "output": 3, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 262144, "maxTokens": 262144, "compat": { "vercelGatewayRouting": { "only": ["fireworks", "novita"], "order": ["fireworks", "novita"] } } } ] } }}