# jevapi
面向 [TypeSafe AI HTTP API](https://docs.typesafe.ai/api) 的非官方异步 Rust binding。
使用 Tokio + reqwest,默认启用 rustls。
[crates.io](https://crates.io/crates/jevapi) · [API 文档](https://docs.rs/jevapi) ·
[GitHub](https://github.com/a1393323447/jevapi)
## 快速开始
通过 crates.io 添加依赖:
```sh
cargo add jevapi
cargo add tokio --features macros,rt-multi-thread
```
或者在 `Cargo.toml` 中配置:
```toml
[dependencies]
jevapi = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```
将密钥放入 `TYPESAFE_API_KEY` 环境变量,然后调用:
```rust,no_run
use jevapi::{Answer, Client, EvaluationRequest, Question};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_env()?;
let request = EvaluationRequest::new("生产环境支付服务中断,请立即处理。")
.question("urgent", Question::noul("是否需要立即处理?"))
.question("team", Question::choice("应该分配给哪个团队?", [
("engineering", Some("故障和技术问题".into())),
("sales", None),
]))
.question("severity", Question::score("问题有多严重?", [
"轻微影响", "部分功能不可用", "生产服务中断",
]));
let response = client.evaluate(&request).await?;
if let Some(Answer::Noul { noul }) = response.answers.get("urgent") {
println!("紧急概率:{noul:.3}");
}
println!("实际模型:{},用量:{:?}", response.model, response.usage);
Ok(())
}
```
## 设计
- `Client`:复用连接池,可廉价克隆,支持 `Send + Sync`;`evaluate(&request).await` 借用请求,便于重复使用。运行时需要 Tokio 的 I/O 和时间驱动。
- `ClientBuilder`:显式配置 API key、base URL、整体超时、重试策略和自定义 `reqwest::Client`。构建返回 `Result`,不因配置错误主动 panic。`Client::new(key)` 使用显式密钥;`Client::from_env()` 读取 `TYPESAFE_API_KEY`。
- `EvaluationRequest`:构造函数要求提供 state,默认模型为 `jev-latest`,可通过 `.model(...)` 指定任意版本。链式 `.question(...)` 添加问题,同名键会替换已有值。也可直接操作公开字段或通过 Serde 反序列化。
- `Question` / `Answer`:带 `type` 标签的枚举,分别完整表达 Noul、Choice、Score。用 `match` 提取结果,避免字符串类型判断及无关可选字段。Score 的 legend 和概率保留 API 的字符串索引;模型返回的数字使用 `f64`。
- `Content`:仅允许顶层字符串、对象、数组;对象和数组内部可包含任意 JSON 值。`Content::try_from(serde_json::json!(...))?` 用于结构化输入。state、instructions、criteria 均支持结构化内容。
- 本地校验:模型非空、至少一个问题、Choice 1–255 个选项、Score 2–10 个等级。其中非空规则属于 SDK 基础校验;官方文档明确给出 Choice 上限和 Score 范围。每次发送前都校验,因此直接修改字段或反序列化不会绕过校验。
- `Error`:区分配置、参数校验、HTTP 传输、API 状态、成功响应解码和整体超时。`ApiError` 保留状态码、响应头和文本正文,可用 `.json()` 读取未预设结构的错误详情。
Noul 的可选准则:
```rust
use jevapi::{NoulCriteria, Question};
let question = Question::noul_with_criteria(
"是否紧急?",
NoulCriteria::default().yes("必须立即处理").no("可以稍后处理"),
);
```
## 配置、重试与并发
```rust,no_run
use std::time::Duration;
use jevapi::{Client, RetryPolicy};
# fn main() -> Result<(), jevapi::Error> {
let client = Client::builder()
.base_url("https://api.typesafe.ai/v1")
.timeout(Duration::from_secs(30))
.retry_policy(RetryPolicy {
max_retries: 2,
initial_delay: Duration::from_millis(500),
max_delay: Duration::from_secs(4),
})
.build()?;
# Ok(())
# }
```
默认整体超时 60 秒,涵盖所有 HTTP 尝试、响应体读取和重试等待。
仅对文档要求重试的 429、529 自动重试,最多额外尝试 3 次。
退避上限从 500ms 开始翻倍,最高 8 秒;每次等待采用上限的 50%–100% 随机抖动。
若响应提供 `Retry-After`(整数秒或 HTTP 日期),等待至少该时长,但仍受整体超时限制。
`RetryPolicy::none()` 关闭重试。
401、422、其他 HTTP 错误、网络错误及无效成功响应直接返回。
默认禁用 HTTP 重定向及 reqwest 内建传输重试,防止隐式重放 POST。
自定义 HTTP 客户端的重定向、传输重试及超时设置由调用方控制。
取消 future 会停止本地请求/等待,但服务端已开始的处理可能继续执行。
共享客户端即可并发,通常无需再套一层 `Arc`:
```rust,no_run
# async fn example(client: jevapi::Client, a: jevapi::EvaluationRequest, b: jevapi::EvaluationRequest) -> Result<(), jevapi::Error> {
let (a, b) = tokio::try_join!(client.evaluate(&a), client.evaluate(&b))?;
# Ok(())
# }
```
API key 在客户端和构造器的 `Debug` 中隐藏。API 错误的 `Display` 不包含响应正文;
`Debug` 和公开的正文/响应头字段仍可能包含业务数据。
## TLS 与范围
默认 feature 为 `rustls-tls`。如需系统 TLS:
```toml
jevapi = { version = "0.1", default-features = false, features = ["native-tls"] }
```
禁用所有 TLS feature 时只提供 HTTP;实际 TypeSafe 端点使用 HTTPS,需要 TLS 后端。
本库实现当前文档中的 `POST /v1/systemone`。文档未定义流式接口,因此没有 SSE/streaming API。
当前目标为原生 Tokio 应用,未声明 WASM 支持。
## 示例与验证
```text
cargo run --example evaluate
cargo run --example concurrent
cargo test --locked
cargo fmt --check
cargo clippy --all-targets --all-features --locked -- -D warnings
cargo doc --no-deps --locked
```
两个示例会请求真实 API,需要环境变量密钥。普通测试使用本地模拟 HTTP 服务,不需要密钥。
`evaluate` 示例在一次请求中覆盖三类问题;`concurrent` 示例演示两个并发请求。
2026-09-22 使用 `evaluate` 示例完成真实 API 验证:返回模型 `jev-1.13.0`,
三种答案均成功解析,消耗 418 个输入 token 和 68 个输出 token。
该结果记录单次验证,模型别名指向和推理结果可能随时间变化。
API 对照来源:[API reference](https://docs.typesafe.ai/api)、
[Models](https://docs.typesafe.ai/models)。
## 许可证
本项目使用 [MIT 许可证](LICENSE)。