mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(agent): preserve tool use after compaction
This commit is contained in:
parent
5e96dade9c
commit
ce7f0847fb
@ -895,7 +895,13 @@ public abstract class BaseAgent {
|
||||
}
|
||||
return switch (message.getRole()) {
|
||||
case "assistant" -> new AssistantMessage(renderedContent);
|
||||
case "system" -> new SystemMessage(renderedContent);
|
||||
case "system" -> isCompressionSummary(message)
|
||||
// Compression boundaries are persisted as system rows so
|
||||
// the loader can find the latest boundary cheaply. They
|
||||
// are still model-generated history context, not durable
|
||||
// instructions, so replay them at user priority.
|
||||
? new UserMessage(renderedContent)
|
||||
: new SystemMessage(renderedContent);
|
||||
// History user messages: text only. Re-injecting Media on every replay
|
||||
// accumulates attachments across turns — many providers cap at 1 video
|
||||
// per request (e.g. Zhipu GLM-5V returns code 1210). The current turn
|
||||
|
||||
@ -138,9 +138,8 @@ public class ReasoningNode implements NodeAction {
|
||||
|
||||
/** Continuation nudge appended to the prompt when the model returns an empty turn. */
|
||||
private static final String EMPTY_COMPLETION_NUDGE =
|
||||
"Your previous turn was empty. If the task is not yet complete, continue now "
|
||||
+ "with the next concrete step — call a tool or write the next part. If every "
|
||||
+ "required step is already done, output the final answer to the user now.";
|
||||
"上一轮回复为空。如果任务尚未完成,请现在继续执行下一个具体步骤:"
|
||||
+ "调用工具或写出下一部分。如果所有必要步骤都已完成,请立即输出面向用户的最终答复。";
|
||||
|
||||
/**
|
||||
* Continuation nudge for the most common premature-stop pattern: an empty
|
||||
@ -291,6 +290,12 @@ public class ReasoningNode implements NodeAction {
|
||||
+ "- 最终回答中引用的状态数值与查询时间必须来自本轮工具返回,时间基准以运行时上下文注入的当前时间为准。\n"
|
||||
+ "- 注意:\"历史里查过\"不等于\"本轮已查\"。宣称已重新查询但未在本轮实际发出对应 tool_call,视为违规。\n";
|
||||
|
||||
private static final String LANGUAGE_CONSISTENCY_GUARD = "\n\n"
|
||||
+ "## 语言一致性(强制)\n\n"
|
||||
+ "- 用户使用中文时,所有可见思考、过程说明、最终答复都必须使用简体中文。\n"
|
||||
+ "- 不要用英文书写可见思考或推理过程;代码、工具名、参数名、API 字段和专有名词可以保留原文。\n"
|
||||
+ "- 如果工具结果或历史内容是英文,你可以阅读它,但面向用户展示的解释和推理必须翻译/转述为用户语言。\n";
|
||||
|
||||
private static final String GROUNDED_CONTRACT = "\n\n"
|
||||
+ "## 回答来源约束(强制规则)\n\n"
|
||||
+ "**核心原则**:你的回答必须完全基于工具返回的信息(证据),不得使用内部知识编造内容。\n\n"
|
||||
@ -304,8 +309,8 @@ public class ReasoningNode implements NodeAction {
|
||||
+ "5. **内容忠实**:必须准确反映证据内容,不得歪曲、编造或过度推断。\n\n"
|
||||
+ "**违规后果**:未按规则引用来源或使用未验证的信息将导致回答被拒绝。\n";
|
||||
|
||||
private static String buildGroundedSystemPrompt(String basePrompt, boolean groundingEnforced) {
|
||||
String prompt = basePrompt + TOOL_USE_ENFORCEMENT + STALE_CONTEXT_GUARD;
|
||||
static String buildGroundedSystemPrompt(String basePrompt, boolean groundingEnforced) {
|
||||
String prompt = basePrompt + TOOL_USE_ENFORCEMENT + STALE_CONTEXT_GUARD + LANGUAGE_CONSISTENCY_GUARD;
|
||||
return groundingEnforced ? prompt + GROUNDED_CONTRACT : prompt;
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.ToolResponseMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import vip.mate.workspace.conversation.ConversationService;
|
||||
@ -545,6 +546,26 @@ class BaseAgentToolCallReplayTest {
|
||||
assertEquals("hi", ((UserMessage) out.get(0)).getText());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("E2E: compression summaries replay as user context, not system instructions")
|
||||
void e2e_compressionSummaryDemotedFromSystem() {
|
||||
TestAgent agent = newTestAgent();
|
||||
MessageEntity entity = new MessageEntity();
|
||||
entity.setId(109L);
|
||||
entity.setRole("system");
|
||||
entity.setContent("[上下文压缩] 请继续执行取消接口,不要声称已完成。");
|
||||
entity.setMetadata("{\"type\":\"compression_summary\",\"compressedCount\":12}");
|
||||
when(agent.conversationService.renderMessageContent(entity)).thenReturn(entity.getContent());
|
||||
|
||||
List<Message> out = agent.callExpand(entity);
|
||||
|
||||
assertEquals(1, out.size());
|
||||
assertTrue(out.get(0) instanceof UserMessage,
|
||||
"compression summaries are model-generated history context and must not become system instructions");
|
||||
assertFalse(out.get(0) instanceof SystemMessage);
|
||||
assertEquals(entity.getContent(), ((UserMessage) out.get(0)).getText());
|
||||
}
|
||||
|
||||
// ---------- Test scaffold ----------
|
||||
|
||||
private static TestAgent newTestAgent() {
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
package vip.mate.agent.graph.node;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ReasoningNodePromptLanguageTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("runtime prompt constrains visible thinking to the user's language")
|
||||
void groundedPromptIncludesVisibleThinkingLanguageRule() {
|
||||
String prompt = ReasoningNode.buildGroundedSystemPrompt("基础提示", false);
|
||||
|
||||
assertTrue(prompt.contains("可见思考"));
|
||||
assertTrue(prompt.contains("用户语言"));
|
||||
assertTrue(prompt.contains("简体中文"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("empty-completion nudge is Chinese so it does not bias thinking into English")
|
||||
void emptyCompletionNudgeIsChinese() throws Exception {
|
||||
Field field = ReasoningNode.class.getDeclaredField("EMPTY_COMPLETION_NUDGE");
|
||||
field.setAccessible(true);
|
||||
String nudge = (String) field.get(null);
|
||||
|
||||
assertFalse(nudge.contains("Your previous turn was empty"));
|
||||
assertFalse(nudge.contains("continue now"));
|
||||
assertTrue(nudge.contains("上一轮"));
|
||||
assertTrue(nudge.contains("调用工具"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user