登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

OpenAI Responses API 怎样区分文本增量与工具事件

来源:17golang原创

时间:2026-10-24 20:07:10 239浏览 收藏

OpenAI Responses API 的流式返回不是“每次收到一段文字”这么简单:文本回答、函数工具参数、文件搜索状态和响应生命周期都通过带有 type 的事件发送。要区分文本增量与工具事件,核心是先按事件类型分流,再用 item_id 和 output_index 维护局部状态。

官方地址:https://platform.openai.com/docs/api-reference/responses-streaming

只把 response.output_text.delta 交给文本渲染器;遇到 response.function_call_arguments.delta 时先缓存字符串,等到对应的 .done 事件再解析和执行工具。不要把所有 delta 拼成一条用户可见文本,也不要在参数尚未完成时调用工具。
  • 文本:response.output_text.delta,可以逐段追加。
  • 工具参数:response.function_call_arguments.delta,必须按调用项单独累积。
  • 工具收束:response.function_call_arguments.done,拿到完整 name 和 arguments。
  • 生命周期:response.created、response.completed、error 等,用来更新会话状态。

先按 type 分流,不要按 delta 字段猜事件

Responses API 在 stream=true 时通过服务器发送事件推送生成过程。事件中都有类型标识,但不同事件携带的字段并不相同:文本增量有 delta 和 content_index,函数参数增量也有 delta,生命周期事件则可能带完整 response 或错误对象。因此,判断依据应是完整的 type 字符串,而不是“看到 delta 就显示”。

事件用途应用侧动作
response.output_text.delta一段新增文本追加到当前文本缓冲区
response.function_call_arguments.delta工具参数的局部字符串按 item_id 追加,暂不解析
response.function_call_arguments.done工具参数已完成检查名称、解析 JSON、进入执行策略
response.completed响应生成结束关闭生成状态,等待工具结果或最终收束
error流中发生错误记录错误并停止继续消费
Responses API 流式事件按 type 分流到文本、工具和生命周期处理器的静态结构说明图
图1:事件路由结构说明图,展示文本、工具和生命周期事件的分流关系;这是原创说明图,不是运行截图。

文本增量可以显示,工具增量只能缓存

response.output_text.delta 的 delta 是面向用户的文本片段,通常可以直接追加到当前回答。工具参数增量看起来也像普通字符串,但它可能只是 JSON 的半截,例如先收到 {"city":",后面才收到城市名和结尾括号;这时既不能把它显示成回答,也不能立即交给 JSON 解析器。

一个响应可能包含多个输出项,所以缓存键不能只用事件类型。至少要按 item_id 区分不同工具调用,必要时再结合 output_index 和工具定义中的调用关联信息。文本渲染器和工具参数缓存也应使用不同的数据结构,避免调试信息混入用户回答。

import json

def consume_event(event, text_parts, tool_buffers):
    # 事件对象来自 SSE 解码层;没有 type 时无法安全决定处理路径。
    event_type = event.get("type")
    if event_type == "response.output_text.delta":
        # 文本增量可以立即显示,但仍按事件顺序追加,避免界面乱序。
        delta = event.get("delta", "")
        text_parts.append(delta)
        return {"kind": "text", "delta": delta}

    if event_type == "response.function_call_arguments.delta":
        # 工具参数可能只是半段 JSON,先按调用项缓存,不能提前执行。
        item_id = event.get("item_id")
        if not item_id:
            raise ValueError("工具增量缺少 item_id")
        tool_buffers.setdefault(item_id, []).append(event.get("delta", ""))
        return {"kind": "tool-buffered", "item_id": item_id}

    if event_type == "response.function_call_arguments.done":
        # done 事件给出完整参数;解析后仍需经过工具白名单和参数校验。
        raw = event.get("arguments", "")
        args = json.loads(raw)
        return {
            "kind": "tool-ready",
            "item_id": event.get("item_id"),
            "name": event.get("name"),
            "arguments": args,
        }

    if event_type in {"response.completed", "error"}:
        # 完成与错误负责收束状态,不应被当成用户可见文本。
        return {"kind": "lifecycle", "type": event_type}

    # 其他事件保留给专门处理器,避免静默吞掉未来新增类型。
    return {"kind": "ignored", "type": event_type}

代码只展示分流策略,没有调用真实 API,也不代表已经在本机执行。生产实现还应处理 SSE 解码、断线、重复事件、取消和请求级超时;其中 JSON 解析失败应进入错误状态,而不是重新拼接到用户文本。

工具参数 done 之后才进入执行层

收到 response.function_call_arguments.done 只说明模型给出了完整的函数名和参数,不等于工具已经执行成功。应用侧还要做名称白名单、参数 schema 校验、权限判断和幂等控制。对不允许的工具或不合规参数,应返回可解释的失败状态,不能把模型输出直接当作执行授权。

如果同时出现文本增量和工具事件,界面可以继续显示已经确定的文本,但工具执行状态应在单独区域呈现。工具执行结果通常需要作为下一轮输入交回 Responses API;不要把本地执行结果伪装成模型刚刚生成的文本。

工具参数完成后经过白名单和参数校验再进入执行层的静态边界说明图
图2:工具执行边界说明图,参数完成、授权校验和执行结果是三个不同状态;这是原创结构图,不是运行证据。

还有一个容易忽略的点:工具参数的局部片段不是可靠的审计对象。审计日志应记录最终解析后的参数摘要、调用项标识和执行结果,而不是把每个 delta 都当成一次调用。跨进程消费时要把调用项状态持久化或交给有序的单一消费者,避免两个消费者同时执行同一个工具。

用生命周期事件收束,而不是看到 done 就宣布成功

response.function_call_arguments.done 只收束“参数生成”这个阶段;响应整体还可能处于进行中,工具本身也可能失败。应用状态建议至少区分 STREAMING、TOOL_READY、TOOL_RUNNING、TOOL_SUCCEEDED、FAILED 和 CANCELLED。response.completed 表示响应生成已完成,不应替代工具业务结果。

排查时先记录 type、sequence_number、item_id 和 output_index,再看文本缓冲、工具缓冲与执行记录是否一一对应。遇到未知事件时保留原始类型并安全忽略,避免新事件导致整个流处理器崩溃;但不要把未知事件当作成功。

为什么工具参数不能边收边执行?因为每个 delta 可能只是 JSON 片段,缺少字段、转义字符或闭合符号时都不能证明参数完整。应该等待对应的 .done 事件,再做解析和策略检查。

只处理 response.output_text.delta 可以吗?如果应用永远不启用工具且不关心生命周期,可以先做最小文本客户端;一旦启用工具、文件搜索或错误恢复,就需要保留未处理事件的记录和明确的状态分支。

官方参考:https://platform.openai.com/docs/api-reference/responses-streaming。事件名称和字段以当前官方参考为准,本文的缓存结构、状态名和 Python 分流代码是独立示例。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>