
欢迎来到实战项目专栏 ~~ 从零实现AI大模型接入SDK博客主页张小姐的猫~江湖背景所属专栏C项目 ~ AI大模型接入SDK作者水平很有限如果发现错误可在评论区指正感谢AI大模型接入SDK欢迎来到实战项目专栏 ~~ 从零实现AI大模型接入SDK数据结构设计消息结构模型的公共配置信息 接入方式模型信息 会话信息日志库概念与封装Provider分析与实现策略模式LLMProvider写在最后数据结构设计虽然各个模型不同但有⼀些公共的配置和描述信息比如通过api调用模型时需要模型名称、温度值、最大tokens数、apikey等在和模型聊天时聊天信息需要管理每次开启和模型的新一轮对话都是一次新的会话将来可能需要实现会话管理。这些数据在多个文件中都会用到因此提前先将这些数据结构定义好以方便后续使用消息结构消息结构的构造方法只需要给定两个参数即可消息ID和时间戳是我们在进行创建的时候才会进行填充的而消息ID通常由系统/会话管理器自动生成不应由调用方手填_timestamp一般是「创建时取当前时间」也不该让调用方传入。创建一条「已知内容」的用户消息//消息结构structMessage{std::string _messageId;//消息IDstd::string _role;//角色std::string _content;//消息内容std::time_t _timestamp;//时间戳//构造函数Message()default;Message(conststd::stringrole,conststd::stringcontent):_role(role),_content(content){}};模型的公共配置信息 接入方式接入方式有两种通过API方式接入云端模型——继承Config通过ollma方式接入本地模型 - 不需要api配置//模型的公共配置信息structConfig{std::string _modelName;//模型名称double_temperature0.7;//温度参数,用来控制模型的输出随机性int_maxTokens2048;//最大输出token数};//通过API方式接入云端模型structApiConfig:publicConfig{std::string _apiKey;//API密钥};//通过ollma方式接入本地模型 - 不需要api配置模型信息 会话信息//LLM模型信息structLLMInfo{std::string _modelName;//模型名称std::string _modelDesc;//模型描述std::string _provider;//模型提供方std::string _endpoint;//模型endpoint base urlbool_isAvailablefalse;//模型是否有效//构造函数LLMInfo()default;LLMInfo(conststd::stringmodelName,conststd::stringmodelDesc,conststd::stringprovider,conststd::stringendpoint):_modelName(modelName),_modelDesc(modelDesc),_provider(provider),_endpoint(endpoint){}};//会话信息structSession{std::string _sessionId;//会话IDstd::string _modelName;//模型名称std::vectorMessage_messages;//会话中的消息列表std::time_t _updatedAt;//会话最后更新时间戳std::time_t _createdAt;//会话创建时间戳//构造函数Session()default;Session(conststd::stringmodelName)//创建时间是要我们创建会话时填入:_modelName(modelName){}};日志库概念与封装C中可以通过cout将信息打印到控制台为什么还要封装日志库呢日志级别管理日志格式化日志存储管理线程安全性能优势因此本项目采用google的spdlog日志库进行日志管理为了使用方便对spdlog库采用单例模式进行简单封装myLog.h日志类的头文件fmt库的使用方式hello,{}, world10s表示左对齐宽度为104d表示右对齐宽度为4VA_ARGS后续要拼接上的参数FILE会自动获取当前文件名LINE会自动获取当前行号二者都是宏#pragmaonce#includemutex#includespdlog/spdlog.hnamespacebite{classLogger{public://spdlog::level::level_enum 枚举类型用于设置日志级别默认是debugstaticvoidinitLogger(conststd::stringloggerName,conststd::stringloggerFile,spdlog::level::level_enum levelspdlog::level::level_enum::info);staticstd::shared_ptrspdlog::loggergetLogger();private:Logger();Logger(constLogger)delete;//拷贝构造函数被禁用Loggeroperator(constLogger)delete;//赋值运算符被禁用private:staticstd::shared_ptrspdlog::logger_logger;staticstd::mutex _mutex;};//fmt库 使用方式hello,{}, world// {:10} 表示右对齐宽度10文件名{:4} 表示左对齐宽度4行号// __FILE__ 会自动获取当前文件名, __LINE__ 会自动获取当前行号// ##__VA_ARGS__ 是GNU扩展可变参数为空时自动吞掉前面的逗号#defineTRACE(formar,...)Logger::getLogger()-trace(std::string([{:10s}:{:4d}]formar),__FILE__,__LINE__,##__VA_ARGS__)#defineDBG(formar,...)Logger::getLogger()-debug(std::string([{:10s}:{:4d}]formar),__FILE__,__LINE__,##__VA_ARGS__)//##__VA_ARGS__:其他的可变参数#defineINFO(formar,...)Logger::getLogger()-info(std::string([{:10s}:{:4d}]formar),__FILE__,__LINE__,##__VA_ARGS__)#defineERR(formar,...)Logger::getLogger()-error(std::string([{:10s}:{:4d}]formar),__FILE__,__LINE__,##__VA_ARGS__)#defineWARN(formar,...)Logger::getLogger()-warn(std::string([{:10s}:{:4d}]formar),__FILE__,__LINE__,##__VA_ARGS__)#defineCRITICAL(formar,...)Logger::getLogger()-trace(std::string([{:10s}:{:4d}]formar),__FILE__,__LINE__,##__VA_ARGS__)}//end bite那么后续我们如何使用这个DBG来打印出一串日志呢DBG(Database open successfully: {},localhost)预处理器做参数匹配formar ←Database open successfully: {}...可变参数←localhost即 __VA_ARGS__localhost替换进myLog.h的宏体得到Logger::getLogger()-debug(std::string([{:10s}:{:4d}]Database open successfully: {}),__FILE__,// 预处理器替换为当前源文件名如 /home/lbw/.../main.cpp__LINE__,// 预处理器替换为当前行号如 42localhost);交给spdlog输出套上 myLog.cpp 的pattern时间戳、级别后最终得到[2026:09:0115:30:12][debug][main.cpp:42]Database open successfully:localhostmyLog.cpp日志类的实现#include../include/util/myLog.h#includespdlog/spdlog.h#includespdlog/sinks/stdout_color_sinks.h#includespdlog/sinks/basic_file_sink.h#includespdlog/async.hnamespacebite{std::shared_ptrspdlog::loggerLogger::_loggernullptr;std::mutex Logger::_mutex;Logger::Logger(){}voidLogger::initLogger(conststd::stringloggerName,conststd::stringloggerFile,spdlog::level::level_enum logLevel){std::lock_guardstd::mutexlock(Logger::_mutex);//出了作用域就解锁if(_loggernullptr){//设置全局自动刷新级别当日志等级达到指定级别时自动刷新日志文件spdlog::flush_on(logLevel);// 启动异步日志将日志存放在队列里由后台线程负责写入文件//参数1队列大小参数2线程数spdlog::init_thread_pool(32768,1);if(stdoutloggerFile){//创建一个带颜色的输出到控制台的日志记录器_loggerspdlog::stdout_color(loggerName);}else{//创建一个文件日志记录器将日志写入指定文件_loggerspdlog::basic_logger_mtspdlog::async_factory(loggerName,loggerFile);}}//格式设置//[%H:%M:%S] 时分秒//[%n] 日志记录器名称//[%-7l] 日志等级左对齐宽度为7//%v 日志消息_logger-set_pattern([%H:%M:%S] [%n][%-7l] %v);_logger-set_level(logLevel);}std::shared_ptrspdlog::loggerLogger::getLogger(){return_logger;}}Provider分析与实现策略模式假设你现在要从宿舍去学校图书馆但宿舍到图书馆之间有⼀段距离你可以采用下面三种方法去实现策略方式实现定义⼀个接口TransportStrategy出行策略分别实现WalkStrategy、BikeStrategy、TaxiStrategy在运行时你可以随时切换策略classTransportStrategy{public:virtualvoidgo()0;};classWalkStrategy:publicTransportStrategy{public:virtualvoidgo()override{cout⾛路去机房;}};classBikeStrategy:publicTransportStrategy{public:virtualvoidgo()override{cout骑⻋去机房;}};classBusStrategy:publicTransportStrategy{public:virtualvoidgo()override{cout打⻋去机房;}};classStudent{private:TransportStrategy*strategy;public:voidsetStrategy(TransportStrategy*s){strategys;}voidgoToLab(){strategy-go();}};intmain(){Student me;me.setStrategy(newWalkStrategy());me.goToLab();// 输出: ⾛路去机房me.setStrategy(newBusStrategy());me.goToLab();// 输出: 打⻋去机房return0;}程序非常美观且灵活在使用时只需和TransportStrategy打交道不需要知道背后到底是WalkStrategy、BikeStrategy或BusStrategy。如果想更换模式只需要更换⼀个具体的策略对象即可程序基本不需要改动。策略模式是设计模式的⼀种它的核心思想是它定义了一些列算法将每一个算法(或行为)封装起来使它们可以相互替换而不用再代码中写一堆if-else/switch来决定用哪个算法。即把“做事的方式”抽象出来运行时根据需要选择哪种方式去执行。LLMProvider#pragmaonce#includemap#includestring#includevector#includefunctionnamespaceai_chat_sdk{classLLMProvider{public://初始化模型virtualboolinitModel(conststd::mapstd::string,std::stringconfig)0;//检查模型是否可用virtualboolisAvailable()const0;//获取模型名称virtualstd::stringgetModelName()const0;//获取模型描述virtualstd::stringgetModelDesc()const0;//发送消息 —— 全量返回virtualvoidsendMessage(conststd::vectorMessagemessages,conststd::mapstd::string,std::stringrequestParam)0;//发送消息 —— 增量返回, 流式返回//messages: 消息列表//requestParam: 请求参数//callback: 对模型返回的增量内容如何进行处理第一个为增量数据第二个为是否是最后一个增量virtualvoidsendMessageStream(conststd::vectorMessagemessages,conststd::mapstd::string,std::stringrequestParam,std::functionvoid(conststd::string,bool)callback)0;};protected:boolis_available_false;//标记模型是否可用std::string _apikey;//模型的API密钥std::string _endpoint;//模型的 base url}先实现这个LLMProvider的抽象类后续再用具体的大模型比如DeepSeek来继承它即可写在最后接下来登场的是deepseek接入封装