跳到主要内容基于 C++ 构建 DeepSeek 大模型推理 SDK:架构设计与工程落地 | 极客日志C++AI算法
基于 C++ 构建 DeepSeek 大模型推理 SDK:架构设计与工程落地
使用 C++ 构建 DeepSeek 大模型推理 SDK 的全过程。涵盖云端鉴权配置、核心数据结构设计(消息、会话)、抽象接口层策略模式应用、具体适配器实现、单元测试体系(Google Test)以及 CMake 构建系统配置。通过面向对象设计与自动化测试,确保 SDK 在生产环境中的稳定性与可维护性。
星落3K 浏览 基于 C++ 构建 DeepSeek 大模型推理 SDK:架构设计与工程落地
前言
在高性能计算与大模型(LLM)应用开发的浪潮中,C++ 凭借其卓越的内存管理能力和运行时效率,成为了构建底层推理 SDK 的首选语言。本文将深入剖析如何从零开始,设计并实现一个能够调用 DeepSeek 模型的 C++ SDK。全过程涵盖了云端鉴权、面向对象架构设计、多态接口封装、单元测试体系构建以及 CMake 编译系统的配置。
一、云端环境配置与鉴权机制
大模型的调用始于服务提供商的鉴权流程。鉴权体系通常采用基于 OAuth2 或 API Key 的机制,确保服务调用的安全性与计费准确性。
注册完成后进入模型广场。在众多大语言模型中,选择目标版本。模型 ID(Model ID)是 API 调用中路由请求的关键标识符,系统后端依据此 ID 将请求分发至加载了特定权重的推理集群。
紧接着,在安全设置中生成 API Key。API Key 本质上是一个加密的凭证,通常包含用户标识和签名信息。在 HTTP 请求头中,它通常以 Bearer Token 的形式存在(Authorization: Bearer <API_KEY>)。该密钥必须严格保密,防止泄露导致额度被盗用。
查阅 API 文档是集成的核心步骤。文档定义了通信协议(HTTP/HTTPS)、请求方法(POST)、数据格式(JSON)以及服务端点(Endpoint)。
文档显示 Base URL 为 <MODEL_ENDPOINT>/v1/chat/completions。这表明该服务遵循 OpenAI 兼容的 API 规范,/v1/chat/completions 是标准的对话补全路由。这也决定了后续 C++ 代码中 HTTP 客户端需要支持 SSL/TLS 加密(即 HTTPS),因此引入 OpenSSL 库是架构设计的必要条件。
二、C++ SDK 核心数据结构设计
SDK 的健壮性取决于底层数据结构的设计。在 SDK/include/common.h 中,通过结构体(Struct)对业务实体进行抽象,利用 C++ 标准模板库(STL)管理内存资源。
1. 消息与配置实体
Message 结构体用于封装对话上下文。
_id:消息唯一标识,用于分布式追踪。
_role:区分 user(用户)、assistant(模型)或 system(系统指令)。
_content:实际文本载荷。
_timestamp:使用 std::time_t 记录时间,便于会话排序与审计。
Config 结构体定义了推理参数。
_temperature:双精度浮点数,控制采样随机性。0.7 是一个平衡创造性与准确性的典型值。
_maxTokens:限制输出长度,防止显存溢出或超长生成。
APIConfig 继承自 Config,体现了面向对象设计的'扩展性'。它在基础配置之上增加了 _apiKey,专门用于云端推理场景。这种设计允许未来扩展本地模型配置(如本地模型路径)而不污染基础配置结构。
2. 模型信息与会话管理
ModelInfo 结构体存储元数据,包括提供方(Provider)、服务端点(Endpoint)及可用性状态。布尔值 _isAvailable 是连接池健康检查的关键指标。
Session 结构体管理多轮对话的上下文。通过 std::vector<Message> 存储历史消息序列。在发送请求时,通常需要将此向量中的历史记录序列化为 JSON 数组,连同新问题一并发送,以维持 LLM 的'记忆'能力。
#pragma once
#include
ai_chat_sdk {
{
std::string _id;
std::string _role;
std::string _content;
std:: _timestamp;
( std::string& role, std::string& content)
: _role(role), _content(content) {}
};
{
std::string _modelName;
_temperature = ;
_maxTokens = ;
};
: Config {
std::string _apiKey;
};
{
std::string _modelName;
std::string _modelDesc;
std::string _provider;
std::string _endpoint;
_isAvailable = ;
( std::string& modelName = ,
std::string& modelDesc = ,
std::string& provider = ,
std::string& endpoint = )
: _modelName(modelName), _modelDesc(modelDesc),
_provider(provider), _endpoint(endpoint) {}
};
{
std::string _sessionId;
std::string _modelName;
std::vector<Message> _messages;
std:: _createAt;
std:: _updateAt;
( std::string& modelName = ) : _modelName(modelName) {}
};
}
<string>
#include <ctime>
#include <vector>
namespace
struct
Message
time_t
Message
const
const
struct
Config
double
0.7
int
2048
struct
APIConfig
public
struct
ModelInfo
bool
false
ModelInfo
const
""
const
""
const
""
const
""
struct
Session
time_t
time_t
Session
const
""
三、抽象接口层设计:策略模式的应用
为了支持多种后端(如 DeepSeek、ChatGPT、本地 Ollama),在 SDK/include/LLMProvider.h 中定义了抽象基类 LLMProvider。这是'依赖倒置原则'的典型应用,上层业务逻辑依赖于抽象接口,而非具体实现。
LLMProvider 声明了纯虚函数(Pure Virtual Functions),强制派生类必须实现这些行为:
initModel:接收 std::map 类型的配置参数,具有极高的灵活性,无需硬编码配置项。
sendMessage:同步阻塞式调用,适用于短文本生成。
sendMessageStream:流式响应接口。利用 std::function 回调机制,实现 Token 逐个返回。这对于提升用户体验至关重要,用户无需等待完整生成即可看到首字。
值得注意的是,代码中原有的 projected: 访问修饰符应修正为 protected:。这是 C++ 中用于限制成员变量仅对派生类可见的关键字。将 _apiKey 和 _endpoint 设为受保护成员,既保证了封装性,又允许子类直接访问这些基础资源。
#include <string.h>
#include <map>
#include <vector>
#include "common.h"
#include <functional>
namespace kk {
class LLMProvider {
public:
virtual bool 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;
virtual std::string sendMessage(const std::vector<Message>& messages,
const std::map<std::string, std::string>& requestParam) = 0;
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) = 0;
protected:
bool _isAvailable = false;
std::string _apiKey;
std::string _endpoint;
};
}
四、DeepSeek 适配器实现
DeepSeekProvider 类继承自 LLMProvider,具体实现了针对 DeepSeek 模型的业务逻辑。
1. 初始化逻辑
在 src/DeepSeekProvider.cpp 中,initModel 函数执行了防御性编程。它首先在传入的 modelConfig 映射中查找 apiKey 和 endpoint。若关键参数缺失,通过宏 ERR 记录错误日志并返回 false,防止系统在配置无效的状态下启动。只有当所有必要参数校验通过后,_isAvailable 标志位才会被置为 true。
2. 信息查询接口
getModelName 与 getModelDesc 提供了模型的自描述能力。
虽然提供的代码片段中省略了 sendMessage 的具体 HTTP 实现(通常涉及 libcurl 或 httplib 的调用、JSON 序列化与反序列化),但架构框架已经明确:接收 Message 向量,构建 JSON Payload,发起 HTTPS POST 请求,解析响应中的 choices[0].message.content,并处理可能的网络异常。
在 include 目录中创建一个 DeepSeekProvider.h 文件
#include "LLMProvider.h"
#include <string.h>
#include <map>
#include <vector>
#include "common.h"
namespace kk {
class DeepSeekProvider : public LLMProvider {
public:
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;
virtual std::string sendMessage(const std::vector<Message>& messages,
const std::map<std::string, std::string>& requestParam);
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);
};
}
然后去 src 中创建一个 DeepSeekProvider.cpp 文件
#include "../include/DeepSeekProvider.h"
#include "../include/util/myLog.h"
#include <httplib.h>
#include <json/json.h>
namespace kk {
bool DeepSeekProvider::initModel(const std::map<std::string, std::string>& modelConfig) {
auto it = modelConfig.find("apiKey");
if (it == modelConfig.end()) {
ERR("DeepSeekProvider initModel: apiKey not found");
return false;
}
_apiKey = it->second;
it = modelConfig.find("endpoint");
if (it == modelConfig.end()) {
ERR("DeepSeekProvider initModel: endpoint not found");
return false;
}
_endpoint = it->second;
_isAvailable = true;
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-v3.2";
}
std::string DeepSeekProvider::getModelDesc() const {
return "deepseek-chat 模型是一个基于 Transformer 架构的对话模型,由 DeepSeek 公司开发。它可以用于生成自然语言对话,支持多轮对话。";
}
std::string DeepSeekProvider::sendMessage(const std::vector<Message>& messages,
const std::map<std::string, std::string>& requestParam) {
httplib::Client cli(_endpoint);
Json::Value root;
Json::Value messages_json(Json::arrayValue);
for (const auto& msg : messages) {
Json::Value m;
m["role"] = msg._role;
m["content"] = msg._content;
messages_json.append(m);
}
root["messages"] = messages_json;
root["model"] = getModelName();
Json::StreamWriterBuilder builder;
const std::string json_str = Json::writeString(builder, root);
httplib::Headers headers;
headers["Content-Type"] = "application/json";
headers["Authorization"] = "Bearer " + _apiKey;
auto res = cli.Post("/v1/chat/completions", json_str, headers);
if (res && res->status == 200) {
Json::Value response;
std::istringstream iss(res->body);
iss >> response;
return response["choices"][0]["message"]["content"].asString();
}
return "";
}
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 sendMessage(messages, requestParam);
}
}
五、单元测试与质量保证
软件工程中,未经测试的代码不具备交付价值。本项目引入 Google Test (gtest) 框架进行单元测试。
1. 测试环境构建
在 testLLM.cpp 中,测试用例 TEST(DeepSeekProviderTest, sendMessage) 模拟了完整的调用流程。
- 智能指针管理:使用
std::make_shared 创建实例,自动管理生命周期,避免内存泄漏。
- 安全凭证注入:通过
std::getenv("deepseek_apikey") 从环境变量读取密钥。这是 DevOps 的最佳实践,严禁将敏感密钥硬编码在源码中。
- 断言机制:
ASSERT_TRUE 验证对象指针有效性及初始化结果。若断言失败,测试立即终止,便于快速定位崩溃点。
#include <gtest/gtest.h>
#include "../SDK/include/DeepSeekProvider.h"
#include "../SDK/include/util/myLog.h"
TEST(DeepSeekProviderTest, sendMessage) {
auto Provider = std::make_shared<kk::DeepSeekProvider>();
ASSERT_TRUE(Provider != nullptr);
std::map<std::string, std::string> modelParam;
const char* env_apikey = std::getenv("deepseek_apikey");
modelParam["apiKey"] = env_apikey ? env_apikey : "sk-xxxxxxxxxxxxxxxxxxxxxxxxxx";
modelParam["endpoint"] = "https://api.deepseek.com";
Provider->initModel(modelParam);
ASSERT_TRUE(Provider->isAvailable());
std::map<std::string, std::string> requestParam = {
{"temperature", "0.5"},
{"max_tokens", "2048"}};
std::vector<kk::Message> messages;
messages.push_back({"user", "你是谁?"});
std::string response = Provider->sendMessage(messages, requestParam);
ASSERT_TRUE(!response.empty());
}
int main(int argc, char** argv) {
kk::Logger::initLogger("testLLM", "stdout", spdlog::level::debug);
INFO("Test log message with value: {}", 42);
INFO("Test log message with two values: {} and {}", 10, 20);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
2. 日志系统
测试入口 main 函数中初始化了 spdlog 日志库。kk::Logger::initLogger 设置了日志级别为 debug,确保在开发阶段能捕获所有交互细节。testing::InitGoogleTest 负责解析命令行参数,驱动测试执行。
#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("[{:>10s}:{:<4d}] " format, __FILE__, __LINE__, ##__VA_ARGS__)
#define TRACE(format, ...) \
kk::Logger::getLogger()->log(spdlog::level::trace, "[{:>10s}:{:<4d}] " format, __FILE__, __LINE__, ##__VA_ARGS__)
#define INFO(format, ...) \
kk::Logger::getLogger()->info("[{:>10s}:{:<4d}] " format, __FILE__, __LINE__, ##__VA_ARGS__)
#define WARN(format, ...) \
kk::Logger::getLogger()->warn("[{:>10s}:{:<4d}] " format, __FILE__, __LINE__, ##__VA_ARGS__)
#define ERR(format, ...) \
kk::Logger::getLogger()->error("[{:>10s}:{:<4d}] " format, __FILE__, __LINE__, ##__VA_ARGS__)
#define CRIT(format, ...) \
kk::Logger::getLogger()->critical("[{:>10s}:{:<4d}] " format, __FILE__, __LINE__, ##__VA_ARGS__)
}
六、CMake 构建系统配置
C++ 项目的构建复杂性通过 CMake 进行管理。CMakeLists.txt 文件定义了编译规则与依赖关系。
1. 依赖管理
项目依赖 OpenSSL 进行 HTTPS 通信,依赖 spdlog 进行日志记录,依赖 gtest 进行测试,依赖 jsoncpp 处理数据格式。
find_package(OpenSSL REQUIRED) 指令在系统中查找 OpenSSL 库的头文件与二进制文件。若未安装,CMake 配置阶段将直接报错阻断。
# 设置 Cmake 的最小版本为 3.10
cmake_minimum_required(VERSION 3.10)
# 设置项目名称为 testLLM
project(testLLM)
# 设置 C++ 标准为 C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 不支持就报错
# 设置构建类型
set(CMAKE_BUILD_TYPE Debug)
# 添加可执行文件
add_executable(testLLM testKKLLM.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)
find_package(OpenSSL REQUIRED) # 找这个库,没找到就报错
include_directories(${OPENSSL_INCLUDE_DIR})
# 添加 CPPHTTPLIB_OPENSSL_SUPPORT 宏定义
target_compile_definitions(testLLM PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
# 链接库
target_link_libraries(testLLM spdlog gtest jsoncpp fmt OpenSSL::Crypto OpenSSL::SSL)
2. 编译目标与链接
add_executable 指令将源文件 DeepSeekProvider.cpp、myLog.cpp 和测试文件 testKKLLM.cpp 编译为可执行文件 testLLM。
target_link_libraries 将具体的第三方库链接至目标文件。特别注意 OpenSSL::Crypto 和 OpenSSL::SSL 的显式链接,这是实现安全套接层通信的基础。
项目目录结构清晰,遵循了 include (头文件) 与 src (源文件) 分离的标准布局,有利于大型项目的模块化管理。
七、编译与调试过程
在 build 目录下执行 cmake ..,CMake 读取上一级目录的配置,生成 Makefile。此步骤成功意味着环境依赖和路径配置均无误。
生成的构建工件包括 Makefile、CMakeCache.txt 等,实现了'外部构建'(Out-of-source build),保持源码目录整洁。
执行 make 命令触发实际编译。在初次尝试中,可能会遇到链接错误或头文件缺失,这通常表现为大量的编译器输出信息。
上图反映了典型的编译期错误。解决此类问题通常需要检查:
- 头文件路径是否通过
include_directories 正确包含。
- 命名空间是否匹配。
- 虚函数是否完全实现。
- 链接库顺序是否正确。
经过调试修正后,再次编译通过。运行 ./testLLM 执行测试程序。
控制台输出显示的绿色 [ PASSED ] 标志表明测试用例执行成功。日志中记录了初始化过程和测试值的输出,验证了 DeepSeekProvider 已正确加载配置,并且能够通过 HTTP 协议与云端 API 建立连接,完成了一次模拟的消息发送流程。
综上所述,构建一个生产级的 C++ LLM SDK 需要跨越网络协议、内存管理、设计模式与自动化测试等多个技术领域。通过严谨的架构设计与详尽的测试验证,能够确保 SDK 在复杂的生产环境中稳定运行。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online