《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
🎬 艾莉丝的简介:
文章目录
- 1 ~> Ollama 流式增量响应 (api/chat)
- 1.1 流式与非流式基础概念
- 1.1.1 核心区分点
- 1.1.2 请求公共参数
- 1.2 curl 接口验证
- 1.2.1 curl 完整请求示例
- 1.2.2 curl 参数释义
- 1.2.3 不同 stream 参数终端表现
- 1.3 Ollama 流式响应报文结构
- 1.3.1 分片数据结构(done=false,持续输出分片)
- 1.3.2 结束分片数据结构(done=true,流结束标记)
- 1.3.2 关键字段解析
- 1.4 C++ httplib 实现流式 sendMessageStream
- 1.4.1 函数整体设计
- 1.4.2 完整业务逻辑流程
- 1.4.3 核心关键代码片段
- 1.5 单元测试实现
- 1.5.1 测试逻辑
- 1.5.2 测试代码片段
- 1.6 编译运行流程(CMake+Make)
- 1.7 关键踩点与易错知识点
- 结尾
1 ~> Ollama 流式增量响应 (api/chat)
1.1 流式与非流式基础概念
1.1.1 核心区分点
- 请求体参数唯一差异:
stream布尔字段stream:true:开启流式响应,分片增量返回,每行输出一条独立 JSON 对象,SSE 风格输出stream:false:关闭流式响应,模型完整生成全部内容后一次性返回完整 JSON 报文
- Ollama 默认行为:
stream=true,默认开启流式输出 - 接口地址:
POST /api/chat - 默认端口:
11434,Ollama 本地服务监听端口
1.1.2 请求公共参数
model:模型名称,必须与ollama pull拉取的模型名称完全匹配messages:消息数组,维护对话上下文role:user用户提问role:assistant大模型回复role:system系统提示词
stream:是否开启流式options:推理配置 JSON 对象temperature:温度系数,取值 0~1;数值越高创造性越强,越低输出越严谨确定num_ctx:Ollama 上下文窗口 token 上限,对应其他大模型接口的 max_ctx/max_tokens
1.2 curl 接口验证
1.2.1 curl 完整请求示例
# Ollama api/chat 流式请求 curl示例curl-s-XPOST"http://127.0.0.1:11434/api/chat"\-H"Content-Type: application/json"\-d'{ "model" : "deepseek-r1:1.5b", "stream" : true, "messages" : [ { "role" : "user", "content" : "你是谁?" } ], "options" : { "temperature" : 0.7, "num_ctx" : 2048 } }'1.2.2 curl 参数释义
-s:静默模式,屏蔽进度条、连接日志,仅输出响应内容-X POST:指定 HTTP 请求方法为 POST-H "Content‑Type: application/json":请求头,告知服务端请求体为 JSON 格式-d:携带 POST 请求体 payload;bash 环境使用单引号包裹 JSON,避免 shell 解析 JSON 内部双引号
1.2.3 不同 stream 参数终端表现
stream=true:终端逐行吐出分片 JSON,模型边生成边返回数据stream=false:阻塞等待模型全部推理完成,一次性输出完整 JSON 报文
1.3 Ollama 流式响应报文结构
1.3.1 分片数据结构(done=false,持续输出分片)
{"model":"deepseek‑r1:1.5b","created_at":"2026‑08‑28T09:14:11.683204021Z","message":{"role":"assistant","content":"您好"},"done":false}1.3.2 结束分片数据结构(done=true,流结束标记)
{"model":"deepseek‑r1:1.5b","created_at":"2026‑08‑28T09:14:28.732433305Z","message":{"role":"assistant","content":""},"done":true,"done_reason":"stop","total_duration":31835253469,"load_duration":12829999536,"prompt_eval_count":6,"prompt_eval_duration":764968000,"eval_count":40,"eval_duration":18124696000}1.3.2 关键字段解析
done布尔字段:流结束判定核心标志false:流式输出未结束,当前为增量分片true:全部内容输出完毕,结束解析循环
message.content:当前分片增量文本,需要业务层拼接所有分片得到完整回答- Ollama 流式特性:Ollama 服务端已经完成原始模型输出封装,返回每行独立 JSON;不使用标准 SSE
data:前缀,分片分隔符为换行符\n,与云端 SSE 接口格式存在差异。
1.4 C++ httplib 实现流式 sendMessageStream
1.4.1 函数整体设计
- 函数签名:
std::string sendMessageStream(消息列表, 请求参数字典, 回调callback) - callback 签名:
std::function<void(const std::string& chunk, bool isFinish)>- chunk:单条增量文本片段
- isFinish:true 代表流传输结束
- 返回值:拼接完成的完整回答字符串
- 依赖库:
httplibhttp 客户端、JsonCppJSON 序列化反序列化库
1.4.2 完整业务逻辑流程
- 前置校验:检测模型实例是否可用,不可用直接返回
- 解析外部传入推理参数
temperature、max_tokens,设置默认兜底值 - 组装
messages数组,转换为 JsonCpp 对象 - 组装
options,注意 Ollama 上下文参数字段名是num_ctx,不是 max_tokens - 组装请求体,强制设置
stream:true,序列化 JSON 字符串 - 创建 httplib 客户端,加长超时时间适配流式长连接
- 连接超时:30s
- 读取超时:300s;流式推理生成耗时较长,读取超时必须放大
- 定义流式接收状态变量
buffer:接收 TCP 字节流缓冲区;TCP 分片会把多条 JSON 块粘包,必须本地缓存缓冲区,按换行分割gotError:请求异常标记streamFinish:流是否正常结束标记fullData:本地拼接完整应答字符串
- 配置
response_handler响应头处理器:捕获 HTTP 状态码,非 200 标记错误终止接收 - 配置
content_receiver内容接收器,处理 TCP 字节流- 将收到字节追加至 buffer 缓冲区
- 循环查找换行符
\n切分 buffer,取出单条 JSON chunk,删除已处理数据 - 跳过空行
- JsonCpp 反序列化单条 chunk
- 如果
chunkJson["done"] == true:设置结束标记,调用 callback (“”,true),终止解析 - 如果存在
message.content,取出增量 delta 片段,追加到 fullData,调用 callback (delta,false)
- 执行
client.send(req)发送 POST 请求 - 请求结束后校验
streamFinish,如果流没有正常结束输出错误日志,触发结束回调 - 返回拼接好的完整
fullData
1.4.3 核心关键代码片段
/** * @brief Ollama 流式对话接口 * @param messages 历史消息上下文 * @param requestParam 外部推理参数字典 * @param callback 分片回调函数,chunk为分片文本,bool标记是否流结束 * @return 拼接完成完整应答 */std::stringOllamaLLMProvider::sendMessageStream(conststd::vector<Message>&messages,conststd::map<std::string,std::string>&requestParam,std::function<void(conststd::string&,bool)>callback){// 1. 模型可用性校验if(!isAvailable()){ERR("OllamaLLMProvider::sendMessageStream: model is not available");return"";}// 2. 解析推理参数,设置默认值floattemperature=0.7f;intmaxTokens=1024;if(requestParam.find("temperature")!=requestParam.end()){temperature=std::stof(requestParam.at("temperature"));}if(requestParam.find("max_tokens")!=requestParam.end()){maxTokens=std::stoi(requestParam.at("max_tokens"));}// 3. 组装消息数组Json::ValuemessageArray(Json::arrayValue);for(constauto&msg:messages){Json::ValuemsgObj(Json::objectValue);msgObj["role"]=msg._role;msgObj["content"]=msg._content;messageArray.append(msgObj);}// 4. 组装请求体options,Ollama上下文参数是num_ctxJson::Valueoptions(Json::objectValue);options["temperature"]=temperature;options["num_ctx"]=maxTokens;Json::ValuerequestBody(Json::objectValue);requestBody["model"]=_modelName;requestBody["messages"]=messageArray;requestBody["options"]=options;requestBody["stream"]=true;// 强制开启流式// JSON序列化Json::StreamWriterBuilder writerBuilder;std::string requestBodyStr=Json::writeString(writerBuilder,requestBody);// 5. http客户端,流式需要放大读取超时httplib::Clientclient(_endpoint.c_str());client.set_connection_timeout(30,0);client.set_read_timeout(300,0);httplib::Headers headers={{"Content‑Type","application/json"}};// 流式状态变量std::string buffer;boolgotError=false;std::string errorMsg;intstatusCode=0;boolstreamFinish=false;std::string fullData;httplib::Request req;req.method="POST";req.path="/api/chat";req.headers=headers;req.body=requestBodyStr;// 响应头处理器,捕获HTTP状态码req.response_handler=[&](consthttplib::Response&res)->bool{statusCode=res.status;if(statusCode!=200){gotError=true;errorMsg="OllamaLLMProvider::sendMessageStream failed, status:"+std::to_string(statusCode);returnfalse;// 终止请求}returntrue;};// TCP内容接收器:处理粘包,缓冲区切分换行JSON块req.content_receiver=[&](constchar*data,size_t datalen,size_t offset,size_t totalLength)->bool{if(gotError){returnfalse;}buffer.append(data,datalen);// 循环分割换行,取出完整JSON chunksize_t pos=0;while((pos=buffer.find('\n',pos))!=std::string::npos){std::string chunk=buffer.substr(0,pos);buffer.erase(0,pos+1);if(chunk.empty()){continue;}// JSON反序列化Json::Value chunkJson;Json::CharReaderBuilder readerBuilder;std::string parseErr;std::istringstreamchunkStream(chunk);if(!Json::parseFromStream(readerBuilder,&chunkStream,&chunkJson,&parseErr)){ERR("OllamaLLMProvider::sendMessageStream parse chunk json error: {}",parseErr);continue;}// 判断流结束标记if(chunkJson.get("done",false).asBool()){streamFinish=true;callback("",true);returntrue;}// 提取增量分片内容if(chunkJson.isMember("message")&&chunkJson["message"].isMember("content")){std::string delta=chunkJson["message"]["content"].asString();fullData+=delta;callback(delta,false);}}returntrue;};// 发送http请求autorespResult=client.send(req);if(!respResult){ERR("OllamaLLMProvider::sendMessageStream send request failed, error:{}",httplib::to_string(respResult.error()));return"";}// 校验流是否正常结束,做异常兜底if(!streamFinish){ERR("OllamaLLMProvider::sendMessageStream stream not finish, fullData:{}",fullData);callback("",true);}returnfullData;}1.5 单元测试实现
1.5.1 测试逻辑
- 构造模型配置,填入模型名称、endpoint 地址
- 初始化 provider,校验模型可用性
isAvailable() - 组装推理参数:temperature、max_tokens
- 组装消息数组,user 提问
- 定义 lambda 回调函数:打印每一个 chunk 分片,当 isFinish 为 true 打印
[DONE] - 调用
sendMessageStream获取完整返回字符串 - 断言返回结果非空,打印完整应答内容
1.5.2 测试代码片段
TEST(OllamaLLMProviderTest,sendMessageStream){std::map<std::string,std::string>modelParam;modelParam["model_name"]="deepseek‑r1:1.5b";modelParam["model_desc"]="本地部署deepseek‑r1:1.5b模型";modelParam["endpoint"]="http://localhost:11434";autoprovider=std::make_shared<OllamaLLMProvider>();provider->initModel(modelParam);ASSERT_TRUE(provider->isAvailable());std::map<std::string,std::string>requestParam={{"temperature","0.7"},{"max_tokens","2048"}};std::vector<ai_chat_sdk::Message>messages;messages.push_back({"user","你是谁?"});// 分片回调lambdaautowriteChunk=[&](conststd::string&chunk,boollast)->void{INFO("chunk:{}",chunk);if(last){INFO("[DONE]");}};std::string fullResp=provider->sendMessageStream(messages,requestParam,writeChunk);ASSERT_FALSE(fullResp.empty());INFO("response:{}",fullResp);}1.6 编译运行流程(CMake+Make)
- 进入 build 构建目录
- 执行
cmake ..,生成 Makefile 构建脚本 - 执行
make编译生成可执行测试程序 - 运行
./testLLM执行单元测试 - 观察日志输出:逐行打印每一块 chunk 分片,流结束打印
[DONE],输出拼接完成完整回答,测试 PASS
1.7 关键踩点与易错知识点
- Ollama 上下文参数字段是
num_ctx,不是 max_tokens,这是高频踩坑点 - TCP 流式传输存在粘包,不能直接按 HTTP 返回块解析,必须维护本地
buffer缓冲区,按换行符切分 JSON 对象 - 流式长连接必须放大
read_timeout,模型推理生成文本需要时间,过小会直接超时断开 - 流结束不能仅依赖收到 done=true 分片;代码需要增加兜底校验,如果请求完成但是
streamFinish没有置 true,判定为异常中断,主动触发结束回调 - Ollama 流式输出和标准 SSE 云端接口差异:Ollama 返回纯 JSON 行,没有
data:前缀,分隔符为\n;云端 SSE 一般使用\n\n作为分隔符,前缀为data:,结束标记为[DONE] - deepseek‑r1 模型会输出 `` 思考过程标签,该标签同样会被拆分为多个增量分片返回,上层业务需要自行处理过滤
结尾
uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
|
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!
往期回顾:
【AI大模型接入SDK】Ollama API 全量响应实现
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა