Responses API 与结构化实现
日期:2026-06-14
前面的课程讲的是方法:怎样写清楚任务、怎样设计上下文、怎样使用 RAG、工具和 Agent。本讲补上工程实现层:当你要把提示词放进一个真实应用时,应该怎样选择 API 形态、输出契约、工具调用和评估接口。
本讲以 OpenAI API 为例,但核心思想是通用的:自然语言负责表达任务,API 参数和 schema 负责约束系统行为。
本讲常见术语先按下面理解,详细定义和更多例子见 00.5-术语词典与最小用例。
| 术语 | 直觉解释 | 最小例子 |
|---|---|---|
| Responses API | OpenAI 承载生成、工具、多轮状态的主要接口 | 一次请求里传入 input、tools 和输出格式 |
| Structured Outputs | 让模型按 schema 输出可校验结构 | 返回严格 JSON,不允许额外字段 |
| Reasoning effort | 控制推理预算的参数 | 简单分类用低预算,复杂调研用较高预算 |
| Tool output | 工具执行后的结果文本或结构化数据 | 查询订单后返回 {status: "shipped"} |
| Cached tokens | 被缓存命中的输入 token | 稳定系统提示反复使用时降低延迟和成本 |
| SDK | 在代码里调用 API 的开发工具包 | 用 Python SDK 发起 Responses 请求 |
| JSON mode | 只约束输出为 JSON 的模式 | 适合简单 JSON,不等于完整 schema 校验 |
一、先分清三层
很多学习材料会把 prompt、API 和 Agent 混在一起。工程实现时先分三层:
| 层级 | 负责什么 | 常见形式 |
|---|---|---|
| Prompt | 任务目标、上下文、边界、输出意图 | instructions、input、system / developer message |
| API 参数 | 模型、推理强度、输出长度、工具、结构化输出 | model、reasoning、text / text.format、tools |
| 应用代码 | 权限、校验、工具执行、日志、重试、评估 | backend、worker、eval runner |
提示词越成熟,越应该把可机器校验的东西移到 API 或代码层,而不是全塞在自然语言里。
二、Responses API 适合什么
OpenAI 当前主要推荐用 Responses API 承载直接模型请求、工具调用、多轮状态和内置工具。它适合:
- 文本生成、分析、总结、抽取。
- 需要结构化输出的任务。
- 需要 function calling、file search、web search、remote MCP 等工具的任务。
- 需要 reasoning effort、状态续接或多轮工具链的任务。
- Agent 原型或应用自管 orchestration 的场景。
一个简单判断:
只要任务涉及 reasoning、tool calling、多轮状态或内置工具,优先考虑 Responses API。
Chat Completions 仍可能出现在旧项目里,但新项目不要为了兼容旧习惯而忽略 Responses API。迁移旧系统时,应先确认现有 SDK、日志、工具调用格式和评估流程能否一起迁移。
三、结构化输出:不要只写“请输出 JSON”
如果输出要被程序消费,单靠提示词要求 JSON 不够稳。更成熟的做法是使用 Structured Outputs 或 JSON Schema。
适合使用 schema 的任务:
- 信息抽取。
- 分类。
- 表单填充。
- 风险标签。
- 自动评分。
- RAG 引用结果。
- 工具参数生成。
Prompt 里仍然要写清楚语义边界,例如“只基于原文”“缺失写 null”。但字段、类型、必填项、枚举值、是否允许额外字段,应尽量交给 schema。
示例:
{
"type": "object",
"additionalProperties": false,
"required": ["case_id", "parties", "judgment_result", "missing_fields"],
"properties": {
"case_id": {
"type": ["string", "null"]
},
"parties": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["role", "name"],
"properties": {
"role": {"type": "string"},
"name": {"type": "string"}
}
}
},
"judgment_result": {
"type": "string"
},
"missing_fields": {
"type": "array",
"items": {"type": "string"}
}
}
}
配套 prompt 应该短而明确:
请只基于输入文书抽取字段。
原文没有的信息写 null,并把字段名放入 missing_fields。
不要补充案外事实。
四、Reasoning 参数不是提示词替代品
推理模型通常会在内部完成复杂推理。不要机械要求模型展示完整思维链。更好的方式是:
- 在 prompt 中写清楚目标和成功标准。
- 用
reasoning.effort或平台等价参数控制推理预算。 - 要求输出“结论、依据、不确定项、核查点”,而不是完整思考过程。
- 对复杂任务用 Eval 选择合适 effort,而不是凭感觉固定高档。
常见策略:
| 任务 | 推理配置思路 |
|---|---|
| 简单分类、抽取 | 低推理预算,重 schema 和规则 |
| 长文档总结 | 中等推理预算,重引用和核查 |
| 复杂 Agent / 调研 | 较高推理预算,重工具 trace 和停止条件 |
| 离线评估极限能力 | 高推理预算,接受更慢和更贵 |
不要把“更高 reasoning effort”当成万能修复。检索错、schema 错、工具权限错、评估集缺失,都不是单纯加推理能解决的。
五、工具调用实现要闭环
Function calling / tool calling 的关键不是“模型会调用工具”,而是应用代码能安全闭环。
最小闭环:
1. 请求模型,并提供可用工具定义。
2. 收到模型生成的工具调用请求。
3. 应用代码校验工具名、权限、参数和风险。
4. 应用代码执行工具。
5. 把工具结果返回给模型。
6. 模型基于工具结果继续回答,或继续请求工具。
7. 达到停止条件后输出最终结果。
应用代码必须处理:
- 工具不存在。
- 参数缺失或类型错误。
- 用户权限不足。
- 高风险写操作未确认。
- 工具超时或返回错误。
- 工具结果包含提示注入。
- 模型循环调用工具。
提示词不能替代这些校验。
六、内置工具、自定义函数和 MCP 怎么选
| 需求 | 首选 |
|---|---|
| 查公开网页 | 平台内置 web search |
| 查上传文件或平台文件库 | file search / retrieval |
| 执行业务系统动作 | 自定义 function calling |
| 连接第三方或内部系统并跨客户端复用 | MCP / connector |
| 本地确定性处理 | 应用代码或脚本 |
MCP server 可能暴露 resources、tools、prompts。它解决的是连接和复用问题,不自动解决权限、安全和审计问题。
接 MCP 前要问:
- 这个 server 暴露了哪些 tools、resources、prompts?
- 认证和 OAuth scope 是否最小化?
- 是否需要工具 allowlist?
- 写操作是否需要人工确认?
- 返回内容是否会泄露密钥、隐私或内部日志?
- 是否有提示注入测试和审计日志?
七、缓存友好的请求结构
Prompt caching 的基本思路是让重复的大块稳定前缀复用。请求结构应尽量:
稳定系统规则
-> 稳定工具定义
-> 固定示例
-> 固定背景材料
-> 动态用户输入
-> 动态检索结果
-> 动态工具结果
OpenAI 的 prompt caching 通常要求 prompt 至少达到 1024 tokens 才会自动启用缓存。短 prompt 不应该为了缓存强行加废话;只有当稳定内容本来就需要重复传时,才值得围绕缓存优化。
容易破坏缓存的做法:
- 时间戳、随机 ID、用户 ID 放在最前面。
- 工具定义每次顺序不同。
- few-shot 示例随机排序。
- RAG 动态片段放在稳定前缀前。
- 每次把无关历史都塞进上下文。
八、API 层最小日志
想要长期优化 prompt,至少记录:
- prompt 版本。
- 模型 ID 或模型别名。
- API 表面:Responses、Chat Completions、Agents SDK 等。
- reasoning effort、temperature、max output tokens 等关键参数。
- input tokens、output tokens、reasoning tokens、cached tokens。
- 工具调用 trace。
- RAG 索引版本和检索片段编号。
- 结构化输出是否通过 schema。
- 用户反馈、人工复核结果、失败原因。
没有这些信息,后面做 Eval、成本优化和回归分析都会变成猜。
九、最小实现练习
做一个“合同字段抽取”小项目:
- 准备 5 份短合同片段。
- 定义 JSON Schema:合同编号、甲方、乙方、付款期限、违约金、缺失字段。
- 写 prompt:只基于原文,缺失写 null。
- 调用 Responses API 或你使用平台的等价接口。
- 用程序检查 JSON 是否可解析、必填字段是否存在。
- 记录失败样例并改 prompt 或 schema。
目标不是一次写出完美 prompt,而是建立“prompt + schema + eval”的最小闭环。
十、常见误区
误区 1:API 参数可以替代任务说明
不对。API 参数控制模型行为边界,prompt 仍要说明业务目标、输入边界和成功标准。
误区 2:写了 JSON 示例就等于结构化输出
不对。JSON 示例能引导模型,但不能像 schema 那样提供机器可校验的契约。
误区 3:用了工具调用就可以相信模型参数
不对。模型生成的是调用请求,不是可信执行命令。应用代码必须校验。
误区 4:最新模型不用 Eval
不对。模型行为会随版本变化。需要稳定输出的应用应固定模型版本或记录模型变更,并跑回归评估。
误区 5:缓存优化就是把 prompt 写长
不对。缓存优化的是重复稳定内容。无意义加长 prompt 会增加成本和干扰。
十一、理解检查
- Prompt、API 参数、应用代码分别负责什么?
- 为什么程序消费的输出应该优先使用 JSON Schema?
- Function calling 的执行权为什么必须在应用代码手里?
- Prompt caching 为什么要求稳定内容放前面?
- 一个 API-backed prompt 上线前至少要记录哪些日志?
十二、下一步
下一讲建议学习:06.6-安全与Prompt Injection专题.md。当模型能读取外部内容、调用工具、连接 MCP 后,安全边界会比单条 prompt 更重要。