统一的 C++26 LLM 客户端库(类 LiteLLM):用一套 OpenAI 风格的类型访问多家大模型厂商。基于 Hackerl/asyncio 协程(curl + libuv 网络栈)、beman.execution(std::execution,P2300)与 nlohmann/json。
- 统一 OpenAI 格式:一套
ChatRequest/ChatResponse/Message/Tool类型,各 Provider 负责与厂商原生 wire format 互转。 - 多厂商:内置 OpenAI、Anthropic、Gemini、Azure OpenAI,以及 DeepSeek / Moonshot / 通义千问 / Ollama 等 OpenAI 兼容端点;可注册任意自定义 OpenAI 兼容端点。
- SSE 流式输出:统一的
StreamChunk(text delta / tool call delta / finish reason / usage)。 - Tool Calling:工具定义(JSON Schema)、
tool_calls解析、tool_result回传,跨厂商一致;ToolLoop进一步自动驱动"模型 ↔ 工具"多轮循环。 - 结构化输出:
ResponseFormat统一封装 JSON mode / JSON Schema(Anthropic 经强制工具模拟)。 - Embeddings:
Client::embedding/Router::embedding,统一EmbeddingRequest/EmbeddingResponse;超出batch_size自动并发分批、按序合并。 - 多模态输入:
Message::user_with_parts组合文本 / 图片(ImagePart)/ 音频(AudioPart);图片支持 http(s) URL 与 base64 data URI,由各 Provider 转成原生 wire 格式。 - 多模态生成:
Client::generate_image(dall-e / gpt-image / Imagen)与Client::generate_speech(OpenAI TTS / Gemini TTS);Gemini File API 大文件上传(Client::upload_file)。 - Realtime:WebSocket 传输 + OpenAI Realtime 事件级会话封装(
llm::realtime::Session)。 - Prompt caching:
Message::cache断点标记(Anthropic 原生映射),Usage::cache_read/write_tokens统一上报命中量。 - Router:按模型重试(指数退避、遵循 Retry-After)+ 跨模型 fallback + 客户端侧 TPM/RPM 配额跳过。
- 协作式取消:
Task::cancel()让co_await立即以ErrorKind::Cancelled失败;sender 桥已接 stop token。 - 工程化:GCRA 限速器(per-endpoint)、结构化日志钩子(
LogCallback)、LiteLLM 风格 cost 估算(estimate_cost_usd)。 - 两套 API 风格:C++20 协程(
co_await返回std::expected<T, ApiError>)与 std::execution sender 桥(sync_wait阻塞调用)。 - 统一错误类型:单一
ApiError贯穿全库,按 HTTP 状态码归类,可判断是否可重试。
┌─────────────────────────────────────────────────────────┐
│ Client (协程 API) Router (重试 / fallback) │
│ chat() / chat_stream() chat() / embedding() │
│ embedding() / generate_image() / generate_speech() │
│ upload_file() ToolLoop (工具循环) │
│ realtime::Session (WebSocket) │
├─────────────────────────────────────────────────────────┤
│ Registry (模型串路由 / 凭证解析 / per-endpoint 超时限速) │
│ "provider/model" -> EndpointConfig + Credentials │
├─────────────────────────────────────────────────────────┤
│ Provider (纯转换层,无协程,可离线单测) │
│ OpenAI │ Anthropic │ Gemini │ Azure │ OpenAI-compatible │
├─────────────────────────────────────────────────────────┤
│ HttpTransport (可 mock 抽象) ws::Connection (WebSocket)│
│ AsyncioTransport -> asyncio::http (curl + libuv) │
│ SseParser (增量 SSE 解析) │
└─────────────────────────────────────────────────────────┘
sender 桥:TaskSender -> beman::execution::sync_wait
(在独立 worker 线程的事件循环中跑协程,结果经 set_value/set_error 回传)
横切:cancellable(协作式取消)│ RateLimiter(GCRA 限速)
│ LogCallback(结构化日志)│ CostRegistry(cost 估算)
所有依赖由源码编译到 third_party/install,不污染系统环境。
# 1. 一次性构建第三方依赖(asyncio/curl/libuv/openssl/json 等)
./third_party/build.sh
# 2. 配置 + 编译(debug preset,示例默认 ON)
cmake --preset debug
cmake --build --preset debug -j
# 3. 跑测试
ctest --preset debug如需关闭示例或测试:-DLLMCLIENT_BUILD_EXAMPLES=OFF / -DLLMCLIENT_BUILD_TESTS=OFF。
要求:CMake ≥ 3.30,支持 C++26 的编译器(GCC 13+;asyncio 的 std::stacktrace 需要 libstdc++exp,CMake 会自动探测链接)。
链接 asyncio::asyncio-main 后由它提供 main(),只需定义 asyncMain:
#include <cstdio>
#include <asyncio/task.h>
#include <llm/client.h>
asyncio::task::Task<void> asyncMain(int argc, char *argv[]) {
llm::Client client;
llm::ChatRequest req;
req.model = "openai/gpt-4o";
req.messages = {
llm::Message::system("You are a helpful assistant."),
llm::Message::user("用一句话介绍 C++ 协程。"),
};
const auto result = co_await client.chat(std::move(req));
if (!result) {
std::fprintf(stderr, "error: %s\n", result.error().to_string().c_str());
co_return;
}
std::printf("%s\n", result->message.content.c_str());
co_return;
}流式:
co_await client.chat_stream(req, [](const llm::StreamChunk &chunk) {
std::fputs(chunk.text_delta.c_str(), stdout);
std::fflush(stdout);
});llm::EmbeddingRequest req;
req.model = "openai/text-embedding-3-small";
req.input = {"hello world", "你好,世界"};
const auto result = co_await client.embedding(std::move(req));
if (result)
std::printf("vectors=%zu dim=%zu\n",
result->embeddings.size(), result->embeddings[0].size());Router::embedding 与 Router::chat 一样提供按模型重试与 fallback。
llm::ChatRequest req;
req.model = "openai/gpt-4o";
req.messages = {llm::Message::user_with_parts({
std::string{"描述这张图片。"},
llm::ImagePart{.url = "https://example.com/cat.jpg"},
// 本地图片改用 base64:llm::ImagePart::from_base64("image/png", base64_data)
})};
const auto result = co_await client.chat(std::move(req));#include <beman/execution/execution.hpp>
#include <llm/client.h>
#include <llm/sender.h>
int main() {
llm::Client client;
llm::ChatRequest req;
req.model = "deepseek/deepseek-chat";
req.messages = {llm::Message::user("Hi")};
try {
// 返回 std::optional<std::tuple<ChatResponse>>
auto result = beman::execution::sync_wait(llm::chat_sender(client, std::move(req)));
if (!result) { /* stopped */ return 1; }
const auto &[resp] = *result;
std::printf("%s\n", resp.message.content.c_str());
}
catch (const llm::ApiError &err) {
// sync_wait 会把错误通道作为异常重新抛出
std::fprintf(stderr, "request failed: %s\n", err.to_string().c_str());
}
}llm::chat_sync(client, req) 是上述两步的简写。
更多完整程序见 examples/:basic_chat、streaming、tool_calling、tool_loop、structured_output、image_generation、realtime、router_fallback、embedding、multimodal_chat、sender_sync_wait(构建产物在 build/debug/examples/)。
ChatRequest::model 采用 LiteLLM 风格:provider/model;不带 / 的裸模型名默认走 OpenAI。内置端点:
| 前缀 | 端点 | API key 环境变量 | 备注 |
|---|---|---|---|
openai |
https://api.openai.com/v1 |
OPENAI_API_KEY |
默认 provider |
anthropic |
https://api.anthropic.com |
ANTHROPIC_API_KEY |
未指定时 max_tokens 默认 4096 |
gemini |
https://generativelanguage.googleapis.com |
GEMINI_API_KEY |
|
azure |
由环境变量提供 | AZURE_OPENAI_API_KEY |
需 AZURE_OPENAI_ENDPOINT;AZURE_OPENAI_API_VERSION 默认 2024-10-21;模型名为部署名 |
deepseek |
https://api.deepseek.com |
DEEPSEEK_API_KEY |
OpenAI 兼容 |
moonshot |
https://api.moonshot.cn/v1 |
MOONSHOT_API_KEY |
OpenAI 兼容 |
qwen |
https://dashscope.aliyuncs.com/compatible-mode/v1 |
DASHSCOPE_API_KEY |
OpenAI 兼容 |
ollama |
http://localhost:11434/v1 |
无需 key | OpenAI 兼容,本地 |
环境变量未设置时 Registry::resolve 返回 ApiError{ErrorKind::Auth}。ChatRequest 也支持按请求覆盖:api_key / base_url / timeout。
llm::Registry::instance().register_openai_compatible(
"groq", // 模型串前缀 groq/...
"https://api.groq.com/openai/v1",
"GROQ_API_KEY" // 环境变量名;空串表示无需 key
);
// 之后即可 req.model = "groq/llama-3.3-70b-versatile";更底层的需求(非 OpenAI 线格式)可实现 llm::Provider 接口后用 register_endpoint 注册。
llm::Router router{
client,
llm::RouterConfig{
.models = {"openai/gpt-4o", "deepseek/deepseek-chat"}, // 按序尝试
.retry = {
.max_retries = 2, // 每个模型首次失败后的重试次数
.initial_delay = std::chrono::milliseconds{500},
.max_delay = std::chrono::milliseconds{8000},
.backoff_factor = 2.0,
},
.fallback_on_non_retryable = true, // 认证/参数错误也切下一个模型
// 可选:客户端侧配额,窗口内耗尽的模型直接跳过
.model_limits = {{"openai/gpt-4o", {.rpm = 500, .tpm = 2000000}}},
},
};
const auto result = co_await router.chat(req); // req.model 被忽略行为:同一模型上,可重试错误(限流 / 5xx / 超时 / 网络错误)按指数退避重试,并遵循响应的 Retry-After;重试耗尽后切到下一个模型。不可重试错误(认证失败、请求非法等)是否立即 fallback 由 fallback_on_non_retryable 控制(默认 true)。全部模型失败时返回最后一个错误。
所有 API 的失败统一为 llm::ApiError(协程版经 std::expected,sender 版经 set_error),to_string() 输出单行描述。
ErrorKind |
含义 | 对应 HTTP |
|---|---|---|
Auth |
认证失败 / 缺 key | 401 / 403 |
RateLimit |
限流(附 retry_after) |
429 |
InvalidRequest |
请求非法 / 未知 provider | 400 / 422 |
NotFound |
模型或路径不存在 | 404 |
Server |
服务端错误 | 5xx |
Timeout |
连接 / 总超时 | — |
Network |
DNS / TLS / 连接失败 | — |
Parse |
响应载荷无法解析 | — |
Cancelled |
调用方主动取消(Task::cancel() / stop token) |
— |
Unknown |
其他 | — |
llm::retryable(kind) 判断某类错误是否值得重试。
- Anthropic 无 embeddings API,也不支持音频输入:相应请求返回
ErrorKind::InvalidRequest。 - OpenAI embeddings 仅解析
encoding_format = "float"(默认)的响应;"base64"暂不支持。 - Gemini embeddings 响应不含 token 统计,
EmbeddingResponse::usage为 0。 - 图片输入仅支持 http(s) URL 与 base64 data URI 两种形式(
ImagePart::from_base64生成后者)。 - 流式工具调用按厂商增量事件合并;Gemini 流没有显式结束标记,按 EOF 处理。
- Anthropic 的 system 消息从
messages中抽出单独传递;max_tokens必填,未指定时默认 4096。 - 单请求超时:默认 600s,可用
ChatRequest::timeout覆盖。
include/llm/ 公共头文件(types / error / provider / registry /
client / router / sender / http / sse / toolloop /
cancellable / ratelimit / logging / cost / ws / realtime)
include/llm/providers/ 各厂商 Provider 头文件
src/ 库实现(src/providers/ 为各厂商实现)
examples/ 示例程序(协程版 ×10 + sender 版 ×1)
tests/ 单元测试
third_party/ 第三方依赖(build.sh 源码编译到 install/)
CMakeLists.txt 根构建脚本(LLMCLIENT_BUILD_EXAMPLES / _TESTS 开关)
CMakePresets.json debug 等构建 preset