一:数据结构的设计
我们需要定义一个文件,这个文件可以看作一个工具包,在各个后续文件中都可以看到!
文件名字:commod.h
1. 定义消息结构
消息结构就是我们与ai对话的内容,类似于这样

在背后,这个消息需要定义许多信息,例如:
1.消息ID
2.角色,如user
3.消息内容
4.消息发送的时间戳
cpp
//消息结构
struct message
{
std::string _messageId; //消息id
std::string _role //角色
std::string _content //消息内容
std::time_t _timeStamp //消息发送时间戳
message(const std::string& role,const std::string& content)
:_role(role),_content(role)
{}
};
2.两种方式接入模型
公共部分:
cpp
//模型的公共配置信息
struct Config
{
std::string _modelName; //模型名称
int _tempratrue = 0.7; //采样温度
int _maxTokens = 2048; //最大生成的字数的数量
};
模型名称:在对话前,可以选择不同的模型
采样温度: 介于 0 和 2 之间。更高的值,如 0.8,会使输出更随机,而更低的值,如 0.2,会使其更加集中和确定。
使用API接入云端模型
cpp
//通过API接入云端模型
struct APIconfig : public Config
{
std::string _apiKey; //接入云端的所需要密钥
};
密钥:API
使用ollama接入我们自己的本地模型
.。。
3.模型描述信息
cpp
//模型描述信息
struct ModelInfo
{
std::string _modelName; //模型名称
std::string _modelDesc; //模型描述
std::string _provider; //模型提供的官方
std::string _endPoint; //模型base url 模型入口地点
bool _isAvailable = falsel; //模型是否可用
ModelInfo(const std::string& modelName = "",const std::string& modelDesc= "",const std::string& provider= "",const std::string& endPoint= ""):
_modelName(modelName),
_modelDesc(modelDesc),
_provider(provider),
_endPoint(endPoint)
{}
};
_endPoint:模型服务的基础访问地址。

4.会话信息
cpp
//会话信息
struct Session
{
std::string _sessionId; //会话id
std::string _modelName; //会话所正在使用的模型名称
std::vector<message> _message; //会话消息列表
std::time_t _createAt; //会议创建的时间戳
std::time_t _updateAt; //会话最后更新的时间戳
Session(const std::string& modelName = "")
:_modelName(modelName)
{}
};
二:日志的设计与封装
为什么对日志库进行封装
在 C++ 中,确实可以通过 std::cout 将信息打印到控制台,但在实际工程中,这种方式远远不足。大型项目通常都会封装日志库,因为它在可维护性、可控性、性能和可扩展性上都明显优于直接输出。
封装日志库的主要优势如下:
- 日志级别管理
成熟的日志库一般支持多种日志级别(TRACE、DEBUG、INFO、WARN、ERROR、FATAL 等)。
开发者可以根据运行环境灵活调整输出内容:
开发环境开启 DEBUG,打印详细调试信息
生产环境只保留 WARN、ERROR 等关键日志
std::cout 没有内置日志级别,所有输出混在一起,无法按等级过滤。
- 富的日志格式
日志库通常支持自动添加:
cpp
时间戳
日志等级
线程 ID
文件名、行号
函数名
例如:
2025-09-24 10:00:00\] \[INFO\] \[main.cpp:123\] This is an info message
这些信息可以帮助快速定位问题。
而 std::cout 不提供格式化功能,需要开发者手动拼接,不仅繁琐还容易出错。
- 灵活的日志存储管理
日志库可以输出到多种目标:
1.控制台
2.文件
3.远程服务器
4.统一日志系统
5.并支持轮转、压缩、归档等功能,例如:
6.每天自动生成新的日志文件
7.文件超过指定大小后自动压缩
而 std::cout 只能打印到控制台,不具备日志管理能力。
- 线程安全
在多线程程序中,多个线程可能同时写入日志。
日志库通常提供内部锁或无锁队列来保证输出不会互相干扰。
std::cout 在多线程环境下很容易出现输出内容交错,开发者需要额外处理线程安全问题
- 性能优势
很多日志库都做了性能优化,例如:
异步写日志(减少主线程阻塞)
内部缓冲
批量写入文件
std::cout 属于同步输出,每次写都会阻塞当前线程,在高并发场景下性能表现较差。
日志具体代码
cpp
//logger.h
#pragma once
#include <mutex>
#include <memory>
#include <spdlog/logger.h>
#include <spdlog/spdlog.h>
namespace zhao
{
class Logger
{
public:
static void initLogger(const std::string& LoggerName, const std::string& LoggerFile,spdlog::level::level_enum logLevel = spdlog::level::info);
static std::shared_ptr<spdlog::logger> getLogger();
private:
Logger();//构造函数
Logger(const Logger& logger) = delete;//拷贝构造
Logger& operator=(const Logger& logger) = delete;
private:
static std::shared_ptr<spdlog::logger> _logger ;
static std::mutex _mutex;
};
#define TRACE(formate,...) zhao::Logger::getLogger()->trace(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
#define DBG(formate,...) zhao::Logger::getLogger()->debug(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
#define INFO(formate,...) zhao::Logger::getLogger()->info(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
#define WARN(formate,...) zhao::Logger::getLogger()->warn(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
#define ERR(formate,...) zhao::Logger::getLogger()->error(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
#define CRIT(formate,...) zhao::Logger::getLogger()->critical(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
}
cpp
//logger.cc
#include"../include/util/myLog.h"
#include<memory>
#include<spdlog/spdlog.h>
#include<spdlog/sinks/basic_file_sink.h>
#include<spdlog/async.h>
#include <spdlog/sinks/stdout_color_sinks.h>
namespace zhao
{
std::shared_ptr<spdlog::logger> Logger::_logger = nullptr;
std::mutex Logger::_mutex;
Logger::Logger()
{}
void Logger::initLogger(const std::string& LoggerName, const std::string& LoggerFile,spdlog::level::level_enum logLevel = spdlog::level::info)
{
if(_logger == nullptr)
{
std::lock_guard<std::mutex> lock(_mutex);
if(_logger == nullptr)
{
//设置全局自动刷新级别,当日志的级别>=logLevel时候,日志会被立即刷新到文件
spdlog::flush_on(logLevel);
//启动异步日志,将日志信息存放到队列中,有后台线程负责写入
// 参数1:队列大小 参数2:后台线程数量
spdlog::init_thread_pool(32768,1);
if(LoggerFile == "stdout")
{
//创建一个带颜色的输出到控制台
_logger = spdlog::stdout_color_mt(LoggerName);
}
else
{
//创建一个文件输出的日志器
_logger = spdlog::basic_logger_mt<spdlog::async_logger>(LoggerName,LoggerFile);
}
}
_logger->set_pattern("[%H:%M:%S][%n][%-71]%v");
_logger->set_level(logLevel);
}
}
std::shared_ptr<spdlog::logger> Logger::getLogger()
{
return _logger;
}
}
LLMProvider
在发消息之前,需要做一些准备工作。
后续会借助API的⽅式接⼊DeepSeek、ChatGPT、Gemini等⼤模型,每个⼤模型将来都需要:
a. 初始化
b. 检测模型是否有效
c. 发送消息给模型
d. 获取模型名称
e. 获取模型描述
f. 保存模型的有效状态、APIKey、模型描述。
操作基本都是相同的,只是实现细节上稍微不同,因此借助策略模式将接⼊模块架构设计如下:

定义抽象类
-
初始化模型
-
检测模型是否有效
-
发送消息给模型
-
获取模型名称
-
获取模型描述
代码:
cpp
#pragma once
#include"commond.h"
#include<map>
#include<string>
#include<vector>
#include<functional>
namespace ai_chat_model
{
class LLMProvider
{
public:
//1. 初始化模型
virtual void initModel(const std::map<std::string,std::string>& modelConfig) = 0;
//2. 检测模型是否有效
virtual bool isAvailable() const =0;
//3. 发送消息给模型
//1-全量返回
virtual void sendMessage(const std::vector<Message>& message,std::map<std::string,
std::string>& requestParam);
//2-流式返回
virtual void sendMessagestream(const std::vector<Message>& message,
std::map<std::string, std::string>& requestParam,
std::function<void(const std::string&, bool)> callBack);
//4. 获取模型名称
virtual std::string getModelName() const = 0;
//5. 获取模型描述
virtual std::string getModelDesc() const = 0;
protected:
bool _isAvailable = false; //模型是否有效
std::string _apiKey;
std::string _endPoint; //url
};
}
deepseek请求具体实现
头文件:deepseekProvider.h
cpp
#pragma once
#include"LLmProvider.h"
namespace ai_chat_model
{
class DeepseekProvider : public LLMProvider
{
public:
//1. 初始化模型
virtual bool initModel(const std::map<std::string,std::string>& modelConfig);
//2. 检测模型是否有效
virtual bool isAvailable() const;
//3. 发送消息给模型
//1-全量返回
virtual void sendMessage(const std::vector<Message>& message,std::map<std::string,
std::string>& requestParam);
//2-流式返回
virtual void sendMessagestream(const std::vector<Message>& message,
std::map<std::string, std::string>& requestParam,
std::function<void(const std::string&, bool)> callBack);\
//4. 获取模型名称
virtual std::string getModelName() const;
//5. 获取模型描述
virtual std::string getModelDesc() const;
};
}
方法具体实现代码:
cpp
#include"../include/deepseekProvider.h"
#include"../include/util/myLog.h"
#include<map>
#include<jsoncpp/json/json.h>
#include<httplib.h>
namespace ai_chat_model
{
//1. 初始化模型
bool DeepseekProvider::initModel(const std::map<std::string,std::string>& modelConfig)
{
//判断是否有错误
auto it1 = modelConfig.find("api_key");
if(it1 == modelConfig.end())
{
ERR("apiKey not found!");
return false;
}
else
{
_apiKey = it1 ->second;
}
auto it2 = modelConfig.find("endPoint");
if(it2 == modelConfig.end())
{
ERR("apiKey not found!");
return false;
}
else
{
_endPoint = it2->second;
}
_isAvailable = true;
INFO("initModel sucess!");
return true;
}
//2. 检测模型是否有效
bool DeepseekProvider::isAvailable() const
{
return _isAvailable;
}
//3. 发送消息给模型
//1-全量返回
std::string DeepseekProvider::sendMessage(const std::vector<Message>& message,std::map<std::string,std::string>& requestParam)
{
INFO("Entering sendMessage");
//1.检测模型是否可用
if(_isAvailable == false)
{
ERR("deepseekModel is Not Available!");
return "";
}
//2.构造请求参数(先定义默认的)
double temperature = 0.7;
int max_tokens = 2048; //可修改
if(requestParam.find("temperature") !=requestParam.end())
{
temperature = std::stod(requestParam.find("temperature")->second);
}
if(requestParam.find("maxTokens") !=requestParam.end())
{
max_tokens = std::stod(requestParam.find("maxTokens")->second);
}
//构造历史消息??什么样的历史消息??max_taoken
Json::Value messageArray(Json::arrayValue);
for(const auto& messages : message)
{
Json::Value messageObject;
messageObject["role"] = messages._role;
messageObject["content"] = messages._content;
messageArray.append(messageObject);
}
//构造请求体request Json
Json::Value requestBody;
requestBody["model"] = getModelName(); //我们锁需要模型的名称
requestBody["messages"] = messageArray;
requestBody["temperature"] = temperature;
requestBody["max_tokens"] = max_tokens;
//序列化
Json::StreamWriterBuilder writeBulder;
std::string requestBodyStr = Json::writeString(writeBulder,requestBody);
writeBulder["indentation"] = "";//有缩进
//构造httplib客户端
INFO("Creating httplib client with endpoint: {}", _endPoint);
httplib::Client client(_endPoint.c_str());
client.set_connection_timeout(30,0);//设置连接超时。如果 30 秒还没连上服务器,就放弃
client.set_read_timeout(60,0);//设置读取超时。因为 AI 生成回复比较慢,这里给了 60 秒的耐心等待服务器返回数据。
//设置请求头
httplib::Headers headers = {
{"Authorization","Bearer "+ _apiKey},
{"Content-Type","application/json"}
};
//发送post请求
INFO("Sending POST request to /v1/chat/completions");
auto response = client.Post("/v1/chat/completions",headers,requestBodyStr,"application/json");
if(!response)
{
ERR("deepseek response return fasle");
return "";
}
INFO("deepseek response return success,status : {}",response->status);
INFO("deepseek response return success,body : {}",response->body);
//还需要检测相应是否成功
if(response->status !=200)
{
return "";
}
//解析结构体
Json::Value responseBody;
Json::CharReaderBuilder readerBuilder;
std::string parseError;
std::istringstream responseStream(response->body);
if(Json::parseFromStream(readerBuilder, responseStream, &responseBody, &parseError))
{
if(responseBody.isMember("choices") && responseBody["choices"].isArray() && !responseBody["choices"].empty())
{
auto choice = responseBody["choices"][0];
if(choice.isMember("message") && choice["message"].isMember("content"))
{
std::string replyContent = choice["message"]["content"].asString();
INFO("DeepSeekProvider response text: {}", replyContent);
return replyContent;
}
}
}
ERR("解析结构体失败");
return "";
}
//2-流式返回
std::string DeepseekProvider::sendMessagestream(const std::vector<Message>& message,
std::map<std::string, std::string>& requestParam,
std::function<void(const std::string&, bool)> callback)
{
//检测模型是否有效
if(_isAvailable == false)
{
ERR("Deepseek is not available!!!");
return "";
}
//构造请求参数
double temperature = 0.7;
int max_tokens = 2048;
if(requestParam.find("temperature")!=requestParam.end())
{
temperature = std::stod(requestParam.find("temperature")->second);
}
if(requestParam.find("max_tokens")!=requestParam.end())
{
max_tokens = std::stod(requestParam.find("max_tokens")->second);
}
//构造历史消息
Json::Value messageArray(Json::arrayValue);
for(auto messages : message)
{
Json::Value messageObject;
messageObject["role"] = messages._role;
messageObject["content"] = messages._content;
messageArray.append(messageObject);
}
//构造请求体
Json::Value requestBody;
requestBody["model"] = getModelName();
requestBody["messages"] = messageArray;
requestBody["max_tokens"] = max_tokens;
requestBody["temperature"] = temperature;
requestBody["stream"] = true;
//序列化
Json::StreamWriterBuilder writerBuilder;
writerBuilder["indentation"] = "";
std::string requestBodystr = Json::writeString(writerBuilder,requestBody);
INFO("DeepseekProvider sendMessagestream responsbody:{}",requestBodystr);
//使用cpp---httplib构造客户端
httplib::Client client(_endPoint.c_str());
client.set_connection_timeout(30,0);
client.set_read_timeout(60,0);
//设置请求头
httplib::Headers headers = {
{"Authorization", "Bearer " + _apiKey}, // [0] 身份验证
{"Content-Type", "application/json"}, // [1] 发送内容格式
{"Accept", "text/event-stream"} // [2] 期望接收格式
};
//设置流式处理变量
std::string buffer; //接受流式响应的数据块
bool gotError = false; //标记响应是否成功
std::string errorMsgl; //错误描述符
int statusCode = 0; //响应状态码
bool streamFinish = false; //标记流式响应是否完成
std::string fullResponse; //积累完整的流式响应
//创建请求对象
httplib::Request req;
req.method = "POST";
req.path = "/v1/chat/completions";
req.headers = headers;
req.body = requestBodystr;
//设置响应处理器
req.response_handler = [&](const httplib::Response& res)->bool{
if(res.status!=200)
{
gotError = true;
errorMsgl = "HTTP status code:" + std::to_string(res.status);
return false;
}
else
{
return true;
}
};
req.content_receiver = [&](const char* data, size_t len, size_t offset, size_t totalLenght)->bool{
if(gotError)
return false;
buffer.append(data,len);
INFO("DeepseekProvider sendMessagestream buffer:{}",buffer);
size_t pos = 0;
while((pos = buffer.find("\n\n")) != std::string::npos)
{
//截取当前找到的数据块
std::string chunk = buffer.substr(0,pos);
buffer.erase(0,pos+2);//还有删除"\n\n"
//解析有效数据
//处理空行和注释
if(chunk.empty()||chunk[0] == ':')
{
continue;
}
if(chunk.compare(0,6,"data: ") == 0)
{
std::string modelData = chunk.substr(6);//把data切掉
if(modelData == "[DONE]")
{
streamFinish = true;
return true;
}
// 反序列化
Json::Value modelDataJson;
Json::CharReaderBuilder reader;
std::string errors;
std::istringstream modelDataStream(modelData);
if (Json::parseFromStream(reader, modelDataStream, &modelDataJson, &errors))
{
// 模型返回的 JSON 格式数据现在就保存在 modelDataJson 中
// 逐层安全检查:choices -> isArray -> !empty -> delta -> content
if (modelDataJson.isMember("choices") &&
modelDataJson["choices"].isArray() &&
!modelDataJson["choices"].empty() &&
modelDataJson["choices"][0].isMember("delta") &&
modelDataJson["choices"][0]["delta"].isMember("content"))
{
std::string content = modelDataJson["choices"][0]["delta"]["content"].asString();
// 处理 deltaContent,例如追加到全文缓存
fullResponse += content;
// 将本次解析出的模型返回的有效数据转给调用 sendMessageStream 函数的用户使用 --- callback
callback(content, false);
}
}
else
{
// 解析失败,输出警告日志
WARN("DeepSeekProvider sendMessageStream parse modelDataJson error: {}", errors);
}
}
}
return true;
};
// 给模型发送请求
auto result = client.send(req);
if(!result){
// 请求发送失败,出现网络问题,比如DNS解析失败、连接超时
ERR("Network error {}", (int)result.error());
return "";
}
// 确保流式操作正确结束
if(!streamFinish){
WARN("stream ended without [DONE] marker");
callback("", true);
}
return fullResponse;
}//
//4. 获取模型名称
std::string DeepseekProvider::getModelName() const
{
return "deepseek-chat";
}
//5. 获取模型描述
std::string DeepseekProvider::getModelDesc() const
{
return "由深度求索创造的免费AI助手,热情细腻";
}
} // namespace