mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 11:13:43 +08:00
feat(agent): 压缩清理占位符信息化——保留工具名/原始大小/首行要点,便于模型判断是否需要重跑
This commit is contained in:
parent
36d1f1027d
commit
727adcd24b
@ -1027,6 +1027,33 @@ public class ConversationWindowManager {
|
||||
+ "' can be called again if its result is needed.]";
|
||||
}
|
||||
|
||||
/**
|
||||
* One-line informative summary for a cleared tool result: tool name,
|
||||
* original size, and the first line as a gist. Far more useful to the
|
||||
* model than a bare "removed" marker — it can decide whether re-running
|
||||
* the tool is worth it without guessing what the output was.
|
||||
*/
|
||||
static String buildInformativeCleared(String toolName, String body) {
|
||||
String safeName = (toolName == null || toolName.isBlank()) ? "tool" : toolName;
|
||||
int length = body == null ? 0 : body.length();
|
||||
String gist = "";
|
||||
if (body != null) {
|
||||
for (String line : body.split("\n", 8)) {
|
||||
String candidate = line.strip();
|
||||
if (!candidate.isEmpty()) {
|
||||
gist = candidate.length() > 80 ? candidate.substring(0, 80) + "…" : candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
StringBuilder sb = new StringBuilder("[").append(safeName)
|
||||
.append(" → ").append(length).append(" chars, cleared to save context");
|
||||
if (!gist.isEmpty()) {
|
||||
sb.append("; began: \"").append(gist).append('"');
|
||||
}
|
||||
return sb.append("; call the tool again if the result is still needed]").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 1 - Soft trim:对工具结果做 head+tail 裁剪(保留首尾各 200 字符)。
|
||||
* <p>Spill-marker responses are left untouched so their on-disk pointer
|
||||
@ -1081,7 +1108,8 @@ public class ConversationWindowManager {
|
||||
replaced.add(r);
|
||||
continue;
|
||||
}
|
||||
replaced.add(new ToolResponseMessage.ToolResponse(r.id(), r.name(), "[tool result removed]"));
|
||||
replaced.add(new ToolResponseMessage.ToolResponse(r.id(), r.name(),
|
||||
buildInformativeCleared(r.name(), r.responseData())));
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
|
||||
@ -71,9 +71,12 @@ class ConversationWindowManagerSpillMarkerPreservationTest {
|
||||
ToolResponseMessage trm0 = (ToolResponseMessage) messages.get(0);
|
||||
ToolResponseMessage trm1 = (ToolResponseMessage) messages.get(1);
|
||||
assertEquals(SPILL_BODY, trm0.getResponses().getFirst().responseData(),
|
||||
"Phase 2 hard clear must not replace a spill-marker body with [tool result removed]");
|
||||
assertEquals("[tool result removed]", trm1.getResponses().getFirst().responseData(),
|
||||
"Phase 2 hard clear must not replace a spill-marker body with the cleared placeholder");
|
||||
String clearedBody = trm1.getResponses().getFirst().responseData();
|
||||
assertTrue(clearedBody.contains("cleared to save context"),
|
||||
"non-spill bodies should still be replaced by Phase 2");
|
||||
assertTrue(clearedBody.contains("search") && clearedBody.contains("2000 chars"),
|
||||
"the cleared placeholder should carry the tool name and original size");
|
||||
assertEquals(1, cleared,
|
||||
"clear counter should reflect only the non-spill body that was actually replaced");
|
||||
}
|
||||
@ -120,7 +123,7 @@ class ConversationWindowManagerSpillMarkerPreservationTest {
|
||||
ToolResponseMessage trm = (ToolResponseMessage) messages.getFirst();
|
||||
assertEquals(SPILL_BODY, trm.getResponses().get(0).responseData(),
|
||||
"the spill response in a mixed message must survive Phase 2");
|
||||
assertEquals("[tool result removed]", trm.getResponses().get(1).responseData(),
|
||||
assertTrue(trm.getResponses().get(1).responseData().contains("cleared to save context"),
|
||||
"the non-spill response in a mixed message must still be cleared");
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
package vip.mate.agent.context;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests for the informative cleared-tool-output placeholder — name, size and
|
||||
* first-line gist survive the clearing so the model can judge whether a
|
||||
* re-run is worthwhile.
|
||||
*/
|
||||
class InformativeClearedPlaceholderTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("placeholder carries tool name, original size and first-line gist")
|
||||
void carriesNameSizeAndGist() {
|
||||
String body = "total 47 tests, 0 failures\n<full test output...>";
|
||||
String placeholder = ConversationWindowManager.buildInformativeCleared("run_tests", body);
|
||||
assertTrue(placeholder.contains("run_tests"));
|
||||
assertTrue(placeholder.contains(body.length() + " chars"));
|
||||
assertTrue(placeholder.contains("total 47 tests, 0 failures"));
|
||||
assertTrue(placeholder.contains("call the tool again"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("long first line is capped at 80 chars")
|
||||
void longGistCapped() {
|
||||
String body = "x".repeat(500);
|
||||
String placeholder = ConversationWindowManager.buildInformativeCleared("read_file", body);
|
||||
assertTrue(placeholder.contains("x".repeat(80) + "…"));
|
||||
assertFalse(placeholder.contains("x".repeat(81)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("null / blank bodies degrade gracefully")
|
||||
void nullAndBlankBodies() {
|
||||
String placeholder = ConversationWindowManager.buildInformativeCleared(null, null);
|
||||
assertTrue(placeholder.contains("tool"));
|
||||
assertTrue(placeholder.contains("0 chars"));
|
||||
assertFalse(placeholder.contains("began:"));
|
||||
|
||||
String blank = ConversationWindowManager.buildInformativeCleared("shell", "\n\n \n");
|
||||
assertFalse(blank.contains("began:"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user