據(jù)結(jié)構(gòu)設(shè)計(jì) 日志庫(kù)封裝 Provider分析與實(shí)現(xiàn))
歡迎來(lái)到實(shí)戰(zhàn)項(xiàng)目專(zhuān)欄 ~~ 從零實(shí)現(xiàn)AI大模型接入SDK博客主頁(yè)張小姐的貓~江湖背景所屬專(zhuān)欄C項(xiàng)目 ~ AI大模型接入SDK作者水平很有限如果發(fā)現(xiàn)錯(cuò)誤可在評(píng)論區(qū)指正感謝AI大模型接入SDK歡迎來(lái)到實(shí)戰(zhàn)項(xiàng)目專(zhuān)欄 ~~ 從零實(shí)現(xiàn)AI大模型接入SDK數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)消息結(jié)構(gòu)模型的公共配置信息 接入方式模型信息 會(huì)話信息日志庫(kù)概念與封裝Provider分析與實(shí)現(xiàn)策略模式LLMProvider寫(xiě)在最后數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)雖然各個(gè)模型不同但有?些公共的配置和描述信息比如通過(guò)api調(diào)用模型時(shí)需要模型名稱、溫度值、最大tokens數(shù)、apikey等在和模型聊天時(shí)聊天信息需要管理每次開(kāi)啟和模型的新一輪對(duì)話都是一次新的會(huì)話將來(lái)可能需要實(shí)現(xiàn)會(huì)話管理。這些數(shù)據(jù)在多個(gè)文件中都會(huì)用到因此提前先將這些數(shù)據(jù)結(jié)構(gòu)定義好以方便后續(xù)使用消息結(jié)構(gòu)消息結(jié)構(gòu)的構(gòu)造方法只需要給定兩個(gè)參數(shù)即可消息ID和時(shí)間戳是我們?cè)谶M(jìn)行創(chuàng)建的時(shí)候才會(huì)進(jìn)行填充的而消息ID通常由系統(tǒng)/會(huì)話管理器自動(dòng)生成不應(yīng)由調(diào)用方手填_timestamp一般是「創(chuàng)建時(shí)取當(dāng)前時(shí)間」也不該讓調(diào)用方傳入。創(chuàng)建一條「已知內(nèi)容」的用戶消息//消息結(jié)構(gòu)structMessage{std::string _messageId;//消息IDstd::string _role;//角色std::string _content;//消息內(nèi)容std::time_t _timestamp;//時(shí)間戳//構(gòu)造函數(shù)Message()default;Message(conststd::stringrole,conststd::stringcontent):_role(role),_content(content){}};模型的公共配置信息 接入方式接入方式有兩種通過(guò)API方式接入云端模型——繼承Config通過(guò)ollma方式接入本地模型 - 不需要api配置//模型的公共配置信息structConfig{std::string _modelName;//模型名稱double_temperature0.7;//溫度參數(shù),用來(lái)控制模型的輸出隨機(jī)性int_maxTokens2048;//最大輸出token數(shù)};//通過(guò)API方式接入云端模型structApiConfig:publicConfig{std::string _apiKey;//API密鑰};//通過(guò)ollma方式接入本地模型 - 不需要api配置模型信息 會(huì)話信息//LLM模型信息structLLMInfo{std::string _modelName;//模型名稱std::string _modelDesc;//模型描述std::string _provider;//模型提供方std::string _endpoint;//模型endpoint base urlbool_isAvailablefalse;//模型是否有效//構(gòu)造函數(shù)LLMInfo()default;LLMInfo(conststd::stringmodelName,conststd::stringmodelDesc,conststd::stringprovider,conststd::stringendpoint):_modelName(modelName),_modelDesc(modelDesc),_provider(provider),_endpoint(endpoint){}};//會(huì)話信息structSession{std::string _sessionId;//會(huì)話IDstd::string _modelName;//模型名稱std::vectorMessage_messages;//會(huì)話中的消息列表std::time_t _updatedAt;//會(huì)話最后更新時(shí)間戳std::time_t _createdAt;//會(huì)話創(chuàng)建時(shí)間戳//構(gòu)造函數(shù)Session()default;Session(conststd::stringmodelName)//創(chuàng)建時(shí)間是要我們創(chuàng)建會(huì)話時(shí)填入:_modelName(modelName){}};日志庫(kù)概念與封裝C中可以通過(guò)cout將信息打印到控制臺(tái)為什么還要封裝日志庫(kù)呢日志級(jí)別管理日志格式化日志存儲(chǔ)管理線程安全性能優(yōu)勢(shì)因此本項(xiàng)目采用google的spdlog日志庫(kù)進(jìn)行日志管理為了使用方便對(duì)spdlog庫(kù)采用單例模式進(jìn)行簡(jiǎn)單封裝myLog.h日志類(lèi)的頭文件fmt庫(kù)的使用方式hello,{}, world10s表示左對(duì)齊寬度為104d表示右對(duì)齊寬度為4VA_ARGS后續(xù)要拼接上的參數(shù)FILE會(huì)自動(dòng)獲取當(dāng)前文件名LINE會(huì)自動(dòng)獲取當(dāng)前行號(hào)二者都是宏#pragmaonce#includemutex#includespdlog/spdlog.hnamespacebite{classLogger{public://spdlog::level::level_enum 枚舉類(lèi)型用于設(shè)置日志級(jí)別默認(rèn)是debugstaticvoidinitLogger(conststd::stringloggerName,conststd::stringloggerFile,spdlog::level::level_enum levelspdlog::level::level_enum::info);staticstd::shared_ptrspdlog::loggergetLogger();private:Logger();Logger(constLogger)delete;//拷貝構(gòu)造函數(shù)被禁用Loggeroperator(constLogger)delete;//賦值運(yùn)算符被禁用private:staticstd::shared_ptrspdlog::logger_logger;staticstd::mutex _mutex;};//fmt庫(kù) 使用方式hello,{}, world// {:10} 表示右對(duì)齊寬度10文件名{:4} 表示左對(duì)齊寬度4行號(hào)// __FILE__ 會(huì)自動(dòng)獲取當(dāng)前文件名, __LINE__ 會(huì)自動(dòng)獲取當(dāng)前行號(hào)// ##__VA_ARGS__ 是GNU擴(kuò)展可變參數(shù)為空時(shí)自動(dòng)吞掉前面的逗號(hào)#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__:其他的可變參數(shù)#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那么后續(xù)我們?nèi)绾问褂眠@個(gè)DBG來(lái)打印出一串日志呢DBG(Database open successfully: {},localhost)預(yù)處理器做參數(shù)匹配formar ←Database open successfully: {}...可變參數(shù)←localhost即 __VA_ARGS__localhost替換進(jìn)myLog.h的宏體得到Logger::getLogger()-debug(std::string([{:10s}:{:4d}]Database open successfully: {}),__FILE__,// 預(yù)處理器替換為當(dāng)前源文件名如 /home/lbw/.../main.cpp__LINE__,// 預(yù)處理器替換為當(dāng)前行號(hào)如 42localhost);交給spdlog輸出套上 myLog.cpp 的pattern時(shí)間戳、級(jí)別后最終得到[2026:09:0115:30:12][debug][main.cpp:42]Database open successfully:localhostmyLog.cpp日志類(lèi)的實(shí)現(xiàn)#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){//設(shè)置全局自動(dòng)刷新級(jí)別當(dāng)日志等級(jí)達(dá)到指定級(jí)別時(shí)自動(dòng)刷新日志文件spdlog::flush_on(logLevel);// 啟動(dòng)異步日志將日志存放在隊(duì)列里由后臺(tái)線程負(fù)責(zé)寫(xiě)入文件//參數(shù)1隊(duì)列大小參數(shù)2線程數(shù)spdlog::init_thread_pool(32768,1);if(stdoutloggerFile){//創(chuàng)建一個(gè)帶顏色的輸出到控制臺(tái)的日志記錄器_loggerspdlog::stdout_color(loggerName);}else{//創(chuàng)建一個(gè)文件日志記錄器將日志寫(xiě)入指定文件_loggerspdlog::basic_logger_mtspdlog::async_factory(loggerName,loggerFile);}}//格式設(shè)置//[%H:%M:%S] 時(shí)分秒//[%n] 日志記錄器名稱//[%-7l] 日志等級(jí)左對(duì)齊寬度為7//%v 日志消息_logger-set_pattern([%H:%M:%S] [%n][%-7l] %v);_logger-set_level(logLevel);}std::shared_ptrspdlog::loggerLogger::getLogger(){return_logger;}}Provider分析與實(shí)現(xiàn)策略模式假設(shè)你現(xiàn)在要從宿舍去學(xué)校圖書(shū)館但宿舍到圖書(shū)館之間有?段距離你可以采用下面三種方法去實(shí)現(xiàn)策略方式實(shí)現(xiàn)定義?個(gè)接口TransportStrategy出行策略分別實(shí)現(xiàn)WalkStrategy、BikeStrategy、TaxiStrategy在運(yùn)行時(shí)你可以隨時(shí)切換策略classTransportStrategy{public:virtualvoidgo()0;};classWalkStrategy:publicTransportStrategy{public:virtualvoidgo()override{cout?路去機(jī)房;}};classBikeStrategy:publicTransportStrategy{public:virtualvoidgo()override{cout騎?去機(jī)房;}};classBusStrategy:publicTransportStrategy{public:virtualvoidgo()override{cout打?去機(jī)房;}};classStudent{private:TransportStrategy*strategy;public:voidsetStrategy(TransportStrategy*s){strategys;}voidgoToLab(){strategy-go();}};intmain(){Student me;me.setStrategy(newWalkStrategy());me.goToLab();// 輸出: ?路去機(jī)房me.setStrategy(newBusStrategy());me.goToLab();// 輸出: 打?去機(jī)房return0;}程序非常美觀且靈活在使用時(shí)只需和TransportStrategy打交道不需要知道背后到底是WalkStrategy、BikeStrategy或BusStrategy。如果想更換模式只需要更換?個(gè)具體的策略對(duì)象即可程序基本不需要改動(dòng)。策略模式是設(shè)計(jì)模式的?種它的核心思想是它定義了一些列算法將每一個(gè)算法(或行為)封裝起來(lái)使它們可以相互替換而不用再代碼中寫(xiě)一堆if-else/switch來(lái)決定用哪個(gè)算法。即把“做事的方式”抽象出來(lái)運(yùn)行時(shí)根據(jù)需要選擇哪種方式去執(zhí)行。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;//發(fā)送消息 —— 全量返回virtualvoidsendMessage(conststd::vectorMessagemessages,conststd::mapstd::string,std::stringrequestParam)0;//發(fā)送消息 —— 增量返回, 流式返回//messages: 消息列表//requestParam: 請(qǐng)求參數(shù)//callback: 對(duì)模型返回的增量?jī)?nèi)容如何進(jìn)行處理第一個(gè)為增量數(shù)據(jù)第二個(gè)為是否是最后一個(gè)增量virtualvoidsendMessageStream(conststd::vectorMessagemessages,conststd::mapstd::string,std::stringrequestParam,std::functionvoid(conststd::string,bool)callback)0;};protected:boolis_available_false;//標(biāo)記模型是否可用std::string _apikey;//模型的API密鑰std::string _endpoint;//模型的 base url}先實(shí)現(xiàn)這個(gè)LLMProvider的抽象類(lèi)后續(xù)再用具體的大模型比如DeepSeek來(lái)繼承它即可寫(xiě)在最后接下來(lái)登場(chǎng)的是deepseek接入封裝