
大家好,我是Halcyon.平安
欢迎文末添加好友交流,共同进步!

-
- 一、本篇概述
- 二、头文件与依赖
-
- [2.1 DeepSeekProvider.h](#2.1 DeepSeekProvider.h)
- [2.2 DeepSeekProvider.cpp 的依赖](#2.2 DeepSeekProvider.cpp 的依赖)
- [三、initModel --- 模型初始化](#三、initModel — 模型初始化)
-
- [3.1 完整源码](#3.1 完整源码)
- [3.2 逐行解析](#3.2 逐行解析)
- [3.3 modelConfig 从哪来?](#3.3 modelConfig 从哪来?)
- [四、isAvailable / getModelName / getModelDesc](#四、isAvailable / getModelName / getModelDesc)
- [五、sendMessage --- 全量发送消息](#五、sendMessage — 全量发送消息)
-
- [5.1 完整源码](#5.1 完整源码)
- [5.2 逐步解析](#5.2 逐步解析)
-
- [第 1 步:安全检查](#第 1 步:安全检查)
- [第 2 步:构造请求参数](#第 2 步:构造请求参数)
- [第 3 步:构造消息数组](#第 3 步:构造消息数组)
- [第 4 步:构造请求体并序列化](#第 4 步:构造请求体并序列化)
- [第 5 步:构造 HTTP 客户端](#第 5 步:构造 HTTP 客户端)
- [第 6 步:发送 POST 请求](#第 6 步:发送 POST 请求)
- [第 7 步:解析响应体](#第 7 步:解析响应体)
- [第 8 步:解析失败兜底](#第 8 步:解析失败兜底)
- [5.3 sendMessage 完整流程图](#5.3 sendMessage 完整流程图)
- [六、DeepSeek API 请求与响应格式](#六、DeepSeek API 请求与响应格式)
-
- [6.1 请求格式](#6.1 请求格式)
- [6.2 响应格式](#6.2 响应格式)
- 七、总结
一、本篇概述
DeepSeekProvider 是第一个实现 LLMProvider 纯虚接口的具体类,也是理解其他三个 Provider(ChatGPT、Gemini、Ollama)的基础。本篇聚焦非流式部分:
plain
DeepSeekProvider
├── initModel() ← 本篇:初始化 API Key、Endpoint
├── isAvailable() ← 本篇:返回可用状态
├── getModelName() ← 本篇:返回固定模型名
├── getModelDesc() ← 本篇:返回模型描述
├── sendMessage() ← 本篇:全量请求,一次性返回完整回复
└── sendMessageStream()← 下篇:流式请求,逐段返回增量数据
二、头文件与依赖
2.1 DeepSeekProvider.h
cpp
#include "LLMProvider.h"
#include "common.h"
namespace ai_chat_sdk{
// LLMProvider 类
class DeepSeekProvider : public LLMProvider{
public:
// 初始化模型
virtual bool initModel(const std::map<std::string, std::string>& modelConfig);
// 检测模型是否有效
virtual bool isAvailable() const;
// 获取模型名称
virtual std::string getModelName() const;
// 获取模型描述
virtual std::string getModelDesc() const;
// 发送消息 - 全量返回
virtual std::string sendMessage(const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam);
// 发送消息 - 增量返回 - 流式响应
virtual std::string sendMessageStream(const std::vector<Message>& messages,
const std::map<std::string, std::string>& requestParam,
std::function<void(const std::string&, bool)> callback);
};
} // end ai_chat_sdk
这里注意到一个细节:头文件中重复包含了
<functional>、<string>、<map>、<vector>和"common.h",这些在LLMProvider.h中已经包含过了。因为#pragma once防止了重复包含导致的编译错误,所以我就直接复制了,其实是可以不要的!
2.2 DeepSeekProvider.cpp 的依赖
cpp
#include "../include/DeepSeekProvider.h"
#include "../include/util/myLog.h"
#include <cstdint>
#include <jsoncpp/json/json.h>
#include <httplib.h>
#include <jsoncpp/json/reader.h>
| 头文件 | 引入原因 |
|---|---|
"DeepSeekProvider.h" |
本类的声明 |
"util/myLog.h" |
日志宏:INFO、ERR、WARN等 |
<cstdint> |
size_t等标准整数类型(流式处理函数参数用到) |
<jsoncpp/json/json.h> |
JsonCpp 核心:Json::Value、Json::StreamWriterBuilder |
<httplib.h> |
cpp-httplib:HTTP 客户端,发送请求到DeepSeek API |
<jsoncpp/json/reader.h> |
JsonCpp 读取:Json::CharReaderBuilder、parseFromStream |
其中
<jsoncpp/json/json.h>和<jsoncpp/json/reader.h>都是 JsonCpp 的头文件,前者提供 JSON 值的构建和序列化,后者提供 JSON 字符串的反序列化(解析)。
三、initModel --- 模型初始化
3.1 完整源码
cpp
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())
{
_endpoint = "https://api.deepseek.com";
}else{
_endpoint = it->second;
}
_isAvailable = true;
INFO("DeepSeekProvider initModel success, endpoint: {}",_endpoint);
return true;
}
3.2 逐行解析
第 1 步:获取 API Key
cpp
auto it = modelConfig.find("api_key");
if(it == modelConfig.end()){
ERR("DeepSeekProvider initModel api_key not found");
return false;
}else{
_apiKey = it->second;
}
modelConfig.find("api_key")--- 在 map 中查找 key 为"api_key"的条目,返回一个迭代器it == modelConfig.end()--- 如果迭代器等于end(),说明没找到it->second--- map 的每个元素是pair<const string, string>,first是 key,second是 value,所以it->second就是 API Key 的值return false--- API Key 是必填项,没有就直接返回初始化失败_apiKey = it->second--- 将 API Key 存入基类的protected成员,后续发送请求时要放在 HTTP Header 中
第 2 步:获取 Endpoint(有默认值)
cpp
it = modelConfig.find("endpoint");
if(it == modelConfig.end()){
_endpoint = "https://api.deepseek.com";
}else{
_endpoint = it->second;
}
- 逻辑和获取 API Key 一样,区别在于:endpoint 不是必填的
- 如果用户没有提供 endpoint,就使用 DeepSeek 官方默认地址
"https://api.deepseek.com" - 这种设计方便了开发者:正常使用时不需要关心 endpoint,只有在使用代理或私有部署时才需要修改
第 3 步:标记可用
cpp
_isAvailable = true;
初始化成功后,将基类的
_isAvailable设为true。之后调用isAvailable()就会返回true,表示模型可以使用了。
3.3 modelConfig 从哪来?
在 LLMManager::initModel() 中,上层传入一个 map<string, string>:
cpp
// 调用方构造配置
std::map<std::string, std::string> config;
config["api_key"] = "sk-xxxxxxxxxxxxxxxx";
config["endpoint"] = "https://api.deepseek.com"; // 可选
// 传入 LLMManager
llmManager.initModel("deepseek-chat", config);
LLMManager内部找到对应的DeepSeekProvider实例,调用它的initModel(config),配置就传到了这里。
四、isAvailable / getModelName / getModelDesc
这三个函数都很简单,直接返回固定值:
cpp
// 检测模型是否可用
bool DeepSeekProvider::isAvailable() const{
return _isAvailable;
}
// 获取模型名称
std::string DeepSeekProvider::getModelName() const{
return "deepseek-chat";
}
// 获取模型的描述信息
std::string DeepSeekProvider::getModelDesc() const{
return "一款实用性强、中文优化的通用对话助手,适合日常问答与创作";
}
| 函数 | 返回值 | 说明 |
|---|---|---|
isAvailable() |
_isAvailable(bool) |
由 initModel()设置,初始化前为 false |
getModelName() |
"deepseek-chat" |
DeepSeek 的模型标识,发送 API 请求时放在 JSON 中 |
getModelDesc() |
中文描述字符串 | 用于前端展示,让用户知道这个模型的特点 |
getModelName() 返回的 "deepseek-chat" 就是 DeepSeek API 要求的 model 字段值,在 sendMessage() 构造请求体时直接使用。
五、sendMessage --- 全量发送消息
5.1 完整源码
cpp
std::string DeepSeekProvider::sendMessage(const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam)
{
// 1. 检测模型是否可用
if(!isAvailable()){
ERR("DeepSeekProvider sendMessage model not available");
return "";
}
// 2. 构造请求参数
double temperature = 0.7;
int maxTokens = 2048;
if(requestParam.find("temperature") != requestParam.end()){
temperature = std::stod(requestParam.at("temperature"));
}
if(requestParam.find("max_tokens") != requestParam.end()){
maxTokens = std::stoi(requestParam.at("max_tokens"));
}
// 构造历史消息
Json::Value messageArray(Json::arrayValue);
for(const auto& message : messages){
Json::Value messageObject;
messageObject["role"] = message._role;
messageObject["content"] = message._content;
messageArray.append(messageObject);
}
// 3. 构造请求体
Json::Value requestBody;
requestBody["model"] = getModelName();
requestBody["messages"] = messageArray;
requestBody["temperature"] = temperature;
requestBody["max_tokens"] = maxTokens;
// 4. 序列化
Json::StreamWriterBuilder writerBuilder;
writerBuilder["indentation"] = "";
std::string requestBodyStr = Json::writeString(writerBuilder, requestBody);
INFO("DeepSeekProvider sendMessage requestBody: {}", requestBodyStr);
// 5. 使用cpp-httplib库构造HTTP客户端
httplib::Client client(_endpoint.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"}
};
// 6. 发送POST请求
auto response = client.Post("/v1/chat/completions", headers, requestBodyStr, "application/json");
if(!response){
ERR("DeepSeekProvider sendMessage POST request failed");
return "";
}
INFO("DeepSeekProvider sendMessage POST request success, status : {}", response->status);
INFO("DeepSeekProvider sendMessage POST request success, body : {}", response->body);
// 检测响应是否成功
if(response->status != 200){
return "";
}
// 7. 解析响应体
Json::Value responseBody;
Json::CharReaderBuilder readerBuilder;
std::string parseError;
std::istringstream responseStream(response->body);
if(Json::parseFromStream(readerBuilder, responseStream, &responseBody, &parseError)){
// 获取message数组
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;
}
}
}
// 8. json解析失败
ERR("DeepSeekProvider sendMessage POST response body parse failed, error");
return "deepseek response json parse failed";
}
5.2 逐步解析
第 1 步:安全检查
cpp
if(!isAvailable()){
ERR("DeepSeekProvider sendMessage model not available");
return "";
}
在发送请求前先检查模型是否初始化成功。如果
initModel()没被调用过或者调用失败,_isAvailable就是false,这里会直接返回空字符串。这是一道防御性编程,防止在模型未就绪时发起无效的网络请求。
第 2 步:构造请求参数
cpp
double temperature = 0.7;
int maxTokens = 2048;
if(requestParam.find("temperature") != requestParam.end()){
temperature = std::stod(requestParam.at("temperature"));
}
if(requestParam.find("max_tokens") != requestParam.end()){
maxTokens = std::stoi(requestParam.at("max_tokens"));
}
- 先设置默认值:
temperature = 0.7,maxTokens = 2048 - 如果
requestParam中有对应的 key,就用用户提供的值覆盖默认值 std::stod()--- string to double,将字符串转为浮点数std::stoi()--- string to int,将字符串转为整数
- std::map::at() 自动根据key值获取对应的value。
- 这里用
find()而不是[]运算符,因为[]在 key 不存在时会自动插入一个默认值 ,而find()只是查找,不会修改 map。
第 3 步:构造消息数组
cpp
Json::Value messageArray(Json::arrayValue);
for(const auto& message : messages){
Json::Value messageObject;
messageObject["role"] = message._role;
messageObject["content"] = message._content;
messageArray.append(messageObject);
}
逐行解析:
Json::Value messageArray(Json::arrayValue)--- 创建一个 JSON 数组 类型的Json::Value对象for(const auto& message : messages)--- 范围 for 循环,const引用避免拷贝;依次遍历messages里面的每一条消息,每次取出来一条,临时叫做message。Json::Value messageObject--- 创建一个用于保存单条消息的 Json::Value 对象,后续通过 ["role"]、["content"] 赋值后形成 Json 对象。messageObject["role"] = message._role--- 设置 JSON 字段,role可能是"user"或"assistant"messageObject["content"] = message._content--- 设置消息正文messageArray.append(messageObject)--- 将这个消息对象追加到数组末尾
构造出的 JSON 数组结构:
cpp
[
{"role": "user", "content": "什么是智能指针?"},
{"role": "assistant", "content": "智能指针是 C++11 引入的..."},
{"role": "user", "content": "详细说说 unique_ptr"}
]
DeepSeek API 要求把完整的对话历史传过来,模型才能理解上下文。
第 4 步:构造请求体并序列化
cpp
Json::Value requestBody;
requestBody["model"] = getModelName();
requestBody["messages"] = messageArray;
requestBody["temperature"] = temperature;
requestBody["max_tokens"] = maxTokens;
把所有参数组装成一个完整的 JSON 对象:
cpp
{
"model": "deepseek-chat",
"messages": [...],
"temperature": 0.7,
"max_tokens": 2048
}
Json::StreamWriterBuilder writerBuilder;
writerBuilder["indentation"] = "";
std::string requestBodyStr = Json::writeString(writerBuilder, requestBody);
StreamWriterBuilder--- JsonCpp 的 JSON 序列化工具writerBuilder["indentation"] = ""--- 设置缩进为空字符串,即紧凑格式(不换行不缩进),减少网络传输量Json::writeString()--- 将Json::Value序列化为std::string
如果用有缩进的格式,输出会是这样的(方便调试但不适合传输):
cpp
{
"model": "deepseek-chat",
"messages": [
{
"role": "user",
"content": "你好"
}
],
"temperature": 0.7,
"max_tokens": 2048
}
紧凑格式(indentation = ""):
cpp
{"model":"deepseek-chat","messages":[{"role":"user","content":"你好"}],"temperature":0.7,"max_tokens":2048}
第 5 步:构造 HTTP 客户端
cpp
httplib::Client client(_endpoint.c_str());
client.set_connection_timeout(30, 0); // 连接超时时间为30秒
client.set_read_timeout(60, 0); // 设置超时时间为60秒
httplib::Client--- cpp-httplib 库的 HTTP 客户端类_endpoint.c_str()--- 将std::string(base url) 转为 C 风格字符串(const char*),cpp-httplib 的构造函数需要这个类型set_connection_timeout(30, 0)--- 设置连接超时为 30 秒 0 微秒。如果 30 秒内无法建立 TCP 连接就放弃set_read_timeout(60, 0)--- 设置读取超时为 60 秒。如果 60 秒内没有收到响应就放弃
为什么全量请求只设 60 秒? 因为全量请求是等模型生成完毕后一次性返回,一般几秒到十几秒就能完成。后面的流式请求会设 300 秒,因为流式是边生成边传输,持续时间更长。
cpp
httplib::Headers headers = {
{"Authorization", "Bearer " + _apiKey},
{"Content-Type", "application/json"}
};
httplib::Headers--- 本质是std::multimap<std::string, std::string>,允许同名 header 出现多次"Authorization", "Bearer " + _apiKey--- API 认证头。Bearer是 OAuth 2.0 标准的认证方式,后面跟 API Key。DeepSeek 用这个来验证调用者身份"Content-Type", "application/json"--- 告诉服务器请求体是 JSON 格式
"Bearer " + _apiKey 利用了 std::string 的 operator+ 拼接,等价于 "Bearer sk-xxxxxxxx"。
第 6 步:发送 POST 请求
cpp
auto response = client.Post("/v1/chat/completions", headers, requestBodyStr, "application/json");
client.Post() 的四个参数:
| 参数 | 值 | 说明 |
|---|---|---|
| 第 1 个(路径) | "/v1/chat/completions" |
DeepSeek 的对话 API 端点 |
| 第 2 个(请求头) | headers |
包含认证和内容类型 |
| 第 3 个(请求体) | requestBodyStr |
序列化后的 JSON 字符串 |
| 第 4 个(内容类型) | "application/json" |
重复指定内容类型(httplib 要求) |
/v1/chat/completions 是 DeepSeek 兼容 OpenAI 格式的对话端点,/v1 是版本号,chat/completions 表示"对话补全"。
cpp
if(!response){
ERR("DeepSeekProvider sendMessage POST request failed");
return "";
}
client.Post() 返回 httplib::Result,它重载了 operator bool():
- 网络层面成功(收到 HTTP 响应)→
true,response有值 - 网络层面失败(DNS 解析失败、连接超时、网络断开)→
false
cpp
if(response->status != 200){
return "";
}
-
response->status--- HTTP 状态码,200表示成功 -
常见的非 200 状态码:
-
401--- API Key 无效429--- 请求太频繁,被限流500--- DeepSeek 服务器内部错误
第 7 步:解析响应体
cpp
Json::Value responseBody;
Json::CharReaderBuilder readerBuilder;
std::string parseError;
std::istringstream responseStream(response->body);
if(Json::parseFromStream(readerBuilder, responseStream, &responseBody, &parseError)){
Json::CharReaderBuilder--- JsonCpp 的 JSON 解析器构建器std::istringstream responseStream(response->body)--- 将响应体字符串包装成输入字符串流,因为parseFromStream需要流参数Json::parseFromStream()--- 从流中解析 JSON,成功返回true,解析结果存在responseBody中,错误信息存在parseError中
DeepSeek 返回的 JSON 结构:
cpp
{
"id": "chatcmpl-xxx",
"object": "chat.completion",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "智能指针是 C++11 引入的..."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 20,
"completion_tokens": 100,
"total_tokens": 120
}
}
接下来逐层提取数据:
cpp
if(responseBody.isMember("choices") && responseBody["choices"].isArray() && !responseBody["choices"].empty()){
三重检查:
isMember("choices")--- JSON 中是否存在choices字段isArray()---choices是否是数组类型!empty()--- 数组是否不为空
cpp
auto choice = responseBody["choices"][0];
if(choice.isMember("message") && choice["message"].isMember("content")){
std::string replyContent = choice["message"]["content"].asString();
responseBody["choices"][0]--- 取第一个 choice(通常只有一个)choice["message"]["content"]--- 沿着 JSON 层级深入到message→content.asString()--- 将Json::Value转为std::string
第 8 步:解析失败兜底
cpp
ERR("DeepSeekProvider sendMessage POST response body parse failed, error");
return "deepseek response json parse failed";
如果走到这里,说明 JSON 解析失败或者 JSON 结构不符合预期。注意这里返回的不是空字符串,而是一个错误提示字符串。这是为了让上层知道"请求成功了但解析失败",区别于"请求本身失败"(返回空字符串)。
5.3 sendMessage 完整流程图
cpp
sendMessage(messages, requestParam)
│
├─ 1. 检查 isAvailable()
│ └─ false → 返回 ""
│
├─ 2. 从 requestParam 取 temperature / max_tokens
│
├─ 3. 构造 JSON 请求体
│ └─ {model, messages, temperature, max_tokens}
│
├─ 4. 序列化为字符串
│
├─ 5. 创建 HTTP Client + 设置超时 + 设置 Header
│
├─ 6. POST → https://api.deepseek.com/v1/chat/completions
│ ├─ 网络失败 → 返回 ""
│ └─ status != 200 → 返回 ""
│
├─ 7. 解析 JSON 响应
│ └─ choices[0].message.content → 返回回复文本
│
└─ 8. 解析失败 → 返回错误提示字符串
六、DeepSeek API 请求与响应格式
6.1 请求格式
cpp
POST https://api.deepseek.com/v1/chat/completions
Headers:
Authorization: Bearer sk-xxxxxxxx
Content-Type: application/json
Body:
{
"model": "deepseek-chat",
"messages": [
{"role": "user", "content": "你好"}
],
"temperature": 0.7,
"max_tokens": 2048
}
6.2 响应格式
cpp
HTTP/1.1 200 OK
Content-Type: application/json
{
"choices": [
{
"message": {
"role": "assistant",
"content": "你好!有什么我可以帮助你的吗?"
}
}
]
}
SDK 需要做的就是把 choices[0].message.content 提取出来返回给调用方。
七、总结
本篇覆盖了 DeepSeekProvider 的非流式部分:
| 函数 | 作用 |
|---|---|
initModel() |
从 map 中提取 API Key(必填)和 Endpoint(有默认值) |
isAvailable() |
返回 _isAvailable,由 initModel() 设置 |
getModelName() |
返回固定值 "deepseek-chat" |
getModelDesc() |
返回中文描述 |
sendMessage() |
构造 JSON → HTTP POST → 解析 JSON 响应 → 返回回复文本 |
sendMessage() 的核心流程是:构造请求 → 发送请求 → 解析响应,这与其他三个 Provider 的全量请求模式完全相同,区别只在于 API 端点和 JSON 字段的细节。
下一篇将深入
sendMessageStream(),解析 SSE 流式数据的接收与处理机制。