修复: 断路器失败判定纯结构化(失败 content 包 JSON envelope,is_failure_content 只读字段不解析内容文本)
This commit is contained in:
@@ -1722,13 +1722,22 @@ async fn count_recent_failures(
|
|||||||
.unwrap_or((0u32, String::new()))
|
.unwrap_or((0u32, String::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── is_failure_content: 失败判定(禁止/跳过重试/失败/Error 关键词) ──
|
// ── is_failure_content: 纯结构化判定(只读字段,绝不解析内容文本) ──
|
||||||
|
// 原理:工具成败是执行层的结构化事实(exit_code / status),断路器只读字段。内容文本(无论含
|
||||||
|
// error/失败/任何词)绝不参与判定 —— 这样读含 error 字样的代码、搜"失败"的结果等成功工具内容
|
||||||
|
// 永远不会被判失败(根除 conv 09e7abfa 的误熔断)。
|
||||||
|
// 失败 content 由 audit/mod.rs 包成 JSON envelope {"status":"failed","error":...};
|
||||||
|
// 成功 content 是 result JSON(run_command 含 exit_code,其他工具无 status 字段即成功)或 namespace:// 占位符。
|
||||||
|
// 非 JSON(namespace 占位符 / 重试 guard skip 纯文本)→ 无结构化失败信号 → 不判(false)。
|
||||||
fn is_failure_content(content: &str) -> bool {
|
fn is_failure_content(content: &str) -> bool {
|
||||||
content.starts_with("禁止")
|
let Ok(v) = serde_json::from_str::<serde_json::Value>(content) else {
|
||||||
|| content.starts_with("已跳过重试")
|
return false; // namespace:// 占位符 / 重试 guard skip 等 → 无结构化失败信号,不判
|
||||||
|| content.contains("失败")
|
};
|
||||||
|| content.contains("Error")
|
if let Some(exit) = v.get("exit_code").and_then(|x| x.as_i64()) {
|
||||||
|| content.contains("error")
|
return exit != 0; // run_command 等:非零退出 = 真失败
|
||||||
|
}
|
||||||
|
matches!(v.get("status").and_then(|s| s.as_str()), Some("failed") | Some("error"))
|
||||||
|
|| v.get("error").is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── summarize_tool_results: tool_result view-only 摘要压缩(扁平抽自原嵌套 map 块) ──
|
// ── summarize_tool_results: tool_result view-only 摘要压缩(扁平抽自原嵌套 map 块) ──
|
||||||
|
|||||||
@@ -503,7 +503,12 @@ pub(crate) async fn process_tool_calls(
|
|||||||
{
|
{
|
||||||
let mut session = session_arc.lock().await;
|
let mut session = session_arc.lock().await;
|
||||||
// T2: 大工具结果进 namespace
|
// T2: 大工具结果进 namespace
|
||||||
let msg_content = if df_ai::namespace_store::should_use_namespace(&content, &draft.name) {
|
// 失败包结构化 envelope {"status":"failed","error":...}:让断路器按 status 判
|
||||||
|
// (不解析内容文本,理论可靠)。emit 给前端的 result 与 audit 仍用原 err_msg 纯文本
|
||||||
|
// (前端卡片/审计可读,与断路器信号解耦)。
|
||||||
|
let msg_content = if status == "failed" {
|
||||||
|
serde_json::json!({"status":"failed","error":&content}).to_string()
|
||||||
|
} else if df_ai::namespace_store::should_use_namespace(&content, &draft.name) {
|
||||||
session.namespace_store.store(&draft.name, &content)
|
session.namespace_store.store(&draft.name, &content)
|
||||||
} else {
|
} else {
|
||||||
content.clone()
|
content.clone()
|
||||||
@@ -622,7 +627,12 @@ pub(crate) async fn process_tool_calls(
|
|||||||
{
|
{
|
||||||
let mut session = session_arc.lock().await;
|
let mut session = session_arc.lock().await;
|
||||||
// T2: 大工具结果进 namespace,主队列只留引用
|
// T2: 大工具结果进 namespace,主队列只留引用
|
||||||
let msg_content = if df_ai::namespace_store::should_use_namespace(&content, &draft.name) {
|
// 失败包结构化 envelope {"status":"failed","error":...}:让断路器按 status 判
|
||||||
|
// (不解析内容文本,理论可靠)。emit 给前端的 result 与 audit 仍用原 err_msg 纯文本
|
||||||
|
// (前端卡片/审计可读,与断路器信号解耦)。
|
||||||
|
let msg_content = if status == "failed" {
|
||||||
|
serde_json::json!({"status":"failed","error":&content}).to_string()
|
||||||
|
} else if df_ai::namespace_store::should_use_namespace(&content, &draft.name) {
|
||||||
session.namespace_store.store(&draft.name, &content)
|
session.namespace_store.store(&draft.name, &content)
|
||||||
} else {
|
} else {
|
||||||
content.clone()
|
content.clone()
|
||||||
|
|||||||
Reference in New Issue
Block a user