Files
TORCH/test/memory-retriever.test.mjs
T

206 lines
5.7 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { filterAndRankMemories } from '../src/services/memory/retriever.js';
import {
formatMemoriesForPrompt,
isMemoryRetrievalEnabled,
renderPromptWithMemoryContext,
} from '../src/services/memory/formatter.js';
function createMemory(overrides = {}) {
return {
schema_version: 1,
id: overrides.id ?? crypto.randomUUID(),
agent_id: overrides.agent_id ?? 'agent-1',
session_id: overrides.session_id ?? 's1',
type: overrides.type ?? 'event',
content: overrides.content ?? 'default content',
summary: overrides.summary ?? 'default summary',
tags: overrides.tags ?? [],
importance: overrides.importance ?? 0.5,
embedding_id: overrides.embedding_id ?? null,
created_at: overrides.created_at ?? 1000,
last_seen: overrides.last_seen ?? 1000,
source: overrides.source ?? 'ingest',
ttl_days: overrides.ttl_days ?? null,
merged_into: overrides.merged_into ?? null,
pinned: overrides.pinned ?? false,
};
}
test('filterAndRankMemories applies metadata filters and composite score', async () => {
const now = 10_000;
const memories = [
createMemory({
id: 'm1',
content: 'billing incident root cause',
summary: 'billing incident',
importance: 0.9,
tags: ['incident', 'billing'],
created_at: 9000,
last_seen: 9500,
pinned: true,
}),
createMemory({
id: 'm2',
content: 'billing note',
summary: 'routine billing note',
importance: 0.2,
tags: ['billing'],
created_at: 8000,
last_seen: 8000,
pinned: false,
}),
createMemory({
id: 'm3',
content: 'other agent record',
summary: 'not for this agent',
agent_id: 'agent-2',
tags: ['incident', 'billing'],
}),
];
const vectorAdapter = {
async embedText() {
return [1, 0];
},
async queryVector() {
return [
{ id: 'm1', score: 0.95 },
{ id: 'm2', score: 0.8 },
];
},
};
const ranked = await filterAndRankMemories(memories, {
agent_id: 'agent-1',
query: 'billing incident',
tags: ['billing'],
timeframe: { from: 7000, to: 10_000 },
pinnedPreference: 'prefer',
k: 5,
now,
vectorAdapter,
});
assert.deepEqual(ranked.map((item) => item.id), ['m1', 'm2']);
const pinnedOnly = await filterAndRankMemories(memories, {
agent_id: 'agent-1',
query: 'billing',
pinnedPreference: 'only',
k: 5,
now,
vectorAdapter,
});
assert.deepEqual(pinnedOnly.map((item) => item.id), ['m1']);
});
test('formatMemoriesForPrompt returns prompt blocks and enforces token budget trimming', () => {
const memories = [
createMemory({
id: 'm1',
created_at: 1_700_000_000_000,
importance: 0.95,
summary: 'A'.repeat(200),
content: 'B'.repeat(500),
tags: ['incident', 'urgent'],
}),
createMemory({
id: 'm2',
created_at: 1_700_000_100_000,
importance: 0.7,
summary: 'second',
content: 'short context',
tags: ['ops'],
}),
];
const result = formatMemoriesForPrompt(memories, {
tokenBudget: 70,
k: 2,
});
assert.ok(result.blocks.length >= 1);
assert.ok(result.blocks.length <= 2);
assert.ok(result.usedTokens <= 70);
assert.ok(result.text.includes('[m1,'));
if (result.blocks.length === 1) {
assert.equal(result.truncated, true);
}
});
test('isMemoryRetrievalEnabled supports global toggle and agent canary list', () => {
assert.equal(isMemoryRetrievalEnabled('agent-a', { TORCH_MEMORY_RETRIEVAL_ENABLED: 'true' }), true);
assert.equal(isMemoryRetrievalEnabled('agent-a', { TORCH_MEMORY_RETRIEVAL_ENABLED: 'false' }), false);
assert.equal(
isMemoryRetrievalEnabled('agent-a', { TORCH_MEMORY_RETRIEVAL_ENABLED: 'agent-a,agent-b' }),
true,
);
assert.equal(
isMemoryRetrievalEnabled('agent-c', { TORCH_MEMORY_RETRIEVAL_ENABLED: 'agent-a,agent-b' }),
false,
);
assert.equal(
isMemoryRetrievalEnabled('agent-a', {
TORCH_MEMORY_ENABLED: 'false',
TORCH_MEMORY_RETRIEVAL_ENABLED: 'true',
}),
false,
);
});
test('renderPromptWithMemoryContext injects bounded memory block before base prompt', async () => {
const memories = [
createMemory({
id: 'm-ctx',
summary: 'Known issue in deploy pipeline',
content: 'Retry build after rotating temporary token',
created_at: 1_700_000_000_000,
importance: 0.91,
tags: ['deploy', 'incident'],
}),
];
const calls = [];
const usageUpdates = [];
const memoryService = {
async getRelevantMemories(params) {
calls.push(params);
return memories;
},
async updateMemoryUsage(id, lastSeen) {
usageUpdates.push({ id, lastSeen });
},
};
const output = await renderPromptWithMemoryContext({
basePrompt: 'Base final prompt body',
userRequest: 'Please debug deployment failure',
agentContext: { branch: 'main', tool: 'torch' },
agent_id: 'agent-a',
memoryService,
retrievalK: 1,
tokenBudget: 60,
env: { TORCH_MEMORY_RETRIEVAL_ENABLED: 'agent-a,agent-b' },
});
assert.equal(calls.length, 1);
assert.equal(calls[0].agent_id, 'agent-a');
assert.equal(calls[0].k, 1);
assert.match(calls[0].query, /Please debug deployment failure/);
assert.match(calls[0].query, /"branch":"main"/);
const headerIndex = output.indexOf('[CONTEXT — Relevant memories]');
const baseIndex = output.indexOf('Base final prompt body');
assert.ok(headerIndex >= 0);
assert.ok(baseIndex > headerIndex);
assert.ok(output.includes('[m-ctx,'));
assert.equal(usageUpdates.length, 1);
assert.equal(usageUpdates[0].id, 'm-ctx');
assert.equal(typeof usageUpdates[0].lastSeen, 'number');
});