OpenAI API 流式传输
OpenAI API 流式传输教程 🌊
本教程将详细解释 OpenAI API 如何进行数据流式传输,从基本的文本块到复杂的工具调用指令。流式传输允许你逐步从模型接收数据,这对于构建响应灵敏的用户界面和处理长输出非常有用。
1. 基础知识:Server-Sent Events (SSE)
OpenAI 使用 Server-Sent Events (SSE) 作为其流式传输的底层协议。当你发起流式请求时,API 会保持 HTTP 连接打开,并以特定格式发送数据。
Content-Type
:服务器将响应Content-Type: text/event-stream
。- 事件结构:数据以数据块的形式发送,每个数据块通常以
data:
开头,后跟一个 JSON 有效负载,并以两个换行符 (\n\n
) 结束。data: {"id":"chatcmpl-xxxx", "choices": [{"delta": {"content": "你好"}}]}data: {"id":"chatcmpl-xxxx", "choices": [{"delta": {"content": "世界"}}]}
- 流结束标记:数据流由一个特殊消息终止:
data: [DONE]
2. 流式传输基本文本响应 📝
这是流式传输最常见的用例——逐块获取模型生成的文本。
请求流式传输:
要在你的 Chat Completions API 请求中启用流式传输,请设置 stream: true
。
文本块(Chunk)的结构:
每个 data:
块将包含一个 JSON 对象。对于文本,关键部分包括:
id
: 聊天补全的唯一 ID。object
: 对象类型 (例如,chat.completion.chunk
)。created
: 创建时间戳。model
: 使用的模型 (例如,gpt-4o-mini
)。choices
: 一个包含选项的数组。对于流式传输,你通常关注choices[0]
。choices[0].delta
: 此对象包含此块中的变更或新数据。delta.role
: 通常在助手回合开始时出现一次 (例如,{"role": "assistant"}
)。delta.content
: 这是实际的文本片段。它可能是一个词、词的一部分或几个词。
choices[0].finish_reason
: 在该选项的流结束前,此字段将为null
。当模型因“自然”停止而完成文本生成时,它将是stop
。其他原因包括length
(达到最大 token 数) 或tool_calls
。
文本流示例流程:
-
请求:
{"model": "gpt-4o-mini","messages": [{"role": "user", "content": "给我讲个简短的故事。"}],"stream": true }
-
流式响应 (简化的
data:
有效负载序列):- 块 1 (角色和内容开始):
{"id": "chatcmpl-123", "object": "chat.completion.chunk", "created": 1717500000, "model": "gpt-4o-mini","choices": [{"index": 0, "delta": {"role": "assistant", "content": "从前"}, "finish_reason": null}] }
- 块 2 (更多内容):
{"id": "chatcmpl-123", "object": "chat.completion.chunk", "created": 1717500000, "model": "gpt-4o-mini","choices": [{"index": 0, "delta": {"content": "有个"}, "finish_reason": null}] }
- 块 3 (更多内容):
{"id": "chatcmpl-123", "object": "chat.completion.chunk", "created": 1717500000, "model": "gpt-4o-mini","choices": [{"index": 0, "delta": {"content": "小村庄..."}, "finish_reason": null}] }
- 块 4 (生成结束):
{"id": "chatcmpl-123", "object": "chat.completion.chunk", "created": 1717500000, "model": "gpt-4o-mini","choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}] }
- 流结束标记:
data: [DONE]
- 块 1 (角色和内容开始):
客户端文本处理逻辑:
你需要累积来自每个块的 delta.content
片段以重建完整的消息。
3. 流式传输工具调用 (高级) 🛠️
当你希望模型输出结构化数据以调用外部函数或工具时,你会使用“工具调用”(Tool Calling)。这也支持流式传输。
请求工具调用:
在你的 API 请求中包含 tools
(描述你可用工具/函数的数组) 和可选的 tool_choice
。
工具调用在流中的显示方式:
除了 delta.content
(或代替它),你会看到 delta.tool_calls
。
tool_call
块的结构:
delta.tool_calls
字段是一个数组,因为模型可能决定调用多个工具。此数组中的每个对象代表特定工具调用的信息块。
choices[0].delta.tool_calls
: 一个数组。每个元素通常具有:index
: 一个整数,标识此块属于哪个工具调用 (如果模型并行调用多个工具,则第一个为 0,第二个为 1,依此类推)。id
: 此特定工具调用实例的唯一 ID (例如,call_abc123
)。此 ID 对于将结果匹配回调用非常重要。type
: 总是"function"
。function
: 一个包含以下内容的对象:name
: 模型想要调用的函数的名称 (例如,get_current_weather
)。这可能分部分流式传输,但通常会完整提供。arguments
: 一个包含函数 JSON 参数的字符串 (例如,{"location": "波士顿"}
)。这部分最常以片段形式流式传输。
工具调用流示例流程:
-
请求 (简化):
{"model": "gpt-4o-mini","messages": [{"role": "user", "content": "波士顿今天天气怎么样?"}],"tools": [{"type": "function","function": {"name": "get_current_weather","description": "获取指定地点的当前天气","parameters": {"type": "object","properties": {"location": {"type": "string", "description": "城市和州,例如 San Francisco, CA"}},"required": ["location"]}}}],"stream": true }
-
流式响应 (简化的
data:
有效负载序列,重点关注工具调用):-
块 1 (工具调用开始,可能包含
role
):{"choices": [{"index": 0,"delta": {"role": "assistant", // 可能在这里或之前的块中"content": null, // 通常为 null,因为模型直接进行工具调用"tool_calls": [{"index": 0// id, type, function name 和 arguments 的开头可能在这里或后续块中}]}}]// ... 其他字段省略 ... }
-
块 2 (提供工具调用的
id
,type
,function.name
):{"choices": [{"index": 0,"delta": {"tool_calls": [{"index": 0,"id": "call_abc123","type": "function","function": {"name": "get_current_weather","arguments": "{" // 参数开始}}]}}]// ... 其他字段省略 ... }
-
块 3 (流式传输
function.arguments
片段):{"choices": [{"index": 0,"delta": {"tool_calls": [{"index": 0,"function": {"arguments": "\"location\":\"波" // 参数片段}}]}}]// ... 其他字段省略 ... }
-
块 4 (继续流式传输
function.arguments
):{"choices": [{"index": 0,"delta": {"tool_calls": [{"index": 0,"function": {"arguments": "士顿\"}" // 参数片段结束}}]}}]// ... 其他字段省略 ... }
-
块 5 (工具调用信息发送完毕,
finish_reason
为tool_calls
):{"choices": [{"index": 0,"delta": {}, // delta 可能为空"finish_reason": "tool_calls"}]// ... 其他字段省略 ... }
-
流结束标记:
data: [DONE]
-
处理并行工具调用:
如果模型决定并行调用多个工具,delta.tool_calls
数组中将包含多个具有不同 index
值的对象。客户端需要能够同时累积每个 index
的工具调用信息。
客户端工具调用处理逻辑:
- 初始化一个数据结构 (例如,一个对象或字典) 来按
index
存储每个工具调用的累积信息 (id
,name
,arguments
片段)。 - 当收到包含
delta.tool_calls
的块时,遍历数组中的每个条目。 - 根据其
index
,将id
,function.name
和function.arguments
的片段附加到相应的累积对象中。 - 持续拼接
arguments
字符串片段。 - 当
finish_reason
为"tool_calls"
时,表示模型已完成指定所有工具调用。此时,你应该已经为每个请求的工具调用累积了完整的name
和arguments
字符串。 - 将累积的
arguments
字符串解析为 JSON 对象,以便执行工具。
4. 整合:客户端逻辑要点
- 初始化累加器:为
choices[0]
维护一个对象,用于累积role
,content
。如果涉及工具调用,则需要一个更复杂的结构来按index
存储每个tool_call
的id
,name
和arguments
。 - 处理每个 SSE 事件:
- 检查是否为
data: [DONE]
,如果是则停止处理。 - 解析
data:
后面的 JSON 字符串。 - 根据
delta
中的字段更新累加器:- 如果存在
delta.role
,则设置角色。 - 如果存在
delta.content
,则追加到累积的文本内容中。 - 如果存在
delta.tool_calls
,则遍历数组,根据index
更新或创建工具调用对象,并追加id
,function.name
和function.arguments
的片段。
- 如果存在
- 检查是否为
- 处理
finish_reason
:stop
: 文本生成完成。length
: 因达到max_tokens
而停止。tool_calls
: 模型请求执行工具调用。此时,你需要将收集到的完整工具调用信息用于执行相应的函数,并将结果作为新的消息发送回 API 以继续对话。- 其他
finish_reason
(如content_filter
)。
5. 关键注意事项和最佳实践 ✨
- 使用官方库:OpenAI 提供了 Python 和 Node.js/TypeScript 的官方客户端库,它们极大地简化了流式响应(包括工具调用)的处理,内置了累积和解析逻辑。强烈建议使用这些库。
- 错误处理:网络连接可能会中断,或者 API 可能返回错误。确保你的代码能够妥善处理这些情况。
- 稳健的 JSON 解析:特别是对于流式传输的
function.arguments
,在将其解析为 JSON 对象之前,务必确保已收到并正确拼接了所有片段。 - API 版本和模型差异:虽然核心流式结构相对稳定,但始终关注 OpenAI 的官方文档,了解特定模型或 API 版本的任何细微差别或新增功能。
希望这份教程能帮助你更好地理解和使用 OpenAI 的流式 API!
相关文章:
OpenAI API 流式传输
OpenAI API 流式传输教程 🌊 本教程将详细解释 OpenAI API 如何进行数据流式传输,从基本的文本块到复杂的工具调用指令。流式传输允许你逐步从模型接收数据,这对于构建响应灵敏的用户界面和处理长输出非常有用。 1. 基础知识:Ser…...
嵌入式分析利器:DuckDB与SqlSugar实战
一、DuckDB 的核心特性与适用场景 DuckDB 是一款 嵌入式分析型数据库(OLAP) ,专为高效查询设计,主要特点包括: 列式存储与向量化引擎 数据按列存储,提升聚合统计效率(如 SUM/AVG…...
嵌入式学习笔记 - freeRTOS任务设计要点
一 中断函数中不允许操作任务 因为中断函数使用的上下文环境是MSP环境,而非PSP环境,不允许挂起任务,不允许阻塞任务的任何操作。 可以使用FromISR函数进行操作。 二 中断的频率与处理时间 中断的处理时间要远低于任务的运行时间ÿ…...

Linux运维笔记:1010实验室电脑资源规范使用指南
文章目录 一. 检查资源使用情况,避免冲突1. 检查在线用户2. 检查 CPU 使用情况3. 检查 GPU 使用情况4. 协作建议 二. 备份重要文件和数据三. 定期清理硬盘空间四. 退出 ThinLinc 时注销,释放内存五. 校外使用时配置 VPN注意事项 总结 实验室的电脑配备了…...

12:点云处理—调平,角度,平面度,高度,体积
1.调平 2.夹角、平面度 3.高度、体积...
Marketo 集成 8x8 Connect 短信 API 指南
一、🔍 项目背景与目标 在营销自动化流程中,需要在用户完成特定行为(如填写表单、完成注册)后,自动发送一条短信进行提醒、欢迎或验证。 Marketo 原生不具备短信发送能力,但支持通过 Webhook 集成第三方 A…...

【Docker 从入门到实战全攻略(二):核心概念 + 命令详解 + 部署案例】
5. Docker Compose Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。通过一个 YAML 文件来配置应用服务,然后使用一个命令即可创建并启动所有服务。 基本命令 docker-compose up # 创建并启动所有服务 docker-compose down # 停止并移除容器、网络等…...
Elasticsearch索引(Index)介绍,它与数据库中的表有什么区别?
在Elasticsearch(ES)中,索引(Index)是存储和组织文档(Document)的逻辑容器,类似于关系型数据库(如MySQL)中的“数据库(Database)”或“表(Table)”,但设计理念和实现机制有显著差异。以下从定义、核心特性、与数据库表的对比三方面详细解析。 一、索引的定义与…...
Elasticsearch中什么是分析器(Analyzer)?它由哪些组件组成?
在Elasticsearch(ES)中,分析器(Analyzer)是处理文本的核心组件,负责将原始文本转换为适合索引和搜索的词项(Term)。它直接影响搜索的准确性和性能,是构建高效搜索系统的关键。 一、分析器的核心作用 1. 分词(Tokenization):将文本拆分为独立的词(Token)。 例如…...
使用 SseEmitter 实现 Spring Boot 后端的流式传输和前端的数据接收
1.普通文本消息的发送和接收 GetMapping("/stream")public SseEmitter streamResponse() {SseEmitter emitter new SseEmitter(0L); // 0L 表示永不超时Executors.newSingleThreadExecutor().execute(() -> {try {for (int i 1; i < 5; i) {emitter.send(&q…...
.net Avalonia 在centos部署
.NET Avalonia 在 CentOS 部署指南 在跨平台应用开发中,.NET Avalonia 凭借其强大的功能和灵活性受到了广泛关注。而将基于 .NET Avalonia 开发的应用程序部署到 CentOS 系统上,是很多开发者会面临的任务。下面就为大家详细介绍在 CentOS 上部署 .NET A…...
MyBatis深度解析:XML/注解配置与动态SQL编写实战
引言 在现代Java企业级应用开发中,MyBatis作为一款优秀的持久层框架,因其灵活性和易用性广受开发者喜爱。相比Hibernate等全自动ORM框架,MyBatis提供了更接近SQL的开发体验,同时又不失面向对象的优雅。本文将深入探讨MyBatis的核…...
面试经验 对常用 LLM 工具链(如 LlamaFactory)的熟悉程度和实践经验
面试场景: 你正在面试一个大型语言模型(LLM)工程师或研究员的职位,面试官想了解你对常用 LLM 工具链(如 LlamaFactory)的熟悉程度和实践经验。 面试经验分享:LlamaFactory-CLI 工具实践 面试官…...

【conda配置深度学习环境】
好的!我们从头开始配置一个基于Conda的虚拟环境,覆盖深度学习(如PyTorch)和传统机器学习(如XGBoost),并适配你的显卡(假设为NVIDIA,若为AMD请告知)。以下是完…...

力扣4.寻找两个正序数组的中位数
文章目录 题目介绍题解 题目介绍 题解 题解链接:题解 核心思路:通过二分查找的确定分割点使左右两部分元素数量相等。 class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int n1 nums1.length;int n2 nums2.length…...

【相机基础知识与物体检测】更新中
参考: 黑马机器人 | 相机标定&物体检测https://robot.czxy.com/docs/camera/ 01-相机基础 相机基础概述 相机是机器视觉的基础,相机直接产生了相机数据。所有视觉算法都是作用在相机数据上的。相机数据的好坏,或者对相机数据的理解方式…...

【前端】性能优化和分类
本页知识点参考:https://zhuanlan.zhihu.com/p/514222781 1. 加载性能优化 1.1 网站性能优化 content方法: 1)减少HTTP请求:合并文件,CSS精灵,inline Image 2)减少DNS查询:DNS缓存&…...

PPO和GRPO算法
verl 是现在非常火的 rl 框架,而且已经支持了多个 rl 算法(ppo、grpo 等等)。 过去对 rl 的理解很粗浅(只知道有好多个角色,有的更新权重,有的不更新),也曾硬着头皮看了一些论文和知…...
ceph 对象存储用户限额满导致无法上传文件
查看日志 kl logs -f rook-ceph-rgw-my-store-a-5cc4c4d5b5-26n6j|grep -i error|head -1Defaulted container "rgw" out of: rgw, log-collector, chown-container-data-dir (init) debug 2025-05-30T19:44:11.573+0000 7fa7b7a6d700...

rk3588 上运行smolvlm-realtime-webcam,将视频转为文字描述
smolvlm-realtime-webcam 是一个开源项目,结合了轻量级多模态模型 SmolVLM 和本地推理引擎 llama.cpp,能够在本地实时处理摄像头视频流,生成自然语言描述, 开源项目地址 https://github.com/ngxson/smolvlm-realtime-webcamhttps…...
某航参数逆向及设备指纹分析
文章目录 1. 写在前面2. 接口分析3. 加密分析4. 算法还原5. 设备指纹风控分析与绕过【🏠作者主页】:吴秋霖 【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究…...
SQL思路解析:窗口滑动的应用
目录 🎯 问题目标 第一步:从数据中我们能直接得到什么? 第二步:我们想要的“7天窗口”长什么样? 第三步:SQL 怎么表达“某一天的前六天”? 🔍JOIN 比窗口函数更灵活 第四步&am…...

Rust 学习笔记:Box<T>
Rust 学习笔记:Box Rust 学习笔记:Box<T\>Box\<T> 简介使用 Box\<T\> 在堆上存储数据启用带有 box 的递归类型关于 cons 列表的介绍计算非递归类型的大小使用 Box\<T\> 获取大小已知的递归类型 Rust 学习笔记:Box<…...
C# 从 ConcurrentDictionary 中取出并移除第一个元素
C# 从 ConcurrentDictionary 中取出并移除第一个元素 要从 ConcurrentDictionary<byte, int> 中取出并移除第一个元素,需要结合 遍历 和 原子移除操作。由于 ConcurrentDictionary 是无序集合,"第一个元素" 通常是指最早添加的元素&…...

操作系统学习(十三)——Linux
一、Linux Linux 是一种类 Unix 的自由开源操作系统内核,由芬兰人 Linus Torvalds 于 1991 年首次发布。如今它广泛应用于服务器、桌面、嵌入式设备、移动设备(如 Android)等领域。 设计思想: 原则描述模块化与可移植性Linux 内…...

NLP学习路线图(二十二): 循环神经网络(RNN)
在自然语言处理(NLP)的广阔天地中,序列数据是绝对的核心——无论是流淌的文本、连续的语音还是跳跃的时间序列,都蕴含着前后紧密关联的信息。传统神经网络如同面对一幅打散的拼图,无法理解词语间的顺序关系,…...

每日一C(1)C语言的内存分布
目录 代码区 常量区 全局/静态区 初始化数据段(.data) 未初始化数据段(.bss) 堆区 栈区 总结 今天我们学习的是C语言的内存分布,以及这些分区所存储的内容和其特点。今天的思维导图如下。 C语言作为一款直接处…...

Photoshop使用钢笔绘制图形
1、绘制脸部路径 选择钢笔工具,再选择“路径”。 基于两个点绘制一个弯曲的曲线 使用Alt键移动单个点,该点决定了后续的曲线方向 继续绘制第3个点 最后一个点首尾是同一个点,使用钢笔保证是闭合回路。 以同样的方式绘制2个眼睛外框。 使用椭…...

应用层协议:HTTP
目录 HTTP:超文本传输协议 1.1 HTTP报文 1.1.1 请求报文 1.1.2 响应报文 1.2 HTTP请求过程和原理 1.2.1 请求过程 1、域名(DNS)解析 2、建立TCP连接(三次握手) 3、发送HTTP请求 4、服务器处理请求 5、返回H…...

复习——C++
1、scanf和scanf_s区别 2、取地址,输出 char ba; char* p&b; cout<<*p; cout<<p; p(char*)"abc"; cout<<*p; cout<<p; cout<<(void*)p; 取地址,把b的地址给p 输出*p,是输出p的空间内的值…...