Providers、Models 与安全模型
Pi 在「统一多 provider」这件事上做得非常彻底——30+ 个内置 provider、4 种 key 解析语法、扩展可动态注册新 provider。但最值得记住的不是支持的 provider 多,而是它对「安全」这件事的态度:明确告知无沙箱,把隔离责任推给 OS / 容器 / 虚拟机。本章拆这两个看似无关、实则同源的设计。
统一 Provider 层(pi-ai)
@earendil-works/pi-ai 是最底层的 LLM 适配包。它把市面上主流的 LLM API 收敛成一个统一接口——上层(agent、coding-agent)不需要关心你用的是 Anthropic、OpenAI 还是 Google。
两类 Provider
| 类型 | 认证方式 | 例子 |
|---|---|---|
| 订阅型 | OAuth via /login | ChatGPT Plus/Pro (Codex)、Claude Pro/Max、GitHub Copilot |
| API Key 型 | 环境变量或 auth.json | OpenAI、Anthropic、Gemini、Mistral、DeepSeek、Groq、Cerebras |
每个 provider 下 Pi 知道其全部可用模型——列表随每次 release 更新,你不需要手动维护 model id。
内置 Provider 清单(节选)
| Provider | Environment Variable | auth.json key |
|---|---|---|
| Anthropic | ANTHROPIC_API_KEY | anthropic |
| OpenAI | OPENAI_API_KEY | openai |
| Google Gemini | GEMINI_API_KEY | google |
| DeepSeek | DEEPSEEK_API_KEY | deepseek |
| Mistral | MISTRAL_API_KEY | mistral |
| Groq | GROQ_API_KEY | groq |
| Cerebras | CEREBRAS_API_KEY | cerebras |
| xAI | XAI_API_KEY | xai |
| OpenRouter | OPENROUTER_API_KEY | openrouter |
| Vercel AI Gateway | AI_GATEWAY_API_KEY | vercel-ai-gateway |
| Hugging Face | HF_TOKEN | huggingface |
| Fireworks | FIREWORKS_API_KEY | fireworks |
| Together AI | TOGETHER_API_KEY | together |
| Kimi For Coding | KIMI_API_KEY | kimi-coding |
| OpenCode Zen | OPENCODE_API_KEY | opencode |
| NVIDIA NIM | NVIDIA_API_KEY | nvidia |
| Cloudflare Workers AI | CLOUDFLARE_API_KEY + CLOUDFLARE_ACCOUNT_ID | cloudflare-workers-ai |
| Cloudflare AI Gateway | CLOUDFLARE_API_KEY + (CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_GATEWAY_ID) | cloudflare-ai-gateway |
| Azure OpenAI | AZURE_OPENAI_API_KEY (+ AZURE_OPENAI_BASE_URL / AZURE_OPENAI_RESOURCE_NAME) | azure-openai-responses |
| Bedrock | AWS profile / IAM keys / bearer token | (env-only) |
| Vertex AI | ADC 或 service account | (env-only) |
| ZAI Coding Plan | ZAI_API_KEY / ZAI_CODING_CN_API_KEY | zai / zai-coding-cn |
完整列表见 packages/ai/src/env-api-keys.ts 的 envMap 常量。
AuthStorage:key 解析的统一入口
AuthStorage 是 Pi 解析凭据的统一接口。它的优先级:
- Runtime overrides(
setRuntimeApiKey,不持久化) auth.json中的存储凭据- 环境变量(
ANTHROPIC_API_KEY等) - Fallback resolver
auth.json 形态
{
"anthropic": { "type": "api_key", "key": "sk-ant-..." },
"openai": { "type": "api_key", "key": "sk-..." }
}
文件以 0600 权限创建(仅用户可读写)。Auth file 凭据优先级高于环境变量。
对于 Cloudflare AI Gateway 这种需要多个参数(API key + account ID + gateway ID)的 provider,auth.json 条目还能携带 env 对象:
{
"cloudflare-ai-gateway": {
"type": "api_key",
"key": "$CLOUDFLARE_API_KEY",
"env": {
"CLOUDFLARE_API_KEY": "...",
"CLOUDFLARE_ACCOUNT_ID": "account-id",
"CLOUDFLARE_GATEWAY_ID": "gateway-id"
}
}
}
env 适用于「让 pi 使用与项目 shell 环境不同的 provider 设置」的所有场景:credential key、provider/model headers、Cloudflare account ID、Azure OpenAI 配置、Vertex project/location、Bedrock 配置、PI_CACHE_RETENTION、HTTP_PROXY/HTTPS_PROXY。
Key 字段的 4 种解析语法
key 字段支持以下形式——这是 Pi 在凭据管理上最精巧的设计:
(a) Shell 命令执行:!command 开头,把整个 value 当命令执行,使用 stdout(缓存于进程生命周期):
{ "type": "api_key", "key": "!security find-generic-password -ws 'anthropic'" }
{ "type": "api_key", "key": "!op read 'op://vault/item/credential'" }
可以直接对接 macOS Keychain、1Password CLI、HashiCorp Vault、Bitwarden 等任意外部凭据存储。
(b) 环境变量插值:$ENV_VAR 或 ${ENV_VAR},插值可嵌在更大的字面量中:
{ "type": "api_key", "key": "$MY_ANTHROPIC_KEY" }
{ "type": "api_key", "key": "${KEY_PREFIX}_${KEY_SUFFIX}" }
$FOO_BAR 表示变量 FOO_BAR;当 BAR 是字面文本时用 ${FOO}_BAR。缺失变量将无法解析。
(c) 转义符:
"$$"→ 字面$"$!"→ 字面!(不触发命令执行)
{ "type": "api_key", "key": "$$literal-dollar-prefix" }
{ "type": "api_key", "key": "$!literal-bang-prefix" }
(d) 字面量:直接使用。纯大写字符串如 MY_API_KEY 视为字面量——要用环境变量请写 $MY_API_KEY。
OAuth 凭据同样存储于该文件,/login 后自动管理。
Cloud Providers
Pi 把几个云厂商单独抽出来,因为它们的配置比 API Key 复杂:
Azure OpenAI
export AZURE_OPENAI_API_KEY=...
export AZURE_OPENAI_BASE_URL=https://your-resource.ai.azure.com
# 也支持 cognitiveservices.azure.com / openai.azure.com
# 根 endpoint 自动归一化为 /openai/v1
# 或用 resource name 代替 base URL
export AZURE_OPENAI_RESOURCE_NAME=your-resource
# 可选
export AZURE_OPENAI_API_VERSION=2024-02-01
export AZURE_OPENAI_DEPLOYMENT_NAME_MAP=gpt-4=my-gpt4,gpt-4o=my-gpt4o
Amazon Bedrock
支持三种认证方式 + ECS task roles + IRSA:
# 方式 1: AWS Profile
export AWS_PROFILE=your-profile
# 方式 2: IAM Keys
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
# 方式 3: Bearer Token
export AWS_BEARER_TOKEN_BEDROCK=...
Prompt caching:对 ID 中含可识别模型名的 Claude 模型自动启用(基础模型与系统定义 inference profiles)。对于 application inference profiles(ARN 不含模型名),需设置 AWS_BEDROCK_FORCE_CACHE=1。
代理场景:
export AWS_ENDPOINT_URL_BEDROCK_RUNTIME=https://my.corp.proxy/bedrock
export AWS_BEDROCK_SKIP_AUTH=1 # 代理无需认证时
export AWS_BEDROCK_FORCE_HTTP1=1 # 代理仅支持 HTTP/1.1 时
Cloudflare AI Gateway
CLOUDFLARE_API_KEY 可通过 /login 设置。Account ID 与 gateway slug 既可设为环境变量,也可放入 auth.json 中 API key 凭据的 env 对象。
4 种认证模式:
| 模式 | 请求认证 | 上游认证 |
|---|---|---|
| Workers AI | 仅 Cloudflare token | Cloudflare native |
| Unified billing | 仅 Cloudflare token | Cloudflare 处理上游认证并扣 credits |
| Stored BYOK | 仅 Cloudflare token | Cloudflare 注入 dashboard 存储的 provider key |
| Inline BYOK | Cloudflare token + 上游 Authorization header | 请求方提供上游 key |
正常 pi 使用推荐 unified billing 或 stored BYOK。Inline BYOK 需通过 models.json provider/model override 配置额外的上游 Authorization header。
Cloudflare Workers AI
export CLOUDFLARE_API_KEY=...
export CLOUDFLARE_ACCOUNT_ID=...
pi --provider cloudflare-workers-ai --model "@cf/moonshotai/kimi-k2.6"
Pi 自动设置 x-session-affinity header 以获得 Cloudflare 的 prefix caching 折扣。
Google Vertex AI
gcloud auth application-default login
export GOOGLE_CLOUD_PROJECT=your-project
export GOOGLE_CLOUD_LOCATION=us-central1
# 或
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa-key.json
自定义 Provider:两种姿势
通过 models.json
添加 Ollama、LM Studio、vLLM 或任何支持以下 API 的 provider——OpenAI Completions、OpenAI Responses、Anthropic Messages、Google Generative AI。详见 models.md。
通过扩展
对需要自定义 API 实现或 OAuth 流程的 provider,创建扩展。详见 custom-provider.md 和示例 examples/extensions/custom-provider-gitlab-duo/(GitLab Duo 自定义 provider)。
扩展方式更强大——可以在 pi.registerProvider() 运行时注册,无需重启。
Model 选择与思考等级
CLI:
# 切换 provider 和 model
pi --provider openai --model gpt-4o "Help me refactor"
# 带 provider 前缀的 model id
pi --model openai/gpt-4o
# 思考等级简写
pi --model sonnet:high "Solve this complex problem"
# 限制 Ctrl+P 循环的模型集合
pi --models "claude-*,gpt-4o"
SDK:
import { getModel } from "@earendil-works/pi-ai";
import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
const authStorage = AuthStorage.create();
const modelRegistry = ModelRegistry.create(authStorage);
const opus = getModel("anthropic", "claude-opus-4-5");
const available = await modelRegistry.getAvailable();
const { session } = await createAgentSession({
model: opus,
thinkingLevel: "medium", // off | minimal | low | medium | high | xhigh
scopedModels: [{ model: opus, thinkingLevel: "high" }],
authStorage, modelRegistry,
});
模型解析顺序:1) 从 session 恢复;2) settings 默认;3) 第一个可用模型。
思考等级从 off 到 xhigh 6 档,是 session 内可调的状态——切思考等级会写一条 thinking_level_change entry 进 session 树,分支回放能完整看到。
Pi 的安全模型:明确告知无沙箱
这一节是 Pi 跟其他 coding agent 最显著的差异之一。
核心事实
Pi 不内置沙箱。内置工具可任意读写文件、执行 shell,扩展以同等权限运行。
文档原话:「This is intentional.」
Pi 面向本地源码树、调用项目工具链、集成用户现有开发环境。文档明确指出进程内「部分 沙箱」会误导用户产生虚假的安全边界感——真正的隔离必须由 OS 或容器/虚拟化层提供。
Project Trust
项目信任是「输入加载守卫」——不是沙箱,也不限制模型在工具内可执行的操作。
触发条件:检测到以下任一即视为「需要信任的资源」:
.pi/settings.json.pi/extensions、skills、prompts、themes.pi/SYSTEM.md或.pi/APPEND_SYSTEM.md- 当前或祖先目录的
.agents/skills
判定流程:
- 首次进入 → 遵循全局
defaultProjectTrust,默认"ask" - 已有决策 → 沿路径向上查找
~/.pi/agent/trust.json中最近祖先的记录 - 决策可由扩展通过
project_trust事件拦截,第一个返回 yes/no 的扩展即拥有决定权
非交互模式(-p、--mode json、--mode rpc)不显示提示,可由 --approve / -a 或 --no-approve / -na 单次覆盖。
Context 文件(AGENTS.md / CLAUDE.md)始终加载,不受 trust 控制——除非显式禁用 context loading。
Trust 的边界
信任 ≠ 安全。文档明确列出无法保护的场景:
- 不可信代码
- 不可信提示
- 不可信模型输出
- 仓库文件、注释、文档、构建产物中的 prompt injection
项目信任仅防止仓库静默修改 Pi 配置/扩展,无法保护上面那些。
容器化建议
Pi 文档推荐三种容器化方案:
- Gondolin — micro-VM(Anthropic 提供的 microVM 方案)
- Plain Docker
- OpenShell
最佳实践:
- 整个
pi进程放入容器,或宿主运行但工具执行路由到 Gondolin micro-VM - 仅挂载工作区所需路径;避免挂载
~/.pi/agent - 使用最小/短期凭证;按需限制网络
- 必要时用只读挂载或沙箱内外文件拷贝代替直接读写
漏洞报告范围
通过仓库 Security Policy 报告。安全敏感报告不得公开 issue。
报告范围明确排除:
- 「预期内的本地 agent 行为」
- 缺乏内置沙箱这一事实
- 不可信内容的 prompt injection
- 用户安装的扩展 / skills 行为
仅当报告展示「真实的权限边界绕过」或 Pi 授予了「本地用户本不具有的访问能力」时,才在安全边界内处理。
为什么 Pi 选择「无沙箱」
把这一节放在最后是有意的——理解 Pi 的安全模型,才能理解前面所有设计选择的根源。
第一,诚实优于虚假安全感。 进程级「弹窗确认」给用户的「安全」感觉是假的——一旦用户在弹窗里点了「允许」,agent 就能做任何事。Pi 选择不制造这种错觉,强迫用户明确意识到「我跑的是无保护的 agent」。
第二,隔离责任在 OS / 容器 / 虚拟机层更合适。 Docker、Gondolin、Firecracker 这类基础设施已经做得很成熟;让 Pi 自己在进程内做一层弱沙箱,反而跟成熟方案抢位置、还做不好。
第三,配合 minimal core 哲学。 Pi 核心只有几件事,加一层沙箱等于把核心代码量翻倍——但沙箱本身又做不到真正安全。这是个负 ROI 的投入。
第四,安全模型跟「extensions 以完整系统权限运行」一致。 如果 Pi 试图限制 agent 行为,又允许 extensions 完整权限——逻辑上不一致。让 extensions 也跑在宿主权限下,整个 agent(包括 extensions)作为一个单元被容器化,比「限制一部分、放任另一部分」更清晰。
这不是说 Pi 的安全模型「更好」——它只是把 trade-off 摆在了用户面前。用户必须自己选择是否用 Docker / Gondolin 包裹 Pi 进程。愿意做 这个动作的用户得到的是「真正隔离的 agent」;不愿意的用户得到的是「明确知道自己没隔离的 agent」。
下一章讲 SDK 与四种运行模式。Pi 把 CLI、SDK、RPC、JSON 四种使用姿势都做了等价支持——同一个 agent session 既可以是 TUI 交互,也可以是 stdin/stdout 上的 JSONL 流、Node.js 嵌入调用。这是 Pi 「自我扩展」哲学的最后一块拼图。