fix(protocol): TopicTokenStats 补齐 cached_tokens 字段,修复前端缓存占比恒为 0

根因:command 层与 protocol 层存在两个同名 TopicTokenStats 结构体,websocket 适配器做 JSON 往返时 protocol 版缺 cached_tokens 字段,serde 反序列化静默丢弃。后端解析/存库/聚合均正常(DB 实测 8832/13916 命中),仅传输层断链。补字段并加往返回归测试固化两结构体对齐不变量。
This commit is contained in:
oudecheng 2026-08-15 21:02:29 +08:00
parent 011b23b54e
commit ca7dcabbde

View File

@ -54,6 +54,11 @@ pub struct TopicTokenStats {
pub prompt_tokens: u64, pub prompt_tokens: u64,
pub completion_tokens: u64, pub completion_tokens: u64,
pub total_tokens: u64, pub total_tokens: u64,
/// 累计缓存命中的输入 tokens 数(老数据为 0
/// 缺失此字段会导致 command 层序列化的 JSON 在此被静默丢弃serde 忽略未知字段),
/// 前端缓存占比恒为 0——两结构体字段必须保持对齐。
#[serde(default)]
pub cached_tokens: u64,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub last_prompt_tokens: Option<u32>, pub last_prompt_tokens: Option<u32>,
#[serde(default)] #[serde(default)]
@ -357,3 +362,35 @@ pub fn serialize_inbound(msg: &WsInbound) -> Result<String, serde_json::Error> {
pub fn serialize_outbound(msg: &WsOutbound) -> Result<String, serde_json::Error> { pub fn serialize_outbound(msg: &WsOutbound) -> Result<String, serde_json::Error> {
serde_json::to_string(msg) serde_json::to_string(msg)
} }
#[cfg(test)]
mod tests {
use super::*;
/// 回归测试command 层 TopicTokenStats 序列化的 JSON 必须能无损还原到
/// protocol 层同名结构体websocket 适配器走这条 JSON 往返路径)。
///
/// 历史缺陷protocol 版曾缺 cached_tokens 字段serde 反序列化时静默丢弃,
/// 导致前端缓存占比恒为 0后端解析、存库、聚合全部正常
/// 两结构体字段必须保持对齐,此测试固化该不变量。
#[test]
fn test_topic_token_stats_roundtrip_preserves_cached_tokens() {
let command_stats = crate::command::handlers::list_topics::TopicTokenStats {
prompt_tokens: 13916,
completion_tokens: 80,
total_tokens: 13996,
cached_tokens: 8832,
last_prompt_tokens: Some(6971),
context_window_tokens: 131072,
};
// 模拟 websocket 适配器的实际路径command JSON → protocol 结构体
let json = serde_json::to_string(&command_stats).unwrap();
let protocol_stats: TopicTokenStats = serde_json::from_str(&json).unwrap();
assert_eq!(protocol_stats.cached_tokens, 8832);
assert_eq!(protocol_stats.prompt_tokens, 13916);
assert_eq!(protocol_stats.last_prompt_tokens, Some(6971));
assert_eq!(protocol_stats.context_window_tokens, 131072);
}
}