接入OpenAI无法发送请求,响应为空?Bug: C++ 接入 OpenAI 中转 API

一、Bug 起因

项目使用C++ 封装 OpenAI 中转 API(ofox.ai)实现大模型调用,核心功能为ChatGPTProvider::sendMessage接口,用于发送对话请求并解析返回结果。测试阶段接口始终调用失败,返回空结果,测试用例全部不通过,初步怀疑是代理配置、网络、API 密钥问题。

于是,我在全网发狠似的配置代理服务器,让云服务器走本地代理。好家伙,后面发现的买的ofoxai中转不需要代理。可是根据官方给我的文档和协议,我都按照其写的,我也是真找不到自己的代码哪里错了
中转API官网查到的base_url

二、Bug 排查经过

  1. 错误方向排查
    • 反复检查代理配置:确认本地代理地址、端口无误,注释代码中代理设置后问题仍存在,排除代理干扰。后面又关闭代理,在apifox知道不需要代理
    • 核对API 密钥与请求头 :确认Authorization请求头格式正确,环境变量OFOX_APIKEY读取正常,排除密钥与鉴权问题。
    • 检查依赖与编译配置 :确认cpp-httplib开启OpenSSL支持、jsoncpp解析正常,网络超时设置合理,排除编译与依赖问题。

2. 关键问题定位

  1. 对比接口文档与请求代码,发现接口地址配置错误 :初始化时endpoint错误填写为https://api.ofox.ai/v1,而/v1属于接口路径,并非域名一部分。

  2. 请求路径错误:原代码Post("/responses")缺少/v1前缀,正确路径应为/v1/responses。

所以我只能说这张图,不亚于诈骗,其他高级语言怎么接入我不知道。C++的endpoint里是不能有/v1。之所以会对其深信不疑,是因为在apifox测试接口时,就是如此填写的,通过了我也就不以为意了
测试通过

三、Bug 修复结果

  1. 修正接口地址 :初始化endpoint改为https://api.ofox.ai
  2. 修正请求路径Post请求路径改为/v1/responses

四、经验总结

  1. 接口配置严谨性 :第三方 API 接入时,严格区分域名(endpoint)接口路径 ,不随意拼接后缀,避免低级格式错误。(就算官方文档如此写的,我也应该保持怀疑测试一下
  2. 排查逻辑优化 :网络请求类 Bug 优先核对地址、路径、参数、请求头 四大核心要素,再排查代理、网络、依赖等问题,提升排查效率。
  3. 文档可信度:第三方中转 API 文档可能存在疏漏,需结合实际请求返回结果验证,不盲目照搬文档示例。
  4. 日志辅助排查:完善请求地址、参数、响应状态与返回体的日志打印,快速定位请求全流程问题。

本次的代码:(展示代码只是为了让bug实在化,而非空中楼阁

//////////////////////////////////////////////////////testLLM.cpp://////////////////////////////////////////

cpp 复制代码
//op1 :包含头文件
#include <gtest/gtest.h>
#include "../sdk/include/DeepseekProvider.h"
#include <memory>
#include <vector>
#include "../sdk/include/util/myLog.h"
#include "../sdk/include/ChatGPTProvider.h"
TEST(testChatGPT,sendMessage)
{
    //op1:构建 
    auto gptProvider = std::make_shared<ai_model_access_tech::ChatGPTProvider>();
    ASSERT_TRUE(gptProvider.get() != nullptr);
    //op2:初始化
    std::map<std::string,std::string> initModelParams;
    initModelParams.emplace("apiKey",std::getenv("OFOX_APIKEY"));
    initModelParams.emplace("endpoint","https://api.ofox.ai");//////////////太坑了/////////
    auto ret = gptProvider->initModel(initModelParams);
    ASSERT_TRUE(gptProvider->isAvailable());
    //op3:发送消息
    std::vector<ai_model_access_tech::Message> messages;
    messages.push_back({"user","你是谁?"});

    std::map<std::string, std::string> messageParams;
    messageParams.emplace("max_output_tokens","50");
    messageParams.emplace("temperature","0.7");

    std::string ret_s = gptProvider->sendMessage(messages,messageParams);
    ASSERT_FALSE(ret_s.empty());
}
int main(int argc,char* argv[])
{
    bear::myLog::initLogger("testLLM","stdout",spdlog::level::info);
    testing::InitGoogleTest(&argc,argv);
    return RUN_ALL_TESTS();
}

//////////////////////////////////////////////////////ChatGPTProvider.cpp://////////////////////////////////////////

cpp 复制代码
#include "../include/ChatGPTProvider.h"
#include "../include/util/myLog.h"
#include <httplib.h>
#include <jsoncpp/json/json.h>

namespace ai_model_access_tech
{
    bool ChatGPTProvider::initModel(const std::map<std::string, std::string> &modelConfigs)
    {
        // 初始化ChatGPT模型:apiKey endpoint
        //  op1:检查是否有apiKey
        auto apiKey_it = modelConfigs.find("apiKey");
        if (apiKey_it != modelConfigs.end())
        {
            _apiKey = apiKey_it->second;
        }
        else
        {
            bear::ERROR("ChatGPTProvider initModel:apiKey not found");
            return false;
        }
        // op2:检查是否有endpoint
        auto endpoint_it = modelConfigs.find("endpoint");
        if (endpoint_it != modelConfigs.end())
        {
            _endpoint = endpoint_it->second;
        }
        else
        {
            bear::ERROR("ChatGPTProvider initModel:endpoint not found ");
            return false;
        }
        // op3:初始化完成
        bear::INFO("ChatGPTProvider initModel success!apiKey: -- ,endpoint :{} ", _endpoint);
        _isAvailable = true;
        return true;
    }
    bool ChatGPTProvider::isAvailable()
    {
        // 判断ChatGPT模型是否可用
        return _isAvailable;
    }
    std::string ChatGPTProvider::getModelName() const
    {
        // return "gpt-4o-mini";
        return "openai/gpt-4o-mini";
    }
    std::string ChatGPTProvider::getModelDesc() const
    {
        return "OpenAI 推出的轻量级、高性价比模型,核心能力接近 GPT-4 Turbo 但更经济~";
    }
    std::string ChatGPTProvider::sendMessage(const std::vector<Message> &messages, const std::map<std::string, std::string> &params)
    {
        // op1 : 判断模型是否有效
        if(!_isAvailable)
        {
            bear::ERROR("ChatGPT Model wrong , doesn't support sendMessage");
            return "";
        }
        // op2 : 创建客户端
        httplib::Client client(_endpoint.c_str());
        std::cout <<_endpoint << std::endl;
        client.set_address_family(AF_INET);
        client.set_connection_timeout(30,0);
        client.set_read_timeout(60,0);
        //  client.set_proxy("127.0.0.1",7897);
        // op3 : 构建请求
            // 1)头
        httplib::Headers headers =  {
            {"Authorization","Bearer "+ _apiKey},
            {"Content-Type", "application/json"}
        };
            // 2)体
        int max_out_tokens = 2048;
        double temperature = 0.7;
        if(params.find("max_output_tokens") != params.end())
        {
            max_out_tokens = std::stoi(params.at("max_output_tokens"));
        }
        if(params.find("temperature") != params.end())
        {
            temperature = std::stod(params.at("temperature"));
        }

        Json::Value messagesArray(Json::arrayValue);
        for(auto &message:messages)
        {
            Json::Value msg;
            msg["role"] = message._role;
            msg["content"] = message._content;
            messagesArray.append(msg);
        }

        Json::Value requestBody;
        requestBody["model"] = getModelName();//此处已经修改为中转API模型
        requestBody["input"] = messagesArray;
        requestBody["temperature"] = temperature;
        requestBody["max_output_tokens"] = max_out_tokens;
        // 此处stream 默认为false ,不必多个流量去写false

        Json::StreamWriterBuilder swb;
        swb["indentation"] = "";
        auto body = Json::writeString(swb,requestBody);
        // op4 : 发送请求,等待响应
        auto response = client.Post("/v1/responses",headers,body,"application/json");////////////////////
        // op5 : 收到响应后,判断响应状态和解析响应
        if(!response)
        {
            bear::ERROR("ChatGPTProvider sent req FAILED or rev response FAILED");
            perror("error :");
            return "";
        }
        bear::INFO("ChatGPTProvider rev response->status:{}",response->status);
        bear::INFO("ChatGPTProvider rev response->body:{}",response->body);
        if(response->status != 200)
        {
            bear::ERROR("ChatGPTProvider sent req success BUT recv response FAILED,{}",to_string(response.error()));
            return "";
        }

        Json::CharReaderBuilder crb;
        std::istringstream responseStr(response->body);
        Json::Value responseJson;
        std::string errors;
        bool ret = Json::parseFromStream(crb,responseStr,&responseJson,&errors);
        if(ret == false)
        {
            bear::ERROR("ChatGPTProvider recv response success BUT parse response FAILED,{}",errors);
            return "";
        }
        bear::INFO("ChatGPTProvider parse response success");
        if(responseJson.isMember("output") && responseJson["output"].isArray() && !responseJson["output"].empty())
        {
            auto output = responseJson["output"][0];
            if(output.isMember("content") && output["content"].isArray() && !output["content"].empty() &&
                output["content"][0].isMember("text"))
                {
                    std::string replyStr = output["content"][0]["text"].asString();
                    return replyStr;
                }
        }
        bear::ERROR("ChatGPTProvider parse response success BUT got empty Msg!");
        return "";
    }
    std::string ChatGPTProvider::sendMessageStream(const std::vector<Message> &messages,
                                                   const std::map<std::string, std::string> &params,
                                                   std::function<void(const std::string &, bool)>
                                                       callback)
    {
        return "";
    } // 对模型返回的增量数据如何处理

} // end ai_model_access_tech

最终:

相关推荐
大橙子打游戏2 小时前
Tokmon -- 监控 Claude Code 自己的 Token 消耗
后端
小码哥_常2 小时前
Spring项目新姿势:Lambda封装Service调用,告别繁琐注入!
后端
程序员鱼皮3 小时前
315 曝光的 GEO 投毒是什么?教你 8 招,让 AI 主动推荐你!
ai·程序员·编程·ai编程·seo
qq_452396233 小时前
【模型手术室】第七篇:模型量化 —— 从 FP16 到 4-bit 的极限压缩与性能翻倍
人工智能·python·ai
不能放弃治疗3 小时前
详解大模型对话 API,messages 角色 system 、user、assistant、tool
后端
hutengyi4 小时前
go测试问题记录
开发语言·后端·golang
青槿吖4 小时前
第二篇:Spring Boot进阶:整合异常处理、测试、多环境与日志,开发稳得一批!
java·spring boot·后端·spring·面试·sqlserver·状态模式
武子康4 小时前
大数据-254 离线数仓 - Airflow 任务调度与工作流管理实战
大数据·后端·apache hive
zhangzhangkeji4 小时前
(2)chat 会话
ai