From 8798668524a43b93a9e60baa8f8f633edce6b306 Mon Sep 17 00:00:00 2001 From: matevip Date: Sun, 24 May 2026 23:00:30 +0800 Subject: [PATCH] feat(agent): require ledger discipline in system prompt for multi-step tasks --- .../mate/agent/graph/node/ReasoningNode.java | 47 +++++++++++++++---- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/agent/graph/node/ReasoningNode.java b/mateclaw-server/src/main/java/vip/mate/agent/graph/node/ReasoningNode.java index 15390d7c..3ee70aed 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/graph/node/ReasoningNode.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/graph/node/ReasoningNode.java @@ -140,15 +140,26 @@ public class ReasoningNode implements NodeAction { + " 请重新调用同一工具但**缩小内容**,或拆成多次顺序调用,**不要改成纯文字回答**。\n" + "- 只在确实没有合适工具,或所有工具步骤都已完成、可以最终回答用户时,\n" + " 才输出无 tool_call 的纯文字回答。\n\n" - + "## 进度跟踪(多步任务必读)\n\n" - + "- 对于包含 ≥3 个独立子步骤的任务(逐项调研、分节起草、批量生成等),\n" - + " 你**应当**使用 `progress_update` 工具维护进度账本:\n" - + " 1. 任务起手时为每个子步骤注册一条 `pending` 条目;\n" - + " 2. 开始执行某条前切到 `in_progress`;\n" - + " 3. 完成后立即切到 `done`;遇到阻塞切到 `blocked` 并写明原因。\n" - + "- 系统会在你的每一次推理前注入一份**当前进度快照**(标题 \"当前任务进度\"),\n" - + " 请把它视为权威的\"已完成清单\",**不要重复执行已经 done 的步骤**。\n" - + "- 单一问题(无须拆解的短任务)不需要使用本工具;用错不会报错,但会浪费一次调用。\n"; + + "## 进度跟踪(多步任务强制规则,不可绕过)\n\n" + + "**触发条件**:用户的任务包含 ≥3 个可枚举子目标 — 比如\n" + + "\"调研 10 个模型\"、\"逐节起草报告\"、\"批量生成 N 份文档\"、\n" + + "\"依次调用 N 个 API\"、\"对每个文件执行同一操作\"等。\n\n" + + "**必须做的事**:\n" + + "1. **第一轮回复就用并行 tool_calls 批量注册全部子目标为 `pending`**\n" + + " 一条回复里 N 个 `progress_update` 同时发出(不要串行)。\n" + + " 例:要调研 10 个模型,第一轮就发 10 个 `progress_update(stepKey=\"model_xxx\", status=\"pending\")`。\n" + + "2. **每开始一个子目标**前发 `progress_update(同 stepKey, status=\"in_progress\")`。\n" + + "3. **每完成一个子目标**后立即发 `progress_update(同 stepKey, status=\"done\")`。\n" + + "4. **无法继续**时发 `progress_update(同 stepKey, status=\"blocked\", note=\"具体原因\")`。\n\n" + + "**为什么必须**:\n" + + "- 系统在你**每一次推理前**注入一份 \"## 当前任务进度\" 快照。\n" + + " 这是你**唯一可信**的\"已完成清单\"——比你记忆里的步骤更权威,因为上下文窗口\n" + + " 会被裁剪,老的工具调用记录会消失,但 ledger 不会。\n" + + "- 不维护 ledger 的后果(实测):\n" + + " · 上下文裁剪后忘记自己做过的步骤,重复执行已完成项 → 浪费迭代预算\n" + + " · 漏做项目 → 任务不完整 → 撞 max_iterations 还没干完\n" + + " · ledger snapshot 永远显示初始状态,对你毫无帮助\n\n" + + "**例外**:单一问题、简单问答、不可拆解的请求 — 不需要用。\n"; private final ChatModel chatModel; private final List toolCallbacks; @@ -511,12 +522,28 @@ public class ReasoningNode implements NodeAction { // history that produced those done entries. Suppressed when the // ledger column is empty so short single-turn questions stay // prompt-cache-friendly. + // + // Past iteration ~10, also emit a stale-reminder SystemMessage when + // the ledger looks abandoned (empty after many turns, or no + // progress_update in >90s). This pushes the model back to the + // ledger discipline before it drifts into the "I'm doing the work + // but never marking it" failure mode observed in round-4 of the + // LLM-review smoke test. if (progressLedgerService != null && conversationId != null && !conversationId.isBlank()) { try { - String snapshot = progressLedgerService.load(conversationId).renderSnapshot(); + vip.mate.agent.progress.ProgressLedger ledger = + progressLedgerService.load(conversationId); + String snapshot = ledger.renderSnapshot(); if (snapshot != null) { nonHistoryPrefix.add(new SystemMessage(snapshot)); } + String staleReminder = ledger.renderStaleReminder( + accessor.iterationCount(), java.time.Instant.now()); + if (staleReminder != null) { + nonHistoryPrefix.add(new SystemMessage(staleReminder)); + log.info("[ReasoningNode] Injected stale-ledger reminder at iter {} for conv {}", + accessor.iterationCount(), conversationId); + } } catch (Exception e) { // Never let a ledger-side failure break the reasoning step. log.warn("[ReasoningNode] Failed to load progress ledger for {}: {}",