Prhub

#44887 [Rust Frontend] Populate `cached_token_count` in responses

原始 PR 作者 BugenZhao 合并时间 2026-06-11 11:50 文件变更 34 提交数 6 评论 4 代码增减 +556 / -262

执行摘要

在 Rust 前端响应中填充 cached_token_count 字段

Issue #44824 指出 Rust 前端定义的 prompt_tokens_details.cached_tokens 字段从未被填充,即使数据已到达前端并用于 Prometheus 指标。Python 前端在 --enable-prompt-tokens-details 下已支持,因此是 Rust 端的功能缺口。

值得精读,特别是 TokenUsage 提取和 ApiServerOptions 结构化的模式,有助于后续维护。同时 opt-in 设计体现了对下游使用的谨慎态度。

讨论亮点

njhill 最初建议默认总是填充 cached_token_count,使 --enable-prompt-tokens-details 成为空操作。但 BugenZhao 指出某些提供商可能不希望暴露缓存令牌数量,因此保留该标志并保持 opt-in。最终采用 opt-in 设计。

实现拆解

  1. 提取 TokenUsage 结构体(rust/src/llm/src/output.rs),包含 prompt_token_countoutput_token_countcached_token_count,统一在各层传递。
  2. types.rs 中修改 Usage::from_counts 接受 cached_tokens 参数,并新增 from_token_usage 便捷方法,根据 enable_prompt_tokens_details 标志决定是否暴露详情。
  3. state.rs 中用 ApiServerOptions 结构体替代独立的 enable_log_requestsenable_request_id_headers 字段,并在 AppState 中统一管理。
  4. cli.rs 中添加 --enable-prompt-tokens-details 标志,在测试中验证标志传递和 args-json 配置(cli/tests.rs)。
  5. 在所有路由入口(chat_completionscompletionsgenerate)中将 api_server_options 传入处理函数,并在构建 Usage 时调用 from_token_usage,传递 enable_prompt_tokens_details 以控制是否暴露详情。
文件 模块 状态 重要度
rust/src/server/src/routes/openai/utils/types.rs 响应类型 modified 8.35
rust/src/server/src/state.rs 应用状态 modified 7.51
rust/src/server/src/routes/openai/chat_completions.rs 聊天路由 modified 7.32
rust/src/cmd/src/cli/tests.rs CLI 测试 modified 7.9
rust/src/server/src/routes/openai/completions.rs 补全路由 modified 7.07
rust/src/server/src/routes/inference/generate.rs 推理路由 modified 7.1
rust/src/cmd/src/cli.rs CLI 参数 modified 6.74

关键符号

Usage::from_counts Usage::from_token_usage AppState::with_api_server_options SharedRuntimeArgs::api_server_options

关键源码片段

rust/src/server/src/routes/openai/utils/types.rs entrypoint

核心变更:修改 Usage 结构体和 from_counts/from_token_usage 方法,新增 cached_tokens 支持,并添加单元测试。

/// 从各部分计数构造 Usage,支持可选缓存令牌数
pub fn from_counts(
    prompt_tokens: usize,
    completion_tokens: usize,
    cached_tokens: Option<usize>, // 缓存令牌数,None 表示不报告
) -> Self {
    Self {
        prompt_tokens,
        total_tokens: prompt_tokens + completion_tokens,
        completion_tokens: Some(completion_tokens),
        prompt_tokens_details: cached_tokens
            .filter(|&c| c > 0) // 仅在缓存数 > 0 时包含
            .map(|c| PromptTokenUsageInfo { cached_tokens: c }),
    }
}/// 从全量 TokenUsage 构造 Usage,并根据标志决定是否暴露缓存详情
pub fn from_token_usage(usage: TokenUsage, enable_prompt_tokens_details: bool) -> Self {
    Self::from_counts(
        usage.prompt_token_count,
        usage.output_token_count,
        enable_prompt_tokens_details.then_some(usage.cached_token_count),
    )
}#[cfg(test)]
mod usage_tests {
    use vllm_llm::TokenUsage;
    use super::Usage;    #[test]
    fn token_usage_hides_prompt_token_details_by_default() {
        let usage = Usage::from_token_usage(
            TokenUsage { prompt_token_count: 5, output_token_count: 2, cached_token_count: 3 },
            false,
        );
        assert!(usage.prompt_tokens_details.is_none());
    }    #[test]
    fn token_usage_includes_prompt_token_details_when_enabled() {
        let usage = Usage::from_token_usage(
            TokenUsage { prompt_token_count: 5, output_token_count: 2, cached_token_count: 3 },
            true,
        );
        assert_eq!(usage.prompt_tokens_details.unwrap().cached_tokens, 3);
    }
}
rust/src/server/src/state.rs core-logic

用 ApiServerOptions 结构体替代多个独立布尔字段,统一管理 API server 行为选项。

/// HTTP/API-server behavior switches.
pub api_server_options: ApiServerOptions, // 聚合所有 API server 选项impl AppState {
    pub fn new(served_model_names: Vec<String>, chat: ChatLlm) -> Self {
        Self {
            served_model_names,
            chat,
            api_server_options: ApiServerOptions::default(), // 默认关闭
            server_info: None,
            api_key_hashes: Vec::new(),
            server_load: AtomicU64::new(0),
            lora_manager: LoraManager::new(),
        }
    }    /// 统一设置 API server 行为选项
    pub fn with_api_server_options(mut self, options: ApiServerOptions) -> Self {
        self.api_server_options = options;
        self
    }
}
rust/src/server/src/routes/openai/chat_completions.rs entrypoint

路由入口修改:从 state 获取 api_server_options 并传递给收集和流式函数,用于控制是否暴露缓存详情。

// 从状态获取选项,替代之前的单个 log_request 布尔值
let api_server_options = state.api_server_options;// 在收集路径中传递
let response = collect_chat_completion(
    chat_stream,
    prepared.request_id,
    prepared.response_model,
    created,
    api_server_options, // 传入完整选项
    prepared.options,
).await?;// 在流式路径中传递
let chunk_stream = chat_completion_chunk_stream(
    chat_stream,
    prepared.request_id,
    prepared.response_model,
    created,
    api_server_options,
    prepared.options,
);

评论区精华

是否默认暴露 cached_token_count 设计

njhill 建议总是填充 cached_token_count,使 --enable-prompt-tokens-details 成为空操作。BugenZhao 认为某些提供商可能不希望暴露缓存令牌数量,应保留标志。

结论:采用 opt-in 设计,保留 --enable-prompt-tokens-details 标志,默认不暴露。 · 已解决

风险与影响

开启 enable_prompt_tokens_details 会暴露缓存命中率信息,对某些用户可能视为敏感数据。API 响应中新增字段可能影响下游解析,但该字段已是 OpenAI 标准,且 Python 前端已有,兼容性良好。实现本身不引入性能或安全风险。

对用户:启用后可监控缓存效率,便于调优。对系统:仅修改响应序列化,无性能影响。对团队:对齐 Python/Rust 前端,减少功能差距。影响范围限于 Rust 前端 OpenAI 和 inference 路由。

功能默认关闭 数据暴露风险

关联 Issue

#44824 [Rust Frontend] OpenAI/inference usage never reports prompt_tokens_details.cached_tokens

完整报告

参与讨论