C++大模型SDK开发实录:从架构设计到流式交互实现
在当今的人工智能应用开发中,如何高效、统一地接入不同厂商的大语言模型(如DeepSeek、OpenAI、Gemini)是一个核心挑战。本项目旨在构建一个名为 ChatSDK 的C++开发包,屏蔽底层API差异,提供统一的会话管理、全量与流式消息发送功能。
本文将首先展示项目的最终形态与环境部署流程,随后深入剖析从零开始构建该SDK的每一个技术细节,涵盖API协议分析、数据架构设计、高性能日志系统封装以及策略模式在模型接入层中的应用。
第一章:成品演示与快速上手
在深入底层代码之前,首先展示ChatSDK的最终运行效果以及如何在服务器环境中部署和使用该SDK。
1.1 项目功能与运行效果
ChatSDK 旨在为C++开发者提供一套简洁的接口,支持以下核心功能:
- 多模型支持:通过统一配置接入不同厂商模型。
- 会话管理:创建会话、获取列表、删除会话及历史消息回溯。
- 双模式响应:支持同步全量返回(适合后台处理)和异步流式返回(适合打字机效果)。
下图展示了基于ChatSDK编写的终端演示程序(Demo)的运行界面。可以看到,用户与模型进行了交互,程序成功输出了DeepSeek模型的回复内容。

1.2 环境依赖与构建工具链
本项目基于Linux环境开发,依赖一系列成熟的开源库来处理网络、日志、JSON解析及测试。
基础依赖安装
在服务器端(推荐使用Trae CN连接),首先需要配置C++编译环境(clangd, CMake Tools)及以下系统库:
bash
# gflags:用于解析命令行参数
sudo apt-get install libgflags-dev
# spdlog:高性能C++日志库,提供异步日志支持
sudo apt-get install libspdlog-dev
# fmt:格式化库,spdlog的核心依赖
sudo apt-get install fmt
# jsoncpp:用于处理模型API交互中的JSON数据序列化与反序列化
sudo apt-get install libjsoncpp-dev
# gtest:Google单元测试框架
sudo apt-get install libgtest-dev
# OpenSSL:支持HTTPS加密通信,保障API Key安全
sudo apt-get install libssl-dev
# cmake:跨平台构建系统
sudo apt-get install cmake
# pkg-config:编译时库文件路径查找工具
sudo apt install pkg-config
# curl:命令行网络工具,用于初步测试API连通性
sudo apt install curl
源码获取与目录结构
获取SDK源码主要有两种方式:通过Git克隆或直接上传压缩包。
bash
git clone https://gitee.com/zhibite-edu/ai-model-acess-dev.git
如果网络环境受限,可将源码下载为Zip包上传至服务器并解压。如下图所示,在服务器中创建 ChatSDK 文件夹,解压后的源码位于 SDK-source 目录中。

1.3 编译构建与排错流程
项目采用 CMake 进行构建管理。标准的构建流程推荐采用"外部构建"方式,即在源码目录外创建一个 build 文件夹,以保持源码整洁。
-
初始化构建目录 :
进入SDK源码路径
ChatSDK/SDK-source/ai-model-acess-tech-master/AIModelAcessTech/sdk,执行以下命令:bashmkdir build && cd build
-
生成Makefile :
在
build目录下执行 CMake 配置命令,读取上级目录的CMakeLists.txt:bashcmake ..

-
编译与依赖修复 :
执行
make命令开始编译。在初次编译时,系统抛出了错误,提示找不到httplib.h。

这是因为项目内部使用了
cpp-httplib库但环境未安装。需执行以下命令补全依赖:bashsudo apt-get update && sudo apt-get install -y libcpp-httplib-dev修复后重新执行
make,编译成功。


1.4 核心架构解析与安装
ChatSDK 采用了清晰的分层架构,核心逻辑代码位于 src 目录下,其中 util 文件夹包含了模型接入的具体实现。

- 核心框架类 :
ChatSDK.cpp:对外门面,封装内部复杂性。LLMManager.cpp:模型生命周期管理与路由。SessionManager.cpp:会话状态与内存管理。DataManager.cpp:基于SQLite的数据持久化层。
- 模型提供者类 :
ChatGPTProvider.cpp/GeminiProvider.cpp/DeepSeekProvider.cpp:实现了具体的API调用逻辑,支持自定义 Endpoint 以适配中转站。
API 接口概览 :
SDK提供了 initModels(初始化)、createSession(创建会话)、sendMessage(全量发送)及 sendMessageStream(流式发送)等标准接口。
安装库文件 :
编译完成后,执行安装命令将头文件和库文件部署到系统目录:
bash
sudo make install

验证安装结果,头文件位于 /usr/local/include/ai_chat_sdk/,库文件位于 /usr/local/lib/。


1.5 客户端集成演示 (ChatDemo)
为了验证SDK的可用性,编写一个名为 chatDemo.cpp 的客户端程序。该程序演示了如何调用DeepSeek模型进行流式对话。
cpp
#include<ai_chat_sdk/chat_sdk.h>
#include<ai_chat_sdk/my_logger.h>//打印日志
#include<iostream>
#include<limits>
void sendMessageStream(ai_chat_sdk::ChatSDK& chat_SDK,std::string session_id)
{
std::cout<<"-----------------------------发送消息-----------------------------"<<std::endl;
std::string message;
std::getline(std::cin, message);//获取用户输入
chat_SDK.sendMessageStream(session_id,message,[](const std::string&response,bool done){
std::cout<<"assistant消息 >"<<response<<std::endl;
if(done)//如果是最后一条消息
{
std::cout<<"-----------------------------消息接收完成-----------------------------"<<std::endl;
}
});//发送消息
std::cout<<"-----------------------------消息发送完成-----------------------------"<<std::endl;
std::cout<<"请输入您的消息:"<<std::endl;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');//清除输入缓冲区
std::cout<<"您输入的消息是:"<<message<<std::endl;
}
int main()
{
bite::Logger::init_logger("aiChatDemo","stdout",spdlog::level::info);//创建一个控制台日志记录器
ai_chat_sdk::ChatSDK chatSDK;//创建一个SDK的对象
//配置deepseek模型
ai_chat_sdk::ApiConfig deepseek;//创建一个配置对象
deepseek.api_key=std::getenv("deepseek_apikey");//设置API Key
deepseek.temperature=0.7;//设置温a度
deepseek.max_tokens=2048;//设置最大token数
deepseek.model_name="deepseek-chat";//设置模型名称
std::vector<std::shared_ptr<ai_chat_sdk::Config>> configs;//创建一个配置对象的向量
configs.push_back(std::make_shared<ai_chat_sdk::ApiConfig>(deepseek));//添加Deepseek的配置对象
//初始化模型
chatSDK.initModels(configs);
std::cout<<"-----------------------------创建会话-----------------------------"<<std::endl;
std::string session_id=chatSDK.createSession("deepseek-chat");//创建一个会话
std::cout<<"session_id:"<<session_id<<std::endl;
int uerOP=1;//用户操作
while(true)
{
std::cout<<"-------------------1、send message 0、exit------------------------"<<std::endl;
std::cin>>uerOP;
if(uerOP==0)
{
break;
}
getchar();//输入用户操作选择之后,需要接受到缓冲区中的回车
sendMessageStream(chatSDK,session_id);//给会话发送消息
}
return 0;
}
配合相应的 CMakeLists.txt 进行编译:
cmake
# 项目名称为AIChatDemo
project(AIChatDemo)
# 设置C++标准,使用C++17标准进行编译,REQUIRED表示编译器如果不支持C++17则报错
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 设置构建类型为Debug
set(CMAKE_BUILD_TYPE Debug)
# 添加可执行文件
add_executable(AIChatDemo chatDemo.cpp)
# 查找OpenSSL包
find_package(OpenSSL REQUIRED)
# 将OpenSSL的头文件目录添加到编译器的搜索路径中
include_directories(${OPENSSL_INCLUDE_DIR})
# 设置库的目录,链接器会在这个目录中查找需要的库文件ai_chat_sdk库文件
link_directories(/usr/local/lib)
# 链接SDK
target_link_libraries(AIChatDemo PRIVATE
ai_chat_sdk
fmt
jsoncpp
OpenSSL::SSL
OpenSSL::Crypto
gflags
spdlog
sqlite3
)

运行编译后的程序,即可实现本章开头展示的对话效果。
第二章:API 协议调研与调试 (DeepSeek)
在开始编码实现SDK之前,必须深入理解目标模型(以DeepSeek为例)的API交互协议。我们使用Postman工具进行预研。
2.1 鉴权与环境配置
首先,将API Key配置在Postman的全局环境变量中,避免硬编码,确保安全性。

在HTTP请求头(Headers)中,设置 Content-Type 为 application/json,并配置 Authorization 字段,值为 Bearer {``{api_key}}。

2.2 接口参数与请求体
DeepSeek的API兼容OpenAI规范。我们需要构建JSON格式的请求体(Body),包含模型名称、消息历史列表等。

请求的URL Endpoint 设置为 https://api.deepseek.com。

2.3 响应模式测试
流式响应 (Streaming) :
将 stream 参数设置为 true。此时,服务器通过 Server-Sent Events (SSE) 协议分块返回数据。在Postman中可以看到数据是逐步到达的,每块数据包含一个 token。

非流式响应 (Blocking) :
将 stream 参数设置为 false。服务器会等待所有内容生成完毕后,一次性返回一个完整的JSON对象。

这一调研过程决定了SDK底层网络模块必须同时支持 HTTP 长连接读取(用于流式)和普通请求读取(用于全量)。
第三章:通用数据结构设计
基于API调研结果,我们抽象出一套通用的数据结构,用于在SDK内部各个模块(模型管理、会话管理、持久化)之间传递数据。这些定义位于 include/common.h。
3.1 核心结构体定义
为了适应不同模型,我们提取了公共配置和描述信息:
cpp
#pragma once
#include <string>
#include <ctime>
#include <vector>
namespace ai_chat_sdk
{
// 消息结构:对应API中的message对象
struct Message
{
std::string _id;
std::string _role; // user, assistant, system
std::string _content; // 消息内容
std::time_t _timestamp;
Message(const std::string& role, const std::string& content)
: _role(role), _content(content) {}
};
// 基础配置:所有模型通用的参数
struct Config
{
std::string _modelName;
double _temperature=0.7; // 控制随机性
int _maxTokens=2048; // 控制长度
};
// API配置:继承自Config,增加鉴权信息
struct APIConfig:public Config
{
std::string _apiKey;
};
// 模型元数据:用于服务发现和状态检查
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; // 消息历史
std::time_t _createAt;
std::time_t _updateAt;
Session( const std::string& modelName="") :_modelName(modelName) {}
};
}
这些结构体构成了SDK的数据骨架,Session 对象将由 SessionManager 在内存中维护,并由 DataManager 序列化到数据库中。
第四章:基础设施------spdlog日志库封装
在C++工程中,std::cout 无法满足多线程安全、日志分级(Trace/Debug/Info/Error)、文件持久化及格式化输出的需求。因此,本项目基于 spdlog 进行了单例封装。
4.1 封装优势与设计理念
- 级别管理:在开发期开启DEBUG,生产期切换至INFO或ERROR,避免日志文件膨胀。
- 格式化:自动注入时间戳、线程ID、文件名及行号,便于定位问题。
- 异步高性能:利用独立线程处理磁盘I/O,避免阻塞主业务线程。
- 线程安全:通过内部锁机制保证多线程并发写入不冲突。
封装采用了单例模式(Singleton Pattern),确保全局只有一个日志管理器实例。
4.2 实现代码解析
头文件 (util/myLog.h) :
定义了 Logger 类及便捷宏。宏定义利用 __FILE__ 和 __LINE__ 自动捕获调用位置。
cpp
#pragma once
#include<spdlog/spdlog.h>
namespace kk
{
class Logger
{
public:
// 初始化日志:指定名称、文件路径、级别
static void initLogger(const std::string &loggerName,const std::string &loggerFile,spdlog::level::level_enum logLevel=spdlog::level::info);
static std::shared_ptr<spdlog::logger> getLogger();
private:
Logger(); // 私有构造
// 禁止拷贝
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
private:
static std::shared_ptr<spdlog::logger> _logger;
static std::mutex _mutex;
};
// 宏定义示例:自动格式化文件名与行号
#define DBG(format, ...)kk::Logger::getLogger()->debug(std::string("[{:>10s}:{<4d}]")+format,__FILE__,__LINE__, ##__VA_ARGS__)
//定义其他日志级别宏
#define TRACE(format, ...)kk::Logger::getLogger()->log(spdlog::level::info,std::string("[{:>10s}:{<4d}]")+format,__FILE__,__LINE__, ##__VA_ARGS__)
#define INF(format, ...)kk::Logger::getLogger()->info(std::string("[{:>10s}:{<4d}]")+format,__FILE__,__LINE__, ##__VA_ARGS__)
#define WRN(format, ...)kk::Logger::getLogger()->warn(std::string("[{:>10s}:{<4d}]")+format,__FILE__,__LINE__, ##__VA_ARGS__)
#define ERR(format, ...)kk::Logger::getLogger()->error(std::string("[{:>10s}:{<4d}]")+format,__FILE__,__LINE__, ##__VA_ARGS__)
#define CRIT(format, ...)kk::Logger::getLogger()->critical(std::string("[{:>10s}:{<4d}]")+format,__FILE__,__LINE__, ##__VA_ARGS__)
}
实现文件 (util/myLog.cpp) :
实现了双重检查锁定(Double-Checked Locking)以保证初始化的线程安全,并配置异步线程池。
cpp
#include "../../include/util/mylog.h" //将头文件包含进来
#include<memory>
#include<spdlog/spdlog.h>
#include<spdlog/sinks/basic_file_sink.h>
#include<spdlog/sinks/stdout_color_sinks.h>
#include<spdlog/async.h>
namespace kk
{
std::shared_ptr<spdlog::logger> Logger::_logger=nullptr;//初始化日志器为空指针
std::mutex Logger::_mutex;//初始化日志器互斥锁
//构造函数
Logger::Logger()
{}
void Logger::initLogger(const std::string &loggerName,const std::string &loggerFile,spdlog::level::level_enum logLevel)
{
if(nullptr==_logger)//如果这个实例是空的,说明我们的日志器还没有被创建
{
//创建一把锁
std::lock_guard<std::mutex> lock(_mutex);
//再次检查是否为空,因为在多线程环境下,可能会有其他线程先创建了实例
if(nullptr==_logger)
{
//设置全局刷新策略,当日志级别大于等于logLevel时,刷新日志
spdlog::flush_on(logLevel);
//启用异步日志,即将日志信息存储放到队列中,有后台线程负责写入
//参数1:队列大小,参数2:后台线程数
spdlog::init_thread_pool(32768,1);//初始化线程池,32768个线程,1个队列
if("stdout"==loggerFile)//如果日志文件路径为stdout,则将日志输出到标准输出流,就是控制台
{
//创建一个带颜色的输出到控制台的日志器
_logger=spdlog::stdout_color_mt(loggerName);
}
else
{
//创建一个文件输出的日志器,日志会被写入到指定的文件中
_logger=spdlog::basic_logger_mt<spdlog::async_logger>(loggerName,loggerFile);//创建一个异步日志器
}
}
//格式设置
//[%H:%M:%S] 时间
//[%n] 日志器名称
//[%l] 日志级别,左对齐,宽度为7
//[%v] 日志消息
_logger->set_pattern("[%H:%M:%S][%n] [%-7l] %v");//设置日志格式
_logger->set_level(logLevel);//设置日志级别
}
}
//获取日志器的方法
std::shared_ptr<spdlog::logger> Logger::getLogger()
{
return _logger;
}
} //end kk


第五章:核心抽象------Provider策略模式实现
为了应对不同模型提供商(DeepSeek, ChatGPT, Gemini)的具体实现差异,同时对上层业务保持接口统一,我们采用了策略模式(Strategy Pattern)。
5.1 抽象基类设计 (LLMProvider)
我们定义了一个纯虚基类 LLMProvider,它规定了所有模型类必须实现的行为:初始化、可用性检查、获取元数据以及发送消息。由于它是抽象类,无法被直接实例化,只能作为接口规范。

头文件 (include/LLMprovider.h):
cpp
#include<string.h>
#include<map>
#include<vector>
#include"common.h"
namespace kk
{
class LLMProvider
{
public:
// 纯虚函数:初始化模型配置
virtual void initModel(const std::map<std::string,std::string>& modelConfig)=0;
// 纯虚函数:检查模型是否可用
virtual bool isAvailable() const=0;
// 纯虚函数:获取模型元数据
virtual std::string getModelName() const=0;
virtual std::string getModelDesc() const=0;
// 核心接口:全量发送消息
void sendMessage(const std::vector<Message>& messages,const std::map<std::string,std::string>& requestParam);
// 核心接口:流式发送消息,通过回调函数(std::function)返回增量数据
void sendMessageStream(const std::vector<Message>& messages,
const std::map<std::string,std::string>& requestParam,
std::function<void(const std::string&,bool)> callback);
private:
bool _isAvailable=false;
std::string _apiKey;
std::string _endpoint;
};
}
5.2 派生类实现逻辑
具体的模型类(如 DeepSeekProvider)将继承该基类。在 initModel 中,子类会解析配置参数:
- API Key:从配置中提取。
- Endpoint:默认使用官方地址,但允许通过配置覆盖,从而支持API中转站(Proxy)。
在 sendMessageStream 的实现中,子类将构造特定的HTTP请求,并利用 curl 或 httplib 的回调机制,每当收到一段SSE数据块时,就解析并调用上层传入的 callback 函数,从而实现流式效果。
通过这种设计,当需要添加新模型(例如Claude)时,只需新增一个继承自 LLMProvider 的类,而无需修改现有的会话管理或界面代码,完美符合开闭原则(Open-Closed Principle)。