Responses API 介绍与 deepseek-harness 应用分析报告
Responses API 介绍与 deepseek-harness 应用分析报告
分析对象:
@deepseek-harness/(DeepSeek Harness,dsh)
报告内容:Responses API 概述、输入/输出消息示例、"卡片"与结构化输入支持、presentCall/presentResult声明示例、在 harness 中的应用。
目录
- 什么是 Responses API
- 与 Chat Completions 的对比
- 输入(请求)消息示例
- 输出(响应)消息示例
- 流式 SSE 事件示例
- "卡片"消息与结构化用户输入
- presentCall / presentResult 声明示例
- 在 deepseek-harness 中的应用
- 小结
1. 什么是 Responses API
OpenAI Responses API(POST /v1/responses)是 OpenAI 于 2025 年推出的统一模型接口,用一个端点整合 Chat Completions、工具调用、推理(reasoning)、结构化输出、文件搜索、computer use 等能力。
它与旧的 Chat Completions 最本质的差别在数据结构上:
| 维度 | 说明 |
|---|---|
| 请求 | 用 input[](消息条目数组)+ 独立的 instructions(系统指令),取代 messages[] + system 角色 |
| 响应 | 用 output[](条目数组:message / function_call / reasoning…)取代 choices[].message |
| 工具调用 | 是 output 里的一等 function_call 条目,而非 assistant 消息内嵌的 tool_calls |
| 推理 | 用原生 reasoning: { effort } 表达 |
| 状态/缓存 | 支持 previous_response_id、store、prompt_cache_key |
2. 与 Chat Completions 的对比
| 维度 | Chat Completions | Responses API |
|---|---|---|
| 端点 | /chat/completions |
/responses |
| 请求结构 | messages[](role 区分 system/user/assistant/tool) |
input[] + 独立 instructions |
| 输出结构 | choices[].message |
output[] 条目数组 |
| 工具调用 | assistant 消息内嵌 tool_calls |
output 中的 function_call 条目 |
| 推理 | 各家私有字段 | 原生 reasoning: { effort } |
| 结构化输出 | response_format: json_schema |
text.format: { type: "json_schema" } |
| 状态/缓存 | 无 | previous_response_id / store / prompt_cache_key |
3. 输入(请求)消息示例
3.1 最简文本请求
// POST /v1/responses
{
"model": "gpt-4o",
"instructions": "You are a helpful assistant.", // 系统指令(独立于 input)
"input": [
{ "role": "user", "content": [ { "type": "input_text", "text": "2 + 2 等于几?" } ] }
],
"temperature": 1.0,
"max_output_tokens": 1024
}
3.2 带工具 + 推理 + 结构化输出
{
"model": "gpt-4o",
"instructions": "查询天气并以 JSON 回答。",
"input": [
{ "role": "user", "content": [ { "type": "input_text", "text": "北京今天天气如何?" } ] }
],
// —— 工具(函数调用,JSON Schema 定义参数)——
"tools": [
{
"type": "function",
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
],
// —— 推理强度 ——
"reasoning": { "effort": "medium" },
// —— 结构化输出(强制 JSON Schema 约束)——
"text": {
"format": {
"type": "json_schema",
"name": "weather_report",
"strict": true,
"schema": {
"type": "object",
"properties": {
"city": { "type": "string" },
"temp_c": { "type": "number" }
},
"required": ["city", "temp_c"],
"additionalProperties": false
}
}
}
}
3.3 多轮会话(带工具调用结果的 input 条目)
第二轮请求把上一轮的 function_call 和工具返回的 function_call_output 作为 input 条目回填——这是 Responses API 与 Chat Completions 最大的结构差异之一:
{
"model": "gpt-4o",
"instructions": "You are a helpful assistant.",
"previous_response_id": "resp_9xK...", // 有状态会话:引用上一轮响应
"input": [
{ "role": "user", "content": [ { "type": "input_text", "text": "北京今天天气如何?" } ] },
{ "role": "assistant", "content": [ { "type": "output_text", "text": "我来查一下。" } ] },
// 模型请求调用工具 —— 一个一等 output 条目,而不是消息内嵌字段
{
"type": "function_call",
"name": "get_weather",
"arguments": "{\"city\":\"北京\"}", // 原始 JSON 字符串
"call_id": "call_abc123"
},
// 工具执行结果回填 —— 通过 call_id 与上面的调用关联
{
"type": "function_call_output",
"call_id": "call_abc123",
"output": "{\"temp_c\": 22, \"condition\": \"晴\"}"
}
]
}
4. 输出(响应)消息示例
4.1 非流式完整响应对象
字段形状以
subagent-codex/tests/responses-fixture.ts的responseObject为准。
{
"id": "resp_fixture",
"object": "response",
"created_at": 1,
"status": "completed",
"model": "gpt-4o",
"error": null,
"incomplete_details": null,
"instructions": null,
"output": [ // 输出条目数组
{
"id": "msg_fixture",
"type": "message", // 条目类型:message / function_call / reasoning ...
"status": "completed",
"role": "assistant",
"content": [ { "type": "output_text", "text": "2 + 2 = 4", "annotations": [], "logprobs": [] } ]
}
],
"usage": {
"input_tokens": 10,
"input_tokens_details": { "cached_tokens": 0 },
"output_tokens": 1,
"output_tokens_details": { "reasoning_tokens": 0 },
"total_tokens": 11
},
"reasoning": { "effort": null, "summary": null },
"text": { "format": { "type": "text" }, "verbosity": "medium" },
"tool_choice": "auto",
"tools": [],
"parallel_tool_calls": true,
"temperature": null,
"max_output_tokens": null,
"previous_response_id": null,
"prompt_cache_key": null,
"prompt_cache_retention": null,
"store": false,
"service_tier": "default",
"background": false,
"safety_identifier": null,
"truncation": "disabled",
"top_logprobs": 0,
"top_p": null,
"user": null,
"metadata": {}
}
4.2 函数调用条目(output 中的 function_call)
{
"type": "function_call",
"id": "fc_fixture",
"call_id": "call_fixture", // 与 function_call_output 关联
"name": "get_weather",
"arguments": "{\"city\":\"北京\"}", // 原始 JSON 字符串
"status": "completed"
}
5. 流式 SSE 事件示例
流式响应的 content-type: text/event-stream,事件序列(取自 completeResponsesEvents):
response.created → response.output_item.added → response.content_part.added
→ response.output_text.delta → response.output_text.done
→ response.content_part.done → response.output_item.done → response.completed
// 每条 data: 一行 JSON
data: {"type":"response.created","response":{ ...status: in_progress, output: [] }}
data: {"type":"response.output_item.added","output_index":0,"item":{ id, type: message, status: in_progress, content: [] }}
data: {"type":"response.content_part.added","item_id":"msg","output_index":0,"content_index":0,"part":{ type: output_text, text: '' }}
data: {"type":"response.output_text.delta","item_id":"msg","delta":"2 + 2 "}
data: {"type":"response.output_text.delta","item_id":"msg","delta":"= 4"}
data: {"type":"response.output_text.done","text":"2 + 2 = 4"}
data: {"type":"response.content_part.done","part":{ ...output_text }}
data: {"type":"response.output_item.done","item":{ ...message completed }}
data: {"type":"response.completed","response":{ ...完整对象 }}
data: [DONE]
函数调用参数的增量事件用 response.function_call_arguments.delta / .done 代替上面的 output_text 系列。
6. "卡片"消息与结构化用户输入
结论一:Responses API 本身没有"卡片"(card)这种消息类型。
它的输入条目content只有input_text/input_image/input_file等,输出条目是message/function_call/reasoning等。没有"表单卡片""widget"这类供用户填写的结构化控件。
Responses API 的结构化输入机制是下面三件事,而不是"卡片":
| 机制 | 说明 | 示例字段 |
|---|---|---|
| 函数调用 | 模型通过 function_call 条目向外部要结构化参数,参数由 JSON Schema 约束 |
tools[] → function_call / function_call_output |
| 结构化输出 | 强制模型输出符合 JSON Schema(strict 模式保证 100% 合法) | text.format = { type: "json_schema", strict: true } |
| 文本输入条目 | 用户结构化的最小载体仍是文本块 | content: [{ type: "input_text" }] |
结论二:deepseek-harness 有"卡片"概念,但它是工具调用的 UI 展示层,不是用户填表用的结构化输入控件。
位于packages/core/tools/src/presentation.ts的 provider-neutral 工具渲染意图词汇表,由工具通过presentCall/presentResult声明,UI 桥接层据此渲染。它与 Responses API 的output条目是两层:Responses 条目是模型→服务端的协议,卡片是工具→前端 UI的展示投影。
harness 中的卡片类型(presentation.ts)
| 卡片 | 说明 |
|---|---|
card: 'generic' |
默认卡片:标题 + 类别图标 + 原始输入 + 跟随文件位置 |
card: 'terminal' |
shell 命令卡片:cwd 头部 + 命令标题 + 输出 + 退出码 |
card: 'diff' |
文件改动内联 diff 卡片(create/edit/overwrite) |
card: 'search' |
搜索卡片:shape: matches(按文件分组)/ shape: paths |
card: 'read' |
文件读取卡片:行号 + 语法高亮 + totalLines |
card: 'web' |
Web 检索卡片:kind: search(引用源)/ kind: fetch(URL+状态码) |
harness 中的"结构化输入"如何实现
harness 用带 JSON Schema 的工具实现结构化,而非"卡片"。最典型的是子代理的结构化输出工具(subagent-in-process-driver/src/structured.ts):
// 工具名与提示词指令
export const STRUCTURED_OUTPUT_TOOL = 'structured_output'
// "你必须调用 structured_output 工具、参数严格匹配 schema,纯文本答案不算结果"
{
"type": "function",
"name": "structured_output",
"parameters": { "... 用户的 JSON Schema ..." } // 结构约束在这里
}
也就是说:"需要用户结构化输入"在 harness 里 = 一个 function 工具的 JSON Schema 参数,它在 Responses API 侧对应 tools[] + function_call/function_call_output 条目,在前端侧才可能被渲染成一张"卡片/表单"UI——而那个 UI 形态不属于 Responses API 协议本身。
7. presentCall / presentResult 声明示例
工具在 defineTool({...}) 里声明两个纯函数展示器——它们不依赖执行、不读环境,UI 可在实时流和回放两条路径上调用:
// ToolDefinition 中的两个可选字段(packages/core/tools/src/schema.ts)
presentCall?(args: InferArgs<S>): ToolCallView | undefined
// 待执行态:调用已发出、结果未回 —— 返回通用/终端/diff 卡片
presentResult?(args: InferArgs<S>, result: ToolResult): ToolResultView | undefined
// 完成态:execute 已返回 —— 返回结果卡片;返回 undefined 时降级为通用卡片
示例 1 · web_search:generic 调用卡片 → web 结果卡片
来源:packages/web/tool-web/src/search.ts。调用态是带 kind: 'search' 的通用卡片;结果态是携带结构化引用源的 card: 'web' 卡片,其 sources 由 output.presentationMeta 投影而来(有损渲染文本无法无损携带)。
export function presentSearchCall(args: { query: string }): GenericCallView {
return { card: 'generic', title: args.query, kind: 'search', rawInput: args.query }
}
export function presentSearchResult(args: { query: string }, result: ToolResult): WebSearchResultView | undefined {
if (result.isError) return undefined // 失败 → 降级为通用卡片
const meta = searchMetaFromResult(result.meta) // 从 meta 窄化出结构化 sources
if (meta === undefined) return undefined
return {
card: 'web',
kind: 'search',
title: args.query,
sources: meta.sources, // WebSource[]:url/title/snippet/publishedAt
truncated: meta.truncated,
...meta.answer !== undefined ? { answer: meta.answer } : {},
}
}
// defineTool 里挂载:
presentCall: presentSearchCall,
presentResult: (args, result) => presentSearchResult(args, result),
示例 2 · bash:terminal 调用卡片 → terminal 结果卡片
来源:packages/shell/tool-bash/src/index.ts。前台命令渲染为终端卡片(后台启动降级为通用卡片);结果态把退出标记拆成退出码胶囊,输出体单独成 output。
function presentBashCall(args: BashCallArgs): GenericCallView | TerminalCallView {
if (args.run_in_background === true) {
return { // 后台:通用卡片
card: 'generic',
title: args.command,
kind: 'execute',
rawInput: args.command,
content: [{ type: 'text', text: args.description }],
}
}
return { // 前台:终端卡片
card: 'terminal',
title: args.command,
description: args.description,
...args.workdir !== undefined ? { cwd: args.workdir } : {},
}
}
function presentBashResult(args: unknown, result: ToolResult): ToolResultView | undefined {
const block = result.content.length === 1 ? result.content[0] : undefined
if (block === undefined || block.type !== 'text') return undefined
const raw = block.text
// 后台确认/错误:无退出码,降级为 fenced 通用卡片
if (isBackground || result.isError) {
return { card: 'generic', content: [{ type: 'text', text: `\`\`\`console\n${raw}\n\`\`\`` }] }
}
const { body, ...exit } = parseExitStatus(raw) // 退出标记 → 退出码胶囊,离开输出体
return { card: 'terminal', output: body, ...exit }
}
示例 3 · write:diff 调用卡片 → diff 结果卡片
来源:packages/fs/tool-fs/src/write.ts。调用态只有参数,新建文件 oldText: null;结果态重复 diff(因为完成态视图会替换待执行态视图),优先用 result.meta 里的应用后元数据。
// 待执行态:无旧内容,oldText 为 null(新建/覆盖)
presentCall(args): DiffCallView {
return {
card: 'diff',
title: `Write ${args.file_path}`,
diffs: [{ path: args.file_path, oldText: null, newText: args.content }],
locations: [{ path: args.file_path }],
}
},
// 完成态:结果视图替换待执行视图,因此重复 diff
presentResult(args, result: ToolResult): DiffResultView | undefined {
if (result.isError) return undefined
const diffs = diffsFromMeta(result.meta)
?? [{ path: args.file_path, oldText: null, newText: args.content }]
return { card: 'diff', title: `Write ${args.file_path}`, diffs }
},
声明要点
- 纯函数、无 I/O——展示器只吃
args(已校验的 typed 参数)和result,UI 可在实时流与回放两条路径复用。 - 返回
undefined即降级——用通用卡片渲染,常用于result.isError或meta畸形(回放安全,不抛异常)。 - 完成态替换待执行态——所以
presentResult常"重复"调用态内容(diff 卡片尤其如此),否则原始结果文本会覆盖卡片。 - 结构化字段走
presentationMeta——无法从有损渲染文本重建的字段(搜索源、diff、read 行号)由output.presentationMeta随会话日志持久化,presentResult再窄化回来。 - 卡片是 UI 展示层,不是 Responses API 协议——
ToolCallView/ToolResultView与 Responses 的output条目分属两个层次,前者由 UI 桥接层消费。
8. 在 deepseek-harness 中的应用
deepseek-harness(dsh)是 DeepSeek AI 的 agent 框架,LLM 层刻意做成供应商中立:核心 dsh-llm 只定义统一词汇(GenerateOptions / StreamChunk / ContentBlock),各 adapter 翻译厂商协议。
Responses API 在三处出现:
| 位置 | 角色 |
|---|---|
packages/llm/llm-pi-ai |
主入口:集成 @earendil-works/pi-ai,协议表 PROTOCOLS 含 'openai-responses' → openAIResponsesApi;路由的 api 字段可指向 Responses;OpenAI 目录模型描述符自带 Responses 协议;replay state 恢复 response id。 |
packages/llm/llm-deepseek |
对照:DeepSeek 官方直连走 Chat Completions(/chat/completions),不走 Responses。 |
packages/subagent/subagent-codex |
测试侧:Codex 原生说 Responses API;responses-fixture.ts 复刻完整 SSE 事件与 response 对象,deepseek-responses-bridge.ts 做 Responses→DeepSeek Chat Completions 的桥接翻译。 |
9. 小结
- Responses API 用
input[]+instructions/output[]取代 Chat Completions 的messages[],工具调用是一等条目,原生支持推理、结构化输出、状态与缓存。 - 输入示例:文本请求、带工具+推理+结构化输出、多轮含
function_call/function_call_output条目。 - 输出示例:完整 response 对象、
function_call条目、SSE 流式事件序列。 - "卡片":Responses API 无原生卡片类型;结构化输入靠函数调用 +
json_schema。deepseek-harness 的"卡片"是工具展示层(generic/terminal/diff/search/read/web),结构化输入靠带 JSON Schema 的工具(如structured_output)。 - presentCall / presentResult:两个纯函数展示器,声明工具在待执行态/完成态的 UI 渲染意图,返回
undefined即降级为通用卡片。 - 在 harness 中:Responses 作为
llm-pi-ai的三种可配置线路协议之一接入;官方 DeepSeek 直连仍走 Chat Completions。
本报告示例的 wire 格式以
deepseek-harness仓库packages/subagent/subagent-codex/tests/responses-fixture.ts、
packages/llm/llm-pi-ai/src/*、packages/core/tools/src/presentation.ts等源码为依据整理。
posted on 2026-08-20 09:20 Gary Zhang 阅读(52) 评论(0) 收藏 举报
浙公网安备 33010602011771号