Skip to content

限额:上下文窗口与输出上限

limits 模块只回答一个问题:发这次请求之前,该按多大的窗口裁历史、max_tokens 最多给多少?

它刻意不是一张全量模型能力表。那类数据周级变动,同一个模型在不同中转站的实际限额也不一样 —— 真要全量表,去拉 models.dev 这类专门的项目。

TokenLimits

rust
pub struct TokenLimits {
    pub context_window: Option<u32>,   // 上下文窗口(输入 + 输出总量),用来裁历史
    pub max_output: Option<u32>,       // 单次输出上限,用来给 max_tokens 封顶
    pub source: LimitSource,           // 这两个数字是谁给的
}

两个数字都是 Option拿不到就是拿不到,不填一个猜的数字

四层来源

优先级来源构造可信度
0用户手填TokenLimits::from_user用户说了算 —— 中转站另有限制时唯一的出口
1端点上报TokenLimits::from_endpoint最准:中转站报的就是它自己的真实限额
2预置静态值ModelOption::preset_limits / preset::model_limits保守兜底,可能过时,应允许用户改
3都没有None让用户填,别猜

🔴 来源必须能被界面分辨。调研过的真实故障几乎都是「把猜的数字当成真的」—— 共同点不是数字错了,而是错了也看不出来。所以 source 是必填字段, 界面可以据此显示「端点上报 128K」还是「预估 128K,可修改」。

为什么多数端点不报

OpenAI 的 /v1/models 规范里就没有上下文字段。实测:

端点/models 带不带限额
OpenRouter✅ 全覆盖,还有「当前服务商实际提供」的那一份
DeepSeekcontext_window + max_output_tokens
LM Studio / Ollama 的兼容层❌ 只有 {id, object, owned_by}

所以预置静态值不是可选项 —— 不做的话,多数端点上这个能力等于不存在。

逐字段合并

rust
use ai_profile::TokenLimits;

let user = TokenLimits::from_user(Some(64_000), None);              // 用户只知道窗口
let preset = TokenLimits::from_preset(Some(128_000), Some(8192));

let l = user.or(preset);
assert_eq!(l.context_window, Some(64_000));   // 用户的
assert_eq!(l.max_output, Some(8192));         // 预置补的

or逐字段回退:用户常常只知道窗口大小(文档写了),不知道输出上限。 整条替换的话,填了窗口反而丢了预置里的输出上限。source 取优先级最高、真正起作用的那一层。

典型的三层叠法:

rust
use ai_profile::{preset, Protocol};

let preset_l = preset::model_limits(Protocol::OpenAiCompatible, Some(&base_url), &model);
let effective = [user_l, endpoint_l, preset_l]
    .into_iter()
    .flatten()
    .reduce(|hi, lo| hi.or(lo));

端点上报从哪来

「获取模型」(连通性验证)已经发了 /models 请求,限额顺带解析出来,不多花一分钱:

VerifyOk 字段内容
limits当前填的那个模型的限额;端点不报时为 None
model_limits端点上报了限额的全部模型 (id, 限额) —— 切换模型时不必再打一次端点

各家字段名不统一,parse_model_limits 按固定顺序依次尝试(context_lengthcontext_windowmax_input_tokens……),OpenRouter 优先取「当前服务商实际提供」的那一份。

用起来

裁历史的预算

rust
let budget = l.input_budget(max_tokens);   // 窗口 − 本次要用的 max_tokens;窗口未知返回 None

reserve_output本次请求实际要用的 max_tokens,不是模型的输出上限 —— 用上限会把预算压得过小。 直接喂给历史裁剪用 history::history_budget,它会再扣掉系统提示与工具定义的余量,见历史裁剪与超长重试

给 max_tokens 封顶

rust
let cap = l.max_output.map_or(requested, |m| requested.min(m));

已知上限只用来收窄,别把请求放大到模型标称的最大值 —— 聚合站可能按上限预扣额度,超长生成也更容易超时。

持久化

LimitSource::as_str / parse 与序列化格式同一套拼写(user / endpoint / preset)。 🔴 预置来源不要落库 —— 它随本库版本更新,存下来就冻结在旧值上了;每次现查即可。

相关章节

MIT 协议开源 · 文档同样欢迎 PR