优化: 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:
@@ -1116,6 +1116,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
));
|
||||
merge_msg.prompt_tokens = Some(0);
|
||||
merge_msg.completion_tokens = Some(0);
|
||||
merge_msg.prompt_cache_hit_tokens = Some(0);
|
||||
merge_msg.prompt_cache_miss_tokens = Some(0);
|
||||
merge_msg.reasoning_tokens = Some(0);
|
||||
session.conv(&conv_id).messages.push(merge_msg);
|
||||
if !merge_result.conflicts.is_empty() {
|
||||
let mut conflict_msg = ChatMessage::assistant(&format!(
|
||||
@@ -1128,6 +1131,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
));
|
||||
conflict_msg.prompt_tokens = Some(0);
|
||||
conflict_msg.completion_tokens = Some(0);
|
||||
conflict_msg.prompt_cache_hit_tokens = Some(0);
|
||||
conflict_msg.prompt_cache_miss_tokens = Some(0);
|
||||
conflict_msg.reasoning_tokens = Some(0);
|
||||
session.conv(&conv_id).messages.push(conflict_msg);
|
||||
}
|
||||
}
|
||||
@@ -1139,6 +1145,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
prompt_tokens: tokens.prompt(),
|
||||
completion_tokens: tokens.completion(),
|
||||
total_tokens: tokens.total(),
|
||||
prompt_cache_hit_tokens: tokens.cache_hit(),
|
||||
prompt_cache_miss_tokens: tokens.cache_miss(),
|
||||
reasoning_tokens: tokens.reasoning(),
|
||||
};
|
||||
finish_round_exit(
|
||||
&session_arc, &db, &conv_id,
|
||||
@@ -1176,6 +1185,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
prompt_tokens: tokens.prompt(),
|
||||
completion_tokens: tokens.completion(),
|
||||
total_tokens: tokens.total(),
|
||||
prompt_cache_hit_tokens: tokens.cache_hit(),
|
||||
prompt_cache_miss_tokens: tokens.cache_miss(),
|
||||
reasoning_tokens: tokens.reasoning(),
|
||||
};
|
||||
// 入口 stop:本轮可能尚未 stream(首轮即停),不记 model——避免把未实际生成的 model 写入 models 数组
|
||||
// 统一走 finish_round_exit:save(Some usage, None model) + spawn_title + emit(None,None,publish=true)
|
||||
@@ -1548,8 +1560,13 @@ pub(crate) async fn run_agentic_loop(
|
||||
prompt_tokens: if round_usage.prompt_tokens == 0 { estimated_prompt } else { round_usage.prompt_tokens },
|
||||
completion_tokens: round_usage.completion_tokens,
|
||||
total_tokens: if round_usage.prompt_tokens == 0 { estimated_prompt + round_usage.completion_tokens } else { round_usage.total_tokens },
|
||||
// 分项 token(2026-08-02):cache/reasoning 透传自 round_usage,落库 + 累加器都需
|
||||
prompt_cache_hit_tokens: round_usage.prompt_cache_hit_tokens,
|
||||
prompt_cache_miss_tokens: round_usage.prompt_cache_miss_tokens,
|
||||
reasoning_tokens: round_usage.reasoning_tokens,
|
||||
};
|
||||
tokens.add(usage.prompt_tokens, usage.completion_tokens);
|
||||
// 累加本轮全量 usage(含 cache/reasoning 分项)到 tokens 累加器
|
||||
tokens.add_usage(&usage);
|
||||
|
||||
// 追加 partial assistant 消息(若无 tool_calls 且有文本)
|
||||
// 退出校验改 conv 存在性 + push 改 per_conv.messages。
|
||||
@@ -1571,8 +1588,12 @@ pub(crate) async fn run_agentic_loop(
|
||||
msg.reasoning_content = round_reasoning_content.clone();
|
||||
// 消息级 token(对齐 push_assistant_message 双轨持久化):本轮 partial usage
|
||||
// (prompt=round 或 estimated 兜底,completion=round)。系统提示消息无 token,不设。
|
||||
// 分项 token(2026-08-02):cache/reasoning 透传自 round_usage。
|
||||
msg.prompt_tokens = Some(usage.prompt_tokens);
|
||||
msg.completion_tokens = Some(usage.completion_tokens);
|
||||
msg.prompt_cache_hit_tokens = Some(usage.prompt_cache_hit_tokens);
|
||||
msg.prompt_cache_miss_tokens = Some(usage.prompt_cache_miss_tokens);
|
||||
msg.reasoning_tokens = Some(usage.reasoning_tokens);
|
||||
conv.messages.push(msg);
|
||||
// 追加系统提示消息:响应因网络中断不完整(对齐决策 a1 系统提示机制)
|
||||
let mut notice = ChatMessage::system("⚠ 响应因网络中断不完整,以上为已接收的部分内容。可重新发送以获取完整回复。");
|
||||
@@ -1590,6 +1611,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
prompt_tokens: tokens.prompt(),
|
||||
completion_tokens: tokens.completion(),
|
||||
total_tokens: usage.total_tokens,
|
||||
prompt_cache_hit_tokens: tokens.cache_hit(),
|
||||
prompt_cache_miss_tokens: tokens.cache_miss(),
|
||||
reasoning_tokens: tokens.reasoning(),
|
||||
};
|
||||
finish_round_exit(
|
||||
&session_arc, &db, &conv_id,
|
||||
@@ -1608,9 +1632,23 @@ pub(crate) async fn run_agentic_loop(
|
||||
// stream 后不再立即释放(会话级并发语义:工具执行期间也占槽)。per-provider permit
|
||||
// (_provider_permit)仍在 candidate 循环内随作用域 Drop 自动释放。
|
||||
|
||||
// 累加本轮 token:provider 流式 usage 的 prompt_tokens 为 0 时(GLM 等),用预估输入兜底
|
||||
// 累加本轮 token:用 provider 真实 usage。GLM 等流式不报 completion_tokens 时为 0,如实反映(不预估)。
|
||||
// 分项 token(2026-08-02):构造本轮完整 usage(prompt 用 estimated 兜底,cache/reasoning 透传 round),
|
||||
// add_usage 一次性累加 prompt/completion/cache/reasoning 到 tokens 累加器。
|
||||
let round_prompt = if round_usage.prompt_tokens == 0 { estimated_prompt } else { round_usage.prompt_tokens };
|
||||
tokens.add(round_prompt, round_usage.completion_tokens);
|
||||
let round_usage_full = df_ai::provider::TokenUsage {
|
||||
prompt_tokens: round_prompt,
|
||||
completion_tokens: round_usage.completion_tokens,
|
||||
total_tokens: if round_usage.prompt_tokens == 0 {
|
||||
estimated_prompt.saturating_add(round_usage.completion_tokens)
|
||||
} else {
|
||||
round_usage.total_tokens
|
||||
},
|
||||
prompt_cache_hit_tokens: round_usage.prompt_cache_hit_tokens,
|
||||
prompt_cache_miss_tokens: round_usage.prompt_cache_miss_tokens,
|
||||
reasoning_tokens: round_usage.reasoning_tokens,
|
||||
};
|
||||
tokens.add_usage(&round_usage_full);
|
||||
|
||||
// 追加 assistant 消息到历史 + G1 目标提取(扁平重构,原嵌套 8 层 → 3 层)
|
||||
let has_tool_calls = !tool_calls_acc.is_empty();
|
||||
@@ -1627,6 +1665,8 @@ pub(crate) async fn run_agentic_loop(
|
||||
&mut session, &conv_id, has_tool_calls, &tool_calls_acc,
|
||||
&full_text, &resolved_model, &last_reasoning_content,
|
||||
round_prompt, round_usage.completion_tokens,
|
||||
round_usage.prompt_cache_hit_tokens, round_usage.prompt_cache_miss_tokens,
|
||||
round_usage.reasoning_tokens,
|
||||
);
|
||||
if GOAL_PIN_ENABLED {
|
||||
update_pinned_goals(&mut session, &conv_id, &tool_calls_acc);
|
||||
@@ -1642,6 +1682,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
prompt_tokens: tokens.prompt(),
|
||||
completion_tokens: tokens.completion(),
|
||||
total_tokens: tokens.total(),
|
||||
prompt_cache_hit_tokens: tokens.cache_hit(),
|
||||
prompt_cache_miss_tokens: tokens.cache_miss(),
|
||||
reasoning_tokens: tokens.reasoning(),
|
||||
};
|
||||
save_conversation(&session_arc, &db, &conv_id, Some(&usage), Some(&resolved_model), true).await;
|
||||
}
|
||||
@@ -1652,6 +1695,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
prompt_tokens: tokens.prompt(),
|
||||
completion_tokens: tokens.completion(),
|
||||
total_tokens: tokens.total(),
|
||||
prompt_cache_hit_tokens: tokens.cache_hit(),
|
||||
prompt_cache_miss_tokens: tokens.cache_miss(),
|
||||
reasoning_tokens: tokens.reasoning(),
|
||||
};
|
||||
// 统一走 finish_round_exit:save(Some usage, Some model) + spawn_title + emit(None,None,publish=true)
|
||||
finish_round_exit(
|
||||
@@ -1722,6 +1768,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
prompt_tokens: tokens.prompt(),
|
||||
completion_tokens: tokens.completion(),
|
||||
total_tokens: tokens.total(),
|
||||
prompt_cache_hit_tokens: tokens.cache_hit(),
|
||||
prompt_cache_miss_tokens: tokens.cache_miss(),
|
||||
reasoning_tokens: tokens.reasoning(),
|
||||
};
|
||||
save_conversation(&session_arc, &db, &conv_id, Some(&usage), Some(&resolved_model), true).await;
|
||||
// 审批等待 return 前 disarm guard——保持 generating=true 留 try_continue 续生成,
|
||||
@@ -1750,6 +1799,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
prompt_tokens: tokens.prompt(),
|
||||
completion_tokens: tokens.completion(),
|
||||
total_tokens: tokens.total(),
|
||||
prompt_cache_hit_tokens: tokens.cache_hit(),
|
||||
prompt_cache_miss_tokens: tokens.cache_miss(),
|
||||
reasoning_tokens: tokens.reasoning(),
|
||||
};
|
||||
// 统一走 finish_round_exit:save(Some usage, Some model) + 不 spawn_title(对齐原无 title) +
|
||||
// emit(Some(true), publish=false)。**do_publish=false 保留原 max_iterations 不 publish 行为**
|
||||
@@ -1773,6 +1825,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
prompt_tokens: tokens.prompt(),
|
||||
completion_tokens: tokens.completion(),
|
||||
total_tokens: tokens.total(),
|
||||
prompt_cache_hit_tokens: tokens.cache_hit(),
|
||||
prompt_cache_miss_tokens: tokens.cache_miss(),
|
||||
reasoning_tokens: tokens.reasoning(),
|
||||
};
|
||||
// 落库 + 标题 + 知识提炼打包后台化:不阻塞 generating 复位与 Completed 事件
|
||||
// save 先行(extract/title 都读已落库消息);extract 内部 fire-and-forget,与 title 可能并发
|
||||
@@ -1806,6 +1861,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
prompt_tokens: tokens.prompt(),
|
||||
completion_tokens: tokens.completion(),
|
||||
total_tokens: usage_total,
|
||||
prompt_cache_hit_tokens: tokens.cache_hit(),
|
||||
prompt_cache_miss_tokens: tokens.cache_miss(),
|
||||
reasoning_tokens: tokens.reasoning(),
|
||||
};
|
||||
emit_ai_completed_once(
|
||||
&app_handle, &conv_id, &normal_usage,
|
||||
@@ -2248,12 +2306,16 @@ async fn emit_ai_completed_once(
|
||||
pinned_goals: &[super::GoalEntry],
|
||||
) {
|
||||
// emit 端(前端通道):incomplete 用 emit_incomplete(MidStream 传 Some(true))。
|
||||
// token 分项(2026-08-02):cache_hit/cache_miss/reasoning 透传前端分计费展示。
|
||||
let _ = app_handle.emit(
|
||||
"ai-chat-event",
|
||||
AiChatEvent::AiCompleted {
|
||||
total_tokens: usage.total_tokens,
|
||||
prompt_tokens: usage.prompt_tokens,
|
||||
completion_tokens: usage.completion_tokens,
|
||||
prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens,
|
||||
prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens,
|
||||
reasoning_tokens: usage.reasoning_tokens,
|
||||
incomplete: emit_incomplete,
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
pinned_goals: pinned_goals.to_vec(),
|
||||
@@ -2267,6 +2329,9 @@ async fn emit_ai_completed_once(
|
||||
total_tokens: usage.total_tokens,
|
||||
prompt_tokens: usage.prompt_tokens,
|
||||
completion_tokens: usage.completion_tokens,
|
||||
prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens,
|
||||
prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens,
|
||||
reasoning_tokens: usage.reasoning_tokens,
|
||||
incomplete: publish_incomplete,
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
pinned_goals: pinned_goals.to_vec(),
|
||||
@@ -2335,6 +2400,7 @@ async fn finish_round_exit(
|
||||
// 入参 session 需外层调用方持锁;本函数只做纯内存 mutate,无 await/emit,不会死锁。
|
||||
// prompt_tokens/completion_tokens: 本轮 LLM 调用 token 用量(消息级持久化,解 reload/压缩/切会话后
|
||||
// 历史 assistant 消息 token 不显)。两构造分支都设。
|
||||
// 分项 token(2026-08-02):cache_hit/cache_miss/reasoning 透传自 round_usage,前端分计费展示。
|
||||
fn push_assistant_message(
|
||||
session: &mut AiSession,
|
||||
conv_id: &str,
|
||||
@@ -2345,6 +2411,9 @@ fn push_assistant_message(
|
||||
last_reasoning_content: &Option<String>,
|
||||
prompt_tokens: u32,
|
||||
completion_tokens: u32,
|
||||
cache_hit: u32,
|
||||
cache_miss: u32,
|
||||
reasoning: u32,
|
||||
) {
|
||||
if has_tool_calls {
|
||||
let mut order: Vec<u32> = tool_calls_acc.keys().copied().collect();
|
||||
@@ -2368,6 +2437,9 @@ fn push_assistant_message(
|
||||
msg.reasoning_content = last_reasoning_content.clone();
|
||||
msg.prompt_tokens = Some(prompt_tokens);
|
||||
msg.completion_tokens = Some(completion_tokens);
|
||||
msg.prompt_cache_hit_tokens = Some(cache_hit);
|
||||
msg.prompt_cache_miss_tokens = Some(cache_miss);
|
||||
msg.reasoning_tokens = Some(reasoning);
|
||||
session.conv(conv_id).messages.push(msg);
|
||||
} else if !full_text.is_empty() {
|
||||
let mut msg = ChatMessage::assistant(full_text);
|
||||
@@ -2375,6 +2447,9 @@ fn push_assistant_message(
|
||||
msg.reasoning_content = last_reasoning_content.clone();
|
||||
msg.prompt_tokens = Some(prompt_tokens);
|
||||
msg.completion_tokens = Some(completion_tokens);
|
||||
msg.prompt_cache_hit_tokens = Some(cache_hit);
|
||||
msg.prompt_cache_miss_tokens = Some(cache_miss);
|
||||
msg.reasoning_tokens = Some(reasoning);
|
||||
session.conv(conv_id).messages.push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user