18 Custom Models
18.1 Overview
Pi supports any provider that speaks a supported API. Custom models are configured via models.json files, allowing you to add local models, self-hosted endpoints, or providers not built into Pi.
18.2 Configuration Files
Custom models are defined in JSON files at these locations:
| Location | Scope |
|---|---|
~/.pi/agent/models.json |
Global (all projects) |
.pi/models.json |
Project-local |
Project-local models are merged with global models. When the same provider name exists in both, the project-local version takes precedence.
18.3 Minimal Example
Add a single model from a local Ollama instance:
[
{
"provider": "ollama",
"baseUrl": "http://localhost:11434/v1",
"apiKey": "ollama",
"api": "openai-completions",
"models": [
{
"id": "llama3.1:8",
"name": "Llama 3.1 8B",
"reasoning": false,
"input": ["text"],
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
"contextWindow": 128000,
"maxTokens": 4096
}
]
}
]After saving, verify the model is available:
pi --list-models llamaSelect it with /model or:
pi --model ollama/llama3.1:818.4 Full Example
[
{
"provider": "local-vllm",
"baseUrl": "http://localhost:8000/v1",
"apiKey": "$VLLM_API_KEY",
"api": "openai-completions",
"headers": {
"X-Custom-Header": "my-value"
},
"models": [
{
"id": "codellama-34b",
"name": "CodeLlama 34B",
"reasoning": false,
"input": ["text"],
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
"contextWindow": 16384,
"maxTokens": 4096
},
{
"id": "deepseek-coder-33b",
"name": "DeepSeek Coder 33B",
"reasoning": false,
"input": ["text"],
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite: 0 },
"contextWindow": 16384,
"maxTokens": 4096
}
]
},
{
"provider": "lm-studio",
"baseUrl": "http://localhost:1234/v1",
"apiKey": "lm-studio",
"api": "openai-completions",
"models": [
{
"id": "qwen2.5-coder-32b",
"name": "Qwen 2.5 Coder 32B",
"reasoning": false,
"input": ["text"],
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
"contextWindow": 32768,
"maxTokens": 8192
}
]
}
]18.5 Supported APIs
Pi supports the following API types:
| API | Value | Description |
|---|---|---|
| OpenAI Chat Completions | "openai-completions" |
OpenAI-compatible /v1/chat/completions endpoint |
| Anthropic Messages | "anthropic" |
Anthropic /v1/messages endpoint |
| Google Gemini | "google" |
Google Gemini API |
| Bedrock | "bedrock" |
Amazon Bedrock API |
The most common for custom models is "openai-completions", as it is the standard API for:
- Ollama (
http://localhost:11434/v1) - LM Studio (
http://localhost:1234/v1) - vLLM (
http://localhost:8000/v1) - llama.cpp server (
http://localhost:8080/v1) - Any OpenAI-compatible endpoint
18.6 Provider Configuration
| Field | Type | Required | Description |
|---|---|---|---|
provider |
string |
Yes | Unique provider name |
baseUrl |
string |
Yes | API base URL |
apiKey |
string |
No | API key (supports $ENV_VAR, !command, literal) |
api |
string |
No | API type (default: "openai-completions") |
headers |
object |
No | Custom HTTP headers |
models |
array |
Yes | Array of model definitions |
oauth |
string |
No | OAuth type ("radius") for Radius gateways |
18.7 Value Resolution
The apiKey field supports multiple resolution strategies:
18.7.1 Environment Interpolation
{
"apiKey": "$MY_API_KEY"
}Uses the value of the MY_API_KEY environment variable.
18.7.2 Command Execution
{
"apiKey": "!security find-generic-password -ws 'my-key'"
}Executes the command and uses stdout as the key.
18.7.3 Literal Value
{
"apiKey": "sk-my-literal-key"
}Plain uppercase strings such as MY_API_KEY are treated as literals. Use $MY_API_KEY for environment variables.
18.7.4 Escapes
"$$"emits a literal$"$!"emits a literal!
The same resolution applies to headers values.
18.8 Custom Headers
Add custom HTTP headers to all API requests:
{
"provider": "my-service",
"baseUrl": "https://api.my-service.com/v1",
"headers": {
"X-API-Version": "2024-01-01",
"X-Tenant-ID": "$TENANT_ID",
"Authorization": "Bearer $SERVICE_TOKEN"
}
}18.9 Model Configuration
Each model in the models array has:
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Model ID sent to the API |
name |
string |
No | Display name (defaults to ID) |
reasoning |
boolean |
No | Whether the model supports thinking/reasoning |
input |
string[] |
No | Input modalities: ["text"], ["text", "image"] |
cost |
object |
No | Cost per million tokens |
contextWindow |
number |
No | Maximum context window in tokens |
maxTokens |
number |
No | Maximum output tokens |
thinkingLevelMap |
object |
No | Maps Pi thinking levels to provider-specific values |
18.9.1 Cost Object
{
"cost": {
"input": 3.00,
"output": 15.00,
"cacheRead": 0.30,
"cacheWrite": 3.75
}
}All values are per million tokens in USD. Set to 0 for free/local models.
18.10 thinkingLevelMap
For models that support reasoning but use different terminology:
{
"id": "my-reasoning-model",
"reasoning": true,
"thinkingLevelMap": {
"off": "none",
"minimal": "low",
"low": "low",
"medium": "medium",
"high": "high",
"xhigh": "high",
"max": "maximum"
}
}Maps Pi’s thinking levels to the model’s expected values. If omitted, Pi passes the level directly.
18.11 Overriding Built-ins
You can override built-in provider and model definitions by using the same provider name:
[
{
"provider": "anthropic",
"baseUrl": "https://my-proxy.example.com/v1",
"apiKey": "$PROXY_API_KEY",
"api": "anthropic",
"models": [
{
"id": "claude-sonnet-4-20250514",
"name": "Claude Sonnet 4 (via proxy)",
"reasoning": true,
"input": ["text", "image"],
"cost": { "input": 3, "output": 15, "cacheRead": 0.3, "cacheWrite": 3.75 },
"contextWindow": 200000,
"maxTokens": 16384
}
]
}
]This replaces all models from the built-in anthropic provider with your custom definitions.
18.12 Per-Model Overrides
Override specific fields of built-in models without replacing the entire provider:
[
{
"provider": "anthropic",
"models": [
{
"id": "claude-sonnet-4-20250514",
"contextWindow": 128000,
"maxTokens": 8192
}
]
}
]This keeps all built-in Anthropic models but overrides specific fields for the specified model ID.
18.13 Compatibility
18.13.1 Modalities
| Input Type | Support |
|---|---|
| Text | All APIs |
| Image | OpenAI, Anthropic, Google |
| Video | Google Gemini only |
| Audio | Not supported via config |
18.13.2 Reasoning Models
For models that use chain-of-thought or extended thinking:
- Set
"reasoning": true - Optionally configure
thinkingLevelMap - Use
Shift+Tabto cycle thinking level
18.13.3 Context Caching
Some providers support prompt caching:
- Anthropic — automatic for Claude models
- Google — automatic for Gemini models
- Cloudflare Workers AI —
x-session-affinityset automatically - OpenAI — not configurable via models.json
For providers that need custom OAuth flows or non-standard APIs, create an extension using pi.registerProvider(). See the Custom Providers chapter.