云计算百科
云计算领域专业知识百科平台

【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 -X POST "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 代表流传输结束
  • 返回值:拼接完成的完整回答字符串
  • 依赖库:httplib http 客户端、JsonCpp JSON 序列化反序列化库

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::string OllamaLLMProvider::sendMessageStream(
    const std::vector<Message>& messages,
    const std::map<std::string, std::string>& requestParam,
    std::function<void(const std::string&, bool)> callback)
    {
    // 1. 模型可用性校验
    if(!isAvailable()){
    ERR("OllamaLLMProvider::sendMessageStream: model is not available");
    return "";
    }

    // 2. 解析推理参数,设置默认值
    float temperature = 0.7f;
    int maxTokens = 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::Value messageArray(Json::arrayValue);
    for(const auto& msg : messages){
    Json::Value msgObj(Json::objectValue);
    msgObj["role"] = msg._role;
    msgObj["content"] = msg._content;
    messageArray.append(msgObj);
    }

    // 4. 组装请求体options,Ollama上下文参数是num_ctx
    Json::Value options(Json::objectValue);
    options["temperature"] = temperature;
    options["num_ctx"] = maxTokens;

    Json::Value requestBody(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::Client client(_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;
    bool gotError = false;
    std::string errorMsg;
    int statusCode = 0;
    bool streamFinish = false;
    std::string fullData;

    httplib::Request req;
    req.method = "POST";
    req.path = "/api/chat";
    req.headers = headers;
    req.body = requestBodyStr;

    // 响应头处理器,捕获HTTP状态码
    req.response_handler = [&](const httplib::Response& res) -> bool{
    statusCode = res.status;
    if(statusCode != 200){
    gotError = true;
    errorMsg = "OllamaLLMProvider::sendMessageStream failed, status:" + std::to_string(statusCode);
    return false; // 终止请求
    }
    return true;
    };

    // TCP内容接收器:处理粘包,缓冲区切分换行JSON块
    req.content_receiver = [&](const char* data, size_t datalen, size_t offset, size_t totalLength) -> bool{
    if(gotError){
    return false;
    }
    buffer.append(data, datalen);

    // 循环分割换行,取出完整JSON chunk
    size_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::istringstream chunkStream(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);
    return true;
    }

    // 提取增量分片内容
    if(chunkJson.isMember("message") && chunkJson["message"].isMember("content")){
    std::string delta = chunkJson["message"]["content"].asString();
    fullData += delta;
    callback(delta, false);
    }
    }
    return true;
    };

    // 发送http请求
    auto respResult = 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);
    }

    return fullData;
    }

    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";

    auto provider = 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", "你是谁?"});

    // 分片回调lambda
    auto writeChunk = [&](const std::string& chunk, bool last) -> 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们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

    艾莉丝努力练剑

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


    👀
    【关注】 跟随我一起深耕技术领域,见证每一次成长。

    ❤️
    【点赞】 让优质内容被更多人看见,让知识传递更有力量。


    【收藏】 把核心知识点存好,在需要时随时查、随时用。

    💬
    【评论】 分享你的经验或疑问,评论区一起交流避坑!

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

    “今日练剑达成!”

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

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

    往期回顾:

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

    🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡

    ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

    在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【AI大模型接入SDK】Ollama API 流式增量响应
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!