【项目】基于SDK实现的智能聊天助手(使用api接入deepseek)------(二)

一:数据结构的设计

我们需要定义一个文件,这个文件可以看作一个工具包,在各个后续文件中都可以看到!

文件名字:commod.h

1. 定义消息结构

消息结构就是我们与ai对话的内容,类似于这样

在背后,这个消息需要定义许多信息,例如:

1.消息ID

2.角色,如user

3.消息内容

4.消息发送的时间戳

cpp 复制代码
//消息结构
    struct message
    {
        std::string _messageId;     //消息id
        std::string _role           //角色
        std::string _content        //消息内容
        std::time_t _timeStamp      //消息发送时间戳 
        message(const std::string& role,const std::string& content)
            :_role(role),_content(role)
        {}
    };

2.两种方式接入模型

公共部分:

cpp 复制代码
    //模型的公共配置信息
    struct Config
    {
        std::string _modelName;     //模型名称
        int _tempratrue = 0.7;      //采样温度
        int _maxTokens = 2048;      //最大生成的字数的数量 
    };

模型名称:在对话前,可以选择不同的模型

采样温度: 介于 0 和 2 之间。更高的值,如 0.8,会使输出更随机,而更低的值,如 0.2,会使其更加集中和确定。

使用API接入云端模型

cpp 复制代码
    //通过API接入云端模型

    struct APIconfig : public Config
    {
        std::string _apiKey;               //接入云端的所需要密钥
    };

密钥:API

使用ollama接入我们自己的本地模型

.。。

3.模型描述信息

cpp 复制代码
    //模型描述信息
    struct ModelInfo
    {
        std::string _modelName;     //模型名称
        std::string _modelDesc;     //模型描述
        std::string _provider;      //模型提供的官方
        std::string _endPoint;      //模型base url  模型入口地点
        bool _isAvailable = falsel;  //模型是否可用

        ModelInfo(const std::string& modelName = "",const std::string& modelDesc= "",const std::string& provider= "",const std::string& endPoint= ""):
            _modelName(modelName),
            _modelDesc(modelDesc),
            _provider(provider),
            _endPoint(endPoint)
        {}
    };

_endPoint:模型服务的基础访问地址。

4.会话信息

cpp 复制代码
//会话信息
    struct Session
    {
        std::string _sessionId;     //会话id
        std::string _modelName;     //会话所正在使用的模型名称
        std::vector<message> _message;      //会话消息列表
        std::time_t _createAt;      //会议创建的时间戳
        std::time_t _updateAt;      //会话最后更新的时间戳

        Session(const std::string& modelName = "")
        :_modelName(modelName)
        
        {}

    };

二:日志的设计与封装

为什么对日志库进行封装

在 C++ 中,确实可以通过 std::cout 将信息打印到控制台,但在实际工程中,这种方式远远不足。大型项目通常都会封装日志库,因为它在可维护性、可控性、性能和可扩展性上都明显优于直接输出。

封装日志库的主要优势如下:

  1. 日志级别管理

成熟的日志库一般支持多种日志级别(TRACE、DEBUG、INFO、WARN、ERROR、FATAL 等)。

开发者可以根据运行环境灵活调整输出内容:

开发环境开启 DEBUG,打印详细调试信息

生产环境只保留 WARN、ERROR 等关键日志

std::cout 没有内置日志级别,所有输出混在一起,无法按等级过滤。

  1. 富的日志格式

日志库通常支持自动添加:

cpp 复制代码
时间戳
日志等级
线程 ID
文件名、行号
函数名

例如:

2025-09-24 10:00:00\] \[INFO\] \[main.cpp:123\] This is an info message

这些信息可以帮助快速定位问题。

而 std::cout 不提供格式化功能,需要开发者手动拼接,不仅繁琐还容易出错。

  1. 灵活的日志存储管理

日志库可以输出到多种目标:

1.控制台

2.文件

3.远程服务器

4.统一日志系统

5.并支持轮转、压缩、归档等功能,例如:

6.每天自动生成新的日志文件

7.文件超过指定大小后自动压缩

而 std::cout 只能打印到控制台,不具备日志管理能力。

  1. 线程安全

在多线程程序中,多个线程可能同时写入日志。

日志库通常提供内部锁或无锁队列来保证输出不会互相干扰。

std::cout 在多线程环境下很容易出现输出内容交错,开发者需要额外处理线程安全问题

  1. 性能优势

很多日志库都做了性能优化,例如:

异步写日志(减少主线程阻塞)

内部缓冲

批量写入文件

std::cout 属于同步输出,每次写都会阻塞当前线程,在高并发场景下性能表现较差。

日志具体代码

cpp 复制代码
//logger.h
#pragma once

#include <mutex>
#include <memory>
#include <spdlog/logger.h>
#include <spdlog/spdlog.h>

namespace zhao
{
    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& logger) = delete;//拷贝构造
        Logger& operator=(const Logger& logger) = delete;

        private:
        static std::shared_ptr<spdlog::logger> _logger ;
        static std::mutex _mutex;
    };


    #define TRACE(formate,...) zhao::Logger::getLogger()->trace(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
    #define DBG(formate,...) zhao::Logger::getLogger()->debug(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
    #define INFO(formate,...) zhao::Logger::getLogger()->info(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
    #define WARN(formate,...) zhao::Logger::getLogger()->warn(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
    #define ERR(formate,...) zhao::Logger::getLogger()->error(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
    #define CRIT(formate,...) zhao::Logger::getLogger()->critical(std::string("[{:>10s}:{:<4d}]")+format,__FILE__,__LINE__,##__VA_ARGS__)

}
cpp 复制代码
//logger.cc
#include"../include/util/myLog.h"
#include<memory>
#include<spdlog/spdlog.h>
#include<spdlog/sinks/basic_file_sink.h>
#include<spdlog/async.h>
#include <spdlog/sinks/stdout_color_sinks.h>



namespace zhao
{
    std::shared_ptr<spdlog::logger> Logger::_logger = nullptr;

    std::mutex Logger::_mutex;

    Logger::Logger()
    {}

    void Logger::initLogger(const std::string& LoggerName, const std::string& LoggerFile,spdlog::level::level_enum logLevel = spdlog::level::info)
    {
        if(_logger == nullptr)
        {
            std::lock_guard<std::mutex> lock(_mutex);
            if(_logger == nullptr)
            {
                //设置全局自动刷新级别,当日志的级别>=logLevel时候,日志会被立即刷新到文件
                spdlog::flush_on(logLevel);
                //启动异步日志,将日志信息存放到队列中,有后台线程负责写入
                // 参数1:队列大小 参数2:后台线程数量
                spdlog::init_thread_pool(32768,1);
                if(LoggerFile == "stdout")
                {
                    //创建一个带颜色的输出到控制台
                    _logger = spdlog::stdout_color_mt(LoggerName);
                }
                else
                {
                    //创建一个文件输出的日志器
                    _logger = spdlog::basic_logger_mt<spdlog::async_logger>(LoggerName,LoggerFile);
                }

            }
            _logger->set_pattern("[%H:%M:%S][%n][%-71]%v");
            _logger->set_level(logLevel);
        }
    }

    std::shared_ptr<spdlog::logger> Logger::getLogger()
    {
        return _logger;
    }

}

LLMProvider

在发消息之前,需要做一些准备工作。

后续会借助API的⽅式接⼊DeepSeek、ChatGPT、Gemini等⼤模型,每个⼤模型将来都需要:

a. 初始化

b. 检测模型是否有效

c. 发送消息给模型

d. 获取模型名称

e. 获取模型描述

f. 保存模型的有效状态、APIKey、模型描述。

操作基本都是相同的,只是实现细节上稍微不同,因此借助策略模式将接⼊模块架构设计如下:

定义抽象类

  1. 初始化模型

  2. 检测模型是否有效

  3. 发送消息给模型

  4. 获取模型名称

  5. 获取模型描述

代码:

cpp 复制代码
#pragma once
#include"commond.h"
#include<map>
#include<string>
#include<vector>
#include<functional>
namespace ai_chat_model
{
    class LLMProvider
    {
        public:
        //1. 初始化模型
        virtual void initModel(const std::map<std::string,std::string>& modelConfig) = 0;
        //2. 检测模型是否有效
        virtual bool isAvailable() const =0;
        //3. 发送消息给模型
            //1-全量返回
        virtual void sendMessage(const std::vector<Message>& message,std::map<std::string,
            std::string>& requestParam);
            //2-流式返回
        virtual void sendMessagestream(const std::vector<Message>& message,
            std::map<std::string, std::string>& requestParam,
            std::function<void(const std::string&, bool)> callBack);
        //4. 获取模型名称
        virtual std::string getModelName() const = 0;
        //5. 获取模型描述
        virtual std::string getModelDesc() const = 0;
        
        protected:

        bool _isAvailable = false; //模型是否有效
        std::string _apiKey;
        std::string _endPoint; //url
    };
}

deepseek请求具体实现

头文件:deepseekProvider.h

cpp 复制代码
#pragma once
#include"LLmProvider.h"

namespace ai_chat_model
{
    class DeepseekProvider : public LLMProvider
    {
        public:
        //1. 初始化模型
        virtual bool initModel(const std::map<std::string,std::string>& modelConfig);
        //2. 检测模型是否有效
        virtual bool isAvailable() const;
        //3. 发送消息给模型

            //1-全量返回
        virtual void sendMessage(const std::vector<Message>& message,std::map<std::string,
            std::string>& requestParam);

            //2-流式返回
        virtual void sendMessagestream(const std::vector<Message>& message,
            std::map<std::string, std::string>& requestParam,
            std::function<void(const std::string&, bool)> callBack);\

        //4. 获取模型名称
        virtual std::string getModelName() const;
        //5. 获取模型描述
        virtual std::string getModelDesc() const;

    };



}

方法具体实现代码:

deepseekProvider.cc

cpp 复制代码
#include"../include/deepseekProvider.h"
#include"../include/util/myLog.h"
#include<map>
#include<jsoncpp/json/json.h>
#include<httplib.h>
namespace ai_chat_model
{
        //1. 初始化模型
        bool DeepseekProvider::initModel(const std::map<std::string,std::string>& modelConfig) 
        {
            //判断是否有错误
            auto it1 = modelConfig.find("api_key");
            if(it1 == modelConfig.end())
            {
                ERR("apiKey not found!");
                return false;   
            }
            else
            {
                _apiKey = it1 ->second;
            }

            auto it2 = modelConfig.find("endPoint");
            if(it2 == modelConfig.end())
            {
                ERR("apiKey not found!");
                return false;   
            }
            else
            {
                _endPoint = it2->second;
            }
            _isAvailable = true;
            INFO("initModel sucess!");
            return true;
        }

        
        //2. 检测模型是否有效
        bool DeepseekProvider::isAvailable() const
        {
            return _isAvailable;
        }
        //3. 发送消息给模型

            //1-全量返回
        std::string DeepseekProvider::sendMessage(const std::vector<Message>& message,std::map<std::string,std::string>& requestParam)
        {
            INFO("Entering sendMessage");
            //1.检测模型是否可用
            if(_isAvailable == false)
            {
                ERR("deepseekModel is Not Available!");
                return "";
            }
            //2.构造请求参数(先定义默认的)
            double temperature = 0.7;
            int max_tokens = 2048; //可修改
            if(requestParam.find("temperature") !=requestParam.end())
            {
                temperature = std::stod(requestParam.find("temperature")->second);
            }
            if(requestParam.find("maxTokens") !=requestParam.end())
            {
                max_tokens = std::stod(requestParam.find("maxTokens")->second);
            }

            //构造历史消息??什么样的历史消息??max_taoken
            Json::Value messageArray(Json::arrayValue);
            for(const auto& messages : message)
            {
                Json::Value messageObject;
            messageObject["role"] = messages._role;
            messageObject["content"] = messages._content;
            messageArray.append(messageObject);
            }
            
            //构造请求体request  Json
            Json::Value requestBody;
            requestBody["model"] = getModelName(); //我们锁需要模型的名称
            requestBody["messages"] = messageArray;
            requestBody["temperature"] = temperature;
            requestBody["max_tokens"] = max_tokens;

            //序列化
            Json::StreamWriterBuilder writeBulder;
            std::string requestBodyStr = Json::writeString(writeBulder,requestBody);
            writeBulder["indentation"] = "";//有缩进

            //构造httplib客户端
            INFO("Creating httplib client with endpoint: {}", _endPoint);
            httplib::Client client(_endPoint.c_str());
            client.set_connection_timeout(30,0);//设置连接超时。如果 30 秒还没连上服务器,就放弃
            client.set_read_timeout(60,0);//设置读取超时。因为 AI 生成回复比较慢,这里给了 60 秒的耐心等待服务器返回数据。

            //设置请求头
            httplib::Headers headers = {
                {"Authorization","Bearer "+ _apiKey},
                {"Content-Type","application/json"}
            };

            //发送post请求
            INFO("Sending POST request to /v1/chat/completions");
            auto response = client.Post("/v1/chat/completions",headers,requestBodyStr,"application/json");
            if(!response)
            {
                ERR("deepseek response return fasle");
                return "";
            }
            INFO("deepseek response return success,status : {}",response->status);
            INFO("deepseek response return success,body : {}",response->body);

            //还需要检测相应是否成功
            if(response->status !=200)
            {
                return "";
            }

            //解析结构体
            Json::Value responseBody;
            Json::CharReaderBuilder readerBuilder;
            std::string parseError;
            std::istringstream responseStream(response->body);
            if(Json::parseFromStream(readerBuilder, responseStream, &responseBody, &parseError))
            {
                if(responseBody.isMember("choices") && responseBody["choices"].isArray() && !responseBody["choices"].empty())
                {
                    auto choice = responseBody["choices"][0];
                    if(choice.isMember("message") && choice["message"].isMember("content"))
                    {
                        std::string replyContent = choice["message"]["content"].asString();
                        INFO("DeepSeekProvider response text: {}", replyContent);
                        return replyContent;
                    }
                }
            }

            ERR("解析结构体失败");
            return "";

        }

            //2-流式返回
        std::string DeepseekProvider::sendMessagestream(const std::vector<Message>& message,
            std::map<std::string, std::string>& requestParam,
            std::function<void(const std::string&, bool)> callback)
            {
                //检测模型是否有效
                if(_isAvailable == false)
                {
                    ERR("Deepseek is not available!!!");
                    return "";
                }

                //构造请求参数
                double temperature = 0.7;
                int max_tokens = 2048;

                if(requestParam.find("temperature")!=requestParam.end())
                {
                    temperature = std::stod(requestParam.find("temperature")->second);
                }

                if(requestParam.find("max_tokens")!=requestParam.end())
                {
                    max_tokens = std::stod(requestParam.find("max_tokens")->second);
                }

                //构造历史消息
                Json::Value messageArray(Json::arrayValue);
                for(auto messages : message)
                {
                    Json::Value messageObject;
                    messageObject["role"] = messages._role;
                    messageObject["content"] = messages._content;
                    messageArray.append(messageObject);
                }

                //构造请求体
                Json::Value requestBody;
                requestBody["model"] = getModelName();
                requestBody["messages"] = messageArray;
                requestBody["max_tokens"] = max_tokens;
                requestBody["temperature"] = temperature;
                requestBody["stream"] = true;

                //序列化
                Json::StreamWriterBuilder writerBuilder;
                writerBuilder["indentation"] = "";
                std::string requestBodystr = Json::writeString(writerBuilder,requestBody);
                INFO("DeepseekProvider sendMessagestream responsbody:{}",requestBodystr);


                //使用cpp---httplib构造客户端
                httplib::Client client(_endPoint.c_str());
                client.set_connection_timeout(30,0);
                client.set_read_timeout(60,0);

                //设置请求头
                httplib::Headers headers = {
                    {"Authorization", "Bearer " + _apiKey},    // [0] 身份验证
                    {"Content-Type", "application/json"},      // [1] 发送内容格式
                    {"Accept", "text/event-stream"}            // [2] 期望接收格式
                };

                //设置流式处理变量
                std::string buffer; //接受流式响应的数据块
                bool gotError = false; //标记响应是否成功
                std::string errorMsgl; //错误描述符
                int statusCode = 0; //响应状态码
                bool streamFinish = false; //标记流式响应是否完成
                std::string fullResponse; //积累完整的流式响应

                //创建请求对象
                httplib::Request req;
                req.method = "POST";
                req.path = "/v1/chat/completions";
                req.headers = headers;
                req.body = requestBodystr;

                //设置响应处理器
                req.response_handler = [&](const httplib::Response& res)->bool{
                    if(res.status!=200)
                    {
                        gotError = true;
                        errorMsgl = "HTTP status code:" + std::to_string(res.status);
                        return false;
                    }
                    else
                    {   
                        return true;
                    }
                };

                req.content_receiver = [&](const char* data, size_t len, size_t offset, size_t totalLenght)->bool{
                    if(gotError)
                    return false;

                    buffer.append(data,len);
                    INFO("DeepseekProvider sendMessagestream buffer:{}",buffer);

                    size_t pos = 0;
                    while((pos = buffer.find("\n\n")) != std::string::npos)
                    {
                        //截取当前找到的数据块
                        std::string chunk = buffer.substr(0,pos);
                        buffer.erase(0,pos+2);//还有删除"\n\n"


                        //解析有效数据
                        //处理空行和注释
                        if(chunk.empty()||chunk[0] == ':')
                        {
                            continue;
                        }

                        if(chunk.compare(0,6,"data: ") == 0)
                        {
                            std::string modelData = chunk.substr(6);//把data切掉
                            if(modelData == "[DONE]")
                            {
                                streamFinish = true;
                                return true;
                            }
                                                    // 反序列化
                            Json::Value modelDataJson;
                            Json::CharReaderBuilder reader;
                            std::string errors;
                            std::istringstream modelDataStream(modelData);

                            if (Json::parseFromStream(reader, modelDataStream, &modelDataJson, &errors)) 
                            {
                                // 模型返回的 JSON 格式数据现在就保存在 modelDataJson 中
                                // 逐层安全检查:choices -> isArray -> !empty -> delta -> content
                                if (modelDataJson.isMember("choices") && 
                                    modelDataJson["choices"].isArray() && 
                                    !modelDataJson["choices"].empty() && 
                                    modelDataJson["choices"][0].isMember("delta") && 
                                    modelDataJson["choices"][0]["delta"].isMember("content")) 
                                    {
                                    
                                        std::string content = modelDataJson["choices"][0]["delta"]["content"].asString();
                                        
                                        // 处理 deltaContent,例如追加到全文缓存
                                        fullResponse += content;

                                        // 将本次解析出的模型返回的有效数据转给调用 sendMessageStream 函数的用户使用 --- callback
                                        callback(content, false);
                                    }
                            } 
                            else 
                            {
                                // 解析失败,输出警告日志
                                WARN("DeepSeekProvider sendMessageStream parse modelDataJson error: {}", errors);
                            }


                        }

                    }
                    return true;
                };
                

                // 给模型发送请求
                auto result = client.send(req);
                if(!result){
                    // 请求发送失败,出现网络问题,比如DNS解析失败、连接超时
                    ERR("Network error {}", (int)result.error());
                    return "";
                }

                // 确保流式操作正确结束
                if(!streamFinish){
                    WARN("stream ended without [DONE] marker");
                    callback("", true);
                }

                return fullResponse;

            }//

        //4. 获取模型名称
        std::string DeepseekProvider::getModelName() const
        {
            return "deepseek-chat";
        }
        //5. 获取模型描述
        std::string DeepseekProvider::getModelDesc() const
        {
            return "由深度求索创造的免费AI助手,热情细腻";
        }
} // namespace 
相关推荐
leiming62 小时前
c++ 容器 queue
开发语言·c++
CSDN_RTKLIB2 小时前
【类定义系列三】内联函数进阶
开发语言·c++
北京流年2 小时前
执行clang --version报错说由于找不到 libgcc_s_seh-1.dll和 找不到 libstdc++-6.dll 这两个文件
开发语言·c++
职业码农NO.13 小时前
系统架构设计中的 15 个关键取舍
设计模式·架构·系统架构·ddd·架构师·设计规范·领域驱动
fpcc3 小时前
跟我学C++中级篇—Linux内核中链表分析
linux·c++·链表
燕双嘤3 小时前
LLM:RAG,设计模式,Agent框架
人工智能·机器学习·设计模式
挖矿大亨3 小时前
C++中的this指针
java·开发语言·c++
阿拉斯攀登3 小时前
设计模式:构建者模式
设计模式·建造者模式·构建者模式
姜糖编程日记3 小时前
C++——初识(2)
开发语言·前端·c++