
🔥小叶-duck:个人主页
❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux系统从入门到实践》《Linux网络从入门到实践》
✨未择之路,不须回头
已 择之路,纵是荆棘遍野,亦作花海遨游
目录
[一、Gemini API 体系概述](#一、Gemini API 体系概述)
[1.1 两套 API 设计机制与选型](#1.1 两套 API 设计机制与选型)
[1.1.1 设计背景](#1.1.1 设计背景)
[1.1.2 接入选型策略](#1.1.2 接入选型策略)
[1.2 Gemini OpenAI 兼容 API 协议规范](#1.2 Gemini OpenAI 兼容 API 协议规范)
[1.2.1 接口端点](#1.2.1 接口端点)
[1.2.2 请求头规范](#1.2.2 请求头规范)
[1.2.3 请求体核心参数](#1.2.3 请求体核心参数)
[二、Apifox 接口测试实操流程](#二、Apifox 接口测试实操流程)
[2.1 环境管理](#2.1 环境管理)
[2.2 接口基础信息填写](#2.2 接口基础信息填写)
[2.3 请求 Body(JSON)配置](#2.3 请求 Body(JSON)配置)
[2.4 请求头配置](#2.4 请求头配置)
[2.5 网络代理配置](#2.5 网络代理配置)
[2.6 发送请求,查看响应结果](#2.6 发送请求,查看响应结果)
[三、GeminiProvider 头文件设计](#三、GeminiProvider 头文件设计)
[3.1 文件与依赖规范](#3.1 文件与依赖规范)
[3.2 类接口标准声明](#3.2 类接口标准声明)
[四、GeminiProvider 源文件核心实现](#四、GeminiProvider 源文件核心实现)
[4.1 模型初始化接口](#4.1 模型初始化接口)
[4.1.1 处理逻辑](#4.1.1 处理逻辑)
[4.1.2 实现代码](#4.1.2 实现代码)
[4.2 状态与元信息接口](#4.2 状态与元信息接口)
[4.2.1 可用性检测](#4.2.1 可用性检测)
[4.2.2 模型描述获取](#4.2.2 模型描述获取)
[五、全量响应 sendMessage 实现](#五、全量响应 sendMessage 实现)
[5.1 检查模型是否有效](#5.1 检查模型是否有效)
[5.2 构造并获取请求参数](#5.2 构造并获取请求参数)
[5.3 构造历史消息,组装 message 数组](#5.3 构造历史消息,组装 message 数组)
[5.4 组装请求体 JSON](#5.4 组装请求体 JSON)
[5.5 JSON 序列化](#5.5 JSON 序列化)
[5.6 httplib 客户端初始化 + 代理环境变量处理](#5.6 httplib 客户端初始化 + 代理环境变量处理)
[5.7 设置请求头](#5.7 设置请求头)
[5.8 发送 POST 请求,校验返回状态](#5.8 发送 POST 请求,校验返回状态)
[5.9 解析响应体](#5.9 解析响应体)
[5.10 提取返回的响应内容](#5.10 提取返回的响应内容)
[六、GTest 全量响应代码测试(TestLLM.cpp)](#六、GTest 全量响应代码测试(TestLLM.cpp))
[6.1 CMakeLists.txt](#6.1 CMakeLists.txt)
前言
在前序章节中,我们已经完成了 SDK 基础框架、日志组件、通用 LLMProvider 抽象基类的搭建,并且先后实现了 DeepSeek、ChatGPT 两个模型的接入逻辑,对 Chat Completions API、Responses API 两套接口的差异、流式 SSE 协议解析都有了比较完整的认知。
本章我们将继续扩展这套可复用的 SDK 框架,完成 Gemini 模型的接入开发。Gemini 存在原生 API 与 OpenAI 兼容 API 两套不同的调用体系,二者鉴权方式、报文结构差异明显,因此在正式编码之前,我们会先梳理两套 API 的设计区别,明确本次项目选用兼容 OpenAI 接口的选型理由,依托网关抹平协议差异,最大限度复用已有抽象层与上层调用逻辑。
内容会从接口协议规范、Apifox 预调试实操开始,再逐步完成 GeminiProvider 头文件声明、初始化、状态检测接口的实现,重点落地全量响应
sendMessage接口,同时处理 Gemini 访问独有的代理配置、鉴权区别等工程问题;最后借助 GTest 编写测试用例、CMake 管理编译,验证整个链路的可用性。
一、Gemini API 体系概述
1.1 两套 API 设计机制与选型
1.1.1 设计背景
Gemini 对外提供两套完全不同的 API 体系,二者定位和发展路线差异很大:
- OpenAI 兼容 API:属于厂商面向存量开发者的过渡方案,核心目的是降低迁移成本,让已经基于 OpenAI 技术栈开发的项目可以快速跑通 Gemini。但这套方案只是兼容层,长期依赖它会被限制在 OpenAI 的协议能力范围内,无法发挥 Gemini 独有的能力,厂商也很难建立属于自己的技术生态护城河。
- 原生 API(
models.generateContent):Google 主推的标准接口,完整释放 Gemini 多模态、工具调用、代码执行、实时检索等原生能力,适合垂直场景深度定制,是 Google 构建自身生态护城河的核心方案。
从行业演进规律来看,很多大模型厂商都会遵循相似路径 :早期 以兼容 API 快速吸引开发者,站稳脚跟之后,再推出自有原生接口标准。

这里有一个很关键的对比,帮我们理解 DeepSeek 和 Gemini 策略上的区别:
- DeepSeek 快速跟进 Responses API: DeepSeek 的产品定位就是对 OpenAI 做无痛平替 ,极低迁移成本是它生态上的核心优势。只要 OpenAI 推出新接口标准(例如从 Chat Completions 演进到面向 Agent 场景的 Responses API),DeepSeek 会快速跟进兼容。开发者甚至不用修改 SDK 调用代码,只替换
api_key和base_url,就可以在 ChatGPT 与 DeepSeek 之间切换。 - Gemini 的 OpenAI 兼容接口定位是 "敲门砖": Google 和 OpenAI 属于直接竞争关系,并不希望 Gemini 仅仅作为 OpenAI 的替代品。它提供的
/v1beta/openai/chat/completions只是一个最低限度的兼容入口,主要用来给存量旧系统做快速测试,吸引开发者体验 Gemini。 Google 重心维护的是自家 GenAI SDK 以及generateContent原生接口。Gemini 很多特色能力:实时搜索 Grounding、上下文缓存、原生音视频多模态、思维签名等,在 OpenAI 兼容协议里无法完整表达。Google 也没有跟进兼容 Responses API,这是我们后续编码实现时必须留意的重点。

1.1.2 接入选型策略
- 快速迁移、复用已有 OpenAI 代码:选用 OpenAI 兼容 API,只需要修改密钥、BaseURL、模型名称三处配置。
- 需要使用 Gemini 多模态、长上下文、工具调用等特色能力:选用 Google 原生 API。
- **本工程接入方案:**优先使用 OpenAI 兼容 API 完成基础对话能力接入,降低开发成本;同时在代码架构上预留原生 API 的扩展入口,后续有需要可以平滑切换。
1.2 Gemini OpenAI 兼容 API 协议规范
Gemini 兼容层支持 chat/completions ,而前面我们的 deepseek 正好也是通过 Chat Completions API接入的,所以在 Gemini 在接口设计上和前面的 deepseek 整体是基本一致的,也就没有太多的注意事项了。
1.2.1 接口端点
- 请求方法:
POST - 基准 URL:
https://generativelanguage.googleapis.com - 接口路径:
/v1beta/openai/chat/completions - 完整请求地址:
https://generativelanguage.googleapis.com/v1beta/openai/chat/completions
1.2.2 请求头规范
| 参数名 | 类型 | 取值规范 |
|---|---|---|
| Content-Type | string | 固定值 application/json |
| Authorization | string | 格式为 Bearer {GEMINI_API_KEY} |
1.2.3 请求体核心参数
| 参数名 | 类型 | 说明 |
|---|---|---|
| model | string | 模型标识,例如 gemini-3.6-flash |
| messages | array | 对话消息列表,每条包含 role 和 content 字段 |
| temperature | float | 采样温度,控制生成随机性,常用 0.7 |
| max_tokens | integer | 单次生成最大 Token 上限 |
| stream | boolean | 是否开启流式响应,true 增量流式返回,false 全量一次性返回 |
请求体示例:
cpp
{
"model": "gemini-3.6-flash",
"messages": [
{
"role": "user",
"content": "你是谁?"
}
],
"temperature": 0.7,
"max_tokens": 2048,
"stream": false
}
二、Apifox 接口测试实操流程
2.1 环境管理
在 Apifox 中先完成环境变量配置,方便后续复用密钥与地址,也便于多环境切换。
- 全局前置 URL:
https://generativelanguage.googleapis.com - 环境变量:
GEMINI_API_KEY:存放 Gemini 的访问密钥,在 Apifox 内加密存储,避免明文写在请求里。
- **环境隔离:**可以区分开发、测试、正式、本地 Mock 多套环境,互不干扰。
2.2 接口基础信息填写
Gemini 兼容 OpenAI 的Chat Completions接口,不支持 Responses API,这一点是和 ChatGPT 最大的区别;报文结构和我们之前实现的 DeepSeek 保持一致。
- 请求方式:
POST - 完整地址:
https://generativelanguage.googleapis.com/v1beta/openai/chat/completions - 拆分理解:
- base_url:
https://generativelanguage.googleapis.com/v1beta/openai - 接口路径:
/chat/completions
- base_url:

2.3 请求 Body(JSON)配置
Body 选择 JSON 类型,核心参数如下:
bash
{
"model": "gemini-3.6-flash",
"messages": [
{
"role": "user",
"content": "你好,请简单介绍自己"
}
],
"temperature": 0.7,
"max_tokens": 2048,
"stream": false
}
stream: false:全量一次性返回;stream: true:开启 SSE 流式增量返回。

2.4 请求头配置
在 Headers 标签页添加两项:
Content-Type: application/jsonAuthorization: Bearer {``{GEMINI_API_KEY}}
{``{GEMINI_API_KEY}}会自动读取上面配置好的环境变量。

2.5 网络代理配置
和 OpenAI 的官方接口 一样,Gemini 官方接口也属于外网服务,需要代理网络才能访问,在 Apifox 的代理设置里单独配置:
- **接口请求代理:**仅 API 流量走代理,Apifox 本身网页访问不受影响
- 自定义代理地址: 填写代理 IP 和端口,示例:
127.0.0.1:7890(本地代理工具常用默认端口,如果代理工具有指定端口则使用指定端口) - **代理绕过列表:**配置不需要走代理的域名,英文逗号分隔
2.6 发送请求,查看响应结果
① 全量响应(stream=false)
返回结构是标准 Chat Completions 格式,顶层choices数组,message中存放完整回答内容,包含finish_reason、usage消耗统计等字段。这个结构也是后续我们实现全量响应代码解析响应体的参考。
bash
{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "你好!我是由Google训练的大型语言模型......",
"role": "assistant"
}
}
],
"created": 1793514015,
"id": "GdrmapNKJylqtsPoL7MQ",
"model": "gemini-3.6-flash",
"object": "chat.completion",
"usage": {
"completion_tokens": 164,
"prompt_tokens": 7,
"total_tokens": 862
}
}

② 流式响应(stream=true)
开启流式之后,服务端以 SSE 分片持续推送数据,每一条data:对应的 JSON 里面使用delta字段携带增量文本,没有额外事件类型区分 ,和 DeepSeek 的 Chat Completions 流式格式一致;流的末尾会单独推送一条 data: [DONE] 标记本次对话结束。
这个结构也是后续我们实现流式响应代码解析响应体的参考。
bash
{
"choices": [
{
"delta": {
"content": "你好!我是由 Google 训练的大型语言模型。你可以把我",
"role": "assistant"
},
"index": 0
}
],
"created": 1793541724,
"id": "WEbnaq2_GabVz7IPrsLN8Qw",
"model": "gemini-3.6-flash",
"object": "chat.completion.chunk"
}

三、GeminiProvider 头文件设计
3.1 文件与依赖规范
- 头文件路径 :
SDK/include/GeminiProvider.h - 源文件路径 :
SDK/src/GeminiProvider.cpp - 第三方依赖:jsoncpp(JSON 序列化与解析)、httplib(HTTP 网络请求)
- 内部依赖:日志工具 myLog.h、抽象基类 LLMProvider.h、通用消息结构体定义
设计思路和前面 DeepSeekProvider、ChatGPTProvider 保持统一,遵循同一个抽象契约,保证 SDK 框架可以无差别切换不同厂商大模型,这也是策略模式带来的扩展性。
3.2 类接口标准声明
cpp
#ifndef GEMINI_PROVIDER_H
#define GEMINI_PROVIDER_H
#include "LLMProvider.h"
namespace ai_chat_sdk
{
// GeminiProvider模型提供者类:用于与Gemini模型交互
class GeminiProvider : public LLMProvider
{
public:
// 初始化模型:根据配置初始化模型,只接收全局连接配置(api_key、base_url)
bool initModel(const std::unordered_map<std::string, std::string> &modelConfig) override;
// 判断模型是否有效
bool IsAvailable() override;
// 获取模型描述
std::string getModelDesc() override;
// 发送消息:全量调用,返回完整响应
// requestParams:针对Gemini模型本次请求专属参数,包含model、temperature、max_output_tokens等
std::optional<std::string> sendMessage(const std::vector<Message> &messages,
const std::unordered_map<std::string, std::string> &requestParams) override;
// 发送消息:增量调用,实时返回响应
// requestParams:针对Gemini模型本次请求专属参数,包含model、temperature、max_output_tokens等
std::optional<std::string> sendMessageStream(const std::vector<Message> &messages,
const std::unordered_map<std::string, std::string> &requestParams,
std::function<void(const std::string &response, bool isEnd)> callback) override;
// 析构函数:释放模型资源
~GeminiProvider() = default;
};
} // namespace ai_chat_sdk
#endif
四、GeminiProvider 源文件核心实现
4.1 模型初始化接口
4.1.1 处理逻辑
- 从配置 map 读取
api_key字段,不存在或者为空字符串,打印错误日志,返回 false - 从配置 map 读取
base_url字段,不存在则打印错误日志,返回 false - 两个参数校验通过,赋值给成员变量,标记
_isAvailable = true,打印初始化成功日志,返回 true
4.1.2 实现代码
cpp
#include "../include/GeminiProvider.h"
namespace ai_chat_sdk
{
// 初始化模型:根据配置初始化模型,只接收全局连接配置(api_key、base_url)
bool GeminiProvider::initModel(const std::unordered_map<std::string, std::string> &modelConfig)
{
auto it = modelConfig.find("api_key");
if (it == modelConfig.end() || it->second == "")
{
ERR("GeminiProvider initModel api_key not found or not config_error");
return false;
}
else
{
_apikey = it->second;
}
it = modelConfig.find("base_url");
if (it == modelConfig.end())
{
ERR("GeminiProvider initModel base_url not found");
return false;
}
else
{
_baseUrl = it->second;
}
_isAvailable = true;
INFO("GeminiProvider initModel success, apikey: {}, base_url: {}", _apikey, _baseUrl);
return true;
}
代码分析:
- 查找
api_key的时候同时做两层判断:key 是否存在 + value 是否为空,防止拿到空密钥; base_url只判断 key 是否存在,这里可以根据后续需求补充空值校验;- 校验全部通过后,打开可用标记
_isAvailable,同时打印 INFO 日志,方便调试的时候确认配置加载情况; - 初始化只保存连接信息,不绑定模型名称、温度等参数 ,遵循我们之前统一的设计规范:连接配置属于 Provider 实例,推理参数属于单次请求。
4.2 状态与元信息接口
4.2.1 可用性检测
cpp
// 判断模型是否有效
bool GeminiProvider::IsAvailable()
{
return _isAvailable;
}
分析: 直接返回内部布尔标记_isAvailable,上层调用方可以在发消息前先调用这个接口做前置校验,避免在未初始化成功的时候发起网络请求。
4.2.2 模型描述获取
cpp
// 获取模型描述
std::string GeminiProvider::getModelDesc()
{
return "gemini-3.6-flash 是 Google 专为 Agent 与复杂工作流优化的轻量主力模型,在保持极致低延迟和低成本的同时,提供前沿的编码、推理与全模态处理能力";
}
**分析:**返回 Gemini 模型的介绍文本,和 DeepSeek、ChatGPT 的 getModelDesc 保持一样的输出格式,上层代码不需要修改,就可以打印任意一个 Provider 的信息,充分体现多态的价值。
五、全量响应 sendMessage 实现
5.1 检查模型是否有效
cpp
// 1. 检查模型是否有效
if (!IsAvailable())
{
ERR("GeminiProvider sendMessage model not available");
return std::nullopt;
}
代码分析: 在发起网络请求之前,先做前置校验。IsAvailable() 读取内部标记 _isAvailable,判断当前 Provider 是否已经完成initModel初始化、密钥和地址是否就绪。
如果没有初始化成功,直接打印错误日志,返回std::nullopt,避免无效的网络调用。
5.2 构造并获取请求参数
cpp
// 2. 构造并获取请求参数
// 2.1 构造默认请求参数:如果requestParams中没有指定temperature、max_tokens,则使用默认值
double temperature = 0.7;
int max_tokens = 2048;
std::string model;
// 2.2 从requestParams中获取temperature、max_tokens参数;如果requestParams中指定了,则使用指定值
auto it = requestParams.find("temperature");
if (it != requestParams.end())
{
temperature = std::stod(it->second);
}
it = requestParams.find("max_tokens");
if (it != requestParams.end())
{
max_tokens = std::stoi(it->second);
}
it = requestParams.find("model");
if (it != requestParams.end())
{
model = it->second;
}
else
{
ERR("model name not found in requestParams, must config it");
return std::nullopt;
}
代码分析
- 先设置默认参数:temperature 默认 0.7、max_tokens 默认 2048,用户不传的时候自动兜底;
- 从入参
requestParams(单次请求参数 map)读取参数,用户传入的值会覆盖默认值; model模型名称不设置默认值,强制要求调用方在每次请求时传入,缺失直接报错返回;- 符合我们整体框架设计:
api_key/base_url是实例初始化阶段的全局配置,model/temperature/max_tokens属于单次推理参数,每次调用可以独立修改。
5.3 构造历史消息,组装 message 数组
cpp
// 2.3 构造并获取历史消息,获取请求体中的 messages 这个json数组
Json::Value messageArray = Json::arrayValue;
for (auto &message : messages)
{
Json::Value messageObj;
messageObj["role"] = message._role;
messageObj["content"] = message._content;
messageArray.append(messageObj);
}
代码分析: 遍历上层传入的 std::vector<Message> 消息列表,循环把每一条消息转换成 JsonCpp 的 JSON 对象,放到数组里。
注意: 这里的 role/content 字段是适配 OpenAI 兼容格式 的 Gemini 接入方式;原生 Gemini 接口字段名称和结构不一样,本项目选用兼容 OpenAI 的网关方式,降低多 Provider 之间的代码差异。
5.4 组装请求体 JSON
cpp
// 3. 构造请求体
// 和历史消息构造一样,请求体的字段名称必须与gemini模型的接口文档中要求的一致
Json::Value requestBody;
requestBody["messages"] = messageArray;
requestBody["temperature"] = temperature;
requestBody["max_tokens"] = max_tokens;
requestBody["model"] = model;
代码分析: 把上面组装好的消息数组、温度、最大输出 token、模型名称,全部填入请求根 JSON 对象。
这一步是报文组装的核心,字段必须和接口文档对齐,字段名写错会直接返回 400 参数错误。
5.5 JSON 序列化
cpp
// 4. 对requestBody进行序列化
Json::StreamWriterBuilder writerbuilder;
std::string requestBodyStr = Json::writeString(writerbuilder, requestBody);
INFO("GeminiProvider sendMessage requestBody: {}", requestBodyStr);
代码分析: 使用 JsonCpp 的StreamWriterBuilder将 Json::Value 序列化为字符串,同时打印完整请求报文日志,方便调试时排查参数错误。
5.6 httplib 客户端初始化 + 代理环境变量处理
cpp
// 5. 使用cpp-httplib构造HTTP客户端
httplib::Client client(scheme_host_port, _baseUrl);
// 5.1 设置超时时间
client.set_read_timeout(sec(60), usec(0)); // 读取超时时间60秒
client.set_connection_timeout(sec(20), usec(0)); // 连接超时时间20秒
// 5.2 关键点:从环境变量中获取代理配置
char *proxy_env = std::getenv("MY_APP_PROXY");
if (proxy_env != nullptr)
{
std::string proxy_str(proxy_env);
auto pos = proxy_str.find(":");
if (pos == std::string::npos)
{
ERR("GeminiProvider sendMessage proxy format error, must be host:port");
return std::nullopt;
}
else
{
std::string proxy_host = proxy_str.substr(0, pos);
int proxy_port = std::stoi(proxy_str.substr(pos + 1));
client.set_proxy(proxy_host, proxy_port);
}
}
else
{
// 针对Gemini模型的特殊情况:如果用户没有配置代理环境变量,那请求无法访问到Gemini模型的服务器
ERR("GeminiProvider sendMessage proxy_env not found, must config it: MY_APP_PROXY=host:port");
return std::nullopt;
}
代码分析:
- 创建 httplib 客户端,绑定提前保存的
_baseUrl;设置连接超时、读取超时,防止网络卡死; - Gemini 官方接口在国内无法直连,所以这里增加了强制代理校验 :读取环境变量
MY_APP_PROXY; - 解析代理字符串,格式要求
host:port,找不到冒号判定格式错误;解析成功后给 httplib 客户端设置代理; - 重点区别于 DeepSeek 可以直连,不需要强制代理;Gemini 这里没有代理直接报错,是该 Provider 独有的业务约束。
5.7 设置请求头
cpp
// 5.3 构建请求头
httplib::Headers headers = {
{"Authorization", "Bearer " + _apikey}
};
代码分析: 构造鉴权请求头,使用 Bearer 鉴权模式,和 OpenAI 体系保持一致,适配兼容 OpenAI 格式的 Gemini 网关。
如果使用 Gemini 原生官方接口,鉴权头为 x-goog-api-key: {apikey},报文结构、字段命名和 OpenAI 体系差异很大,这就是两种接入方式的核心差异点。
配套原生接口 curl 示例:
bash
curl -X POST https://generativelanguage.googleapis.com/v1beta/interactions \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-flash-latest",
"input": "Hello, how are you?"
}'
5.8 发送 POST 请求,校验返回状态
cpp
// 6. 向指定服务器发送POST请求
auto resp = client.Post(path: "/v1beta/openai/chat/completions", headers, body: requestBodyStr, content_type: "application/json");
if (!resp)
{
ERR("GeminiProvider sendMessage failed, error: {}", httplib::to_string(resp.error()));
return std::nullopt;
}
if (resp->status != 200)
{
ERR("GeminiProvider sendMessage Post request failed, status: {}, body: {}", resp->status, resp->body);
return std::nullopt;
}
INFO("GeminiProvider sendMessage Post request success, status: {}, body: {}", resp->status, resp->body);
代码分析:
- 调用 Post 发送 JSON 报文,目标路径是兼容 OpenAI 的 /v1beta/openai/chat/completions;
- 第一层判断:
!resp代表网络层面失败(连接失败、超时、ssl 错误),打印 httplib 内部错误; - 第二层判断:status 不等于 200,代表服务端返回业务错误(密钥错误、参数非法、额度耗尽),打印响应体方便定位;
- 200 成功后打印完整响应报文日志,用于调试。
5.9 解析响应体
cpp
// 7. 解析响应体 -- 反序列化
Json::CharReaderBuilder readerbuilder;
Json::Value responseBody;
std::stringstream responseStream(str::resp->body);
std::string errorMsg;
if (!Json::parseFromStream(readerbuilder, &responseStream, &responseBody, &errorMsg))
{
ERR("GeminiProvider sendMessage parse responseBody failed, err: {}", errorMsg);
return std::nullopt;
}
代码分析: 使用 JsonCpp 的CharReaderBuilder流式解析返回的 JSON 字符串;解析失败时拿到错误信息返回空,防止非法 JSON 导致程序异常。
5.10 提取返回的响应内容
cpp
// 8. 从responseBody中提取响应内容
// 8.1 先检查 choices 是否存在且是数组且不为空 -- 保证我们能取到有数据的choices字段
if (responseBody.isMember("choices") && responseBody["choices"].isArray() && !responseBody["choices"].empty())
{
Json::Value choice = responseBody["choices"][0];
// 8.2 检查choices数组中是否存在message字段且message字段中是否存在content
if (choice.isMember("message") && choice["message"].isObject() &&
choice["message"].isMember("content") && choice["message"]["content"].isString())
{
std::string generatedText = choice["message"]["content"].asString();
INFO("GeminiProvider sendMessage generatedText: {}", generatedText);
return generatedText;
}
else
{
ERR("GeminiProvider: message or content field missing in response.");
return std::nullopt;
}
}
else
{
ERR("GeminiProvider: choices array is empty or not found in response.");
return std::nullopt;
}
**代码分析:**按照 OpenAI 兼容格式的返回结构逐层校验:
- 判断顶层是否存在
choices、是数组、数组不为空; - 取第一个 choice 元素,再校验
message对象、content字符串字段; - 全部校验通过,取出 content 文本,返回给上层调用;任意一层字段缺失,打印对应错误日志,返回
std::nullopt。

多层防御性判断,避免响应结构和预期不一致时,JsonCpp 访问字段抛出异常、造成程序崩溃。
六、GTest 全量响应代码测试(TestLLM.cpp)
cpp
#include "../SDK/include/ChatGPTProvider.h"
#include "../SDK/include/DeepSeekProvider.h"
#include "../SDK/include/GeminiProvider.h"
#include "../SDK/include/OllamaLLMProvider.h"
#include <gtest/gtest.h>
#include <spdlog/common.h>
using namespace ai_chat_sdk;
TEST(GeminiProviderTest, sendMessage)
{
// 1、创建GeminiProvider对象
std::shared_ptr<LLMProvider> geminiProvider = std::make_shared<GeminiProvider>();
// 1.1 检查GeminiProvider是否初始化成功
ASSERT_TRUE(geminiProvider != nullptr);
// 2、构建模型配置
std::unordered_map<std::string, std::string> modelConfig;
modelConfig["api_key"] = getenv("gemini_apikey") ? getenv("gemini_apikey") : "";
modelConfig["base_url"] = "https://generativelanguage.googleapis.com";
// 3、初始化GeminiProvider
geminiProvider->initModel(modelConfig);
// 3.1 检查模型是否有效
ASSERT_TRUE(geminiProvider->IsAvailable());
// 4、构建请求参数配置(不写则使用默认值)
std::unordered_map<std::string, std::string> requestConfig;
requestConfig["model"] = "gemini-3.6-flash";
requestConfig["temperature"] = "0.7";
requestConfig["max_tokens"] = "2048";
// 5、构建消息列表
std::vector<Message> messages;
messages.push_back({"user", "你好, 请简单介绍一下你自己"});
// 6、调用sendMessage发送消息
auto response = geminiProvider->sendMessage(messages, requestConfig);
// 6.1 检查响应是否为空
ASSERT_TRUE(response.has_value());
INFO("response: {}", response.value());
}
int main(int argc, char **argv)
{
// 初始化Google Test框架
testing::InitGoogleTest(&argc, argv);
// 初始化日志系统
Logger::logger_init("testLLM", "stdout", spdlog::level::info);
// 运行所有测试用例
return RUN_ALL_TESTS();
}
6.1 CMakeLists.txt
bash
# 设置Cmake最低版本要求
cmake_minimum_required(VERSION 3.10)
# 项目名称
project(testLLM)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) # 要求必须使用C++17标准
# 设置构建类型为Debug
set(CMAKE_BUILD_TYPE Debug)
# 添加可执行文件
add_executable(testLLM TestLLM.cpp
../SDK/src/DeepSeekProvider.cpp
../SDK/src/util/MyLog.cpp
../SDK/src/ChatGPTProvider.cpp
../SDK/src/GeminiProvider.cpp)
#设置生成可执行文件的输出目录
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
# ${CMAKE_BINARY_DIR} 表示生成可执行文件的所在目录,即cmake指令执行所在目录
# 添加头文件搜索路径
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../SDK/include)
# ${CMAKE_CURRENT_SOURCE_DIR} 表示当前CMakeLists.txt所在目录
# ${CMAKE_SOURCE_DIR} 表示项目根目录,即所有CMakeLists.txt所在路径中最外层目录
# 查找第三方库:确保在使用前已经通过find_package命令找到并配置了这些库
find_package(GTest REQUIRED)
find_package(fmt REQUIRED)
find_package(spdlog REQUIRED)
find_package(jsoncpp REQUIRED)
find_package(OpenSSL REQUIRED)
# 链接第三方库
target_link_libraries(testLLM gtest fmt spdlog jsoncpp pthread OpenSSL::SSL OpenSSL::Crypto)
在 CMakeLists.txt 文件当中,只需要将 GeminiProvider 源文件 的所在位置 添加到add_executable当中即可。
测试结果展示:

全量响应接口完整代码
cpp
// 发送消息:全量调用,返回完整响应
// requestParams:针对Gemini模型本次请求专属参数,包含model、temperature、max_output_tokens等
std::optional<std::string> GeminiProvider::sendMessage(const std::vector<Message> &messages,
const std::unordered_map<std::string, std::string> &requestParams)
{
// 1. 检查模型是否有效
if (!IsAvailable())
{
ERR("GeminiProvider sendMessage model not available");
return std::nullopt;
}
// 2、构造并获取请求参数
// 2.1 构造默认请求参数:如果requestParams中没有指定temperature、max_tokens,则使用默认值
double temperature = 0.7;
int max_tokens = 2048;
std::string model;
// 2.2 从requestParams中获取temperature、max_tokens参数:如果requestParams中指定了,则使用指定值
auto it = requestParams.find("temperature");
if (it != requestParams.end())
{
temperature = std::stod(it->second);
}
it = requestParams.find("max_tokens");
if (it != requestParams.end())
{
max_tokens = std::stoi(it->second);
}
it = requestParams.find("model");
if (it != requestParams.end())
{
model = it->second;
}
else
{
ERR("model name not found in requestParams, must config it");
return std::nullopt;
}
// 2.3 构造并获取历史消息:获取请求体中的 messages 这个Json数组
Json::Value messageArray(Json::arrayValue);
for (auto &message : messages)
{
Json::Value messageObj;
messageObj["role"] = message._role;
messageObj["content"] = message._content;
messageArray.append(messageObj);
}
// 3、构造请求体
// 和历史消息构造一样,请求体的字段名称必须与gemini模型的接口文档中要求的一致
Json::Value requestBody;
requestBody["messages"] = messageArray;
requestBody["temperature"] = temperature;
requestBody["max_tokens"] = max_tokens;
requestBody["model"] = model;
// 4、对requestBody进行序列化
Json::StreamWriterBuilder writerbuilder;
std::string requestBodyStr = Json::writeString(writerbuilder, requestBody);
INFO("GeminiProvider sendMessage requestBody: {}", requestBodyStr);
// 5、使用cpp-httplib构建HTTP客户端
httplib::Client client(_baseUrl);
// 5.1设置超时时间
client.set_read_timeout(60, 0); // 读取超时时间60秒
client.set_connection_timeout(20, 0); // 连接超时时间20秒
// 5.2 关键点:从环境变量中获取代理配置
char *proxy_env = std::getenv("MY_APP_PROXY");
if (proxy_env != nullptr)
{
std::string proxy_str(proxy_env);
auto pos = proxy_str.find(":");
if (pos == std::string::npos)
{
ERR("GeminiProvider sendMessage proxy_env format error, must be host:port");
return std::nullopt;
}
else
{
std::string proxy_host = proxy_str.substr(0, pos);
int proxy_port = std::stoi(proxy_str.substr(pos + 1));
client.set_proxy(proxy_host, proxy_port);
}
}
else
{
// 针对Gemini模型的特殊情况:如果用户没有配置代理环境变量,那么请求无法访问到Gemini模型的服务器
ERR("GeminiProvider sendMessage proxy_env not found, must config it: MY_APP_PROXY=host:port");
return std::nullopt;
}
// 5.3 构建请求头
httplib::Headers headers = {
{"Authorization", "Bearer " + _apikey}};
// 6、向指定服务器发送POST请求
auto resp = client.Post("/v1beta/openai/chat/completions", headers, requestBodyStr, "application/json");
if (!resp)
{
ERR("GeminiProvider sendMessage failed, error: {}", httplib::to_string(resp.error()));
return std::nullopt;
}
if (resp->status != 200)
{
ERR("GeminiProvider sendMessage Post request failed, status: {}, body: {}", resp->status, resp->body);
return std::nullopt;
}
INFO("GeminiProvider sendMessage Post request success, status: {}, body: {}", resp->status, resp->body);
// 7、解析响应体 -- 反序列化
Json::CharReaderBuilder readerbuilder;
Json::Value responseBody;
std::stringstream responceStream(resp->body);
std::string errorMsg;
if (!Json::parseFromStream(readerbuilder, responceStream, &responseBody, &errorMsg))
{
ERR("GeminiProvider sendMessage parse responceBody failed, err: {}", errorMsg);
return std::nullopt;
}
// 8、从responceBody中提取响应内容
// 8.1 先检查 choices 是否存在且是数组且不为空 -- 保证我们能获取到有数据的choices字段
if (responseBody.isMember("choices") && responseBody["choices"].isArray() && !responseBody["choices"].empty())
{
Json::Value choice = responseBody["choices"][0];
// 8.2 检查choices数组中是否存在message字段且message字段中是否存在content
if (choice.isMember("message") && choice["message"].isObject() &&
choice["message"].isMember("content") && choice["message"]["content"].isString())
{
std::string generatedText = choice["message"]["content"].asString();
INFO("GeminiProvider sendMessage generatedText: {}", generatedText);
return generatedText;
}
else
{
ERR("GeminiProvider: message or content field missing in response.");
return std::nullopt;
}
}
else
{
ERR("GeminiProvider: choices array is empty or not found in response.");
return std::nullopt;
}
}
结束语
到这里,Gemini 的全量响应接入开发与验证工作就全部完成了。我们先是区分了 Gemini 原生 API 和 OpenAI 兼容 API 两套体系,敲定了兼容网关的接入方案,依靠 Apifox 提前调通接口、确认报文规范;接着一步步完成 GeminiProvider 的类声明、基础接口实现,落地了全量
sendMessage接口,处理了代理、鉴权这类和前面模型不一样的细节,最后通过 GTest 测试用例验证了整条调用链路可以正常工作。对比之前 DeepSeek、ChatGPT 的接入过程,也能直观感受到我们抽象基类 LLMProvider 的价值:新增一个模型厂商,只需要实现对应的子类,上层调用逻辑几乎不用改动,框架的扩展性得到了很好的体现。