CI / checks (push) Has been cancelled
Cap get_api_docs, break duplicate tool rounds, force a reply after successful writes, and log each tool call so limit hits are diagnosable.
153 lines
4.6 KiB
TypeScript
153 lines
4.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import {
|
|
fingerprintToolCalls,
|
|
isSuccessfulWrite,
|
|
summarizeToolTrace,
|
|
truncateToolResult,
|
|
} from "../../src/modules/agent/server/loop-guards";
|
|
import { runAgentChat } from "../../src/modules/agent/server/run";
|
|
import type {
|
|
ChatCompletionRequest,
|
|
ChatCompletionResult,
|
|
LlmClient,
|
|
} from "../../src/lib/llm/types";
|
|
|
|
describe("loop-guards", () => {
|
|
it("fingerprints tool calls stably regardless of key order", () => {
|
|
const a = fingerprintToolCalls([
|
|
{ name: "create_event", arguments: '{"title":"Dentist","calendarName":"Family"}' },
|
|
]);
|
|
const b = fingerprintToolCalls([
|
|
{ name: "create_event", arguments: '{"calendarName":"Family","title":"Dentist"}' },
|
|
]);
|
|
assert.equal(a, b);
|
|
});
|
|
|
|
it("treats create_event 201 as a successful write", () => {
|
|
assert.equal(isSuccessfulWrite("create_event", 201), true);
|
|
assert.equal(isSuccessfulWrite("list_calendars", 200), false);
|
|
assert.equal(
|
|
isSuccessfulWrite("call_api", 201, '{"method":"POST","path":"/api/v1/events"}'),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
isSuccessfulWrite("call_api", 200, '{"method":"GET","path":"/api/v1/events"}'),
|
|
false,
|
|
);
|
|
});
|
|
|
|
it("truncates oversized tool results", () => {
|
|
const result = truncateToolResult("x".repeat(7000), 100);
|
|
assert.ok(result.length < 200);
|
|
assert.match(result, /truncated/);
|
|
});
|
|
|
|
it("summarizes tool traces", () => {
|
|
assert.equal(
|
|
summarizeToolTrace([
|
|
{ name: "list_calendars", status: 200 },
|
|
{ name: "create_event", status: 201 },
|
|
]),
|
|
"list_calendars→200, create_event→201",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("runAgentChat loop guards", () => {
|
|
it("stops after a successful write instead of looping", async () => {
|
|
let calls = 0;
|
|
const llm: LlmClient = {
|
|
async chatCompletion(request: ChatCompletionRequest): Promise<ChatCompletionResult> {
|
|
calls += 1;
|
|
if (calls === 1) {
|
|
return {
|
|
message: {
|
|
role: "assistant",
|
|
content: null,
|
|
tool_calls: [
|
|
{
|
|
id: "1",
|
|
type: "function",
|
|
function: {
|
|
name: "create_event",
|
|
arguments: JSON.stringify({
|
|
title: "Dentist",
|
|
startAt: "2026-07-10T15:00:00.000Z",
|
|
endAt: "2026-07-10T16:00:00.000Z",
|
|
}),
|
|
},
|
|
},
|
|
],
|
|
},
|
|
finishReason: "tool_calls",
|
|
};
|
|
}
|
|
|
|
assert.equal(request.tools, undefined);
|
|
return {
|
|
message: { role: "assistant", content: "Added Dentist to your calendar." },
|
|
finishReason: "stop",
|
|
};
|
|
},
|
|
};
|
|
|
|
const result = await runAgentChat({
|
|
messages: [{ role: "user", content: "add dentist tomorrow at 10" }],
|
|
request: new Request("http://localhost:3000/api/agent/chat"),
|
|
llm,
|
|
executeTool: async () =>
|
|
JSON.stringify({ status: 201, body: { id: "evt-1", title: "Dentist" } }),
|
|
});
|
|
|
|
assert.equal(calls, 2);
|
|
assert.equal(result.toolCalls.length, 1);
|
|
assert.equal(result.toolCalls[0]?.name, "create_event");
|
|
assert.match(result.message.content, /Dentist/);
|
|
});
|
|
|
|
it("breaks duplicate identical tool rounds", async () => {
|
|
let calls = 0;
|
|
const llm: LlmClient = {
|
|
async chatCompletion(): Promise<ChatCompletionResult> {
|
|
calls += 1;
|
|
if (calls <= 2) {
|
|
return {
|
|
message: {
|
|
role: "assistant",
|
|
content: null,
|
|
tool_calls: [
|
|
{
|
|
id: String(calls),
|
|
type: "function",
|
|
function: {
|
|
name: "list_calendars",
|
|
arguments: "{}",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
finishReason: "tool_calls",
|
|
};
|
|
}
|
|
return {
|
|
message: { role: "assistant", content: "You have one Family calendar." },
|
|
finishReason: "stop",
|
|
};
|
|
},
|
|
};
|
|
|
|
const result = await runAgentChat({
|
|
messages: [{ role: "user", content: "what calendars do I have?" }],
|
|
request: new Request("http://localhost:3000/api/agent/chat"),
|
|
llm,
|
|
executeTool: async () =>
|
|
JSON.stringify({ status: 200, body: [{ id: "cal-1", name: "Family" }] }),
|
|
});
|
|
|
|
assert.equal(calls, 3);
|
|
assert.equal(result.toolCalls.length, 1);
|
|
assert.match(result.message.content, /Family/);
|
|
});
|
|
});
|