news 2026/9/13 23:31:10

【AI大模型接入SDK】Ollama API 流式增量响应

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【AI大模型接入SDK】Ollama API 流式增量响应


🎬 个人主页艾莉丝努力练剑

专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
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;不使用标准 SSEdata:前缀,分片分隔符为换行符\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 完整业务逻辑流程

  1. 前置校验:检测模型实例是否可用,不可用直接返回
  2. 解析外部传入推理参数temperaturemax_tokens,设置默认兜底值
  3. 组装messages数组,转换为 JsonCpp 对象
  4. 组装options,注意 Ollama 上下文参数字段名是num_ctx,不是 max_tokens
  5. 组装请求体,强制设置stream:true,序列化 JSON 字符串
  6. 创建 httplib 客户端,加长超时时间适配流式长连接
  • 连接超时:30s
  • 读取超时:300s;流式推理生成耗时较长,读取超时必须放大
  1. 定义流式接收状态变量
  • buffer:接收 TCP 字节流缓冲区;TCP 分片会把多条 JSON 块粘包,必须本地缓存缓冲区,按换行分割
  • gotError:请求异常标记
  • streamFinish:流是否正常结束标记
  • fullData:本地拼接完整应答字符串
  1. 配置response_handler响应头处理器:捕获 HTTP 状态码,非 200 标记错误终止接收
  2. 配置content_receiver内容接收器,处理 TCP 字节流
    1. 将收到字节追加至 buffer 缓冲区
    2. 循环查找换行符\n切分 buffer,取出单条 JSON chunk,删除已处理数据
    3. 跳过空行
    4. JsonCpp 反序列化单条 chunk
    5. 如果chunkJson["done"] == true:设置结束标记,调用 callback (“”,true),终止解析
    6. 如果存在message.content,取出增量 delta 片段,追加到 fullData,调用 callback (delta,false)
  3. 执行client.send(req)发送 POST 请求
  4. 请求结束后校验streamFinish,如果流没有正常结束输出错误日志,触发结束回调
  5. 返回拼接好的完整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 测试逻辑

  1. 构造模型配置,填入模型名称、endpoint 地址
  2. 初始化 provider,校验模型可用性isAvailable()
  3. 组装推理参数:temperature、max_tokens
  4. 组装消息数组,user 提问
  5. 定义 lambda 回调函数:打印每一个 chunk 分片,当 isFinish 为 true 打印[DONE]
  6. 调用sendMessageStream获取完整返回字符串
  7. 断言返回结果非空,打印完整应答内容

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)

  1. 进入 build 构建目录
  2. 执行cmake ..,生成 Makefile 构建脚本
  3. 执行make编译生成可执行测试程序
  4. 运行./testLLM执行单元测试
  5. 观察日志输出:逐行打印每一块 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们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

艾莉丝努力练剑

C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主

👀【关注】跟随我一起深耕技术领域,见证每一次成长。
❤️【点赞】让优质内容被更多人看见,让知识传递更有力量。
【收藏】把核心知识点存好,在需要时随时查、随时用。
💬【评论】分享你的经验或疑问,评论区一起交流避坑!

不要忘记给博主“一键四连”哦!

“今日练剑达成!”

“技术之路难免有困惑,但同行的人会让前进更有方向。”

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!

往期回顾

【AI大模型接入SDK】Ollama API 全量响应实现

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡
૮₍ ˶ ˊ ᴥ ˋ˶₎ა

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 23:28:44

8款热门AI论文写作工具横向实测,本硕博撰稿避坑实操指南

前言&#xff1a;AI 写论文乱象频发&#xff0c;实测 8 款工具理清适配边界 每到毕业季&#xff0c;本科生、硕博生都会集中寻找 AI 论文辅助工具&#xff0c;市面各类写作软件层出不穷。然而&#xff0c;这些工具普遍存在几大硬伤&#xff1a;虚假参考文献、无法匹配本校格式…

作者头像 李华
网站建设 2026/9/13 23:27:44

YOLOv5草莓目标检测实战:从数据标注到调优的完整流程

简介&#xff1a;基于YOLOv5的草莓目标检测项目&#xff0c;源自“猛犸杯”比赛赛题&#xff0c;因赛事主办方提供的数据量非常少&#xff0c;数据预处理与增强便成为提升模型准确率的关键。项目定位于人工智能、计算机、通信工程、自动化等专业方向&#xff0c;适用于在校学生…

作者头像 李华
网站建设 2026/9/13 23:27:24

第31章 咸•感应 初见

2017年的巴黎春天来得比往年晚。四月了&#xff0c;塞纳河两岸的梧桐树才开始冒出极小的芽点&#xff0c;那种绿色淡得像水彩在纸面上洇开后的第一层底色。悦儿住在大学城一间不到十二平米的宿舍里&#xff0c;房间里有一张床、一张书桌、一个塞满了书的小书架。桌面的正中间放…

作者头像 李华
网站建设 2026/9/13 23:26:54

MXNet原生实现人脸年龄性别识别全流程

简介&#xff1a;本资源是基于MXNet框架实现的高精度年龄与性别识别模型&#xff0c;面向深度学习初学者、计算机视觉开发者及AI项目实践者&#xff0c;解决人脸属性分析这一典型CV任务。资源包共28个文件&#xff0c;含13个Python源码&#xff08;涵盖数据预处理、MTNN人脸检测…

作者头像 李华