C++大模型SDK开发实录(二):DeepSeek模型接入、HTTP通信实现与GTest单元测试
在完成了SDK的底层架构设计与抽象基类的定义后,接下来的核心任务是实现具体的模型接入逻辑。本文将以DeepSeek模型为例,详细阐述如何通过C++代码构建HTTP请求、处理API鉴权、解析JSON响应,并通过严格的单元测试验证模块功能的正确性。如果文章中的代码存在问题,可以直接去仓库拉取最新代码在include目录下创建。该文件声明了DeepSeek提供者类,重写了基类中的纯虚函数。可以看到,除了
这里写目录标题
前言
在完成了SDK的底层架构设计与抽象基类LLMProvider的定义后,接下来的核心任务是实现具体的模型接入逻辑。本文将以DeepSeek模型为例,详细阐述如何通过C++代码构建HTTP请求、处理API鉴权、解析JSON响应,并通过严格的单元测试验证模块功能的正确性。
如果文章中的代码存在问题,可以直接去仓库拉取最新代码
Ai_Model_SDK
https://gitee.com/caijiuuyk/ai_-model_-sdk
第一章:DeepSeek Provider类的派生与初始化
根据策略模式的设计,我们需要创建一个继承自LLMProvider的具体实现类DeepSeekProvider。该类负责管理DeepSeek模型的生命周期、配置加载以及消息发送逻辑。
1.1 头文件定义
在include目录下创建DeepSeekProvider.h。该文件声明了DeepSeek提供者类,重写了基类中的纯虚函数。可以看到,除了基础的初始化和元数据获取接口外,重点在于sendMessage(全量返回)和sendMessageStream(流式返回)的声明。
SDK/include/DeepSeekProvider.h 代码如下:
#include"LLMProvider.h"
#include<string.h>
#include<map>
#include<vector>
#include"common.h"
namespace kk
{
// DeepSeekProvider类,继承自抽象基类LLMProvider
class DeepSeekProvider : public LLMProvider
{
public:
// 初始化模型
// modelConfig: 包含apiKey, endpoint等配置信息的映射
virtual void initModel(const std::map<std::string,std::string>& modelConfig);
// 检查模型是否可用
virtual bool isAvailable() const;
// 获取模型名称
virtual std::string getModelName() const;
// 获取模型描述
virtual std::string getModelDesc() const;
// 发送消息 ---全量返回 非流式响应
// messages: 上下文消息列表
// requestParam: 运行时参数(如temperature, maxTokens)
std::string sendMessage(const std::vector<Message>& messages,const std::map<std::string,std::string>& requestParam);
// 发送消息 ---全量返回 流式响应
// callback: 回调函数,用于处理增量返回的Token
virtual std::string sendMessageStream(const std::vector<Message>& messages,
const std::map<std::string,std::string>& requestParam,
std::function<void(const std::string&,bool)> callback);
};
}
1.2 初始化逻辑实现
在src目录下创建DeepSeekProvider.cpp。初始化的核心逻辑在于解析传入的配置参数(modelConfig),提取API Key和Endpoint(API接入点),并设置模型的可用状态。这里使用了我们之前封装的myLog.h进行日志记录,以便在初始化失败时快速定位问题。
SDK/src/DeepSeekProvider.cpp 初始化部分代码:
#include"../include/DeepSeekProvider.h"
#include"../include/util/myLog.h"
namespace kk
{
// 初始化模型实现
bool DeepSeekProvider::initModel(const std::map<std::string,std::string>& modelConfig)
{
// 1. 初始化API key
auto it =modelConfig.find("apiKey");
if(it==modelConfig.end())// 没找到apikey的话
{
ERR("DeepSeekProvider initModel:apiKey not found");// 打印错误日志
return false;
}
_apiKey=it->second;// 找到了赋值
// 2. 初始化endpoint
it=modelConfig.find("endpoint");
if(it==modelConfig.end())// 没找到endpoint的话
{
ERR("DeepSeekProvider initModel:endpoint not found");// 打印错误日志
return false;
}
_endpoint=it->second;// 找到了赋值
_isAvailable=true;// 标记模型可用
// 初始化成功,打印关键信息(注意生产环境不要打印完整的Key)
INFO("DeepSeekProvider initModel:success,apiKey:%s,endpoint:%s",_apiKey.c_str(),_endpoint.c_str());
return true;
}
// 检测模型是否可用
bool DeepSeekProvider::isAvailable() const
{
return _isAvailable;
}
// 获取模型名称
std::string DeepSeekProvider::getModelName() const
{
return "deepseek-chat";
}
// 获取模型的描述信息
std::string DeepSeekProvider::getModelDesc() const
{
return "deepseek-chat模型是一个基于Transformer架构的对话模型,由DeepSeek公司开发。它可以用于生成自然语言对话,支持多轮对话。";
}
// sendMessage 和 sendMessageStream 的具体实现将在下文详述
}
第二章:HTTP通信与JSON处理核心实现
为了实现sendMessage功能,我们需要引入HTTP客户端库来发送网络请求,并引入JSON库来处理数据交互。
2.1 依赖库安装:cpp-httplib
本项目选用cpp-httplib作为HTTP客户端,它是一个单头文件(header-only)的跨平台C++库,使用非常便捷。
安装方式一:Git克隆
可以直接从GitHub克隆源码,并将头文件复制到系统目录:
dev@dev-host:~/workspace$ git clone https://github.com/yhirose/cpp-httplib.git
# 注意:cpp-httplib是header-only库,只需要一个头文件即可
# 将httplib.h拷贝到系统目录下,这样在程序中#include <httplib.h>时能直接找到
bit@bit08:~/cpp-httplib/cpp-httplib$ sudo cp httplib.h /usr/include
安装方式二:压缩包解压
如果在服务器端Git网络受限,可以通过上传压缩包的方式进行安装。
unzip cpp-httplib-master.zip
解压后的文件结构如下,只需关注核心的httplib.h文件。

2.2 全量消息发送逻辑详解
sendMessage函数的实现是SDK的核心之一。其工作流程如下:
- 状态校验:确认模型已初始化且可用。
- 参数构造:解析用户传入的
requestParam(如温度、MaxTokens),设置默认值。 - JSON序列化:使用
jsoncpp库,将消息历史(vector<Message>)和配置参数组装成符合DeepSeek API规范的JSON对象,并序列化为字符串。 - HTTP请求构建:实例化
httplib::Client,设置超时时间,配置Header(包含Bearer Token认证)。 - 发送与接收:发送POST请求,等待服务器的全量响应。
- 错误处理:检查HTTP状态码(期望为200)和网络错误。
- JSON反序列化:解析API返回的JSON数据,提取
choices[0].message.content字段作为最终回复。
以下是DeepSeekProvider.cpp中关于sendMessage的完整实现代码:
#include"../include/DeepSeekProvider.h"
#include"../include/util/myLog.h"
#include<jsoncpp/json/json.h>
#include<httplib.h>
namespace kk
{
// ... 前文的initModel等代码 ...
// 发送消息 ---全量返回 非流式响应
std::string DeepSeekProvider::sendMessage(const std::vector<Message>& messages,const std::map<std::string,std::string>& requestParam)
{
// 1、检测模型是否可用
if(!isAvailable())
{
ERR("DeepSeekProvider sendMessage:model not available");
return "";
}
// 2、构造请求参数
double temperature=0.7;// 默认温度
int maxTokens=2048; // 默认最大token
if(requestParam.find("temperature")!=requestParam.end())
{
temperature=std::stod(requestParam.at("temperature"));
}
if(requestParam.find("maxTokens")!=requestParam.end())
{
maxTokens=std::stoi(requestParam.at("maxTokens"));
}
// 3、构造历史消息 JSON数组
Json::Value messageArray(Json::arrayValue);
for(const auto& message:messages)
{
Json::Value messageJson(Json::objectValue);
messageJson["role"]=message._role;
messageJson["content"]=message._content;
messageArray.append(messageJson);
}
// 4、构造整个请求体 JSON对象
Json::Value requestBody(Json::objectValue);
requestBody["model"]=getModelName(); // 设置模型名称
requestBody["messages"]=messageArray; // 设置上下文
requestBody["temperature"]=temperature;
requestBody["max_tokens"]=maxTokens;
// 5、序列化:将JSON对象转为字符串
Json::StreamWriterBuilder writerBuilder;
writerBuilder["indentation"]=""; // 设置缩进为空,压缩数据传输量
std::string requestBodyStr=Json::writeString(writerBuilder,requestBody);
INFO("DeepSeekProvider sendMessage:requestBody:%s",requestBodyStr.c_str());
// 6、使用cpp-httplib库构造http客户端
// _endpoint 需为 base url,例如 https://api.deepseek.com
httplib::Client client(_endpoint.c_str());
client.set_connection_timeout(30); // 连接超时30秒
client.set_read_timeout(60,0); // 读取超时60秒,防止大模型生成时间过长导致断连
// 7、设置请求头
httplib::Headers headers=
{
{"Authorization", "Bearer " + _apiKey}, // 鉴权的核心
{"Content-Type", "application/json"}
};
// 8、发送POST请求
auto response=client.Post("/v1/chat/completions",headers,requestBodyStr,"application/json");
if(!response) // 网络层面的请求失败
{
ERR("DeepSeekProvider sendMessage:request failed,error message:{}",httplib::to_string(response.error()));
return "";
}
INFO("DeepSeekProvider sendMessage:POST request success ,status code:{}",response->status);
INFO("DeepSeekProvider sendMessage:response body:{}",response->body);
// 9、检测HTTP响应状态
if(response->status!=200)
return "";
// 10、解析响应体
Json::Value responseBody;
Json::CharReaderBuilder readerBuilder;
std::string parseError;
std::istringstream responseStream(response->body);
// 解析JSON字符串
if(Json::parseFromStream(readerBuilder,responseStream,&responseBody,&parseError))
{
// 导航至 message 内容:responseBody["choices"][0]["message"]["content"]
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 sendMessage:replyContent:{}",replyContent);
return replyContent;
}
}
// JSON结构不符合预期
ERR("DeepSeekProvider sendMessage:JSON parse failed,error message:{}",parseError);
return "json parse failed";
}
return "json parse failed";
}
// 占位实现,流式响应将在后续章节详细讲解
std::string DeepSeekProvider::sendMessageStream(const std::vector<Message>& messages,
const std::map<std::string,std::string>& requestParam,
std::function<void(const std::string&,bool)> callback)
{
return "stream not implemented yet";
}
}
第三章:基于Google Test的单元测试
代码编写完成后,必须进行严格的测试。本项目引入C++领域最权威的单元测试框架 Google Test (gtest)。
3.1 GTest环境搭建
在Ubuntu环境下,可以通过apt快速安装GTest开发库:
sudo apt-get install libgtest-dev
3.2 编写测试用例
在项目根目录下创建一个test文件夹,并编写testLLM.cpp。测试用例的设计涵盖了从对象创建、初始化到发送消息的完整流程。为了安全起见,API Key通过环境变量获取,支持回退到硬编码的Key(仅用于本地调试)。
test/testLLM.cpp 代码:
#include<gtest/gtest.h>
#include"../SDK/include/DeepSeekProvider.h"
#include"../SDK/include/util/myLog.h"
// 测试DeepSeekProvider的核心流程
TEST(DeepSeekProviderTest,sendMessage)
{
// 1. 创建Provider实例
auto Provider=std::make_shared<kk::DeepSeekProvider>();
ASSERT_TRUE(Provider!=nullptr); // 断言指针有效
// 2. 准备初始化参数
std::map<std::string,std::string> modelParam;
const char* env_apikey = std::getenv("deepseek_apikey");
// 优先读取环境变量,否则使用备用Key
modelParam["apiKey"] = env_apikey ? env_apikey : "sk-xxxxxxxxxxxxxxxxxxxxxxxx";
// 支持配置自定义Endpoint,例如中转站地址
modelParam["endpoint"]="https://api.deepseek.com";
// 3. 初始化并检查可用性
Provider->initModel(modelParam);
ASSERT_TRUE(Provider->isAvailable());
// 4. 准备请求运行时参数
std::map<std::string,std::string> requestParam=
{
{"temperature","0.5"},
{"max_tokens","2048"}
};
// 5. 构造测试消息
std::vector<kk::Message> messages;
messages.push_back({"user","你是谁?"});
// 6. 调用核心接口并验证
std::string response=Provider->sendMessage(messages,requestParam);
ASSERT_TRUE(!response.empty()); // 断言回复不为空
}
int main(int argc, char **argv)
{
// 初始化日志库,开启DEBUG级别以便查看详细通信过程
kk::Logger::initLogger("testLLM","stdout",spdlog::level::debug);
INFO("Test start...");
// 初始化GTest框架
testing::InitGoogleTest(&argc, argv);
// 运行所有注册的TEST宏
return RUN_ALL_TESTS();
}
3.3 构建配置与编译
在test目录下创建独立的CMakeLists.txt,用于构建测试可执行文件。需要特别注意的是,这里必须正确链接OpenSSL、JsonCpp、Fmt、Spdlog以及GTest库。
test/CMakeLists.txt 配置:
# 设置Cmake的最小版本为3.10
cmake_minimum_required(VERSION 3.10)
# 项目名称
project(testLLM)
# 设置C++标准为C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_BUILD_TYPE Debug)
# 添加可执行文件,包含测试代码和SDK源码
add_executable(testLLM testLLM.cpp
../SDK/src/DeepSeekProvider.cpp
../SDK/src/util/myLog.cpp)
# 设置输出目录
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR})
# 添加头文件搜索路径
include_directories(${CMAKE_PROJECT_INCLUDE_DIR}/ ../SDK/include)
# 查找并链接OpenSSL
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})
# 宏定义:告诉cpp-httplib支持HTTPS
target_compile_definitions(testLLM PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
# 链接所有依赖库
target_link_libraries(testLLM spdlog gtest jsoncpp fmt OpenSSL::Crypto OpenSSL::SSL)
目录结构确认:
确保文件布局如下所示:
3.4 编译与运行
进入test目录,创建build文件夹并进行编译:
mkdir build && cd build
cmake ..
CMake配置成功后,会生成Makefile:

执行make进行编译。在开发过程中,可能会遇到库链接顺序或头文件缺失导致的编译错误,需要根据报错信息仔细调整CMakeLists.txt。
修复问题后编译成功,运行生成的可执行文件./testLLM。可以看到控制台输出了请求的JSON Body、服务器返回的状态码以及解析后的回复内容。这里演示了通过自定义Endpoint连接API中转站的场景。

至此,我们已经成功实现了DeepSeek模型的全量消息发送功能,并通过单元测试验证了从网络通信到JSON解析的完整链路。下一章将深入最具挑战性的部分——流式响应(Streaming Response) 的实现。
更多推荐

所有评论(0)