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 llama

Select it with /model or:

pi --model ollama/llama3.1:8

18.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:

  1. Set "reasoning": true
  2. Optionally configure thinkingLevelMap
  3. Use Shift+Tab to 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 AIx-session-affinity set automatically
  • OpenAI — not configurable via models.json
Tip

For providers that need custom OAuth flows or non-standard APIs, create an extension using pi.registerProvider(). See the Custom Providers chapter.