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

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

AI大模型接入SDK

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

🌍 Gemini初始化 --- 头文件

ChatgptProvider .h的实现

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

cpp 复制代码
#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的实现

此处主要是进行设置模型apikeybaseURL模型名称模型描述

cpp 复制代码
#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模型

BaseURLhttps://generativelanguage.googleapis.com

请求URLPOST /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格式解析
  • 返回模型发送消息的内容
cpp 复制代码
//发送消息 ------ 全量返回
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导入到环境变量中

cpp 复制代码
#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

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

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

cpp 复制代码
//发送消息 ------ 流式返回
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: "

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

cpp 复制代码
//从此处就是对一个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: ",是就对其数据进行反序列化!

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

cpp 复制代码
//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本地接入大模型

相关推荐
时空节拍AI数字人1 小时前
AI 数字人为什么需要“3D”?2D 不够用吗
人工智能·网络协议·tcp/ip·3d·信息可视化
米小虾1 小时前
4-bit 量化"几乎无损"?把它放进 Agent 循环里再试一次
人工智能·agent
陈皮糖..1 小时前
从零搭建一个简易 AI 运维问答机器人(RAG + LangChain + Streamlit)
运维·人工智能·ai·langchain·机器人
Thomas21431 小时前
mysql 索引面试复习
数据库·mysql·面试
Elastic 中国社区官方博客1 小时前
Elasticsearch Python DSL 客户端开发
大数据·数据库·python·elasticsearch·搜索引擎·全文检索
jimmyleeee1 小时前
大模型安全之五:LLM输出安全
人工智能·安全
风哥2号1 小时前
数据库教程FGMT03‑生产环境Linux+Oracle19c+ASM安装配置与项目实战
linux·数据库
HIT_Weston2 小时前
214、【AI】【模型部署】阿里云 PAI:从开发到部署的一站式平台
人工智能·模型部署
一木 之林2 小时前
七、一-AI 工程实践、插件化调试与软件交付
java·linux·c++