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 调用:

  1. 环境搭建:两个 header-only 库,零配置
  2. 非流式调用:发送请求,解析 JSON,获取完整回复
  3. 流式响应:实时接收 token,逐字输出
  4. 完整聊天程序:支持多轮对话、消息历史管理
  5. 错误处理:重试机制、速率限制处理、指数退避
  6. 性能优化:连接复用、异步请求、消息截断、缓存

C++ 调 OpenAI API 没有想象中那么难,核心就是 HTTP + JSON,这在 C++ 生态里都是成熟方案。

如果你正在做一个 C++ 项目,想集成 AI 能力,这套方案可以直接拿去用。有问题评论区见。

我是林夕07,我们下一篇见!


提示:本文使用的 cpp-httplibnlohmann/json 都是 MIT 许可,可以在商业项目中使用。完整的代码示例已上传到 GitHub,欢迎 Star。

安全提醒:永远不要在代码中硬编码 API Key。使用环境变量或配置文件管理密钥,并确保 .gitignore 中包含配置文件。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐