跳转到主要内容

16 自定义模型

通过 ~/.pi/agent/models.json 添加自定义提供商与模型,配置兼容性与逐模型覆盖。

通过 ~/.pi/agent/models.json 添加自定义提供商与模型(Ollama、vLLM、LM Studio、代理)。

目录

最小示例

对于本地模型(Ollama、LM Studio、vLLM),每个模型只需提供 id:

{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai-completions",
      "apiKey": "ollama",
      "models": [
        { "id": "llama3.1:8b" },
        { "id": "qwen2.5-coder:7b" }
      ]
    }
  }
}

apiKey 的值只是一个占位符,因为 Ollama 会忽略它。pi 仍要求模型在通过认证之后才会出现在 /model 中,因此不带密钥的本地服务器应保留一个虚拟值,用 /login 为该提供商保存一个密钥,或在选择模型时传入 --api-key。

一些 OpenAI 兼容服务器无法理解推理模型所用的 developer 角色。对于这类提供商,将 compat.supportsDeveloperRole 设为 false,pi 便会改用 system 消息来发送系统提示词。如果服务器同样不支持 reasoning_effort,请将 compat.supportsReasoningEffort 也设为 false。

compat 可以设在提供商级别,作用于所有模型;也可以设在模型级别,为特定模型做覆盖。这常见于 Ollama、vLLM、SGLang 以及类似的 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
        }
      ]
    }
  }
}

完整示例

在需要具体数值时覆盖默认值:

{
  "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 }
        }
      ]
    }
  }
}

每次打开 /model 时该文件都会重新加载。可以在会话中随时编辑,无需重启。

Google AI Studio 示例

配合 baseUrl 使用 google-generative-ai,即可添加来自 Google AI Studio 的模型,包括自定义的 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
        }
      ]
    }
  }
}

为 google-generative-ai API 类型添加自定义模型时,必须提供 baseUrl。

支持的 API

API说明
openai-completionsOpenAI Chat Completions(兼容性最好)
openai-responsesOpenAI Responses API
anthropic-messagesAnthropic Messages API
google-generative-aiGoogle Generative AI

在提供商级别设置 api(作为所有模型的默认值),或在模型级别设置(按模型覆盖)。

提供商配置

字段说明
baseUrlAPI 端点 URL
apiAPI 类型(见上文)
apiKey可选的 API 密钥配置(取值解析见下文)。当认证由 /login/auth.json 或 CLI 的 --api-key 提供时,可将其省略。
oauth动态 OAuth 提供商类型。目前支持 "radius";需要网关的 baseUrl。
headers自定义请求头(取值解析见下文)
authHeader设为 true 时自动添加 Authorization: Bearer <apiKey>
models模型配置数组
modelOverrides针对该提供商的内置模型或扩展注册模型进行逐模型覆盖

对于带 models 的提供商,非内置的提供商配置需要在提供商级别或模型级别提供 baseUrl 和 api 值。加载该文件本身并不要求 apiKey:当认证通过 /login/auth.json、CLI --api-key 或提供商 apiKey 配置好之后,模型即可使用。如果未配置任何认证,模型仍会加载,但在 /model 和 --list-models 中不可用。

取值解析

apiKey 与 headers 字段支持命令执行、环境变量插值和字面量:

  • Shell 命令: 值以 "!command" 开头时,整个值会作为命令执行,并使用其 stdout
    "apiKey": "!security find-generic-password -ws 'anthropic'"
    "apiKey": "!op read 'op://vault/item/credential'"
  • 环境变量插值: "$ENV_VAR" 或 "${ENV_VAR}" 使用指定变量的值。插值也可以在更长的字面量内部使用。
    "apiKey": "$MY_API_KEY"
    "apiKey": "${KEY_PREFIX}_${KEY_SUFFIX}"
    $FOO_BAR 指向变量 FOO_BAR;如果 BAR 是字面文本,应使用 ${FOO}_BAR。环境变量缺失时,该值视为未解析。
  • 转义: "$$" 输出字面量 "$";"$!" 输出字面量 "!",且不会触发命令执行。
    "apiKey": "$$literal-dollar-prefix"
    "apiKey": "$!literal-bang-prefix"
  • 字面量: 直接使用原值。MY_API_KEY 这类纯大写字符串就是字面量;要引用环境变量请写 $MY_API_KEY。
    "apiKey": "sk-..."

对于 models.json,shell 命令在请求时解析。pi 有意不为任意命令应用内置的 TTL、过期复用或恢复逻辑——不同的命令需要不同的缓存与失败策略,pi 无法推断出正确的做法。

如果你的命令较慢、开销大、受速率限制,或希望在偶发失败时沿用先前的值,请把它包装进你自己的脚本或命令中,实现你想要的缓存或 TTL 行为。

/model 的可用性检查只依据已配置的认证信息,不会执行 shell 命令。

自定义请求头

{
  "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": [...]
    }
  }
}

模型配置

字段必填默认值说明
id是—模型标识符(传给 API)
name否id人类可读的模型标签。用于模型匹配(--model 的模式),并作为次要模型详情文本显示。
api否提供商的 api为该模型覆盖提供商的 API
reasoning否false支持扩展思考
thinkingLevelMap否省略将 pi 的思考级别映射为提供商取值,并标记不支持的级别(见下文)
input否["text"]输入类型:["text"] 或 ["text", "image"]
contextWindow否128000上下文窗口大小(token 数)
maxTokens否16384最大输出 token 数
samplingParams否省略逐字合并进每个请求体的采样参数(见下文)
cost否全为零每百万 token 费率,可选按请求整体生效的输入定价分层
compat否提供商的 compat提供商兼容性覆盖。两者都设置时会与提供商级别的 compat 合并。

费用分层提供一套完整的替代费率;当总输入用量(input + cacheRead + cacheWrite)超过 inputTokensAbove 时,对整个请求生效。多个分层同时命中时,以阈值最高的为准。

{
  "cost": {
    "input": 5,
    "output": 30,
    "cacheRead": 0.5,
    "cacheWrite": 6.25,
    "tiers": [
      {
        "inputTokensAbove": 272000,
        "input": 10,
        "output": 45,
        "cacheRead": 1,
        "cacheWrite": 12.5
      }
    ]
  }
}

当前行为:

  • /model、--list-models 和交互式页脚均按模型 id 显示条目。
  • 配置的 name 用于模型匹配和次要模型详情文本,不会替换页脚/状态栏中的模型 id。

采样参数

samplingParams 是一个自由格式的对象,会逐字合并进该模型的每个请求体,并且合并在 pi 自身设置的字段之后,因此它的键优先。可用它发送 pi 未建模的采样参数——包括服务器特有的参数,比如 llama.cpp 的 min_p 或 vLLM 的 top_k:

{
  "id": "deepseek-v4-flash",
  "samplingParams": {
    "temperature": 1.0,
    "top_p": 0.95,
    "top_k": 0,
    "min_p": 0.0
  }
}

只有 OpenAI 兼容 API 会应用它(openai-completions、openai-responses、azure-openai-responses);其他 API 会忽略它。其中的键会覆盖 pi 的具名请求字段(例如这里的 temperature 键优先于请求级别的 temperature),因此建议将它作为模型采样参数的唯一事实来源。在 modelOverrides 中,samplingParams 会按键与基础模型的值合并。

固定的思考 token 上限也可以放在这里,但它不会跟随 thinkingBudgets 变化,也不会为回答预留空间。这类需求请优先使用 compat.thinkingTokenBudgetField(或其别名 supportsThinkingTokenBudget)。

思考级别映射

在模型上使用 thinkingLevelMap 来描述该模型特有的思考控制。键是 pi 的思考级别:off、minimal、low、medium、high、xhigh、max。映射可以留有空档,例如某模型可以提供 high 和 max,而不提供 xhigh。

取值有三态:

取值含义
省略high 及以下的标准级别使用提供商的默认映射;扩展的 xhigh 和 max 级别视为不支持
string该级别受支持,此值会发送给提供商
null该级别不受支持,会被隐藏/跳过/钳制掉

仅支持 off、high 和 max 推理的模型示例:

{
  "id": "deepseek-v4-pro",
  "reasoning": true,
  "thinkingLevelMap": {
    "minimal": null,
    "low": null,
    "medium": null,
    "high": "high",
    "xhigh": null,
    "max": "max"
  }
}

思考无法被禁用的模型示例:

{
  "id": "always-thinking-model",
  "reasoning": true,
  "thinkingLevelMap": {
    "off": null
  }
}

迁移:旧配置中使用的 compat.reasoningEffortMap 应迁移到模型级别的 thinkingLevelMap。对不应出现在 UI 中的级别使用 null。

覆盖内置提供商

将内置提供商的请求路由到代理,而无需重新定义模型:

{
  "providers": {
    "anthropic": {
      "baseUrl": "https://my-proxy.example.com/v1"
    }
  }
}

所有内置 Anthropic 模型仍然可用。已有的 OAuth 或 API 密钥认证继续有效。

要将自定义模型合并进内置提供商,请加上 models 数组:

{
  "providers": {
    "anthropic": {
      "baseUrl": "https://my-proxy.example.com/v1",
      "apiKey": "$ANTHROPIC_API_KEY",
      "api": "anthropic-messages",
      "models": [...]
    }
  }
}

合并语义:

  • 保留内置模型。
  • 自定义模型按 id 在该提供商内执行 upsert。
  • 如果自定义模型的 id 与某个内置模型相同,该自定义模型会替换对应的内置模型。
  • 如果自定义模型的 id 是新的,会与内置模型并列添加。

按模型覆盖

使用 modelOverrides 定制内置模型以及匹配的扩展注册模型,而无需替换该提供商的完整模型列表。

{
  "providers": {
    "openrouter": {
      "modelOverrides": {
        "anthropic/claude-sonnet-4": {
          "name": "Claude Sonnet 4 (Bedrock Route)",
          "compat": {
            "openRouterRouting": {
              "only": ["amazon-bedrock"]
            }
          }
        }
      }
    }
  }
}

modelOverrides 支持按模型设置以下字段:name、reasoning、thinkingLevelMap、input、cost(部分)、contextWindow、maxTokens、samplingParams(按键合并)、headers、compat。

OpenAI 直连的 GPT-5.6 Sol、Terra 和 Luna 默认使用 272000 上下文窗口,以便请求保持在 OpenAI 的短上下文定价档内。要启用 OpenAI 的 1.05M 上下文窗口,请为你使用的每个模型调大该值:

{
  "providers": {
    "openai": {
      "modelOverrides": {
        "gpt-5.6-sol": {
          "contextWindow": 1050000
        }
      }
    }
  }
}

该覆盖会保留内置的定价元数据。总输入 token 超过 272K 的请求,将按 GPT-5.6 的长上下文费率对整个请求计费。需要时可对 gpt-5.6-terra 或 gpt-5.6-luna 应用相同的覆盖。

行为说明:

  • modelOverrides 应用于内置提供商模型以及匹配的扩展注册提供商模型。
  • 未知的模型 ID 会被忽略。
  • 可以将提供商级别的 baseUrl/headers 与 modelOverrides 组合使用。
  • 覆盖 name 只影响模型匹配和次要详情文本;页脚和主模型列表仍显示模型 id。
  • 如果提供商还定义了 models,自定义模型会在内置覆盖之后合并。id 相同的自定义模型会替换已被覆盖的内置模型条目。

Anthropic Messages 兼容性

对于使用 api: "anthropic-messages" 的提供商或代理,可用 compat 控制 Anthropic 特有的请求兼容性。

默认情况下,pi 会为每个工具发送 eager_input_streaming: true。如果代理或 Anthropic 兼容后端拒绝该字段,请将 supportsEagerToolInputStreaming 设为 false。这样 pi 会省略 tools[].eager_input_streaming,并在带工具的请求中改用旧版 fine-grained-tool-streaming-2025-05-14 beta 头。

某些 Anthropic 模型需要自适应思考(thinking.type: "adaptive" 加 output_config.effort),而不是传统的基于预算的思考负载。内置模型会自动设置。对于路由到这些模型的自定义提供商或别名,请将 forceAdaptiveThinking 设为 true。

支持逐轮 effort 的 Claude 模型使用 supportsMidConvoEffort。启用后,pi 会持久化每个响应中提供商返回的 effort,在后续请求中重建仅含 effort 的系统消息,并发送带 prefix_mismatch_behavior: "drop_block" 的思考绑定控制,以避免过期的已签名思考前缀导致持续的 400 响应。请只对忠实实现 Anthropic Messages 传输且确切受支持的 Claude 模型启用它;不要为仅模仿 Messages 结构的 API 启用。

某些 Anthropic 兼容提供商会输出签名为空的思考块,并且期望在回放时原样传回。只应对这类提供商将 allowEmptySignature 设为 true;真正的 Anthropic 会拒绝空的思考签名。

内置 Anthropic 模型在模型元数据中启用了 supportsStrictTools。自定义的 Anthropic 兼容模型,在其端点接受严格 JSON schema 工具定义时,必须将它设为 true。

{
  "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"]
        }
      ]
    }
  }
}
字段说明
supportsEagerToolInputStreaming提供商是否接受逐工具的 eager_input_streaming。默认:true。设为 false 可省略该字段,并在带工具的请求中使用旧版细粒度工具流式 beta 头。
supportsLongCacheRetention当缓存保留策略为 long 时,提供商是否接受 Anthropic 长缓存保留(cache_control.ttl: "1h")。默认:true。
sendSessionAffinityHeaders启用缓存时,是否根据会话 id 发送 x-session-affinity。默认:对已知提供商自动检测。
supportsCacheControlOnTools提供商是否接受工具定义上的 Anthropic 风格 cache_control 标记。默认:true。
forceAdaptiveThinking是否为该模型发送自适应思考(thinking.type: "adaptive" 加 output_config.effort)。内置自适应模型会自动设置。默认:false。
supportsMidConvoEffort确切的 Claude 模型传输是否支持逐轮 effort 系统消息与思考绑定控制。启用后,pi 会持久化原生 effort 级别并始终发送 drop_block。默认:false。
allowEmptySignature回放时是否将空思考签名保持为 signature: "",而不是把思考转换为文本。默认:false。
supportsStrictTools提供商是否接受严格 JSON schema 工具定义。默认:false;内置 Anthropic 模型在生成的元数据中启用了它。

OpenAI 兼容性

对于只部分兼容 OpenAI 的提供商,使用 compat 字段。

  • 提供商级别的 compat 为该提供商下的所有模型提供默认值。
  • 模型级别的 compat 为该模型覆盖提供商级别的值。
{
  "providers": {
    "local-llm": {
      "baseUrl": "http://localhost:8080/v1",
      "api": "openai-completions",
      "compat": {
        "supportsUsageInStreaming": false,
        "maxTokensField": "max_tokens"
      },
      "models": [...]
    }
  }
}
字段说明
supportsStore提供商支持 store 字段
supportsDeveloperRole使用 developer 还是 system 角色
supportsReasoningEffort是否支持 reasoning_effort 参数
supportsUsageInStreaming是否支持 stream_options: { include_usage: true }(默认:true)
supportsFinishReason流式响应是否包含 finish_reason。为 false 时,pi 会在流结束时推断 stop 或 toolUse。默认:true。
maxTokensField使用 max_completion_tokens 还是 max_tokens
requiresToolResultName在工具结果消息中包含 name
requiresAssistantAfterToolResult在工具结果之后的用户消息前插入一条 assistant 消息
requiresThinkingAsText将思考块转换为纯文本
requiresReasoningContentOnAssistantMessages启用推理时,在所有回放的 assistant 消息上包含空的 reasoning_content
thinkingFormat使用 reasoning_effort、openrouter、deepseek、together、baseten、zai、qwen、chat-template 或 qwen-chat-template 思考参数
chatTemplateKwargsthinkingFormat: "chat-template" 时的 chat_template_kwargs 取值;使用 { "$var": "thinking.enabled" }、{ "$var": "thinking.effort" } 或 { "$var": "thinking.budget" } 表示由 pi 控制的思考取值
chatTemplateArgsthinkingFormat: "baseten" 时的 chat_template_args 取值;使用 { "$var": "thinking.enabled" }、{ "$var": "thinking.effort" } 或 { "$var": "thinking.budget" } 表示由 pi 控制的思考取值
thinkingTokenBudgetField用于根据 thinkingBudgets 限制推理 token 的顶层请求字段,并会钳制以确保至少为回答保留 1024 个 token。"thinking_token_budget"(vLLM)、"thinking_budget"(Qwen/DashScope/SGLang)、"thinking_budget_tokens"(llama.cpp)。默认关闭;不会在生成的模型目录上设置。
supportsThinkingTokenBudgetthinkingTokenBudgetField: "thinking_token_budget"(vLLM)的别名。建议优先使用 thinkingTokenBudgetField。默认:false。
cacheControlFormat在系统提示词、最后一个工具定义以及最后一条 user、assistant 或工具结果文本内容上使用 Anthropic 风格的 cache_control 标记。目前仅支持 anthropic。
sendSessionAffinityHeaders对 openai-completions,启用缓存时根据会话 id 发送会话亲和头。默认:false。
sessionAffinityFormat对 openai-completions 和 openai-responses 的会话亲和头格式:openai 发送 session_id/x-client-request-id(completions 还会发送 x-session-affinity);openai-nosession 省略包含下划线的 session_id 头;openrouter 发送 x-session-id。不影响 prompt_cache_key 请求体参数。默认:自动检测。
supportsStrictMode提供商是否接受严格 JSON schema 的函数工具定义。默认值取决于 API;内置 OpenAI 模型带有显式的能力元数据。
supportsOpenAIGrammarToolsOpenAI 兼容 API 是否输出自定义 Lark/正则文法工具。为 false 时,文法约束的工具会退回为普通函数工具。默认:false;内置模型目录为 OpenAI、OpenAI Codex、Azure OpenAI、GitHub Copilot、opencode 和 Cloudflare AI Gateway 上的 GPT-5+ 模型启用了它。
deferredToolsMode使用提供商特有的延迟工具序列化。目前仅支持 "kimi",用于 Kimi 的 OpenAI 兼容 Chat Completions 格式。
supportsLongCacheRetention当缓存保留策略为 long 时,提供商是否接受长缓存保留:GPT-5.6+ Responses 模型使用 prompt_cache_options.ttl: "30m",较早的 OpenAI 模型使用 prompt_cache_retention: "24h",cacheControlFormat 为 anthropic 时使用 cache_control.ttl: "1h"。默认:true。
openRouterRoutingOpenRouter 提供商路由偏好。该对象会原样放入 OpenRouter API 请求的 provider 字段中发送。
vercelGatewayRoutingVercel AI Gateway 用于提供商选择的路由配置(only、order)

openrouter 使用 reasoning: { effort }。together 使用 reasoning: { enabled },并在启用 supportsReasoningEffort 时同时发送 reasoning_effort。qwen 使用顶层的 enable_thinking。对于需要 chat_template_kwargs.enable_thinking 和 preserve_thinking 的本地 Qwen 兼容服务器,使用 qwen-chat-template。对于需要可配置 chat_template_kwargs 的 vLLM/Hugging Face 聊天模板,使用 chat-template,例如 DeepSeek V3.x 模板可用 chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }。对于通过 chat_template_args 暴露开关控制并可选支持顶层 reasoning_effort 的提供商,将 thinkingFormat: "baseten" 与 chatTemplateArgs 搭配使用。

thinkingTokenBudgetField 与 thinkingFormat 相互独立。不要在生成的 Qwen 模型目录上启用它:这些模型已经发送 reasoning_effort,而 DashScope 会拒绝 thinking_budget 与 reasoning_effort 同时出现。

cacheControlFormat: "anthropic" 适用于通过文本内容和工具定义上的 cache_control 标记暴露 Anthropic 风格提示词缓存的 OpenAI 兼容提供商。

示例:

{
  "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
              }
            }
          }
        }
      ]
    }
  }
}

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"]
            }
          }
        }
      ]
    }
  }
}