C++AI大模型接入SDK—deepseek接入封装

C++AI大模型接入SDK---deepseek接入封装

文章目录

项目地址: 橘子师兄/ai-model-acess-tech - Gitee.com

博客专栏:C++AI大模型接入SDK_橘子师兄的博客-CSDN博客

博主首页:橘子师兄-CSDN博客

1、DeepSeek提供API

DeepSeek的Chat Completion的参数说明:https://api-docs.deepseek.com/zh-cn/api/create-chatcompletion

Base URL:https://api.deepseek.com

模型名称:deepseek-chat,实际指向DeepSeek-V3.1模型

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 创意类写作/诗歌创作

响应参数:

python 复制代码
{
	"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协议的无状态特性一致。
  • 系统提示:若需保持角色设定,如始终以专家身份回答,每次请求必须包含系统级指令
  • 对话历史:模型仅处理当前请求中的上下文,无法关联前序对话

2、大模型初始化

在使用deepseek前,需要先配置好deepseek需要的一些参数信息,比如api-key、model、temperature、max_tokes等信息,否则无法正常使用deepseek的api。

c++ 复制代码
////////////////////////////////// DeepSeekProvider.h
////////////////////////////////////
#include "ILLMProvider.h"
namespace ai_chat_sdk {
class DeepSeekProvider : public ILLMProvider{
public:
	// 初始化模型 key: api_key, value: api_key
	virtual bool initModel(const std::map<std::string, std::string>&
model_config) override;
	// 检测模型是否有效
	virtual bool isAvailable() override;
	// 获取模型名称
	virtual std::string getModelName()const override;
	// 获取模型描述信息,主要在网页中展示时使用
	virtual std::string getModelDesc()const override;
};
    
} // end ai_chat_sdk
c++ 复制代码
//////////////////////////////////// DeepSeekProvider.cpp
//////////////////////////////
#include "DeepSeekProvider.h"
#include "../../util/my_logger.h"
#include <jsoncpp/json/json.h>
#include <jsoncpp/json/reader.h>
#include <jsoncpp/json/value.h>
#include <httplib.h>
#include <sstream>
namespace ai_chat_sdk {
// 初始化模型
bool DeepSeekProvider::initModel(const std::map<std::string, std::string>&
model_config){
	// 初始化API Key
	auto it = model_config.find("api_key");
	if(it == model_config.end()){
		ERR("DeepSeekProvider: api_key is empty!");
		return false;
	}else{
		_api_key = it->second;
    }
	// 初始化endpoint
	it = model_config.find("base_url");
	if(it != model_config.end()){
		_endpoint = it->second;
	}else{
		_endpoint = "https://api.deepseek.com";
	}
	// 设置初始化成功标记
	_isAvailable = true;
	INFO("init success!!!!");
	INFO("DeepSeek provider init successs with endpoint : {}", _endpoint);
	return true;
}
// 检测模型是否有效
bool DeepSeekProvider::isAvailable(){
	return _isAvailable;
}
// 获取模型名称
std::string DeepSeekProvider::getModelName()const{
	return "deepseek-chat";
}
// 获取模型描述信息-主要在网页中展示时使用
std::string DeepSeekProvider::getModelDesc()const{
	return "一款实用性强、中文优化的通用对话助手,适合日常问答与创作";
}
} // end ai_chat_sdk	
字段说明 字段类型 字段说明
model string 是否成功
messages string 结果描述
temperature double 响应数据
max_tokens int 会话id

响应格式:

python 复制代码
{
	"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"
}
c++ 复制代码
////////////////////////////////////DeepSeekProvider.h
class DeepSeekProvider : public ILLMProvider {
    // ...
    // 发送消息给模型
    virtual std::string sendMessage(const std::vector<Message>& messages,
                                    const std::map<std::string, std::string>& request_param) override;
};

// 发送消息给模型
std::string DeepSeekProvider::sendMessage(const std::vector<Message>& messages,
                                          const std::map<std::string, std::string>& request_param) {
    // 检查模型是否有效
    if (!_isAvailable) {
        ERR("DeepSeekProvider: model is not init!");
        return "";
    }

    // 获取采样温度 和 max_tokens
    double temperature = 0.7;
    int max_tokens = 2048;

    if (request_param.find("temperature") != request_param.end()) {
        temperature = std::stof(request_param.at("temperature"));
    }
    if (request_param.find("max_tokens") != request_param.end()) {
        max_tokens = std::stoi(request_param.at("max_tokens"));
    }

    // 构建历史消息
    Json::Value messages_array(Json::arrayValue);
    for (const auto& message : messages) {
        Json::Value msg;
        msg["role"] = message.role;
        msg["content"] = message.content;
        messages_array.append(msg);
    }

    // 构建请求体
    Json::Value request_body;
    request_body["model"] = "deepseek-chat";
    request_body["messages"] = messages_array;
    request_body["temperature"] = temperature;
    request_body["max_tokens"] = max_tokens;

    // 序列化
    Json::StreamWriterBuilder writer;
    std::string json_string = Json::writeString(writer, request_body);
    DBG("DeepSeekProvider: request_body: {}", json_string);

    // 创建HTTP Client
    httplib::Client client(_endpoint);
    client.set_connection_timeout(30, 0);  // 30秒超时
    client.set_read_timeout(60, 0);        // 60秒读取超时

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

    // 发送POST请求
    auto response = client.Post("/v1/chat/completions", headers, json_string, "application/json");
    if (!response) {
        ERR("Failed to connect to DeepSeek API - check network and SSL");
        return "";
    }

    DBG("DeepSeek API response status: {}", response->status);
    DBG("DeepSeek API response body: {}", response->body);

    // 检查响应是否成功
    if (response->status != 200) {
        ERR("DeepSeek API returned non-200 status: {} - {}", response->status, response->body);
        return "";
    }

    // 解析响应体
    Json::Value response_json;
    Json::CharReaderBuilder reader_builder;
    std::string parse_errors;
    std::istringstream response_stream(response->body);

    if (!Json::parseFromStream(reader_builder, response_stream, &response_json, &parse_errors)) {
        ERR("Failed to parse DeepSeek API response: {}", parse_errors);
        return "";
    }

    // 解析大模型回复内容
    // 大模型回复包含在choices json数组中
    if (response_json.isMember("choices") && response_json["choices"].isArray() &&
        !response_json["choices"].empty()) {
        auto& choice = response_json["choices"][0];
        if (choice.isMember("message") && choice["message"].isMember("content")) {
            std::string reply_content = choice["message"]["content"].asString();
            INFO("Received DeepSeek response: {}", reply_content);
            return reply_content;
        }
    }

    // 解析失败,返回错误信息
    ERR("Invalid response format from DeepSeek API");
    return "Invalid response format from DeepSeek API";
}

4、发送消息--全量返回测试

c++ 复制代码
//////////////////////////////// testLLM.cpp ////////////////////////////////
#include <gtest/gtest.h>
#include "../sdk/include/util/my_logger.h"
#include "../sdk/include/DeepSeekProvider.h"

TEST(DeepSeekProviderTest, sendMessageDeepSeek) {
    std::map<std::string, std::string> param_map;
    param_map["api_key"] = std::getenv("deepseek_apikey");
    param_map["temperature"] = "0.7";
    
    auto deepseekProvider = std::make_shared<ai_chat_sdk::DeepSeekProvider>();
    ASSERT_TRUE(deepseekProvider != nullptr);
    
    deepseekProvider->initModel(param_map);
    ASSERT_TRUE(deepseekProvider->isAvailable());
    
    std::vector<ai_chat_sdk::Message> messages;
    messages.push_back({"user", "你好"});
    
    std::string response = deepseekProvider->sendMessage(messages, param_map);
    ASSERT_FALSE(response.empty());
    
    INFO("fulldata : {}", response);
}

int main(int argc, char* argv[]) {
    // 初始化日志库
    bite::Logger::init_logger("aiChatServer", "stdout", spdlog::level::debug);
    
    // 初始化gtest库
    testing::InitGoogleTest(&argc, argv);
    
    // 运行所有测试
    return RUN_ALL_TESTS();
}
c++ 复制代码
#///////////////////////////// CMakeLists.txt //////////////////////////////

# CMake 最低版本要求
cmake_minimum_required(VERSION 3.10)

# 项目名称
project(LLMTest)

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

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

# 添加可执行文件
add_executable(LLMTest 
    testLLMProvider.cpp
    ../sdk/src/DeepSeekProvider.cpp
)

# 设置输出目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build)

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

# 查找OpenSSL包
find_package(OpenSSL REQUIRED)

# 包含OpenSSL头文件路径
include_directories(${OPENSSL_INCLUDE_DIR})

# 添加编译定义
target_compile_definitions(LLMTest PUBLIC
    CPPHTTPLIB_OPENSSL_SUPPORT
)

# 设置库的目录
link_directories(/usr/local/lib)

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

注意:httplib库默认使用http协议,而deepseek的官网链接使用https协议,因此在编译时需要链接OpenSSL开发库以支持SSL/TLS。否则在使用httplib创建http客户端时报错:

c++ 复制代码
terminate called after throwing an instance of 'std::invalid_argument'
what(): 'https' scheme is not supported.

ubuntu下安装OpenSSL开发库命令

bash 复制代码
sudo apt-get install libssl-dev

将OpenSSL库配置到CMakeList.txt文件,否则编译时不会链接OpenSSL库,具体参考CMakeList.txt红色标记部分。

编译运行程序,就能看到Deepseek的响应:

bash 复制代码
DeepSeek API response_body: {"id":"eabeea4d-71f5-4084-8cc6-
bbd36292f947","object":"chat.completion","created":1756369697,"model":"deepseek
-chat","choices":[{"index":0,"message":{"role":"assistant","content":"你好!很高
兴见到你,有什么可以帮你的吗?
😊"},"logprobs":null,"finish_reason":"stop"}],"usage":
{"prompt_tokens":5,"completion_tokens":14,"total_tokens":19,"prompt_tokens_deta
ils":
{"cached_tokens":0},"prompt_cache_hit_tokens":0,"prompt_cache_miss_tokens":5},"
system_fingerprint":"fp_feb633d1f5_prod0820_fp8_kvcache"}

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

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中,请求参数的定义(部分参数)如下:

c++ 复制代码
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>
    
    // 路径参数或路由参数
    std::unordered_map<std::string, std::string> path_params;  // 类型为 unordered_map<string, string>
    
    // 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中,而是每收到一小块数据就立刻调用该回调函数,处理实时数据,

6、发送消息-流式返回

URL: /v1/chat/completions

参数:

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

响应格式:

python 复制代码
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] // 最后一行,表示流式传输彻底结束
c++ 复制代码
////////////////////////////// DeepSeekProvider.h
///////////////////////////////////////

// ...
class DeepSeekProvider : public ILLMProvider {
public:
    // ...
    
    // 发送消息给模型 - 全量返回
    virtual std::string sendMessage(
        const std::vector<Message>& messages,
        const std::map<std::string, std::string>& request_param
    ) override;
    
    // 发送消息给模型 - 流式响应
    virtual std::string sendMessageStream(
        const std::vector<Message>& messages,
        const std::map<std::string, std::string>& request_param,
        std::function<void(const std::string&, bool)> callback
    ) override;
};
c++ 复制代码
////////////////////////////// DeepSeekProvider.cpp
///////////////////////////////////////

// ...
std::string DeepSeekProvider::sendMessageStream(
    const std::vector<Message>& messages,
    const std::map<std::string, std::string>& request_param,
    std::function<void(const std::string&, bool)> callback
) {
    INFO("DeepSeekProvider sendMessageStream");
    
    if (!_isAvailable) {
        ERR("DeepSeekProvider is not available");
        return "";
    }
    
    // 获取采样温度 和 max_tokens
    double temperature = 0.7;
    int max_tokens = 2048;
    
    if (request_param.find("temperature") != request_param.end()) {
        temperature = std::stof(request_param.at("temperature"));
    }
    if (request_param.find("max_tokens") != request_param.end()) {
        max_tokens = std::stoi(request_param.at("max_tokens"));
    }
    
    // 构建历史消息
    Json::Value messages_array(Json::arrayValue);
    for (const auto& message : messages) {
        Json::Value msg;
        msg["role"] = message.role;
        msg["content"] = message.content;
        messages_array.append(msg);
    }
    
    // 构建请求体
    Json::Value request_body;
    request_body["model"] = "deepseek-chat";
    request_body["messages"] = messages_array;
    request_body["temperature"] = temperature;
    request_body["max_tokens"] = max_tokens;
    request_body["stream"] = true; // 开启流式响应
    
    // 序列化
    Json::StreamWriterBuilder writer;
    std::string json_string = Json::writeString(writer, request_body);
    DBG("DeepSeekProvider: Send stream request to deepseek Server, request_body: {}", json_string);
    
    // 创建HTTP Client
    httplib::Client client(_endpoint);
    client.set_connection_timeout(30, 0); // 30秒超时
    client.set_read_timeout(300, 0);      // 流式响应需要更长的时间
    
    // 设置请求头
    httplib::Headers headers = {
        {"Authorization", "Bearer " + _api_key},
        {"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.headers = headers;
    req.body = json_string;
    
    // HTTP协议响应的格式:
    // 一个状态行:HTTP/1.1 200 OK
    // 一组响应头:Content-Type,Content-Length
    // 空行:\r\n\r\n
    // 响应体
    // 普通的HTTP响应是 一头一体
    // 流式响应:只有一个响应头,但响应体被拆分成多个块(chunks)陆续发送,即一头多块
    
    // 响应头处理
    req.response_handler = [&](const httplib::Response& response) {
        statusCode = response.status;
        if (200 != statusCode) {
            gotError = true;
            errorMsg = "HTTP Error:" + std::to_string(statusCode);
            return false; // 终止请求
        }
        return true; // 继续接收数据
    };
    
    // 响应体处理(流式回调)
    req.content_receiver = [&](const char* data, size_t len, uint64_t offset,
                              uint64_t totalLength) {
        // 如果http请求头出错,就不需要再继续接收了
        if (gotError) {
            return false;
        }
        
        // 追加新数据到缓冲区
        buffer.append(data, len);
        std::cout << "buffer: " << buffer << std::endl;
        
        // 处理所有完整的事件,事件和事件之间以\n\n分隔
        size_t pos = 0;
        while ((pos = buffer.find("\n\n")) != std::string::npos) {
            std::string event = buffer.substr(0, pos);
            buffer.erase(0, pos + 2); // 移除已经处理的事件
            
            // 处理空行和注释,以:开头的是注释行
            if (event.empty() || event[0] == ':') {
                continue;
            }
            
            // 检查事件类型
            if (event.compare(0, 6, "data: ") == 0) {
                std::string jsonStr = event.substr(6);
                
                // 处理结束标记
                if (jsonStr == "[DONE]") {
                    callback("", true);
                    streamFinish = true;
                    return true;
                }
                
                // 解析json数据
                Json::Value chunk;
                Json::CharReaderBuilder readerBuilder;
                std::string errs;
                std::istringstream jsonStream(jsonStr);
                
                if (Json::parseFromStream(readerBuilder, jsonStream, &chunk, &errs)) {
                    // 提取增量内容
                    if (chunk.isMember("choices") &&
                        chunk["choices"].isArray() &&
                        !chunk["choices"].empty() &&
                        chunk["choices"][0].isMember("delta") &&
                        chunk["choices"][0]["delta"].isMember("content")) {
                        std::string content = chunk["choices"][0]["delta"]["content"].asString();
                        
                        // 累积到完整响应
                        fullResponse += content;
                        callback(content, false);
                    }
                } else {
                    // WARN("DeepSeek SSE JSON parse error : {}", errs);
                }
            }
        }
        return true; // 继续接收数据
    };
    
    // 发送请求并处理结果
    auto res = client.send(req);
    
    // send的返回值类型是Result类型,Result类型内部实现了operator bool(),
    // 允许将Result类型的实例隐式转换为bool类型
    if (!res) {
        // 请求连接失败,网络问题、DNS解析失败等
        auto err = res.error();
        ERR("Network error : {}", std::to_string(static_cast<int>(err)));
        return "";
    }
    
    // 确保流正常结束
    if (!streamFinish) {
        WARN("Stream ended without [DONE] marker");
        callback("", true);
    }
    
    return fullResponse;
}

7、发送消息-流式返回测试

c++ 复制代码
/////////////////////////////// testLLM.cpp //////////////////////////////////
// ...
TEST(DeepSeekProviderTest, sendMessageStreamDeepSeek) {
    std::map<std::string, std::string> param_map;
    param_map["api_key"] = std::getenv("deepseek_apikey");
    param_map["temperature"] = "0.7";
    
    auto deepseekProvider = std::make_shared<ai_chat_sdk::DeepSeekProvider>();
    ASSERT_TRUE(deepseekProvider != nullptr);
    
    deepseekProvider->initModel(param_map);
    ASSERT_TRUE(deepseekProvider->isAvailable());
    
    std::vector<ai_chat_sdk::Message> messages;
    messages.push_back({"user", "你好"});
    messages.push_back({"assistant", "你好! 很高兴见到你, 有什么可以帮你的吗?"});
    messages.push_back({"user", "请问什么是SSE?"});
    
    auto write_chunk = [&](const std::string& chunk, bool last) {
        INFO("chunk : {}", chunk);
        if (last) {
            INFO("[DONE]");
        }
    };
    
    std::string fulldata = deepseekProvider->sendMessageStream(messages, param_map, write_chunk);
    ASSERT_FALSE(fulldata.empty());
    
    INFO("fulldata : {}", fulldata);
}
复制代码
运行结果部分截图:
...
buffer: data: {"id":"ca195b58-4bf1-4ebd-903f-
25db748ff989","object":"chat.completion.chunk","created":1757563343,"model":"de
epseekchat","
system_fingerprint":"fp_08f168e49b_prod0820_fp8_kvcache","choices":
[{"index":0,"delta":{"content":"如果"},"logprobs":null,"finish_reason":null}]}
data: {"id":"ca195b58-4bf1-4ebd-903f-
25db748ff989","object":"chat.completion.chunk","created":1757563343,"model":"de
epseekchat","
system_fingerprint":"fp_08f168e49b_prod0820_fp8_kvcache","choices":
[{"index":0,"delta":{"content":"讨论"},"logprobs":null,"finish_reason":null}]}
buffer: data: {"id":"ca195b58-4bf1-4ebd-903f-
25db748ff989","object":"chat.completion.chunk","created":1757563343,"model":"de
epseekchat","
system_fingerprint":"fp_08f168e49b_prod0820_fp8_kvcache","choices":
[{"index":0,"delta":{"content":"**"},"logprobs":null,"finish_reason":null}]}
data: {"id":"ca195b58-4bf1-4ebd-903f-
25db748ff989","object":"chat.completion.chunk","created":1757563343,"model":"de
epseekchat","
system_fingerprint":"fp_08f168e49b_prod0820_fp8_kvcache","choices":
[{"index":0,"delta":{"content":"网页"},"logprobs":null,"finish_reason":null}]}
buffer: data: {"id":"ca195b58-4bf1-4ebd-903f-
25db748ff989","object":"chat.completion.chunk","created":1757563343,"model":"de
epseekchat","
system_fingerprint":"fp_08f168e49b_prod0820_fp8_kvcache","choices":
[{"index":0,"delta":{"content":"实时"},"logprobs":null,"finish_reason":null}]}
buffer: data: {"id":"ca195b58-4bf1-4ebd-903f-
25db748ff989","object":"chat.completion.chunk","created":1757563343,"model":"de
epseekchat","
system_fingerprint":"fp_08f168e49b_prod0820_fp8_kvcache","choices":
[{"index":0,"delta":{"content":"聊天"},"logprobs":null,"finish_reason":null}]}
buffer: data: {"id":"ca195b58-4bf1-4ebd-903f-
25db748ff989","object":"chat.completion.chunk","created":1757563343,"model":"de
epseekchat","
system_fingerprint":"fp_08f168e49b_prod0820_fp8_kvcache","choices":
[{"index":0,"delta":{"content":"、"},"logprobs":null,"finish_reason":null}]}
data: {"id":"ca195b58-4bf1-4ebd-903f-
25db748ff989","object":"chat.completion.chunk","created":1757563343,"model":"de
epseekchat","
system_fingerprint":"fp_08f168e49b_prod0820_fp8_kvcache","choices":
[{"index":0,"delta":{"content":"推送"},"logprobs":null,"finish_reason":null}]}
...
相关推荐
黄小耶@2 小时前
基于 双向RNN网络 的中文文本预测模型
人工智能·rnn·深度学习
gdutxiaoxu2 小时前
browser-use - 让AI Agent真正“会“用浏览器
人工智能·ai agent
Fairy要carry2 小时前
面试-OnlyDecoder用于嵌入模型
人工智能
陈天伟教授2 小时前
人工智能应用-机器视觉:AI 鉴伪 03.换脸伪造技术
人工智能·神经网络·生成对抗网络
Yeats_Liao2 小时前
长文本优化:KV Cache机制与显存占用平衡策略
人工智能·深度学习·学习·机器学习·华为
清酒难咽2 小时前
算法案例之蛮力法
c++·经验分享·算法
石逸凡2 小时前
基于实体地图的金融大模型落地方法探索与前景展望
人工智能·金融
cooldream20092 小时前
辩核AI具身辩论数字人训练系统:技术架构与功能体系全解析
人工智能·架构·具身数字人
星爷AG I2 小时前
9-13 视知觉组织(AGI基础理论)
人工智能·agi