
1. 为什么要把 Postman 的 API 能力塞进 Codex 里1.1 一个真实痛点接口调试和智能体开发是两套割裂的流程我平时的工作流大概是这样接口调试、参数验证、环境切换全在 Postman 里完成等到要把某个接口封装成智能体可调用的能力时又得手动把 URL、Header、鉴权方式、请求体结构一条条抄到代码里或者写成一份 JSON Schema 喂给模型。这个搬运过程看着简单实际上特别容易出错——Header 少一个Content-Type、鉴权 token 前缀写错、query 参数和 body 参数混在一起模型调用时直接给你返回 400然后你还要回头对照 Postman 里的原始配置排查半天。这个项目的核心思路就是把 Postman 里已经调通的接口直接转成 Codex 能识别的 Skill技能让智能体在推理过程中可以像调用本地函数一样调用这些 API而不需要人工二次翻译。说白了Postman 负责把接口跑通Codex 负责决定什么时候调、传什么参数中间那层胶水由插件自动生成。这件事解决的是三个具体问题。第一配置复用Postman 里已经验证过的请求配置包括环境变量、鉴权、前置脚本不用重写。第二降低出错率人工手写工具描述时最容易漏掉的参数类型、必填项、枚举值插件可以从 Postman 的 Collection 里结构化提取。第三迭代速度接口改了Postman 里更新一下重新导出 Skill 就行不用改智能体的核心代码。适合读这篇的人有三类正在做智能体开发、需要给模型挂载外部 API 能力的工程师日常用 Postman 做接口管理、想把这部分资产复用到 AI 场景的测试或后端同学以及刚开始接触 Codex、想搞清楚 Skill 机制到底怎么落地的新手。下面我会按整体设计思路 → 核心细节 → 实操流程 → 踩坑排查的顺序把整套东西拆开讲。1.2 先搞清楚 Codex 的 Skill 到底是什么在动手之前必须把概念对齐不然很容易走偏。Codex 这类智能体框架里的 Skill本质上是一份结构化的能力声明它告诉模型三件事这个能力叫什么、什么情况下该用它、调用时需要哪些参数。模型根据用户的自然语言输入判断是否需要触发这个 Skill然后把参数填好交给运行时去执行真正的 HTTP 请求。一份典型的 Skill 描述包含这几个字段name技能名通常用蛇形命名、description自然语言描述这段文字直接决定模型会不会在正确时机调用它、parameters参数列表每个参数有类型、是否必填、描述、枚举约束、以及执行体可以是 HTTP 请求模板也可以是本地函数。关键点在于description 写得好不好直接决定智能体的调用准确率。我见过太多人把 description 写成调用用户接口结果模型根本不知道什么时候该用。正确的写法应该包含触发场景比如当用户需要查询某个订单的物流状态时使用需要提供订单号。而 Postman 的 Collection 里恰好包含了生成这些字段所需的全部信息请求方法、URL、路径参数、query 参数、Header、body schema、示例响应。插件要做的就是把这些信息映射成 Skill 的字段。这个映射关系是整个项目的技术核心后面会详细展开。2. 整体设计与方案选型为什么是插件而不是脚本2.1 三种可选路线对比在决定用插件形式之前我实际评估过三条路线各有取舍方案实现方式优点缺点纯脚本转换写个 Python 脚本读 Postman 导出的 JSON生成 Skill 文件实现快依赖少每次接口变更都要手动跑脚本无法实时同步中间服务起一个服务Codex 通过 HTTP 调用服务内部转发到真实 API灵活可加缓存和日志多一层部署延迟增加调试链路变长插件形式在 Codex 侧做插件直接读取 Postman 配置并注册 Skill配置复用度高接近原生体验需要理解两边的数据结构和生命周期我最终选插件形式核心理由是配置的单一数据源。接口定义只在 Postman 里维护一份插件负责读取和转换避免了Postman 一份、代码里一份、文档里一份的三处同步问题。中间服务方案虽然灵活但对于把已有 API 变成 Skill这个诉求来说属于过度设计——大部分场景下我们不需要在转发层做额外逻辑直接让智能体发请求就行。2.2 数据流设计整个插件的数据流是这样的Postman CollectionJSON 格式→ 插件解析器 → 中间表示IR→ Skill 生成器 → Codex 可加载的 Skill 定义。中间表示这一层是我特意加的很多人会省略它直接做转换但加上之后好处很明显Postman 的 Collection 格式版本多v2.0、v2.1 差异不小Codex 的 Skill 格式也可能演进中间加一层 IR 之后两边的适配逻辑解耦任何一边变了只改对应的适配器就行。IR 的结构大概长这样每个接口抽象成一个Endpoint对象包含method、baseUrl、path、pathParams、queryParams、headers、bodySchema、auth、examples。这个结构足够表达绝大多数 REST 接口同时又不绑定任何一方的具体格式。2.3 鉴权处理的关键决策鉴权是最容易出问题的地方我单独拿出来说。Postman 里的鉴权方式五花八门Bearer Token、API Key放 Header 或 Query、Basic Auth、OAuth2。插件不能把真实的密钥写进 Skill 文件里——这是安全红线Skill 文件可能会被提交到代码仓库。我的做法是Skill 里只声明鉴权方式和占位符真实密钥通过环境变量注入。比如 Postman 里配置的是Authorization: Bearer {{token}}生成的 Skill 里就写成Authorization: Bearer ${API_TOKEN}运行时从环境变量读取。这样 Skill 文件可以安全地版本化管理密钥通过部署环境注入。注意千万不要图省事把真实 token 硬编码进 Skill 文件。我见过有人把生产环境的 key 提交到公开仓库后果很严重。养成配置与密钥分离的习惯从第一个项目就做起。3. 核心细节解析Postman 配置到 Skill 的字段映射3.1 请求方法与 URL 的拆解Postman 的请求 URL 通常长这样https://api.example.com/v1/users/:userId/orders?statuspaidlimit20。这里面混了四类信息base URL、路径、路径参数:userId、query 参数status、limit。生成 Skill 时要把它们拆开。base URL 单独存因为它可能随环境变化测试环境、生产环境路径参数要提取成必填参数query 参数要区分必填和选填——Postman 里如果参数被勾选为 enabled 且没有默认值通常视为必填有默认值的视为选填。这里有个坑Postman 的路径参数写法在不同版本里可能是:userId也可能是{{userId}}。插件解析时两种都要兼容。我的处理逻辑是先匹配:(\w)再匹配\{\{(\w)\}\}统一转成 Skill 的路径参数占位符。3.2 参数类型的推断Postman 本身对参数类型的描述比较弱query 参数默认都是字符串。但 Skill 里如果能声明准确的类型integer、boolean、array模型的参数填充准确率会明显提升。我的推断策略是这样的先看 Postman 的示例值example如果示例值是纯数字就推断为 integer是true/false就推断为 boolean是逗号分隔的多值就推断为 array。如果 Postman 里配了 JSON Schema较新版本支持直接用它。都没有的话退回到 string但在 description 里补充说明格式。body 参数的处理更复杂一些。如果 Postman 里 body 是 raw JSON直接解析成 JSON Schema如果是 form-data 或 x-www-form-urlencoded按字段逐个转成参数。JSON 嵌套结构要递归处理数组元素类型也要推断。3.3 description 的自动生成与人工润色前面说过 description 决定调用准确率但自动生成的 description 往往很生硬。我的做法是插件生成一个基础版本包含方法、路径、主要参数说明然后留一个描述覆盖的配置项允许人工补充触发场景。比如自动生成的是GET /v1/users/{userId}/orders查询用户订单人工润色后变成当用户询问某个用户的订单列表、购买记录时使用。需要提供用户 ID可选按订单状态筛选。后者明显更容易被模型正确触发。实操心得description 里最好包含什么时候用和什么时候不用两个维度。只写正向场景模型容易在相似场景下误触发。比如订单查询和订单详情是两个 Skill就要在描述里明确区分列表用前者单个订单用后者。3.4 响应结构的处理Skill 本身通常不需要声明响应结构但有一个例外如果智能体需要根据响应内容做后续决策那么响应里的关键字段最好在 description 里提一句。比如返回结果包含 orderId、status、estimatedDelivery 字段这样模型知道拿到响应后能提取什么。我在插件里加了一个可选的响应摘要功能从 Postman 的示例响应里提取顶层字段名生成一句话摘要附在 description 末尾。这个功能默认关闭因为不是所有场景都需要但对多步推理的任务帮助很大。4. 实操过程从零把插件跑起来4.1 环境准备与依赖安装先把基础环境搭好。我用的技术栈是 Node.js因为 Postman 的 Collection SDK 是 JS 生态的解析起来最顺Codex 侧的插件接口用 TypeScript 写方便类型检查。# 初始化项目 mkdir postman-codex-bridge cd postman-codex-bridge npm init -y # 核心依赖 npm install postman-collection commander chalk npm install -D typescript types/node ts-node # 初始化 TS 配置 npx tsc --initpostman-collection这个库是官方出的能直接把导出的 Collection JSON 解析成对象省去大量手写解析的工作。commander用来做命令行入口chalk做终端输出美化。tsconfig 里记得把target设成 ES2020 以上module设成 commonjsCodex 插件环境通常兼容 CJSstrict打开类型检查能帮你提前发现很多字段映射的错误。4.2 导出 Postman Collection在 Postman 里选中要转换的 Collection右键 → Export → 选择 Collection v2.1 格式。强烈建议用 v2.1它对参数类型、示例值的描述比 v2.0 完整得多解析出来的 Skill 质量明显更高。导出后会得到一个 JSON 文件结构大致是{ info: {...}, item: [...] }item是树形结构可能嵌套文件夹。插件需要递归遍历把所有叶子节点真正的请求提取出来。# 把导出的文件放到项目里 mkdir -p collections output cp ~/Downloads/my-api.postman_collection.json collections/4.3 编写解析器核心逻辑解析器的入口函数大概是这样import { Collection, Item, Request } from postman-collection; interface Endpoint { name: string; method: string; baseUrl: string; path: string; pathParams: Param[]; queryParams: Param[]; headers: Header[]; bodySchema?: any; auth?: AuthConfig; description: string; } function parseCollection(filePath: string): Endpoint[] { const raw require(filePath); const collection new Collection(raw); const endpoints: Endpoint[] []; const walk (items: Item[]) { for (const item of items) { if (item.items item.items.count() 0) { walk(item.items.all()); } else if (item.request) { endpoints.push(convertRequest(item)); } } }; walk(collection.items.all()); return endpoints; }walk函数递归处理嵌套文件夹这是必须的因为实际项目里 Collection 往往按业务模块分了多层目录。convertRequest是单个请求的转换逻辑负责把 URL 拆解、参数分类、body 解析。4.4 URL 拆解的具体实现URL 拆解是解析器里最容易写错的部分我单独贴一下function splitUrl(url: string) { const u new URL(url); const baseUrl ${u.protocol}//${u.host}; const path u.pathname; const queryParams []; u.searchParams.forEach((value, key) { queryParams.push({ name: key, value, required: !value }); }); // 提取路径参数 :userId 或 {{userId}} const pathParams []; const colonMatch path.matchAll(/:(\w)/g); for (const m of colonMatch) { pathParams.push({ name: m[1], required: true }); } return { baseUrl, path, pathParams, queryParams }; }注意required的判断逻辑query 参数如果 Postman 里没填值通常意味着它是必填的用户需要自己填填了值的往往是选填有默认值。这个判断不是绝对的所以我在配置里留了手动覆盖的开关。4.5 生成 Skill 定义拿到 Endpoint 数组后生成 Skill 文件function generateSkill(ep: Endpoint): SkillDef { const properties: Recordstring, any {}; const required: string[] []; for (const p of [...ep.pathParams, ...ep.queryParams]) { properties[p.name] { type: inferType(p.value), description: p.description || ${p.name} 参数, }; if (p.required) required.push(p.name); } return { name: toSnakeCase(ep.name), description: buildDescription(ep), parameters: { type: object, properties, required, }, executor: { type: http, method: ep.method, url: ${ep.baseUrl}${ep.path}, headers: ep.headers, auth: ep.auth, }, }; }toSnakeCase把 Postman 里的中文或驼峰名称转成合法的技能名buildDescription拼装前面说的触发场景描述。executor部分是运行时真正发请求用的模板。4.6 注册到 Codex 并验证生成的 Skill 文件放到 Codex 的 skills 目录下重启或热加载后就能被识别。验证分两步先看技能列表里有没有正确加载再用自然语言触发一次观察模型是否填对了参数。# 生成所有 Skill npx ts-node src/index.ts --input collections/my-api.json --output output/skills # 检查生成结果 ls output/skills/ cat output/skills/get_user_orders.json第一次跑建议只放一两个接口确认链路通了再批量处理。我一开始贪心一次性导了三十多个接口结果某个接口的 body schema 解析报错整个生成流程中断排查起来很费劲。小步验证是硬道理。5. 常见问题与排查技巧实录5.1 参数丢失或类型错误最常见的现象是Skill 加载成功但模型调用时提示缺少必填参数或者参数类型不匹配。根因通常是 Postman 里的参数配置不规范。排查顺序先看导出的 Collection JSON 里那个参数到底长什么样再看解析后的 IR最后看生成的 Skill。哪一层丢了就在哪一层修。我遇到过 Postman 里参数写在 URL 的 query string 里但没在 Params 面板里显式列出导致解析器读不到——这种情况要么在 Postman 里补上要么在解析器里加一层从 URL 兜底提取的逻辑。5.2 鉴权失败如果调用返回 401 或 403先确认环境变量有没有正确注入。我踩过的坑是Skill 里写的是${API_TOKEN}但运行时环境变量名写成了API_KEY两边对不上请求头里带了个空值。另一个常见问题是 token 前缀。Postman 里配 Bearer Token 时Postman 会自动加Bearer前缀但导出后这个前缀信息可能丢失。生成 Skill 时要显式补上否则服务端收到的是裸 token直接拒绝。5.3 模型不触发或误触发这是 description 的问题不是技术问题。如果模型该调用时不调用检查 description 里有没有明确的触发场景词如果乱调用检查是不是多个 Skill 的描述太相似模型分不清。我的经验是给每个 Skill 的描述加一个边界说明比如仅用于查询单个订单详情不用于订单列表查询。这种负向约束能显著降低误触发率。5.4 常见问题速查表现象可能原因排查方向Skill 未加载文件格式错误或路径不对检查 JSON 语法确认 skills 目录缺少必填参数Postman 参数未显式配置对照 Collection JSON 检查401/403环境变量未注入或前缀丢失检查运行时环境变量和 Header 拼接模型不调用description 缺少触发场景补充什么时候用的描述模型误调用多个 Skill 描述重叠加边界说明明确区分场景body 解析失败嵌套结构或非 JSON 格式检查 Postman body 类型必要时手动指定 schema5.5 几个我踩过的坑第一个坑是环境变量污染。Postman 的 Collection 里可能引用了环境变量{{baseUrl}}导出时这些变量不会带出来导致生成的 Skill 里 URL 是空的。解决办法是在插件里加一个环境变量映射表把 Postman 的变量名映射到实际值。第二个坑是中文技能名。Postman 里接口名经常是中文直接转成 Skill 名会出问题。我的处理是保留中文名作为 description 的一部分技能名用拼音或英文实在不行用api_001这种编号。第三个坑是批量生成时的顺序依赖。有些接口需要先调 A 拿到 token 再调 B这种有状态依赖的场景单个 Skill 无法表达。我的做法是在 description 里注明调用前需先获取 token或者干脆把这类接口排除单独用代码处理。最后分享一个实用技巧给生成的 Skill 加一个version字段每次从 Postman 重新生成时递增。这样当智能体行为异常时可以快速确认是不是 Skill 更新导致的回滚也有依据。这套东西我从最初的手写脚本到现在稳定跑在几个项目里前后迭代了大概两个月。最大的体会是别追求一步到位的全自动先把最常用的几个接口跑通验证 description 的写法、鉴权的处理、参数类型的推断这些经验积累起来之后再批量处理剩下的接口就顺理成章了。接口调试和智能体开发本来就不该是两套割裂的流程把它们打通之后你会发现给智能体加新能力的成本低了很多。