mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 20:28:29 +00:00
test(agents): dedupe ping-pong loop test scaffolding
This commit is contained in:
@@ -45,6 +45,50 @@ function recordSuccessfulCall(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function recordSuccessfulPingPongCalls(params: {
|
||||||
|
state: SessionState;
|
||||||
|
readParams: { path: string };
|
||||||
|
listParams: { dir: string };
|
||||||
|
count: number;
|
||||||
|
textAtIndex: (toolName: "read" | "list", index: number) => string;
|
||||||
|
}) {
|
||||||
|
for (let i = 0; i < params.count; i += 1) {
|
||||||
|
if (i % 2 === 0) {
|
||||||
|
recordSuccessfulCall(
|
||||||
|
params.state,
|
||||||
|
"read",
|
||||||
|
params.readParams,
|
||||||
|
{ content: [{ type: "text", text: params.textAtIndex("read", i) }], details: { ok: true } },
|
||||||
|
i,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
recordSuccessfulCall(
|
||||||
|
params.state,
|
||||||
|
"list",
|
||||||
|
params.listParams,
|
||||||
|
{ content: [{ type: "text", text: params.textAtIndex("list", i) }], details: { ok: true } },
|
||||||
|
i,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectPingPongLoop(
|
||||||
|
loopResult: ReturnType<typeof detectToolCallLoop>,
|
||||||
|
expected: { level: "warning" | "critical"; count: number; expectCriticalText?: boolean },
|
||||||
|
) {
|
||||||
|
expect(loopResult.stuck).toBe(true);
|
||||||
|
if (!loopResult.stuck) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(loopResult.level).toBe(expected.level);
|
||||||
|
expect(loopResult.detector).toBe("ping_pong");
|
||||||
|
expect(loopResult.count).toBe(expected.count);
|
||||||
|
if (expected.expectCriticalText) {
|
||||||
|
expect(loopResult.message).toContain("CRITICAL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("tool-loop-detection", () => {
|
describe("tool-loop-detection", () => {
|
||||||
describe("hashToolCall", () => {
|
describe("hashToolCall", () => {
|
||||||
it("creates consistent hash for same tool and params", () => {
|
it("creates consistent hash for same tool and params", () => {
|
||||||
@@ -356,11 +400,8 @@ describe("tool-loop-detection", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loopResult = detectToolCallLoop(state, "list", listParams, enabledLoopDetectionConfig);
|
const loopResult = detectToolCallLoop(state, "list", listParams, enabledLoopDetectionConfig);
|
||||||
expect(loopResult.stuck).toBe(true);
|
expectPingPongLoop(loopResult, { level: "warning", count: WARNING_THRESHOLD });
|
||||||
if (loopResult.stuck) {
|
if (loopResult.stuck) {
|
||||||
expect(loopResult.level).toBe("warning");
|
|
||||||
expect(loopResult.detector).toBe("ping_pong");
|
|
||||||
expect(loopResult.count).toBe(WARNING_THRESHOLD);
|
|
||||||
expect(loopResult.message).toContain("ping-pong loop");
|
expect(loopResult.message).toContain("ping-pong loop");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -370,33 +411,21 @@ describe("tool-loop-detection", () => {
|
|||||||
const readParams = { path: "/a.txt" };
|
const readParams = { path: "/a.txt" };
|
||||||
const listParams = { dir: "/workspace" };
|
const listParams = { dir: "/workspace" };
|
||||||
|
|
||||||
for (let i = 0; i < CRITICAL_THRESHOLD - 1; i += 1) {
|
recordSuccessfulPingPongCalls({
|
||||||
if (i % 2 === 0) {
|
state,
|
||||||
recordSuccessfulCall(
|
readParams,
|
||||||
state,
|
listParams,
|
||||||
"read",
|
count: CRITICAL_THRESHOLD - 1,
|
||||||
readParams,
|
textAtIndex: (toolName) => (toolName === "read" ? "read stable" : "list stable"),
|
||||||
{ content: [{ type: "text", text: "read stable" }], details: { ok: true } },
|
});
|
||||||
i,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
recordSuccessfulCall(
|
|
||||||
state,
|
|
||||||
"list",
|
|
||||||
listParams,
|
|
||||||
{ content: [{ type: "text", text: "list stable" }], details: { ok: true } },
|
|
||||||
i,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loopResult = detectToolCallLoop(state, "list", listParams, enabledLoopDetectionConfig);
|
const loopResult = detectToolCallLoop(state, "list", listParams, enabledLoopDetectionConfig);
|
||||||
expect(loopResult.stuck).toBe(true);
|
expectPingPongLoop(loopResult, {
|
||||||
|
level: "critical",
|
||||||
|
count: CRITICAL_THRESHOLD,
|
||||||
|
expectCriticalText: true,
|
||||||
|
});
|
||||||
if (loopResult.stuck) {
|
if (loopResult.stuck) {
|
||||||
expect(loopResult.level).toBe("critical");
|
|
||||||
expect(loopResult.detector).toBe("ping_pong");
|
|
||||||
expect(loopResult.count).toBe(CRITICAL_THRESHOLD);
|
|
||||||
expect(loopResult.message).toContain("CRITICAL");
|
|
||||||
expect(loopResult.message).toContain("ping-pong loop");
|
expect(loopResult.message).toContain("ping-pong loop");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -406,33 +435,16 @@ describe("tool-loop-detection", () => {
|
|||||||
const readParams = { path: "/a.txt" };
|
const readParams = { path: "/a.txt" };
|
||||||
const listParams = { dir: "/workspace" };
|
const listParams = { dir: "/workspace" };
|
||||||
|
|
||||||
for (let i = 0; i < CRITICAL_THRESHOLD - 1; i += 1) {
|
recordSuccessfulPingPongCalls({
|
||||||
if (i % 2 === 0) {
|
state,
|
||||||
recordSuccessfulCall(
|
readParams,
|
||||||
state,
|
listParams,
|
||||||
"read",
|
count: CRITICAL_THRESHOLD - 1,
|
||||||
readParams,
|
textAtIndex: (toolName, index) => `${toolName} ${index}`,
|
||||||
{ content: [{ type: "text", text: `read ${i}` }], details: { ok: true } },
|
});
|
||||||
i,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
recordSuccessfulCall(
|
|
||||||
state,
|
|
||||||
"list",
|
|
||||||
listParams,
|
|
||||||
{ content: [{ type: "text", text: `list ${i}` }], details: { ok: true } },
|
|
||||||
i,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loopResult = detectToolCallLoop(state, "list", listParams, enabledLoopDetectionConfig);
|
const loopResult = detectToolCallLoop(state, "list", listParams, enabledLoopDetectionConfig);
|
||||||
expect(loopResult.stuck).toBe(true);
|
expectPingPongLoop(loopResult, { level: "warning", count: CRITICAL_THRESHOLD });
|
||||||
if (loopResult.stuck) {
|
|
||||||
expect(loopResult.level).toBe("warning");
|
|
||||||
expect(loopResult.detector).toBe("ping_pong");
|
|
||||||
expect(loopResult.count).toBe(CRITICAL_THRESHOLD);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not flag ping-pong when alternation is broken", () => {
|
it("does not flag ping-pong when alternation is broken", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user