【AI大模型接入SDK】DeepSeek 流式响应(增量返回)的实现和参数

发布时间:2026/9/3 20:46:50
【AI大模型接入SDK】DeepSeek 流式响应(增量返回)的实现和参数 个人主页艾莉丝努力练剑❄专栏传送门《C语言》《数据结构与算法》《C/C干货分享学习过程记录》《Linux操作系统编程详解》《笔试/面试常见算法从基础到进阶》《Python干货分享》⭐️为天地立心为生民立命为往圣继绝学为万世开太平 艾莉丝的简介文章目录1 ~ SSE 流式响应基础原理1.1 流式响应核心概念1.2 SSE 协议数据规范1.3 cpp-httplib 流式处理机制2 ~ DeepSeek 流式响应接口实现2.1 接口定义与职责边界2.1.1 方法签名2.1.2 回调契约2.2 完整实现流程2.2.1 前置校验2.2.2 请求参数组装2.2.3 消息体构造2.2.4 请求体序列化2.2.5 HTTP 客户端初始化2.2.6 请求头配置2.2.7 流式上下文变量2.2.8 请求对象构建2.2.9 响应处理器2.2.10 数据接收与解析处理器2.2.11 请求发送与异常处理2.2.12 流结束兜底逻辑3 ~ 编译问题定位与修复3.1 错误现象3.2 根因分析3.3 修复方案4 ~ 单元测试用例设计4.1 测试依赖环境4.2 测试用例实现4.2.1 初始化与配置4.2.2 流式回调实现4.2.3 结果断言4.3 入口函数5 ~ 构建运行与结果验证5.1 构建配置5.1.1 CMakeLists.txt5.2 编译执行5.3 验证标准结尾1 ~ SSE 流式响应基础原理1.1 流式响应核心概念定义大模型服务端采用边推理、边生成、边推送的机制通过 HTTP 长连接将生成结果逐块返回客户端而非等待完整结果生成后一次性返回。核心价值降低用户等待感知时长模拟打字机交互效果大幅提升生成式对话的交互体验。技术基础基于 Server-Sent EventsSSEHTTP 流式协议实现是当前大模型对话接口的标准交互模式。1.2 SSE 协议数据规范分隔规则每条事件数据以两个连续换行符\n\n作为事件边界分隔符。标准字段data必填字段承载消息的实际载荷内容。event可选字段标识事件类型。id可选字段消息唯一标识。retry可选字段连接断开后的重连间隔单位为毫秒。流结束标记服务端传输完成后发送data: [DONE]作为流式传输终止标识。1.3 cpp-httplib 流式处理机制核心回调接口content_receiver类型签名std::functionbool(const char* data, size_t len, uint64_t offset, uint64_t totalLength)参数说明data当前接收到的数据块内存指针。len当前数据块的字节长度。offset当前数据块在完整响应体中的字节偏移量。totalLength响应体的总字节长度。返回值语义返回true表示继续接收后续数据返回false表示终止数据接收。工作模式一旦为请求设置content_receiver回调HTTP 请求自动进入非阻塞模式每收到一小块网络数据立即触发回调无需等待完整响应体接收完成。配套回调response_handler在 HTTP 响应头接收完成时触发且仅触发一次用于校验响应状态码与响应头合法性。2 ~ DeepSeek 流式响应接口实现2.1 接口定义与职责边界2.1.1 方法签名namespace ai_chat_sdk { class DeepSeekProvider { public: /** * brief 发送消息并接收流式增量响应 * param messages 历史对话消息列表 * param requestParam 请求参数映射temperature、max_tokens等 * param callback 增量数据回调函数由调用方实现业务逻辑 * return std::string 完整的响应文本所有增量拼接结果 */ std::string sendMessageStream( const std::vectorMessage messages, const std::mapstd::string, std::string requestParam, std::functionvoid(const std::string, bool) callback ); }; }2.1.2 回调契约第一个参数模型返回的增量文本片段仅包含本次新生成的内容。第二个参数流结束标记true表示所有数据已返回完毕false表示仍有后续数据。职责边界SDK 仅负责解析协议并推送增量数据数据的业务处理控制台打印、界面渲染、文件写入等完全由上层调用方实现。2.2 完整实现流程2.2.1 前置校验// 1. 模型可用性校验 if (!isAvailable()) { ERR(DeepSeekProvider sendMessageStream model not available); return ; }校验 API 密钥与服务端点是否已正确初始化不可用则直接返回空字符串并记录错误日志。2.2.2 请求参数组装// 2. 构造请求参数设置默认值 double temperature 0.7; int maxTokens 2048; // 覆盖用户传入的自定义参数 if (requestParam.find(temperature) ! requestParam.end()) { temperature std::stod(requestParam.at(temperature)); } if (requestParam.find(max_tokens) ! requestParam.end()) { maxTokens std::stoi(requestParam.at(max_tokens)); }采用默认值兜底 用户参数覆盖的设计保证参数合法性与灵活性。2.2.3 消息体构造// 3. 构造历史消息JSON数组 Json::Value messageArray(Json::arrayValue); for (const auto message : messages) { Json::Value messageObject; messageObject[role] message._role; messageObject[content] message._content; messageArray.append(messageObject); }将内部Message结构体转换为符合 DeepSeek API 规范的 JSON 消息数组格式。2.2.4 请求体序列化// 4. 构造请求体并序列化 Json::Value requestBody; requestBody[model] getModelName(); requestBody[messages] messageArray; requestBody[temperature] temperature; requestBody[max_tokens] maxTokens; requestBody[stream] true; // 核心字段开启流式响应模式 Json::StreamWriterBuilder writerBuilder; writerBuilder[indentation] ; // 关闭缩进生成紧凑JSON std::string requestBodyStr Json::writeString(writerBuilder, requestBody);与非流式请求的核心差异新增stream: true字段通知服务端以 SSE 格式返回增量数据。2.2.5 HTTP 客户端初始化// 5. 构造HTTP客户端并配置超时 httplib::Client client(_endpoint.c_str()); client.set_connection_timeout(30, 0); // 连接超时30秒 client.set_read_timeout(300, 0); // 读取超时300秒适配长时长流式生成读取超时显著长于普通请求避免大模型长文本生成过程中连接被提前断开。2.2.6 请求头配置// 6. 设置HTTP请求头 httplib::Headers headers { {Authorization, Bearer _apikey}, {Content-Type, application/json}, {Accept, text/event-stream} // 声明客户端接受SSE事件流格式 };新增Accept: text/event-stream请求头明确告知服务端客户端支持流式响应协议。2.2.7 流式上下文变量// 7. 定义流式处理共享上下文变量 std::string buffer; // 数据接收缓冲区用于粘包处理 bool gotError false; // 响应错误标记 std::string errorMsg; // 错误详情描述 bool streamFinish false; // 流式传输完成标记 std::string fullResponse; // 完整响应累积结果所有变量通过 lambda 引用捕获在多个回调函数之间共享状态。2.2.8 请求对象构建// 8. 创建HTTP请求对象 httplib::Request req; req.method POST; req.path /v1/chat/completions; req.headers headers; req.body requestBodyStr;2.2.9 响应处理器// 9. 设置响应处理器响应头到达时触发一次 req.response_handler [](const httplib::Response res) - bool { if (res.status ! 200) { gotError true; errorMsg HTTP status code: std::to_string(res.status); return false; // 状态码异常终止后续数据接收 } return true; // 响应正常继续接收数据 };仅负责 HTTP 层状态校验不处理响应体内容状态异常时直接终止数据流。2.2.10 数据接收与解析处理器// 10. 设置数据接收处理器每收到一块数据触发一次 req.content_receiver [](const char* data, size_t len, size_t offset, size_t totalLength) - bool { // 已发生错误则直接终止 if (gotError) return false; // 数据追加到缓冲区 buffer.append(data, len); // 循环解析所有完整事件块 size_t pos 0; while ((pos buffer.find(\n\n, pos)) ! std::string::npos) { std::string chunk buffer.substr(0, pos); buffer.erase(0, pos 2); // 移除已处理事件分隔符 // 跳过空块与注释行 if (chunk.empty() || chunk.front() :) continue; // 提取data字段载荷 if (chunk.compare(0, 6, data:) 0) { std::string modelData chunk.substr(6); // 检测流结束标记 if (modelData [DONE]) { streamFinish true; return true; } // JSON反序列化 Json::Value modelDataJson; Json::CharReaderBuilder reader; std::string errors; std::istringstream modelDataStream(modelData); if (!Json::parseFromStream(reader, modelDataStream, modelDataJson, errors)) { gotError true; errorMsg JSON parse error: errors; return false; } // 提取增量内容并回调 if (modelDataJson.isMember(choices) modelDataJson[choices].isArray() !modelDataJson[choices].empty() modelDataJson[choices][0].isMember(delta) modelDataJson[choices][0][delta].isMember(content)) { std::string content modelDataJson[choices][0][delta][content].asString(); fullResponse content; // 累积完整响应 callback(content, false); // 向上层推送增量数据 } } } return true; };核心逻辑通过缓冲区实现 TCP 粘包处理严格按照 SSE 协议边界解析事件解析出增量文本后同时完成完整响应累积与上层回调通知。2.2.11 请求发送与异常处理// 11. 发送HTTP请求 auto result client.send(req); if (!result) { // 连接建立阶段失败DNS解析失败、连接超时等 ERR(Network error {}, to_string(result.error())); return ; }注意设置content_receiver后send方法为非阻塞调用返回值仅表示连接建立阶段是否成功。2.2.12 流结束兜底逻辑// 12. 流式结束兜底未收到DONE标记时主动触发结束回调 if (!streamFinish) { WARN(stream ended without [DONE] marker); callback(, true); } return fullResponse;应对网络异常断开等场景保证上层回调总能收到结束通知避免状态机挂死。3 ~ 编译问题定位与修复3.1 错误现象编译报错spdlog日志宏无法格式化httplib::Error类型参数提示类型不匹配。触发代码ERR(Network error {}, result.error());3.2 根因分析httplib::Error是 C11 强类型枚举enum class不支持隐式类型转换spdlog 默认格式化器无法识别该类型。cpp-httplib 库内部提供了std::string to_string(Error error)转换函数与operator重载但未被 spdlog 自动适配。3.3 修复方案// 修复前编译失败 ERR(Network error {}, result.error()); // 修复后编译通过 ERR(Network error {}, to_string(result.error()));显式调用httplib::to_string()将枚举值转换为字符串再传入日志宏进行格式化。4 ~ 单元测试用例设计4.1 测试依赖环境测试框架Google Testgtest日志组件spdlog第三方依赖jsoncpp、cpp-httplib、OpenSSL4.2 测试用例实现4.2.1 初始化与配置#include spdlog/common.h #include ../sdk/include/DeepSeekProvider.h #include ../sdk/include/util/myLog.h TEST(DeepSeekProviderTest, sendMessageStream) { // 实例化Provider对象 auto provider std::make_sharedai_chat_sdk::DeepSeekProvider(); ASSERT_TRUE(provider ! nullptr); // 模型初始化 std::mapstd::string, std::string modelParam; modelParam[api_key] std::getenv(deepseek_apikey); modelParam[endpoint] https://api.deepseek.com; provider-initModel(modelParam); ASSERT_TRUE(provider-isAvailable()); // 请求参数配置 std::mapstd::string, std::string requestParam { {temperature, 0.7}, {max_tokens, 2048} }; // 构造测试消息 std::vectorai_chat_sdk::Message messages; messages.push_back({user, 你是谁?});4.2.2 流式回调实现// 定义增量数据处理回调 auto writeChunk [](const std::string chunk, bool last) { INFO(chunk: {}, chunk); if (last) { INFO([DONE]); } }; // 调用流式接口 std::string fullData provider-sendMessageStream(messages, requestParam, writeChunk);采用 lambda 表达式实现回调将增量内容打印到控制台结束时输出 DONE 标记。4.2.3 结果断言// 校验完整响应非空 ASSERT_FALSE(fullData.empty()); INFO(response: {}, fullData); }4.3 入口函数int main(int argc, char *argv[]) { // 初始化日志库 bite::Logger::initLogger(testLLM, stdout, spdlog::level::debug); // 初始化Google Test testing::InitGoogleTest(argc, argv); return RUN_ALL_TESTS(); }5 ~ 构建运行与结果验证5.1 构建配置5.1.1 CMakeLists.txtcmake_minimum_required(VERSION 3.10) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) project(testLLM) set(CMAKE_BUILD_TYPE Debug) # 添加可执行文件 add_executable(testLLM testLLM.cpp ../sdk/src/util/myLog.cpp ../sdk/src/DeepSeekProvider.cpp ) # 头文件路径 include_directories(${CMAKE_PROJECT_SOURCE_DIR}/../sdk/include) find_package(OpenSSL REQUIRED) include_directories(${OPENSSL_INCLUDE_DIR}) # 编译宏 target_compile_definitions(testLLM PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT) # 链接库 target_link_libraries(testLLM jsoncpp fmt spdlog gtest OpenSSL::SSL OpenSSL::Crypto )5.2 编译执行# 进入构建目录cdtest/build# 生成构建文件cmake..# 清理并编译makecleanmake# 运行测试程序./testLLM5.3 验证标准编译阶段无语法错误与类型错误可执行文件生成成功。运行阶段程序无崩溃退出网络异常时有明确的错误日志输出。流式特性数据按增量逐块返回顺序正确无乱序呈现打字机输出效果。完整性最终累积的完整响应与非流式接口返回结果语义一致无内容缺失。协议合规流结束时正确触发lasttrue回调DONE 标记处理逻辑正常。测试结果gtest 用例输出[ PASSED ]所有断言校验通过。结尾uu们本文的内容到这里就全部结束了艾莉丝在这里再次感谢您的阅读艾莉丝努力练剑C/C Linux 底层探索者 | 一个正在努力练剑的技术博主【关注】跟随我一起深耕技术领域见证每一次成长。❤️【点赞】让优质内容被更多人看见让知识传递更有力量。⭐【收藏】把核心知识点存好在需要时随时查、随时用。【评论】分享你的经验或疑问评论区一起交流避坑不要忘记给博主“一键四连”哦“今日练剑达成”“技术之路难免有困惑但同行的人会让前进更有方向。”结语希望对学习Linux相关内容的uu有所帮助不要忘记给博主“一键四连”哦往期回顾【AI大模型接入SDK】httplib Request参数与HTTP流式响应博主在这里放了一只小狗大家看完了摸摸小狗放松一下吧૮₍ ˶ ˊ ᴥ ˋ˶₎ა