Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
111 lines
4.2 KiB
TypeScript
111 lines
4.2 KiB
TypeScript
import { mkdtemp, rm } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { runTrial } from "./evals/runner.js";
|
|
import { startModelEmulator } from "./model-emulator.js";
|
|
|
|
const databaseAvailable = process.env.VERIFY_DATABASE === "1" && Boolean(process.env.DATABASE_URL);
|
|
|
|
describe.skipIf(!databaseAvailable)("eval history accounting", () => {
|
|
it.each([2, 1])(
|
|
"retains cleared-run tool calls with a budget of %s",
|
|
async (maxToolCalls) => {
|
|
const fixtureKey = "offline-eval-history-key";
|
|
const model = await startModelEmulator({
|
|
apiKey: fixtureKey,
|
|
steps: ["first", "second"].flatMap((turn) => [
|
|
{
|
|
expect(request) {
|
|
const messages = JSON.stringify(request.messages);
|
|
expect(messages).toContain(`Save ${turn} turn.`);
|
|
if (turn === "second") expect(messages).not.toContain("Save first turn.");
|
|
},
|
|
response: {
|
|
type: "tool" as const,
|
|
id: `${turn}-write`,
|
|
name: "write_file",
|
|
arguments: { path: `results/${turn}.txt`, content: turn },
|
|
},
|
|
},
|
|
{
|
|
expect(request) {
|
|
const tool = request.messages.findLast((message) => message.role === "tool");
|
|
expect(tool?.tool_call_id).toBe(`${turn}-write`);
|
|
expect(JSON.parse(String(tool?.content))).toMatchObject({ ok: true });
|
|
},
|
|
response: { type: "text" as const, text: `Saved ${turn} turn.` },
|
|
},
|
|
]),
|
|
});
|
|
const dataDir = await mkdtemp(path.join(tmpdir(), "rakazo-eval-history-"));
|
|
try {
|
|
const { createApp } = await import("../../../apps/api/src/app.ts");
|
|
const result = await runTrial(
|
|
{
|
|
id: "cleared-history-fixture",
|
|
purpose: "Keep the full trial budget after clearing the first conversation.",
|
|
steps: [{ ask: "Save first turn." }, { clear: true }, { ask: "Save second turn." }],
|
|
files: ["results/first.txt", "results/second.txt"],
|
|
grade: ({ files }) => [
|
|
{
|
|
id: "both-files-saved",
|
|
pass:
|
|
files["results/first.txt"] === "first" &&
|
|
files["results/second.txt"] === "second",
|
|
},
|
|
],
|
|
},
|
|
1,
|
|
{
|
|
connection: {
|
|
provider: model.model.provider,
|
|
modelId: model.model.id,
|
|
baseUrl: model.baseUrl,
|
|
apiKey: fixtureKey,
|
|
},
|
|
timeoutMs: 20_000,
|
|
maxToolCalls,
|
|
createApp: (composio) =>
|
|
createApp({
|
|
databaseUrl: process.env.DATABASE_URL!,
|
|
realtimeDatabaseUrl: process.env.DATABASE_URL!,
|
|
authUrl: "http://127.0.0.1:5173",
|
|
webOrigin: "http://127.0.0.1:5173",
|
|
dataDir,
|
|
sandboxProvider: "fake",
|
|
agentRuntime: "pi",
|
|
wakeupDriver: "memory",
|
|
signupsEnabled: "true",
|
|
composio,
|
|
encryptionKey: "offline-eval-history-encryption-key",
|
|
}),
|
|
},
|
|
);
|
|
expect(result.cleanupFailed).toBe(false);
|
|
expect(result.toolCalls).toBe(2);
|
|
if (maxToolCalls === 2) {
|
|
model.assertComplete();
|
|
expect(result).toMatchObject({ status: "passed", category: null, reason: null });
|
|
expect(result.trace).toEqual([
|
|
{ step: 1, status: "completed", tools: ["write_file"] },
|
|
{ step: 3, status: "completed", tools: ["write_file"] },
|
|
]);
|
|
} else {
|
|
// Cleanup may abort the last model response as soon as the second tool
|
|
// exceeds the budget, so only the successful journey requires every SSE step.
|
|
expect(result).toMatchObject({
|
|
status: "failed",
|
|
category: "incomplete",
|
|
reason: "Tool call budget exceeded",
|
|
});
|
|
}
|
|
} finally {
|
|
await model.close();
|
|
await rm(dataDir, { recursive: true, force: true });
|
|
}
|
|
},
|
|
30_000,
|
|
);
|
|
});
|