【C++ AI 大模型接入 SDK】 - 公共数据结构

一、文件概述

common.h 定义了 SDK 中所有模块共用的基础数据结构,相当于整个项目的"数据字典"。它是纯头文件,没有 .cpp 实现,只包含结构体定义。

复制代码
common.h
  ├── Message      消息结构(用户发言、模型回复)
  ├── Config       模型配置基类
  ├── APIConfig    云端模型配置(需要 API Key)
  ├── OllamaConfig 本地模型配置(不需要 API Key)
  ├── ModelInfo    模型信息(名称、描述、是否可用)
  └── Session      会话信息(一次完整的对话上下文)

所有结构体都在 ai_chat_sdk 命名空间下。


二、Message --- 消息结构

2.1 完整源码

cpp 复制代码
struct Message{
    std::string _messageId;       // 消息ID
    std::string _role;            // 角色,如 user、assistant
    std::string _content;         // 消息内容
    std::time_t _timestamp;       // 消息发送时间戳

    Message(const std::string& role = "", const std::string& content = "")
        : _role(role), _content(content), _timestamp(0)
    {}
};

2.2 字段说明

字段 类型 说明
_messageId std::string 消息唯一标识,格式如 msg_1715366400_00000001
_role std::string 角色:"user" 用户消息,"assistant" 模型回复
_content std::string 消息正文内容
_timestamp std::time_t 消息发送的 Unix 时间戳

2.3 角色的含义

在与大模型对话时,消息按角色区分:

cpp 复制代码
Message userMsg("user", "请介绍一下 C++11 的智能指针");       // 用户发送
Message assistantMsg("assistant", "C++11 引入了三种智能指针..."); // 模型回复

LLM API 要求消息列表按 role 区分是谁说的,模型根据上下文(历史 user/assistant 消息)来生成回复。

2.4 构造函数设计

cpp 复制代码
Message(const std::string& role = "", const std::string& content = "")
    : _role(role), _content(content), _timestamp(0)
{}
  • 参数都有默认值,可以无参构造:Message msg;
  • 也可以直接传角色和内容:Message msg("user", "你好");
  • _messageId_timestamp 不在构造参数中,由 SessionManager (后续实现)在添加消息时自动生成

三、Config 体系 --- 模型配置

模型配置采用继承体系 ,用一个基类 Config 和两个子类来区分云端模型和本地模型:

复制代码
        Config(基类)
        ├── _modelName
        ├── _temperature
        ├── _maxTokens
        └── virtual ~Config() = default
              ↑
    ┌─────────┴──────────┐
    │                    │
APIConfig            OllamaConfig
	├── _apiKey          ├── _modelName
                         ├── _modelDesc
                         └── _endpoint

3.1 Config 基类

cpp 复制代码
struct Config{
    std::string _modelName;       // 模型名称
    double _temperature = 0.7;    // 温度参数
    int _maxTokens = 2048;       // 最大生成令牌数

    virtual ~Config() = default;  // 虚析构函数
};
字段 说明
_modelName 模型名称,如 "deepseek-chat""doubao-pro"
_temperature 温度参数(0.0 ~ 2.0),越低越确定性强,越高越随机有创意(官方文档中有说明)
_maxTokens 模型单次回复的最大 token 数(官方文档中有说明)
virtual ~Config() = default 的作用
cpp 复制代码
virtual ~Config() = default;

这一行看似简单,但非常重要 。它解决的是 向下转型(downcast)的安全性问题

在本项目中,ChatSDK 接收一个 vector<shared_ptr<Config>> 配置列表,里面可能混合了 APIConfigOllamaConfig。SDK 需要判断每个配置具体是哪种类型:

cpp 复制代码
// ChatSDK.cpp 中的实际代码
for(const auto& config : configs){
    // 尝试将 Config* 转为 APIConfig*
    if(auto apiConfig = std::dynamic_pointer_cast<APIConfig>(config)){
        // 这是一个云端模型配置
    }
    // 尝试将 Config* 转为 OllamaConfig*
    else if(auto ollamaConfig = std::dynamic_pointer_cast<OllamaConfig>(config)){
        // 这是一个本地模型配置
    }
}

dynamic_pointer_cast 能安全地执行向下转型,前提是基类必须有至少一个虚函数virtual ~Config() = default 就提供了这个虚函数,使得 dynamic_pointer_cast 可以正确判断对象的实际类型。

如果不加 virtual 析构函数,dynamic_pointer_cast 会编译失败,因为 Config 没有虚函数表(vtable),RTTI(运行时类型信息)无法工作。

3.2 APIConfig --- 云端模型配置

cpp 复制代码
struct APIConfig : public Config{
    std::string _apiKey;          // API密钥
};

继承 Config 的所有字段,额外增加 _apiKey。用于需要通过 API Key 认证的云端模型(DeepSeek、豆包、通义千问)。

使用示例(ChatServer.cpp 中的实际代码------后续讲解):

cpp 复制代码
auto deepseekConfig = std::make_shared<APIConfig>();
deepseekConfig->_modelName = "deepseek-chat";
deepseekConfig->_apiKey = "sk-xxxxxxxx";          // 从环境变量获取
deepseekConfig->_temperature = 0.7;
deepseekConfig->_maxTokens = 2048;

3.3 OllamaConfig --- 本地模型配置

cpp 复制代码
struct OllamaConfig : public Config{
    std::string _modelName;       // 模型名称
    std::string _modelDesc;       // 模型描述
    std::string _endpoint;        // 模型 API 地址
};

Ollama 本地模型不需要 API Key,但需要指定模型名称、描述和服务地址。

使用示例:

cpp 复制代码
auto ollamaConfig = std::make_shared<OllamaConfig>();          //这句代码解释同上
ollamaConfig->_modelName = "deepseek-r1:1.5b";
ollamaConfig->_modelDesc = "DeepSeek R1 1.5B 本地模型";
ollamaConfig->_endpoint = "http://localhost:11434";
ollamaConfig->_temperature = 0.7;
ollamaConfig->_maxTokens = 1024;

3.4 为什么 OllamaConfig 有自己的 _modelName?

注意到 OllamaConfig 有一个自己的 _modelName,和基类 Config 中的 _modelName两个不同的字段

  • Config::_modelName:SDK 内部用于注册和查找模型
  • OllamaConfig::_modelName:传递给 Ollama 服务的实际模型名称

这是因为 Ollama 支持任意本地模型,模型名称由用户决定,不能像云端模型那样硬编码。

3.5 配置在 SDK 中的流转

复制代码
用户创建 Config              ChatSDK 接收配置              分发到对应 Provider
─────────────              ──────────────              ──────────────────

APIConfig ───────┐
                 │
                 ├──→ vector<shared_ptr<Config>> ──→ dynamic_pointer_cast
                 │        传入 ChatSDK::initModels()     判断配置类型后分发
OllamaConfig ────┘                              │
												│
                                     			│
                 ┌────────────────────┼─────────┼──────────┬────────────────────┐
                 ↓                    ↓                    ↓                    ↓
          DeepSeekProvider      DoubaoProvider        QwenProvider        OllamaProvider
          使用 APIConfig        使用 APIConfig        使用 APIConfig      使用 OllamaConfig

四、ModelInfo --- 模型信息

4.1 完整源码

cpp 复制代码
struct ModelInfo{
    std::string _modelName;       // 模型名称
    std::string _modelDesc;       // 模型描述
    std::string _provider;        // 模型提供者
    std::string _endpoint;        // 模型 API 地址
    bool _isAvailable = false;    // 模型是否可用

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

4.2 字段说明

字段 说明
_modelName 模型名称,如 "deepseek-chat"
_modelDesc 模型描述,如 "中文优化的通用对话助手"
_provider 模型提供者标识
_endpoint API 地址,如 "https://api.deepseek.com"
_isAvailable 模型是否初始化成功并可用,默认 false

4.3 与 Config 的区别

对比 Config / APIConfig / OllamaConfig ModelInfo
用途 输入:用户传入的配置信息 输出:SDK 对外暴露的模型状态
包含 API Key、温度参数等敏感/运行参数 名称、描述、是否可用等展示信息
使用者 ChatSDK 内部初始化模型时使用 ChatServer 返回给前端展示用

ModelInfo 是只读的描述信息,由 LLMManager 在模型注册和初始化时创建,通过 ChatSDK::getAvailableModels() 返回给上层。


五、Session --- 会话结构

5.1 完整源码

cpp 复制代码
struct Session{
    std::string _sessionId;           // 会话ID
    std::string _modelName;           // 会话使用的模型名称
    std::vector<Message> _messages;   // 会话中的消息列表
    std::time_t _createdAt;           // 会话创建时间戳
    std::time_t _updatedAt;           // 会话最后更新时间戳

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

5.2 字段说明

字段 类型 说明
_sessionId std::string 会话唯一标识,格式如 session_1715366400_00000001
_modelName std::string 该会话绑定的模型名称
_messages std::vector<Message> 该会话的所有历史消息
_createdAt std::time_t 会话创建时间
_updatedAt std::time_t 最后一次消息的时间

5.3 会话的作用

在 AI 对话中,会话(Session) 是一次完整的对话上下文。它把用户和模型之间的多轮对话组织在一起:

复制代码
Session(会话ID: session_1715366400_00000001,模型: deepseek-chat)
  ├── Message(role="user", content="什么是智能指针?")
  ├── Message(role="assistant", content="智能指针是 C++11 引入的...")
  ├── Message(role="user", content="unique_ptr 和 shared_ptr 有什么区别?")
  └── Message(role="assistant", content="主要区别在于所有权模型...")

每次用户发消息时,SDK 会把该会话的完整历史消息发给模型,模型据此生成上下文相关的回复。这就是为什么 AI 能"记住"之前聊过的内容。

5.4 构造函数设计

cpp 复制代码
Session(const std::string& modelName = "")
    : _modelName(modelName)
{}

只需传模型名称,其他字段由 SessionManager 创建会话时自动填充(后续实现):

  • _sessionIdgenerateSessionId() 生成
  • _createdAt / _updatedAt → 当前时间戳
  • _messages → 初始为空列表

六、数据结构之间的关系

复制代码
ChatSDK
  ├── LLMManager
  │     ├── map<string, unique_ptr<LLMProvider>>   管理 Provider 实例
  │     └── map<string, ModelInfo>                 记录模型信息(对外暴露)
  │
  └── SessionManager
        ├── map<string, shared_ptr<Session>>        内存中的会话缓存
        │         └── Session
        │               └── vector<Message>         消息列表
        │
        └── DataManager
              └── SQLite 数据库                      持久化存储 Session + Message

核心流转:

  1. 用户创建 ConfigAPIConfig / OllamaConfig),传入 ChatSDK::initModels()
  2. ChatSDK 通过 dynamic_pointer_cast 判断配置类型,分发给对应的 LLMProvider
  3. 用户通过 ChatSDK::createSession(modelName) 创建会话,得到 Session(含 sessionId
  4. 用户通过 ChatSDK::sendMessage(sessionId, content) 发消息,SDK 自动维护 Message 列表
  5. 所有 SessionMessage 通过 DataManager 持久化到 SQLite

这些后续实现,我们是从底层向上层讲解