aps-agent/scripts/context-builder.test.mjs

66 lines
3.2 KiB
JavaScript
Raw Permalink Normal View History

/** 上下文构建用例(任务 A3.8):预算、折叠、追溯、钉住引用。 */
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { buildContext } from '@aps/server/app/context-builder';
const message = (id, role, text) => ({ messageId: id, role, ts: 1, text });
test('历史很短时全部保留,不产生摘要', () => {
const messages = [message('m1', 'user', '你好'), message('m2', 'assistant', '你好,有什么可以帮你')];
const result = buildContext({ messages });
assert.ok(result.prefix.includes('【最近的对话】'));
assert.deepEqual(result.includedMessageIds, ['m1', 'm2']);
assert.deepEqual(result.droppedMessageIds, []);
assert.equal(result.summary, '');
});
test('空历史不注入任何前缀', () => {
assert.equal(buildContext({ messages: [] }).prefix, '');
});
test('超预算时老消息折叠进摘要,且能追到原始消息 id', () => {
const messages = [];
for (let index = 0; index < 40; index += 1) {
messages.push(message(`m${index}`, index % 2 === 0 ? 'user' : 'assistant', `第 ${index} 条消息,`.repeat(20)));
}
const result = buildContext({ messages, budget: { maxChars: 1200, recentMessages: 4, summaryMaxChars: 800 } });
assert.ok(result.droppedMessageIds.length > 0, '应有消息被折叠');
assert.equal(result.summarySourceIds.length, result.droppedMessageIds.length, '摘要来源应与折叠集合一致');
assert.ok(result.summary.length > 0);
// 摘要里逐条带来源 id,任何结论都能追回原文
for (const id of result.summarySourceIds.slice(0, 3)) {
assert.ok(result.summary.includes(id), `摘要应包含来源 ${id}`);
}
// 最近若干轮必须原样保留
const lastIds = messages.slice(-4).map((item) => item.messageId);
for (const id of lastIds) assert.ok(result.includedMessageIds.includes(id), `${id} 应保留`);
});
test('工具执行只以引用进摘要,不进历史原文', () => {
const messages = Array.from({ length: 30 }, (_, index) =>
message(`m${index}`, index % 2 === 0 ? 'user' : 'assistant', `内容 ${index} `.repeat(40)),
);
const result = buildContext({
messages,
executions: [
{ capabilityId: 'fake.echo', status: 'succeeded', executionId: 'exec-123', paramsDigest: 'sha256:abc', createdAt: 1 },
],
budget: { maxChars: 900, recentMessages: 2, summaryMaxChars: 600 },
});
assert.ok(result.prefix.includes('【相关工具执行(只给引用,需要细节请调对应能力)】'), result.prefix.slice(0, 200));
assert.ok(result.prefix.includes('exec-123'), '应给出执行号引用');
assert.ok(result.prefix.includes('sha256:abc'), '应给出参数摘要');
assert.ok(!result.prefix.includes('"echo"'), '不应把工具结果原文塞进上下文');
});
test('钉住的消息即使很老也保留', () => {
const messages = Array.from({ length: 30 }, (_, index) =>
message(`m${index}`, index % 2 === 0 ? 'user' : 'assistant', `内容 ${index} `.repeat(40)),
);
const result = buildContext({ messages, pinnedMessageIds: ['m0'], budget: { maxChars: 800, recentMessages: 2, summaryMaxChars: 400 } });
assert.ok(result.includedMessageIds.includes('m0'), '钉住的消息必须保留');
assert.ok(!result.droppedMessageIds.includes('m0'));
});