文章目录
- 一、前言
-
- [1. 项目介绍](#1. 项目介绍)
- [2. 最终效果](#2. 最终效果)
- [3. gitee链接](#3. gitee链接)
- [二、 环境配置](#二、 环境配置)
-
- [1. 依赖库安装命令](#1. 依赖库安装命令)
- [2. cpp-httplib 配置(header-only 库)](#2. cpp-httplib 配置(header-only 库))
- [3. 安装SQLite](#3. 安装SQLite)
- [三、 项目实现](#三、 项目实现)
-
- [1. 目录结构与开发顺序](#1. 目录结构与开发顺序)
- [2. common.hpp](#2. common.hpp)
- [3. LLMProvider.hpp](#3. LLMProvider.hpp)
- [4. DeepSeekProvider类](#4. DeepSeekProvider类)
- [5. 测试DeepSeek接口是否成功](#5. 测试DeepSeek接口是否成功)
- [6. LLMManager类](#6. LLMManager类)
- [7. 不接入DataManager的SessionManager](#7. 不接入DataManager的SessionManager)
- [8. DataManager类](#8. DataManager类)
- [9. 接入DataManager的SessionManager](#9. 接入DataManager的SessionManager)
- [10. ChatSDK](#10. ChatSDK)
- [11. ChatSDK测试](#11. ChatSDK测试)
- [12. ChatServer类与前端](#12. ChatServer类与前端)
一、前言
1. 项目介绍
本项目带领大家从零实现C++AI大语言模型接入SDK,SDK核心特性如下:
✅统一的多模型接入:支持DeepSeek-V3.2-Exp、gemini-2.0-flash、gpt-4o-mini等主流模型;支持通过Ollama无缝接入本地模型(deepseek-r1:1.5b),保障数据隐私与低延迟
✅高效流式响应与解析:完整支持 Server-Sent Events 流式传输,实时解析模型返回的数据流,实现类似"逐字打印"效果
✅完整的会话管理:自动维护多轮对话上下文;支持创建、删除、获取会话列表,历史会话信息查看;支持应用重启后会话状态恢复,实现"断点续聊"
✅开箱即用的SDK设计:清晰简洁的API接口,方便在项目中集成以及扩展本项目不仅提供了一个功能完备的SDK,还包含一个基于该SDK实现的智能聊天机器人,演示如何将SDK快速接入到自己项目中。
目前博主只是接入了一个deepseek,自己感兴趣还可以接入chatgpt、Gemini等等大模型,和deepseek的接入过程几乎一摸一样,大家感兴趣可以自己试试
2. 最终效果



3. gitee链接
gitee链接:https://gitee.com/qi-haozhe/ai-model-acess-tech
超链接
大家可以点击提交记录,就可以看我从零开始,先写的什么,后写的什么了。

二、 环境配置
博主是在云服务器上写的,建议大家如果要配置ubuntu环境的话,选择新一点的版本,这样安装各种依赖方便点。此处我的云服务器版本是ubuntu24.04

1. 依赖库安装命令
bash
# 1. gflags 安装
sudo apt-get install libgflags-dev
# 2. spdlog 安装
sudo apt-get install libspdlog-dev
# 3. fmt 安装
sudo apt-get install fmt
# 4. jsoncpp 安装
sudo apt-get install libjsoncpp-dev
# 5. gtest 安装
sudo apt-get install libgtest-dev
# 6. ssl 安装
sudo apt-get install libssl-dev
# 7. cmake 安装
sudo apt-get install cmake
# 8. pkg-config 安装(编译时查找库文件工具)
sudo apt install pkg-config
# 9. curl 工具安装
sudo apt install curl
2. cpp-httplib 配置(header-only 库)
bash
# 1. 下载库
git clone https://github.com/yhirose/cpp-httplib.git
# 2. 拷贝头文件到系统目录(全局可用)
sudo cp cpp-httplib/httplib.h /usr/include/
说明:cpp-httplib是单头文件库,代码中直接
#include <httplib.h>即可使用
3. 安装SQLite
bash
# 1. 安装sqlite3命令行工具(用于终端操作数据库)
sudo apt install sqlite3
# 2. 安装sqlite开发库(用于C/C++程序编译)
sudo apt install libsqlite3-dev
# 3. 查看开发库包含的文件及路径(可选)
dpkg -L libsqlite3-dev
- 头文件路径:
/usr/include/sqlite3.h、/usr/include/sqlite3ext.h - 静态库文件:
/usr/lib/x86_64-linux-gnu/libsqlite3.a - 动态库文件:
/usr/lib/x86_64-linux-gnu/libsqlite3.so - 配置文件:
/usr/lib/x86_64-linux-gnu/pkgconfig/sqlite3.pc
编译时链接sqlite3库
Makefile中链接
makefile
g++ main.cpp -o my_program -lsqlite3
CMakeLists.txt中链接
cmake
target_link_libraries(${SDK_NAME} sqlite3)
三、 项目实现
1. 目录结构与开发顺序
最终目录结构如下图所示

编写顺序建议先写sdk目录下的所有内容,sdk的完成了再去写ChatServer
目录中的内容
sdk开发顺序
- common.hpp
- util/myLog.hpp myLog.cc
- LLMProvider.hpp DeepSeekProvider.cc
- 去test目录下写个测试,看看写好的DeepSeek能不能正常调用
- LLMManager.hpp LLMManager.cc
- 写不接入DataManager的SessionManager.hpp SessionManager.cc
- DataManager.hpp DataManager.cc
- 完善SessionManager.hpp SessionManager.cc 接入DataManager类
- ChatSDK.hpp ChatSDK.cc
- 最后test目录下写个测试,测一下ChatSDK能不能正常使用就行
- sdk目录整体工作完成
ChatServer开发顺序
- ChatServer.hpp ChatServer.cc 都写完
- 直接写个main.cc启动服务器即可
- 至此本项目的服务端部分就完成了
- 最后写个前端界面和配置文件就行了,前端界面要不让ai写一个,要不直接把我build目录下的前端代码拷贝到你项目中即可。
2. common.hpp
这里面就定义一些基础的数据结构,以备后面需要。
Message定义消息结构体,用来表示每条消息记录
Config用来表示模型的配置信息 析构函数记得声明虚函数要不然可能会有问题
ModelInfo用来描述模型信息
Session描述每个会话的信息
这里的会话是指这个:

cpp
#pragma once
#include <vector>
#include <string>
#include <ctime>
namespace ai_chat_sdk{
struct Message{
std::string _messageId;
std::string _role; //表示这条消息是用户发的还是ai回复的
std::string _content;
std::time_t _timestamp;
Message(const std::string &role = "", const std::string &content = "")
: _role(role), _content(content), _timestamp(0)
{}
};
struct Config{
std::string _modelName;
double _temperature; //回答发散程度
int _maxTokens; //模型最大tokens,这个太小可能会导致模型回答不完一个问题就结束了
virtual ~Config() = default;
};
struct ApiConfig:public Config
{
std::string _apiKey;
};
struct OllamaConfig : public Config
{
std::string _modelName; // 模型名称
std::string _modelDesc; // 模型描述
std::string _endpoint; // 模型API endpoint base url
};
struct ModelInfo{
std::string _modelName;
std::string _modelDesc;
std::string _provider;
std::string _endPoint;
bool _isAvailable = false;
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){}
};
struct Session{
std::string _sessionId;
std::string _modelName; //会话用的哪一个模型
std::vector<Message> _messages; //会话内的所有消息,包含用户问的和ai回复的
std::time_t _createTime;
std::time_t _updateTime;
Session(const std::string &modelName = "")
: _modelName(modelName)
{}
};
}// end ai_chat_sdk
3. LLMProvider.hpp
这个类就是一个抽象类,里面定义了一堆虚函数。后面所有要接入的模型都需要继承这一个类然后重写这些虚函数。也就是说你后面要接入DeepSeek、ChatGPT、Gemini的话都创建一个类,继承LLMProvider实现虚函数就行。算是一个策略类,以后把LLMProvider这个父类指针当作成员变量,想要什么模型就把什么子类指针赋值给父类指针就行,这里是利用了一下多态。
成员变量设置为protected,三个成员变量分别表示是否就绪、apikey和请求地址,比如deepseek的请求地址就是https://api.deepseek.com
cpp
#pragma once
#include <string>
#include <map>
#include <vector>
#include <functional>
#include "common.hpp"
namespace ai_chat_sdk {
class LLMProvider{
public:
// 初始化模型
virtual bool initModel(const std::map<std::string, std::string>& modelConfig) = 0;
//模型是否有效
virtual bool isAvailable() = 0;
// 获取模型名称
virtual std::string getModelName() const = 0;
// 获取模型描述
virtual std::string getModelDesc() const = 0;
// 发送消息并获取响应
virtual std::string sendMessage(const std::vector<Message>& messages,const std::map<std::string, std::string>& requestParams) = 0;
// 发送消息并获取流式响应
virtual std::string sendMessageStream(const std::vector<Message>& messages,
const std::map<std::string, std::string>& requestParams,
std::function<void(const std::string&, bool)> callback) = 0; // callback: 对模型返回的增量数据如何处理,第一个参数为增量数据,第二个参数为是否为最后一个增量数据)
protected:
bool _isAvailable = false;
std::string _apiKey;
std::string _endPoint;
};
}
4. DeepSeekProvider类
DeepSeekProvider类就是继承LLMProvider抽象类重写虚函数而已。
前四个都比较简单,我们重点说一下最后两个虚函数具体如何实现:
// 发送消息并获取响应 std::string sendMessage(const std::vector&messages,const std::map<std::string, std::string>& requestParams);
//发送消息并获取流式响应
std::string sendMessageStream(const std::vector&messages, const std::map<std::string, std::string>& requestParams, std::function<void(const std::string&, bool)> callback);
cpp
//DeepSeekProvider.hpp
#pragma once
#include "LLMProvider.hpp"
namespace ai_chat_sdk {
class DeepSeekProvider : public LLMProvider {
public:
// 初始化模型
bool initModel(const std::map<std::string, std::string>& modelConfig);
//模型是否有效
bool isAvailable();
// 获取模型名称
std::string getModelName() const;
// 获取模型描述
std::string getModelDesc() const;
// 发送消息并获取响应
std::string sendMessage(const std::vector<Message>& messages,const std::map<std::string, std::string>& requestParams);
// 发送消息并获取流式响应
std::string sendMessageStream(const std::vector<Message>& messages,
const std::map<std::string, std::string>& requestParams,
std::function<void(const std::string&, bool)> callback);
};
} // end ai_chat_sdk
全量响应
这个函数的主要功能就是拼凑json格式的请求消息,然后通过httplib构造http请求,去deepseek官网根据你设置的deepseek_apikey鉴权,然后获取响应消息,解析响应消息然后返回字符串就行,这个返回的字符串未来就是前端显示的字符串。
拼装请求JSON → 发起HTTP POST请求 → 接收HTTP响应 → 校验响应有效性 → 解析响应JSON → 精准提取AI回复内容 → 返回纯文本结果
这个函数就主要是全量响应,意思是你发送请求后,只有当获取到全部的响应之后才会返回到前端,给人的感觉就是你前端问了一个问题,然后等了几秒钟,然后突然所有结果全都展现出来了,与之对应的就是流式响应,现在的ai都是流式响应。
先从传入的requestParams中读取配置参数,没有传参则使用默认值,不影响协议和格式,只影响模型生成策略:
cpp
double temperature = 0.7; // 默认温度值,控制回复随机性
int maxTokens = 2048; // 默认最大生成长度
// 解析外部传入的参数覆盖默认值
if(requestParams.find("temperature") != requestParams.end()) temperature = std::stod(requestParams.at("temperature"));
if(requestParams.find("max_tokens") != requestParams.end()) maxTokens = std::stoi(requestParams.at("max_tokens"));
然后是构造请求消息,请求的http body字段就是下面这个表格,这几个字段是必填项,我们按照格式拼接就行。

大模型需要上下文,所以每次请求的时候我们需要把传入的std::vector<Message>历史消息列表,转成JSON数组格式:
cpp
Json::Value messageArray(Json::arrayValue); // 创建JSON数组对象
for(const auto &msg : messages){
Json::Value messageJson; // 单个消息的JSON对象
messageJson["role"] = msg._role; // 填充角色:user/assistant/system
messageJson["content"] = msg._content; // 填充消息内容:用户提问/AI回复
messageArray.append(messageJson); // 单个消息对象加入数组
}
拼装后的messageArray JSON结构示例:
json
[
{"role":"user","content":"你好"},
{"role":"assistant","content":"您好,有什么可以帮您?"},
{"role":"user","content":"给我整理一份C++库清单"}
]
组装完整的请求JSON根对象按 DeepSeek 官方接口规范,拼接顶层必填字段,缺一不可:
cpp
Json::Value requestJson; // JSON根对象
requestJson["model"] = getModelName(); // 必填:模型名称 deepseek-chat
requestJson["messages"] = messageArray; // 必填:历史消息数组
requestJson["temperature"] = temperature; // 可选:随机性参数
requestJson["max_tokens"] = maxTokens; // 可选:最大生成token数
JSON对象 → 无缩进的纯字符串(请求体最终格式)
cpp
Json::StreamWriterBuilder writerBuilder;
writerBuilder["indentation"] = ""; // 关键:设置空缩进,生成紧凑的JSON字符串,减少传输体积
std::string requestBodyStr = Json::writeString(writerBuilder, requestJson);
最终拼接完成的requestBodyStr 标准请求体(纯字符串),直接作为HTTP POST的请求体发送:
json
{"model":"deepseek-chat","messages":[{"role":"user","content":"你好"},{"role":"assistant","content":"您好"},{"role":"user","content":"整理C++库清单"}],"temperature":0.7,"max_tokens":2048}
发起HTTP POST请求(cpp-httplib实现)
cpp
httplib::Client cli(_endPoint.c_str()); // 创建HTTP客户端,_endPoint是DeepSeek的接口域名/地址
cli.set_connection_timeout(30); // 连接超时30秒
cli.set_read_timeout(60); // 读取响应超时60秒
// 设置HTTP请求头(重中之重,2个必填请求头,缺一不可)
httplib::Headers headers = {
{"Authorization", "Bearer " + _apiKey}, // 身份认证:接口鉴权,格式固定 Bearer+空格+APIKey
{"Content-Type", "application/json"} // 告诉服务端:本次请求体是JSON格式
};
// 发起POST请求:【接口路径 + 请求头 + 请求体字符串 + 请求体格式】
auto res = cli.Post("/v1/chat/completions", headers, requestBodyStr, "application/json");
然后就是获取响应解析响应了:
- 大模型返回的
res->body是一个 JSON格式的字符串,格式是固定的(接口文档约定) - 使用
JsonCpp把「JSON字符串」解析成「C++的Json::Value对象」 - 按照 JSON的层级结构,一层一层「精准取值」,最终拿到AI回复的纯文本内容
- 所有取值步骤都做了合法性判断,防止JSON字段缺失导致程序崩溃
响应会有如下字段,我们只需要把choices->message->content提取到就行,也就是说我们只要这个content字段,根据json的语法去获取之后再校验一下就行。
json
{
"id": "chatcmpl-xxx",
"object": "chat.completion",
"created": 1735620000,
"model": "deepseek-chat",
"choices": [ // 核心数组,固定只有1个元素
{
"index": 0,
"message": { // 核心对象
"role": "assistant",
"content": "这是AI给你的最终回复内容,纯文本格式" // ✅ 我们要的最终数据
},
"finish_reason": "stop"
}
],
"usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30}
}

cpp
Json::Value responseBody; // 存储解析后的JSON对象
Json::CharReaderBuilder readerBuilder; // JsonCpp解析器
std::string parseError; // 解析失败的错误信息
std::istringstream responseStream(res->body); // 把响应字符串转成输入流
// 核心解析API:把响应流解析到responseBody对象中
bool parseOk = Json::parseFromStream(readerBuilder, responseStream, &responseBody, &parseError);
对应的提取代码(和上面的JSON结构完全一一对应)
cpp
if(parseOk){ // 第一步:判断JSON解析是否成功
// 第二步:判断是否有choices字段 + 是数组 + 数组非空
if(responseBody.isMember("choices") && responseBody["choices"].isArray() && !responseBody["choices"].empty()){
auto choice = responseBody["choices"][0]; // 取choices数组的第一个元素(固定只有1个)
// 第三步:判断choice里有message字段 + message里有content字段
if(choice.isMember("message") && choice["message"].isMember("content")){
// ✅ 最终提取:拿到AI回复的纯文本内容
std::string replyContent = choice["message"]["content"].asString();
INFO("DeepSeekProvider response text: {}", replyContent);
return replyContent; // 返回结果,结束函数
}
}
}
完整代码:
cpp
std::string DeepSeekProvider::sendMessage(const std::vector<Message> &messages, const std::map<std::string, std::string> &requestParams)
{
if(!isAvailable()){
ERROR("DeepSeekProvider sendMessage model not available");
return "";
}
double temperature = 0.7;
int maxTokens = 2048;
if(requestParams.find("temperature") != requestParams.end()){
temperature = std::stod(requestParams.at("temperature"));
}
if(requestParams.find("max_tokens") != requestParams.end()){
maxTokens = std::stoi(requestParams.at("max_tokens"));
}
//根据以前的messages构建历史消息记录
Json::Value messageArray(Json::arrayValue);
for(const auto &msg : messages){
//INFO("Role: {}, Content: {}", msg._role, msg._content);
Json::Value messageJson;
messageJson["role"] = msg._role;
messageJson["content"] = msg._content;
messageArray.append(messageJson);
}
Json::Value requestJson;
requestJson["model"] = getModelName();
requestJson["messages"] = messageArray;
requestJson["temperature"] = temperature;
requestJson["max_tokens"] = maxTokens;
Json::StreamWriterBuilder writerBuilder;
writerBuilder["indentation"] = "";
std::string requestBodyStr = Json::writeString(writerBuilder, requestJson);
INFO("DeepSeekProvider sendMessage requestBody: {}", requestBodyStr);
httplib::Client cli(_endPoint.c_str());
cli.set_connection_timeout(30); // 连接超时30秒
cli.set_read_timeout(60); // 读取超时60秒
// 设置请求头
httplib::Headers headers = {
{"Authorization", "Bearer " + _apiKey},
{"Content-Type", "application/json"}
};
auto res = cli.Post("/v1/chat/completions", headers, requestBodyStr, "application/json");
if(!res){
ERROR("DeepSeekProvider sendMessage request failed, error: {}", httplib::to_string(res.error()));
return "";
}
INFO("DeepSeekProvider sendMessage POST request success, status : {}", res->status);
INFO("DeepSeekProvider sendMessage POST request success, body : {}", res->body);
// 检测响应是否成功
if(res->status != 200){
return "";
}
Json::Value responseBody;
Json::CharReaderBuilder readerBuilder;
std::string parseError;
std::istringstream responseStream(res->body);
if(Json::parseFromStream(readerBuilder, responseStream, &responseBody, &parseError)){
// 获取message数组
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;
}
}
}
// 8. json解析失败
ERROR("DeepSeekProvider sendMessage POST response body parse failed, error");
return "deepseek response json parse failed";
}
cpp
//DeepSeekProvider.cc
#include "../include/DeepSeekProvider.hpp"
#include "../include/util/myLog.hpp"
#include "../include/DeepSeekProvider.hpp"
#include <jsoncpp/json/json.h>
#include "../include/httplib.h"
#include <jsoncpp/json/reader.h>
namespace ai_chat_sdk
{
bool DeepSeekProvider::initModel(const std::map<std::string, std::string> &modelConfig){
auto it = modelConfig.find("api_key");
if(it != modelConfig.end()){
_apiKey = it->second;
} else {
ERROR("DeepSeekProvider initModel api_key not found");
return false;
}
it = modelConfig.find("end_point");
if(it != modelConfig.end()){
_endPoint = it->second;
} else {
_endPoint = "https://api.deepseek.com";
}
_isAvailable = true;
INFO("DeepSeekProvider initModel success, endpoint: {}",_endPoint);
return true;
}
bool DeepSeekProvider::isAvailable()
{
return _isAvailable;
}
std::string DeepSeekProvider::getModelName() const
{
return "deepseek-chat";
}
std::string DeepSeekProvider::getModelDesc() const{
return "一款实用性强、中文优化的通用对话助手,适合日常问答与创作";
}
std::string DeepSeekProvider::sendMessage(const std::vector<Message> &messages, const std::map<std::string, std::string> &requestParams)
{
if(!isAvailable()){
ERROR("DeepSeekProvider sendMessage model not available");
return "";
}
double temperature = 0.7;
int maxTokens = 2048;
if(requestParams.find("temperature") != requestParams.end()){
temperature = std::stod(requestParams.at("temperature"));
}
if(requestParams.find("max_tokens") != requestParams.end()){
maxTokens = std::stoi(requestParams.at("max_tokens"));
}
//根据以前的messages构建历史消息记录
Json::Value messageArray(Json::arrayValue);
for(const auto &msg : messages){
//INFO("Role: {}, Content: {}", msg._role, msg._content);
Json::Value messageJson;
messageJson["role"] = msg._role;
messageJson["content"] = msg._content;
messageArray.append(messageJson);
}
Json::Value requestJson;
requestJson["model"] = getModelName();
requestJson["messages"] = messageArray;
requestJson["temperature"] = temperature;
requestJson["max_tokens"] = maxTokens;
Json::StreamWriterBuilder writerBuilder;
writerBuilder["indentation"] = "";
std::string requestBodyStr = Json::writeString(writerBuilder, requestJson);
INFO("DeepSeekProvider sendMessage requestBody: {}", requestBodyStr);
httplib::Client cli(_endPoint.c_str());
cli.set_connection_timeout(30); // 连接超时30秒
cli.set_read_timeout(60); // 读取超时60秒
// 设置请求头
httplib::Headers headers = {
{"Authorization", "Bearer " + _apiKey},
{"Content-Type", "application/json"}
};
auto res = cli.Post("/v1/chat/completions", headers, requestBodyStr, "application/json");
if(!res){
ERROR("DeepSeekProvider sendMessage request failed, error: {}", httplib::to_string(res.error()));
return "";
}
INFO("DeepSeekProvider sendMessage POST request success, status : {}", res->status);
INFO("DeepSeekProvider sendMessage POST request success, body : {}", res->body);
// 检测响应是否成功
if(res->status != 200){
return "";
}
Json::Value responseBody;
Json::CharReaderBuilder readerBuilder;
std::string parseError;
std::istringstream responseStream(res->body);
if(Json::parseFromStream(readerBuilder, responseStream, &responseBody, &parseError)){
// 获取message数组
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;
}
}
}
// 8. json解析失败
ERROR("DeepSeekProvider sendMessage POST response body parse failed, error");
return "deepseek response json parse failed";
}
std::string DeepSeekProvider::sendMessageStream(const std::vector<Message> &messages,
const std::map<std::string, std::string> &requestParams,
std::function<void(const std::string &, bool)> callback)
{
if(!isAvailable()){
ERROR("DeepSeekProvider sendMessageStream model not available");
return "";
}
// 2. 构造请求参数
double temperature = 0.7;
int maxTokens = 2048;
if(requestParams.find("temperature") != requestParams.end()){
temperature = std::stod(requestParams.at("temperature"));
}
if(requestParams.find("max_tokens") != requestParams.end()){
maxTokens = std::stoi(requestParams.at("max_tokens"));
}
// 构造历史消息
Json::Value messageArray(Json::arrayValue);
for(const auto& message : messages){
Json::Value messageObject;
messageObject["role"] = message._role;
messageObject["content"] = message._content;
messageArray.append(messageObject);
}
// 3. 构造请求体
Json::Value requestBody;
requestBody["model"] = getModelName();
requestBody["messages"] = messageArray;
requestBody["temperature"] = temperature;
requestBody["max_tokens"] = maxTokens;
requestBody["stream"] = true;
// 4. 序列化
Json::StreamWriterBuilder writerBuilder;
writerBuilder["indentation"] = "";
std::string requestBodyStr = Json::writeString(writerBuilder, requestBody);
INFO("DeepSeekProvider sendMessageStream requestBody: {}", requestBodyStr);
// 5. 使用cpp-httplib库构造HTTP客户端
httplib::Client client(_endPoint.c_str());
client.set_connection_timeout(30, 0); // 连接超时时间为30秒
client.set_read_timeout(300, 0); // 流式响应需要更长的时间,设置超时时间为300秒
// 设置请求头
httplib::Headers headers = {
{"Authorization", "Bearer " + _apiKey},
{"Content-Type", "application/json"},
{"Accept", "text/event-stream"}
};
// 流式处理变量
std::string buffer; // 接受流式响应的数据块
bool gotError = false; // 标记响应是否成功
std::string errorMsg; // 错误描述符
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) {
if(res.status != 200){
gotError = true;
errorMsg = "HTTP status code: " + std::to_string(res.status);
return false; // 终止请求
}
return true; // 继续接收后续数据
};
// 设置数据接收处理器--解析流式响应的每个块的数据
req.content_receiver = [&](const char* data, size_t len, size_t offset, size_t totalLength){
// 验证响应头是否错误,如果出错就不需要再继续接收数据
if(gotError){
return false;
}
// 追加数据到buffer
buffer.append(data, len);
INFO("DeepSeekProvider sendMessageStream buffer: {}", buffer);
// 处理所有的流式响应的数据块,注意:数据块之间是一个\n\n分隔
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);
// 解析该块响应数据的中模型返回的有效数据
// 处理空行和注释,注意:以:开头的行是注释行,需要忽略
if(chunk.empty() || chunk[0] == ':'){
continue;
}
// 获取模型返回的有效数据
if(chunk.compare(0, 6, "data: ") == 0){
std::string modelData = chunk.substr(6);
// 检测是否为结束标记
if(modelData == "[DONE]"){
callback("", true);
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
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
fullResponse += content;
// 将本次解析出的模型返回的有效数据转给调用sendMessageStraem函数的用户使用---callback
callback(content, false);
}
}else{
WARN("DeepSeekProvider sendMessageStream parse modelDataJson error: {}", errors);
}
}
}
return true;
};
// 给模型发送请求
auto result = client.send(req);
if(!result){
// 请求发送失败,出现网络问题,比如DNS解析失败、连接超时
ERROR("Network error {}", to_string(result.error()));
return "";
}
// 确保流式操作正确结束
if(!streamFinish){
WARN("stream ended without [DONE] marker");
callback("", true);
}
return fullResponse;
}
} // end ai_chat_sdk
流式响应
关于流式响应我们这里用的是SSE协议,流式响应在这里的使用方法就是客户端一次请求,服务端分多次把完整的数据发过来,即所谓的流式,具体介绍如下:
/v1/chat/completions + stream=true 是 text/event-stream (简称SSE) 协议,这是大模型流式输出的事实标准协议(OpenAI/DeepSeek/Anthropic 全用它),不是普通的HTTP JSON响应,这是理解流式的前提:
- SSE 是单向的服务端推流、客户端流式接收的 HTTP 协议,基于 HTTP 长连接实现
- 服务端不会一次性返回完整JSON,而是分批次、一小块一小块的返回数据
- 数据格式约定:每个有效数据块用
\n\n双换行符分隔 ,每个块的开头固定是data:前缀 - 结束标记固定:服务端最后会返回
data: [DONE]表示流推送完成 - 空行/以
:开头的行是注释行,协议层面要求客户端直接忽略,不做解析
cpp-httplib 实现流式响应的核心:两个回调函数
cpp-httplib 是一个轻量但功能完整的C++ HTTP客户端库,它对流式HTTP响应 的支持,完全依赖两个核心回调 ,
回调1:req.response_handler ------ 响应头回调(只执行1次)
cpp
req.response_handler = [&](const httplib::Response& res) {
if(res.status != 200){
gotError = true;
errorMsg = "HTTP status code: " + std::to_string(res.status);
return false; // 终止请求
}
return true; // 继续接收后续数据
};
作用&执行时机
- 执行时机:客户端接收到服务端返回的 HTTP响应头(Status+Headers)时,立刻执行,且只会执行一次
- 核心作用:校验请求是否成功、提前拦截错误,不需要等到接收完整数据再判断
- 返回值规则:
return true表示「响应头合法,继续接收流式数据体」;return false表示「响应非法,立刻终止整个请求,不再接收任何数据」 - 此处是判断状态码是否为200,非200则标记错误、终止请求。
回调2:req.content_receiver ------ 流式数据体回调(核心!执行N次)
cpp
req.content_receiver = [&](const char* data, size_t len, size_t offset, size_t totalLength){
// 流式数据解析逻辑
};
服务端返回的流式数据体 会被拆分成多个二进制数据块 ,每收到一小块数据,这个回调就会被执行一次,直到:
- 服务端推送完所有数据、主动关闭连接;
- 回调内部返回
false主动终止; - 超时/网络异常中断。
比如大模型返回100个字,会拆成10次推送,这个回调就执行10次,每次拿到10个字左右的二进制数据。
回到我们的代码中:
先要设置http头部字段,要设置流式字段
cpp
// 设置请求头
httplib::Headers headers = {
{"Authorization", "Bearer " + _apiKey},
{"Content-Type", "application/json"},
{"Accept", "text/event-stream"} //设置流式响应
};
后面就是设置了两个关键的回调函数:
这个是首次返回的时候执行一次的会回调函数,用于决定是否继续接收后面的信息,返回true表示继续接收反之直接停止,不再接收。下面的函数就是判断返回的htto状态码是不是
200,是则继续接收,反之停止。
cpp
// 设置响应处理器
req.response_handler = [&](const httplib::Response& res) {
if(res.status != 200){
gotError = true;
errorMsg = "HTTP status code: " + std::to_string(res.status);
return false; // 终止请求
}
return true; // 继续接收后续数据
};
下面这个是第二个重要回调,用于每次收到服务端发来的消息片段的处理。
这个回调函数就是把全量返回获取响应消息中的message的流程干了一边,然后每次提取到消息之后执行一下函数形参设置的回调函数而已,这个回调函数其实就是ChatServer用来流式发给前端的。
cpp
// 设置数据接收处理器--解析流式响应的每个块的数据
req.content_receiver = [&](const char* data, size_t len, size_t offset, size_t totalLength){
// 验证响应头是否错误,如果出错就不需要再继续接收数据
if(gotError){
return false;
}
// 追加数据到buffer
buffer.append(data, len);
INFO("DeepSeekProvider sendMessageStream buffer: {}", buffer);
// 处理所有的流式响应的数据块,注意:数据块之间是一个\n\n分隔
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);
// 解析该块响应数据的中模型返回的有效数据
// 处理空行和注释,注意:以:开头的行是注释行,需要忽略
if(chunk.empty() || chunk[0] == ':'){
continue;
}
// 获取模型返回的有效数据
if(chunk.compare(0, 6, "data: ") == 0){
std::string modelData = chunk.substr(6);
// 检测是否为结束标记
if(modelData == "[DONE]"){
callback("", true);
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
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
fullResponse += content;
// 将本次解析出的模型返回的有效数据转给调用sendMessageStraem函数的用户使用---callback
callback(content, false);
}
}else{
WARN("DeepSeekProvider sendMessageStream parse modelDataJson error: {}", errors);
}
}
}
return true;
};
完整代码:
cpp
std::string DeepSeekProvider::sendMessageStream(const std::vector<Message> &messages,
const std::map<std::string, std::string> &requestParams,
std::function<void(const std::string &, bool)> callback)
{
if(!isAvailable()){
ERROR("DeepSeekProvider sendMessageStream model not available");
return "";
}
// 2. 构造请求参数
double temperature = 0.7;
int maxTokens = 2048;
if(requestParams.find("temperature") != requestParams.end()){
temperature = std::stod(requestParams.at("temperature"));
}
if(requestParams.find("max_tokens") != requestParams.end()){
maxTokens = std::stoi(requestParams.at("max_tokens"));
}
// 构造历史消息
Json::Value messageArray(Json::arrayValue);
for(const auto& message : messages){
Json::Value messageObject;
messageObject["role"] = message._role;
messageObject["content"] = message._content;
messageArray.append(messageObject);
}
// 3. 构造请求体
Json::Value requestBody;
requestBody["model"] = getModelName();
requestBody["messages"] = messageArray;
requestBody["temperature"] = temperature;
requestBody["max_tokens"] = maxTokens;
requestBody["stream"] = true;
// 4. 序列化
Json::StreamWriterBuilder writerBuilder;
writerBuilder["indentation"] = "";
std::string requestBodyStr = Json::writeString(writerBuilder, requestBody);
INFO("DeepSeekProvider sendMessageStream requestBody: {}", requestBodyStr);
// 5. 使用cpp-httplib库构造HTTP客户端
httplib::Client client(_endPoint.c_str());
client.set_connection_timeout(30, 0); // 连接超时时间为30秒
client.set_read_timeout(300, 0); // 流式响应需要更长的时间,设置超时时间为300秒
// 设置请求头
httplib::Headers headers = {
{"Authorization", "Bearer " + _apiKey},
{"Content-Type", "application/json"},
{"Accept", "text/event-stream"}
};
// 流式处理变量
std::string buffer; // 接受流式响应的数据块
bool gotError = false; // 标记响应是否成功
std::string errorMsg; // 错误描述符
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) {
if(res.status != 200){
gotError = true;
errorMsg = "HTTP status code: " + std::to_string(res.status);
return false; // 终止请求
}
return true; // 继续接收后续数据
};
// 设置数据接收处理器--解析流式响应的每个块的数据
req.content_receiver = [&](const char* data, size_t len, size_t offset, size_t totalLength){
// 验证响应头是否错误,如果出错就不需要再继续接收数据
if(gotError){
return false;
}
// 追加数据到buffer
buffer.append(data, len);
INFO("DeepSeekProvider sendMessageStream buffer: {}", buffer);
// 处理所有的流式响应的数据块,注意:数据块之间是一个\n\n分隔
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);
// 解析该块响应数据的中模型返回的有效数据
// 处理空行和注释,注意:以:开头的行是注释行,需要忽略
if(chunk.empty() || chunk[0] == ':'){
continue;
}
// 获取模型返回的有效数据
if(chunk.compare(0, 6, "data: ") == 0){
std::string modelData = chunk.substr(6);
// 检测是否为结束标记
if(modelData == "[DONE]"){
callback("", true);
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
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
fullResponse += content;
// 将本次解析出的模型返回的有效数据转给调用sendMessageStraem函数的用户使用---callback
callback(content, false);
}
}else{
WARN("DeepSeekProvider sendMessageStream parse modelDataJson error: {}", errors);
}
}
}
return true;
};
// 给模型发送请求
auto result = client.send(req);
if(!result){
// 请求发送失败,出现网络问题,比如DNS解析失败、连接超时
ERROR("Network error {}", to_string(result.error()));
return "";
}
// 确保流式操作正确结束
if(!streamFinish){
WARN("stream ended without [DONE] marker");
callback("", true);
}
return fullResponse;
}
5. 测试DeepSeek接口是否成功
这是当时测试的cmake和main.cc以及运行结果截图,不多赘述,大家可以自己测一测:
运行前记得设置自己的deepseek apikey环境变量,各位可以去deepseek官网弄一下,比较简单不多说了。
终端执行:
cpp
export deepseek_apikey="你的deepseek apikey"
cmake:
cpp
# 设置Cmake的最小版本
cmake_minimum_required(VERSION 3.10)
# 项目名称
project(testLLM)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 设置构建类型Debug
set(CMAKE_BUILD_TYPE Debug)
# 添加可执行文件
add_executable(testLLM testLLM.cc
../sdk/src/util/myLog.cc
../sdk/src/DeepSeekProvider.cc
)
# 设置输出目录
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
# 添加头文件搜索路劲
include_directories(${CMAKE_SOURCE_DIR}/../sdk/include)
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})
# 添加CPPHTTPLIB_OPENSSL_SUPPORT定义
target_compile_definitions(testLLM PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
# 链接库
target_link_libraries(testLLM pthread jsoncpp fmt spdlog gtest OpenSSL::SSL OpenSSL::Crypto)
cpp
#include "../sdk/include/DeepSeekProvider.hpp"
#include <iostream>
#include <gtest/gtest.h>
#include "../sdk/include/util/myLog.hpp"
TEST(DeepSeekProviderTest, sendMessage){
auto provider = std::make_shared<ai_chat_sdk::DeepSeekProvider>();
ASSERT_TRUE(provider != nullptr);
std::map<std::string, std::string> modelParam;
modelParam["api_key"] = std::getenv("deepseek_apikey");
modelParam["endpoint"] = "https://api.deepseek.com";
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", "你是谁?"});
// 实例化DeepSeekProvider的对象
// 调用sendMessage方法
std::string response = provider->sendMessage(messages, requestParam);
ASSERT_FALSE(response.empty());
INFO("fulldata : {}", response);
// auto writeChunk = [&](const std::string& chunk, bool last){
// INFO("chunk : {}", chunk);
// if(last){
// INFO("[DONE]");
// }
// };
// std::string fullData = provider->sendMessageStream(messages, requestParam, writeChunk);
// ASSERT_FALSE(fullData.empty());
// INFO("response : {}", fullData);
}
int main(int argc, char **argv) {
lg::Logger::initLogger("testLLM", "stdout", spdlog::level::debug);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
运行结果:

6. LLMManager类
LLMManager 是AI聊天SDK的核心管理类、统一入口类 ,采用管理者设计模式 ,对底层各类大模型提供商(如DeepSeekProvider)做统一封装与调度,向上层提供标准化的大模型调用接口,屏蔽不同大模型的底层实现差异,是整个SDK的调度中枢。
核心作用:统一管理所有大模型的注册、初始化、可用性校验,以及统一转发全量/流式消息请求,提供极简、统一的调用入口,该类主要是放到ChatSDK类里面使用的,ChatSDK里面会有一个 LLMManager对象,要调用什么模型直接通过这个对象调用就行。
以下是所有业务:
- 保存会话数据 :建立
<session_id, session>映射关系,实现多会话的关联存储与查询。 - 创建会话:在连接大模型前创建会话,后续聊天消息统一存入该会话。
- 按ID获取会话:通过会话ID查询指定会话。
- 向会话添加消息:将用户提问、模型回复保存至对应会话。
- 获取会话历史消息:根据会话ID提取所有消息,用于界面展示。
- 获取所有会话列表:查询全量会话列表,用于界面展示。
- 删除会话:支持删除指定会话。
- 更新会话时间戳:用户再次使用会话时,更新其时间戳。
- 清空所有会话:删除所有会话数据。
- 统计会话总数:获取当前会话的数量。
- 生成会话ID:生成唯一ID标识会话。
- 生成消息ID:生成唯一ID标识单条消息。
以下是简要介绍,不是很难,不多赘述,
-
私有成员(核心存储)
_providers:键值对映射,存储已注册的「模型名称-大模型提供商实例」,通过智能指针管理不同厂商的模型实现类,解耦底层不同大模型的差异;_modelInfos:存储所有模型的基础信息与可用状态,维护模型启用/禁用标识。
-
核心对外接口功能
registerProvider:注册大模型提供商,将具体的模型实现类挂载到管理器中,完成模型接入;initModel:初始化指定模型,传入模型配置参数完成鉴权、连接等前置操作,初始化成功后标记模型为可用状态;getAvailableModels:查询所有已初始化、可正常调用的模型列表;isModelAvailable:校验指定模型是否完成初始化、处于可用状态;sendMessage:统一转发全量消息请求,调用对应模型的全量响应接口,返回完整AI回复文本;sendMessageStream:统一转发流式消息请求,调用对应模型的流式响应接口,透传流式回调函数,实现实时文本推送。
这样设计的优势
该类是上层业务与底层大模型的中间层 ,上层业务无需关注具体大模型的对接细节,只需通过该类的统一接口即可完成所有操作,做到一次调用、适配所有模型,极大降低上层接入成本。
调用逻辑
业务层调用流程:注册模型 → 初始化模型 → 校验可用性 → 发起全量/流式请求,所有操作均通过该类完成,逻辑闭环且职责单一。
完整代码:
cpp
#pragma once
#include <map>
#include <memory>
#include "LLMProvider.hpp"
namespace ai_chat_sdk {
class LLMManager {
public:
bool registerProvider(const std::string& providerName,std::unique_ptr<LLMProvider> provider);
bool initModel(const std::string& providerName, const std::map<std::string, std::string>& modelParam);
std::vector<ModelInfo> getAvailableModels()const;
bool isModelAvailable(const std::string& modelName)const;
std::string sendMessage(const std::string& modelName, const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam);
std::string sendMessageStream(const std::string& modelName, const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam, std::function<void(const std::string&, bool)>& callback);
LLMManager() = default;
private:
std::map<std::string, std::unique_ptr<LLMProvider>> _providers;
std::map<std::string, ModelInfo> _modelInfos;
};
} // end ai_chat_sdk
cpp
#include "../include/LLMManager.hpp"
#include "../include/util/myLog.hpp"
#include "../include/common.hpp"
namespace ai_chat_sdk {
// 注册LLM提供者
bool LLMManager::registerProvider(const std::string& modelName, std::unique_ptr<LLMProvider> provider){
// 参数检测
if(!provider){
ERROR("cannot register nullptr provider, modelName = {}", modelName);
return false;
}
// 注意:unique_ptr是防拷贝的,此处只能通过move的方式将资源转移给当前对象
_providers[modelName] = std::move(provider);
// 添加模型信息
_modelInfos[modelName] = ModelInfo(modelName);
// 模型初始化成功
INFO("register provider success, modelName = {}", modelName);
return true;
}
// 初始化指定模型
bool LLMManager::initModel(const std::string& modelName, const std::map<std::string, std::string>& modelParam){
// 检测模型是否注册
auto it = _providers.find(modelName);
if(it == _providers.end()){
ERROR("model provider not found, modelName = {}", modelName);
return false;
}
// 模型已经注册成功,可以初始化该模型
bool isSuccess = it->second->initModel(modelParam);
if(!isSuccess){
ERROR("init model failed, modelName = {}", modelName);
}else{
INFO("init model success, modelName = {}", modelName);
_modelInfos[modelName]._modelDesc = it->second->getModelDesc();
_modelInfos[modelName]._isAvailable = true;
}
return isSuccess;
}
// 获取可用模型
std::vector<ModelInfo> LLMManager::getAvailableModels()const{
std::vector<ModelInfo> models;
for(const auto& pair : _modelInfos){
if(pair.second._isAvailable){
models.push_back(pair.second);
}
}
return models;
}
// 检查模型是否可用
bool LLMManager::isModelAvailable(const std::string& modelName)const{
auto it = _modelInfos.find(modelName);
if(it == _modelInfos.end()){
return false;
}
return it->second._isAvailable;
}
// 发送消息给指定模型
std::string LLMManager::sendMessage(const std::string& modelName, const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam){
// 检测模型是否注册
auto it = _providers.find(modelName);
if(it == _providers.end()){
ERROR("model provider not found, modelName = {}", modelName);
return "";
}
// 检测模型是否可用
if(!it->second->isAvailable()){
ERROR("model not available, modelName = {}", modelName);
return "";
}
// 模型已经注册,并且已经初始化成功
return it->second->sendMessage(messages, requestParam);
}
// 发送消息流给指定模型
std::string LLMManager::sendMessageStream(const std::string& modelName, const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam, std::function<void(const std::string&, bool)>& callback){
// 检测模型是否注册
auto it = _providers.find(modelName);
if(it == _providers.end()){
ERROR("model provider not found, modelName = {}", modelName);
return "";
}
// 检测模型是否可用
if(!it->second->isAvailable()){
ERROR("model not available, modelName = {}", modelName);
return "";
}
// 模型已经注册,并且已经初始化成功
return it->second->sendMessageStream(messages, requestParam, callback);
}
}
7. 不接入DataManager的SessionManager
这里的SessionManager即会话管理,这里的会话就是大家每次用ai的时候这个对话用完想要新建一个对话的对话(这里表述好像有点小绕),看图片一目了然如图:就这个东西。

所有的一切都是围绕这个展开的,具体包括会话的获取、创建、删除、更新时间戳、清空以及增加消息。
所谓不接入DataManager的SessionManager就是没接入数据库,所有操作都是在内存中进行的,后续我们写完DataManager之后再完善这个类。
具体实现就不多介绍了,都很简单,也没啥陌生的语法。
全部代码:
cpp
#pragma once
#include <atomic>
#include <unordered_map>
#include <mutex>
#include <memory>
#include <map>
#include "DataManager.hpp"
#include "common.hpp"
namespace ai_chat_sdk {
class SessionManager {
public:
static SessionManager& getInstance() {
static SessionManager instance;
return instance;
}
std::string createSession(const std::string& modelName);
std::shared_ptr<Session> getSession(const std::string& sessionId);
bool addMessage(const std::string& sessionId, const Message& message);
std::vector<Message> getHistroyMessages(const std::string& sessionId) const;
std::vector<std::string> getSessionLists() const;
bool deleteSession(const std::string& sessionId);
void updateSessionTimestamp(const std::string& sessionId);
void clearAllSessions();
size_t getSessionCount() const;
private:
SessionManager() = default;
SessionManager(const SessionManager&) = delete;
SessionManager &operator=(const SessionManager &) = delete;
// ⽣成唯⼀会话id 返回会话id
std::string generateSessionId();
// ⽣成唯⼀消息id 返回消息id
std::string generateMessageId();
private:
std::unordered_map<std::string, std::shared_ptr<Session>> _sessions;
mutable std::mutex _mutex; // 在const成员函数中,可能会修改所的状态,因此需要
static std::atomic<int64_t> _message_counter; // 记录所有会话中消息总数
static std::atomic<int64_t> _session_counter; // 记录会话总数
};
} // end ai_chat_sdk
cpp
#include "../include/SessionManager.hpp"
#include "../include/util/myLog.hpp"
#include <iomanip>
#include <sstream>
namespace ai_chat_sdk
{
std::atomic<int64_t> SessionManager::_message_counter{0};
std::atomic<int64_t> SessionManager::_session_counter{0};
std::string SessionManager::createSession(const std::string &modelName){
std::unique_lock<std::mutex> lock(_mutex);
std::string sessionId = generateSessionId();
auto session = std::make_shared<Session>(modelName);
session->_sessionId = sessionId;
_sessions[sessionId] = session;
INFO("create session, session_id: {}, model_name: {}", sessionId,modelName);
return sessionId;
}
std::shared_ptr<Session> SessionManager::getSession(const std::string &sessionId){
std::unique_lock<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
if (it != _sessions.end()) {
return it->second;
}
return nullptr;
}
bool SessionManager::addMessage(const std::string &sessionId, const Message &message){
std::unique_lock<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
if (it != _sessions.end()) {
Message msg = message;
msg._messageId = generateMessageId();
msg._timestamp = std::time(nullptr);
it->second->_messages.push_back(msg);
it->second->_updateTime = std::time(nullptr);
INFO("add message, session_id: {}, message_id: {}", sessionId, msg._messageId);
return true;
}
return false;
}
std::vector<Message> SessionManager::getHistroyMessages(const std::string &sessionId) const{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
if (it != _sessions.end()) {
return it->second->_messages;
}
return {};
}
std::vector<std::string> SessionManager::getSessionLists() const
{
std::unique_lock<std::mutex> lock(_mutex);
std::vector<std::pair<std::time_t, std::shared_ptr<const Session>>> temp;
temp.reserve(_sessions.size());
// 填充临时会话
for (const auto &pair : _sessions)
{
const auto &sessionPtr = pair.second;
temp.emplace_back(sessionPtr->_updateTime, sessionPtr);
}
// 按更新时间排序, > 排降序
std::sort(temp.begin(), temp.end(), [](const auto &a, const auto &b)
{ return a.first > b.first; });
// 构造返回列表
std::vector<std::string> session_ids;
session_ids.reserve(temp.size());
for (const auto &item : temp)
{
session_ids.push_back(item.second->_sessionId);
}
return session_ids;
}
bool SessionManager::deleteSession(const std::string &sessionId)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
if (it != _sessions.end()) {
_sessions.erase(it);
INFO("delete session, session_id: {}", sessionId);
return true;
}
INFO("session {} not found for delete", sessionId);
return false;
}
void SessionManager::updateSessionTimestamp(const std::string &sessionId){
std::unique_lock<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
if (it != _sessions.end()) {
it->second->_updateTime = std::time(nullptr);
INFO("update session timestamp, session_id: {}", sessionId);
}
}
void SessionManager::clearAllSessions(){
std::unique_lock<std::mutex> lock(_mutex);
_sessions.clear();
INFO("clear all sessions");
}
size_t SessionManager::getSessionCount() const{
std::unique_lock<std::mutex> lock(_mutex);
return _sessions.size();
}
// ⽣成唯⼀会话id 返回会话id
std::string SessionManager::generateSessionId()
{
// 会话计数⾃增
_session_counter.fetch_add(1);
std::time_t time = std::time(nullptr);
// 会话id格式:session_时间戳_会话计数
std::ostringstream os;
os << "session_" << time << "_" << std::setfill('0') << std::setw(8) << _session_counter;
return os.str();
}
// ⽣成唯⼀消息id 返回消息id
std::string SessionManager::generateMessageId()
{
// 消息计数⾃增
_message_counter.fetch_add(1);
std::time_t time = std::time(nullptr);
// 消息id格式:msg_时间戳_消息计数
std::ostringstream os;
os << "msg_" << time << "_" << std::setfill('0') << std::setw(8) << _message_counter;
return os.str();
}
} // end ai_chat_sdk
8. DataManager类
DataManager类是基于sqlite3原生C接口 实现的 会话+消息 数据库持久化管理类,是AI聊天SDK的本地数据层,负责会话、聊天消息的增删改查。
成员变量:
- 私有成员:
sqlite3* _db(数据库句柄)、std::string _dbName(数据库文件路径)、mutable std::mutex _mutex(线程安全锁,所有操作加锁,支持多线程调用) - 构造函数:打开指定sqlite数据库文件,调用
initDataBase()初始化表结构;析构函数安全关闭数据库句柄。
数据库表设计:
- sessions 会话表 :主键
session_id,存储会话ID、模型名、创建时间、更新时间; - messages 消息表 :主键
message_id,外键session_id关联会话表,存储消息ID、角色(role)、消息内容、时间戳; - 外键特性:
ON DELETE CASCADE级联删除 → 删除会话时,关联的所有消息自动删除,无需手动删消息。
工具函数:
initDataBase():创建会话/消息两张表(CREATE TABLE IF NOT EXISTS幂等创建,避免重复建表报错);executeSQL():执行无返回值的原生SQL(建表/清空表等),封装sqlite3_exec,包含错误日志与内存释放。
封装会话全量CRUD:插入会话、查询单个会话(自动加载关联消息)、更新会话时间戳、删除会话、查询所有会话ID/会话列表、统计会话数、清空所有会话;
封装消息全量CRUD:插入消息(插入时自动更新会话最新时间戳)、查询指定会话的所有消息、删除指定会话的消息;
再细的具体实现大家就直接看代码吧,也不难。
全部代码:
cpp
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <mutex>
#include <sqlite3.h>
#include "common.hpp"
namespace ai_chat_sdk
{
class DataManager
{
public:
DataManager(const std::string &dbName);
~DataManager();
// Session相关操作
// 插入新会话
bool insertSession(const Session &session);
// 获取指定会话信息
std::shared_ptr<Session> getSession(const std::string &sessionId) const;
// 更新指定会话的时间戳
bool updateSessionTimestamp(const std::string &sessionId, std::time_t timestamp);
// 删除指定会话--注意:删除会话时,也需要删除该会话中管理的所有的消息
bool deleteSession(const std::string &sessionId);
// 获取所有会话id
std::vector<std::string> getAllSessionIds() const;
// 获取所有会话信息
std::vector<std::shared_ptr<Session>> getAllSessions() const;
// 删除所有会话
bool clearAllSessions();
// 获取会话总数
size_t getSessionCount() const;
////////////////////////////////////////////////////////////////////
// Message相关操作
// 插入新消息--注意:插入消息时,需要更新会话的时间戳
bool insertMessage(const std::string &sessionId, const Message &message);
// 获取指定会话的历史消息
std::vector<Message> getSessionMessages(const std::string &sessionId) const;
// 删除指定会话的所有消息
bool deleteSessionMessages(const std::string &sessionId);
private:
// 初始化数据库 -- 创建数据库表
bool initDataBase();
// 执行SQL语句的工具函数
bool executeSQL(const std::string &sql);
private:
sqlite3 *_db;
std::string _dbName;
mutable std::mutex _mutex;
};
} // end ai_chat_sdk
cpp
#include "../include/DataManager.hpp"
#include "../include/util/myLog.hpp"
namespace ai_chat_sdk
{
DataManager::DataManager(const std::string &dbName)
: _dbName(dbName), _db(nullptr)
{
// 创建并打开数据库
int rc = sqlite3_open(dbName.c_str(), &_db);
if (rc != SQLITE_OK)
{
ERROR("打开数据库失败:{}", sqlite3_errmsg(_db));
}
INFO("打开数据库成功:{}", dbName);
// 初始化数据库表 - 创建会话表和消息表
if (!initDataBase())
{
sqlite3_close(_db);
_db = nullptr;
ERROR("初始化数据库表失败");
}
}
DataManager::~DataManager()
{
if (_db)
{
sqlite3_close(_db);
}
}
// 初始化数据库
bool DataManager::initDataBase()
{
// 创建会话表
std::string createSessionTable = R"(
CREATE TABLE IF NOT EXISTS sessions (
session_id TEXT PRIMARY KEY,
model_name TEXT NOT NULL,
create_time INTEGER NOT NULL,
update_time INTEGER NOT NULL
);
)";
// 执行创建Sessions表的SQL语句
if (!executeSQL(createSessionTable))
{
return false;
}
// 创建消息表
std::string createMessageTable = R"(
CREATE TABLE IF NOT EXISTS messages (
message_id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
timestamp INTEGER NOT NULL,
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
);
)";
// 执行创建Messages表的SQL语句
if (!executeSQL(createMessageTable))
{
return false;
}
return true;
}
bool DataManager::executeSQL(const std::string &sql)
{
if (!_db)
{
ERROR("数据库未初始化");
return false;
}
char *errMsg = nullptr;
int rc = sqlite3_exec(_db, sql.c_str(), nullptr, nullptr, &errMsg);
if (rc != SQLITE_OK)
{
ERROR("执行SQL语句失败: {}", errMsg);
sqlite3_free(errMsg);
return false;
}
return true;
}
// 插入会话
bool DataManager::insertSession(const Session &session)
{
std::lock_guard<std::mutex> lock(_mutex);
// 构建SQL语句
std::string insertSQL = R"(
INSERT INTO sessions (session_id, model_name, create_time, update_time)
VALUES (?, ?, ?, ?);
)";
// 准备SQL语句
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(_db, insertSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("insertSession - 准备语句失败:{}", sqlite3_errmsg(_db));
return false;
}
// 绑定参数
sqlite3_bind_text(stmt, 1, session._sessionId.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, session._modelName.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(stmt, 3, static_cast<int64_t>(session._createTime));
sqlite3_bind_int64(stmt, 4, static_cast<int64_t>(session._updateTime));
// 执行SQL语句
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
ERROR("insertSession - 执行语句失败:{}", sqlite3_errmsg(_db));
sqlite3_finalize(stmt);
return false;
}
// 释放语句
sqlite3_finalize(stmt);
INFO("insertSession - 插入会话成功:{}", session._sessionId);
return true;
}
// 获取指定sessionId的会话信息
std::shared_ptr<Session> DataManager::getSession(const std::string &sessionId) const
{
std::lock_guard<std::mutex> lock(_mutex);
// 构建SQL语句
std::string selectSQL = R"(
SELECT model_name, create_time, update_time FROM sessions WHERE session_id = ?;
)";
// 准备SQL语句
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(_db, selectSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("getSession - 准备语句失败:{}", sqlite3_errmsg(_db));
return nullptr;
}
// 绑定参数
sqlite3_bind_text(stmt, 1, sessionId.c_str(), -1, SQLITE_TRANSIENT);
// 执行SQL语句
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
ERROR("getSession - 执行语句失败:{}", sqlite3_errmsg(_db));
sqlite3_finalize(stmt);
return nullptr;
}
// 从结果集中提取数据
std::string modelName = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 0));
int64_t createTime = sqlite3_column_int64(stmt, 1);
int64_t updateTime = sqlite3_column_int64(stmt, 2);
// 创建会话对象
auto session = std::make_shared<Session>(modelName);
session->_sessionId = sessionId;
session->_createTime = static_cast<std::time_t>(createTime);
session->_updateTime = static_cast<std::time_t>(updateTime);
// 释放语句
sqlite3_finalize(stmt);
INFO("getSession - 获取会话成功:{}", sessionId);
// 获取该会话的所有消息
session->_messages = getSessionMessages(sessionId);
return session;
}
// 更新指定会话的时间戳
bool DataManager::updateSessionTimestamp(const std::string &sessionId, std::time_t timestamp)
{
std::lock_guard<std::mutex> lock(_mutex);
// 构建SQL语句
std::string updateSQL = R"(
UPDATE sessions SET update_time = ? WHERE session_id = ?;
)";
// 准备SQL语句
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(_db, updateSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("updateSessionTimestamp - 准备语句失败:{}", sqlite3_errmsg(_db));
return false;
}
// 绑定参数
sqlite3_bind_int64(stmt, 1, static_cast<int64_t>(timestamp));
sqlite3_bind_text(stmt, 2, sessionId.c_str(), -1, SQLITE_TRANSIENT);
// 执行SQL语句
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
ERROR("updateSessionTimestamp - 执行语句失败:{}", sqlite3_errmsg(_db));
sqlite3_finalize(stmt);
return false;
}
// 释放语句
sqlite3_finalize(stmt);
INFO("updateSessionTimestamp - 更新会话时间戳成功:{}", sessionId);
return true;
}
// 删除指定会话
bool DataManager::deleteSession(const std::string &sessionId)
{
std::lock_guard<std::mutex> lock(_mutex);
// 构建SQL语句
std::string deleteSQL = R"(
DELETE FROM sessions WHERE session_id = ?;
)";
// 准备SQL语句
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(_db, deleteSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("deleteSession - 准备语句失败:{}", sqlite3_errmsg(_db));
return false;
}
// 绑定参数
sqlite3_bind_text(stmt, 1, sessionId.c_str(), -1, SQLITE_TRANSIENT);
// 执行SQL语句
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
ERROR("deleteSession - 执行语句失败:{}", sqlite3_errmsg(_db));
sqlite3_finalize(stmt);
return false;
}
// 释放语句
sqlite3_finalize(stmt);
INFO("deleteSession - 删除会话成功:{}", sessionId);
return true;
}
// 获取所有会话id
std::vector<std::string> DataManager::getAllSessionIds() const
{
std::lock_guard<std::mutex> lock(_mutex);
// 构建SQL语句
std::string selectSQL = R"(
SELECT session_id FROM sessions ORDER BY update_time DESC;
)";
// 准备SQL语句
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(_db, selectSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("getAllSessionIds - 准备语句失败:{}", sqlite3_errmsg(_db));
return {};
}
std::vector<std::string> sessionIds;
while (sqlite3_step(stmt) == SQLITE_ROW)
{
std::string sessionId = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 0));
sessionIds.push_back(sessionId);
}
// 释放语句
sqlite3_finalize(stmt);
INFO("getAllSessionIds - 获取所有会话id成功, 会话总数:{}", sessionIds.size());
return sessionIds;
}
// 获取所有session信息,并按照更新时间降序排列
std::vector<std::shared_ptr<Session>> DataManager::getAllSessions() const
{
std::lock_guard<std::mutex> lock(_mutex);
// 构建SQL语句
std::string selectSQL = R"(
SELECT session_id, model_name, create_time, update_time FROM sessions ORDER BY update_time DESC;
)";
// 准备SQL语句
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(_db, selectSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("getAllSessionIds - 准备语句失败:{}", sqlite3_errmsg(_db));
return {};
}
std::vector<std::shared_ptr<Session>> sessions;
while (sqlite3_step(stmt) == SQLITE_ROW)
{
std::string sessionId = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 0));
std::string modelName = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 1));
int64_t createTime = sqlite3_column_int64(stmt, 2);
int64_t updateTime = sqlite3_column_int64(stmt, 3);
auto session = std::make_shared<Session>(modelName);
session->_sessionId = sessionId;
session->_createTime = static_cast<std::time_t>(createTime);
session->_updateTime = static_cast<std::time_t>(updateTime);
sessions.push_back(session);
// 历史消息暂时不获取,需要时再通过会话id来进行获取
}
// 释放语句
sqlite3_finalize(stmt);
INFO("getAllSessions - 获取所有会话信息成功, 会话总数:{}", sessions.size());
return sessions;
}
// 获取会话总数
size_t DataManager::getSessionCount() const
{
std::lock_guard<std::mutex> lock(_mutex);
// 准备SQL语句
std::string selectSQL = R"(
SELECT COUNT(*) FROM sessions;
)";
// 准备SQL语句
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(_db, selectSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("getSessionCount - 准备语句失败:{}", sqlite3_errmsg(_db));
return 0;
}
// 执行SQL语句
rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW)
{
ERROR("getSessionCount - 执行语句失败:{}", sqlite3_errmsg(_db));
sqlite3_finalize(stmt);
return 0;
}
// 获取会话总数
size_t count = sqlite3_column_int64(stmt, 0);
// 释放语句
sqlite3_finalize(stmt);
INFO("getSessionCount - 获取会话总数成功:{}", count);
return count;
}
// 删除所有会话
bool DataManager::clearAllSessions()
{
std::lock_guard<std::mutex> lock(_mutex);
// 构建SQL语句
std::string deleteSQL = R"(
DELETE FROM sessions;
)";
// 准备SQL语句
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(_db, deleteSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("clearAllSessions - 准备语句失败:{}", sqlite3_errmsg(_db));
return false;
}
// 执行SQL语句
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
ERROR("clearAllSessions - 执行语句失败:{}", sqlite3_errmsg(_db));
sqlite3_finalize(stmt);
return false;
}
// 释放语句
sqlite3_finalize(stmt);
INFO("clearAllSessions - 删除所有会话成功");
return true;
}
/////////////////////////////////////////////////////////////Messages///////////////////////////////////////
// 插入新消息--注意:插入消息时,需要更新会话的时间戳
bool DataManager::insertMessage(const std::string &sessionId, const Message &message)
{
std::lock_guard<std::mutex> lock(_mutex);
// 构建SQL语句
std::string insertSQL = R"(
INSERT INTO messages (message_id, session_id, role, content, timestamp)
VALUES (?, ?, ?, ?, ?);
)";
// 准备SQL语句
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(_db, insertSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("insertMessage - 准备语句失败:{}", sqlite3_errmsg(_db));
return false;
}
// 绑定参数
sqlite3_bind_text(stmt, 1, message._messageId.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, sessionId.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, message._role.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 4, message._content.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(stmt, 5, static_cast<int64_t>(message._timestamp));
// 执行SQL语句
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
ERROR("insertMessage - 执行语句失败:{}", sqlite3_errmsg(_db));
sqlite3_finalize(stmt);
return false;
}
// 同时更新session的update_time
std::string updateSQL = R"(
UPDATE sessions SET update_time = ? WHERE session_id = ?;
)";
// 准备SQL语句
sqlite3_stmt *updateStmt;
rc = sqlite3_prepare_v2(_db, updateSQL.c_str(), -1, &updateStmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("insertMessage - 准备语句失败:{}", sqlite3_errmsg(_db));
sqlite3_finalize(stmt);
return false;
}
// 绑定参数
sqlite3_bind_int64(updateStmt, 1, static_cast<int64_t>(message._timestamp));
sqlite3_bind_text(updateStmt, 2, sessionId.c_str(), -1, SQLITE_TRANSIENT);
// 执行SQL语句
rc = sqlite3_step(updateStmt);
if (rc != SQLITE_DONE)
{
ERROR("insertMessage - 执行语句失败:{}", sqlite3_errmsg(_db));
sqlite3_finalize(updateStmt);
return false;
}
// 释放语句
sqlite3_finalize(stmt);
sqlite3_finalize(updateStmt);
INFO("insertMessage - 插入消息成功:{}", message._messageId);
return true;
}
// 获取会话中的所有消息
std::vector<Message> DataManager::getSessionMessages(const std::string &sessionId) const
{
std::lock_guard<std::mutex> lock(_mutex);
// 准备SQL语句
std::string selectSQL = R"(
SELECT message_id, role, content, timestamp FROM messages WHERE session_id = ?;
)";
// 准备SQL语句
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(_db, selectSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("getSessionMessages - 准备语句失败:{}", sqlite3_errmsg(_db));
return {};
}
// 绑定参数
sqlite3_bind_text(stmt, 1, sessionId.c_str(), -1, SQLITE_TRANSIENT);
// 执行SQL语句
std::vector<Message> messages;
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
{
Message message;
message._messageId = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 0));
message._role = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 1));
message._content = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 2));
message._timestamp = static_cast<std::time_t>(sqlite3_column_int64(stmt, 3));
messages.push_back(message);
}
if (rc != SQLITE_DONE)
{
ERROR("getSessionMessages - 执行语句失败:{}", sqlite3_errmsg(_db));
sqlite3_finalize(stmt);
return {};
}
// 释放语句
sqlite3_finalize(stmt);
return messages;
}
// 删除制定会话的历史消息
bool DataManager::deleteSessionMessages(const std::string &sessionId)
{
std::lock_guard<std::mutex> lock(_mutex);
// 构建SQL语句
std::string deleteSQL = R"(
DELETE FROM messages WHERE session_id = ?;
)";
// 准备SQL语句
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(_db, deleteSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
ERROR("deleteSessionMessages - 准备语句失败:{}", sqlite3_errmsg(_db));
return false;
}
// 绑定参数
sqlite3_bind_text(stmt, 1, sessionId.c_str(), -1, SQLITE_TRANSIENT);
// 执行SQL语句
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
ERROR("deleteSessionMessages - 执行语句失败:{}", sqlite3_errmsg(_db));
sqlite3_finalize(stmt);
return false;
}
// 释放语句
sqlite3_finalize(stmt);
INFO("deleteSessionMessages - 删除会话消息成功:{}", sessionId);
return true;
}
} // namespace ai_chat_sdk
9. 接入DataManager的SessionManager
就是SessionManager加一个DataManager类型的成员变量,在执行完会话的相关操作之后,再继续执行封装好的数据库操作进行持久化即可。其实就是每个成员函数后面再添几行代码就行。
如下:
SessionManager.cpp - 构造函数
cpp
SessionManager::SessionManager()
: _dataManager("chatDB.db")
{
// 获取所有会话
auto sessions = _dataManager.getAllSessions();
for(auto& session : sessions){
_sessions[session->session_id] = session;
}
INFO("SessionManager init, session count: {}", _sessions.size());
}
SessionManager.cpp - 创建会话 代码块
cpp
// 创建会话
std::string SessionManager::createSession(const std::string& model_name){
std::string session_id;
std::shared_ptr<Session> session;
_mutex.lock();
// ⽣成会话id
//...
_mutex.unlock();
// 插⼊会话到数据库
_dataManager.insertSession(*session);
return session_id;
}
SessionManager.cpp - 获取会话 代码块
cpp
// 获取会话
std::shared_ptr<Session> SessionManager::getSession(const std::string& session_id){
// 先快速检查内存中是否存在
_mutex.lock();
auto it = _sessions.find(session_id);
if(it != _sessions.end()){
_mutex.unlock();
// 获取该会话的消息列表
it->second->messages = _dataManager.getMessagesBySessionId(session_id);
return it->second;
}
_mutex.unlock();
// 内存中没有, 从数据库中查,顺便内容中也存储⼀份
auto sessionPtr = _dataManager.getSession(session_id);
if(sessionPtr){
// 在更新内存前再次加锁,防⽌其他线程已经添加
std::lock_guard<std::mutex> lock(_mutex);
// 再次检查,因为可能在获取锁的过程中,其他线程已经添加了这个session
auto it = _sessions.find(session_id);
if(it == _sessions.end()){
_sessions[session_id] = sessionPtr;
}
// 获取该会话的消息列表
sessionPtr->messages = _dataManager.getMessagesBySessionId(session_id);
return sessionPtr;
}
WARN("session {} not found", session_id);
return nullptr;
}
SessionManager.cpp - 添加消息 代码块
cpp
// 添加消息
bool SessionManager::addMessage(const std::string& session_id, const Message& message){
_mutex.lock();
// 获取sessionId对应的会话
// ...
_mutex.unlock();
// 插⼊消息到数据库
_dataManager.insertMessage(session_id,msg);
return true;
}
SessionManager.cpp - 获取会话历史 代码块
cpp
// 获取会话历史
std::vector<Message> SessionManager::getSessionHistory(const std::string& session_id){
_mutex.lock();
// 先从内存中获取,如果内存中获取不到,再到数据库获取
// ...
_mutex.unlock();
// 从数据库中获取消息
return _dataManager.getMessagesBySessionId(session_id);
}
SessionManager.cpp - 获取会话列表 代码块
cpp
// 获取会话列表, 包含sessionId和modelName
std::vector<std::string> SessionManager::getSessionList()const{
// 先从数据库获取
auto sessions = _dataManager.getAllSessions();
std::lock_guard<std::mutex> lock(_mutex);
std::vector<std::pair<std::time_t, std::shared_ptr<const Session>>> temp;
temp.reserve(_sessions.size());
// 填充临时会话
for(const auto& pair : _sessions){
const auto& session_ptr = pair.second;
temp.emplace_back(session_ptr->update_time, session_ptr);
}
// 合并数据库会话和内存会话
for(const auto& session : sessions){
if(_sessions.find(session->session_id) == _sessions.end()){
temp.emplace_back(session->update_time, session);
}
}
// 按更新时间排序, > 排降序
// ...
}
SessionManager.cpp - 删除会话 代码块
cpp
// 删除会话
bool SessionManager::deleteSession(const std::string& session_id){
_mutex.lock();
// 先删除内存中会话信息,然后删除数据库
// ...
_mutex.unlock();
// 从数据库中删除会话以及该会话对应的消息列表
_dataManager.deleteSession(session_id);
return true;
}
SessionManager.cpp - 更新会话时间戳 代码块
cpp
void SessionManager::updateSessionTimestamp(const std::string& session_id){
_mutex.lock();
// 先更新内容中指定会话的时间戳,然后同步到数据库
// ...
_mutex.unlock();
// 更新数据库中的会话时间戳
_dataManager.updateSessionTimestamp(session_id, it->second->update_time);
}
SessionManager.cpp - 获取会话总数 代码块
cpp
// 获取会话总数
size_t SessionManager::getSessionCount()const{
return _dataManager.getSessionCount();
}
完整代码:
cpp
#include "../include/SessionManager.hpp"
#include "../include/util/myLog.hpp"
#include <iomanip>
#include <sstream>
namespace ai_chat_sdk
{
std::atomic<int64_t> SessionManager::_message_counter{0};
std::atomic<int64_t> SessionManager::_session_counter{0};
SessionManager::SessionManager() : _dataManager("chatDB.db")
{
auto sessions = _dataManager.getAllSessions();
for (auto &session : sessions)
{
_sessions[session->_sessionId] = session;
}
INFO("SessionManager init, session count: {}", _sessions.size());
};
std::string SessionManager::createSession(const std::string &modelName){
std::unique_lock<std::mutex> lock(_mutex);
std::string sessionId = generateSessionId();
auto session = std::make_shared<Session>(modelName);
session->_sessionId = sessionId;
_sessions[sessionId] = session;
INFO("create session, session_id: {}, model_name: {}", sessionId,modelName);
_dataManager.insertSession(*session);
return sessionId;
}
std::shared_ptr<Session> SessionManager::getSession(const std::string &sessionId)
{
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
if (it != _sessions.end())
{
it->second->_messages = _dataManager.getSessionMessages(sessionId);
return it->second;
}
}
auto sessionPtr = _dataManager.getSession(sessionId);
if (sessionPtr) {
std::unique_lock<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
if (it == _sessions.end()) {
_sessions[sessionId] = sessionPtr;
}
sessionPtr->_messages = _dataManager.getSessionMessages(sessionId);
return sessionPtr;
}
WARN("session {} not found", sessionId);
return nullptr;
}
bool SessionManager::addMessage(const std::string &sessionId, const Message &message){
std::unique_lock<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
if (it != _sessions.end()) {
Message msg = message;
msg._messageId = generateMessageId();
msg._timestamp = std::time(nullptr);
it->second->_messages.push_back(msg);
it->second->_updateTime = std::time(nullptr);
_dataManager.insertMessage(sessionId, msg);
INFO("add message, session_id: {}, message_id: {}", sessionId, msg._messageId);
return true;
}
return false;
}
std::vector<Message> SessionManager::getHistroyMessages(const std::string &sessionId) const{
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
if (it != _sessions.end())
{
return it->second->_messages;
}
}
return _dataManager.getSessionMessages(sessionId);
}
// 获取所有会话列表
std::vector<std::string> SessionManager::getSessionLists() const{
auto sessions = _dataManager.getAllSessions();
std::unique_lock<std::mutex> lock(_mutex);
// 构建一个临时对话列表,将其内容的会话按照会话更新的时间戳降序排列
std::vector<std::pair<std::time_t, std::shared_ptr<Session>>> temp;
temp.reserve(_sessions.size());
// 将会话添加到临时列表中
for (const auto &pair : _sessions)
{
temp.emplace_back(pair.second->_updateTime, pair.second);
}
// 将数据库中的会话添加到临时列表中
for (const auto &session : sessions)
{
if (_sessions.find(session->_sessionId) == _sessions.end())
{
temp.emplace_back(session->_updateTime, session);
}
}
// 按照会话更新时间戳降序排序
std::sort(temp.begin(), temp.end(), [](const auto &a, const auto &b)
{ return a.first > b.first; });
std::vector<std::string> sessionIds;
sessionIds.reserve(_sessions.size());
// 从临时列表中提取会话id
for (const auto &pair : temp)
{
sessionIds.push_back(pair.second->_sessionId);
}
return sessionIds;
}
bool SessionManager::deleteSession(const std::string &sessionId)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
if (it != _sessions.end()) {
_sessions.erase(it);
_dataManager.deleteSession(sessionId);
INFO("delete session, session_id: {}", sessionId);
return true;
}
INFO("session {} not found for delete", sessionId);
return false;
}
void SessionManager::updateSessionTimestamp(const std::string &sessionId){
std::unique_lock<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
if (it != _sessions.end()) {
it->second->_updateTime = std::time(nullptr);
_dataManager.updateSessionTimestamp(sessionId, it->second->_updateTime);
INFO("update session timestamp, session_id: {}", sessionId);
}
}
void SessionManager::clearAllSessions(){
std::unique_lock<std::mutex> lock(_mutex);
_sessions.clear();
_dataManager.clearAllSessions();
INFO("clear all sessions");
}
size_t SessionManager::getSessionCount() const{
std::unique_lock<std::mutex> lock(_mutex);
return _sessions.size();
}
// ⽣成唯⼀会话id 返回会话id
std::string SessionManager::generateSessionId()
{
// 会话计数⾃增
_session_counter.fetch_add(1);
std::time_t time = std::time(nullptr);
// 会话id格式:session_时间戳_会话计数
std::ostringstream os;
os << "session_" << time << "_" << std::setfill('0') << std::setw(8) << _session_counter;
return os.str();
}
// ⽣成唯⼀消息id 返回消息id
std::string SessionManager::generateMessageId()
{
// 消息计数⾃增
_message_counter.fetch_add(1);
std::time_t time = std::time(nullptr);
// 消息id格式:msg_时间戳_消息计数
std::ostringstream os;
os << "msg_" << time << "_" << std::setfill('0') << std::setw(8) << _message_counter;
return os.str();
}
} // end ai_chat_sdk
10. ChatSDK
到这里其实基本功能就已经做完了,但我们要给它封装成SDK。
那么问题来了,何为SDK?
SDK 核心定义
SDK 是 Software Develop Kit(软件开发工具包) 的缩写,是程序员的「工具箱」,是一套帮开发者方便开发特定功能的工具包。
类比:电脑清灰、加装内存条需要螺丝刀、撬棒等专业工具组成的工具箱;开发者实现特定功能,就需要对应的 SDK 工具包。
举例
- 微信支付SDK:接入后即可在程序中实现微信支付,无需自研支付安全、加密、银行对接等复杂逻辑。
- 相机SDK:接入后APP可直接调用手机摄像头完成拍照、录像功能。
SDK 组成内容
- 库文件:已封装好的现成功能,按需直接调用即可。
- API接口:标准化交互规则,指导开发者与平台/功能进行对接。
- 文档:使用说明书,说明各类功能、接口的使用方式。
- 示例代码:实操参考,参照即可快速跑通功能。
- 调试工具:排查开发、调用过程中的问题。
SDK 核心价值
SDK 是厂商打包好的一套「现成工具+说明书」,让开发者可以更快、更安全、更省心地在自己的软件中实现特定功能,无需从零开发底层逻辑。
SDK 和 API 的区别
| 特性 | API | SDK |
|---|---|---|
| 概念 | 一组规则和定义,用于标准化不同软件的交互方式 | 包含API、库、文档、示例代码等的一个全面的工具包 |
| 优势 | 提供与特定服务或系统交互的接口 | 提供了完整的开发环境和工具 |
| 形式 | 接口文档,比如:RESTful API | 一个可本地下载的完整工具库包 |
| 关系 | API 是 SDK 的一部分 | SDK 包含 API |
| 选择 | 如果需要与某个服务进行交互,但不需要开发完整的应用程序 | 需要开发一个完整的应用程序,且需要一系列工具和资源来支持开发过程 |
通俗类比
做一道菜(开发应用):
- API 就像是菜谱,只告诉你食材和步骤,锅碗瓢盆、刀具等需要自己准备(自研底层代码)。
- SDK 则是「食材配送盒+全套厨具+菜谱」,包含食材(库文件)、菜谱(API文档)、厨具(开发工具)、教程(示例代码),开箱即用。
具体功能与实现:
ChatSDK 是AI聊天能力的统一顶层入口类,封装模型管理、会话管理所有能力,对外提供极简调用接口,屏蔽底层实现细节。
包含以下功能:
- 模型初始化:注册+初始化云端/本地Ollama多类大模型(DeepSeek、GPT、Gemini、Ollama),校验密钥/地址等配置;
- 会话管理:创建会话、查询会话、获取会话列表、删除会话,所有会话操作委托给
SessionManager; - 消息交互:提供同步全量返回 、流式增量返回两种发消息接口,自动拼接会话历史上下文,消息自动落库持久化;
- 基础能力:获取当前可用模型列表。
核心实现逻辑
- 初始化流程:
initModels为入口,先registerAllProvider注册模型提供者,再initProviders按配置初始化云端/Ollama模型,校验参数合法性后完成模型就绪; - 核心依赖:内部持有
LLMManager(模型调度/调用)、SessionManager(会话增删查/消息管理)两个核心管理类,解耦模型与会话能力; - 消息发送流程:校验SDK初始化状态 → 获取会话+历史消息 → 拼接请求参数 → 调用模型生成回复 → 自动保存用户/模型消息、更新会话时间戳;
- 双重消息能力:同步接口一次性返回完整回复,流式接口通过回调函数增量返回内容,满足不同业务场景;
- 全链路校验:所有接口前置判断SDK初始化状态,参数合法性校验完善,异常日志清晰,保证调用安全。
其实就是对SessionManager又封装了一层,大体逻辑几乎一摸一样,不多赘述。
11. ChatSDK测试
以下是测试的cmake、main.cc代码和测试结果,经供参考:
cmake:
cpp
# 设置Cmake的最小版本
cmake_minimum_required(VERSION 3.10)
# 项目名称
project(testLLM)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 设置构建类型Debug
set(CMAKE_BUILD_TYPE Debug)
# 添加可执行文件
add_executable(testLLM testLLM.cc
../sdk/src/util/myLog.cc
../sdk/src/ChatSDK.cc
../sdk/src/SessionManager.cc
../sdk/src/DataManager.cc
../sdk/src/DeepSeekProvider.cc
../sdk/src/LLMManager.cc
)
# 设置输出目录
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
# 添加头文件搜索路劲
include_directories(${CMAKE_SOURCE_DIR}/../sdk/include)
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})
# 添加CPPHTTPLIB_OPENSSL_SUPPORT定义
target_compile_definitions(testLLM PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
# 链接库
target_link_libraries(testLLM pthread jsoncpp fmt spdlog gtest OpenSSL::SSL OpenSSL::Crypto sqlite3)
cpp
#include <gtest/gtest.h>
#include <istream>
#include <memory>
#include <spdlog/common.h>
#include "../sdk/include/DeepSeekProvider.hpp"
#include "../sdk/include/util/myLog.hpp"
#include "../sdk/include/ChatSDK.hpp"
#include <iostream>
#include <string>
#include <vector>
// TEST(DeepSeekProviderTest, sendMessage){
// auto provider = std::make_shared<ai_chat_sdk::DeepSeekProvider>();
// ASSERT_TRUE(provider != nullptr);
// std::map<std::string, std::string> modelParam;
// modelParam["api_key"] = std::getenv("deepseek_apikey");
// modelParam["endpoint"] = "https://api.deepseek.com";
// 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", "你是谁?"});
// // 实例化DeepSeekProvider的对象
// // 调用sendMessage方法
// std::string response = provider->sendMessage(messages, requestParam);
// ASSERT_FALSE(response.empty());
// INFO("fulldata : {}", response);
// // auto writeChunk = [&](const std::string& chunk, bool last){
// // INFO("chunk : {}", chunk);
// // if(last){
// // INFO("[DONE]");
// // }
// // };
// // std::string fullData = provider->sendMessageStream(messages, requestParam, writeChunk);
// // ASSERT_FALSE(fullData.empty());
// // INFO("response : {}", fullData);
// }
TEST(ChatSDKTest, sendMessage){
auto sdk = std::make_shared<ai_chat_sdk::ChatSDK>();
ASSERT_TRUE(sdk != nullptr);
// 配置支持的模型参数:云模型-deepseek-chat gpt-4o-mini gemini-2.0-flash Ollama本地接入deepseek-r1:1.5b
// deepseek-chat
auto deepseekConfig = std::make_shared<ai_chat_sdk::ApiConfig>();
ASSERT_TRUE(deepseekConfig != nullptr);
deepseekConfig->_modelName = "deepseek-chat";
deepseekConfig->_apiKey = std::getenv("deepseek_apikey");
ASSERT_FALSE(deepseekConfig->_apiKey.empty());
deepseekConfig->_temperature = 0.7;
deepseekConfig->_maxTokens = 2048;
std::vector<std::shared_ptr<ai_chat_sdk::Config>> modelConfigs = {
deepseekConfig
};
sdk->initModels(modelConfigs);
// 创建会话
auto sessionId = sdk->createSession(deepseekConfig->_modelName);
ASSERT_FALSE(sessionId.empty());
std::string message;
std::cout<<">>> ";
std::getline(std::cin, message);
auto response = sdk->sendMessage(sessionId, message);
ASSERT_FALSE(response.empty());
std::cout<<">>> ";
std::getline(std::cin, message);
sdk->sendMessage(sessionId, message);
ASSERT_FALSE(response.empty());
// 获取会话历史消息
auto messages = sdk->_sessionManager.getHistroyMessages(sessionId);
for(const auto& msg : messages){
std::cout<<msg._role<<": "<<msg._content<<std::endl;
}
ASSERT_FALSE(messages.empty());
}
int main(int argc, char **argv) {
lg::Logger::initLogger("testLLM", "stdout", spdlog::level::debug);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
测试结果:

至此服务端全部结束,想要接入其他模型的可以继续写一下Provider类就行。
12. ChatServer类与前端
ChatServer类就是充当前后端交互的媒介,在这个类中直接写好对应的http请求与响应格式,然后注册对应回调函数即可。
聊天Demo核心功能
- 获取会话列表
- 获取支持的模型
- 新建会话
- 发送消息(默认流式返回)
- 获取会话历史
- 删除会话
接口列表(按功能分类)
1. 获取会话列表
-
请求URL:
GET /api/sessions -
响应(200 OK):
字段名 类型 说明 success bool 是否成功 message string 结果描述 data array 会话数据列表 data.id string 会话id data.model string 模型名称 data.created_at int64_t 创建时间戳 data.updated_at int64_t 更新时间戳 data.message_count int 对话次数 data.first_user_message string 第一条用户消息
2. 获取可用模型
-
请求URL:
GET /api/models -
响应(200 OK):
字段名 类型 说明 success bool 是否成功 message string 结果描述 data array 模型列表 data.name string 模型名称 data.desc string 模型描述
3. 创建新会话
-
请求URL:
POST /api/session -
请求参数:
字段名 类型 说明 model string 模型名称 -
响应(200 OK):
字段名 类型 说明 success bool 是否成功 message string 结果描述 data object 会话数据 data.session_id string 会话id data.model string 模型名称
4. 获取会话历史
-
请求URL:
GET /api/session/${session_id}/history -
响应(200 OK):
字段名 类型 说明 success bool 是否成功 message string 结果描述 data array 消息列表 data.id string 消息id data.role string 消息类型 data.content string 消息正文 data.timestamp int64_t 消息时间戳
5. 发送消息-全量返回
-
请求URL:
POST /api/message -
请求参数:
字段名 类型 说明 session_id string 会话id message string 消息内容 -
响应(200 OK):
字段名 类型 说明 success bool 是否成功 message string 结果描述 data object 响应数据 data.session_id string 会话id data.response string 模型返回内容
6. 发送消息-流式响应
-
请求URL:
POST /api/message/async -
请求参数:
字段名 类型 说明 session_id string 会话id message string 消息内容 -
响应(200 OK):
流式返回格式(SSE):data: 正文 data: 正文 ... data: [DONE]
7. 删除会话
-
请求URL:
DELETE /api/session/${session_id} -
响应(200 OK):
字段名 类型 说明 success bool 是否成功 message string 结果描述
具体实现就不多赘述了,就是按着协议格式去拼凑请求和解析响应返回给前端即可。
完整代码:
cpp
#pragma once
#include <httplib.h>
#include <memory>
#include <ai_chat_sdk/ChatSDK.hpp>
namespace ai_chat_server{
// 服务器配置信息
struct ServerConfig{
std::string host = "0.0.0.0"; // 服务器绑定ip
int port = 8080; // 服务器绑定端口
std::string logLevel = "INFO"; // 日志级别
// 模型需要的配置信息
double temperature = 0.7; // 温度参数
int maxTokens = 1024; // 最大token数
// API Key
std::string deepseekAPIKey; // deepseek API Key
std::string geminiAPIKey; // gemini API Key
std::string chatGPTAPIKey; // chatGPT API Key
// Ollama
std::string ollamaModelName; // Ollama模型名称
std::string ollamaModelDesc; // Ollama模型描述
std::string ollamaEndpoint; // Ollama API 地址
};
class ChatServer{
public:
ChatServer(const ServerConfig& config);
bool start(); // 启动服务器
void stop(); // 停止服务器
bool isRunning()const; // 是否正在运行
private:
// 构造响应
std::string buildResponse(const std::string& message, bool success = false);
// 处理创建会话请求
void handleCreateSessionRequest(const httplib::Request& request, httplib::Response& response);
// 处理获取会话列表请求
void handleGetSessionListsRequest(const httplib::Request& request, httplib::Response& response);
// 处理获取模型列表请求
void handleGetModelListsRequest(const httplib::Request& request, httplib::Response& response);
// 处理删除会话请求
void handleDeleteSessionRequest(const httplib::Request& request, httplib::Response& response);
// 处理获取历史消息请求
void handleGetHistoryMessagesRequest(const httplib::Request& request, httplib::Response& response);
// 处理发送消息请求-全量返回
void handleSendMessageRequest(const httplib::Request& request, httplib::Response& response);
// 处理发送消息请求-增量返回
void handleSendMessageStreamRequest(const httplib::Request& request, httplib::Response& response);
// 设置HTTP路由规则
void setHttpRoutes();
private:
ServerConfig _config; // 服务器配置信息
std::unique_ptr<httplib::Server> _chatServer = nullptr; // HTTP服务器
std::shared_ptr<ai_chat_sdk::ChatSDK> _chatSDK = nullptr; // 聊天SDK
std::atomic<bool> _isRunning = {false}; // 是否正在运行
};
} // end ai_chat_server
cpp
#include "ChatServer.hpp"
#include <ai_chat_sdk/util/myLog.hpp>
#include <httplib.h>
#include <jsoncpp/json/forwards.h>
#include <jsoncpp/json/value.h>
#include <jsoncpp/json/reader.h>
#include <jsoncpp/json/writer.h>
namespace ai_chat_server
{
ChatServer::ChatServer(const ServerConfig &config)
{
_chatSDK = std::make_shared<ai_chat_sdk::ChatSDK>();
std::vector<std::shared_ptr<ai_chat_sdk::Config>> modelConfigs;
if (!config.deepseekAPIKey.empty()) {
auto deepseekConfig = std::make_shared<ai_chat_sdk::ApiConfig>();
deepseekConfig->_modelName = "deepseek-chat";
deepseekConfig->_apiKey = config.deepseekAPIKey;
deepseekConfig->_temperature = config.temperature;
deepseekConfig->_maxTokens = config.maxTokens;
modelConfigs.push_back(deepseekConfig);
}
if (!config.ollamaModelName.empty()) {
auto ollamaConfig = std::make_shared<ai_chat_sdk::OllamaConfig>();
ollamaConfig->_modelName = config.ollamaModelName;
ollamaConfig->Config::_modelName = config.ollamaModelName;
ollamaConfig->_modelDesc = config.ollamaModelDesc;
ollamaConfig->_endpoint = config.ollamaEndpoint;
ollamaConfig->_temperature = config.temperature;
ollamaConfig->_maxTokens = config.maxTokens;
modelConfigs.push_back(ollamaConfig);
}
INFO("start init ChatSDK models...");
if (!_chatSDK->initModels(modelConfigs))
{
ERROR("ChatSDK init Failed!!!");
return;
}
INFO("ChatSDK models init success!!!");
// 创建http服务器
_chatServer = std::make_unique<httplib::Server>();
if (!_chatServer)
{
ERROR("ChatServer init Failed!!!");
return;
}
}
bool ChatServer::start()
{
if (_isRunning.load())
{
ERROR("ChatServer is running!!!");
return false;
}
// 设置路由规则
setHttpRoutes();
// 设置静态资源的路径
// 前端页面相关的所有文件都放在www目录下 注意:将来前端页面名称命名为index.html
// 当用户在浏览器中输入:http://ip:port/index.html http://ip:port也能访问index.html页面
// 在httplib中,默认情况下,如果请求路径中只有ip和端口,httplib默认会使用index.html文件
_chatServer->set_mount_point("/", "./www");
// 为了不卡服务器云不卡主线程,服务器在单独的线程中运行
std::thread serverThread([this]()
{
_chatServer->listen(_config.host, _config.port);
INFO("ChatServer start on {} :{}", _config.host, _config.port); });
serverThread.detach();
_isRunning.store(true);
INFO("ChatServer start success!!!");
return true;
}
void ChatServer::stop()
{
if (!_isRunning.load())
{
ERROR("ChatServer is not running!!!");
return;
}
if (_chatServer)
{
_chatServer->stop();
}
_isRunning.store(false);
INFO("ChatServer stop success!!!");
}
bool ChatServer::isRunning() const
{
return _isRunning.load();
}
// 构造响应
std::string ChatServer::buildResponse(const std::string &message, bool success)
{
Json::Value responseJson;
responseJson["success"] = success;
responseJson["message"] = message;
// 序列化
Json::StreamWriterBuilder writerBuilder;
return Json::writeString(writerBuilder, responseJson);
}
// 处理创建会话请求
void ChatServer::handleCreateSessionRequest(const httplib::Request &request, httplib::Response &response)
{
// 获取请求参数,请求参数在请求体
// 通过反序列化拿到请求体的json格式
Json::Value requestJson;
Json::Reader reader;
if (!reader.parse(request.body, requestJson))
{
std::string errorJsonStr = buildResponse("parse request body failed, json format error");
response.status = 400; // 客户端发送的请求有语法错误,服务器无法理解或处理该请求
response.set_content(errorJsonStr, "application/json");
return;
}
// 获取请求参数
std::string modelName = requestJson.get("model", "deepseek-chat").asString();
// 创建会话
std::string sessionID = _chatSDK->createSession(modelName);
if (sessionID.empty())
{
std::string errorJsonStr = buildResponse("create session failed");
response.status = 500; // 服务器内部错误,无法完成请求
response.set_content(errorJsonStr, "application/json");
return;
}
// 构建响应体
Json::Value dataJson;
dataJson["session_id"] = sessionID;
dataJson["model"] = modelName;
Json::Value responseJson;
responseJson["success"] = true;
responseJson["message"] = "create session success";
responseJson["data"] = dataJson;
// 序列化
Json::StreamWriterBuilder writerBuilder;
std::string responseJsonStr = Json::writeString(writerBuilder, responseJson);
response.status = 200; // 成功
response.set_content(responseJsonStr, "application/json");
}
// 处理获取会话列表请求
void ChatServer::handleGetSessionListsRequest(const httplib::Request &request, httplib::Response &response)
{
// 获取会话列表
std::vector<std::string> sessionIDs = _chatSDK->getSessionLists();
// 构建session信息
Json::Value dataArray(Json::arrayValue);
for (const auto &sessionID : sessionIDs)
{
auto session = _chatSDK->getSession(sessionID);
if (session)
{
Json::Value sessionJson;
sessionJson["id"] = session->_sessionId;
sessionJson["model"] = session->_modelName;
sessionJson["created_at"] = static_cast<int64_t>(session->_createTime);
sessionJson["updated_at"] = static_cast<int64_t>(session->_updateTime);
sessionJson["message_count"] = session->_messages.size();
if (!session->_messages.empty())
{
sessionJson["first_user_message"] = session->_messages.front()._content;
}
dataArray.append(sessionJson);
}
}
// 构建响应体
Json::Value responseJson;
responseJson["success"] = true;
responseJson["message"] = "get session lists success";
responseJson["data"] = dataArray;
// 序列化
Json::StreamWriterBuilder writerBuilder;
std::string responseJsonStr = Json::writeString(writerBuilder, responseJson);
response.status = 200; // 成功
response.set_content(responseJsonStr, "application/json");
}
// 处理获取模型列表请求
void ChatServer::handleGetModelListsRequest(const httplib::Request &request, httplib::Response &response)
{
// 获取支持的模型列表
auto modelLists = _chatSDK->getAvailableModels();
// 构建响应体
Json::Value dataArray(Json::arrayValue);
for (const auto &modelInfo : modelLists)
{
Json::Value modelJson;
modelJson["name"] = modelInfo._modelName;
modelJson["desc"] = modelInfo._modelDesc;
dataArray.append(modelJson);
}
// 构建响应体
Json::Value responseJson;
responseJson["success"] = true;
responseJson["message"] = "get model lists success";
responseJson["data"] = dataArray;
// 序列化
Json::StreamWriterBuilder writerBuilder;
std::string responseJsonStr = Json::writeString(writerBuilder, responseJson);
response.status = 200; // 成功
response.set_content(responseJsonStr, "application/json");
}
// 处理删除会话请求
void ChatServer::handleDeleteSessionRequest(const httplib::Request &request, httplib::Response &response)
{
// 获取会话id,注意:会话id是一个路径参数
std::string sessionId = request.matches[1];
// 删除会话
bool ret = _chatSDK->deleteSession(sessionId);
if (ret)
{
std::string errorJsonStr = buildResponse("delete session success", true);
response.status = 200;
response.set_content(errorJsonStr, "application/json");
}
else
{
std::string errorJsonStr = buildResponse("delete session failed, session not found");
response.status = 404; // 会话不存在
response.set_content(errorJsonStr, "application/json");
}
}
// 处理获取历史消息请求
void ChatServer::handleGetHistoryMessagesRequest(const httplib::Request &request, httplib::Response &response)
{
// 获取会话id
std::string sessionId = request.matches[1];
// 获取会话
auto session = _chatSDK->getSession(sessionId);
if (!session)
{
std::string errorJsonStr = buildResponse("session not found");
response.status = 404; // 会话不存在
response.set_content(errorJsonStr, "application/json");
return;
}
// 构建历史消息列表
Json::Value dataArray(Json::arrayValue);
for (const auto &message : session->_messages)
{
Json::Value messageJson;
messageJson["id"] = message._messageId;
messageJson["role"] = message._role;
messageJson["content"] = message._content;
messageJson["timestamp"] = static_cast<int64_t>(message._timestamp);
dataArray.append(messageJson);
}
// 构建响应体
Json::Value responseJson;
responseJson["success"] = true;
responseJson["message"] = "get history messages success";
responseJson["data"] = dataArray;
// 序列化
Json::StreamWriterBuilder writerBuilder;
std::string responseJsonStr = Json::writeString(writerBuilder, responseJson);
response.status = 200; // 成功
response.set_content(responseJsonStr, "application/json");
}
// 处理发送消息请求-全量返回
void ChatServer::handleSendMessageRequest(const httplib::Request &request, httplib::Response &response)
{
// 获取请求参数
Json::Value requestJson;
Json::Reader reader;
if (!reader.parse(request.body, requestJson))
{
std::string errorJsonStr = buildResponse("parse request body failed, json format error");
response.status = 400; // 解析请求参数失败
response.set_content(errorJsonStr, "application/json");
return;
}
// 解析请求参数
std::string sessionId = requestJson["session_id"].asString();
std::string message = requestJson["message"].asString();
if (sessionId.empty() || message.empty())
{
std::string errorJsonStr = buildResponse("session_id or message is empty");
response.status = 400; // 解析请求参数失败
response.set_content(errorJsonStr, "application/json");
return;
}
// 发送消息
std::string assistantMessage = _chatSDK->sendMessage(sessionId, message);
if (assistantMessage.empty())
{
std::string errorJsonStr = buildResponse("Failed to send AI response message");
response.status = 500; // 发送消息失败
response.set_content(errorJsonStr, "application/json");
return;
}
// 构造响应参数
Json::Value dataJson;
dataJson["session_id"] = sessionId;
dataJson["response"] = assistantMessage;
dataJson["data"]["assistant_message"] = assistantMessage;
// 构建响应体
Json::Value responseJson;
responseJson["success"] = true;
responseJson["message"] = "send message success";
responseJson["data"] = dataJson;
// 序列化
Json::StreamWriterBuilder writerBuilder;
std::string responseJsonStr = Json::writeString(writerBuilder, responseJson);
response.status = 200; // 成功
response.set_content(responseJsonStr, "application/json");
}
// 处理发送消息请求-增量返回
void ChatServer::handleSendMessageStreamRequest(const httplib::Request &request, httplib::Response &response)
{
// 获取请求参数
Json::Value requestJson;
Json::Reader reader;
if (!reader.parse(request.body, requestJson))
{
std::string errorJsonStr = buildResponse("parse request body failed, json format error");
response.status = 400; // 解析请求参数失败
response.set_content(errorJsonStr, "application/json");
return;
}
// 解析请求参数
std::string sessionId = requestJson["session_id"].asString();
std::string message = requestJson["message"].asString();
if (sessionId.empty() || message.empty())
{
std::string errorJsonStr = buildResponse("session_id or message is empty");
response.status = 400; // 解析请求参数失败
response.set_content(errorJsonStr, "application/json");
return;
}
// 准备流式响应
response.status = 200; // 成功
response.set_header("Cache-Control", "no-cache"); // 不使用缓存,服务器立即将数据发送到网络
response.set_header("Connection", "keep-alive"); // 保持连接,服务器不会关闭连接
response.set_header("Access-Control-Allow-Origin", "*"); // 允许跨域请求
response.set_header("Access-Control-Allow-Headers", "*"); // 允许所有请求头
// set_chunked_content_provider:告诉服务器,响应内从不是一次性发送的,而是分多次逐步发送给客户端,一般用在实时生成响应内容 或者 流式数据传输场景
//
response.set_chunked_content_provider("text/event-stream", [this, sessionId, message](size_t offset, httplib::DataSink &dataSink) -> bool
{
auto writeChunk = [&](const std::string &chunk, bool last)
{
// 将chunk转换为SSE数据格式
// Json::valueToQuotedString: 对chunk进行Json转换,目的防止chunk中包含一些特殊字符来破坏数据格式,比如:在chunk中包含了两个连续的换行,就会影响SSE数据格式
std::string sseData = "data: " + Json::valueToQuotedString(chunk.c_str()) + "\n\n";
// 需要将模型返回的结果 chunk 发送给客户单
dataSink.write(sseData.c_str(), sseData.size()); // 将数据写入响应流,即立即发送给客户单,该方法不会等待缓冲区满之后发送
// 处理结束标记
if (last)
{
// 流向响应结束
std::string doneData = "data: [DONE]\n\n";
dataSink.write(doneData.c_str(), doneData.size());
dataSink.done(); // 表示流式响应结束
return false; // 不再有后续数据
}
return true;
};
// 先给客户端发送一个空的数据块,避免客户端长时间的等待
if (!writeChunk("", false))
{
return false;
}
// 发送消息流
_chatSDK->sendMessageStream(sessionId, message, writeChunk);
return false; // 不再有后续数据
});
}
// 设置HTTP路由规则
void ChatServer::setHttpRoutes()
{
// 处理创建会话请求
_chatServer->Post("/api/session", [this](const httplib::Request &request, httplib::Response &response)
{ handleCreateSessionRequest(request, response); });
// 处理获取会话列表请求
_chatServer->Get("/api/sessions", [this](const httplib::Request &request, httplib::Response &response)
{ handleGetSessionListsRequest(request, response); });
// 处理获取模型列表请求
_chatServer->Get("/api/models", [this](const httplib::Request &request, httplib::Response &response)
{ handleGetModelListsRequest(request, response); });
// 处理删除会话请求
_chatServer->Delete("/api/session/(.*)", [this](const httplib::Request &request, httplib::Response &response)
{ handleDeleteSessionRequest(request, response); });
// 处理获取历史消息请求
_chatServer->Get("/api/session/(.*)/history", [this](const httplib::Request &request, httplib::Response &response)
{ handleGetHistoryMessagesRequest(request, response); });
// 处理发送消息请求-全量返回
_chatServer->Post("/api/message", [this](const httplib::Request &request, httplib::Response &response)
{ handleSendMessageRequest(request, response); });
// 处理发送消息请求-增量返回
_chatServer->Post("/api/message/async", [this](const httplib::Request &request, httplib::Response &response)
{ handleSendMessageStreamRequest(request, response); });
}
} // end ai_chat_server
cpp
#include "ChatServer.hpp"
#include <gflags/gflags.h>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>
#include <stdexcept>
#include <chrono>
#include <thread>
#include <spdlog/common.h>
// 正确的日志头文件路径
#include <ai_chat_sdk/util/myLog.hpp>
// 定义gflags参数
DEFINE_string(host, "0.0.0.0", "服务器绑定的地址");
DEFINE_int32(port, 50090, "服务器绑定的端口号");
DEFINE_string(log_level, "INFO", "日志级别");
DEFINE_double(temperature, 0.7, "温度值,影响生成文本的随机性");
DEFINE_int32(max_tokens, 2048, "最大token数");
DEFINE_string(config_file, "./ChatServer.conf", "配置文件路径");
// DEFINE_bool(version, false, "显示版本信息");
// Ollama配置参数
DEFINE_string(ollama_model_name, "", "Ollama模型名称");
DEFINE_string(ollama_model_desc, "", "Ollama模型描述");
DEFINE_string(ollama_endpoint, "", "Ollama API地址");
// 版本号
const std::string VERSION = "1.0.0";
// 从环境变量获取API密钥
std::string getEnvVar(const std::string& key) {
char* value = std::getenv(key.c_str());
return value ? std::string(value) : "";
}
// 注意:配置文件ChatServer.conf将由gflags库自动解析,不需要手动生成
// 验证配置参数
bool validateConfig(ai_chat_server::ServerConfig& config) {
// 验证温度值
if (config.temperature < 0.0 || config.temperature > 2.0) {
ERROR("错误: 温度值必须在0.0到2.0之间,当前值: {}", config.temperature);
return false;
}
// 验证最大token数
if (config.maxTokens <= 0) {
ERROR("错误: 最大token数必须为正数,当前值: {}", config.maxTokens);
return false;
}
// 验证至少有一个API密钥不为空
if (config.deepseekAPIKey.empty() && config.chatGPTAPIKey.empty() && config.geminiAPIKey.empty() && config.ollamaModelName.empty()) {
ERROR("错误: 至少需要提供一个有效的API密钥或Ollama模型配置");
return false;
}
// 验证Ollama配置参数
if (!config.ollamaModelName.empty()) {
if (config.ollamaModelDesc.empty() || config.ollamaEndpoint.empty()) {
ERROR("错误: 如果提供了Ollama模型名称,则必须同时提供模型描述和端点");
return false;
}
}
return true;
}
// 显示接口说明
void showAPIInfo() {
std::cout << "\nChatServer API接口说明:\n";
std::cout << " POST /api/session - 创建新会话\n";
std::cout << " GET /api/sessions - 获取所有会话列表\n";
std::cout << " GET /api/models - 获取可用模型列表\n";
std::cout << " DELETE /api/session/{session_id} - 删除指定会话\n";
std::cout << " GET /api/session/{session_id}/history - 获取会话历史消息\n";
std::cout << " POST /api/message - 发送消息(全量返回)\n";
std::cout << " POST /api/message/async - 发送消息(流式返回)\n";
std::cout << "\n使用示例:\n";
std::cout << " # 基本启动\n";
std::cout << " ./AIChatServer\n";
std::cout << "\n # 指定端口启动\n";
std::cout << " ./AIChatServer --port=9000\n";
std::cout << "\n # 使用指定配置文件\n";
std::cout << " ./AIChatServer --config_file=my_config.conf\n";
std::cout << "\n # 设置环境变量后启动\n";
std::cout << " export DEEPSEEK_API_KEY=your_api_key\n";
std::cout << " ./AIChatServer\n";
}
int main(int argc, char** argv) {
try {
// 显示帮助信息(当使用-h或--help时,gflags会自动显示帮助信息并退出)
// 这里我们额外显示API接口说明
if (argc == 2 && (std::string(argv[1]) == "-h" || std::string(argv[1]) == "--help")) {
showAPIInfo();
return 0;
}
// 解析命令行参数
gflags::SetUsageMessage("AIChatServer - AI聊天服务器\n\n使用方法: ./AIChatServer [options]");
gflags::ParseCommandLineFlags(&argc, &argv, true);
gflags::SetVersionString(VERSION);
// 使用gflags库自动解析配置文件
// 如果配置文件存在,gflags会自动加载其中的参数
// 注意:gflags会按照以下顺序解析参数:默认值 -> 配置文件 -> 命令行参数
std::ifstream file(FLAGS_config_file);
if (file) {
gflags::SetCommandLineOption("flagfile", FLAGS_config_file.c_str());
}
// 构建ServerConfig
ai_chat_server::ServerConfig config;
config.host = FLAGS_host;
config.port = FLAGS_port;
config.logLevel = FLAGS_log_level;
config.temperature = FLAGS_temperature;
config.maxTokens = FLAGS_max_tokens;
// 从环境变量获取API密钥
config.deepseekAPIKey = getEnvVar("deepseek_apikey");
// config.chatGPTAPIKey = getEnvVar("chatgpt_apikey");
// config.geminiAPIKey = getEnvVar("gemini_apikey_new");
// 从命令行参数获取Ollama配置
config.ollamaModelName = FLAGS_ollama_model_name;
config.ollamaModelDesc = FLAGS_ollama_model_desc;
config.ollamaEndpoint = FLAGS_ollama_endpoint;
// 验证配置参数
if (!validateConfig(config)) {
ERROR("配置验证失败,请检查参数设置");
return 1;
}
// 设置日志级别
spdlog::level::level_enum logLevel = spdlog::level::info; // 默认INFO级别
if (config.logLevel == "TRACE") logLevel = spdlog::level::trace;
else if (config.logLevel == "DEBUG") logLevel = spdlog::level::debug;
else if (config.logLevel == "INFO") logLevel = spdlog::level::info;
else if (config.logLevel == "WARN" || config.logLevel == "WARNING") logLevel = spdlog::level::warn;
else if (config.logLevel == "ERROR") logLevel = spdlog::level::err;
else if (config.logLevel == "CRITICAL") logLevel = spdlog::level::critical;
// 初始化日志组件
lg::Logger::initLogger("ChatServer", "stdout", logLevel);
// 显示当前配置
INFO("AIChatServer 启动配置:");
INFO(" 版本: {}", VERSION);
INFO(" 主机: {}", config.host);
INFO(" 端口: {}", config.port);
INFO(" 日志级别: {}", config.logLevel);
INFO(" 温度值: {}", config.temperature);
INFO(" 最大Token: {}", config.maxTokens);
INFO(" DeepSeek API Key: {}", (config.deepseekAPIKey.empty() ? "未设置" : "已设置"));
INFO(" ChatGPT API Key: {}", (config.chatGPTAPIKey.empty() ? "未设置" : "已设置"));
INFO(" Gemini API Key: {}", (config.geminiAPIKey.empty() ? "未设置" : "已设置"));
INFO(" Ollama 模型: {}", (config.ollamaModelName.empty() ? "未设置" : config.ollamaModelName));
INFO(" Ollama 模型描述: {}", (config.ollamaModelDesc.empty() ? "未设置" : config.ollamaModelDesc));
INFO(" Ollama 端点: {}", (config.ollamaEndpoint.empty() ? "未设置" : config.ollamaEndpoint));
// 创建并启动ChatServer
ai_chat_server::ChatServer server(config);
if (server.start()) {
INFO("ChatServer 启动成功!");
INFO("服务器地址: http://{}:{}", config.host, config.port);
// 主线程等待,让服务器在单独线程中运行
while (server.isRunning()) {
std::this_thread::sleep_for(std::chrono::seconds(100));
}
} else {
ERROR("ChatServer 启动失败!");
return 1;
}
return 0;
} catch (const std::exception& e) {
ERROR("发生异常: {}", e.what());
return 1;
} catch (...) {
ERROR("发生未知异常");
return 1;
}
}
前端的话,直接把上面的请求响应的http格式发给AI,让AI整一个就行,或者直接把我gitee中的build目录下的这几个文件复制过去就行。

最后记得.js文件中修改成你的IP+PORT,然后就可以运行了。
