【C++ AI 大模型接入 SDK】 - DeepSeek 模型接入(上)


大家好,我是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" 日志宏:INFOERRWARN
<cstdint> size_t等标准整数类型(流式处理函数参数用到)
<jsoncpp/json/json.h> JsonCpp 核心:Json::ValueJson::StreamWriterBuilder
<httplib.h> cpp-httplib:HTTP 客户端,发送请求到DeepSeek API
<jsoncpp/json/reader.h> JsonCpp 读取:Json::CharReaderBuilderparseFromStream

其中 <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() _isAvailablebool 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.7maxTokens = 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::stringoperator+ 拼接,等价于 "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 响应)→ trueresponse 有值
  • 网络层面失败(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()){

三重检查:

  1. isMember("choices") --- JSON 中是否存在 choices 字段
  2. isArray() --- choices 是否是数组类型
  3. !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 层级深入到 messagecontent
  • .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 流式数据的接收与处理机制。

相关推荐
加号31 小时前
【C#】 串口通信技术深度解析及实现
开发语言·c#
sycmancia2 小时前
Qt——编辑交互功能的实现
开发语言·qt
石山代码3 小时前
C++ 内存分区 堆区
java·开发语言·c++
无风听海3 小时前
C# 隐式转换深度解析
java·开发语言·c#
一只大袋鼠4 小时前
Git 进阶(二):分支管理、暂存栈、远程仓库与多人协作
java·开发语言·git
LuminousCPP4 小时前
数据结构 - 线性表第四篇:C 语言通讯录优化升级全记录(踩坑 + 思考)
c语言·开发语言·数据结构·经验分享·笔记·学习
web3.08889994 小时前
1688 图搜接口(item_search_img / 拍立淘) 接入方法
开发语言·python
张小姐的猫5 小时前
【Linux】多线程 —— 线程互斥
linux·运维·服务器·c++
один but you5 小时前
从可变参数到 emplace:现代 C++ 性能优化的核心组合
java·开发语言