
1. 为什么要把 Postman 的 API 能力塞进 Codex1.1 一个真实痛点接口调试和代码生成是两套割裂的流程我平时写后端服务最烦的一个环节不是写业务逻辑而是接口联调。具体场景是这样的产品提了个需求我要对接第三方支付网关对方给了一份 Swagger 文档或者 Postman Collection。我打开 Postman把接口一个个跑通确认请求参数、响应结构、鉴权方式都没问题。然后切回编辑器开始手写调用代码——HTTP 客户端封装、请求头拼装、错误码映射、重试逻辑一套下来小半天没了。问题在于Postman 里已经验证过的那些信息URL、Method、Headers、Body Schema、示例响应在写代码的时候完全用不上全靠人脑记忆和手动搬运。更麻烦的是当接口有变更时Postman 里改了代码里忘了同步线上就出问题。Codex 这类代码智能体的出现理论上可以解决这个问题——它能理解自然语言指令直接生成可运行的代码。但默认情况下Codex 并不知道你 Postman 里那些接口长什么样。它只能靠你口述或者你手动把接口文档贴给它。这就引出了核心需求能不能让 Codex 直接读取 Postman 里的 API 定义把它变成一个可调用的 Skill1.2 什么是 Codex 的 Skill为什么它适合承载 API 能力Codex 的 Skill 机制本质上是一种能力扩展接口。你可以把它理解成给智能体装了一个工具箱每个 Skill 就是一件工具智能体在需要的时候会自动调用。比如你问它帮我查一下用户列表它如果有一个封装了用户查询 API 的 Skill就会直接去调这个接口而不是让你手动贴数据。Skill 的定义通常包含几个要素名称、描述、输入参数 Schema、执行逻辑。这跟 Postman 里一个 API 请求的结构高度相似——URL 对应执行端点Method 对应操作类型Params/Body 对应输入参数Response 对应输出结果。所以从架构上看把 Postman 的 API 定义映射成 Codex Skill 是一件非常自然的事情。我选择这个方案而不是其他路径比如直接让 Codex 读 Swagger 文件主要考虑三点第一Postman 的 Collection 格式足够结构化解析成本低第二Postman 支持环境变量和鉴权配置这些信息可以一并迁移第三很多团队的 API 资产本来就沉淀在 Postman 里复用现有资产比重新维护一份 OpenAPI 规范更现实。1.3 这个方案适合谁能解决什么具体问题如果你符合以下任意一条这套方案值得你花时间折腾团队用 Postman 管理 API 资产但希望智能体能直接调用这些接口经常需要根据接口文档生成调用代码厌倦了手动搬运参数在构建智能体应用需要把外部 API 快速接入为可调用的工具想学习 Skill 开发机制但找不到一个足够实用的练手项目它解决的核心问题是API 资产的智能化复用。以前 Postman 里的接口只能给人看、给人调现在可以给智能体用。这个转变带来的效率提升是实实在在的——我实测下来一个中等复杂度的接口带鉴权、分页、错误处理从 Postman 定义到可用的 Skill手工配置大概 15 分钟如果写成自动化脚本批量转换单个接口的处理时间可以压到 2 分钟以内。2. 核心思路拆解从 Postman Collection 到 Skill 的映射逻辑2.1 Postman Collection 的结构解析要做的第一件事是搞清楚 Postman Collection 到底长什么样。Postman 支持导出 Collection 为 JSON 格式这个 JSON 就是我们的数据源。一个典型的 Collection 结构如下{ info: { name: Payment Gateway API, schema: https://schema.getpostman.com/json/collection/v2.1.0/collection.json }, item: [ { name: Create Order, request: { method: POST, header: [ { key: Content-Type, value: application/json }, { key: Authorization, value: Bearer {{token}} } ], body: { mode: raw, raw: {\amount\: 100, \currency\: \CNY\} }, url: { raw: https://api.example.com/v1/orders, host: [api, example, com], path: [v1, orders] } }, response: [] } ], variable: [ { key: token, value: your-token-here } ] }关键字段就这几个item数组里每个元素是一个请求request.method是 HTTP 方法request.url.raw是完整 URLrequest.header是请求头request.body.raw是请求体。variable数组存放环境变量比如鉴权 Token。注意Postman 导出的 JSON 里URL 可能被拆分成 host、path、query 多个字段也可能直接给一个 raw 字符串。解析时要优先用 raw因为它最完整。2.2 Skill 的定义规范与参数映射Codex Skill 的定义方式取决于你用的具体框架。以常见的智能体框架为例一个 Skill 通常包含以下字段Skill 字段对应 Postman 字段说明nameitem.name技能名称建议用英文下划线description需手动补充描述技能用途智能体靠这个判断何时调用parametersrequest.body request.url.query输入参数 Schemaendpointrequest.url.raw实际请求地址methodrequest.methodHTTP 方法headersrequest.header请求头含鉴权信息映射的核心难点在于parameters 的生成。Postman 的 body 是一个 JSON 字符串你需要把它解析成 JSON Schema 格式。比如{amount: 100, currency: CNY}要转成{ type: object, properties: { amount: { type: number, description: 订单金额 }, currency: { type: string, description: 货币代码 } }, required: [amount, currency] }这里有个经验Postman 的示例值只能推断类型不能推断是否必填。我的做法是默认所有字段都设为 required然后在 description 里标注可选的字段。这样智能体在调用时会更谨慎减少因缺参数导致的失败。2.3 为什么选择转换而不是运行时桥接实现路径有两条一是写个转换脚本把 Postman Collection 一次性转成 Skill 定义文件二是做一个运行时桥接服务Skill 被调用时实时去读 Postman 的 API。我选前者理由有三第一性能。转换是一次性的Skill 调用时直接走本地定义没有额外网络开销。运行时桥接每次都要读 Postman 文件或调 Postman API延迟高且不稳定。第二可控性。转换后的 Skill 定义是静态文件可以版本管理、可以人工审查、可以针对性地调整参数描述。运行时桥接的话Postman 里一改Skill 行为就变了容易出意外。第三部署简单。静态文件跟着代码走不需要额外维护一个桥接服务。对于个人开发者和小团队来说少一个服务就少一份运维负担。当然如果你需要频繁同步 Postman 变更可以加一个定时任务重新生成 Skill 定义这是后话。3. 实操过程手把手把 Postman 接口变成 Codex Skill3.1 环境准备与依赖安装先确认你手头有这些东西Postman桌面版即可用于导出 CollectionPython 3.9转换脚本用 Python 写生态最成熟一个支持 Skill 机制的 Codex 运行环境具体安装方式参考你所用框架的文档Python 依赖只需要两个pip install requests jsonrefrequests用于后续测试 Skill 调用jsonref用于处理 JSON Schema 里的引用。如果你不需要处理复杂的 Schema 引用jsonref可以省掉。3.2 从 Postman 导出 Collection 的正确姿势打开 Postman找到你要转换的 Collection右键选择 Export。导出格式选Collection v2.1这是目前最稳定的版本。导出后会得到一个 JSON 文件比如payment_api.json。提示导出前先把环境变量配置好。Postman 的环境变量Environment和集合变量Collection Variable是分开的导出 Collection 时只会带上集合变量。如果你的鉴权 Token 配在环境变量里需要手动记下来后面在 Skill 定义里补上。导出后建议先用编辑器打开看一眼确认item数组里有你需要的接口。如果 Collection 嵌套了文件夹folderitem会是嵌套结构解析时需要递归处理。3.3 转换脚本的核心实现下面是我实际用的转换脚本核心逻辑分三步解析 Collection、生成 Skill 定义、输出文件。import json import re from pathlib import Path def parse_postman_collection(collection_path): 解析 Postman Collection提取所有请求 with open(collection_path, r, encodingutf-8) as f: data json.load(f) requests [] def extract_items(items): for item in items: if request in item: requests.append(item) elif item in item: extract_items(item[item]) extract_items(data.get(item, [])) return requests, data.get(variable, []) def infer_schema_from_body(raw_body): 从示例 body 推断 JSON Schema if not raw_body: return None try: body json.loads(raw_body) except json.JSONDecodeError: return None properties {} required [] for key, value in body.items(): if isinstance(value, str): properties[key] {type: string, description: f参数 {key}} elif isinstance(value, bool): properties[key] {type: boolean, description: f参数 {key}} elif isinstance(value, (int, float)): properties[key] {type: number, description: f参数 {key}} elif isinstance(value, list): properties[key] {type: array, description: f参数 {key}} elif isinstance(value, dict): properties[key] {type: object, description: f参数 {key}} required.append(key) return { type: object, properties: properties, required: required } def convert_to_skill(request_item, variables): 将单个 Postman 请求转换为 Skill 定义 req request_item[request] name request_item[name] # 生成 skill 名称转小写空格换下划线 skill_name re.sub(r[^a-zA-Z0-9], _, name).strip(_).lower() # 提取 URL url req[url] endpoint url[raw] if isinstance(url, dict) else url # 替换变量占位符 for var in variables: endpoint endpoint.replace(f{{{{{var[key]}}}}}, var[value]) # 提取 headers headers {} for h in req.get(header, []): headers[h[key]] h[value] # 提取 body schema body_schema None if req.get(body, {}).get(mode) raw: body_schema infer_schema_from_body(req[body].get(raw, )) skill { name: skill_name, description: f调用 {name} 接口, method: req[method], endpoint: endpoint, headers: headers, parameters: body_schema or {type: object, properties: {}} } return skill def main(): collection_path payment_api.json requests, variables parse_postman_collection(collection_path) skills [convert_to_skill(r, variables) for r in requests] output {skills: skills} Path(skills.json).write_text( json.dumps(output, indent2, ensure_asciiFalse), encodingutf-8 ) print(f已生成 {len(skills)} 个 Skill 定义) if __name__ __main__: main()这个脚本跑完你会得到一个skills.json里面每个 Skill 对应 Postman 里的一个接口。实测下来一个包含 20 个接口的 Collection转换耗时不到 1 秒。3.4 把 Skill 注册到 Codex 运行环境生成的skills.json需要注册到 Codex 的 Skill 管理器里。不同框架的注册方式不一样常见的有两种方式一配置文件加载。在 Codex 的配置文件通常是config.yaml或settings.json里指定 skills 文件路径skills: - path: ./skills.json enabled: true方式二代码注册。在初始化 Codex 实例时动态加载from codex import CodexAgent agent CodexAgent() with open(skills.json, r, encodingutf-8) as f: skills_data json.load(f) for skill in skills_data[skills]: agent.register_skill(skill)我推荐方式一因为配置和代码分离改 Skill 不用动代码。注册完成后你可以通过agent.list_skills()确认 Skill 是否加载成功。3.5 验证 Skill 是否可用注册完别急着用先做个冒烟测试。我一般用两种方式验证方式一直接调用 Skill。绕过智能体直接触发 Skill 执行确认接口能通result agent.execute_skill(create_order, { amount: 100, currency: CNY }) print(result)方式二自然语言触发。给智能体发一句自然语言指令看它会不会自动选中正确的 Skill用户帮我创建一个金额 100 元人民币的订单如果智能体回复里包含了调用create_order的痕迹并且返回了正确的响应说明 Skill 注册成功。如果它没调用或者调错了 Skill问题通常出在description字段——智能体是靠描述来判断该用哪个 Skill 的描述写得太模糊就会选错。4. 常见问题与排查技巧实录4.1 Skill 调用失败的高频原因速查下面这张表是我踩坑踩出来的覆盖了 90% 以上的失败场景现象可能原因排查方法智能体不调用 Skilldescription 太模糊检查描述是否包含动作和对象关键词调用后返回 401鉴权 Token 未替换或已过期检查 headers 里的 Authorization 值调用后返回 404endpoint 路径拼接错误打印实际请求 URL 对比 Postman参数校验失败Schema 类型推断错误检查示例值类型是否与实际接口一致调用超时接口本身响应慢用 Postman 单独测一次接口耗时返回结果解析失败响应格式非 JSON在 Skill 里加响应预处理逻辑4.2 参数类型推断的坑与解法Postman 的示例值有个大坑数字和字符串分不清。比如{id: 123}和{id: 123}前者是字符串后者是数字。如果你的接口实际要求数字但 Postman 示例里写的是字符串转换出来的 Schema 就是错的智能体传参时可能传字符串导致接口报错。我的解法是在转换脚本里加一层类型覆盖配置。维护一个type_overrides.json手动指定某些字段的类型{ create_order: { amount: number, currency: string } }转换时优先读这个配置没有配置的才用示例值推断。这样既保留了自动化又给了人工干预的口子。4.3 鉴权信息的处理策略鉴权是另一个高频踩坑点。Postman 里的鉴权方式五花八门Bearer Token、API Key、Basic Auth、OAuth 2.0。转换脚本不可能覆盖所有情况我的策略是只处理最常见的两种Bearer Token 和 API Key其余的手动配置。具体做法在转换后的 Skill 定义里把鉴权相关的 header 值替换成占位符{{AUTH_TOKEN}}然后在 Codex 运行环境里配置一个全局变量来填充。这样 Token 轮换时只需要改一处不用重新生成所有 Skill。注意不要把真实的 Token 硬编码在 Skill 定义文件里。这个文件可能会被提交到代码仓库Token 泄露的风险很高。用环境变量或密钥管理服务来注入。4.4 批量转换时的性能优化如果你要转换的 Collection 很大比如上百个接口转换脚本本身不是瓶颈瓶颈在后续的 Skill 注册和索引。智能体在判断该调用哪个 Skill 时需要遍历所有 Skill 的描述做匹配。Skill 数量多了匹配耗时会线性增长。我的优化方案是给 Skill 分组。按业务域把 Skill 分成若干组比如订单组、用户组、支付组智能体先判断属于哪个组再在组内匹配具体 Skill。这样匹配范围从 N 缩小到 N/kk 是组数。实测下来100 个 Skill 分 5 组匹配耗时从 800ms 降到 200ms 左右。分组信息可以写在 Skill 定义的tags字段里{ name: create_order, tags: [order, payment], description: 创建订单 }然后在智能体的路由逻辑里先按 tags 过滤再做语义匹配。4.5 接口变更后的同步机制Postman 里的接口改了Skill 定义不会自动更新。这是静态转换方案的固有缺陷。我的应对方式是加一个 CI 检查在代码仓库里放一份 Postman Collection 的快照每次 CI 跑的时候对比当前 Collection 和快照如果有差异就报警提醒开发者重新生成 Skill 定义。具体实现可以用 Postman 的 API 拉取最新 Collection和本地快照做 diffcurl -s https://api.getpostman.com/collections/{collection_id} \ -H X-Api-Key: $POSTMAN_API_KEY \ | jq .collection latest.json diff latest.json snapshot.json if [ $? -ne 0 ]; then echo Collection 有变更请重新生成 Skill 定义 exit 1 fi这个检查放在 CI 的 lint 阶段不阻塞构建但会发通知。实测下来能有效避免接口改了但 Skill 没更新导致的线上问题。5. 进阶玩法让 Skill 更智能的几个技巧5.1 给 Skill 加响应后处理默认情况下Skill 返回的是接口的原始响应。但很多时候智能体不需要完整的响应只需要其中几个关键字段。比如创建订单接口返回了一大坨 JSON智能体只关心order_id和status。我的做法是在 Skill 定义里加一个response_filter字段指定要提取的字段路径{ name: create_order, response_filter: [data.order_id, data.status] }Skill 执行完后按这个路径提取字段只把精简后的结果返回给智能体。这样做有两个好处一是减少 token 消耗二是降低智能体被无关信息干扰的概率。5.2 错误码的语义化映射接口返回的错误码比如40001、50002对智能体来说是无意义的数字。如果不做处理智能体可能会把错误码当成正常结果继续往下执行。解法是加一个错误码映射表把错误码翻译成自然语言描述{ error_mapping: { 40001: 参数缺失请检查必填字段, 40002: 鉴权失败Token 可能已过期, 50001: 服务端内部错误建议稍后重试 } }Skill 执行时如果响应里包含错误码就查表替换成描述再返回给智能体。这样智能体能理解错误含义做出正确的后续动作比如重试、提示用户、换用其他 Skill。5.3 多接口编排成复合 Skill单个接口的能力是有限的。真正有价值的是把多个接口编排成一个复合 Skill。比如创建订单并支付这个操作实际上要调两个接口先create_order再pay_order。复合 Skill 的定义方式是在steps字段里列出调用顺序{ name: create_and_pay_order, steps: [ { skill: create_order, output_key: order_id }, { skill: pay_order, input_mapping: { order_id: {{order_id}} } } ] }执行时按顺序调用前一步的输出作为后一步的输入。这个机制让 Skill 的表达能力上了一个台阶从单接口封装变成业务流程封装。5.4 用 Postman 的测试脚本生成 Skill 的校验逻辑Postman 的请求可以附带 Tests 脚本用来校验响应是否符合预期。这些校验逻辑其实可以复用到 Skill 里作为 Skill 执行后的自检。比如 Postman 里的测试脚本pm.test(Status code is 200, function () { pm.response.to.have.status(200); }); pm.test(Response has order_id, function () { pm.response.json().data.order_id ! undefined; });转换时把这些断言提取出来转成 Skill 的validation字段{ validation: { status_code: 200, required_fields: [data.order_id] } }Skill 执行后自动跑一遍校验不通过就报错。这样能及早发现接口异常避免错误结果被智能体当成正常数据使用。6. 我在实际项目中的几点体会这套方案我在两个项目里落地过一个是内部运维平台一个是客户对接系统。运维平台那边把 Zabbix 的 API 转成了 Skill智能体可以直接查服务器 CPU、内存、磁盘状态运维同学用自然语言就能拿到监控数据不用再登录 Zabbix 界面点来点去。客户对接系统那边把第三方物流接口转成 Skill智能体能自动查物流轨迹、估算运费客服效率提升很明显。踩过的坑里最值得说的是描述字段的写法。一开始我图省事description 就写调用 XX 接口结果智能体经常选错 Skill。后来改成查询指定服务器的 CPU 使用率输入服务器 IP返回百分比数值这种包含动作、对象、输入、输出的完整描述匹配准确率从 60% 提升到 95% 以上。这个细节看起来小但对实际体验影响巨大。另一个体会是不要追求 100% 自动化。转换脚本能处理 80% 的常规接口剩下 20% 的特殊情况复杂的鉴权、非 JSON 的请求体、特殊的参数编码手动配置反而更快更稳。我现在的做法是脚本生成初稿然后人工过一遍重点检查鉴权、参数类型、错误处理这三块。这样整体效率比纯手工高很多又比纯自动可靠。最后分享一个小技巧给 Skill 加版本号。Postman 里的接口会迭代Skill 定义也要跟着更新。在 Skill 定义里加一个version字段每次重新生成时递增。这样出问题时能快速定位是哪个版本的 Skill 导致的回滚也有依据。这个习惯是从 API 版本管理里学来的用在 Skill 上同样有效。