返回博客

API 与 SDK指南

用 async-openai 在 Rust 中发出第一个请求:基础 URL、密钥、用量

一个使用 tokio 和 async-openai 的小型 Rust 程序,向 CheapTokens 发送一次 chat completion,并打印回复和 token 计数。

作者:CheapTokens Team发布于 4 分钟阅读

Rust 第一个请求:cargo add async-openai、导出密钥、cargo run
本页内容
  1. 为什么用 create_byot 而不是类型化请求
  2. 设置项目
  3. 程序
  4. 返回什么
  5. 一次请求的成本
  6. 切换模型
  7. 如果它不工作

要在 Rust 中用 async-openai 调用 CheapTokens,先用基础 URL https://api.cheaptokens.dev/v1 和你的密钥构建一个 OpenAIConfig,然后用 create_byot 和 JSON 请求体发送请求。回复文本和 token 计数从返回的 JSON 中取出。这大约 25 行代码,而且只要改一个字符串,同一个程序就能用于 CheapTokens 模型页面上的任何模型。

该 crate 的 docs.rs 页面(2026 年 11 月 8 日查看)在 MIT 许可证下列出 0.43.0 版本,本指南遵循的就是这个版本。

程序流程:从环境变量取密钥、OpenAIConfig、create_byot、JSON Value
程序逐步做了什么

为什么用 create_byot 而不是类型化请求#

async-openai 通常提供类型化的请求构建器:你填充一个结构体,它返回一个结构体。这很舒服,直到网关返回了结构体不认识的字段。byot 特性("自带类型")增加了 *_byot 方法,可以接受任意 JSON 请求体,并返回任何你能反序列化成的类型,包括普通的 serde_json::Value。

对于第一个请求,这正是你想要的。你写与 curl 调用中相同的 JSON,然后直接从响应中读取 usage,无需查找类型。

设置项目#

分步操作

创建项目并设置密钥

添加 crate

在一个新项目(cargo new cheaptokens-rust)中,添加三个依赖。async-openai 在这里需要两个特性:byot 用于 create_byot 方法,chat-completion 用于 chat 部分。

bash
cargo add async-openai --features byot,chat-completion
cargo add tokio --features macros,rt-multi-thread
cargo add serde_json

将密钥放入环境变量

在你的 CheapTokens 账户中创建密钥,并让它远离源代码。如果你想看详细版本,请参阅如何安全地存储 API 密钥。

bash
export CHEAPTOKENS_API_KEY="paste-your-key-here"

在 Windows PowerShell 中,同一行是 $env:CHEAPTOKENS_API_KEY="paste-your-key-here"。

运行程序

将下一节的代码粘贴到 src/main.rs 中并启动它。

bash
cargo run

程序#

rust
use async_openai::{config::OpenAIConfig, Client};
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = std::env::var("CHEAPTOKENS_API_KEY")?;

    let config = OpenAIConfig::new()
        .with_api_key(key)
        .with_api_base("https://api.cheaptokens.dev/v1");
    let client = Client::with_config(config);

    let response: Value = client
        .chat()
        .create_byot(json!({
            "model": "deepseek-ai/DeepSeek-V4-Flash-0731",
            "messages": [
                {"role": "user", "content": "Explain what a token is in one sentence."}
            ],
            "max_tokens": 100
        }))
        .await?;

    let choice = &response["choices"][0];
    println!("{}", choice["message"]["content"].as_str().unwrap_or("(no text)"));
    println!("finish_reason: {}", choice["finish_reason"]);
    println!("input tokens:  {}", response["usage"]["prompt_tokens"]);
    println!("output tokens: {}", response["usage"]["completion_tokens"]);
    Ok(())
}

三行代码承载了整个思路。with_api_base 是 CheapTokens 接入的地方:基础 URL 以 /v1 结尾,async-openai 会自己添加端点路径。with_api_key 接收我们从环境变量中读取的值。而 model 是完整 id deepseek-ai/DeepSeek-V4-Flash-0731——价格表中的短名称不被 API 接受。

用缺失的键索引 Value 会得到 null 而不是 panic,因此字段名拼写错误会在输出中显示为 null,而不是崩溃。这在探索响应时很方便。

返回什么#

网关使用 OpenAI Chat Completions 格式,因此响应具有通常的结构(此处已裁剪,... 标记我们跳过的字段):

json
{
  "choices": [
    { "message": { "role": "assistant", "content": "..." }, "finish_reason": "stop" }
  ],
  "usage": { "prompt_tokens": 0, "completion_tokens": 0 }
}

usage 中的数字是你请求的真实计数,因此运行时会替换掉零。prompt_tokens 是你发送的内容,completion_tokens 是模型写出的内容。它们按不同价格计费,这正是打印它们的目的。如果这个区别对你来说很新鲜,输入 vs 输出 token 用五分钟解释了它。

自己试试试试看:将 "max_tokens" 设为 5 并再次运行。finish_reason 说什么?显示答案

在 OpenAI Chat Completions 格式中,它说 length。模型达到了你的上限,句子在中途被截断。正常回答时它说 stop。如果你在真实任务中看到 length,请提高 max_tokens——这篇文章涵盖了修复方法。

一次请求的成本#

假设你在循环中运行这个调用,输入一个小问题,输出一个简短回答:每次 300 个输入 token 和 200 个输出 token。在 DeepSeek-V4-Flash 上,每次请求是 $0.000025,基于每 1M 输入 token $0.015 和每 1M 输出 token $0.10 的实时价格。输入和输出合计,这比 OpenRouter 便宜,而且该模型保持 380K 上下文,最多回答 32,768 个 token。

下表显示了每个模型上的相同请求,乘以 1,000。

模型 每次请求 1,000 次请求 OpenRouter,1,000 次请求
GLM-5.3-Flash $0.000014 $0.014 $0.0267
DeepSeek-V4-Flash $0.000025 $0.0245 $0.049
MiniMax-M2.7 $0.000045 $0.045 $0.54

每次请求 300 个输入 token 和 200 个输出 token。每 100 万 token 的价格为当前价格,会随我们的资费一同变动;OpenRouter 列显示相同工作负载的参考价格。

价格在定价页面上。

切换模型#

模型改变时代码不变;只有 "model" 中的字符串改变。例如,zai-org/GLM-5.3-Flash 有 390K 上下文,最多回答 8,192 个 token。要完全避免编辑代码,可以用 std::env::var("CHEAPTOKENS_MODEL") 从第二个变量读取 id。如果你不确定你的密钥能看到哪些 id,请用 GET /v1/models 列出模型。

如果它不工作#

  • 一开始就出现 environment variable not found:密钥没有在这个终端中导出。在运行 cargo run 的同一个窗口中再次导出它。
  • API 返回 401 错误:密钥错误,或者周围有多余的空格或引号。
  • 404 错误:检查基础 URL 是否以 /v1 结尾,以及模型 id 是否是完整的,带有 deepseek-ai/ 前缀。API 401 或 404:密钥、基础 URL、模型 id 会逐步说明。
  • 编译器找不到 create_byot:byot 特性未启用。再次运行设置中的 cargo add 行。

对于不属于上述任何情况的连接问题,请参阅常见连接错误。

同样的第一个请求也存在于其他语言中:Go 和 Ruby。有关 API 本身的更多信息,请打开 CheapTokens 文档。