云计算百科
云计算领域专业知识百科平台

【AI大模型接入SDK】 —— Gemini接入封装

🌈欢迎来到实战项目专栏 ~~ 从零实现AI大模型接入SDK

  • 🌍博客主页:张小姐的猫~江湖背景
  • 🔥所属专栏:C++项目 ~ AI大模型接入SDK
  • 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏 在这里插入图片描述

请添加图片描述

AI大模型接入SDK

  • 🌈欢迎来到实战项目专栏 ~~ 从零实现AI大模型接入SDK
  • 🌍 Gemini初始化 — 头文件
  • 🌍 Gemini的API介绍
    • 🔥 API测试
  • 🌍发送消息 – 全量返回
  • 🌍发送消息 – 全量返回测试
  • 🍀发送消息 – 流式返回
  • 🍀发送消息 – 流式返回测试
  • 📢写在最后

请添加图片描述

🌍 Gemini初始化 — 头文件

ChatgptProvider .h的实现

同样的去继承LLMProvider的实现方法即可

#pragma once
#include "LLMProvider.h"

namespace ai_chat_sdk{
class GeminiProvider : public LLMProvider{
public:
//初始化模型
bool initModel(const std::map<std::string, std::string>& config) override;
//检查模型是否可用
bool isAvailable() const override;
//获取模型名称
std::string getModelName() const override;
//获取模型描述
std::string getModelDesc() const override;
//发送消息 —— 全量返回
std::string sendMessage(const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam) override;
//发送消息 —— 增量返回, 流式返回
std::string sendMessageStream(const std::vector<Message>& messages,
const std::map<std::string, std::string>& requestParam,
std::function<void(const std::string&, bool)> callback) override;
};
}

ChatgptProvider .cpp的实现

此处主要是进行设置模型apikey、baseURL、模型名称 与模型描述等

#include "../include/util/GeminiProvider.h"
#include "../include/util/myLog.h"
#include "jsoncpp/json/json.h"
#include "../include/util/httplib.h"
#include <sstream>

namespace ai_chat_sdk{
//初始化模型
bool GeminiProvider::initModel(const std::map<std::string, std::string>& config){
//初始化API Key
auto it = config.find("_apikey");
if(it == config.end()){
ERR("GeminiProvider initModel: _apikey not found in config");
return false;
}else{
_apikey = it->second;
}

//初始化Base URL
it = config.find("_endpoint");
if(it == config.end()){
ERR("GeminiProvider initModel: _baseurl not found in config");
return false;
}else{
_endpoint = it->second;
}

_is_available = true;
INFO("GeminiProvider initModel success, endpoint: {}", _endpoint.c_str());
return true;
}

//检测模型是否可用
bool GeminiProvider::isAvailable() const{
return _is_available;
}
//获取模型名称
std::string GeminiProvider::getModelName() const{
return "gemini-3.5-flash";
}
//获取模型描述
std::string GeminiProvider::getModelDesc() const{
return "Gemini 3.5 Flash 是新一代 Gemini 模型,定位是:接近旗舰模型的推理/编程能力 + Flash 系列的高速度和较低成本";
}
}

🌍 Gemini的API介绍

对应的API文档:传送门 Gemini模型也兼容OPenAI,即可以采用类似OpenAI的格式访问gemini模型 BaseURL:https://generativelanguage.googleapis.com

请求URL: POST /v1beta/openai/chat/completions

在这里插入图片描述

响应参数:

在这里插入图片描述

与DeepSeek类似,Gemini也不会保存历史会话记录,因此在给Gemini发送请求时,需要提供之前聊天的上下文记录,Gemini才会根据上下文记录提供对应的响应

🔥 API测试

接下来进行API的测试

在这里插入图片描述

测试api成功!

在这里插入图片描述

🌍发送消息 – 全量返回

接入Gemini系列模型时,Google提供了专门的api接口,同时也兼容OpenAIapi。为了减少复杂的实现快速接⼊,本文使用OpenAI兼容的API快速接入

URL: /v1/chat/completions 请求参数:

在这里插入图片描述

响应格式: 在这里插入图片描述

全量返回的步骤:

  • 检测模型是否可用
  • 构造请求参数:模型名称、消息列表、温度值、最大token数 — Json
  • 构造历史信息数组 – 把过往的回复都存进一个数组
  • 对Json对象进行序列化
  • 创建HTTP客户端,设置请求头:content-type,认证方式
  • 给模型发送请求,等待模型回复
  • 解析模型的响应结果 —— 反序列化:按照返回的json格式解析
  • 返回模型发送消息的内容

在这里插入图片描述

//发送消息 —— 全量返回
std::string GeminiProvider::sendMessage(const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam)
{
//1.检测模型是否可用
if(!_is_available){
ERR("GeminiProvider sendMessage: model is not available");
return "";
}

//2.构建请求参数:温度 与 max_tokens
double temperature = 0.7;
int max_tokens = 2048;
if(requestParam.find("temperature") != requestParam.end()){
temperature = std::stod(requestParam.at("temperature"));
}
if(requestParam.find("max_tokens") != requestParam.end()){
max_tokens = std::stoi(requestParam.at("max_tokens"));
}

//3.构造历史信息数组
Json::Value messageArray(Json::arrayValue); //历史信息数组
for(const auto& msg : messages){
Json::Value message;
message["role"] = msg._role;
message["content"] = msg._content;
messageArray.append(message);
}

//4.构造请求体
Json::Value requestBody;
requestBody["model"] = getModelName();
requestBody["messages"] = messageArray; //历史信息数组
requestBody["temperature"] = temperature; //温度参数
requestBody["max_tokens"] = max_tokens; //最大输出token数

//5.对构造好的请求体进行序列化
Json::StreamWriterBuilder writerBuilder;
writerBuilder["indentation"] = "";
std::string requestBodyStr = Json::writeString(writerBuilder, requestBody);

//6.创建客户端
httplib::Client client(_endpoint);
client.set_connection_timeout(30, 0); // 30秒连接超时
client.set_read_timeout(60, 0); // 60秒读取超时
//无需代理,使用的是中转站

//7.设置请求头参数
httplib::Headers headers = {
{"Authorization", "Bearer " + _apikey}
};

//8.发送POST请求
httplib::Result result = client.Post("/v1/chat/completions", headers, requestBodyStr, "application/json");
if(!result) // 请求失败
{
ERR("GeminiProvider sendMessage: request failed, status code: {}", to_string(result.error()));
return "";
}

//模型响应不成功
if(result->status != 200)
{
ERR("GeminiProvider sendMessage: request failed, status code: {}", result->status);
return "";
}

INFO("GeminiProvider sendMessage: HTTP响应成功, status: {}", result->status);
INFO("GeminiProvider sendMessage: HTTP响应体: {}", result->body);

//9.反序列化 模型返回的响应结果
Json::CharReaderBuilder readerBuilder;
Json::Value responseBody;
std::string errors;
std::istringstream iss(result->body);
if(!Json::parseFromStream(readerBuilder, iss, &responseBody, &errors))
{
ERR("GeminiProvider sendMessage: parse response body failed, errors: {}", errors);
return "";
}

//提取出模型的响应内容
if(responseBody.isMember("choices") && responseBody["choices"].isArray() && responseBody["choices"].size() > 0)
{
Json::Value choices = responseBody["choices"][0];
if(choices.isMember("message") && choices["message"].isMember("content") && choices["message"]["content"].isString())
{
std::string reply = choices["message"]["content"].asString();
INFO("GeminiProvider sendMessage: model reply: {}", reply);
return reply;
}
}

//响应内容结构有问题
ERR("GeminiProvider sendMessage: response body structure is wrong");
return "";
}

🌍发送消息 – 全量返回测试

提前把Gemini的API导入到环境变量中

#include <gtest/gtest.h>
#include "../SDK/include/util/DeepSeekProvider.h"
#include "../SDK/include/util/ChatgptProvider.h"
#include "../SDK/include/util/GeminiProvider.h"
#include <iostream>
#include <cstdlib>
#include "../SDK/include/util/myLog.h"

//Gemini — 全量返回测试
TEST(GeminiProvider, sendMessage)
{
//实例化GeminiProvider的对象
auto provider = std::make_shared<ai_chat_sdk::GeminiProvider>();
ASSERT_TRUE(provider != nullptr); //不报错就是成功

std::map<std::string, std::string> modelParams;
modelParams["_apikey"] = std::getenv("GEMINI_API_KEY");
modelParams["_endpoint"] = "https://modelflare.dev";

//初始化对象
provider->initModel(modelParams);
ASSERT_TRUE(provider->isAvailable()); //

//因为调用sendMessage方法需要传入参数:requestParam、message
const std::map<std::string, std::string> requestParam = {
{"temperature", "0.7"},
{"max_tokens", "2048"}
};
//调用sendMessage方法,发送消息
std::vector<ai_chat_sdk::Message> messages;
messages.push_back(ai_chat_sdk::Message("user", "你是谁?"));

//全量返回测试
std::string fulldata = provider->sendMessage(messages, requestParam)

ASSERT_FALSE(fulldata.empty()); //期待响应是false,如果fulldata是有数据的,没问题;反之数据为空会导致测试失败,报错
INFO("response: {}", fulldata);
}

int main(int argc, char** argv)
{
//初始化日志库 —— 因为DeepSeekProvider.cpp 中使用了日志库
bite::Logger::initLogger("testLLM", "stdout", spdlog::level::level_enum::debug);

//初始化Google Test
testing::InitGoogleTest(&argc, argv);

//运行所有测试用例
return RUN_ALL_TESTS();
}

对应的CMakelist文件添加上对应的GeminiProvider.cpp

#设置Cmake最低版本号
cmake_minimum_required(VERSION 3.10)

#设置项目名称
project(testLLM)

#设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

#设置构建类型Debug —— 按照debug类型进行编译
set(CMAKE_BUILD_TYPE Debug)

#添加可执行文件
add_executable(testLLM testLLM.cpp
../SDK/src/util/myLog.cpp
../SDK/src/util/ChatgptProvider.cpp
../SDK/src/util/GeminiProvider.cpp
../SDK/src/util/DeepSeekProvider.cpp)

#设置输出目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build)

#设置头文件的搜索路径
include_directories(${PROJECT_SOURCE_DIR}/../SDK/include)

#查找OpenSSL库
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})

#启用httplib的HTTPS支持
target_compile_definitions(testLLM PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)

#设置链接库的搜索路径
target_link_libraries(testLLM jsoncpp spdlog gtest fmt OpenSSL::SSL OpenSSL::Crypto)

在这里插入图片描述

🍀发送消息 – 流式返回

URL:/v1/chat/completions

在这里插入图片描述

流式返回的响应格式如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

//发送消息 —— 流式返回
std::string GeminiProvider::sendMessageStream(const std::vector<Message>& messages,
const std::map<std::string, std::string>& requestParam,
std::function<void(const std::string&, bool)> callback)
{
//1.检测模型是否可用
if(!_is_available)
{
ERR("GeminiProvider sendMessageStream: model is not available");
return "";
}

//2.构建请求参数:温度 与 max_tokens
double temperature = 0.7;
int max_tokens = 2048;
if(requestParam.find("temperature") != requestParam.end()){
temperature = std::stod(requestParam.at("temperature"));
}
if(requestParam.find("max_tokens") != requestParam.end()){
max_tokens = std::stoi(requestParam.at("max_tokens"));
}

//3.构造历史信息数组
Json::Value messageArray(Json::arrayValue); //历史信息数组
for(const auto& msg : messages){
Json::Value message;
message["role"] = msg._role;
message["content"] = msg._content;
messageArray.append(message);
}

//4.构造请求体
Json::Value requestBody;
requestBody["model"] = getModelName();
requestBody["messages"] = messageArray; //历史信息数组
requestBody["temperature"] = temperature; //温度参数
requestBody["max_tokens"] = max_tokens; //最大输出token数
requestBody["stream"] = true; //打开流式返回

//5.序列化请求体
Json::StreamWriterBuilder writerBuilder;
writerBuilder["indentation"] = "";
std::string requestBodyStr = Json::writeString(writerBuilder, requestBody);

//6.创建客户端,我们链接的是中转站,不需要开代理
httplib::Client client(_endpoint);
client.set_connection_timeout(60, 0); // 60秒连接超时
client.set_read_timeout(300, 0); // 300秒读取超时

//7.设置请求头参数
httplib::Headers headers = {
{"content-type", "application/json"},
{"Authorization", "Bearer " + _apikey},
{"Accept", "text/event-stream"} //接受流式返回
};

//流式处理的变量
std::string buffer; //接收流式响应的数据块
bool gotError = false; //标记响应是否成功
std::string errMsg; //错误描述信息
int statusCode = 0; //响应状态码
bool streamFinished = false; //标记流式响应是否结束
std::string fullResponse; //最终累计完整的响应内容

//创建请求对象 与全量返回方式不一样
httplib::Request request; //请求对象
request.method = "POST"; //请求方法
request.path = "/v1/responses"; //请求路径
request.headers = headers; //请求头
request.body = requestBodyStr;

//客户端提前准备好响应处理器 —— 检测响应码
request.response_handler = [&](const httplib::Response& req){ //响应处理器
statusCode = req.status;
if(statusCode != 200){
gotError = true;
errMsg = "HTTP status code: " + std::to_string(statusCode);
return false; // 中止请求
}
return true;
};

//设置内容处理器
request.content_receiver = [&](const char* data, size_t datalen, size_t offset, size_t totallength)->bool{
if(gotError)
return false;

buffer.append(data, datalen); //不断叠加数据块
INFO("GeminiProvider sendMessageStream: received data:{} ", buffer);

//处理所有的增量数据,数据之间以\\n\\n分隔
size_t pos = 0;
while((pos = buffer.find("\\n\\n")) != std::string::npos)
{
std::string chunk = buffer.substr(0, pos);
buffer.erase(0, pos + 2);

if(chunk.empty() || chunk[0] != ':') //忽略空 chunk 或者以:(注释)开头的chunk
continue;
//从此处就是对一个chunk的有效数据进行处理

}
};
}

在这里插入图片描述

此时我们拿到的数据是一个字节流!,首先比较前6个字符是否符合 "data: "

在这里插入图片描述

拿到数据块中的有效数据进行拼接即可

//从此处就是对一个chunk的有效数据进行处理
if(chunk.compare(0, 6, "data: ") == 0)
{
std::string modelData = chunk.substr(6); //从6位置开始截取到末位

if(modelData == "[DONE]")
{
streamFinished = true;
return true;
}

//modelData是收到模型返回的JSON字符串 —— 要进行反序列化
Json::Value modelDataJson;
Json::CharReaderBuilder readerBuilder;
std::string errors;
std::istringstream iss(modelData);
if(!Json::parseFromStream(readerBuilder, iss, &modelDataJson, &errors))
{
ERR("GeminiProvider sendMessageStream: parse model data failed, errors: {}", errors);
return false;
}

//提取出模型的响应内容
if(modelDataJson.isMember("choices") &&
modelDataJson["choices"].isArray() &&
modelDataJson["choices"].size() > 0)
{
Json::Value choices = modelDataJson["choices"][0];
if(choices.isMember("delta") && choices["delta"].isMember("content") && choices["delta"]["content"].isString())
{
std::string content = choices["delta"]["content"].asString();
fullResponse += content;
callback(content, false); //每收到一个增量文本片段就 callback 一次
}
}
else
{
//单次的JSON解析失败了
WARN("GeminiProvider sendMessageStream parse JSON failed: {}", errors);
}
}

总体的处理流程是:先根据 \\n\\n 来划分出单个的增量数据块(字节流),接着对比增量数据块的前6个字符是否为"data: ",是就对其数据进行反序列化!

🍀发送消息 – 流式返回测试

//Gemini — 全量返回测试
TEST(GeminiProvider, sendMessage)
{
//实例化GeminiProvider的对象
auto provider = std::make_shared<ai_chat_sdk::GeminiProvider>();
ASSERT_TRUE(provider != nullptr); //不报错就是成功

std::map<std::string, std::string> modelParams;
modelParams["_apikey"] = std::getenv("GEMINI_API_KEY");
modelParams["_endpoint"] = "https://modelflare.dev";

//初始化对象
provider->initModel(modelParams);
ASSERT_TRUE(provider->isAvailable()); //

//因为调用sendMessage方法需要传入参数:requestParam、message
const std::map<std::string, std::string> requestParam = {
{"temperature", "0.7"},
{"max_tokens", "2048"}
};
//调用sendMessage方法,发送消息
std::vector<ai_chat_sdk::Message> messages;
messages.push_back(ai_chat_sdk::Message("user", "你是谁?"));

//流式响应
auto writechunk = [&](const std::string& chunk, bool last) {
INFO("chunk: {}", chunk);
if (last) {
INFO("[Done]");
}
};
std::string fulldata = provider->sendMessageStream(messages, requestParam, writechunk);

ASSERT_FALSE(fulldata.empty()); //期待响应是false,如果fulldata是有数据的,没问题;反之数据为空会导致测试失败,报错
INFO("response: {}", fulldata);
}

int main(int argc, char** argv)
{
//初始化日志库 —— 因为DeepSeekProvider.cpp 中使用了日志库
bite::Logger::initLogger("testLLM", "stdout", spdlog::level::level_enum::debug);

//初始化Google Test
testing::InitGoogleTest(&argc, argv);

//运行所有测试用例
return RUN_ALL_TESTS();
}

在这里插入图片描述

至此三种模型的API接入都实现完成并且测试成功!

📢写在最后

接下来登场的是 Ollama本地接入大模型

请添加图片描述

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【AI大模型接入SDK】 —— Gemini接入封装
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!