API Design Principles

本篇回答的问题

一种行为如何在 Rust、CLI、HTTP、Python 与 C 之间保持一致,而不强迫每个表面都 暴露完全相同的机制?

分层表面

表面主要受众设计侧重
Rust facadeRust 应用带类型的所有权与可组合性
Engine API高级集成方完整的生成策略/控制
CLI/REPL人类与脚本可发现性、稳定的 stdout、可操作的错误
HTTP server兼容 OpenAI 的客户端wire 兼容、streaming、会话
PythonPython/NumPy 用户熟悉的 session API,显式处的 DLPack
C ABI跨语言 host不透明 handle、value/vtable ABI、显式所有权

这些表面共享的是语义,而不一定是完全相同的签名。

单一语义内核

prompt 处理、生成选项、finish reason、stop 行为与采样都应收敛到引擎的类型上。 binding 把数据 marshal 进这个内核,而不是重新实现生成策略。

例如,一个外部 sampler 替换的是终端 token 选择,而引擎仍然施加所配置的 processor/constraint 链。

可操作的失败

每个表面都应保留:

  • 是哪个操作失败了;
  • 涉及了哪个 argument/node/shape/dtype/path/device;
  • 为什么被拒绝;
  • 调用方如何修复;
  • 在安全的前提下给出底层原因。

Rust 使用带类型的 crate 错误;编排层可以加上 anyhow::Context;C 使用机器可读的 状态码加上一条丰富的消息;Python/HTTP 在映射时不擦除诊断细节。

按语言划分的所有权

Rust

利用所有权、borrowing 与 newtype 让非法状态难以表达。可失败的设备/运行时工作 返回 Result

C

  • 不透明 handle 有配对的 create/release 函数;
  • 在解引用之前检查 null;
  • 每个 panic 都在 ABI 边界处被捕获;
  • 状态/消息的所有权是显式的;
  • 调用方与库绝不释放对方未指明的 heap 分配。

Python

  • 拷贝出的 NumPy 输出是安全的默认;
  • DLPack/zero-copy 路径是显式的;
  • thread/可重入行为被明确说明;
  • iterator/callback 的完成语义必须区分 token event 与最终结果状态。

HTTP

  • request/response 的形状遵循目标中兼容 OpenAI 的契约;
  • SSE streaming 保持 completion/error 语义;
  • debug/admin 端点是 opt-in 的;
  • 默认不记录 payload 与 secret。

兼容性策略

对仓库自有的 Rust/产品 API 而言,它处于 pre-release 阶段:干净地重塑它们并更新 所有调用方,而不是不断堆积别名与弃用。

这种自由不适用于:

  • ONNX 语义/opset;
  • 已文档化的模型 metadata;
  • 稳定的 C/plugin ABI 版本;
  • 向用户承诺的兼容 OpenAI 的 wire 行为;
  • 受支持的 Python wheel ABI。

显式行为胜过隐藏的便利

  • 除非显式启用,否则不做静默的 CPU 回退;
  • eager API 中不做隐式的跨设备传输;
  • 在使用可选行为之前先做 capability 协商;
  • 在需要 metadata/图结构之处,不按模型名做 dispatch;
  • 报告一个受约束的回退,而不是假装请求的模式已经运行。

正式来源

相关笔记