LLMProvider 接口与四类模型提供者实现
一、策略模式设计概述
项目采用**策略模式(Strategy Pattern)**设计 LLM 提供者层,将不同模型的调用逻辑封装到独立的实现类中,通过统一的接口对外暴露。
设计目标:
- 统一接口:所有模型提供者遵循相同的调用契约
- 解耦扩展:新增模型只需实现
LLMProvider接口,无需修改已有代码 - 多态调用:上层代码通过基类指针调用,运行时动态分发到具体实现
1.1 类图关系
┌─────────────────────┐
│ LLMProvider │ (抽象策略接口)
│ ─────────────────── │
│ + initModel() │
│ + isProviderAvailable() │
│ + getProviderName() │
│ + getProviderDesc() │
│ + sendMessage() │
│ + sendMessageStream() │
│ ─────────────────── │
│ # _isAvailable │
│ # _apiKey │
│ # _endpoint │
└──────────┬──────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ChatGPT │ │DeepSeek │ │Gemini │
│Provider │ │Provider │ │Provider │
└─────────────┘ └─────────────┘ └─────────────┘
┌─────────────┐
│OllamaLLM │
│Provider │
│ ─────────── │
│ # _modelName│
│ # _modelDesc│
└─────────────┘
二、LLMProvider 抽象接口
2.1 接口定义
class LLMProvider
{
public:
// 初始化模型(纯虚函数)
virtual bool initModel(const std::map<std::string, std::string>& modelConfig) = 0;
// 检查提供方是否可用
virtual bool isProviderAvailable() const = 0;
// 获取提供方名称/描述
virtual std::string getProviderName() const = 0;
virtual std::string getProviderDesc() const = 0;
// 发送消息——全量返回
virtual std::string sendMessage(const std::vector<Message>& messages,
const std::map<std::string, std::string>& requestParams) = 0;
// 发送消息——流式返回
virtual std::string sendMessageStream(const std::vector<Message>& messages,
const std::map<std::string, std::string>& requestParams,
std::function<void(const std::string&, bool)> callback) = 0;
protected:
bool _isAvailable = false; // 提供方是否可用
std::string _apiKey; // API 密钥
std::string _endpoint; // API 端点地址
};
2.2 接口方法说明
| 方法 | 说明 | 返回值 |
|---|---|---|
initModel() | 初始化模型,设置 API Key、Endpoint 等配置 | bool,初始化是否成功 |
isProviderAvailable() | 检查模型是否已初始化并可用 | bool |
getProviderName() | 获取模型名称标识 | std::string |
getProviderDesc() | 获取模型描述信息 | std::string |
sendMessage() | 发送消息并等待完整响应 | std::string,完整回复内容 |
sendMessageStream() | 流式发送消息,通过回调逐步返回 | std::string,聚合的完整回复 |
2.3 纯虚函数与保护成员
设计要点:
- 所有方法声明为纯虚函数(
= 0),强制子类实现 _isAvailable、_apiKey、_endpoint声明为 protected,子类可直接访问- 子类可根据需要扩展额外的成员变量(如
OllamaLLMProvider的_modelName)
三、四类 Provider 实现详解
3.1 DeepSeekProvider(标准 OpenAI 兼容)
3.1.1 基本信息
| 属性 | 值 |
|---|---|
| 模型名称 | deepseek-chat |
| API 端点 | https://api.deepseek.com |
| API 路径 | /v1/chat/completions |
| 协议格式 | OpenAI 兼容格式 |
3.1.2 initModel 实现
bool DeepSeekProvider::initModel(const std::map<std::string, std::string>& modelConfig)
{
// 1. 提取 api_key
auto it = modelConfig.find("api_key");
if(it == modelConfig.end()) {
ERR("api_key not found");
return false;
}
_apiKey = it->second;
// 2. 提取 endpoint(可选,有默认值)
it = modelConfig.find("endpoint");
if(it == modelConfig.end()) {
_endpoint = "https://api.deepseek.com"; // 默认端点
} else {
_endpoint = it->second;
}
_isAvailable = true;
return true;
}
特点:
endpoint参数可选,提供默认值- 只需
api_key即可完成初始化
3.1.3 sendMessage(全量返回)实现流程
┌──────────────────────────────────────────────────────────────┐
│ sendMessage 全量流程 │
├──────────────────────────────────────────────────────────────┤
│ 1. 检查 _isAvailable │
│ │ │
│ ▼ │
│ 2. 提取请求参数(temperature, max_tokens) │
│ │ │
│ ▼ │
│ 3. 构造 JSON 请求体 │
│ { │
│ "model": "deepseek-chat", │
│ "messages": [...], │
│ "temperature": 0.7, │
│ "max_tokens": 2048 │
│ } │
│ │ │
│ ▼ │
│ 4. 序列化 JSON │
│ │ │
│ ▼ │
│ 5. 创建 httplib::Client,设置超时 │
│ │ │
│ ▼ │
│ 6. POST /v1/chat/completions │
│ │ │
│ ▼ │
│ 7. 检查响应状态码(200) │
│ │ │
│ ▼ │
│ 8. 解析响应 JSON,提取 choices[0].message.content │
│ │ │
│ ▼ │
│ 9. 返回回复内容 │
└──────────────────────────────────────────────────────────────┘
请求体格式:
{
"model": "deepseek-chat",
"messages": [
{"role": "user", "content": "你好"}
],
"temperature": 0.7,
"max_tokens": 2048
}
响应体格式:
{
"choices": [
{
"message": {
"role": "assistant",
"content": "你好!有什么我可以帮助你的吗?"
}
}
]
}
3.1.4 sendMessageStream(流式返回)实现流程
┌──────────────────────────────────────────────────────────────┐
│ sendMessageStream 流式流程 │
├──────────────────────────────────────────────────────────────┤
│ 1. 构造请求体,添加 "stream": true │
│ │ │
│ ▼ │
│ 2. 设置请求头 Accept: text/event-stream │
│ │ │
│ ▼ │
│ 3. 创建 httplib::Request 对象 │
│ │ │
│ ▼ │
│ 4. 设置 response_handler(检查状态码) │
│ │ │
│ ▼ │
│ 5. 设置 content_receiver(数据接收器) │
│ │ │
│ ├──▶ 追加数据到 buffer │
│ │ │
│ ├──▶ 按 "\n\n" 分割数据块 │
│ │ │
│ ├──▶ 解析 "data: " 前缀的 SSE 事件 │
│ │ │
│ ├──▶ 检测 "[DONE]" 结束标记 │
│ │ │
│ ├──▶ 提取 delta.content 增量内容 │
│ │ │
│ └──▶ 调用 callback(content, false) │
│ │ │
│ ▼ │
│ 6. 流结束,调用 callback("", true) │
│ │ │
│ ▼ │
│ 7. 返回聚合的完整响应 │
└──────────────────────────────────────────────────────────────┘
SSE 数据格式:
data: {"choices":[{"delta":{"content":"你"}}]}
data: {"choices":[{"delta":{"content":"好"}}]}
data: {"choices":[{"delta":{"content":"!"}}]}
data: [DONE]
流式处理关键代码:
// 设置数据接收处理器
req.content_receiver = [&](const char* data, size_t len, size_t offset, size_t totallength)
{
buffer.append(data, len);
// 按 "\n\n" 分割 SSE 事件
size_t pos = 0;
while((pos = buffer.find("\n\n", pos)) != std::string::npos)
{
std::string chunk = buffer.substr(0, pos);
buffer.erase(0, pos + 2);
if(chunk.compare(0, 6, "data: ") == 0)
{
std::string modelData = chunk.substr(6);
if(modelData == "[DONE]") {
streamFinished = true;
return true;
}
// 解析 JSON 提取增量内容
Json::Value modelDataJson;
// ...
std::string content = modelDataJson["choices"][0]["delta"]["content"].asString();
fullResponse += content;
callback(content, false);
}
}
return true;
};
3.2 ChatGPTProvider(OpenAI 新 API 格式)
3.2.1 基本信息
| 属性 | 值 |
|---|---|
| 模型名称 | gpt-4o-mini |
| API 端点 | https://api.openai.com |
| API 路径 | /v1/responses(新 API) |
| 代理设置 | http://127.0.0.1:7890(硬编码) |
3.2.2 与 DeepSeek 的主要差异
| 差异点 | ChatGPTProvider | DeepSeekProvider |
|---|---|---|
| API 路径 | /v1/responses | /v1/chat/completions |
| 请求字段 | input(数组) | messages(数组) |
| 响应字段 | output[0].content[0].text | choices[0].message.content |
| 流式事件 | response.output_text_delta 等 | data: + delta.content |
| 代理 | 强制设置代理 | 无代理 |
3.2.3 请求体格式差异
ChatGPT 请求体:
{
"model": "gpt-4o-mini",
"input": [ // 注意:使用 input 而非 messages
{"role": "user", "content": "你好"}
],
"temperature": 0.7,
"max_output_tokens": 2048 // 注意:字段名不同
}
响应体格式:
{
"output": [
{
"content": [
{"text": "你好!有什么我可以帮助你的吗?"}
]
}
]
}
3.2.4 流式事件类型
ChatGPT 使用**事件类型(event type)**区分不同阶段:
| 事件类型 | 说明 |
|---|---|
response.output_text_delta | 增量文本内容 |
response.output_item.done | 单个输出项完成(包含完整文本) |
response.completed | 整个响应完成 |
SSE 格式:
event: response.output_text_delta
data: {"delta": "你"}
event: response.output_item.done
data: {"item": {"content": [{"text": "你好!"}]}}
event: response.completed
data: {}
3.2.5 代理设置(硬编码)
// 硬编码代理地址
client.set_proxy("http://127.0.0.1", 7890);
问题:代理地址硬编码,不同网络环境需要修改源码。
3.3 GeminiProvider(Google OpenAI 兼容模式)
3.3.1 基本信息
| 属性 | 值 |
|---|---|
| 模型名称 | gemini-2.0-flash |
| API 端点 | https://generativelanguage.googleapis.com |
| API 路径 | /v1beta/openai/chat/completions |
| 协议格式 | OpenAI 兼容模式 |
3.3.2 与 DeepSeek 的差异
| 差异点 | GeminiProvider | DeepSeekProvider |
|---|---|---|
| API 路径 | /v1beta/openai/chat/completions | /v1/chat/completions |
| 代理 | 强制设置代理 | 无代理 |
| 响应格式 | OpenAI 兼容(相同) | OpenAI 兼容(相同) |
3.3.3 initModel 特殊点
// 注意:配置键名是 "api_Key"(大小写敏感)
auto apiKey_it = modelConfig.find("api_Key");
auto endpointIt = modelConfig.find("endpoint");
if(apiKey_it == modelConfig.end() || endpointIt == modelConfig.end()) {
ERR("api_Key or endpoint not found");
return false;
}
问题:键名 "api_Key" 与其他 Provider 的 "api_key" 不一致(大小写差异)。
3.3.4 流式响应
与 DeepSeek 相同,使用标准 SSE 格式,通过 delta.content 提取增量内容。
3.4 OllamaLLMProvider(本地模型)
3.4.1 基本信息
| 属性 | 值 |
|---|---|
| 模型名称 | 用户指定(如 deepseek-r1:1.5b) |
| API 端点 | 用户指定(如 http://localhost:11434) |
| API 路径 | /api/chat |
| 认证方式 | 无需 API Key |
3.4.2 特殊成员变量
protected:
std::string _modelName; // 模型名称(动态)
std::string _modelDesc; // 模型描述信息
特点:不使用 _apiKey,模型名称通过配置动态指定。
3.4.3 initModel 实现
bool OllamaLLMProvider::initModel(const std::map<std::string, std::string>& modelConfig)
{
// 必需参数:model_name
if(modelConfig.find("model_name") == modelConfig.end()) {
ERR("model_name not found");
return false;
}
_modelName = modelConfig.at("model_name");
// 必需参数:model_desc
if(modelConfig.find("model_desc") == modelConfig.end()) {
ERR("model_desc not found");
return false;
}
_modelDesc = modelConfig.at("model_desc");
// 必需参数:endpoint
if(modelConfig.find("endpoint") == modelConfig.end()) {
ERR("endpoint not found");
return false;
}
_endpoint = modelConfig.at("endpoint");
_isAvailable = true;
return true;
}
必需配置项:
model_name:Ollama 模型名称(如deepseek-r1:1.5b)model_desc:模型描述(用于展示)endpoint:Ollama 服务地址(如http://localhost:11434)
3.4.4 请求体格式
{
"model": "deepseek-r1:1.5b",
"messages": [
{"role": "user", "content": "你好"}
],
"options": {
"temperature": 0.7,
"num_ctx": 1024
},
"stream": false
}
差异:
- 使用
options包装温度等参数 - 使用
num_ctx而非max_tokens
3.4.5 响应体格式
{
"message": {
"role": "assistant",
"content": "你好!有什么我可以帮助你的吗?"
}
}
差异:响应结构与其他 API 不同,没有 choices 数组。
3.4.6 流式响应格式(非 SSE)
关键差异:Ollama 流式响应不使用 SSE 格式,而是每行一个 JSON 对象:
{"message":{"content":"你"},"done":false}
{"message":{"content":"好"},"done":false}
{"message":{"content":"!"},"done":true}
流式处理代码:
// 按 "\n" 分割,而非 "\n\n"
while((pos = buffer.find("\n", pos)) != std::string::npos)
{
std::string chunk = buffer.substr(0, pos);
buffer.erase(0, pos + 1);
// 解析 JSON
Json::Value chunkJson;
// ...
// 检查结束标记:done 字段
if(chunkJson.get("done", false).asBool()) {
streamFinish = true;
callback("", true);
return true;
}
// 提取内容
std::string delta = chunkJson["message"]["content"].asString();
fullData += delta;
callback(delta, false);
}
四、四种 Provider 对比总结
4.1 基础信息对比
| Provider | 模型名称 | API 路径 | 协议格式 | 认证方式 |
|---|---|---|---|---|
| DeepSeekProvider | deepseek-chat | /v1/chat/completions | OpenAI 标准 | Bearer Token |
| ChatGPTProvider | gpt-4o-mini | /v1/responses | OpenAI 新格式 | Bearer Token |
| GeminiProvider | gemini-2.0-flash | /v1beta/openai/chat/completions | OpenAI 兼容 | Bearer Token |
| OllamaLLMProvider | 用户指定 | /api/chat | Ollama 原生 | 无 |
4.2 初始化参数对比
| Provider | 必需参数 | 可选参数 |
|---|---|---|
| DeepSeekProvider | api_key | endpoint(有默认值) |
| ChatGPTProvider | api_key, endpoint | 无 |
| GeminiProvider | api_Key, endpoint | 无 |
| OllamaLLMProvider | model_name, model_desc, endpoint | 无 |
4.3 请求体格式对比
// DeepSeek / Gemini(OpenAI 标准)
{
"model": "deepseek-chat",
"messages": [...],
"temperature": 0.7,
"max_tokens": 2048,
"stream": true
}
// ChatGPT(新 API)
{
"model": "gpt-4o-mini",
"input": [...], // 字段名不同
"temperature": 0.7,
"max_output_tokens": 2048, // 字段名不同
"stream": true
}
// Ollama(原生格式)
{
"model": "deepseek-r1:1.5b",
"messages": [...],
"options": { // 嵌套结构
"temperature": 0.7,
"num_ctx": 1024 // 字段名不同
},
"stream": true
}
4.4 流式响应处理对比
| Provider | 数据分割符 | 结束标记 | 增量内容路径 |
|---|---|---|---|
| DeepSeekProvider | \n\n | data: [DONE] | choices[0].delta.content |
| ChatGPTProvider | \n\n | event: response.completed | delta 或 output_item.done |
| GeminiProvider | \n\n | data: [DONE] | choices[0].delta.content |
| OllamaLLMProvider | \n | done: true | message.content |

329

被折叠的 条评论
为什么被折叠?



