
🔥小叶-duck:个人主页
❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux系统从入门到实践》《Linux网络从入门到实践》
✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游
目录
[一、Apifox 接口测试实操流程](#一、Apifox 接口测试实操流程)
[1.1 环境管理](#1.1 环境管理)
[1.2 接口基础信息填写](#1.2 接口基础信息填写)
[1.3 请求 Body(JSON)配置](#1.3 请求 Body(JSON)配置)
[1.4 请求头配置](#1.4 请求头配置)
[1.5 发送请求,查看响应结果](#1.5 发送请求,查看响应结果)
[二、OllamaLLMProvider 类设计与初始化实现](#二、OllamaLLMProvider 类设计与初始化实现)
[2.1 类设计思路](#2.1 类设计思路)
[2.1.1 继承关系](#2.1.1 继承关系)
[2.1.2 核心成员变量](#2.1.2 核心成员变量)
[2.1.3 设计原则](#2.1.3 设计原则)
[2.2 头文件定义(OllamaLLMProvider.h)](#2.2 头文件定义(OllamaLLMProvider.h))
[2.3 初始化函数实现(OllamaLLMProvider.cpp)](#2.3 初始化函数实现(OllamaLLMProvider.cpp))
[2.3.1 初始化执行步骤](#2.3.1 初始化执行步骤)
[2.3.2 实现代码](#2.3.2 实现代码)
[3.1 校验 Provider 可用状态](#3.1 校验 Provider 可用状态)
[3.2 构造并获取请求参数](#3.2 构造并获取请求参数)
[3.2.1 设置默认值,读取本次请求动态参数](#3.2.1 设置默认值,读取本次请求动态参数)
[3.2.2 组装 options 配置对象](#3.2.2 组装 options 配置对象)
[3.2.3 组装历史消息 messages 数组](#3.2.3 组装历史消息 messages 数组)
[3.3 构造请求体](#3.3 构造请求体)
[3.4 JSON 序列化](#3.4 JSON 序列化)
[3.5 创建 httplib 客户端 + 设置超时](#3.5 创建 httplib 客户端 + 设置超时)
[3.6 发送 POST 请求](#3.6 发送 POST 请求)
[3.7 解析响应体](#3.7 解析响应体)
[3.8 提取响应内容](#3.8 提取响应内容)
[四、全量接口 sendMessage 完整代码](#四、全量接口 sendMessage 完整代码)
[五、GTest 全量响应代码测试(TestLLM.cpp)](#五、GTest 全量响应代码测试(TestLLM.cpp))
[5.1 测试用例编写](#5.1 测试用例编写)
[5.2 CMake 构建配置](#5.2 CMake 构建配置)
前言
Ollama 为本地部署大模型提供了轻量化的服务方案,我们可以在本地主机或者内网服务器上跑起模型,通过 HTTP 接口直接调用,不需要依赖外部公网服务。本章我们就围绕 Ollama 完成 Provider 的完整落地。
首先我们会在 Apifox 中完成 Ollama 接口的调试,直观熟悉它的请求报文、返回格式,区分它和 OpenAI 系列 API 的差异;随后完成
OllamaLLMProvider的类结构设计、初始化逻辑编写;接着实现非流式全量响应接口,一步步拆解每一处参数组装、网络请求、JSON 解析的逻辑;最后基于 GTest 编写测试用例,搭配 CMake 完成编译验证。
一、Apifox 接口测试实操流程
在实现 OllamaProvider 模块之前,我们先使用 Apifox 第三方工具对 Ollama 完成接口调试,通过可视化工具我们就能先直观验证 Ollama 的请求、返回报文结构,把非流式、流式两种返回格式摸清楚,这会为后续 C++ 的 JSON 解析、SSE 事件处理提供直接的参考依据。
1.1 环境管理
在 Apifox 中先配置好环境信息,方便后续复用服务地址,适配本地、局域网多套环境切换。
- 全局前置 Base URL:
http://127.0.0.1:11434 - 环境说明: Ollama 默认本地服务地址为
127.0.0.1:11434;如果需要局域网其他设备访问,需要提前把 Ollama 的OLLAMA_HOST设置为0.0.0.0,再把地址替换为本机局域网 IP。 - **环境隔离:**可以区分本地开发、内网服务器两套环境,互不干扰。
注意: Ollama 原生
/api/chat接口不需要 API Key 鉴权 ,和前面 Gemini、DeepSeek、ChatGPT 云端 API 不一样,请求头不需要携带 Authorization 密钥。
1.2 接口基础信息填写
Ollama 原生对话接口为 /api/chat,它的报文结构和我们之前对接的 Chat Completions 系列接口有明显差异,字段命名、响应结构都属于 Ollama 自有规范,这也是我们实现 OllamaProvider 需要重点适配的地方。
- 请求方式:
POST - 完整地址:
http://127.0.0.1:11434/api/chat - 拆分理解:
- base_url:
http://127.0.0.1:11434 - 接口路径:
/api/chat
- base_url:

1.3 请求 Body(JSON)配置
Body 选择 JSON 类型 ,核心参数说明如下:
bash
{
"model": "deepseek-r1:7b",
"options": {
"temperature": 0.7,
"num_ctx": 2048
},
"messages": [
{
"role": "user",
"content": "你好,请简单介绍自己"
}
],
"stream": false
}
model:指定本地已经拉取好的模型名称,必须和ollama list列出的模型名保持一致options:模型推理参数对象,用来控制本次生成的推理配置temperature:生成随机性,值越高创造性越强num_ctx:上下文窗口长度,控制单次对话能容纳的 token 上限
messages:对话消息数组,role/content 结构和 OpenAI 体系保持一致,支持 user /assistant/systemstream:流式开关stream: false:关闭流式,一次性返回完整全量响应stream: true:开启 SSE 流式增量返回,分片持续推送内容

1.4 请求头配置
在 Headers 标签只需要添加一项:
Content-Type: application/jsonOllama 原生接口无 token 鉴权,不需要额外配置 Authorization 请求头获取 apikey 的情况了。

1.5 发送请求,查看响应结果
① 全量响应(stream=false)
响应为单条完整 JSON,顶层没有 choices 数组,直接使用 message 字段存放回答内容,同时附带 done、done_reason、推理耗时、token 统计等 Ollama 专属字段,这是我们后续全量响应解析代码的参考依据。
bash
{
"model": "deepseek-r1:7b",
"created_at": "2026-11-03T14:25:23.74674712Z",
"message": {
"role": "assistant",
"content": "\n\n您好!我是由中国的深度求索(DeepSeek)公司开发的智能助手DeepSeek-R1。如您有任何问题,我会尽我所能为您提供帮助。"
},
"done": true,
"done_reason": "stop",
"total_duration": 1714442600,
"load_duration": 3092700,
"prompt_eval_count": 8,
"prompt_eval_cached_count": 2,
"prompt_eval_duration": 228479000,
"eval_count": 75,
"eval_duration": 1480458000
}
关键字段说明:
done: true:代表本次对话全部结束message.content:模型返回的完整回答文本eval_count / prompt_eval_count:统计本次推理消耗的 token 数量

② 流式响应(stream=true)
开启流式之后,Ollama 接口同样使用 stream=true 开启增量推送,但它和我们前面对接的 DeepSeek、OpenAI 远端服务的 SSE 报文格式有明显区别:
远端大模型标准 SSE 会使用 data: 包裹 JSON 块,块与块之间以 \n\n 分隔 ;
而Ollama 内部已经做了一层封装处理 ,直接返回一行一条独立 JSON,分片之间只用单个换行符 \n 分隔 ,**不再使用 \n\n 作为分割标记。**这是对接 Ollama 流式接口最核心的差异点,也意味着我们之前写的 SSE 解析逻辑不能直接复用,解析器需要单独适配。
每一条独立 JSON 都携带增量内容,没有 delta 字段 ,新增文本直接放在 message.content 字段中; 通过 done 字段判断会话是否结束:
done: false:代表当前只是中间分片,模型还有后续增量内容持续返回;done: true:代表本轮流式输出全部完成,这条分片里还会附带耗时、token 计数等统计附属字段,作为结束标记。
bash
{
"model": "deepseek-r1:7b",
"created_at": "2026-11-03T14:16:55.7797762Z",
"message": {
"role": "assistant",
"content": "我会"
},
"done": false
}
末尾结束分片示例:
bash
{
"model": "deepseek-r1:7b",
"created_at": "2026-11-03T14:16:55.91648522Z",
"message": {
"role": "assistant",
"content": ""
},
"done": true,
"done_reason": "stop",
"total_duration": 187439500,
"load_duration": 2571800,
"prompt_eval_count": 7,
"prompt_eval_cached_count": 0,
"prompt_eval_duration": 376366000,
"eval_count": 75,
"eval_duration": 148828000
}
重点对比: DeepSeek、Gemini 的 Chat Completions 流式是
delta增量字段;Ollama 原生接口直接复用message.content,并且依靠done布尔值判断结束,这是 OllamaProvider 代码里最核心的差异化适配点。

二、OllamaLLMProvider 类设计与初始化实现
2.1 类设计思路
2.1.1 继承关系
OllamaLLMProvider 继承自抽象基类 LLMProvider,遵守我们 SDK 统一定义的大模型接入抽象接口契约。依托多态,它实现 Ollama 本地模型服务专属的接入逻辑,上层业务不需要关心底层是 DeepSeek 远端、OpenAI 还是 Ollama 本地模型,统一使用基类提供的接口调用。
2.1.2 核心成员变量
_modelName:Ollama 内的模型标识名称,和ollama pull拉取的模型名称保持一致,例如deepseek-r1:1.5b_modelDesc:模型描述文本,用于上层界面、日志展示模型信息_baseUrl:Ollama 服务的 HTTP 根地址,默认本地一般为http://127.0.0.1:11434_isAvailable:布尔标记,代表当前 Provider 是否初始化成功、可用
2.1.3 设计原则
配置与代码解耦:模型名称、服务地址、描述信息全部通过外部配置传入。切换不同 Ollama 模型、切换 Ollama 部署的机器地址时,只修改配置内容,不需要改动业务代码。
2.2 头文件定义(OllamaLLMProvider.h)
cpp
#ifndef OLLAMA_LLM_PROVIDER_H
#define OLLAMA_LLM_PROVIDER_H
#include "LLMProvider.h"
namespace ai_chat_sdk
{
// Ollamad第三方工具提供者:实现Ollama本地接入模型的调用接口
class OllamaLLMProvider : public LLMProvider
{
public:
// 初始化模型:根据配置初始化模型
bool initModel(const std::unordered_map<std::string, std::string> &modelConfig) override;
// 判断模型是否有效
bool IsAvailable() override;
// 获取模型描述
std::string getModelDesc() override;
// 发送消息:全量调用,返回完整响应
// requestParams:针对Ollama本次请求专属参数,包含model、options(包括temperature、num_ctx等)
std::optional<std::string> sendMessage(const std::vector<Message> &messages,
const std::unordered_map<std::string, std::string> &requestParams) override;
// 发送消息:增量调用,实时返回响应
// requestParams:针对Ollama模型本次请求专属参数,包含model、options(包括temperature、num_ctx等)
std::optional<std::string> sendMessageStream(const std::vector<Message> &messages,
const std::unordered_map<std::string, std::string> &requestParams,
std::function<void(const std::string &response, bool isEnd)> callback) override;
private:
// 由于ollama只是作为第三方工具,这个工具的作用是可以本地接入不同的大模型,所以我们需要额外添加模型名称字段来指定接入具体的模型
std::string _modelName; // 模型名称,如deepseek-r1:7b
std::string _modelDesc; // 模型描述,如deepseek-r1:7b是一个7b参数的模型
};
} // namespace ai_chat_sdk
#endif
代码说明:
- 所有对外接口都使用
override关键字,强制对齐基类LLMProvider的纯虚函数签名,保证多态正常工作; - 区分两类参数:
modelConfig是初始化阶段固定配置 (baseUrl、模型名、描述);requestParams是每次发请求时动态传入的参数,比如 temperature、上下文窗口大小,每次对话可以独立修改; - 私有成员仅存放 Provider 实例固定不变的属性,和每次请求的动态参数做隔离,和我们前面整套 SDK 的设计规范保持统一。
2.3 初始化函数实现(OllamaLLMProvider.cpp)
2.3.1 初始化执行步骤
- 在传入的
modelConfig中查找base_url,如果不存在,打印错误日志,直接返回初始化失败; - 读取
base_url的值,赋值给成员变量_baseUrl; - 在
modelConfig中查找modelName,缺失则打印错误日志,返回失败; - 读取
modelName的值,赋值给成员变量_modelName; - 在
modelConfig中查找modelDesc,缺失则打印错误日志,返回失败; - 读取
modelDesc的值,赋值给成员变量_modelDesc; - 全部参数校验读取成功后,将
_isAvailable置为 true,返回 true,标记初始化完成。
2.3.2 实现代码
cpp
#include "../include/OllamaLLMProvider.h"
#include "../include/util/myLog.h"
namespace ai_chat_sdk
{
bool OllamaLLMProvider::initModel(const std::unordered_map<std::string, std::string> &modelConfig)
{
// 从配置提取Ollama服务地址 base_url
auto it = modelConfig.find("base_url");
if (it == modelConfig.end())
{
ERR("OllamaLLMProvider: _baseUrl not found in modelConfig");
return false;
}
else
{
_baseUrl = it->second;
}
// 提取模型名称
it = modelConfig.find("modelName");
if (it == modelConfig.end())
{
ERR("OllamaLLMProvider: _modelName not found in modelConfig");
return false;
}
else
{
_modelName = it->second;
}
// 提取模型描述
it = modelConfig.find("modelDesc");
if (it == modelConfig.end())
{
ERR("OllamaLLMProvider: _modelDesc not found in modelConfig");
return false;
}
else
{
_modelDesc = it->second;
}
_isAvailable = true;
return true;
}
// 判断模型是否可用
bool OllamaLLMProvider::IsAvailable()
{
return _isAvailable;
}
// 获取模型描述信息
std::string OllamaLLMProvider::getModelDesc()
{
return _modelDesc;
}
}
代码说明:
- 使用
unordered_map读取配置,每一个必填字段都做存在性校验,避免空配置导致后续网络请求异常; - 借助我们前面封装好的日志组件
ERR输出错误信息,方便定位配置缺失问题; IsAvailable、getModelDesc为简单的成员读取接口,作为发送请求的条件判断以及给上层框架做状态查询、展示使用;- 初始化只负责固化实例的基础信息,不会填充对话请求参数 ,温度、最大 token 等参数依旧放在每次调用
sendMessage/sendMessageStream时传入,和整套 SDK 设计保持一致。
三、全量响应实现流程
说明: 本接口为非流式完整应答接口,一次性拿到模型全部返回结果,和前面 DeepSeek、ChatGPT 的接口架构对齐,但请求报文、返回 JSON 字段遵循 Ollama 接口规范。
还有一个需要注意的点:Ollama 不同于前面的云端模型发送请求,请求参数 stream 默认是流式开启的 ,所以我们设计非流式响应接口时,不能像之前的模型设计一样直接忽略,必须手动设置为 false。
3.1 校验 Provider 可用状态
cpp
if (!IsAvailable())
{
ERR("OllamaLLMProvider sendMessage not available");
return std::nullopt;
}
代码分析: 在执行网络请求之前,先判断当前实例是否初始化成功。如果initModel没有正常执行、配置缺失,直接返回空,避免发起无效 HTTP 请求。这是所有 Provider 统一的前置校验逻辑。
3.2 构造并获取请求参数
3.2.1 设置默认值,读取本次请求动态参数
cpp
double temperature = 0.7;
int max_tokens = 2048;
auto it = requestParams.find("temperature");
if (it != requestParams.end())
{
temperature = std::stod(it->second);
}
it = requestParams.find("max_tokens");
if (requestParams.find("max_tokens") != requestParams.end())
{
max_tokens = std::stoi(it->second);
}
代码分析: temperature、max_tokens属于单次请求可变参数 ,放在requestParams,而不是初始化配置里。
- 外部传入则使用传入值;
- 不传就使用内置默认值;
- 注意: Ollama 接口里,上下文窗口大小参数名称为
num_ctx,我们把 max_tokens 映射给num_ctx。
3.2.2 组装 options 配置对象
cpp
Json::Value options;
options["temperature"] = temperature;
options["num_ctx"] = max_tokens;
代码分析: Ollama 的参数放在options子对象中,这是和 DeepSeek、OpenAI 接口最大的区别之一。温度、上下文长度等参数都打包进 options,而不是直接平铺在请求根节点。
3.2.3 组装历史消息 messages 数组
cpp
Json::Value messageArray(Json::arrayValue);
for (const auto &message : messages)
{
Json::Value messageObject(Json::objectValue);
messageObject["role"] = message._role;
messageObject["content"] = message._content;
messageArray.append(messageObject);
}
代码分析: 遍历上层传入的messages消息数组,把我们 SDK 内部定义的Message结构,转换成 Ollama 接口要求的role + contentJSON 数组。 这里消息角色体系和 OpenAI 兼容,所以转换逻辑和 DeepSeek 大体一致。
3.3 构造请求体
cpp
Json::Value requestBody;
requestBody["model"] = _modelName;
requestBody["options"] = options;
requestBody["messages"] = messageArray;
requestBody["stream"] = false;
代码分析:
model:使用初始化时保存的_modelName,也就是 Ollama 本地模型名;options:上面组装好的温度、上下文参数;messages:对话历史;stream=false:关键标记,关闭流式,代表本次是全量一次性返回。Ollama 默认 stream 就是开启状态,不设置默认就是走分片回调逻辑,全量响应必须手动设置为 false。
对比:流式接口这里会设置
stream=true,同时走分片回调逻辑。
3.4 JSON 序列化
cpp
Json::StreamWriterBuilder writerbuilder;
std::string requestBodyStr = Json::writeString(writerbuilder, requestBody);
INFO("OllamaLLMProvider sendMessage requestBodyStr: {}", requestBodyStr);
**代码分析:**借助 JsonCpp 将 JSON 对象序列成字符串,同时打印请求报文日志,方便调试,查看发给 Ollama 服务的内容是否符合预期。
3.5 创建 httplib 客户端 + 设置超时
cpp
httplib::Client client(_baseUrl);
client.set_read_timeout(60, 0);
client.set_connection_timeout(30, 0);
httplib::Headers headers = {
{"Content-Type", "application/json"}
};
代码分析:
- 客户端地址来自初始化保存的
_baseUrl,一般本地默认是http://127.0.0.1:11434; - 设置连接超时、读取超时,防止请求卡死;
- 请求头固定为
application/json,Ollama 接口接收 JSON 报文。
3.6 发送 POST 请求
cpp
auto res = client.Post("/api/chat", headers, requestBodyStr, "application/json");
if (!res)
{
ERR("OllamaLLMProvider sendMessage failed: {}", httplib::to_string(res.error()));
return std::nullopt;
}
if (res->status != 200)
{
ERR("OllamaLLMProvider sendMessage failed, status code:{}, body:{}", res->status, res->body);
return std::nullopt;
}
代码分析:
- 请求路径:
/api/chat,是 Ollama 对话接口地址; - **第一层判断:**网络层面失败(连接失败、超时等),打印 httplib 内部错误;
- **第二层判断:**HTTP 状态码非 200,打印返回体,方便排查 Ollama 服务报错,比如模型不存在、参数非法。
3.7 解析响应体
cpp
Json::Value responseJson;
std::stringstream responseStream(res->body);
std::string errorMsg;
if (!Json::parseFromStream(readerbuilder, responseStream, &responseJson, &errorMsg))
{
ERR("OllamaLLMProvider sendMessage: parse responseJson failed, errorMsg:{}", errorMsg);
return std::nullopt;
}
**代码分析:**将服务返回的完整字符串反序列化为 Json 对象;如果 JSON 格式损坏,直接返回失败并打印解析错误。
3.8 提取响应内容
cpp
if (responseJson.isMember("message") && responseJson["message"].isObject() &&
responseJson["message"]["content"].isString())
{
std::string content = responseJson["message"]["content"].asString();
INFO("OllamaLLMProvider sendMessage success, content: {}", content);
return content;
}
else
{
ERR("OllamaLLMProvider sendMessage failed: responseJson not contain message.content");
return std::nullopt;
}
四、全量接口 sendMessage 完整代码
cpp
// 发送消息:全量调用,返回完整响应
// requestParams:针对Ollama模型本次请求专属参数,包含model、options(包括temperature、num_ctx等)
std::optional<std::string> OllamaLLMProvider::sendMessage(const std::vector<Message> &messages,
const std::unordered_map<std::string, std::string> &requestParams)
{
// 1. 检查模型是否有效
if (!IsAvailable())
{
ERR("OllamaLLMProvider sendMessage model not available");
return std::nullopt;
}
// 2、构造并获取请求参数
// 2.1 从requestParams中提取temperature和max_tokens参数,若未指定则使用默认值
double temperature = 0.7;
int max_tokens = 2048;
auto it = requestParams.find("temperature");
if (it != requestParams.end())
{
temperature = std::stod(it->second);
}
it = requestParams.find("max_tokens");
if (it != requestParams.end())
{
max_tokens = std::stoi(it->second);
}
// 2.2 构造Ollama模型请求参数
Json::Value options;
options["temperature"] = temperature;
options["num_ctx"] = max_tokens;
// 2.3 构造并获取历史消息:获取请求体中的 messages 这个Json数组
Json::Value messageArray(Json::arrayValue);
for (const auto &message : messages)
{
Json::Value messageObject(Json::objectValue);
messageObject["role"] = message._role;
messageObject["content"] = message._content;
messageArray.append(messageObject);
}
// 3、构造请求体
// 和历史消息构造一样,请求体的字段名称必须与ollama的接口文档中要求的一致
Json::Value requestBody;
requestBody["model"] = _modelName;
requestBody["options"] = options;
requestBody["messages"] = messageArray;
requestBody["stream"] = false;
// 4、对requestBody进行序列化
Json::StreamWriterBuilder writerbuilder;
std::string requestBodyStr = Json::writeString(writerbuilder, requestBody);
INFO("OllamaLLMProvider sendMessage requestBodyStr: {}", requestBodyStr);
// 5、使用cpp-httplib构建HTTP客户端
httplib::Client client(_baseUrl);
// 5.1设置超时时间
client.set_read_timeout(60, 0); // 读取超时时间60秒
client.set_connection_timeout(30, 0); // 连接超时时间30秒
// 5.2设置请求头
httplib::Headers headers = {
{"Content-Type", "application/json"}};
// 6、向指定服务器发送POST请求
auto res = client.Post("/api/chat", headers, requestBodyStr, "application/json");
if (!res)
{
ERR("OllamaLLMProvider sendMessage failed: {}", httplib::to_string(res.error()));
return std::nullopt;
}
if (res->status != 200)
{
ERR("OllamaLLMProvider sendMessage failed: status code {}, body: {}", res->status, res->body);
return std::nullopt;
}
// 7、解析响应体 -- 反序列化
Json::Value responseJson;
Json::CharReaderBuilder readerbuilder;
std::stringstream responceStream(res->body);
std::string errorMsg;
if (!Json::parseFromStream(readerbuilder, responceStream, &responseJson, &errorMsg))
{
ERR("OllamaLLMProvider sendMessage failed: parse responseJson failed, errorMsg: {}", errorMsg);
return std::nullopt;
}
// 8、从responceBody中提取响应内容
if (responseJson.isMember("message") && responseJson["message"].isObject() &&
responseJson["message"].isMember("content") && responseJson["message"]["content"].isString())
{
std::string content = responseJson["message"]["content"].asString();
INFO("OllamaLLMProvider sendMessage success, content: {}", content);
return content;
}
else
{
ERR("OllamaLLMProvider sendMessage failed: responseJson not contain message.content");
return std::nullopt;
}
}
五、GTest 全量响应代码测试(TestLLM.cpp)
5.1 测试用例编写
cpp
#include "../SDK/include/ChatGPTProvider.h"
#include "../SDK/include/DeepSeekProvider.h"
#include "../SDK/include/GeminiProvider.h"
#include "../SDK/include/LLMManager.h"
#include "../SDK/include/OllamaLLMProvider.h"
#include <gtest/gtest.h>
#include <spdlog/common.h>
using namespace ai_chat_sdk;
TEST(OllamaLLMProviderTest, sendMessage)
{
// 1、创建OllamaLLMProvider对象
std::shared_ptr<LLMProvider> ollamaProvider = std::make_shared<OllamaLLMProvider>();
// 1.1 检查OllamaLLMProvider是否初始化成功
ASSERT_TRUE(ollamaProvider != nullptr);
// 2、构建模型配置
std::unordered_map<std::string, std::string> modelConfig;
modelConfig["modelName"] = "deepseek-r1:7b";
// modelConfig["base_url"] = "http://localhost:11434";
modelConfig["base_url"] = "http://192.168.159.1:11434";
modelConfig["modelDesc"] = "本地部署deepseek-r1:7b模型, 是一个参数规模为7000万的预训练语言模型, 能够提供强大的文本生成能力和多任务处理能力";
// 3、初始化OllamaLLMProvider
ollamaProvider->initModel(modelConfig);
// 3.1 检查模型是否有效
ASSERT_TRUE(ollamaProvider->IsAvailable());
// 4、构建请求参数配置(不写则使用默认值)
std::unordered_map<std::string, std::string> requestConfig;
requestConfig["temperature"] = "0.7";
requestConfig["max_tokens"] = "2048";
// 5、构建消息列表
std::vector<Message> messages;
messages.push_back({"user", "你好,请介绍自己"});
// 6、调用sendMessage发送消息
auto response = ollamaProvider->sendMessage(messages, requestConfig);
// 6.1 检查响应是否为空
ASSERT_TRUE(response.has_value());
INFO("response: {}", response.value());
}
int main(int argc, char **argv)
{
// 初始化Google Test框架
testing::InitGoogleTest(&argc, argv);
// 初始化日志系统
Logger::logger_init("testLLM", "stdout", spdlog::level::info);
// 运行所有测试用例
return RUN_ALL_TESTS();
}
5.2 CMake 构建配置
bash
# 设置Cmake最低版本要求
cmake_minimum_required(VERSION 3.10)
# 项目名称
project(testLLM)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) # 要求必须使用C++17标准
# 设置构建类型为Debug
set(CMAKE_BUILD_TYPE Debug)
# 添加可执行文件
add_executable(testLLM TestLLM.cpp
../SDK/src/DeepSeekProvider.cpp
../SDK/src/util/MyLog.cpp
../SDK/src/ChatGPTProvider.cpp
../SDK/src/GeminiProvider.cpp
../SDK/src/OllamaLLMProvider.cpp)
#设置生成可执行文件的输出目录
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
# ${CMAKE_BINARY_DIR} 表示生成可执行文件的所在目录,即cmake指令执行所在目录
# 添加头文件搜索路径
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../SDK/include)
# ${CMAKE_CURRENT_SOURCE_DIR} 表示当前CMakeLists.txt所在目录
# ${CMAKE_SOURCE_DIR} 表示项目根目录,即所有CMakeLists.txt所在路径中最外层目录
# 查找第三方库:确保在使用前已经通过find_package命令找到并配置了这些库
find_package(GTest REQUIRED)
find_package(fmt REQUIRED)
find_package(spdlog REQUIRED)
find_package(jsoncpp REQUIRED)
find_package(OpenSSL REQUIRED)
# 链接第三方库
target_link_libraries(testLLM gtest fmt spdlog jsoncpp pthread OpenSSL::SSL OpenSSL::Crypto)
运行结果展示:

结束语
本章我们完成了 Ollama 本地模型 Provider 从接口调试、类设计、初始化,到全量应答接口编码与 GTest 测试的全部流程。 我们可以看到,依托之前设计好的
LLMProvider抽象契约,只需要针对 Ollama 独有的报文结构、参数组织规则进行适配,就能快速新增一套本地模型接入能力,这也印证了策略模式和多态抽象给 SDK 带来的良好扩展性。Ollama 的全量接口采用一次性返回结果的方式,实现逻辑相对简单;而 Ollama 的流式响应并不遵循标准 SSE 协议,采用换行分隔 JSON 块的形式推送数据,还需要我们自己处理分片半包缓冲。下一章,我们就基于当前的代码基础,继续实现
sendMessageStream流式接口,完成 Ollama 流式对话能力的开发。