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

请添加图片描述

请添加图片描述

GPT-6 Astra 是 OpenAI 最新一代的旗舰模型,定位是处理“复杂、需要连续完成很多步骤”的任务。

你可以把它理解成:

  • 🧠 更强的推理:适合复杂数学、科学分析、决策和研究。
  • 💻 更强的编程:不仅写代码,还能理解代码库、测试和排查问题。
  • 🖥️ 会操作电脑:可以使用浏览器、软件和各种工具完成实际任务,比如填表、整理资料、做网站测试等。
  • 📚 更适合长任务:可以把一个大目标拆成很多步骤,持续执行,而不是每一步都要你手把手告诉它。
  • 📄 办公能力更强:能够制作和处理文档、表格、演示文稿,并尽量遵循你给的模板和格式。
  • 🔐 安全能力也更强:同时它的网络安全能力已经非常强,因此 OpenAI 对它增加了更严格的安全监控。

🌍Chatgpt初始化 — 头文件

ChatgptProvider .h的实现

和deepseek的头文件差不多,把类名改成ChatgptProvider 就行

主要设置apikey以及OpenAI提供的应用编程接口的根端点(所有特定地址的起点和前缀,就像是某栋楼的地址本身,URL是根端点+路径+[查询参数])。apikey需要自己到OpenAI的官网申请,OpenAI提供的API根端点是:https://api.openai.com

这里我使用的是中转站的api,所以baseurl也会不太一样

#pragma once
#include <map>
#include <string>
#include <vector>
#include <functional>
#include "LLMProvider.h"

namespace ai_chat_sdk{
    class ChatgptProvider : 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的实现

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


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

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

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

    //检测模型是否可用
    bool ChatgptProvider::isAvailable() const{
        return _is_available;
    }
    //获取模型名称
    std::string ChatgptProvider::getModelName() const{
        return "gpt-5.6-sol";
    }
    //获取模型描述
    std::string ChatgptProvider::getModelDesc() const{
        return "gpt-5.6-sol是 OpenAI 最新一代的旗舰模型,定位是处理“复杂、需要连续完成很多步骤”的任务";
    }
    //发送消息 —— 全量返回
    std::string ChatgptProvider::sendMessage(const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam)
    {
        return "";
    }
    //发送消息 —— 流式返回
    std::string ChatgptProvider::sendMessageStream(const std::vector<Message>& messages, 
                                            const std::map<std::string, std::string>& requestParam, 
                                            std::function<void(const std::string&, bool)> callback)
    {
        return "";
    }
} //end ai_chat_sdk

🌏ChatGPT提供API

ChatCompletion的官方参数文档:https://platform.openai.com/docs/api-reference/chat/create官方提供的参数比较多,此处仅介绍部分重要参数,红色表示后续项目中需要使用字段,具体含义如下:

在这里插入图片描述

在这里插入图片描述

Baseurl:https://api.openai.com
模型名称gpt-5.6-sol
gpt-5.6-sol 模型的聊天补全接口设置如下:
请求URL

POST /v1/chat/completions 

请求头参数:

在这里插入图片描述

请求体参数:

在这里插入图片描述

返回响应 200 ok

🔥Responses API

对于新创建的项目,官方更推荐优先使用ResponsesAPI,以使用OpenAI平台的最新功能

ChatCompletionsAPI,是传统的聊天接口,简单好用,但只能处理文本
ResponsesAPI:以事件驱动,支持多模态和复杂交互,更适合做应用级AI

以下是两种不同类型API的对比:

对比项Chat Completions APIResponses API
输入聊天消息数组(messages: [{role, content}]统一的 input(可包含文本、音频、图像等),更灵活
输出一段完整的文本回复(可流式,但只有文字)通过事件流(semantic events) 输出:text.deltaaudio.deltacompletedtool_call
流式能力只支持文本流式(逐字符/逐 token 返回)支持多模态流式(文本、语音、工具调用、结构化消息),更细粒度
多模态支持主要是文本(部分模型可输入图像)原生支持文本、音频、图像等,能生成文字边输出语音
可控性一次请求 = 一次完整回复,过程中不可中断可在生成中动态打断、分支,或让模型调用工具函数
典型场景聊天机器人、FAQ、简单对话系统AI 助理、语音对话机器人、智能办公助手、多模态应用
生活示例学生在比特答疑工具上提问:

学生: 我的 oj 题整你通过这部分用例,为什么?

老师: 我先看下题目…我在看下你的代码…

你代码中比较条件写错了…

应该这么写…你先修改下试试…
你参加一个会议,智能助理会不断抛出“事件”:

•“正在讲 PPT 第 3 页了”(output_text
• “这里有个视频片段要放”(output_audio
• “我需要查一下资料”(tool_call
• “会议结束”(completed

因此现在官方是比较推荐在新项目中使用ResponsesAPI

🔥API测试

同样的方式在apifox上进行测试:设置请求体和请求头

在这里插入图片描述

在这里插入图片描述

测试返回的响应结构体是:

{
    "id": "resp_0dc4e3eaa553dd5b016a9ec40016c887d1a0a92b5a9634e255",
    "object": "response",
    "created_at": 1788789760,
    "status": "completed",
    "background": false,
    "completed_at": 1788789761,
    "error": null,
    "frequency_penalty": 0.0,
    "incomplete_details": null,
    "max_output_tokens": null,
    "max_tool_calls": null,
    "model": "gpt-5.6-sol",
    "moderation": null,
    "output": [
        {
            "id": "msg_0dc4e3eaa553dd5b016a9ec40135a487d1889ab21cf0786ae9",
            "type": "message",
            "status": "completed",
            "content": [
                {
                    "type": "output_text",
                    "annotations": [],
                    "logprobs": [],
                    "text": "我是 Codex,一个在你的工作区里协作的软件工程助手。我可以阅读和修改代码、运行命令和测试、排查问题、实现功能,并直接完成工程任务。"
                }
            ],
            "phase": "final_answer",
            "role": "assistant"
        }
    ],
    "parallel_tool_calls": true,
    ...

api测试没有问题,进行下一步

🌍发送消息 - 全量返回

在向大模型提问时,模型将回答文本一次性返回

URL/v1/chat/completions

在这里插入图片描述

全量返回的步骤:

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

这里与DS接入有区别的是

  • 最大tokens数名称变了 —— max_input_tokens
  • 消息列表字段变成了input
  • 设置HTTP客户端的时候,需要额外的设置一个代理set_proxy
  • 发送消息时POST的请求路径要变化
bool ChatgptProvider::initModel(const std::map<std::string, std::string>& config){
    //初始化API Key
    auto it = config.find("_apikey");
    if(it == config.end()){
        ERR("ChatgptProvider initModel: _apikey not found in config");
        return false;
    }else{
        _apikey = it->second;
    }

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

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

//检测模型是否可用
bool ChatgptProvider::isAvailable() const{
    return _is_available;
}
//获取模型名称
std::string ChatgptProvider::getModelName() const{
    return "gpt-5.6-sol";
}
//获取模型描述
std::string ChatgptProvider::getModelDesc() const{
    return "gpt-5.6-sol是 OpenAI 最新一代的旗舰模型,定位是处理“复杂、需要连续完成很多步骤”的任务";
}
//发送消息 —— 全量返回
std::string ChatgptProvider::sendMessage(const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam)
{
    //1.检测模型是否可用
    if(!_is_available){
        ERR("ChatgptProvider sendMessage: model is not available");
        return "";
    }
    //2.构建请求参数 
    double temperature = 0.7;
    int maxOutputParam = 2048;
    if(requestParam.find("max_input_tokens") != requestParam.end()){
        maxOutputParam = std::stoi(requestParam.at("max_tokens"));
    }
    if(requestParam.find("temperature") != requestParam.end()){
        temperature = std::stod(requestParam.at("temperature"));
    }

    //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"] = "codex";
    requestBody["input"] = messageArray;       //注意gpt这里是input
    requestBody["temperature"] = temperature;
    requestBody["max_output_tokens"] = maxOutputParam; 
    requestBody["stream"] = false;  //默认是false,非流式传递

    //5.序列化
    Json::StreamWriterBuilder writerBuilder;
    writerBuilder["indentation"] = "";
    std::string requestBodyStr = Json::writeString(writerBuilder, requestBody);  //工厂对象,要进行处理的Json对象

    //6.创建HTTP客户端
    httplib::Client client(_endpoint);
    client.set_connection_timeout(30);                    // 30秒连接超时
    client.set_read_timeout(60);                          // 60秒读取超时
    client.set_proxy("127.0.0.1", 7890);           // 设置代理

    //7.设置请求头参数
    httplib::Headers headers = {
        {"Authorization", "Bearer " + _apikey},    //认证令牌
        {"Content-Type", "application/json"}       //JSON格式
    };

    //8.发送POST请求
    httplib::Result response = client.Post("/v1/responses", headers, requestBodyStr, "application/json");

    if(!response){
        ERR("DeepSeekProvider sendMessage: HTTP请求失败");
        return "";
    }

    //检测是否响应成功
    if(response->status != 200)
    {
        ERR("ChatgptProvider sendMessage: HTTP响应失败, status: {}", response->status);
        return "";
    }

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

    //反序列化 模型返回的响应结果 
    Json::CharReaderBuilder readerBuilder;
    Json::Value responseBody;     //存放解析的结果
    std::string errors;
    std::istringstream responseStream(response->body); // 响应流,把响应体转换为字符串输入流
    if(!Json::parseFromStream(readerBuilder, responseStream, &responseBody, &errors))
        ERR("ChatgptProvider sendMessage: 解析HTTP响应结果失败");
        return "";
    }

💥重点

接下来变化比较大的点在:怎么把有效信息从返回的响应体中提取出来

在这里插入图片描述

解析的流程:

  • 检测responseBody是否包含"output"字段
  • 检测responseBody["output"] 是否为一个Json数组
  • 检测这个Json数组是否为空?
  • 如果上述的条件都成立,取出responseBody["output"][0] ~ 也是一个Json对象 Output
    • 继续检测 Output对象中是否包含"content"字段
    • 检测Output是否为Json数组
    • 检测Output是否为空?
    • 检测Output是否包含"text"字段即可
    • 如果上述都检测没问题,就能拿到返回的有效数据
string reply = output["content"][0]["text"].asString
//从响应体中提取出返回的文本内容 - responseBody
if(responseBody.isMember("output") && responseBody["output"].isArray() && responseBody["output"].size() > 0)
{
    //模型的回复刚好是output的第0个
    auto output = responseBody["output"][0];
    if(output.isMember("content") && output["content"].isArray() && output["content"].size() > 0 && output["content"][0].isMember("text"))
    {
        auto replyString = output["content"][0]["text"].asString();
        INFO("ChatgptProvider sendMessage: 模型回复: {}", replyString);
        return replyString;
    }
}
//10.处理模型返回结构异常问题
ERR("ChatgptProvider sendMessage parse failed, errors: {}", errors);

return "";

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

在之前的testLLM.cpp文件下多添加一个测试用例TEST(ChatgptProvider, sendMessage)

//Chatgpt测试
TEST(ChatgptProvider, sendMessage) 
{
    //实例化ChatgptProvider的对象
    auto provider = std::make_shared<ai_chat_sdk::ChatgptProvider>();
    ASSERT_TRUE(provider != nullptr); //不报错就是成功

    std::map<std::string, std::string> modelParams;
    modelParams["_apikey"] = std::getenv("CHATGPT_API_KEY");
    modelParams["_endpoint"] = "https://portdan.com";

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

    //因为调用sendMessage方法需要传入参数:requestParam、message
    const std::map<std::string, std::string> requestParam = {
        {"temperature", "0.7"}, 
        {"max_output_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,就是符合预期
    INFO("response: {}", fulldata);
}

首先执行下面的命令,看看是否和外网连接成功?

curl -I http://openai.com

在这里插入图片描述

正常来说:如果我们接入的是api访问的是我openai的官网,是需要在Linux环境下去设置代理的

  • 但是我们这里接入的是中转站的API,所以省略了配置环境这一步

在这里插入图片描述

🍀发送消息 - 流式返回

在发送 /v1/responses 如果在请求参数中带上 stream=true 时,表示开启流式响应。OpenAI预定义了一些事件类型,每个响应都有特定的事件类型:

在这里插入图片描述

一般数据格式是如下图:DeepSeek的事件类型event全是设置成message

在这里插入图片描述

ChatGPT的responses API的流式响应是基于事件机制的

  • response.create:表示响应对象创建好了
  • response.in_progress:表示模型开始工作
  • response.output_text.delta:表示一段段文字实时流出这个事件内容是模型返回的增量数据!
  • response.output_item.done :表示该输出块结束 — 一个输出单元结束,但output_item中,在流式过程中可能会包含多个delta字段(多个增量数据的内容)
  • response.completed:表示整个响应结束,流式响应过程全部正式结束

可以把事件想象成大厨在做菜:delta就是对应的一道道菜!所有的菜都上完了之后输出item.done:表示菜已经上齐了,上齐之后服务员会跟顾客说菜已经上齐对应response.completed

与DeepSeek的流式返回的重复部分如下:

//发送消息 —— 流式返回
std::string ChatgptProvider::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("ChatgptProvider sendMessageStream: model is not available");
        return "";
    }

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

    //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"] = "gpt-5.6-sol";
    requestBody["input"] = messageArray;
    requestBody["temperature"] = temperature;
    requestBody["max_output_tokens"] = maxOutputParam;
    requestBody["stream"] = true;

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

    //6.创建HTTP客户端
    httplib::Client client(_endpoint);
    client.set_connection_timeout(60, 0);          // 30秒连接超时
    client.set_read_timeout(300, 0);       
    //此处不使用代理 —— 我们是中转站

    //7.设置请求头
    httplib::Headers headers = {
        {"Authorization", "Bearer " + _apikey},
        {"Content-Type", "application/json"},      //此处要加上内容类型, 因为是用send方式发送, 不再是POST中带上application/json
        {"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{  //数据接收处理器
        //提前检测响应头是否出错,如果http出错,没有必要继续处理数据
        if(gotError){
            return false;
        }
        buffer.append(data, datalen);

        INFO("ChatgptProvider sendMessageStream receive data:{}", buffer);

        //处理所有的流式响应数据块,注意数据块之间是\n\n分隔的
        size_t pos = 0;
        while(pos = buffer.find("\n\n") != std::string::npos) //找到数据块的结束符
        {
            std::string event = buffer.substr(0, pos);
            buffer.erase(0, pos + 2);  //删除已处理的数据块,变成一个新buffer

			//后面补充
        }
        return true; //继续接收后续数据块
    };

    //发送请求
    auto result = client.send(request);
    if(!result)
    {
        ERR("ChatgptProvider sendMessageStream: request failed, error: {}", to_string(result.error()));
        return "";
    }

    //确保流式操作正确结束 —— 防止出现收不到complete的情况
    if(!streamFinished)
    {
        WARN("stream ended without response.completed");
        callback("", true);   //告诉用户,事件已经结束
        return "";
    }

    return "";
}

此时这个版本还没有对事件的类型做具体的解析! 也就是只拿到 了data数据,但是数据内部是什么样的?怎么样使用这些数据还没进行处理

我们这里先简单的进行一次测试,看看测试的结果如何!

[19:55:20] [testLLM][info   ] [/home/lbw/SDK/ai-model/AIModelAcessTech/SDK/src/util/ChatgptProvider.cpp:232 ]ChatgptProvider sendMessageStream receive data:event: response.created
data: {"type":"response.created","response":{"id":"resp_0c6578bda96fd936016a9ff7a24ea087d2849f5ed30b101393","object":"response","created_at":1788868519,"model":"gpt-5.6-sol","status":"in_progress","output":[],"service_tier":"auto"}}

event: response.output_item.added
data: {"item":{"content":[{"text":"","type":"output_text"}],"id":"item_16e6790121a73833f9c18d48","role":"assistant","status":"in_progress","type":"message"},"output_index":0,"sequence_number":1,"type":"response.output_item.added"}

event: response.content_part.added
data: {"content_index":0,"item_id":"item_16e6790121a73833f9c18d48","output_index":0,"part":{"annotations":[],"logprobs":[],"text":"","type":"output_text"},"sequence_number":2,"type":"response.content_part.added"}

event: response.output_text.delta
data: {"content_index":0,"delta":"我是","item_id":"item_16e6790121a73833f9c18d48","output_index":0,"sequence_number":3,"type":"response.output_text.delta"}

event: response.output_item.done
data: {"item":{"content":[{"text":"我是 Codex,一个基于 GPT-5 的编程助手。我可以协助你阅读、修改和测试代码,也能分析项目结构、定位问题并实现功能。","type":"output_text"}],"id":"item_16e6790121a73833f9c18d48","role":"assistant","status":"completed","type":"message"},"output_index":0,"sequence_number":41,"type":"response.output_item.done"}

[19:55:21] [testLLM][info   ] [/home/lbw/SDK/ai-model/AIModelAcessTech/SDK/src/util/ChatgptProvider.cpp:232 ]ChatgptProvider sendMessageStream receive data:event: response.completed
data: {"type":"response.completed","response":{"id":"resp_0c6578bda96fd936016a9ff7a24ea087d2849f5ed30b101393","object":"response","created_at":1788868519,"model":"gpt-5.6-sol","status":"completed","output":[{"type":"message","id":"item_16e6790121a73833f9c18d48","role":"assistant","content":[{"type":"output_text","text":"我是 Codex,一个基于 GPT-5 的编程助手。我可以协助你阅读、修改和测试代码,也能分析项目结构、定位问题并实现功能。"}],"status":"completed"}],"usage":{"input_tokens":15122,"output_tokens":52,"total_tokens":15174,"input_tokens_details":{"cached_tokens":7488}},"service_tier":"default"},"sequence_number":42}

data: [DONE]

我们可以看到gpt返回给我们的结果格式都是:

event:xxxx
data:{....}

event:xxxx
data:{....}

...

在这里插入图片描述

我们截取出来的数据如上图所示,这是 SSE 数据流,不是纯 JSON

数据块是两层结构

  • 外层是 SSE 文本流:用 event:data: 进行组织,事件之间用\n\n分隔
  • 内层 data: 的值才是 JSON:{“content_index”:0,“delta”:“分析”,…}
SSE 事件块
  ↓ 按行拆分
event: response.output_text.delta   → 提取 eventType
data: {"delta":"分析",...}          → 提取 eventData(纯 JSON)
  ↓ 只把 eventData 给 Json 解析
{"delta":"分析",...}                → 解析成功 → 取 delta 字段

于是代码实现如下:

//解析 event 与 data 行(SSE 格式,不是 JSON)
std::istringstream eventStream(event);
std::string eventType;
std::string eventData;
std::string line;
while(std::getline(eventStream, line)){
    if(line.substr(0, 7) == "event: ")
        eventType = line.substr(7);
    else if(line.substr(0, 6) == "data: ")
        eventData = line.substr(6);
}

走到这一步,就进一步的筛选出我们要用的字段,比如类型等于"response.output_text.delta""response.output_item.done""response.completed"

对于response.output_text.delta类型

在这里插入图片描述
在上面的data数据里,我们只需要找出对应的delta即可

而对于response.output_item.done类型

在这里插入图片描述

一步步的解析出对应的text数据和delta数据

//只把 data 行的 JSON 字符串给 Json 解析
Json::CharReaderBuilder readerBuilder;
Json::Value chunk;
std::string errors;
std::istringstream deltaStream(eventData);
if(!Json::parseFromStream(readerBuilder, deltaStream, &chunk, &errors))
{
    ERR("ChatgptProvider sendMessageStream: parse delta json failed, errors: {}", errors);
    continue;  // 只是一个处理失败 ,继续处理下一个数据块
}

//按照事件类型来进行分析
if(eventType == "response.output_text.delta"){      //对delta类型数据进行处理
    if(chunk.isMember("delta") && chunk["delta"].isString()){
        std::string delta = chunk["delta"].asString();
        callback(delta, false);
    }
}
else if(eventType == "response.output_item.done"){
    //表示一个输出项完成了,将其完整的数据拼接到fullResponse中
    if(chunk.isMember("item") && chunk["item"].isObject())
    {
        Json::Value item = chunk["item"];
        if(item.isMember("content") && item["content"].isArray() && item["content"].size() > 0 && item["content"][0].isMember("text") && item["content"][0]["text"].isString()){
            fullResponse += item["content"][0]["text"].asString();
        }
    }
}
else if(eventType == "response.completed"){         //对completed类型数据进行处理
    streamFinished = true;
    callback("", true);
    return true;
}

注意在截取子串取event时的比较:与这个"event: "进行比较,后续再做类型区分的时候,才是纯类型的比较 —— 不要把空格也截取进去类型里了(容易出错)

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

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

//DeepSeek测试
//测试名, 测试用例名
#if 0
TEST(DeepSeekProvider, sendMessage) 
{
    //实例化DeepSeekProvider的对象
    auto provider = std::make_shared<ai_chat_sdk::DeepSeekProvider>();
    ASSERT_TRUE(provider != nullptr); //不报错就是成功

    //
    std::map<std::string, std::string> modelParams;
    modelParams["_apikey"] = std::getenv("DEEPSEEK_API_KEY");
    modelParams["_endpoint"] = "https://api.deepseek.com";

    //初始化对象
    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,就是符合预期
    INFO("response: {}", fulldata);
}
#endif

//Chatgpt测试
TEST(ChatgptProvider, sendMessage) 
{
    //实例化ChatgptProvider的对象
    auto provider = std::make_shared<ai_chat_sdk::ChatgptProvider>();
    ASSERT_TRUE(provider != nullptr); //不报错就是成功

    std::map<std::string, std::string> modelParams;
    modelParams["_apikey"] = std::getenv("CHATGPT_API_KEY");
    modelParams["_endpoint"] = "https://portdan.com";

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

    //因为调用sendMessage方法需要传入参数:requestParam、message
    const std::map<std::string, std::string> requestParam = {
        {"temperature", "0.7"}, 
        {"max_output_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);

    //流式响应
    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();
}

看看测试效果如何——

在这里插入图片描述

📢写在最后

接下来登场的是 接入Gemini

请添加图片描述

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐