优化: 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:
@@ -588,6 +588,10 @@ impl LlmProvider for AnthropicCompatProvider {
|
||||
prompt_tokens: resp.usage.input_tokens,
|
||||
completion_tokens: resp.usage.output_tokens,
|
||||
total_tokens: resp.usage.input_tokens + resp.usage.output_tokens,
|
||||
// anthropic cache 字段:creation(写入)= miss 全价语义,read(命中)= hit 低价
|
||||
prompt_cache_hit_tokens: resp.usage.cache_read_input_tokens,
|
||||
prompt_cache_miss_tokens: resp.usage.cache_creation_input_tokens,
|
||||
reasoning_tokens: 0,
|
||||
};
|
||||
AttemptOutcome::Ok(CompletionResponse {
|
||||
text,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1603,11 +1603,7 @@ mod tests {
|
||||
Ok(crate::provider::CompletionResponse {
|
||||
text: self.response_text.clone(),
|
||||
model: "mock".to_string(),
|
||||
usage: crate::provider::TokenUsage {
|
||||
prompt_tokens: 0,
|
||||
completion_tokens: 0,
|
||||
total_tokens: 0,
|
||||
},
|
||||
usage: crate::provider::TokenUsage::default(),
|
||||
tool_calls: None,
|
||||
reasoning_content: None,
|
||||
})
|
||||
|
||||
@@ -535,11 +535,10 @@ impl LlmProvider for OpenAICompatProvider {
|
||||
prompt_tokens: u.prompt_tokens,
|
||||
completion_tokens: u.completion_tokens,
|
||||
total_tokens: u.total_tokens,
|
||||
}).unwrap_or(TokenUsage {
|
||||
prompt_tokens: 0,
|
||||
completion_tokens: 0,
|
||||
total_tokens: 0,
|
||||
});
|
||||
prompt_cache_hit_tokens: u.prompt_cache_hit_tokens,
|
||||
prompt_cache_miss_tokens: u.prompt_cache_miss_tokens,
|
||||
reasoning_tokens: u.reasoning_tokens,
|
||||
}).unwrap_or_default();
|
||||
AttemptOutcome::Ok(CompletionResponse {
|
||||
text,
|
||||
model: body.model,
|
||||
|
||||
@@ -102,6 +102,18 @@ pub(crate) struct OpenAiUsage {
|
||||
pub prompt_tokens: u32,
|
||||
pub completion_tokens: u32,
|
||||
pub total_tokens: u32,
|
||||
/// DeepSeek 扩展:缓存命中 token(低价,deepseek-chat/reasoner prompt_cache_hit_tokens)。
|
||||
/// OpenAI 官方(o1 等)无此字段 → serde default 0。其他 OpenAI 兼容网关若支持 cache 也用此名。
|
||||
#[serde(default)]
|
||||
pub prompt_cache_hit_tokens: u32,
|
||||
/// DeepSeek 扩展:未命中 token(全价真实输入,prompt_cache_miss_tokens)。
|
||||
/// OpenAI 官方无此字段 → serde default 0。
|
||||
#[serde(default)]
|
||||
pub prompt_cache_miss_tokens: u32,
|
||||
/// DeepSeek-reasoner / OpenAI o1 扩展:思考 token(隐藏输出,reasoning_tokens)。
|
||||
/// 非 reasoning 模型无此字段 → serde default 0。
|
||||
#[serde(default)]
|
||||
pub reasoning_tokens: u32,
|
||||
}
|
||||
|
||||
/// SSE 流式响应 chunk
|
||||
@@ -200,6 +212,9 @@ pub(crate) fn apply_openai_sse(data: &str, usage_accum: &mut Option<TokenUsage>)
|
||||
prompt_tokens: u.prompt_tokens,
|
||||
completion_tokens: u.completion_tokens,
|
||||
total_tokens: u.total_tokens,
|
||||
prompt_cache_hit_tokens: u.prompt_cache_hit_tokens,
|
||||
prompt_cache_miss_tokens: u.prompt_cache_miss_tokens,
|
||||
reasoning_tokens: u.reasoning_tokens,
|
||||
});
|
||||
}
|
||||
if let Some(choice) = chunk.choices.into_iter().next() {
|
||||
|
||||
Reference in New Issue
Block a user