🎬 个人主页:艾莉丝努力练剑
❄专栏传送门:《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 完整业务逻辑流程
- 连接超时:30s
- 读取超时:300s;流式推理生成耗时较长,读取超时必须放大
- buffer:接收 TCP 字节流缓冲区;TCP 分片会把多条 JSON 块粘包,必须本地缓存缓冲区,按换行分割
- gotError:请求异常标记
- 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 测试逻辑
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)
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 全量响应实现
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡
૮₍ ˶ ˊ ᴥ ˋ˶₎ა

网硕互联帮助中心







评论前必须登录!
注册