当前位置:首页 > 文章列表 > 科技周边 > 人工智能 > OpenAI 后台响应被取消后客户端怎么保存最终状态

OpenAI 后台响应被取消后客户端怎么保存最终状态

来源:17golang原创 2026-11-11 05:49:31 0浏览 收藏

我在接入后台响应时最容易踩的坑,是把“用户发起了取消”当成“本地任务已经取消”。真正可靠的做法是保存 OpenAI 返回的 response_id,把 response.cancelled 作为独立终态事件写入任务表,并用条件更新保证重复 webhook 和迟到事件不会把终态改回运行中。

官方地址:https://developers.openai.com/api/docs/guides/background

结论:本地任务至少要区分 queued、running、completed、failed、cancelled 和 incomplete;收到取消事件后只允许幂等地落为 cancelled,后续事件不能覆盖已经确认的终态。

先把取消当成一种独立终态

后台模式会异步创建响应,客户端可以轮询响应对象,也可以接收 webhook。官方文档把 queued、in_progress 和离开这两个状态后的最终状态区分开;只有设置了 background=true 的响应才支持取消。webhook 事件中,response.cancelled 表示后台响应已经被取消,它不应该和普通失败共用一个模糊的 error 状态。

我会在本地任务表中保存外部响应和业务状态的双重信息:response_id 用于关联 OpenAI 对象,status 用于驱动客户端展示,version 或更新时间用于挡住旧事件。下面这张图只表达状态关系,是静态说明图,不是实际运行结果。

response_id 从后台响应状态进入取消 webhook 并落到本地 cancelled 终态的静态关系图
图1:响应状态关系说明图,展示取消事件如何落到本地任务的 cancelled 终态。

任务表不要只存一个布尔值

如果只有 is_done,取消、失败和正常完成都会变成同一种结果,客户端无法决定是否重试,也无法向用户解释任务为什么停止。最小表结构可以这样设计:

CREATE TABLE ai_tasks (
  id BIGINT PRIMARY KEY,
  response_id VARCHAR(128) NOT NULL UNIQUE,
  status VARCHAR(24) NOT NULL,
  version INT NOT NULL DEFAULT 0,
  last_event_id VARCHAR(128),
  updated_at TIMESTAMP NOT NULL
);

-- response_id 建唯一约束,防止同一个后台响应被重复创建成本地任务。
-- status 保存业务终态,last_event_id 用于记录最近一次已处理的事件。
UPDATE ai_tasks
SET status = 'cancelled', version = version + 1,
    last_event_id = :event_id, updated_at = CURRENT_TIMESTAMP
WHERE response_id = :response_id
  AND status IN ('queued', 'running');

关键不是字段多,而是更新条件。只有 queued 或 running 允许转成 cancelled;如果同一个事件被投递两次,第二次更新不到行即可视为幂等成功。如果 completed 已经先落库,迟到的 cancelled 不应覆盖它;如果业务明确规定取消优先级,也应该把这条优先级写进状态机,而不是靠最后到达的网络包决定。

Webhook 接收层要验签并快速返回

OpenAI 的 webhook 指南建议在服务端验证签名,且验签应使用尚未解析的原始请求体。接收函数只做验签、记录事件和投递内部任务,然后尽快返回 2xx;耗时的数据库联动或通知放到后台 worker。官方文档还说明,失败响应会触发重试,webhook-id 可以作为去重键。

下面的 Python 示例展示处理思路,具体 SDK 版本以项目实际依赖为准。代码没有把密钥写入文件,unwrap 处理的是原始字节,事件业务处理则交给一个幂等函数。

import os
from openai import OpenAI, InvalidWebhookSignatureError

client = OpenAI(webhook_secret=os.environ["OPENAI_WEBHOOK_SECRET"])

def receive_webhook(raw_body: bytes, headers: dict) -> int:
    # 先用原始请求体验签,不能先 json.loads 再把重排后的内容拿去验证。
    try:
        event = client.webhooks.unwrap(raw_body, headers)
    except InvalidWebhookSignatureError:
        # 签名不可信时直接拒绝,不把事件写入任务队列。
        return 400

    event_key = headers.get("webhook-id")
    # 先按 event_key 去重,再把业务更新交给可重试的 worker。
    if not event_key or already_seen(event_key):
        return 200

    save_event_once(event_key, event.type, event.data.id)
    enqueue_state_update(event_key, event.type, event.data.id)
    # 快速确认收到,避免非必要的 webhook 重试。
    return 200

def apply_state_update(event_key: str, event_type: str, response_id: str) -> None:
    # 只有未结束任务允许被取消、完成或失败事件推进。
    target = {
        "response.cancelled": "cancelled",
        "response.completed": "completed",
        "response.failed": "failed",
        "response.incomplete": "incomplete",
    }.get(event_type)
    if target is not None:
        conditional_update(response_id, target, event_key)

这里的 already_seen 和 save_event_once 应该共享唯一约束,不能只放在进程内存里;服务重启或多副本部署后,内存集合会失效。事件类型不在映射表时可以记录并返回成功,避免未知事件阻塞整个投递链,同时保留告警供后续补充。

原始 webhook 请求经过验签和 webhook-id 去重后执行条件 UPDATE 的静态结构图
图2:幂等落库结构图,展示重复或迟到事件不会覆盖已保存的取消终态。

把恢复和清理策略也写进状态模型

取消不是异常重试的同义词。客户端收到 cancelled 后,可以把任务展示为“已取消”,允许用户新建一次任务;failed 才适合根据错误类型决定自动重试;incomplete 则应单独保留原因,避免把不完整结果误当成成功。查询详情时仍可带上 response_id,方便把本地记录和远端对象对应起来。

清理时也不要马上删除所有行。至少保留事件去重键、最终状态和更新时间一段时间,让重试到达时仍能得到幂等结果;超过保留期再归档或删除事件明细。对外返回状态时只暴露业务需要的字段,不要把 webhook 签名、内部队列名或密钥相关信息放进客户端响应。

常见问题

收到 response.cancelled 后还要继续轮询吗?

通常不必把取消当成继续等待的中间态。保存为 cancelled 后结束本地任务;如果需要补充审计信息,再异步读取或记录,不要让用户界面一直显示处理中。

重复 webhook 会不会把 version 加很多次?

用 webhook-id 唯一约束做事件级去重,再用状态条件做任务级幂等。重复事件第一次更新成功,后续请求应直接返回成功但不重复推进业务状态。

为什么不能只在前端点击取消时改状态?

前端可能断网、刷新或重复点击,真正的远端状态仍由 API 和 webhook 传递。服务端收到 response.cancelled 后再落库,才能让多个客户端看到同一个最终状态。

我现在会把这类任务拆成两层:外部用 response_id 追踪 OpenAI 响应,内部用带条件的状态机追踪业务任务。这样取消、失败、完成和不完整结果各自有明确含义,网络重试也不会把已经确定的 cancelled 改回处理中。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Haystack Pipeline 组件连接类型不匹配怎么办Haystack Pipeline 组件连接类型不匹配怎么办
上一篇
Haystack Pipeline 组件连接类型不匹配怎么办
Hydra 怎么在命令行切换配置组并允许缺省配置
下一篇
Hydra 怎么在命令行切换配置组并允许缺省配置
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    976次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    982次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1098次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1068次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    906次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码