C++ 调用 OpenAI API:手把手搭建 AI 应用后端
C++ 调用 OpenAI API:手把手搭建 AI 应用后端
大家好,我是林夕07。Python 调 OpenAI API 你肯定见过无数教程了,但用 C++ 调呢?今天这篇文章,我手把手带你用 C++ 实现一个完整的 OpenAI Chat Completions 调用——从发 HTTP 请求、解析 JSON、到流式响应,全部用 C++ 原生实现。跟着做,你就能跑起来。
为什么用 C++ 调 OpenAI API?
可能会有朋友问:Python 调 API 那么方便,为什么还要折腾 C++?
几个实际理由:
- 性能敏感场景:游戏引擎、实时系统、嵌入式设备,这些地方跑 Python 不现实
- 现有 C++ 项目集成:你的项目本来就是 C++,不想为了调 API 再引入 Python 运行时
- 学习和探索:了解 HTTP 请求、JSON 解析、流式处理在 C++ 中是怎么做的
- 生产级后端:C++ 的内存控制和性能在高并发场景下确实有优势
好了,理由说够了,开干。
一、环境准备
1.1 你需要什么
- C++ 编译器:GCC 10+、Clang 12+ 或 MSVC 2019+
- CMake:3.15+(用于管理构建)
- OpenAI API Key:在 platform.openai.com 注册获取
1.2 安装依赖库
我们用两个第三方库:
| 库 | 用途 | 安装方式 |
|---|---|---|
| cpp-httplib | HTTP 客户端 | Header-only,直接下载 |
| nlohmann/json | JSON 解析 | Header-only,直接下载 |
最简安装方式——直接下载头文件:
# 创建项目目录
mkdir cpp-openai-chat && cd cpp-openai-chat
# 下载 cpp-httplib(单个头文件)
curl -L https://raw.githubusercontent.com/yhirose/cpp-httplib/master/httplib.h -o httplib.h
# 下载 nlohmann/json(单个头文件)
curl -L https://raw.githubusercontent.com/nlohmann/json/develop/single_include/nlohmann/json.hpp -o json.hpp
两个 .h 文件扔到项目目录就行,零依赖,header-only,舒服。
1.3 项目结构
cpp-openai-chat/
├── CMakeLists.txt # 构建配置
├── httplib.h # HTTP 库
├── json.hpp # JSON 库
└── main.cpp # 主程序
二、OpenAI Chat Completions API 简介
在写代码之前,先搞清楚我们要调的 API 长什么样。
2.1 基本信息
| 项目 | 值 |
|---|---|
| 端点 | https://api.openai.com/v1/chat/completions |
| 方法 | POST |
| Content-Type | application/json |
| 认证 | Authorization: Bearer <API_KEY> |
2.2 请求格式
{
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "你是一个有帮助的助手。"},
{"role": "user", "content": "你好,请介绍一下 C++23。"}
],
"temperature": 0.7,
"max_tokens": 1000,
"stream": false
}
2.3 响应格式(非流式)
{
"id": "chatcmpl-xxx",
"object": "chat.completion",
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "C++23 是 C++ 标准的最新版本……"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 20,
"completion_tokens": 150,
"total_tokens": 170
}
}
理解了这些,我们就可以写代码了。
三、基础实现:非流式调用
先来一个最基础的版本——发送请求,拿到完整响应。
// main.cpp - 基础版本
#define CPPHTTPLIB_OPENSSL_SUPPORT // 如果你需要 HTTPS
#include "httplib.h"
#include "json.hpp"
#include <iostream>
#include <string>
#include <stdexcept>
using json = nlohmann::json;
// OpenAI API 配置
struct OpenAIConfig {
std::string api_key;
std::string model = "gpt-4o";
double temperature = 0.7;
int max_tokens = 1000;
};
class OpenAIClient {
public:
explicit OpenAIClient(const OpenAIConfig& config)
: config_(config),
cli_("api.openai.com", 443) // HTTPS 端口
{
}
/**
* 发送聊天请求并获取完整响应
*
* @param messages 消息列表,格式为 [{"role": "...", "content": "..."}]
* @return 模型的回复文本
* @throws std::runtime_error 当请求失败时
*/
std::string chat(const json& messages) {
// 构造请求体
json request_body = {
{"model", config_.model},
{"messages", messages},
{"temperature", config_.temperature},
{"max_tokens", config_.max_tokens},
{"stream", false}
};
// 设置请求头
httplib::Headers headers = {
{"Content-Type", "application/json"},
{"Authorization", "Bearer " + config_.api_key}
};
// 发送 POST 请求
auto res = cli_.Post("/v1/chat/completions",
headers,
request_body.dump(),
"application/json");
// 检查响应
if (!res) {
throw std::runtime_error("请求失败:无法连接到 OpenAI 服务器");
}
if (res->status != 200) {
throw std::runtime_error(
"API 错误 (HTTP " + std::to_string(res->status) + "): " +
res->body
);
}
// 解析响应
json response = json::parse(res->body);
return response["choices"][0]["message"]["content"].get<std::string>();
}
private:
OpenAIConfig config_;
httplib::SSLClient cli_;
};
int main() {
// ⚠️ 实际使用时,请从环境变量读取 API Key,不要硬编码!
OpenAIConfig config;
config.api_key = "sk-your-api-key-here"; // 替换成你的 key
config.model = "gpt-4o";
OpenAIClient client(config);
// 构造消息
json messages = json::array({
{{"role", "system"}, {"content", "你是一个有帮助的助手。请用中文回答。"}},
{{"role", "user"}, {"content", "用一句话解释什么是 C++ 模板元编程。"}}
});
try {
std::string reply = client.chat(messages);
std::cout << "AI 回复: " << reply << std::endl;
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}
return 0;
}
四、流式响应处理
流式(streaming)响应是现代 AI 应用的标配——模型不再等全部生成完再返回,而是一个 token 一个 token 地"吐"出来,用户能实时看到回复。
4.1 SSE(Server-Sent Events)格式
OpenAI 的流式响应使用 SSE 格式,每个数据块长这样:
data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"你好"},"index":0}]}
data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":",世界"},"index":0}]}
data: [DONE]
每行以 data: 开头,内容是 JSON,最后以 data: [DONE] 结束。
4.2 流式调用实现
// stream_chat.cpp - 流式版本
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "httplib.h"
#include "json.hpp"
#include <iostream>
#include <string>
#include <thread>
using json = nlohmann::json;
class OpenAIStreamClient {
public:
explicit OpenAIStreamClient(const std::string& api_key)
: cli_("api.openai.com", 443),
api_key_(api_key)
{
// 启用 SSL 验证(生产环境建议开启)
cli_.enable_server_certificate_verification(true);
}
/**
* 流式聊天:实时打印模型生成的每个 token
*
* @param messages 消息列表
* @return 完整的回复文本
*/
std::string chat_stream(const json& messages) {
json request_body = {
{"model", "gpt-4o"},
{"messages", messages},
{"temperature", 0.7},
{"max_tokens", 1000},
{"stream", true} // 关键:启用流式
};
httplib::Headers headers = {
{"Content-Type", "application/json"},
{"Authorization", "Bearer " + api_key_}
};
std::string full_response;
std::string buffer; // 用于缓存不完整的 SSE 行
// 使用内容接收器实现真正的流式处理
// content_receiver 的签名是 bool(const char* data, size_t data_length)
auto res = cli_.Post("/v1/chat/completions",
headers,
request_body.dump(),
"application/json",
[&](const char* data, size_t data_length) {
// 将接收到的数据追加到缓冲区
buffer.append(data, data_length);
// 按行处理 SSE 数据
size_t pos;
while ((pos = buffer.find('\n')) != std::string::npos) {
std::string line = buffer.substr(0, pos);
buffer.erase(0, pos + 1);
// 去掉行尾的 \r
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (line.empty()) continue;
// 去掉 "data: " 前缀
if (line.rfind("data: ", 0) == 0) {
std::string payload = line.substr(6);
// 检查是否结束
if (payload == "[DONE]") {
std::cout << std::endl;
return true;
}
try {
json chunk = json::parse(payload);
auto& choice = chunk["choices"][0];
if (choice.contains("delta") &&
choice["delta"].contains("content")) {
std::string content = choice["delta"]["content"];
std::cout << content << std::flush;
full_response += content;
}
} catch (const json::exception&) {
// 忽略解析错误(可能是不完整的 chunk)
}
}
}
return true; // 返回 true 继续接收
});
// 检查最终响应状态
if (!res || res->status != 200) {
int status = res ? res->status : -1;
throw std::runtime_error("流式请求失败,HTTP 状态: " +
std::to_string(status));
}
return full_response;
}
private:
httplib::SSLClient cli_;
std::string api_key_;
};
int main() {
const char* api_key = std::getenv("OPENAI_API_KEY");
if (!api_key) {
std::cerr << "请设置环境变量 OPENAI_API_KEY" << std::endl;
return 1;
}
OpenAIStreamClient client(api_key);
json messages = json::array({
{{"role", "system"}, {"content", "你是一个有帮助的助手。"}},
{{"role", "user"}, {"content", "写一首关于秋天的五言绝句。"}}
});
try {
std::cout << "AI: ";
std::string full = client.chat_stream(messages);
// full 包含完整回复文本,可用于后续处理
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}
return 0;
}
五、完整示例:命令行聊天程序
把上面的代码整合一下,做一个可以持续对话的命令行聊天程序:
// chat_cli.cpp - 完整的命令行聊天程序
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "httplib.h"
#include "json.hpp"
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
using json = nlohmann::json;
class ChatCLI {
public:
ChatCLI() : cli_("api.openai.com", 443) {
api_key_ = std::getenv("OPENAI_API_KEY");
if (!api_key_) {
throw std::runtime_error("请设置环境变量 OPENAI_API_KEY");
}
// 添加系统消息
messages_.push_back({
{"role", "system"},
{"content", "你是一个有帮助的助手。请用中文回答,保持简洁。"}
});
}
void run() {
std::cout << "=========================================" << std::endl;
std::cout << " C++ OpenAI Chat (输入 'quit' 退出)" << std::endl;
std::cout << "=========================================" << std::endl;
std::string input;
while (true) {
std::cout << "\n你: ";
std::getline(std::cin, input);
if (input == "quit" || input == "exit" || input.empty()) {
std::cout << "再见!" << std::endl;
break;
}
// 添加用户消息
messages_.push_back({{"role", "user"}, {"content", input}});
try {
std::string reply = send_request();
std::cout << "\nAI: " << reply << std::endl;
// 添加助手回复到历史
messages_.push_back({{"role", "assistant"}, {"content", reply}});
// 保留最近 20 轮对话,防止 token 超限
while (messages_.size() > 41) { // 1 system + 40 messages
messages_.erase(messages_.begin() + 1);
}
} catch (const std::exception& e) {
std::cerr << "\n[错误] " << e.what() << std::endl;
// 移除发送失败的用户消息
messages_.pop_back();
}
}
}
private:
std::string send_request() {
json request_body = {
{"model", "gpt-4o"},
{"messages", messages_},
{"temperature", 0.7},
{"max_tokens", 500},
{"stream", false}
};
httplib::Headers headers = {
{"Content-Type", "application/json"},
{"Authorization", "Bearer " + api_key_}
};
// 带重试机制的请求
for (int retry = 0; retry < 3; ++retry) {
auto res = cli_.Post("/v1/chat/completions",
headers,
request_body.dump(),
"application/json");
if (res) {
if (res->status == 200) {
json response = json::parse(res->body);
return response["choices"][0]["message"]["content"];
}
if (res->status == 429) {
// 速率限制,等待后重试
std::cerr << "[警告] 速率限制,2 秒后重试..." << std::endl;
#ifdef _WIN32
Sleep(2000);
#else
sleep(2);
#endif
continue;
}
// 其他错误直接抛出
throw std::runtime_error(
"API 错误 (HTTP " + std::to_string(res->status) + "): " +
res->body
);
}
// 网络错误重试
std::cerr << "[警告] 网络错误,1 秒后重试..." << std::endl;
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
}
throw std::runtime_error("请求失败:已重试 3 次仍无法连接");
}
const char* api_key_;
httplib::SSLClient cli_;
json messages_ = json::array();
};
int main() {
try {
ChatCLI cli;
cli.run();
} catch (const std::exception& e) {
std::cerr << "启动失败: " << e.what() << std::endl;
return 1;
}
return 0;
}
六、编译和运行
6.1 CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(cpp-openai-chat LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 查找 OpenSSL(HTTPS 支持)
find_package(OpenSSL REQUIRED)
# 添加可执行文件
add_executable(chat_cli chat_cli.cpp)
# 链接依赖
target_link_libraries(chat_cli PRIVATE
OpenSSL::SSL
OpenSSL::Crypto
)
# Windows 下需要链接 ws2_32(Winsock)
if(WIN32)
target_link_libraries(chat_cli PRIVATE ws2_32 crypt32)
endif()
6.2 编译命令
Linux / macOS:
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
Windows (MSVC):
mkdir build && cd build
cmake .. -G "Visual Studio 17 2022" -A x64
cmake --build . --config Release
不需要 CMake,直接编译(Linux):
g++ -std=c++20 -O2 chat_cli.cpp -o chat_cli \
-lssl -lcrypto -lpthread
6.3 运行
# 设置 API Key
export OPENAI_API_KEY="sk-your-key-here" # Linux/macOS
set OPENAI_API_KEY=sk-your-key-here # Windows CMD
# 运行
./chat_cli
七、错误处理和重试机制
在生产环境中,你至少需要处理以下几种错误场景:
| 错误类型 | HTTP 状态码 | 处理方式 |
|---|---|---|
| API Key 无效 | 401 | 立即停止,提示用户检查 Key |
| 速率限制 | 429 | 指数退避重试(2s, 4s, 8s…) |
| 服务器过载 | 503 | 延迟重试 |
| 网络超时 | - | 重试 3 次,间隔递增 |
| 请求体过大 | 400 | 截断消息历史,减少 token |
| JSON 解析失败 | 200 | 记录日志,返回兜底提示 |
指数退避重试的核心逻辑:
#include <thread>
#include <chrono>
/**
* 带指数退避的重试执行器
*
* @param func 要执行的操作
* @param max_retries 最大重试次数
* @param base_delay_ms 初始延迟(毫秒)
*/
template<typename Func>
auto retry_with_backoff(Func func, int max_retries = 3,
int base_delay_ms = 1000) {
for (int attempt = 0; attempt <= max_retries; ++attempt) {
try {
return func();
} catch (const std::exception& e) {
if (attempt == max_retries) throw;
int delay = base_delay_ms * (1 << attempt); // 指数增长
std::cerr << "[重试 " << (attempt + 1) << "/" << max_retries
<< "] 等待 " << delay << "ms..." << std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(delay));
}
}
throw std::runtime_error("重试次数已用完");
}
// 使用示例
auto result = retry_with_backoff([&]() {
return client.chat(messages);
});
八、性能优化建议
当你把这个方案用到生产环境时,有几个优化方向值得考虑:
8.1 连接池复用
// httplib 默认会复用连接,但确保不要每次都创建新客户端
// ✅ 正确:一个客户端实例复用
httplib::SSLClient cli("api.openai.com", 443);
// 多次调用复用同一个 cli
// ❌ 错误:每次请求都创建新客户端
// for (...) {
// httplib::SSLClient new_cli("api.openai.com", 443); // 浪费!
// }
8.2 异步请求
// 使用 cpp-httplib 的异步接口
cli.PostAsync("/v1/chat/completions",
headers,
request_body.dump(),
"application/json",
[&](const httplib::Response& res) {
// 在回调中处理响应
if (res.status == 200) {
auto response = json::parse(res.body);
// ... 处理结果
}
}
);
8.3 消息历史管理
/**
* 智能截断消息历史,确保不超过 token 限制
* 策略:保留系统消息 + 最近 N 轮对话
*/
json truncate_history(json& messages, size_t max_turns = 20) {
json truncated = json::array();
// 保留系统消息
if (!messages.empty() && messages[0]["role"] == "system") {
truncated.push_back(messages[0]);
}
// 保留最近的对话(跳过系统消息)
size_t start = (truncated.empty()) ? 0 : 1;
size_t keep = std::min(max_turns * 2, messages.size() - start);
for (size_t i = messages.size() - keep; i < messages.size(); ++i) {
truncated.push_back(messages[i]);
}
return truncated;
}
8.4 本地缓存
对于重复的查询,可以用本地缓存避免重复调用 API:
#include <unordered_map>
#include <mutex>
class ResponseCache {
public:
bool has(const std::string& key) const {
std::lock_guard lock(mutex_);
return cache_.count(key) > 0;
}
std::string get(const std::string& key) const {
std::lock_guard lock(mutex_);
return cache_.at(key);
}
void put(const std::string& key, const std::string& value) {
std::lock_guard lock(mutex_);
cache_[key] = value;
}
private:
mutable std::mutex mutex_;
std::unordered_map<std::string, std::string> cache_;
};
总结
这篇文章从零开始,带你用 C++ 实现了完整的 OpenAI API 调用:
- 环境搭建:两个 header-only 库,零配置
- 非流式调用:发送请求,解析 JSON,获取完整回复
- 流式响应:实时接收 token,逐字输出
- 完整聊天程序:支持多轮对话、消息历史管理
- 错误处理:重试机制、速率限制处理、指数退避
- 性能优化:连接复用、异步请求、消息截断、缓存
C++ 调 OpenAI API 没有想象中那么难,核心就是 HTTP + JSON,这在 C++ 生态里都是成熟方案。
如果你正在做一个 C++ 项目,想集成 AI 能力,这套方案可以直接拿去用。有问题评论区见。
我是林夕07,我们下一篇见!
提示:本文使用的
cpp-httplib和nlohmann/json都是 MIT 许可,可以在商业项目中使用。完整的代码示例已上传到 GitHub,欢迎 Star。安全提醒:永远不要在代码中硬编码 API Key。使用环境变量或配置文件管理密钥,并确保
.gitignore中包含配置文件。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)