优化: aichat效率批(save无变化捷径/断路器尾窗/system缓存指纹/会话列表刷新收敛) + 新增 LLM prompt caching(cache_control开关默认关/system稳定段易变段分离治缓存命中率) + 销账

This commit is contained in:
lxy
2026-08-09 21:35:13 +08:00
parent 4df91ed155
commit 7eb2e25ed5
13 changed files with 698 additions and 180 deletions
+43 -2
View File
@@ -263,6 +263,15 @@ pub const CIRCUIT_BREAKER_THRESHOLD: u32 = 3;
/// 单轮并行失败爆发,又足够长以容忍偶发抖动。
pub const CIRCUIT_BREAKER_WINDOW: usize = 20;
/// BE2(AC-EFF-R2-1):断路器/探索熔断读消息的尾部窗口大小。
///
/// count_recent_failures / check_stall_breaker 只关心最近若干条消息(末尾工具结果 + 其前驱
/// assistant tool_calls / 最近几轮工具签名),用 ContextManager::recent_messages 只 clone 尾部
/// 切片,避免 all_messages_clone 每轮全量深克隆(长对话每轮 O(n) clone 浪费)。
/// 40:同轮工具回填通常 1~5 条,富余覆盖其定义调用与最近多轮;超窗工具映射退化为
/// unknown_tool(仅失败分组更粗)/ 签名样本截断(仍达 STALL_BREAKER_SAMPLE_SIZE=12),不误熔断。
pub const BREAKER_TAIL_N: usize = 40;
/// L1 断路器总开关(默认 true)。false → 跳过断路器检查,降级为纯 max_iterations
/// 旧行为(排障/对比/临时关闭用)。机制优先 prompt 说教,每改配开关 + 兜底(关降级旧行为)。
pub const CIRCUIT_BREAKER_ENABLED: bool = true;
@@ -598,6 +607,25 @@ async fn emit_retry_attempt(
tokio::time::sleep(delay).await;
}
/// 把 Anthropic prompt caching 易变尾段追加到请求消息流**末尾**(一条 user 消息)。
///
/// 多场景命中率设计要点:
/// - **放消息流末尾而非 system**:任何位于 system 之后的消息级缓存断点(含未来「多轮对话缓存」
/// 在末条 user 上打断点)都在易变段之前——易变内容(日期/清单/augmentation/知识)每日/每请求
/// 变化时,只影响其自身及之后(无),不失效已缓存的 system/历史前缀。若放首条 user,跨天日期
/// 变化会失效其后全部历史缓存。
/// - **用 user 角色而非 system**:anthropic_compat::convert_request 会把 System-role 消息抽到
/// 顶层 system(受 cache_control 缓存),易变段若为 system 会随请求变化失效整段 system 缓存。
/// 必须 user 承载,convert_request 对连续 user 做 merge_consecutive_users,追加安全。
/// - **不持久化**:仅注入本轮请求的 messages clone,per_conv.messages 不含易变段 → 每轮重建
/// 恰好一条,无重复。
fn merge_volatile_tail(messages: &mut Vec<ChatMessage>, tail: &str) {
if tail.is_empty() {
return;
}
messages.push(ChatMessage::user(tail));
}
/// Agentic 循环:流式接收 → 工具执行 → 结果回传 LLM → 循环
///
/// 退出条件:
@@ -611,6 +639,9 @@ pub(crate) async fn run_agentic_loop(
app_handle: AppHandle,
provider_config: AiProviderRecord,
system_prompt: String,
// Anthropic prompt caching 易变尾段(缓存模式:日期/清单/augmentation/知识,追加到消息流末尾;
// None = 开关关,保持现状行为)。
volatile_tail: Option<String>,
conv_id: String,
knowledge_config: crate::state::KnowledgeConfig,
llm_concurrency: LlmConcurrency,
@@ -1523,6 +1554,11 @@ pub(crate) async fn run_agentic_loop(
}
let mut msgs = vec![ChatMessage::system(&system_prompt)];
msgs.extend(history_msgs);
// Anthropic prompt caching:把易变尾段(日期/清单/augmentation/知识)追加到消息流末尾。
// 放末尾而非 system:易变内容不进缓存前缀,每日/每请求变化不失效 system 缓存(跨天仍命中)。
if let Some(tail) = &volatile_tail {
merge_volatile_tail(&mut msgs, tail);
}
msgs
};
@@ -2188,10 +2224,13 @@ async fn count_recent_failures(
conv_id: &str,
fail_window: &mut std::collections::VecDeque<String>,
) -> (u32, String) {
// BE2(AC-EFF-R2-1):只克隆尾部 BREAKER_TAIL_N 条,避免 all_messages_clone 全量深克隆
// (长对话每轮 O(n) clone 浪费)。窗口覆盖末尾 Tool 结果与其前驱 assistant tool_calls
// (同轮工具回填通常 1~5 条,40 足富余;超窗工具映射退化为 unknown_tool,仅失败分组更粗,不误熔断)。
let messages = {
let session = session_arc.lock().await;
match session.conv_read(conv_id) {
Some(conv) => conv.messages.all_messages_clone(),
Some(conv) => conv.messages.recent_messages(BREAKER_TAIL_N),
None => Vec::new(),
}
};
@@ -2424,8 +2463,10 @@ async fn check_stall_breaker(
// arguments 非合法 JSON 时 to_string 兜底(签名仍可比,只是兜底全量)。
let signatures: Vec<String> = {
let session = session_arc.lock().await;
// BE2(AC-EFF-R2-1):只取尾部 BREAKER_TAIL_N 条(签名抽取只需最近几轮工具调用,
// 全量 clone 纯浪费;STALL_BREAKER_SAMPLE_SIZE=12 ≪ 40,样本充足)。
let messages = match session.conv_read(conv_id) {
Some(conv) => conv.messages.all_messages_clone(),
Some(conv) => conv.messages.recent_messages(BREAKER_TAIL_N),
None => Vec::new(),
};
let mut sigs: Vec<String> = Vec::new();