后端与接口
把 Runpod 代码从 GraphQL / REST…
当需要把使用 Runpod GraphQL API 或 REST v1 的代码库迁移到 REST v2 时,按盘点、破坏性变更简报、分桶计划、逐文件迁移、实机验证、总结六步执行。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
把 Runpod 代码从 GraphQL特定
解决思路
- 先推断迁移范围并直接说明,不要一上来就问问卷:用户说“迁移到 v2”或没具体说明 → 范围 all(REST v1 和 GraphQL);说“只迁 REST”“别动 GraphQL” → 范围 rest;说“让我们摆脱 GraphQL” → 范围 graphql。说明匹配了哪一行然后继续。
- 提前说明有些能力在 v2 中没有对应实现、无论范围如何都必须留在 GraphQL:账户/账单身份(myself)、secrets、spot/interruptible pods、集群创建/删除。所谓“完整”迁移仍会保留这些调用。
- 绝不重写 serverless job API:https://api.runpod.ai/v2/<endpointId>/run、/runsync、/status、/stream、/cancel 是另一个 API,路径里恰好有 v2,保持不变且不在范围内;盘点时单独报告。
- 第 1 步盘点:先解析扫描脚本路径(它随 SKILL.md 一起安装,不在用户仓库里)。依次尝试 $CLAUDE_PLUGIN_ROOT/skills/runpod-migrate/scripts/rp_api_inventory.py、加载本 SKILL.md 的目录下的 scripts/rp_api_inventory.py、以及 find ~/.claude ~/.agents ~/.codex ~/.config -name rp_api_inventory.py。
- 在用户仓库根目录运行:python3 "$SCAN" . > runpod-api-inventory.md;需要驱动编辑时加 --json > runpod-api-inventory.json;仅迁 REST 时加 --scope rest。该文件落在用户仓库里,交付前删除或加入 gitignore。
- 在编辑任何东西之前,先把盘点表展示给用户:按代际分桶(GraphQL、REST v1、v1/GraphQL 字段名、已是 REST v2、serverless job API、SDK/CLI 包装),并给出建议的逐文件顺序。用户常常不知道自己用的是哪个版本。
- 注意扫描器的分类价值:区分 api.runpod.ai/v2(serverless job API,不能动)与 api.runpod.io/v2(要迁到的控制面);对两版都合法的名字(/pods、["pods"]、idleTimeout)在同行有 v2 上下文时抑制命中;还查找字段名而不只是 URL,以捕获从不出现 runpod 字样的文件。
- 手工检查扫描器的四个盲区:基址在配置里(settings.yaml、.env、ConfigMap、Terraform)时,grep 读取该配置键的调用方;路径由 helper 拼装(_url("pods", pod_id, "stop"))时逐个打开“可能的间接调用点”;SDK 包装(import runpod)时检查 requirements.txt/lockfile 和 SDK 发布说明;生成客户端时从 v2 spec 重新生成,不要编辑生成文件。
- 阅读扫描器标记的代码并追踪调用方:重命名的响应字段(如 costPerHr → cost)会破坏每个调用者,而不只是请求构造处。若已有代码图或 LSP 索引,这一步值得用上。
- 第 2 步:阅读 reference/breaking-changes.md,告诉用户哪些破坏性变更实际适用于他们的代码。两类:重命名和移动(响亮,v2 用 422 按名拒绝未知请求字段);同名不同义(安静,是绿色测试套件不能作为证明的原因)。
- 特别提醒两个最坑的同名不同义:flashboot 从布尔变成三值枚举;v1 的 /billing/endpoints(serverless 花费)在 v2 是 /billing/serverless,而 v2 的 /billing/endpoints 计费的是另一个产品(public endpoints),会返回 200 和该产品的正确总额。
- 第 3 步:写计划并分桶,全程保持分离:Required(不这样做在 v2 上跑不起来)、Cleanup(两种都能跑,但 v2 允许删代码,如手拼 job URL、手写可用性重试、可改为 SSE 的轮询)、用户必须做的决定(代码依赖 v2 已移除的能力:spot/interruptible pods、savings plans、dockerEntrypoint、放置约束 countryCodes/minRAMPerGPU 等、pod reset、每 pod GPU 回退)。
- 对第三桶,在写代码前停下来问用户,但要带上替代方案:有可行重建的(countryCodes → catalog 过滤 + dataCenterIds + 放置断言;每 pod GPU 回退 → 按可用性排序的循环)就展示并问那个会改变方案的问题(如 countryCodes 是偏好还是合规要求);没有替代的,选项是接受行为变化、把该调用留在 v1/GraphQL、或重新设计,只有用户能选。
- 不要把移除说成死路而其实有重建方案,那会逼用户保留本不必保留的 v1 调用;也绝不要用 # no v2 equivalent 注释直接删字段,那会静默改变基础设施行为。若该桶为空就说明为空。
- 第 4 步:按扫描器建议顺序(调用点最少优先)逐文件迁移,但有一个覆盖规则:若多个调用点共享同一个传输 helper,先迁 helper,无论其数量多少(扫描器按调用点计数排序,看不到 import)。
- 每个文件用 reference/rest-v1-to-v2.md 或 reference/graphql-to-v2.md 映射路径和字段;若字段不在表里或 API 与表不符,直接查 spec。
- pod 上的 gpuTypeIds + gpuTypePriority 意味着在写新代码而非重命名字段:v2 pod 只接受一种 GPU 类型,服务端回退变成客户端对 catalog 的循环(见 breaking-changes.md 的 GPU fallback 实现);endpoints 不需要循环,gpu.pools 已是列表。
- 本次迁移引入的任何 GET /v2/catalog/gpus、/catalog/cpus、/catalog/datacenters 都要请求可用性:加 include=AVAILABILITY(datacenters 用 GPU_AVAILABILITY/CPU_AVAILABILITY)。不要因为旧代码没问就省略。
- 注意 include=AVAILABILITY 单独使用会 400:在 /catalog/gpus 和 /catalog/cpus 上必须同时带 product,不带也 400。product 取 POD、SERVERLESS 或 CLUSTER(CPU 取 POD 或 SERVERLESS),要与创建的对象匹配,例如 GET /v2/catalog/gpus?include=AVAILABILITY&product=POD。
- 向用户提供回滚开关 RUNPOD_API_V1=1(见 reference/rollback-flag.md):生产服务值得,一次性脚本可跳过。
- 绝不在同一个 commit 里同时改行为和 API 版本,包括 v2 带来的改进(如遇 status == "ERROR" 快速失败而非超时);这些放到 cleanup 桶单独提交,否则回滚要同时放弃两者。
- 第 5 步:对实机 API 验证。重跑扫描器证明调用点已消失:python3 "$SCAN" . --scope rest --fail-on-legacy(若仍有 v1 则退出 1)。
- 正确使用两个标记:rp-migrate: keep-v1 用于有意保留的遗留代码(RUNPOD_API_V1 回滚分支、无 v2 对应的 GraphQL 调用),报告在“有意保留”下;rp-migrate: ignore 用于已正确代码的误报,报告在“标记的误报”下。两者都接受 line、start/end 区域或 file 范围,都会从计划和 --fail-on-legacy 中排除。用 keep-v1 消音误报等于在报告里记录谎言。
- 注意迁移后盲区反转:迁移前隐藏 v1 代码,迁移后可能把正确的 v2 代码标为遗留。扫描器处理了两种常见情况(尾部 # was imageName 注释;常量中的基址 f"{BASE}/pods" 且 BASE 在文件内定义为 v2 URL);但基址从其他模块 import 或运行时从配置构建时仍可能误判。相信退出码前先读被标记的行,真误报用 ignore 标记而不是削弱门禁。
- 读操作免费:列出 pods、endpoints、volumes、catalog,确认解包新信封({"pods": [...]} 而非裸数组)。写操作花钱:创建 → 断言 → 删除,用能证明形状的最小对象,绝不对用户已有资源测试。
- 用 breaking-changes.md 的表格解读 422,包括那个令人困惑的情况:缺少必填字段会让验证器把你有效的字段报成“additional properties not allowed”。
- 第 6 步:按固定结构写总结(多数用户只读总结不读 diff):## Required for the migration(<file:line> — 改了什么、为什么必须改);## Cleanup enabled by v2(<file:line> — 删掉或简化了什么);## Behavior changes to watch(适用的同名不同义项);## Still on GraphQL (no v2 equivalent)(myself/secrets/spot pods/clusters 及原因);## Unlocks: what you can build now(结合该代码库已有行为)。
- 最后一段“Unlocks”价值最高:不要贴通用功能列表,而是看用户一直在构建和挣扎的东西(包括会话中已知信息),指出 v2 在哪里改变它,例如“你的 wait_until_running 循环在 pod 失败时会超时;v2 的 ERROR 状态让它几秒内失败”胜过“v2 有更丰富的状态值”。reference/unlocks.md 按“如果代码做 X → v2 提供 Y”组织,正是为此。
- 以 spec 为准:reference/ 中的映射表验证于 2026-08-10,v2 在活跃开发,把它们当快路径而非权威。两个 spec 公开且无需认证:curl -s https://api.runpod.io/v2/openapi.json -o /tmp/rp-v2.json;curl -s https://rest.runpod.io/v1/openapi.json -o /tmp/rp-v1.json。
- 写任何 create 调用前,用给定 Python 脚本解析请求体实际接受和必填(*)的字段,它会解析 allOf 组合;把参数换成 CreatePodRequest、UpdatePodRequest、CreateTemplateRequest、CreateNetworkVolumeRequest 等。
- 当 422 提到不认识的字段时,用给定 Python 单行命令查哪些 schema 提到该字段(示例查 flashboot)。
- 来源冲突时的优先级:实机观察行为 > spec > 这些表。spec 不总是对的(如 timeout 文档默认 300000 ms 但实际返回 0);若运行中的 API 与 spec 矛盾,相信 API 并在总结中说明,让用户知道文档默认值不可依赖。若发现 reference/ 中的映射与 spec 不符,修正调用并标记漂移。
- GraphQL 没有机器可读 schema(introspection 被禁用),无法用此方式检查,见 reference/graphql-to-v2.md 的说明。
- 工具说明:盘点扫描器刻意是 grep 级脚本而非代码图索引,因为 API 代际是 URL 字符串和字段名的属性,必须适用于任意语言的客户仓库且零配置给出一致答案。代码智能索引(LSP 或已在运行的 MCP 图服务器)在别处有用——第 1 步的爆炸半径问题“谁调用了这个响应字段刚被重命名的 wrapper”——而不是检测;已有就用,不要为此专门搭建。
适用边界 · 注意事项
- 仅需管理 Runpod 基础设施而非迁移代码时(应使用 runpod-mcp 或 runpodctl)。
- 代码只使用 serverless job API(api.runpod.ai/v2/...)时——那是另一个 API,不在迁移范围内。
- 用户没有 Runpod 代码库或不需要从 GraphQL/REST v1 迁到 REST v2 时。