目录

一、api接入大模型思路

1.1 API key 获取

1.2 apifox

1.3 deep seek的api使用

1.4 ChatGPT的api获取

​编辑

1.5 Gemini的api获取

二、数据结构设计

三、日志库封装

四、模型Provider

4.1 策略模式

4.2 LLMProvider

五、deepseek接入封装

5.1 deep seek提供api

5.2 大模型初始化

5.3 发送消息全量返回

5.4 发送消息-全量返回测试

5.5 流式响应

5.6 发送消息-流式返回

5.7 发送消息-流式返回测试


一、api接入大模型思路

⼤模型需要强⼤的算法⽀持,训练⼀个⼤模型的成本太⾼,动辄⼏万甚⾄上百万,⽽且中间技术⻔槛也⽐较⾼,普通⽤⼾本地部署成本很⾼。为了降低⼤家的使⽤⻔槛,各个⼤模型⼚商都开放了基于HTTP协议的⼤模型的API接⼝,不仅可以让企业或个⼈⽅便快速集成⼤模型能⼒,专注业务开发,构建⾃⼰个性化的服务,也能吸引⼤量开发者参与开发出各种应⽤服务,还能凑近整个⽣态的繁荣。目前就是两种方式:
云端接入:通过官方提供的api接入云端大模型---deepseek:提供了一个网页的产品,提供给普通大众使用,还有操作模型的api,供给程序员使用。
优势:开箱即用,零门槛、模型顶级、能力强大、成本可控、持续更新。
劣势:有数据安全风险,持续成本(每次都要充值),网络延迟。
本地部署:借助ollama工具部署在本地。
优势:绝对的数据安全、无需持续付费、网络延迟问题、对模型的完全可控。
劣势:硬件成本,模型性能,运维和技术门槛,模型需要手动更新。

1.1 API key 获取

API Key是是⼀种⽤于⾝份验证和授权的密钥,本质就是⼀个通过特定算法⽣成的字符串,主要⽤于:
  • ⾝份验证:验证你的应⽤程序是否被授权,只有拥有API Key的⽤⼾才能调⽤⼤模型的API
  • 授权:限制应⽤程序对特定功能或资源的访问权限。⽐如:某些⾼级功能需要特定API Key才能访
  • 监控和限制:监控API的使⽤情况,⽐如统计请求次数、限制请求频率,以防滥⽤
  • 计费:根据API的使⽤情况,向用户收取费⽤

例子:

考上⼤学后,家⻓为⽅便给你提供⽣活费以及学费,帮你办了⼀张某银⾏的银⾏卡,拿着银⾏卡就可以享受该银⾏提供的查询、取钱、转账等服务。
API Key:就像银⾏卡,每次办理业务时都需要带上银⾏卡
⾝份认证:你去银⾏柜台办理业务,柜员会要求你出⽰银⾏卡。刷卡或读卡的过程就是验证这张卡是否真实、有效,是否属于本⾏
授权:你的普通储蓄卡只能存取款和转账,⽽你⽗⺟的VIP⿊⾦卡还能享受机场贵宾厅、专属理财经理等服务。卡的等级决定了你能做什么
监控和限制:银⾏会监控每张卡的交易。你的银⾏卡可能有单⽇转账限额1万元的规定,防⽌卡⽚被盗后损失过⼤
计费:银⾏会根据你的账⼾流⽔向你收取⼿续费、短信通知费等。

点击右下角创建就能获取API key。

官方文档能够查看价格和如何使用。

1.2 apifox

APIfox官网:Apifox - API 文档、调试、Mock、测试一体化协作平台。拥有接口文档管理、接口调试、Mock、自动化测试等功能,接口开发、测试、联调效率,提升 10 倍。最好用的接口文档管理工具,接口自动化测试工具。

进入官网点击下载就行,下载也很简单,一路点击next就行。

1.3 deep seek的api使用

登录完成后

点击新建项目

进入项目在左边的接口那里点击新建,选择post然后在复制蓝色部分就行。

点击调试旁边的设计模式不要选择调试模式。

选择body,创建节点。

官方文档:

配置环境:本地值填写你的API key。最后保存。

按照下面搞好,保存。

点击右上角三个杠,前置URL选择蓝色部分。

保存关闭。然后保存所有配置之后点击调试界面,body下面的json,自动生成案例,content里面改成“你是谁”就行。最后点击发送。

然后我们将stream改成false。可以看到直接全部发给我了。

1.4 ChatGPT的api获取

openai官网:OpenAI | Research & Deployment

进去之后选择API平台。然后点击左下角头像。点击Organization setting。

进入到这个页面。

点击创建。

1.5 Gemini的api获取

谷歌官网:Google AI Studio | Gemini API  |  Google AI for Developers

登录进去。点击这个。

依旧创建,流程类似,这里就不过多展示了。

二、数据结构设计

虽然各个模型不同,但有⼀些公共的配置和描述信息,⽐如:
通过api调⽤模型时需要模型名称、温度值、最⼤tokens数、api key等;
在和模型聊天时,聊天信息需要管理;
每次开启和模型的新⼀轮对话,都是⼀次新的会话,将来可能需要实现会话管理。
这些数据在多个⽂件中都会⽤到,因此提前先将这些数据结构定义好,以⽅便后续使⽤。
#pragma once
#include <string>


namespace ai_chat_sdk {
    // 消息结构
    struct Message {
        std::string _messageId; // 消息ID
        std::string _role; // 角色
        std::string _timestamp; // 发送消息时间戳
        std::string _content; // 消息内容

        // 构造函数
        Message( const std::string& role, const std::string& content)
            :  _role(role), _content(content) {}

        
    };

    // 模型的公共配置信息
    struct Config {
        std::string _modelName;     // 模型名称
        double _temperature = 0.7;  // 温度参数,用于控制生成文本的随机性 低表示严谨,高表示想象力丰富
        int _maxTokens = 2048;      // 最大生成令牌数
    };

    // 通过API方式接入云端模型的配置
    struct ApiConfig : public Config {
        std::string _apiKey;     // API密钥
        
    };

    // 通过Ollama方式接入本地模型的配置
    


    // LLM信息
    struct ModelInfo {
        std::string _modelName; // 模型名称
        std::string _modelDesc; // 模型描述
        std::string _modelProvider; // 模型供应商
        std::string _modelEndpoint; // 模型端点
        
        ModelInfo(const std::string& modelName, const std::string& modelDesc, const std::string& modelProvider, const std::string& modelEndpoint)
            : _modelName(modelName), _modelDesc(modelDesc), _modelProvider(modelProvider), _modelEndpoint(modelEndpoint) {}
    };

    // 会话信息
    struct SessionInfo {
        std::string _sessionId; // 会话ID
        std::string _modelName; // 模型名称
        std::vector<Message> _sessionMessages; // 会话消息
        std::time_t _sessionStartTime; // 会话开始时间戳
        std::time_t _sessionEndTime; // 会话结束时间戳
        
        SessionInfo(const std::string& modelName = "")
            : _modelName(modelName) {}
    };


}

温度解释如下,还有其他的专有名词都可以去api开放平台文档里面找到。我就不一一列举了。

三、日志库封装

C++中可以通过cout将信息打印到控制台,为什么还要封装⽇志库呢?
封装⽇志库有诸多优势:
  • ⽇志级别管理
⽇志库通常⽀持多种⽇志级别(如TRACE、DEBUG、INFO、WARN、ERROR、FATAL等)。开发者可以根据需要设置不同的⽇志级别,以便在开发、测试和⽣产环境中灵活控制⽇志输出。
在开发阶段,可以将⽇志级别设置为DEBUG,输出详细的调试信息;在⽣产环境中,将⽇志级别
设置为ERROR或WARNING,只记录关键的错误和警告信息,避免⽇志⽂件过⼤。
std::cout 没有内置的⽇志级别管理功能,所有输出都会被打印到控制台,⽆法根据上下⽂灵
活控制输出内容。
  • ⽇志格式化
⽇志库可以提供灵活的⽇志格式化功能,包括时间戳、⽇志级别、线程信息、⽂件名、⾏号等。如
[2025-09-24 10:00:00] [INFO] [main.cpp:123] This is an info
message 。这种格式化输出有助于快速定位问题和理解⽇志内容。
std::cout 输出的内容格式单⼀,没有内置的格式化功能,需要⼿动添加时间戳、⽂件名等信
息,代码繁琐且容易出错。
  • ⽇志存储管理
⽇志库可以将⽇志信息输出到多种⽬标,如控制台、⽂件、远程服务器等。同时,⽇志库通常⽀持
⽇志⽂件的轮转、压缩和归档,⽅便⻓期存储和管理。
⽐如设置⽇志⽂件每天⾃动轮转,并在⽂件⼤⼩超过⼀定阈值时进⾏压缩归档。这有助于避免⽇志
⽂件过⼤导致磁盘空间不⾜。
std::cout 只能将信息输出到控制台,⽆法直接⽀持⽇志⽂件的存储和管理功能。
  • 线程安全
在多线程程序中,⽇志库通常提供了线程安全的机制,确保⽇志输出不会出现冲突或数据错乱。在
多线程环境下,多个线程可能同时尝试写⼊⽇志。⽇志库通过锁或其他同步机制确保⽇志输出的线
程安全。
std::cout 在多线程环境下可能会出现⽇志输出混乱的问题,需要开发者⼿动实现线程安全机
制。
  • 性能优势
⽇志库通常会进⾏性能优化,例如通过异步写⼊⽇志、缓冲机制等,减少⽇志输出对程序性能的影
响。
std::cout 是同步操作,每次输出都会阻塞当前线程,可能对程序性能产⽣较⼤影响。
因此,本项⽬采⽤google的spdlog⽇志库进⾏⽇志管理,为了使⽤⽅便,对spdlog库采⽤单例模式进⾏简单封装。

代码块:

mylog.h

#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/logger.h>


namespace my_log {
 class Logger {
    public:
        static void initLogger(const std::string& loggerName,const std::string& loggerFile,spdlog::level::level_enum level = spdlog::level::level_enum::info);
        static std::shared_ptr<spdlog::logger> getLogger();
    private:
        Logger() = default;
        Logger(const Logger&) = delete;
        Logger& operator=(const Logger&) = delete;
        
    private:
        static std::shared_ptr<spdlog::logger> _logger;
        static std::mutex _mutex;
 };


//fmt
// string s = std::format("hello,{}","world")
// 09:08:08 [ai_chat_sdk][info][/home/zzz/gitcode1/chatSDK/SDK-source/sdk/src/util/myLog.cpp]
#define DBG(format, ...) my_log::getLogger()->debug(std::string("[{:>10}:{:<4d}]")+format, __func__, __LINE__, ##__VA_ARGS__)
#define INF(format, ...) my_log::getLogger()->info(std::string("[{:>10}:{:<4d}]")+format, __func__, __LINE__, ##__VA_ARGS__)
#define WAR(format, ...) my_log::getLogger()->warn(std::string("[{:>10}:{:<4d}]")+format, __func__, __LINE__, ##__VA_ARGS__)
#define ERR(format, ...) my_log::getLogger()->error(std::string("[{:>10}:{:<4d}]")+format, __func__, __LINE__, ##__VA_ARGS__)
#define CRIT(format, ...) my_log::getLogger()->critical(std::string("[{:>10}:{:<4d}]")+format, __func__, __LINE__, ##__VA_ARGS__)
#define TRACE(format, ...) my_log::getLogger()->trace(std::string("[{:>10}:{:<4d}]")+format, __func__, __LINE__, ##__VA_ARGS__)


} //end namespace my_log

mylog.cpp

#include "../../include/util/myLog.h"
#include <memory>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_logger_sink.h>
#include <spdlog/sinks/stdout_color_sink.h>
#include <spdlog/async.h>

namespace my_log {
    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) {
        if (_logger == _logger) {
            std::lock_guard<std::mutex> lock(_mutex);
            if (_logger == nullptr) {
                // 设置日志级别,当日志级别为info时,
                spdlog::flush_on(logLevel);
                // 启用异步日志记录,将日志存放在消息队列中,有后台线程负责写入
                // 参数1:消息队列大小,单位为字节
                // 参数2:后台线程数量
                spdlog::init_thread_pool(32768,1);
                if ("stdout" == loggerFile) {
                    // 创建一个异步日志记录器,将日志写入到标准输出流
                    _logger = spdlog::stdout_color_mt(loggerName);
                } else {
                    // 创建一个异步日志记录器,将日志写入到指定文件
                    _logger = spdlog::basic_logger_mt(loggerName, loggerFile);
                }
            }

            // 设置日志格式,%Y:%m:%d %H:%M:%S 表示时间,%-7l 表示日志级别左对齐,%v 表示日志内容
            _logger->set_pattern("[%Y:%m:%d %H:%M:%S] [%-7l] %v");
            // 设置日志级别
            _logger->set_level(logLevel);
        }
    }

    // 获取日志记录器
    std::shared_ptr<spdlog::logger> Logger::getLogger() {
        return _logger;
    }

}

目前结构:

四、模型Provider

4.1 策略模式

假设你现在要从宿舍去学校图书馆,但宿舍到图书馆之间有⼀段距离,你可以采⽤下属三⽅ 式去:
⾛路(最节省钱,但慢)
骑⾃⾏⻋(中等速度,中等花销)
坐校内公交⻋(最快,但贵)
传统方式:
if (今天没钱) {
⽤⾛路();
} else if (今天想省时间) {
⽤打⻋();
} else {
⽤⾃⾏⻋();
}
⼀堆if-else,代码会越来越乱。

策略方式

设置策略(打⻋)
执⾏策略()
设置不同策略,⾏为⽅式就改变,程序美观且灵活
策略⽅式实现:
定义⼀个接⼝ TransportStrategy (出⾏策略)。
分别实现 WalkStrategy BikeStrategy TaxiStrategy
在运⾏时,你可以随时切换策略:

代码块:

class TransportStrategy {
public:
virtual void go() = 0;
};
class WalkStrategy : public TransportStrategy {
public:
virtual void go() override { cout << "⾛路去机房🚶"; }
};
class BikeStrategy : public TransportStrategy {
public:
virtual void go() override { cout << "骑⻋去机房🚴"; }
};
class BusStrategy : public TransportStrategy {
public:
virtual void go() override { cout << "打⻋去机房🚕"; }
};
class Student {
private:
TransportStrategy* strategy;
public:
void setStrategy(TransportStrategy* s) { strategy = s; }
void goToLab() { strategy->go(); }
};
int main(){
Student me;
me.setStrategy(new WalkStrategy());
me.goToLab(); // 输出: ⾛路去机房🚶
me.setStrategy(new BusStrategy());
me.goToLab(); // 输出: 打⻋去机房🚕
return 0;
}
程序⾮常美观且灵活,在使⽤时只需和TransportStrategy 打交道,不需要知道背后到底是
WalkStrategy、BikeStrategy或BusStrategy。如果想更换模式,只需要更换⼀个具体的策略对象即
可,程序基本不需要改动。
策略模式是设计模式的⼀种,它的核⼼思想是它定义了⼀些列算法,将每⼀个算法(或⾏为)封装起来,使它们可以相互替换,⽽不⽤再代码中写⼀堆if-else/switch来决定⽤哪个算法。即把“做事的⽅ 式”抽象出来,运⾏时根据需要选择哪种⽅式去执⾏。

4.2 LLMProvider

后续会借助API的⽅式接⼊DeepSeek、ChatGPT、Gemini等⼤模型,每个⼤模型将来都需要:
a. 初始化
b. 检测模型是否有效
c. 发送消息给模型
d. 获取模型名称
e. 获取模型描述
f. 保存模型的有效状态、API Key、模型描述。
操作基本都是相同的,只是实现细节上稍微不同,因此借助策略模式将接⼊模块架构设计如下:

代码块:

#include <string>

namespace ai_chat_sdk {
    // LLM提供器接口
    class LLMProvider {
    public:
        // 初始化模型
        virtual void initModel(const std::map<std::string, std::string>& modelConfig) = 0;
        // 检查模型是否可用
        virtual bool isAvailable() const = 0;
        // 获取模型名称
        virtual std::string getModelName() const = 0;
        // 获取模型描述
        virtual std::string getModelDesc() const = 0;
        // 发送消息 - 全量返回
        void sendMessage(const std::vector<Message>& messages, const std::map<std::string,map::string>& requesParam);
        // 发送消息 - 流式返回
        void sendMessageStream(const std::vector<Message>& messages, const std::map<std::string,map::string>& requesParam,
            std::function<void(const std::string& ,bool)> callback);// callback: 回调函数,用于处理流式返回

    private:
        bool _isAvailable = false;
        std::string _apiKey;
        std::string _endpoint;
    };
}

五、deepseek接入封装

5.1 deep seek提供api

DeepSeek的Chat Completion的参数说明: 对话补全 | DeepSeek API Docs
Base URL:
模型名称:deepseek-chat,实际指向deepseek-v4-flash 模型
DeepSeek的API兼容OPenAI,因此请求和响应参数基本与ChatGPT相同:
deepseek-chat模型的聊天补全接⼝设置如下:
请求URL POST /v1/chat/completions
请求头参数:
字段名称
字段类型
字段说明
Content-Type
string
application/json
Authorization
string
"Bearer " + _api_key
请求体参数:
字段名称
字段类型
字段说明
model
string
模型名称
messages
array
历史对话,内部为每个对话的object,包含role和content两个字段
temperature
string
采样温度
max_tokens
integer
最⼤tokens数
【温度 - temperature】
调整AI⽣成内容随机性的参数,温度值越⾼,AI回答越天⻢⾏空;温度越低,回答越保守靠谱。
DeepSeek官⽹建议:
温度
场景
0.0
代码⽣成/数学解题
1.0
数据抽取/分析
1.3
通⽤对话
1.3
翻译
1.5
创意类写作/诗歌创作

响应参数:
{
"id":"8b1ca715-9270-429a-b40d-2a644f6e1d3f",
"object":"chat.completion",
"created":1754880537,
"model":"deepseek-chat",
"choices":[
{
"index":0,
"message":
{
"role":"assistant",
"content":"我是DeepSeek Chat,由深度求索公司(DeepSeek)开发的智能
AI助⼿!✨ 我可以帮你解答问题、提供建议、整理信息,甚⾄陪你聊天。⽆论是学习、⼯作,还是⽇
常⽣活中的⼩困惑,都可以来找我聊聊!😊 \n\n有什么我可以帮你的吗?"},
"logprobs":null,
"finish_reason":"stop"
}
],
"usage":{
"prompt_tokens":5,
"completion_tokens":63,
"total_tokens":68,
"prompt_tokens_details":{
"cached_tokens":0
},
"prompt_cache_hit_tokens":0,
"prompt_cache_miss_tokens":5
},
"system_fingerprint":"fp_8802369eaa_prod0623_fp8_kvcache"
}
注意:
  • ⽆状态服务原则:DeepSeek的API基于⽆状态设计,每次请求视为独⽴会话。若需维护对话连续性,必须由客⼾端主动管理并传递完整上下⽂。这与HTTP协议的⽆状态特性⼀致。
  • 系统提⽰:若需保持⻆⾊设定,如始终以专家⾝份回答,每次请求必须包含系统级指令
  • 对话历史:模型仅处理当前请求中的上下⽂,⽆法关联前序对话

可以看到,第一次交互的时候,我已经将名字告诉他了,但是第二次的时候却说不知道,说明它并不能记住之前对话内容。

但是官网却能记住,因为网页后台:它替用户维护了聊天记录,deep seek后天会将之前的聊天记录+本次新的提问一起发送给模型,模型在回复的时候,就会参考之前的会话记录,然后再进行思考回复。

这样子一起发送的时候就会记住。

5.2 大模型初始化

在使⽤deepseek前,需要先配置好deepseek需要的⼀些参数信息,⽐如api-key、model、
temperature、max_tokes等信息,否则⽆法正常使⽤deepseek的api。
因为后面我们还要接入ChatGPT和Gemini的大模型,有大量代码重复所以我们再创建一个common.h头文件。
#pragma once
#include <string>
#include <vector>
#include <ctime>
#include <map>
#include <functional>

namespace ai_chat_sdk {
    // 消息结构
    struct Message {
        std::string _messageId; // 消息ID
        std::string _role; // 角色
        std::string _timestamp; // 发送消息时间戳
        std::string _content; // 消息内容

        // 构造函数
        Message( const std::string& role, const std::string& content)
            :  _role(role), _content(content) {}
    };

    // 模型的公共配置信息
    struct Config {
        std::string _modelName;     // 模型名称
        double _temperature = 0.7;  // 温度参数,用于控制生成文本的随机性 低表示严谨,高表示想象力丰富
        int _maxTokens = 2048;      // 最大生成令牌数
    };

    // 通过API方式接入云端模型的配置
    struct ApiConfig : public Config {
        std::string _apiKey;     // API密钥
        
    };

    // 通过Ollama方式接入本地模型的配置
    


    // LLM信息
    struct ModelInfo {
        std::string _modelName; // 模型名称
        std::string _modelDesc; // 模型描述
        std::string _modelProvider; // 模型供应商
        std::string _modelEndpoint; // 模型端点
        
        ModelInfo(const std::string& modelName, const std::string& modelDesc, const std::string& modelProvider, const std::string& modelEndpoint)
            : _modelName(modelName), _modelDesc(modelDesc), _modelProvider(modelProvider), _modelEndpoint(modelEndpoint) {}
    };

    // 会话信息
    struct SessionInfo {
        std::string _sessionId; // 会话ID
        std::string _modelName; // 模型名称
        std::vector<Message> _sessionMessages; // 会话消息
        std::time_t _sessionStartTime; // 会话开始时间戳
        std::time_t _sessionEndTime; // 会话结束时间戳
        
        SessionInfo(const std::string& modelName = "")
            : _modelName(modelName) {}
    };

    
}

DeepSeekProvider.h头文件代码块:

#pragma once
#include "LLMProvider.h"

namespace ai_chat_sdk {
    // DeepSeek 模型提供器实现
    class DeepSeekProvider : public LLMProvider {
    public:
        // 初始化模型
        bool initModel(const std::map<std::string, std::string>& modelConfig) override;
        // 检查模型是否可用
        bool isAvailable() const override;
        // 获取模型名称
        std::string getModelName() const override;
        // 获取模型描述
        std::string getModelDesc() const override;
        // 发送消息 - 全量返回
        std::string sendMessage(const std::vector<Message>& messages,
                                const std::map<std::string, std::string>& requestParams) override;
        // 发送消息 - 流式返回
        std::string sendMessageStream(const std::vector<Message>& messages,
                                      const std::map<std::string, std::string>& requestParams,
                                      std::function<void(const std::string&, bool)> callback) override;
        // 发送消息 - 增量返回
        std::string sendMessageIncremental(const std::vector<Message>& messages,
                                           const std::map<std::string, std::string>& requestParams,
                                           std::function<void(const std::string&, bool)> callback);
    };
}

DeepSeekProvider.cpp代码块:

#include "DeepSeekProvider.h"
#include "util/myLog.h"
#include <jsoncpp/json/json.h>
#include <httplib.h>
using namespace httplib;


namespace ai_chat_sdk {
    //deepseekprovider 类实现
    bool DeepSeekProvider::initModel(const std::map<std::string, std::string>& modelConfig) {
        // 初始化API key
        auto it = modelConfig.find("api_key");
        if (it == modelConfig.end()) {
            ERR("DeepSeekProvider initModel api_Key not found");
            return false;
        }else{
            _apiKey = it->second;
        }

        // 初始化base_url
        it = modelConfig.find("endpoint");
        if (it == modelConfig.end()) {
            ERR("DeepSeekProvider initModel endpoint not found");
            return false;
        }else{
            _base_url = it->second;
        }

        _isAvailable = true;
        INFO("DeepSeekProvider initModel success,api_Key: {}, endpoint: {}", _apiKey, _base_url);
        return true;
    }

    // 检测模型是否可用
    bool DeepSeekProvider::isAvailable() const {
        return _isAvailable;
    }

    // 获取模型名称
    std::string DeepSeekProvider::getModelName() const{
        return "deepseek-v4-flash";
    }

    // 获取模型描述信息
    std::string DeepSeekProvider::getModelDesc() const{
        return "我是deepseek-v4-flash模型";
    }

    // 发送消息 - 全量返回
    std::string DeepSeekProvider::sendMessage(const std::vector<Message>& messages,const std::map<std::string, std::string>& requestParams) 
    {
        // 检测模型是否可用
        if (!isAvailable()) {
            ERR("DeepSeekProvider sendMessage model not available");
            return "";
        }

        // 构造请求参数
        double temperature = 0.7;
        int maxTokens = 2048;
        if (requestParams.find("temperature") != requestParams.end()) {
            temperature = std::stod(requestParams.at("temperature"));
        }
        if (requestParams.find("max_tokens") != requestParams.end()) {
            maxTokens = std::stoi(requestParams.at("max_tokens"));
        }

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

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

        // 序列化
        Json::StreamWriterBuilder writerBuilder;
        writerBuilder["indentation"] = "  ";
        std::string requestBodyStr = Json::writeString(writerBuilder, requestBody);
        INFO("DeepSeekProvider sendMessage requestBody: {}", requestBodyStr);

        // 使用cpp httplib库构造HTTP客户端
        httplib::Client client(_base_url.c_str());
        client.set_connection_timeout(30,0);        // 连接超时时间30秒
        client.set_read_timeout(60,0);              // 读取超时时间60秒


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

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

        // 检测响应状态码是否成功
        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)) {
            // 获取messages数组
            if (responseBody.isMember("choices") && responseBody["choices"].isArray() && !responseBody["choices"].empty()) {
                auto choice = responseBody["choices"][0];
                if (choice.isMember("messages") && choice["messages"].isMember("content")) {
                    std::string replyContent = choice["messages"]["content"].asString();
                    INFO("DeepSeekProvider sendMessage response success, replyContent: {}", replyContent);
                    return replyContent;
                }
            }
        }

        // json解析失败
        ERR("DeepSeekProvider sendMessage response parse error: {}", parseError);
        return "deepseek response json parse faile";

        // 构造请求URL
        std::string requestUrl = _base_url + "/v1/chat/completions";

        return "";
    }
    
    // 发送消息-增量返回-流式响应
    std::string DeepSeekProvider::sendMessageStream(const std::vector<Message>& messages,
                                                    const std::map<std::string, std::string>& requestParams,
                                                    std::function<void(const std::string&,bool)> callback)
    {
        // 检测模型是否可用
        if (!isAvailable()) {
            ERR("DeepSeekProvider sendMessageIncremental model not available");
            return "";
        }

        // 构造请求参数
        double temperature = 0.7;
        int maxTokens = 2048;
        if (requestParams.find("temperature") != requestParams.end()) {
            temperature = std::stod(requestParams.at("temperature"));
        }
        if (requestParams.find("max_tokens") != requestParams.end()) {
            maxTokens = std::stoi(requestParams.at("max_tokens"));
        }

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

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

        // 序列化
        Json::StreamWriterBuilder writerBuilder;
        writerBuilder["indentation"] = "  ";
        std::string requestBodyStr = Json::writeString(writerBuilder, requestBody);
        INFO("DeepSeekProvider sendMessageIncremental requestBody: {}", requestBodyStr);

        // 使用cpp httplib库构造HTTP客户端
        httplib::Client client(_base_url.c_str());
        client.set_connection_timeout(30,0);        // 连接超时时间30秒
        client.set_read_timeout(300,0);              // 流式响应需要更长时间,读取超时时间300秒

        // 构造请求头
        httplib::Headers headers = {
            {"Authorization", "Bearer " + _apiKey},
            {"Content-Type", "application/json"},
            {"Accept", "text/event-stream"}
        };

        // 流式处理变量
        std::string buffer;         // 流式响应缓冲区
        bool gotError = false;      // 是否获取到错误信息
        std::string errorMsg;       // 错误信息
        int statusCode = 0;       // 响应状态码
        bool streamFinish = false; // 流式响应是否结束
        std::string fullResponse;   // 完整响应体

        // 创建请求对象
        httplib::Request req;
        req.method = "POST";
        req.path = "/v1/chat/completions";
        req.body = requestBodyStr;
        req.headers = headers;
        // 设置响应处理器
        req.response_handler = [&](const httplib::Response& resp) {
            statusCode = resp.status;
            if (statusCode != 200) {
                // 流式响应开始
                gotError = true;
                errorMsg = "HTTP status code: " + std::to_string(statusCode);
                ERR("DeepSeekProvider sendMessageIncremental response_handler, status: {}, error: {}", statusCode, errorMsg);
                return false;
            }
            return true;    //继续接受后续数据
        };

        // 设置数据接收处理--解析流式响应的每个块的数据
        req.content_receiver = [&](const char* data, size_t len,size_t offset,size_t totalLength) {
            // 验证响应头是否出错,出错则直接返回false,不继续后续处理
            if (gotError) {
                return false;
            }
            
            // 追加数据到buffer
            buffer.append(data, len);
            INFO("DeepSeekProvider sendMessageIncremental content_receiver, buffer: {}", buffer);
            
            // 处理所有流式响应的数据块,注意数据块之间是以\n\n分隔的
            size_t pos = 0;
            while((pos = buffer.find("\n\n")) != std::string::npos){
                // 截取当前找到的数据块
                std::string chunk = buffer.substr(0, pos);
                // 谳过当前数据块的\n\n
                buffer.erase(0, pos + 2);
                
                // 解析该块响应数据中的模型返回的有效数据
                // 处理空行和注释,注意:以: 开头的行是注释,需要跳过
                if (chunk.empty() || chunk[0] == ':') {
                    continue;
                }
                // 获取模型返回的有效数据
                if (chunk.compare(0,6,"data: ") == 0){
                    std::string modelData = chunk.substr(6);

                    // 检测是否为标记结束
                    if (modelData == "[DONE]"){
                        streamFinish = true;
                        break;
                    }

                    // 反序列化JSON字符串
                    Json::Value modelDataJson;
                    Json::CharReaderBuilder reader;
                    std::string errors;
                    std::istringstream modelDataStream(modelData);
                    if(Json::parseFromStream(reader,modelDataStream,&modelDataJson,&errors)){
                        // 模型返回的json格式的数据现在就保存在modelDataJson
                        if(modelDataJson.isMember("choices") && 
                           modelDataJson["choices"].isArray() && 
                           !modelDataJson["choices"].empty() &&
                           modelDataJson["choices"][0]["delta"].isMember("content")){
                             std::string Content = modelDataJson["choices"][0]["delta"]["content"].asString();
                             // 处理Content,将它追加到fullResponse中
                             fullResponse += Content;

                             //将本次解析出的模型返回的内容追加到回调函数中处理
                             callback(Content,false);
                        }
                    }else{
                        WARN("DeepSeekProvider sendMessageIncremental content_receiver, errors: {}", errors);
                    }   
                }
            }
            return true;
        };

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

        if(!streamFinish) {
            WARN("stream ended without [DONE] marker");
            callback("",true);
        }
        return fullResponse;
    }

    // 发送消息-流式响应
    // std::string DeepSeekProvider::sendMessageStream(const std::vector<Message>& messages,
    //                                                  const std::map<std::string, std::string>& requestParams,
    //                                                  std::function<void(const std::string&,bool)> callback)
    // {
    //     return sendMessageIncremental(messages, requestParams, callback);
    // }
}

5.3 发送消息全量返回

在向⼤模型提问时,模型将回答⽂本⼀次性返回。
URL: /v1/chat/completions
参数:
字段名称
字段类型
字段说明
model
string
是否成功
messages
string
结果描述
temperature
double
响应数据
max_tokens
int
会话id

示例:

{
"model": "deepseek-chat",
"messages": [
{
"role": "user",
"content": "你是谁?"
},
],
"temperature": 0.7,
"max_tokens": 2048,
}

响应格式

{
"id": "d41df5f7-046d-45a3-818c-512b990fff73",
"object": "chat.completion",
"created": 1756726494,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "你的名字是你在注册时使⽤的称呼,或者你可以告诉我你希望我怎么
称呼你?😊"
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 20,
"total_tokens": 29,
"prompt_tokens_details": {
"cached_tokens": 0
},
"prompt_cache_hit_tokens": 0,
"prompt_cache_miss_tokens": 9
},
"system_fingerprint": "fp_feb633d1f5_prod0820_fp8_kvcache"
}

代码块:全量返回部分代码块

    // 发送消息 - 全量返回
    std::string DeepSeekProvider::sendMessage(const std::vector<Message>& messages,const std::map<std::string, std::string>& requestParams) 
    {
        // 检测模型是否可用
        if (!isAvailable()) {
            ERR("DeepSeekProvider sendMessage model not available");
            return "";
        }

        // 构造请求参数
        double temperature = 0.7;
        int maxTokens = 2048;
        if (requestParams.find("temperature") != requestParams.end()) {
            temperature = std::stod(requestParams.at("temperature"));
        }
        if (requestParams.find("max_tokens") != requestParams.end()) {
            maxTokens = std::stoi(requestParams.at("max_tokens"));
        }

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

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

        // 序列化
        Json::StreamWriterBuilder writerBuilder;
        writerBuilder["indentation"] = "  ";
        std::string requestBodyStr = Json::writeString(writerBuilder, requestBody);
        INFO("DeepSeekProvider sendMessage requestBody: {}", requestBodyStr);

        // 使用cpp httplib库构造HTTP客户端
        httplib::Client client(_base_url.c_str());
        client.set_connection_timeout(30,0);        // 连接超时时间30秒
        client.set_read_timeout(60,0);              // 读取超时时间60秒


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

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

        // 检测响应状态码是否成功
        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)) {
            // 获取messages数组
            if (responseBody.isMember("choices") && responseBody["choices"].isArray() && !responseBody["choices"].empty()) {
                auto choice = responseBody["choices"][0];
                if (choice.isMember("messages") && choice["messages"].isMember("content")) {
                    std::string replyContent = choice["messages"]["content"].asString();
                    INFO("DeepSeekProvider sendMessage response success, replyContent: {}", replyContent);
                    return replyContent;
                }
            }
        }

        // json解析失败
        ERR("DeepSeekProvider sendMessage response parse error: {}", parseError);
        return "deepseek response json parse faile";

        // 构造请求URL
        std::string requestUrl = _base_url + "/v1/chat/completions";

        return "";
    }

5.4 发送消息-全量返回测试

testLLM.cpp

#include <gtest/gtest.h>
#include "../sdk/include/DeepSeekProvider.h"
#include "../sdk/include/util/myLog.h"
#include "../sdk/include/ChatGPTProvider.h"
#include "../sdk/include/GeminiProvider.h"


//deepseek 测试
TEST(DeepSeekProviderTest, sendMessage) {
    
    auto provider = std::make_shared<ai_chat_sdk::DeepSeekProvider>();
    ASSERT_TRUE(provider != nullptr);

    std::map<std::string, std::string> modelParam;
    modelParam["api_key"] = std::getenv("deepseek_apikey");
    modelParam["endpoint"] = "https://api.deepseek.com";

    provider->initModel(modelParam);
    ASSERT_TRUE(provider->isAvailable());

    std::map<std::string, std::string> requestParams = {
        {"temperature", "0.7"},
        {"max_tokens", "2048"},
    };
    std::vector<ai_chat_sdk::Message> messages;
    messages.push_back({"user", "你是谁?"});

    // 实例化DeepSeekProvider的对象
    // 调用sendMessage方法
    // std::string response = provider->sendMessage(messages, requestParams);
    // ASSERT_FALSE(response.empty());
    auto writeChunk = [&](const std::string& chunk,bool last){
        INFO("chunk: {}", chunk);
        if(last){
            INFO("[DONE]");
        }
    };
    std::string fullData = provider->sendMessageStream(messages, requestParams,writeChunk);
    ASSERT_FALSE(fullData.empty());
    INFO("response : {}",fullData);
}
int main(int argc, char **argv) {
    // 初始化spdlog日志库
    my_log::Logger::initLogger("testLLM","stdout",spdlog::level::debug);
    // 初始化gtets库
    testing::InitGoogleTest(&argc, argv);
    //执行所有测试用例
    return RUN_ALL_TESTS();
}

CMakeLists.txt

# 设置Cmake最小版本
cmake_minimum_required(VERSION 3.10)

# 项目名称
project(testLLM)

# 设置cpp标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 设置构建类型debug
set(CMAKE_BUILD_TYPE Debug)

# 添加可执行文件
add_executable(testLLM test_LLM.cpp
../sdk/src/util/myLog.cpp
../sdk/src/DeepSeekProvider.cpp
../sdk/src/ChatGPTProvider.cpp
../sdk/src/GeminiProvider.cpp
)

# 设置输出目录
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})

# 添加头文件搜索路径
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../sdk/include)

find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})

# 添加宏定义
target_compile_definitions(testLLM PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)

# 链接库
target_link_libraries(testLLM jsoncpp fmt spdlog gtest OpenSSL::SSL OpenSSL::Crypto)

注意:httplib库默认使⽤http协议,⽽deepseek的官⽹链接使⽤https协议,因此在编译时需要链接
OpenSSL开发库以⽀持SSL/TLS。否则在使⽤httplib创建http客⼾端时报错:
ubuntu下安装OpenSSL开发库命令
sudo apt-get install libssl-dev
将OpenSSL库配置到CMakeList.txt⽂件,否则编译时不会链接OpenSSL库,具体参考CMakeList.txt红⾊标记部分。
编译运⾏程序,就能看到Deepseek的响应:

[2026:07:26 11:10:03] [info   ] [sendMessage:88  ]DeepSeekProvider sendMessage requestBody: {
  "max_tokens" : 2048,
  "messages" : 
  [
    {
      "content" : "\u4f60\u662f\u8c01\uff1f",
      "role" : "user"
    }
  ],
  "model" : "deepseek-v4-flash",
  "temperature" : 0.69999999999999996
}
[2026:07:26 11:10:05] [info   ] [sendMessage:108 ]DeepSeekProvider sendMessage response success, body: {"id":"6f3e06a3-c296-48f8-b972-b4e08da81c04","object":"chat.completion","created":1785035403,"model":"deepseek-v4-flash","choices":[{"index":0,"message":{"role":"assistant","content":"你好!我是DeepSeek,由深度求索公司创造的AI助手。我是一个纯文本模型,可以帮你解答问题、处理信息、进行创作等。我支持阅读链接、上传文件(图片、PDF、Word、Excel等),还能联网搜索(需要手动开启)。目前我是完全免费的,上下文处理能力达到1M,可以一次性处理大量文本。有什么我可以帮你的吗?","reasoning_content":"好的,用户问了一个简单的自我介绍问题。我需要清晰、友好地说明自己的身份和功能。我是DeepSeek,由深度求索公司创造。可以简要说明我的能力范围,比如文本处理、文件支持、联网搜索等,让用户知道我能做什么。同时要强调免费和长上下文的特点,这对用户可能有吸引力。最后以开放性的问题结束,邀请用户提出具体需求。"},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":6,"completion_tokens":166,"total_tokens":172,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens_details":{"reasoning_tokens":83},"prompt_cache_hit_tokens":0,"prompt_cache_miss_tokens":6},"system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402"}

全量返回⽐较适合⽣成⽂档、数据报表之类,⽤⼾⼀次性拿到完整的数据⽂件。但对于聊天场景不是很友好,如果⼤模型⼀次回复内容较多,会让⽤⼾等待时间过⻓,体验不是很好。因此,⼀般聊天场 景中基本使⽤流式响应。

5.5 流式响应

HTTP协议是严格的"请求-响应"模型,永远是客⼾端发起请求,服务器才能响应,服务器就像个"哑
巴",它知道更多内容,但是它⽆法主动告诉你。这种⼀问⼀答的模式对于⼤部分⽹⻚浏览器、数据提 交等场景已经⾜够了。

但是有些场景下,服务器需要主动向客⼾端推送⼀些实时数据,⽐如,在看体育直播时,服务器要及时将⽐赛分数、⾦球球员等信息推送给客⼾端;在多⼈在线游戏中,服务器需要实时同步玩家的操作和游戏状态;在使⽤导航类应⽤时,服务器需要实时推动导航信息等。
⼤佬们也发现这个问题了,在2004年的时候Ian Hickson就提出了SSE概念,Opera浏览器是第⼀个⽀持SSE的,2011年开始,⼀些主流浏览器(Chrome、Firefox、Safari)开始逐步⽀持SSE,2015年时SSE规范才正式成为W3C的标准。
SSE协议
SSE是Server Send Event的缩写,即服务器发送事件,是建⽴在HTTP协议之上的开发标准,允许服务器主动向客⼾端(如浏览器)推送实时数据。

SSE通过单⼀的持久连接实现数据的实时传输,客⼾端⽆需频繁发起请求。
SSE协议特点
  • 单向通信:服务器可以主动推送数据到客⼾端,但客⼾端⽆法直接通过SSE向服务器发送数据
  • 基于HTTP协议:SSE使⽤标准的HTTP协议,⽆需额外的协议或端⼝配置,兼容性好易于实现
  • 轻量级:SSE的实现更简单,代码量少,适合简单的实时数据推送场景
  • ⾃动重连:如果连接断开,浏览器会⾃动尝试重新连接,⽆需开发者⼿动处理重连逻辑
  • ⽀持事件类型:服务器可以发送不同类型事件,客⼾端可以根据事件类型执⾏不同的操作
  • ⽀持消息ID:每条消息可以包含⼀个唯⼀的ID,⽤于断线重连后恢复消息流
数据格式:
每个事件可以包含以下字段:
  • data:消息内容(必须)
  • event:事件类型(可选)
  • id:消息ID(可选)
  • retry:重连时间(可选,单位:毫秒)
data: Hello, world!
event: message
id: 123
retry: 10000
data: Another message
每条消息以两个换⾏符 (\n\n) 结束,消息流传输完毕后会有专⻔的结束标记,不同实现结束标记不
同,⽐如data: [DONE]。
前⾯我们演⽰向DeepSeek、ChatGPT、Gemini等⼤模型提问时,这些⼤模型并不是⼀次性将完整回答丢给⽤⼾,⽽是服务器边思考,边主动将思考结果吐(推送)给⽤⼾的,就和打字⼀样⼀点点输出,⽤⼾不需要⻓时间的等待,能及时看到服务器响应的结果,体验⽐较好,这种⽅式称为流式响应。SSE推出后实际不温不⽕,⼤模型爆⽕后,正式⼤模型场景的需要,SSE协议就爆⽕了。
WebSocket协议
SSE协议有⼀个缺陷就是单向传输,即数据只能由服务器给客⼾端推送,在新闻推送、股票⾏情、体育⽐分等场景是⽐较合适的,因为这些场景客⼾端⽆需给服务器发数据。
但有些场景SSE就束⼿⽆策了。⽐如:你在你们宿舍的微信群⾥发了⼀个消息"谁去⻝堂帮我捎个饭",服务器收到后需要"谁去⻝堂帮我捎个个饭"这条消息主动推送给群中其他⼈,其他⼈收到消息后,就需要发消息回应你⽽不是不闻不问。此处由舍友回复"滚犊⼦",那服务器收到后⼜要推送给其他⼈...该场景中,不仅需要服务器主动给客⼾端推送消息,也需要客⼾端给服务器发送消息。这种场景下WebSocket协议就派上⽤场了。

SSE与WebSocket区别
特性
SSE
WebSocket
通信⽅向
单向通道:服务器 → 客⼾端
双向通道:服务器 <=> 客⼾端
设计⽬的
服务器主动推送数据(如新闻、状态更新)
双向实时对话(如聊天、游戏操作同步)
协议
HTTP
独⽴的TCP协议,咱HTTP捂⼿后升级协议(ws/wss)
数据格式
纯⽂本
⼆进制或⽂本
⾃动重连
内置
较需要⼿动实现
使⽤场景
实时通知、⽇志流、LLM响应等单向场景
实时聊天、多⼈在线游戏、实时交易等双向交互场景
为什么DeepSeek的助⼿消息使⽤SSE,不使⽤websocket?
答:⼤模型的回复是服务器向客⼾端推送数据的单项数据流,在此期间客⼾端不需要给⼤模型服务器发送消息,⽽SSE刚好是服务器主动单项给客⼾端推送数据,并且实现简单⾼效,因此⼤模型回复通常都使⽤SSE协议。
HTTP普通响应体和流式响应体

普通HTTP响应体中,⼀个响应包含⼀个响应头和⼀个响应体,
在HTTP流式返回响应体中,⼀个响应包含⼀个响应头和多个响应块。在流式返回时,会先返回响应头,然后在逐个返回各个响应体,因此在发送流式响应时,需要在请求参数中告知HTTP服务器,响应头和chunk该如何处理。在Httplib中,请求参数的定义(部分参数)如下:
struct Request {
// 通⽤参数
std::string method; // 请求⽅法,GET、POST等
std::string path; // 资源路径,URL中域名之后的部分,⽐如:/api/users
Headers headers; // HTTP请求头,类型为 multimap<string, string>
std::string body; // HTTP请求体
// 查询参数:
Params params; // 查询参数,类型为 multimap<string, string>
// 路径参数或路由参数, 类型为 unordered_map<string, string>
std::unordered_map<std::string, std::string> path_params;
// for client
ResponseHandler response_handler;
ContentReceiverWithProgress content_receiver;
// ...
};
response_handler 为响应处理回调函数,实际类型为 std::function<void(const Response&)> 如果发起请求时设置该函数,当客⼾端收到完整的HTTP响应头和⼀些体(如果存在) 后,会调⽤该函数,并传⼊构造好的Response对象。
content_recevier 内容接收回调函数,是处理流式处理响应的关键,类型为:
function<bool(const char* data, size_t len, uint64_t offset, uint64_t total)>
  • data:指向当前接收到的数据块的指针
  • len: 当前数据块的⻓度
  • offset: 当前数据块在请求体中的偏移量
  • total: 请求体的总⻓度
  • 返回值:true表⽰继续接收数据,false表⽰停⽌接收数据
设置该回调函数后,客⼾端不会等待整个响应体传输完再存到response.body中,⽽是每收到⼀⼩块数据就⽴刻调⽤该回调函数,处理实时数据,

5.6 发送消息-流式返回

URL: /v1/chat/completions
参数:
字段名称
字段类型
字段说明
model
string
是否成功
messages
string
结果描述
temperature
double
响应数据
max_tokens
int
会话id
stream
boolean
是否开启流式响

示例:

{
"model": "deepseek-chat",
"messages": [
{
"role": "user",
"content": "你是谁?"
},
],
"temperature": 0.7,
"max_tokens": 2048,
"stream" : true
}

响应格式:

data: {
"id": "chatcmpl-1234567890",
"object": "chat.completion.chunk",
"created": 1700000000,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"delta": {
"role": "assistant",
"content": ""
},
"finish_reason": null
}
]
}
data: {
"id": "chatcmpl-1234567890",
"object": "chat.completion.chunk",
"created": 1700000000,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"delta": {
"content": "以下"
},
"finish_reason": null
}
]
}
....
data: {
"id": "chatcmpl-1234567890",
"object": "chat.completion.chunk",
"created": 1700000000,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"delta": {},
"finish_reason": "stop"
}
]
}
data: [DONE] // 最后⼀⾏,表⽰流式传输彻底结束

头文件里面加上这个就行。

// 发送消息 - 流式返回
        std::string sendMessageStream(const std::vector<Message>& messages,
                                      const std::map<std::string, std::string>& requestParams,
                                      std::function<void(const std::string&, bool)> callback) override;

deepseekprovider.cpp里面

// 发送消息-增量返回-流式响应
    std::string DeepSeekProvider::sendMessageStream(const std::vector<Message>& messages,
                                                    const std::map<std::string, std::string>& requestParams,
                                                    std::function<void(const std::string&,bool)> callback)
    {
        // 检测模型是否可用
        if (!isAvailable()) {
            ERR("DeepSeekProvider sendMessageIncremental model not available");
            return "";
        }

        // 构造请求参数
        double temperature = 0.7;
        int maxTokens = 2048;
        if (requestParams.find("temperature") != requestParams.end()) {
            temperature = std::stod(requestParams.at("temperature"));
        }
        if (requestParams.find("max_tokens") != requestParams.end()) {
            maxTokens = std::stoi(requestParams.at("max_tokens"));
        }

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

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

        // 序列化
        Json::StreamWriterBuilder writerBuilder;
        writerBuilder["indentation"] = "  ";
        std::string requestBodyStr = Json::writeString(writerBuilder, requestBody);
        INFO("DeepSeekProvider sendMessageIncremental requestBody: {}", requestBodyStr);

        // 使用cpp httplib库构造HTTP客户端
        httplib::Client client(_base_url.c_str());
        client.set_connection_timeout(30,0);        // 连接超时时间30秒
        client.set_read_timeout(300,0);              // 流式响应需要更长时间,读取超时时间300秒

        // 构造请求头
        httplib::Headers headers = {
            {"Authorization", "Bearer " + _apiKey},
            {"Content-Type", "application/json"},
            {"Accept", "text/event-stream"}
        };

        // 流式处理变量
        std::string buffer;         // 流式响应缓冲区
        bool gotError = false;      // 是否获取到错误信息
        std::string errorMsg;       // 错误信息
        int statusCode = 0;       // 响应状态码
        bool streamFinish = false; // 流式响应是否结束
        std::string fullResponse;   // 完整响应体

        // 创建请求对象
        httplib::Request req;
        req.method = "POST";
        req.path = "/v1/chat/completions";
        req.body = requestBodyStr;
        req.headers = headers;
        // 设置响应处理器
        req.response_handler = [&](const httplib::Response& resp) {
            statusCode = resp.status;
            if (statusCode != 200) {
                // 流式响应开始
                gotError = true;
                errorMsg = "HTTP status code: " + std::to_string(statusCode);
                ERR("DeepSeekProvider sendMessageIncremental response_handler, status: {}, error: {}", statusCode, errorMsg);
                return false;
            }
            return true;    //继续接受后续数据
        };

        // 设置数据接收处理--解析流式响应的每个块的数据
        req.content_receiver = [&](const char* data, size_t len,size_t offset,size_t totalLength) {
            // 验证响应头是否出错,出错则直接返回false,不继续后续处理
            if (gotError) {
                return false;
            }
            
            // 追加数据到buffer
            buffer.append(data, len);
            INFO("DeepSeekProvider sendMessageIncremental content_receiver, buffer: {}", buffer);
            
            // 处理所有流式响应的数据块,注意数据块之间是以\n\n分隔的
            size_t pos = 0;
            while((pos = buffer.find("\n\n")) != std::string::npos){
                // 截取当前找到的数据块
                std::string chunk = buffer.substr(0, pos);
                // 谳过当前数据块的\n\n
                buffer.erase(0, pos + 2);
                
                // 解析该块响应数据中的模型返回的有效数据
                // 处理空行和注释,注意:以: 开头的行是注释,需要跳过
                if (chunk.empty() || chunk[0] == ':') {
                    continue;
                }
                // 获取模型返回的有效数据
                if (chunk.compare(0,6,"data: ") == 0){
                    std::string modelData = chunk.substr(6);

                    // 检测是否为标记结束
                    if (modelData == "[DONE]"){
                        streamFinish = true;
                        break;
                    }

                    // 反序列化JSON字符串
                    Json::Value modelDataJson;
                    Json::CharReaderBuilder reader;
                    std::string errors;
                    std::istringstream modelDataStream(modelData);
                    if(Json::parseFromStream(reader,modelDataStream,&modelDataJson,&errors)){
                        // 模型返回的json格式的数据现在就保存在modelDataJson
                        if(modelDataJson.isMember("choices") && 
                           modelDataJson["choices"].isArray() && 
                           !modelDataJson["choices"].empty() &&
                           modelDataJson["choices"][0]["delta"].isMember("content")){
                             std::string Content = modelDataJson["choices"][0]["delta"]["content"].asString();
                             // 处理Content,将它追加到fullResponse中
                             fullResponse += Content;

                             //将本次解析出的模型返回的内容追加到回调函数中处理
                             callback(Content,false);
                        }
                    }else{
                        WARN("DeepSeekProvider sendMessageIncremental content_receiver, errors: {}", errors);
                    }   
                }
            }
            return true;
        };

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

        if(!streamFinish) {
            WARN("stream ended without [DONE] marker");
            callback("",true);
        }
        return fullResponse;
    }

5.7 发送消息-流式返回测试

// deepseek 测试
TEST(DeepSeekProviderTest, sendMessage) {
    
    auto provider = std::make_shared<ai_chat_sdk::DeepSeekProvider>();
    ASSERT_TRUE(provider != nullptr);

    std::map<std::string, std::string> modelParam;
    modelParam["api_key"] = std::getenv("deepseek_apikey");
    modelParam["endpoint"] = "https://api.deepseek.com";

    provider->initModel(modelParam);
    ASSERT_TRUE(provider->isAvailable());

    std::map<std::string, std::string> requestParams = {
        {"temperature", "0.7"},
        {"max_tokens", "2048"},
    };
    std::vector<ai_chat_sdk::Message> messages;
    messages.push_back({"user", "你是谁?"});

    // 实例化DeepSeekProvider的对象
    // 调用sendMessage方法
    // 全量响应
    // std::string response = provider->sendMessage(messages, requestParams);
    // ASSERT_FALSE(response.empty());

    //流式响应
    auto writeChunk = [&](const std::string& chunk,bool last){
        INFO("chunk: {}", chunk);
        if(last){
            INFO("[DONE]");
        }
    };
    std::string fullData = provider->sendMessageStream(messages, requestParams,writeChunk);
    ASSERT_FALSE(fullData.empty());
    INFO("response : {}",fullData);
}

运行结果:

更多推荐