diff --git a/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java b/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java index 168c25bd..5d3648a7 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java @@ -710,7 +710,8 @@ public class AgentGraphBuilder { ObservationProcessor observationProcessor = new ObservationProcessor(graphObservationProperties); ObservationNode observationNode = new ObservationNode(observationProcessor, streamTracker); SummarizingNode summarizingNode = new SummarizingNode(chatModel, streamingHelper, streamTracker); - LimitExceededNode limitExceededNode = new LimitExceededNode(chatModel, observationProcessor, streamingHelper, i18nService); + LimitExceededNode limitExceededNode = new LimitExceededNode( + chatModel, observationProcessor, streamingHelper, i18nService, progressLedgerService); FinalAnswerNode finalAnswerNode = new FinalAnswerNode(generatedFileCache); KeyStrategyFactory keyStrategyFactory = KeyStrategy.builder() diff --git a/mateclaw-server/src/main/java/vip/mate/agent/graph/node/LimitExceededNode.java b/mateclaw-server/src/main/java/vip/mate/agent/graph/node/LimitExceededNode.java index f5da9d95..3346b387 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/graph/node/LimitExceededNode.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/graph/node/LimitExceededNode.java @@ -47,18 +47,33 @@ public class LimitExceededNode implements NodeAction { private final NodeStreamingChatHelper streamingHelper; /** Optional i18n service; nullable so legacy/tests without Spring context still work. */ private final I18nService i18n; + /** + * Optional ledger loader. When set, the conversation's progress snapshot + * (done / in-progress / pending) is appended to the LLM's context so the + * "graceful wrap-up" answer can be honest about which steps actually + * finished and which were still pending when the iteration cap hit. + * Null in legacy/test constructors — the wrap behaves as before. + */ + private final vip.mate.agent.progress.ProgressLedgerService progressLedgerService; public LimitExceededNode(ChatModel chatModel, ObservationProcessor observationProcessor, NodeStreamingChatHelper streamingHelper) { - this(chatModel, observationProcessor, streamingHelper, null); + this(chatModel, observationProcessor, streamingHelper, null, null); } public LimitExceededNode(ChatModel chatModel, ObservationProcessor observationProcessor, NodeStreamingChatHelper streamingHelper, I18nService i18n) { + this(chatModel, observationProcessor, streamingHelper, i18n, null); + } + + public LimitExceededNode(ChatModel chatModel, ObservationProcessor observationProcessor, + NodeStreamingChatHelper streamingHelper, I18nService i18n, + vip.mate.agent.progress.ProgressLedgerService progressLedgerService) { this.chatModel = chatModel; this.observationProcessor = observationProcessor; this.streamingHelper = streamingHelper; this.i18n = i18n; + this.progressLedgerService = progressLedgerService; } /** @@ -66,7 +81,7 @@ public class LimitExceededNode implements NodeAction { */ @Deprecated public LimitExceededNode(ChatModel chatModel, ObservationProcessor observationProcessor) { - this(chatModel, observationProcessor, null, null); + this(chatModel, observationProcessor, null, null, null); } @Override @@ -100,6 +115,25 @@ public class LimitExceededNode implements NodeAction { contextForLLM = i18n != null ? i18n.msg("agent.limit_exceeded.empty_context") : "(no tool results)"; } + // Prepend the conversation's progress ledger snapshot when available + // so the wrap-up answer can be honest about partial completion ("4/10 + // models researched, 6 still pending") rather than vaguely describing + // "what I tried". Without this, hitting the iteration cap on a + // 10-step task produces a useless catch-all message — observed in + // round-4 of the LLM-review smoke test. + String ledgerSnapshot = null; + if (progressLedgerService != null && conversationId != null && !conversationId.isBlank()) { + try { + ledgerSnapshot = progressLedgerService.load(conversationId).renderSnapshot(); + } catch (Exception e) { + log.warn("[LimitExceededNode] Failed to load progress ledger for {}: {}", + conversationId, e.getMessage()); + } + } + if (ledgerSnapshot != null) { + contextForLLM = ledgerSnapshot + "\n\n---\n\n" + contextForLLM; + } + // 构建 prompt String systemPrompt = SYSTEM_TEMPLATE.replace("{maxIterations}", String.valueOf(maxIterations)); String userPrompt = USER_TEMPLATE diff --git a/mateclaw-server/src/main/resources/prompts/graph/limit-exceeded-system.txt b/mateclaw-server/src/main/resources/prompts/graph/limit-exceeded-system.txt index fd8f7d26..3393eee1 100644 --- a/mateclaw-server/src/main/resources/prompts/graph/limit-exceeded-system.txt +++ b/mateclaw-server/src/main/resources/prompts/graph/limit-exceeded-system.txt @@ -5,6 +5,7 @@ 1. 给出简洁、诚实、可执行的回答 2. 若信息不足以完全回答,明确说明哪些部分还不确定 2.1. 若所依据的数据被截断或不完整(含"已截断/TRUNCATED/preview/INCOMPLETE"等标记),明确告知用户数据不完整并指出缺口,**绝不编造、补全或重新编号缺失的数据条目**,也不要把明细列表缩减为范围表述 +2.2. 如果上下文里出现"## 当前任务进度"账本,**以账本为权威完成度** —— done 的步骤真的做完了,pending / in_progress 的步骤明确告诉用户"未完成",并把这些条目列出来作为下一步建议 3. 如果还有未完成的方向,简要列出建议的下一步 4. **不要为「未完成」道歉,不要提及「推理步数」「迭代次数」「工具调用上限」这类技术细节** —— 直接给结论 5. 保持输出简洁,避免重复已知内容