基于C++构建DeepSeek大模型推理SDK:从架构设计到工程落地

基于C++构建DeepSeek大模型推理SDK:从架构设计到工程落地

这里写目录标题

前言

在高性能计算与大模型(LLM)应用开发的浪潮中,C++凭借其卓越的内存管理能力和运行时效率,成为了构建底层推理SDK的首选语言。本文将深入剖析如何从零开始,设计并实现一个能够调用DeepSeek模型的C++ SDK。全通过程涵盖了云端鉴权、面向对象架构设计、多态接口封装、单元测试体系构建以及CMake编译系统的配置。

一、 云端环境配置与鉴权机制

大模型的调用始于服务提供商的鉴权流程。本次实战选用蓝耘(Lanyun)作为算力与模型服务平台。鉴权体系通常采用基于OAuth2或API Key的机制,确保服务调用的安全性与计费准确性。

访问控制台注册页面,完成账户初始化。

https://console.lanyun.net/#/register?promoterCode=5663b8b127
image.png

注册完成后进入模型广场。在众多大语言模型中,选择DeepSeek-V3.2版本。模型ID(Model ID)是API调用中路由请求的关键标识符,系统后端依据此ID将请求分发至加载了特定权重的推理集群。此处记录模型ID为 /maas/deepseek-ai/DeepSeek-V3.2

image.png

紧接着,在安全设置中生成API Key。API Key本质上是一个加密的凭证,通常包含用户标识和签名信息。在HTTP请求头中,它通常以Bearer Token的形式存在(Authorization: Bearer <API_KEY>)。该密钥必须严格保密,防止泄露导致额度被盗用。

image.png

查阅API文档是集成的核心步骤。文档定义了通信协议(HTTP/HTTPS)、请求方法(POST)、数据格式(JSON)以及服务端点(Endpoint)。
文档显示Base URL为 https://maas-api.lanyun.net/v1/chat/completions。这表明该服务遵循OpenAI兼容的API规范,/v1/chat/completions 是标准的对话补全路由。这也决定了后续C++代码中HTTP客户端需要支持SSL/TLS加密(即HTTPS),因此引入OpenSSL库是架构设计的必要条件。

image.png

二、 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的“记忆”能力。
ai_-model_-sdk/SDK/include/common.h

在这里插入图片描述
#pragmaonce#include<string>#include<ctime>#include<vector>namespace ai_chat_sdk {//消息结构structMessage{ std::string _id;//消息id std::string _role;//消息角色 std::string _content;//消息内容 std::time_t _timestamp;//消息时间戳//构造函数Message(const std::string& role,const std::string& content):_role(role),_content(content){}//模型的公共配置信息};structConfig{ std::string _modelName;//模型名称double _temperature=0.7;//温度参数,用来控制模型的生成随机性,默认值为0.7int _maxTokens=2048;//最大token数,用来控制模型的生成长度,默认值为2048};//通过API方式接入云端模型structAPIConfig:publicConfig//继承Config{ std::string _apiKey;//api key};//通过ollama接入本地模型---不需要apikey//LLM模型信息structModelInfo{ std::string _modelName;//模型名称 std::string _modelDesc;//模型描述信息 std::string _provider;//模型提供方 std::string _endpoint;//模型访问地址 base urlbool _isAvailable=false;//模型是否可用,默认值为falseModelInfo(const std::string& modelName="",const std::string& modelDesc="",const std::string& provider="",const std::string& endpoint=""):_modelName(modelName),_modelDesc(modelDesc),_provider(provider),_endpoint(endpoint){}};//会话结构structSession{ std::string _sessionId;//会话id std::string _modelName;//模型名称 std::vector<Message> _messages;//会话消息列表 std::time_t _createAt;//会话创建时间戳 std::time_t _updateAt;//会话更新时间戳//构造函数Session(const std::string& modelName=""):_modelName(modelName){}};}//end ai_chat_sdk

三、 抽象接口层设计:策略模式的应用

为了支持多种后端(如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 设为受保护成员,既保证了封装性,又允许子类直接访问这些基础资源。

在这里插入图片描述

ai_-model_-sdk/SDK/include/LLMprovider.h

#include<string.h>#include<map>#include<vector>#include"common.h"#include<functional>namespace kk {//LLMProvider类classLLMProvider{public://初始化模型virtualboolinitModel(const std::map<std::string,std::string>& modelConfig)=0;//初始化模型,传入模型配置参数:模型名称、模型路径、模型参数等//检查模型是否可用virtualboolisAvailable()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;//第三个参数为回调函数,这个回调函数第一个参数是模型返回的数据,第二个参数为是否为最后一个响应//callback:增量返回的每一个token,以及是否是最好一个token projected:bool _isAvailable=false;//模型是否可用 std::string _apiKey;//模型API密钥 std::string _endpoint;//模型API地址};}

四、 DeepSeek 适配器实现

DeepSeekProvider 类继承自 LLMProvider,具体实现了针对DeepSeek模型的业务逻辑。

1. 初始化逻辑

src/DeepSeekProvider.cpp 中,initModel 函数执行了防御性编程。它首先在传入的 modelConfig 映射中查找 apiKeyendpoint。若关键参数缺失,通过宏 ERR 记录错误日志并返回 false,防止系统在配置无效的状态下启动。只有当所有必要参数校验通过后,_isAvailable 标志位才会被置为 true

2. 信息查询接口

getModelNamegetModelDesc 提供了模型的自描述能力。

image.png

上图展示了代码中返回模型名称的实现细节。这不仅仅是返回一个字符串,它在多模型混合调度的系统中,是路由分发和日志记录的重要依据。

虽然提供的代码片段中省略了 sendMessage 的具体HTTP实现(通常涉及 libcurlhttplib 的调用、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 {//LLMProvider类classDeepSeekProvider:publicLLMProvider{public://初始化模型virtualvoidinitModel(const std::map<std::string,std::string>& modelConfig);//初始化模型,传入模型配置参数:模型名称、模型路径、模型参数等//检查模型是否可用virtualboolisAvailable()const;//获取模型名称virtual std::string getModelName()const;// 获取模型描述virtual std::string getModelDesc()const;//发送消息 ---全量返回 非流式响应 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,//第一个参数为消息列表,第二个参数为请求参数virtual std::function<void(const std::string&,bool)> callback);//第三个参数为回调函数,这个回调函数第一个参数是模型返回的数据,第二个参数为是否为最后一个响应//callback:增量返回的每一个token,以及是否是最好一个token};}

直接就是集成LLMProvider这个类

然后去src中创建一个DeepSeekProvider.cpp文件

#include"../include/DeepSeekProvider.h"#include"../include/util/myLog.h"namespace kk {//初始化模型boolDeepSeekProvider::initModel(const std::map<std::string,std::string>& modelConfig){//初始化API keyauto it =modelConfig.find("apiKey");if(it==modelConfig.end())//没找到apikey的话{ERR("DeepSeekProvider initModel:apiKey not found");//打印一个错误日志returnfalse;} _apiKey=it->second;//找到了的话就给apikey进行一个赋值操作//初始化endpoint it=modelConfig.find("endpoint");if(it==modelConfig.end())//没找到endpoint的话{ERR("DeepSeekProvider initModel:endpoint not found");//打印一个错误日志returnfalse;} _endpoint=it->second;//找到了的话就给endpoint进行一个赋值操作 _isAvailable=true;//模型可用//初始化成功,打印日志INFO("DeepSeekProvider initModel:success,apiKey:%s,endpoint:%s",_apiKey.c_str(),_endpoint.c_str());returntrue;}//检测模型是否可用boolDeepSeekProvider::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)//第一个参数为消息列表,第二个参数为请求参数{}//发送消息 ---全量返回 流式响应 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)//第三个参数为回调函数,这个回调函数第一个参数是模型返回的数据,第二个参数为是否为最后一个响应//callback:增量返回的每一个token,以及是否是最好一个token{}}

五、 单元测试与质量保证

软件工程中,未经测试的代码不具备交付价值。本项目引入 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"//测试DeepSeekProvider的initModel方法TEST(DeepSeekProviderTest,sendMessage)//第一个参数是测试用例的名称,第二个参数是测试用例的描述{auto Provider=std::make_shared<kk::DeepSeekProvider>();//创建一个DeepSeekProvider对象的智能指针ASSERT_TRUE(Provider!=nullptr);//断言Provider对象不为空指针 std::map<std::string,std::string> modelParam;constchar* env_apikey = std::getenv("deepseek_apikey"); modelParam["apiKey"]= env_apikey ? env_apikey :"sk-xxxxxxxxxxxxxxxxxxxxxxxxxx"; modelParam["endpoint"]="https://maas-api.lanyun.net";//实例化DeepSeekProvider对象 Provider->initModel(modelParam);//初始化模型,传入模型配置映射ASSERT_TRUE(Provider->isAvailable());//断言Provider对象不为空指针 std::map<std::string,std::string> requestParam={//创建一个模型配置映射,包含temperature和max_tokens{"temperature","0.5"},{"max_tokens","2048"}}; std::vector<kk::Message> messages; messages.push_back({"user","你是谁?"});//添加一个用户消息//调用sendMessage方法 std::string response=Provider->sendMessage(messages,requestParam);//传入消息列表和请求参数,返回模型的响应ASSERT_TRUE(!response.empty());//断言响应字符串不为空}intmain(int argc,char**argv){//初始化spdlog日志库 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);//初始化gtest库 testing::InitGoogleTest(&argc, argv);//调用初始化Google Test框架,将命令行参数传递给它//执行所有的测试用例returnRUN_ALL_TESTS();}

2. 日志系统

测试入口 main 函数中初始化了 spdlog 日志库。kk::Logger::initLogger 设置了日志级别为 debug,确保在开发阶段能捕获所有交互细节。testing::InitGoogleTest 负责解析命令行参数,驱动测试执行。

#pragmaonce#include<spdlog/spdlog.h>namespace kk {//单例模式classLogger{public://获取单例实例staticvoidinitLogger(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;//静态互斥锁};//调试日志宏//format:日志格式//...:可变参数//__FILE__:当前文件名//__LINE__:当前行号//将文件名和行号格式化为[文件名:行号]的形式//__VA_ARGS__:可变参数//将格式化后的字符串和可变参数传递给日志器的debug方法//09:04:03 [aiChat] [DEBUG] [/home/ubuntu/ai_-model_-sdk/SDK/src/util/myLog.cpp:123] 这是一条调试日志//DBG("这是一条调试日志":{}:{},dbName,dbType)#defineDBG(format,...)kk::Logger::getLogger()->debug("[{:>10s}:{:<4d}] "format,__FILE__,__LINE__,##__VA_ARGS__)//定义其他日志级别宏#defineTRACE(format,...)kk::Logger::getLogger()->log(spdlog::level::trace,"[{:>10s}:{:<4d}] "format,__FILE__,__LINE__,##__VA_ARGS__)#defineINFO(format,...)kk::Logger::getLogger()->info("[{:>10s}:{:<4d}] "format,__FILE__,__LINE__,##__VA_ARGS__)#defineWARN(format,...)kk::Logger::getLogger()->warn("[{:>10s}:{:<4d}] "format,__FILE__,__LINE__,##__VA_ARGS__)#defineERR(format,...)kk::Logger::getLogger()->error("[{:>10s}:{:<4d}] "format,__FILE__,__LINE__,##__VA_ARGS__)#defineCRIT(format,...)kk::Logger::getLogger()->critical("[{:>10s}:{:<4d}] "format,__FILE__,__LINE__,##__VA_ARGS__)}//end kk

六、 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.cppmyLog.cpp 和测试文件 testKKLLM.cpp 编译为可执行文件 testLLM
target_link_libraries 将具体的第三方库链接至目标文件。特别注意 OpenSSL::CryptoOpenSSL::SSL 的显式链接,这是实现安全套接层通信的基础。

项目目录结构清晰,遵循了 include (头文件) 与 src (源文件) 分离的标准布局,有利于大型项目的模块化管理。

image.png

七、 编译与调试过程

build 目录下执行 cmake ..,CMake 读取上一级目录的配置,生成 Makefile。此步骤成功意味着环境依赖和路径配置均无误。

image.png

生成的构建工件包括 Makefile、CMakeCache.txt 等,实现了“外部构建”(Out-of-source build),保持源码目录整洁。

image.png

执行 make 命令触发实际编译。在初次尝试中,可能会遇到链接错误或头文件缺失,这通常表现为大量的编译器输出信息。

image.png

上图反映了典型的编译期错误。解决此类问题通常需要检查:

  1. 头文件路径是否通过 include_directories 正确包含。
  2. 命名空间是否匹配。
  3. 虚函数是否完全实现。
  4. 链接库顺序是否正确。

经过调试修正后,再次编译通过。运行 ./testLLM 执行测试程序。

image.png

控制台输出显示的绿色 [ PASSED ] 标志表明测试用例执行成功。日志中记录了初始化过程和测试值的输出,验证了 DeepSeekProvider 已正确加载配置,并且能够通过 HTTP 协议与云端 API 建立连接,完成了一次模拟的消息发送流程。

综上所述,构建一个生产级的C++ LLM SDK需要跨越网络协议、内存管理、设计模式与自动化测试等多个技术领域。通过严谨的架构设计与详尽的测试验证,能够确保SDK在复杂的生产环境中稳定运行。

Read more

企业微信外部群“群机器人”主动推送消息实现指南

QiWe开放平台 · 开发者名片                 API驱动企微自动化,让开发更高效         核心能力:企微二次开发服务 | 多语言接入 | 免Root授权         官方站点:https://www.qiweapi.com(功能全景)         开发文档:https://doc.qiweapi.com(开发指南)         团队定位:专注企微API生态的技术服务团队        对接通道:搜「QiWe 开放平台」联系客服         核心理念:合规赋能,让企微开发更简单、更高效 在企业微信的生态开发中,针对外部群(包含微信用户的群聊)进行自动化消息推送,最稳健且合规的方式是利用群机器人(Webhook)。本文将从技术逻辑、核心步骤及注意事项三个维度,分享如何实现这一功能。 一、 实现逻辑简述 企业微信外部群机器人主要通过一个唯一的 Webhook 地址 接收标准的 HTTP POST 请求。开发者只需将构造好的

By Ne0inhk
共绩算力 RTX 5090 极速部署 Stable Diffusion WebUI:新手也能秒开 AI 绘图工作站

共绩算力 RTX 5090 极速部署 Stable Diffusion WebUI:新手也能秒开 AI 绘图工作站

还在为本地硬件不足跑不动 AI 绘图模型发愁?想快速拥有高性价比的 Stable Diffusion 绘图环境?今天给大家带来共绩算力 RTX 5090 部署 Stable Diffusion WebUI(增强版)的详细教程,全程零兼容冲突,从云主机配置到生成第一张 AI 画作仅需 30 分钟,步骤清晰可复现,无论是设计爱好者还是 AI 新手都能轻松上手! 目录 一、为什么选择共绩算力部署 Stable Diffusion? 二、环境准备:精准配置云主机 2.1 创建云主机实例 1.2 登录云主机终端 二、完整部署流程 2.1 环境清理与依赖安装 2.2 下载与配置Stable Diffusion WebUI

By Ne0inhk
高端播控去 FPGA 化,电鱼智能 RK3588 提供单芯片 8K 编解码架构

高端播控去 FPGA 化,电鱼智能 RK3588 提供单芯片 8K 编解码架构

什么是 电鱼智能 EFISH-SBC-RK3588? 电鱼智能 EFISH-SBC-RK3588 是一款旗舰级多媒体核心平台,搭载 Rockchip RK3588 SoC。 对于播控行业,它不仅是一颗 CPU,更是一个**“异构视频工作站”**: * VPU:支持 8K @ 60fps H.265/AV1 视频硬解码,及 8K @ 30fps 编码。 * RGA (Raster Graphic Acceleration):这是替代 FPGA 的关键。它是一个独立的 2D 硬件加速器,专职负责图像的裁剪 (Crop)、缩放 (Resize)、旋转 (Rotate) 和 格式转换 (Format Convert),效率远超 GPU,且不占用

By Ne0inhk