修复: G2 签名熔断误杀红绿迭代

- 同签名死磕判定改无写间隔连续段口径,间插写类调用=红绿验证不累计
- 写证据=is_write_evidence_tool 五个改文件工具,读类不算证据
- 阈值 3 与纯死磕语义不变,根治脚本失败致执行中断
- 新增红绿迭代豁免与读间隔死磕两场景测试
This commit is contained in:
lxy
2026-10-01 22:36:26 +08:00
parent 26d9a1af1e
commit dd0afcf0d1
2 changed files with 96 additions and 28 deletions
+9 -4
View File
@@ -185,6 +185,8 @@ pub const TOPIC_MARKER_GOAL_AWARE: bool = true;
// grep 无匹配是有效排除信号非漂移,误熔断正常排除式搜索(实证 ac448296)。
// - 新(当前):连续 N 轮工具调用签名重复(is_repetitive_exploration 判 tool_call_signature)。
// 漂移本质 = AI 卡住反复同样调用;正常探索(换词/换路径/换工具)签名不同,真死循环签名重复。
// 红绿迭代(同验证命令反复跑+间插写调用)按写间隔豁免不算漂移,见 is_repetitive_exploration。
// 红绿迭代豁免:同验证命令反复跑但间插写类调用(patch/write)不算漂移,见 is_repetitive_exploration 写间隔论证。
//
// 机制(非 prompt 说教,骨架沿用):连续 N 轮判定签名重复 → 两段式(WARN_FIRST 先警示再熔断):
// 警示注入下轮 prompt 给 LLM 自纠机会,熔断(guard.reset + AiHelpRequired)逼用户换思路/人工介入。
@@ -373,7 +375,7 @@ pub(crate) use stall_detector::{
pub(crate) use helpers::try_continue_agent_loop;
pub(crate) use helpers::infer_goal_from_tool_calls;
pub(crate) use helpers::{
tool_call_signature, is_repetitive_exploration, is_pure_greeting, is_tool_call_over_limit,
tool_call_signature, is_repetitive_exploration, is_write_evidence_tool, is_pure_greeting, is_tool_call_over_limit,
DestructiveLoopSample, destructive_loop_sample, detect_destructive_loop,
};
@@ -1947,7 +1949,10 @@ async fn check_stall_breaker(
// 就抽取每工具签名(name + arguments JSON parse),累积到 STALL_BREAKER_SAMPLE_SIZE 个。
// arguments 非合法 JSON 时 to_string 兜底(签名仍可比,只是兜底全量)。
// 同时并行抽取回环样本(写/删工具记路径,其他占位),两缓冲同序位对齐。
let (signatures, loop_samples): (Vec<String>, Vec<DestructiveLoopSample>) = {
// 签名样本带写证据标记(写类调用 true),喂 is_repetitive_exploration 做红绿迭代豁免。
// 写类调用同时打写证据标记(is_write_evidence_tool),供 G2 单点死磕的写间隔豁免:
// 红绿迭代(同验证命令反复跑+间插写)=验证行为不算漂移,根治脚本失败致执行中断。
let (signatures, loop_samples): (Vec<(String, bool)>, Vec<DestructiveLoopSample>) = {
let session = session_arc.lock().await;
// BE2只取尾部 BREAKER_TAIL_N 条(签名抽取只需最近几轮工具调用,
// 全量 clone 纯浪费;STALL_BREAKER_SAMPLE_SIZE=12 ≪ 40,样本充足)。
@@ -1955,7 +1960,7 @@ async fn check_stall_breaker(
Some(conv) => conv.messages.recent_messages(BREAKER_TAIL_N),
None => Vec::new(),
};
let mut sigs: Vec<String> = Vec::new();
let mut sigs: Vec<(String, bool)> = Vec::new();
let mut loops: Vec<DestructiveLoopSample> = Vec::new();
for m in messages.iter().rev() {
if sigs.len() >= STALL_BREAKER_SAMPLE_SIZE {
@@ -1972,7 +1977,7 @@ async fn check_stall_breaker(
}
let args: serde_json::Value =
serde_json::from_str(&tc.function.arguments).unwrap_or(serde_json::Value::Null);
sigs.push(tool_call_signature(&tc.function.name, &args));
sigs.push((tool_call_signature(&tc.function.name, &args), is_write_evidence_tool(&tc.function.name)));
loops.push(destructive_loop_sample(&tc.function.name, &args));
}
}