一、文件概述
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>> 配置列表,里面可能混合了 APIConfig 和 OllamaConfig。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 创建会话时自动填充(后续实现):
_sessionId→generateSessionId()生成_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
核心流转:
- 用户创建
Config(APIConfig/OllamaConfig),传入ChatSDK::initModels() ChatSDK通过dynamic_pointer_cast判断配置类型,分发给对应的LLMProvider- 用户通过
ChatSDK::createSession(modelName)创建会话,得到Session(含sessionId) - 用户通过
ChatSDK::sendMessage(sessionId, content)发消息,SDK 自动维护Message列表 - 所有
Session和Message通过DataManager持久化到 SQLite
这些后续实现,我们是从底层向上层讲解