把 pi-coding-agent 的 DeepSeek provider 从传统的 openai-completions(/chat/completions)切换到 DeepSeek 原生支持的 OpenAI Responses API(/v1/responses)的完整记录,环境为 Termux (Android) + pi-coding-agent 0.83.0,作者「糖醋鱼的小破站」于 2026-08-02 实测。

背景

DeepSeek 现已原生支持 OpenAI Responses API 格式(端点 /v1/responses)。修改前,pi 内置目录(pi-ai/dist/providers/data/deepseek.json)把 DeepSeek 模型注册为 openai-completions + baseUrl: https://api.deepseek.com,即走传统 /chat/completions 格式,未使用 Responses API。

Responses API 的优势(实测)

  1. reasoning 是一等公民:chat completions 下 DeepSeek 思考内容走非标准扩展字段 reasoning_content,pi 需 thinkingFormat: "deepseek" 等特判;responses 下 reasoning 是标准结构化 item(type: "reasoning"),流式事件 response.reasoning_text.delta,标准解析器直接处理。实测 thinking 实时流入 pi 的 thinking 槽位,usage.output_tokens_details.reasoning_tokens 正常统计。
  2. 工具调用更规范:工具调用是独立结构化 item(function_call / function_call_output),通过 call_id 关联;response.function_call_arguments.delta 支持边生成边解析(实测一次调用收到 13 个参数增量事件),多轮工具循环比 chat completions 的强耦合顺序更不容易出错。
  3. 显式缓存控制:prompt_cache_key / prompt_cache_retention 可显式控制上下文缓存;实测 pi 在 PI_CACHE_RETENTION=long 下发送 prompt_cache_retention: "24h" 被 DeepSeek 接受,端到端测试第 2 轮 cacheRead: 384 命中缓存。
  4. 输入为结构化 item 数组:多轮对话中 assistant 的 reasoning 与 message 可独立回传,角色/顺序约束比 chat completions 宽松。
  5. 标准统一、面向未来:OpenAI 新一代主推接口,新特性(web_search、结构化输出、文件搜索)会优先在 Responses API 提供。
  6. 官方 SDK 生态:client.responses.create() 原生支持,pi 直接复用 openai 包,无需自定义 HTTP 封装。

已知差异(相对 OpenAI)

  • include: ["reasoning.encrypted_content"] 被忽略:DeepSeek 返回明文 reasoning_text(对 pi 无影响,明文解析更简单)。
  • 未验证/可能暂不支持 OpenAI 专属能力:web_search 工具、结构化输出(json_schema)、文件搜索等,后续需等 DeepSeek 补齐或通过兼容层实现。

端点可用性验证(curl 直测)

  • GET /v1/models:返回 deepseek-v4-flash、deepseek-v4-pro。
  • 非流式 POST /v1/responses:HTTP 200,标准 Responses 结构,reasoning item + message item 齐全。
  • 模拟 pi 客户端完整参数全部通过:stream: true、store: false、prompt_cache_key、prompt_cache_retention: "24h"、max_output_tokens: 384000、temperature、reasoning: {effort, summary}、system/developer role、工具调用流式。
  • SSE 事件序列为标准 Responses 协议:response.created → response.in_progress → response.output_item.added(reasoning) → response.reasoning_text.delta → … → response.completed。

pi 侧关键代码调研

  • API 分发(pi-ai/dist/compat.js):openai-completions → /chat/completions,openai-responses → /v1/responses;openai-responses 原先只分配给 openai / azure-openai-responses / openai-codex。
  • 客户端行为(dist/api/openai-responses.js):baseURL + /v1/responses;发送 stream、store、缓存参数、max_output_tokens、tools、reasoning、include:["reasoning.encrypted_content"];sessionAffinityFormat 默认 “openai” → 发送 session_id 与 x-client-request-id 头。
  • reasoning 明文解析(dist/api/openai-responses-shared.js):response.reasoning_text.delta 追加到 thinking block;response.output_item.done(reasoning item)序列化存入 block.thinkingSignature 用于多轮回放。
  • 多轮 reasoning 回放要求(关键兼容点):DeepSeek 要求上一轮 assistant 消息的 reasoning_text 必须原样回传,否则 400(The reasoning_text in the thinking mode must be passed back to the API.)。pi 通过 thinkingSignature 满足,但前提是回放的 assistant 消息带 provider/api/model 字段(isSameModel 判断为 true)——测试脚本最初漏传这些字段导致 400。

配置修改(models.json)

关键发现(dist/core/provider-composer.js):

  • applyModelsJson 对内置模型只应用 baseUrl 与 compat,provider 级 api 不会作用于内置模型。
  • applyModelOverride(modelOverrides)不含 api 字段,无法通过它改 api。
  • 因此必须用 models 数组按 id 重定义内置模型(同名 id 替换内置模型),并在模型级设置 api: "openai-responses"。

最终 ~/.pi/agent/models.json:

{
  "providers": {
    "deepseek": {
      "baseUrl": "https://api.deepseek.com",
      "api": "openai-responses",
      "compat": {
        "supportsLongCacheRetention": true,
        "sendSessionAffinityHeaders": true
      },
      "models": [
        {
          "id": "deepseek-v4-flash",
          "name": "DeepSeek V4 Flash",
          "api": "openai-responses",
          "reasoning": true,
          "input": ["text"],
          "cost": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 },
          "contextWindow": 1000000,
          "maxTokens": 384000,
          "thinkingLevelMap": { "minimal": null, "low": null, "medium": null, "high": "high", "max": "max" },
          "compat": {
            "supportsDeveloperRole": false,
            "supportsLongCacheRetention": true
          }
        },
        {
          "id": "deepseek-v4-pro",
          "name": "DeepSeek V4 Pro",
          "api": "openai-responses",
          "reasoning": true,
          "input": ["text"],
          "cost": { "input": 0.435, "output": 0.87, "cacheRead": 0.003625, "cacheWrite": 0 },
          "contextWindow": 1000000,
          "maxTokens": 384000,
          "thinkingLevelMap": { "minimal": null, "low": null, "medium": null, "high": "high", "max": "max" },
          "compat": {
            "supportsDeveloperRole": false,
            "supportsLongCacheRetention": true
          }
        }
      ]
    }
  }
}

注意点:supportsDeveloperRole: false 让 pi 发 system role(与内置目录一致);保留原 cost / contextWindow / maxTokens / thinkingLevelMap 避免重定义丢失信息;schema 对 api 无枚举限制,openai-responses 合法。

验证与端到端实测

  • pi --list-models 正确列出 deepseek-v4-flash / deepseek-v4-pro(1M / 384K)。
  • 用 ModelRuntime 解析确认模型 api: "openai-responses"。
  • 端到端测试(pi 真实客户端 + 工具循环):“What is 17 times 23? Use the calculator tool, then give the final number.”
    • 第 1 轮:stopReason: toolUse,工具调用 calculator {"a":17,"b":23},cacheRead: 384。
    • 第 2 轮(回传 reasoning + 工具结果):stopReason: stop,最终答案 391。
    • 结论:工具调用 / 流式输出 / thinking / usage 统计全部正常;cacheRead: 384 证明 Responses API 下上下文缓存生效;多轮 reasoning 回传满足 400 校验。

生效方式与回退

  • 生效:pi 启动时读 models.json;编辑后重新打开 /model 选模型或重启 pi。正在运行的会话仍用旧配置,新会话/新请求走 Responses API。
  • 回退:把 models.json 中的 "api": "openai-responses" 改回 "openai-completions"(或删除 models 数组,仅保留原 compat),重启 pi 即可。

涉及文件

文件 说明
~/.pi/agent/models.json 修改的配置文件(provider 覆盖)
~/.pi/agent/auth.json DeepSeek API key(未改动)
~/.pi/agent/settings.json 默认 provider/model/thinking 设置(未改动)
pi-ai/dist/providers/data/deepseek.json pi 内置 DeepSeek 目录(只读参考)
pi-ai/dist/api/openai-responses.js Responses 客户端实现(只读参考)
pi-ai/dist/api/openai-responses-shared.js 流解析/消息转换(只读参考)
pi-ai/dist/core/provider-composer.js pi 配置合并逻辑(只读参考)

原文:Pi agent 接入 DeepSeek OpenAI Responses API 完整记录(公众号「糖醋鱼的小破站」)