修复: 压缩边界对齐与断路器学习语义
- 压缩边界按对话单元回退,不再劈开工具对 - 停止与接管退出补跳过结果,畸形不再新增 - 失败指纹纳入参数,改参重试不再累计熔断 - 成功断连失败段,红绿循环不再误熔断 - 脚本非零退出豁免,同命令死磕由签名熔断兜底 - 熔断前加自纠警示轮,临时路径写删豁免
This commit is contained in:
@@ -771,6 +771,10 @@ pub(crate) async fn run_agentic_loop(
|
||||
// 治"全 loop 累加不衰减":早期偶发失败不会与后期叠加误熔断。
|
||||
let mut fail_window: std::collections::VecDeque<String> = std::collections::VecDeque::new();
|
||||
|
||||
// L1 断路器两段式警示位(对齐 G2 WARN_FIRST):同 key 失败达 THRESHOLD-1 时注入一次
|
||||
// 系统警示给 LLM 自纠机会,达 THRESHOLD 才熔断。置位后本 loop 不重复警示。
|
||||
let mut breaker_warned: bool = false;
|
||||
|
||||
// G2 探索熔断:重复探索计数器(loop 生命周期累计,与 fail_window 同生命周期)。
|
||||
// 每轮 process_tool_calls 后 check_stall_breaker 取最近 N 个 assistant tool_calls 签名,
|
||||
// 喂 is_repetitive_exploration 判重复,重复 stall_count+=1,非重复重置 0。
|
||||
@@ -1086,7 +1090,7 @@ pub(crate) async fn run_agentic_loop(
|
||||
&mut guard, &mut tokens, &mut saved_token_snapshot,
|
||||
&resolved_model,
|
||||
tool_calls_acc,
|
||||
&mut fail_window,
|
||||
&mut fail_window, &mut breaker_warned,
|
||||
&mut stall_count, &mut stall_warned, &mut loop_count, &mut loop_warned,
|
||||
).await {
|
||||
return;
|
||||
@@ -1612,6 +1616,24 @@ async fn finalize_round_output(
|
||||
// 停止信号 或 已被新 loop 接管(stale):已生成文本入库后退出,不再执行后续工具调用。
|
||||
// F1:stale 时旧 loop 在此退出,不执行工具(防重复工具执行);teardown_round 内部跳过 emit。
|
||||
if stop_flag.load(Ordering::SeqCst) || loop_epoch_arc.load(Ordering::SeqCst) != my_epoch {
|
||||
// 本轮 tool_calls 尚未执行,而 assistant 头(含 tool_calls)已 push 且 P0-2 已落库,
|
||||
// 直接退出会给历史留下无 result 的悬空 tool_calls(sanitize 每请求告警的确定性供给)。
|
||||
// 退出前逐个补跳过型 tool_result 闭环三元组,随后走既有 teardown save 落库。
|
||||
// 短锁段纯写,锁边界与 epoch 语义不变:stale 时 teardown 因 epoch 不匹配跳过 save,
|
||||
// 补的 result 留在内存,由 owner loop 的下一次 save 收敛落库。
|
||||
if !tool_calls_acc.is_empty() {
|
||||
let mut drafts: Vec<(u32, &super::ToolCallDraft)> =
|
||||
tool_calls_acc.iter().map(|(k, d)| (*k, d)).collect();
|
||||
drafts.sort_by_key(|(k, _)| *k);
|
||||
let mut session = session_arc.lock().await;
|
||||
if session.per_conv.contains_key(conv_id) {
|
||||
let conv = session.conv(conv_id);
|
||||
for (_, d) in drafts {
|
||||
conv.messages.push(ChatMessage::tool_result(
|
||||
&d.id, "用户停止或被接管,工具未执行"));
|
||||
}
|
||||
}
|
||||
}
|
||||
// save_usage 增量(自上次 save 后新增);emit_usage 累计快照(前端展示本 loop 总量)
|
||||
let save_usage = usage_delta_since(tokens, saved_token_snapshot);
|
||||
let emit_usage = tokens_snapshot(tokens);
|
||||
@@ -1697,6 +1719,7 @@ async fn process_round_tools(
|
||||
resolved_model: &str,
|
||||
tool_calls_acc: std::collections::HashMap<u32, super::ToolCallDraft>,
|
||||
fail_window: &mut std::collections::VecDeque<String>,
|
||||
breaker_warned: &mut bool,
|
||||
stall_count: &mut u32,
|
||||
stall_warned: &mut bool,
|
||||
loop_count: &mut u32,
|
||||
@@ -1732,6 +1755,13 @@ async fn process_round_tools(
|
||||
if CIRCUIT_BREAKER_ENABLED {
|
||||
let (max_count, sample_key) = count_recent_failures(session_arc, conv_id, fail_window).await;
|
||||
// 锁已随作用域 drop,可安全 await/emit(避免持锁 await 死锁)。
|
||||
// 两段式(对齐 G2 WARN_FIRST 骨架):达 THRESHOLD-1 先经 insert_system_notice 注入
|
||||
// 警示(尾部追加不破前缀),带失败样本 key 与建议动作,给 LLM 查环境/换工具/调参数的
|
||||
// 自纠机会;达 THRESHOLD 才熔断。
|
||||
if max_count == CIRCUIT_BREAKER_THRESHOLD.saturating_sub(1) && !*breaker_warned {
|
||||
*breaker_warned = true;
|
||||
insert_circuit_breaker_warning(session_arc, conv_id, iteration, &sample_key).await;
|
||||
}
|
||||
if max_count >= CIRCUIT_BREAKER_THRESHOLD {
|
||||
tracing::warn!(
|
||||
conv_id = %conv_id,
|
||||
@@ -2114,6 +2144,30 @@ async fn insert_stall_warning(
|
||||
.await;
|
||||
}
|
||||
|
||||
// L1 断路器两段式警示:同 key 失败达 THRESHOLD-1 时注入系统软提示(尾部追加,不破前缀)。
|
||||
// 文本带失败样本 key(工具与参数指纹、错误指纹)与建议动作,给 LLM 自纠机会,熔断前先警示。
|
||||
async fn insert_circuit_breaker_warning(
|
||||
session_arc: &Arc<Mutex<AiSession>>,
|
||||
conv_id: &str,
|
||||
iteration: usize,
|
||||
sample_key: &str,
|
||||
) {
|
||||
let warn_text = format!(
|
||||
"⚠ 工具连续失败(样本: {})。同一做法再试大概率仍失败,建议先查环境(路径/权限/依赖是否就绪),再换用其他工具或调整参数与方案;若确认无法继续,直接向用户说明阻塞点。",
|
||||
sample_key
|
||||
);
|
||||
helpers::insert_system_notice(
|
||||
session_arc,
|
||||
conv_id,
|
||||
&warn_text,
|
||||
&format!(
|
||||
"L1 断路器:同类失败警示已 insert(软提示,给 LLM 自纠机会) iteration={} sample_key={}",
|
||||
iteration, sample_key
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// ── fetch_goal_summary: 读 pinned_goals 拼接为 "(当前目标: a; b)" 或空串 ──
|
||||
async fn fetch_goal_summary(session_arc: &Arc<Mutex<AiSession>>, conv_id: &str) -> String {
|
||||
let goals = session_arc
|
||||
@@ -2250,6 +2304,43 @@ async fn emit_ai_completed_once(
|
||||
"AI 回复已就绪,点击托盘图标查看",
|
||||
);
|
||||
}
|
||||
// 编排会话完成通知桥:工作流 AiAgentNode 挂起的等待方在此投递最终报告。
|
||||
// 本函数是全部正常出口(5 类 teardown_round 出口 + 正常完成直调)的统一单点,
|
||||
// 到达者必为 owner loop(epoch 守卫在各调用方:teardown_round 入口 / 正常完成双检),
|
||||
// force_send 接管后的旧 loop 不触发。无等待方(普通用户会话)has 判空短路,零会话锁开销。
|
||||
{
|
||||
let app_state = app_handle.state::<AppState>();
|
||||
if app_state.has_agent_completion_waiter(conv_id) {
|
||||
let report = last_active_assistant_text(&app_state.ai_session, conv_id).await;
|
||||
let delivered = app_state.notify_agent_completion(conv_id, report);
|
||||
tracing::info!(
|
||||
conv_id = %conv_id,
|
||||
delivered,
|
||||
"[ai] 编排会话收敛,完成通知桥投递最终报告"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 取会话末条 active assistant 消息文本(编排会话最终报告投递用)。
|
||||
///
|
||||
/// conv 不存在 / 无 assistant 消息返空串,原样投递,空文本由节点侧判「无输出」失败
|
||||
/// (用户 stop 在任何输出前的场景)。读内存 per_conv 非常规出口:正常完成路径 save 落库
|
||||
/// 在后台 spawn 里,时序上可能晚于本函数,内存消息是唯一已确定就绪的真相源。
|
||||
async fn last_active_assistant_text(
|
||||
session_arc: &Arc<Mutex<AiSession>>,
|
||||
conv_id: &str,
|
||||
) -> String {
|
||||
let session = session_arc.lock().await;
|
||||
session
|
||||
.conv_read(conv_id)
|
||||
.map(|c| c.messages.all_messages_clone())
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.find(|m| matches!(m.role, MessageRole::Assistant) && m.is_active())
|
||||
.map(|m| m.content)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// 构造 AiCompleted 事件(emit_ai_completed_once 内部去重用,字段口径见其文档)。
|
||||
|
||||
Reference in New Issue
Block a user