🌈欢迎来到实战项目专栏 ~~ 从零实现AI大模型接入SDK
- 🌍博客主页:张小姐的猫~江湖背景
- 🔥所属专栏:C++项目 ~ AI大模型接入SDK
- 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
AI大模型接入SDK
- 🌈欢迎来到实战项目专栏 ~~ 从零实现AI大模型接入SDK
- 🌏 模型管理
- 🌏 会话管理
- 🔥会话介绍
- 🔥会话管理数据结构设计
- 🔥会话实现
- 📢写在最后
🌏 模型管理
现在deepseek-chat、gpt-5.6-sol 、gemini-3.7-flash模型已经接入成功,每个模型都对应单独的Provider类,为了后续使用简单,再封装⼀个LLMManager类将模型管理起来,后续通过多态的方式实现模型路由
LLMManager.h 的实现
#pragmaonce#include"LLMProvider.h"#include<map>#include<memory>namespaceai_chat_sdk{classLLMManager{public://注册LLM提供者voidregisterProvider(conststd::unique_ptr<LLMProvider>&provider);//初始化指定模型boolinitModel(conststd::string&modelName,conststd::map<std::string,std::string>&modelParams);//获取可用的模型std::vector<LLMInfo>getAvailableModels()const;//检测模型是否可用boolisModelAvailable(conststd::string&modelName)const;//发送全量信息给指定模型 (模型名,历史消息,请求参数)std::stringsendMessage(conststd::string&modelName,conststd::vector<Message>&messages,conststd::map<std::string,std::string>&requestParams);//发送流式信息给指定模型std::stringsendMessageStream(conststd::string&modelName,conststd::vector<Message>&messages,conststd::map<std::string,std::string>&requestParams,std::function<void(conststd::string&,bool)>callback);private://key:模型名称, value: 模型提供器 ~ 基类的指针std::map<std::string,std::unique_ptr<LLMProvider>>providers;//key:模型名称, value: 模型信息std::map<std::string,LLMInfo>modelInfos;};}LLMManager.cpp 的实现
#include"../include/util/LLMManager.h"#include"../include/util/myLog.h"#include"../include/util/Common.h"namespaceai_chat_sdk{//注册LLM提供者boolLLMManager::registerProvider(conststd::string&modelName,std::unique_ptr<LLMProvider>provider){//参数检测if(!provider){ERR("can not register a null provider, modelname = {}",modelName);returnfalse;}//因为unique_ptr是不允许赋值的, 但是可以进行移动构造//std::move把一个左值强制转成右值引用,说明不再需要这个对象了_providers[modelName]=std::move(provider);//添加模型信息_modelInfos[modelName]=LLMInfo(modelName);//模型初始化成功INFO("register provider success, modelname = {}",modelName);returntrue;}//初始化指定模型boolLLMManager::initModel(conststd::string&modelName,conststd::map<std::string,std::string>&modelParams){//检测模型是否注册了autoit=_providers.find(modelName);if(it==_providers.end()){ERR("model not registered, modelname = {}",modelName);returnfalse;}//模型注册成功,可以开始初始化了 ———— 调用模型自己的initModel方法boolisSuccess=it->second->initModel(modelParams);if(!isSuccess){ERR("model init failed, modelname = {}",modelName);}else{INFO("model init success, modelname = {}",modelName);_modelInfos[modelName]._modelDesc=it->second->getModelDesc();_modelInfos[modelName]._isAvailable=true;}returnisSuccess;}//获取可用的模型std::vector<LLMInfo>LLMManager::getAvailableModels()const{std::vector<LLMInfo>availableModels;// 可用的模型数组for(constauto&pair:_modelInfos){// 遍历所有模型信息if(pair.second._isAvailable){// 如果模型可用availableModels.push_back(pair.second);// 添加到可用模型数组}}returnavailableModels;}//检测模型是否可用boolLLMManager::isModelAvailable(conststd::string&modelName)const{autoit=_modelInfos.find(modelName);if(it==_modelInfos.end()){returnfalse;}returnit->second._isAvailable;// 返回模型是否可用}//发送全量信息给指定模型 (模型名,历史消息,请求参数)std::stringLLMManager::sendMessage(conststd::string&modelName,conststd::vector<Message>&messages,conststd::map<std::string,std::string>&requestParams){//检测模型是否注册了autoit=_providers.find(modelName);if(it==_providers.end()){ERR("model not registered, modelname = {}",modelName);return"";}//检测模型是否可用if(!it->second->isAvailable()){ERR("model not available, modelname = {}",modelName);return"";}//模型注册并且可用,发送消息returnit->second->sendMessage(messages,requestParams);}std::stringLLMManager::sendMessageStream(conststd::string&modelName,conststd::vector<Message>&messages,conststd::map<std::string,std::string>&requestParams,std::function<void(conststd::string&,bool)>callback){//检测模型是否注册了autoit=_providers.find(modelName);if(it==_providers.end()){ERR("model not registered, modelname = {}",modelName);return"";}//检测模型是否可用if(!it->second->isAvailable()){ERR("model not available, modelname = {}",modelName);return"";}//模型注册并且可用,发送消息returnit->second->sendMessageStream(messages,requestParams,callback);return"";}}🌏 会话管理
假设现在借助LLMManager搭建⼀个大模型后端服务,用户和模型进行了多轮会话,每个会话中都包含了好多条消息,在某个会话中,和模型聊天的多轮消息该如何管理?多个会话该如何管理?
解决该问题的⼀种方式是引入Session,每次和模型开启新一轮聊天,都可以看成是一次会话。
在一次会话中,会和模型进行多次聊天,这些聊天信息可以保存在本次会话中,除了保存会话信息外,还需要保存模型名称、会话创建时间等都需要保存
🔥会话介绍
会话是用户与大语言模型之间的⼀系列连续交互,它通过维护上下⽂和状态信息,确保对话的连贯性和⼀致性。由于大模型不会为用户管理会话信息,因此需要程序员手动完成会话管理
会话管理涉及以下内容:
注意:会话管理模块会保存所有的会话,在同一时刻,可能会对多个会话进行操作,因此创建会话、更新会话、删除会话等时需要考虑线程安全问题
🔥会话管理数据结构设计
//会话信息structSession{std::string _sessionId;//会话IDstd::string _modelName;//模型名称std::vector<Message>_messages;//会话中的消息列表std::time_t _updatedAt;//会话最后更新时间戳std::time_t _createdAt;//会话创建时间戳//构造函数Session()=default;Session(conststd::string&modelName="")//创建时间是要我们创建会话时填入:_modelName(modelName){}};🔥会话实现
SessionManager.h实现
实现有个细节:
- 有几个函数是设置成
const只读函数的,但是为了保证线程安全,仍需要加锁 mutable修饰锁,即使在 const 函数中也可以被修改(加锁/解锁),而其他成员变量仍然受 const 保护,不会被意外修改
#pragmaonce#include<atomic>#include<mutex>#include<memory>#include<unordered_map>#include"Common.h"namespaceai_chat_sdk{classSessionManager{public://创建会话,提供模型,返回会话idstd::stringcreateSession(conststd::string&modelName);//通过会话id获取会话信息std::shared_ptr<Session>getSession(conststd::string&SessionId)const;//往某个会话中添加消息booladdMessage(conststd::string&SessionId,constMessage&message);//获取某个会话的所有历史信息std::vector<Message>getHistoryMessages(conststd::string&SessionId)const;//更新会话的时间戳voidupdateSessionTimestamp(conststd::string&SessionId);//获取所有会话列表std::vector<std::string>getSessionLists()const;//删除某个会话booldelteSession(conststd::string sessionId);//清空所有会话voidclearAllSessions();//获取会话总数size_tgetSessionCount()const;private:std::stringgenerateSessionId();//生成会话idstd::stringgenerateMessageId(size_t messageCount);//生成消息idprivate://管理所有会话信息:key:会话id;value:会话信息std::unordered_map<std::string,std::shared_ptr<Session>>_sessions;mutablestd::mutex _mutex;//表明在当前成员变量_mutex在const成员函数当中也是允许被修改的!std::atomic<int64_t>_sessionCounter={0};//记录所有会话的总数};}SessionManager.cpp实现
fetch_add:_sessionCounter— 是类的成员变量,被多个线程共享,所以需要 std::atomic 保证线程安全;一般在调用generateMessageId前就已经是加过锁了,所以这里不需要原子操作- 创建会话是用智能指针去进行管理的,避免会话太多时,数据过大
- 往会话中添加消息:因为
message是const的,不能修改其_messageId,所以要创建一个复制体来更新其_messageId
//生成会话id 格式:session_时间戳_会话计数std::stringSessionManager::generateSessionId(){_sessionCounter.fetch_add(1);//原子操作,会话计数加1std::time_t time=std::time(nullptr);//获取当前时间戳std::ostringstream oss;//字符串流,写入将数据拼接成字符串// session_1672531200_0001:设置会话计数的宽度为4,不足4位的用0填充oss<<"session_"<<time<<"_"<<std::setw(4)<<std::setfill('0')<<_sessionCounter;returnoss.str();}//生成消息idstd::stringSessionManager::generateMessageId(size_t messageCount){messageCount++;std::time_t time=std::time(nullptr);//格式同上std::ostringstream oss;oss<<"msg_"<<time<<"_"<<std::setw(4)<<std::setfill('0')<<messageCount;returnoss.str();}//创建会话,提供模型,返回会话idstd::stringSessionManager::createSession(conststd::string&modelName){std::lock_guard<std::mutex>lock(_mutex);//生成会话idstd::string sessionId=generateSessionId();//创建会话,设置会话id、模型名称autosession=std::make_shared<Session>(modelName);session->_sessionId=sessionId;//加入到会话列表_sessions[sessionId]=session;returnsessionId;}//通过会话id获取会话信息std::shared_ptr<Session>SessionManager::getSession(conststd::string&sessionId)const{std::lock_guard<std::mutex>lock(_mutex);autoit=_sessions.find(sessionId);if(it==_sessions.end()){returnnullptr;}returnit->second;}//往某个会话中添加消息boolSessionManager::addMessage(conststd::string&sessionId,constMessage&message){std::lock_guard<std::mutex>lock(_mutex);//通过会话id获取会话信息autoit=_sessions.find(sessionId);if(it==_sessions.end()){returnfalse;}//添加消息 —— 因为message是const的,不能修改其_messageIdMessagemsg(message._role,message._content);msg._messageId=generateMessageId(it->second->_messages.size());//消息添加到会话中it->second->_messages.push_back(msg);//添加消息it->second->_updatedAt=std::time(nullptr);//更新会话时间戳INFO("Add message to session successfully: message_id {}, message_content {}",msg._messageId,msg._content);returntrue;}//获取某个会话的所有历史信息std::vector<Message>SessionManager::getHistoryMessages(conststd::string&sessionId)const{std::lock_guard<std::mutex>lock(_mutex);autoit=_sessions.find(sessionId);if(it==_sessions.end()){return{};}returnit->second->_messages;}//更新会话的时间戳voidSessionManager::updateSessionTimestamp(conststd::string&sessionId){std::lock_guard<std::mutex>lock(_mutex);autoit=_sessions.find(sessionId);if(it==_sessions.end()){return;}it->second->_updatedAt=std::time(nullptr);}获取会话列表实现:
获取所有会话列表, 实际返回的是所有会话的会话id
能否直接返回会话的对象呢?可以的但是不够友好,原因如下
- 直接返回会话对象,在返回时候会增加内存拷贝的开销,影响程序的运行效率
- SessionManager中基本都是通过会话id来操作对应的会话对象的,如果要返回会话对象的话,其他模块就要对会话对象进行操作,SessionManager和其他模块的耦合度增加,不便于后期维护;所以返回会话对象不友好!
- 并且会话信息存储在
sqlite中,获取会话历史消息、删除会话等,都是通过会话id来进行的,因此返回会话id更利于CURD,返回会话id会更好
注意:在返回的这里会话id中,最好将会话按照更新的时间戳进行降序排列(更加关心最近的聊天!)
//获取所有会话列表, 实际返回的是所有会话的会话idstd::vector<std::string>SessionManager::getSessionLists()const{std::lock_guard<std::mutex>lock(_mutex);//构造一个临时的会话列表,将其内容按照更新时间降序排列std::vector<std::pair<std::time_t,std::shared_ptr<Session>>>temp;temp.reserve(_sessions.size());//将所有会话信息复制到临时会话列表中for(constauto&pair:_sessions){temp.emplace_back(pair.second->_updatedAt,pair.second);}//按照更新时间降序排列:自定义比较器std::sort(temp.begin(),temp.end(),[](constauto&a,constauto&b){returna.first>b.first;});std::vector<std::string>sessionList;sessionList.reserve(temp.size());for(constauto&pair:temp){sessionList.push_back(pair.second->_sessionId);}returnsessionList;}剩下的函数实现 :删除会话、清理会话、获取会话总数
//删除会话boolSessionManager::delteSession(conststd::string sessionId){std::lock_guard<std::mutex>lock(_mutex);autoit=_sessions.find(sessionId);if(it==_sessions.end())returnfalse;_sessions.erase(it);returntrue;}//清空所有会话voidSessionManager::clearAllSessions(){std::lock_guard<std::mutex>lock(_mutex);_sessions.clear();}//获取会话总数size_tSessionManager::getSessionCount()const{std::lock_guard<std::mutex>lock(_mutex);return_sessions.size();}📢写在最后
接下来登场的是