一、Bug 起因
项目使用C++ 封装 OpenAI 中转 API(ofox.ai)实现大模型调用,核心功能为ChatGPTProvider::sendMessage接口,用于发送对话请求并解析返回结果。测试阶段接口始终调用失败,返回空结果,测试用例全部不通过,初步怀疑是代理配置、网络、API 密钥问题。
于是,我在全网发狠似的配置代理服务器,让云服务器走本地代理。好家伙,后面发现的买的ofoxai中转不需要代理。可是根据官方给我的文档和协议,我都按照其写的,我也是真找不到自己的代码哪里错了
中转API官网查到的base_url
二、Bug 排查经过
- 错误方向排查
- 反复检查代理配置:确认本地代理地址、端口无误,注释代码中代理设置后问题仍存在,排除代理干扰。后面又关闭代理,在apifox知道不需要代理
- 核对API 密钥与请求头 :确认
Authorization请求头格式正确,环境变量OFOX_APIKEY读取正常,排除密钥与鉴权问题。 - 检查依赖与编译配置 :确认
cpp-httplib开启OpenSSL支持、jsoncpp解析正常,网络超时设置合理,排除编译与依赖问题。
2. 关键问题定位
-
对比接口文档与请求代码,发现接口地址配置错误 :初始化时endpoint错误填写为https://api.ofox.ai/v1,而/v1属于接口路径,并非域名一部分。
-
请求路径错误:原代码Post("/responses")缺少/v1前缀,正确路径应为/v1/responses。
所以我只能说这张图,不亚于诈骗,其他高级语言怎么接入我不知道。C++的endpoint里是不能有/v1。之所以会对其深信不疑,是因为在apifox测试接口时,就是如此填写的,通过了我也就不以为意了
测试通过
三、Bug 修复结果
- 修正接口地址 :初始化
endpoint改为https://api.ofox.ai。 - 修正请求路径 :
Post请求路径改为/v1/responses。
四、经验总结
- 接口配置严谨性 :第三方 API 接入时,严格区分域名(endpoint) 与接口路径 ,不随意拼接后缀,避免低级格式错误。(就算官方文档如此写的,我也应该保持怀疑测试一下
- 排查逻辑优化 :网络请求类 Bug 优先核对地址、路径、参数、请求头 四大核心要素,再排查代理、网络、依赖等问题,提升排查效率。
- 文档可信度:第三方中转 API 文档可能存在疏漏,需结合实际请求返回结果验证,不盲目照搬文档示例。
- 日志辅助排查:完善请求地址、参数、响应状态与返回体的日志打印,快速定位请求全流程问题。
本次的代码:(展示代码只是为了让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> ¶ms)
{
// 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> ¶ms,
std::function<void(const std::string &, bool)>
callback)
{
return "";
} // 对模型返回的增量数据如何处理
} // end ai_model_access_tech
最终:
