优化: token分项显示(in/cache/out/reasoning)+ 详情面板 + base前置

token分项(各计费不同,不显 total):df-ai 解析 provider cache/reasoning(openai_compat prompt_cache_hit/miss/reasoning_tokens + anthropic cache_read/creation)+ TokenUsage 加字段(全构造点)+ AiMessage/AiCompleted/DB V39(ai_messages 加 cache_hit/miss/reasoning 列)+ message_repo 映射(持久化)+ 前端 MessageList 显 in·cache·out·reason(in=cache_miss 全价,reasoning 有才显)+ 点击 token 弹详情面板(完整 usage+缓存命中率+model)+ df-miniapp 同步

base前置(提升 prompt cache 命中率):chat.rs aug 拼 base 后(4处)+ knowledge_inject 知识拼 base 后(固定 base 前缀,cache 命中)

附修:replace_conversation 原 13 列 INSERT 丢消息级 token → 改 18 列
This commit is contained in:
lxy
2026-08-03 01:22:30 +08:00
parent 864c696b70
commit a031521776
25 changed files with 563 additions and 45 deletions
+25 -2
View File
@@ -75,6 +75,16 @@ pub(crate) struct AnthropicContentBlock {
pub(crate) struct AnthropicUsage {
pub input_tokens: u32,
pub output_tokens: u32,
/// Anthropic prompt caching 扩展:cache 写入 token(本次写入缓存,计费如输入但稍便宜)。
/// 映射到 TokenUsage.prompt_cache_miss_tokens(全价输入语义)。
/// 非 cache 场景无此字段 → serde default 0。
#[serde(default)]
pub cache_creation_input_tokens: u32,
/// Anthropic prompt caching 扩展:cache 命中读取 token(低价)。
/// 映射到 TokenUsage.prompt_cache_hit_tokens。
/// 非 cache 场景无此字段 → serde default 0。
#[serde(default)]
pub cache_read_input_tokens: u32,
}
// ============================================================
@@ -114,7 +124,8 @@ pub(crate) fn apply_anthropic_event(data: &str, usage_accum: &mut Option<TokenUs
};
let ty = v.get("type").and_then(|t| t.as_str()).unwrap_or("");
match ty {
// 消息开始:取 input_tokens 初始化累积器(output 此时未知,置 0)
// 消息开始:取 input_tokens 初始化累积器(output 此时未知,置 0)
// anthropic prompt caching:cache_creation/read 在 message_start.usage 携带。
"message_start" => {
if let Some(inp) = v
.get("message")
@@ -122,10 +133,22 @@ pub(crate) fn apply_anthropic_event(data: &str, usage_accum: &mut Option<TokenUs
.and_then(|u| u.get("input_tokens"))
.and_then(|t| t.as_u64())
{
let u_obj = v.get("message").and_then(|m| m.get("usage"));
let cache_read = u_obj
.and_then(|u| u.get("cache_read_input_tokens"))
.and_then(|t| t.as_u64())
.unwrap_or(0) as u32;
let cache_creation = u_obj
.and_then(|u| u.get("cache_creation_input_tokens"))
.and_then(|t| t.as_u64())
.unwrap_or(0) as u32;
*usage_accum = Some(TokenUsage {
prompt_tokens: inp as u32,
completion_tokens: 0,
total_tokens: inp as u32,
prompt_cache_hit_tokens: cache_read,
prompt_cache_miss_tokens: cache_creation,
reasoning_tokens: 0,
});
}
StreamChunk { delta: String::new(), finished: false, tool_calls: None, usage: None, error: None, reasoning_content: None }
@@ -134,7 +157,7 @@ pub(crate) fn apply_anthropic_event(data: &str, usage_accum: &mut Option<TokenUs
"message_delta" => {
if let Some(out) = v.get("usage").and_then(|u| u.get("output_tokens")).and_then(|t| t.as_u64()) {
let acc = usage_accum
.get_or_insert(TokenUsage { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
.get_or_insert(TokenUsage::default());
acc.completion_tokens = out as u32;
acc.total_tokens = acc.prompt_tokens + acc.completion_tokens;
}