gijl/lv-storage / llama.cpp /tools /ui /tests /unit /compaction.test.ts
gijl's picture
download
raw
9.41 kB
import { COMPACTION } from '$lib/constants';
import { MessageRole, MessageType } from '$lib/enums';
import type { DatabaseMessage } from '$lib/types';
import {
canCompactMessages,
compactionCut,
compactionTokensBefore,
sliceAtLastCompaction
} from '$lib/utils';
import { describe, expect, it } from 'vitest';
const CONV_ID = 'conv';
function makeMessage(
id: string,
role: MessageRole,
content: string,
type: MessageType = MessageType.TEXT
): DatabaseMessage {
return {
children: [],
content,
convId: CONV_ID,
id,
parent: null,
role,
timestamp: 0,
type
} as DatabaseMessage;
}
function makeCompactionNode(id: string, summary: string, kept?: number): DatabaseMessage {
return { ...makeMessage(id, MessageRole.USER, summary, MessageType.COMPACTION), kept };
}
describe('sliceAtLastCompaction', () => {
it('returns the list unchanged when no compaction node is present', () => {
const messages = [
makeMessage('u1', MessageRole.USER, 'hi'),
makeMessage('a1', MessageRole.ASSISTANT, 'hello')
];
expect(sliceAtLastCompaction(messages)).toBe(messages);
});
it('drops messages before the boundary and keeps the system prefix', () => {
const system = makeMessage('s1', MessageRole.SYSTEM, 'be brief', MessageType.SYSTEM);
const messages = [
system,
makeMessage('u1', MessageRole.USER, 'old question'),
makeMessage('a1', MessageRole.ASSISTANT, 'old answer'),
makeCompactionNode('c1', 'summary'),
makeMessage('u2', MessageRole.USER, 'new question')
];
const sliced = sliceAtLastCompaction(messages);
expect(sliced.map((m) => m.id)).toEqual(['s1', 'c1', 'u2']);
expect(sliced[0]).toBe(system);
});
it('frames the compaction node content without mutating the original', () => {
const node = makeCompactionNode('c1', 'summary text');
const messages = [makeMessage('u1', MessageRole.USER, 'question'), node];
const sliced = sliceAtLastCompaction(messages);
const framed = COMPACTION.CONTEXT_TEMPLATE.replace(
COMPACTION.SUMMARY_PLACEHOLDER,
() => 'summary text'
);
expect(sliced[0].content).toBe(framed);
expect(sliced[0].role).toBe(MessageRole.USER);
expect(node.content).toBe('summary text');
});
it('frames a summary as written, whatever it holds', () => {
const summary = "echo $HOME $& $$ $'";
const sliced = sliceAtLastCompaction([makeCompactionNode('c1', summary)]);
const [before, after] = COMPACTION.CONTEXT_TEMPLATE.split(COMPACTION.SUMMARY_PLACEHOLDER);
expect(sliced[0].content).toBe(before + summary + after);
});
it('slices at the last boundary when the branch was compacted twice', () => {
const messages = [
makeCompactionNode('c1', 'first summary'),
makeMessage('u1', MessageRole.USER, 'question'),
makeMessage('a1', MessageRole.ASSISTANT, 'answer'),
makeCompactionNode('c2', 'second summary'),
makeMessage('u2', MessageRole.USER, 'follow-up')
];
const sliced = sliceAtLastCompaction(messages);
expect(sliced.map((m) => m.id)).toEqual(['c2', 'u2']);
});
it('ignores an empty node left by an interrupted compaction', () => {
const messages = [
makeMessage('u1', MessageRole.USER, 'question'),
makeMessage('a1', MessageRole.ASSISTANT, 'answer'),
makeCompactionNode('c1', '')
];
expect(sliceAtLastCompaction(messages)).toBe(messages);
});
});
describe('sliceAtLastCompaction with kept steps', () => {
const ids = (messages: DatabaseMessage[]) => messages.map((m) => m.id);
it('keeps the last steps before the node, a tool result with its call', () => {
const messages = [
makeMessage('s1', MessageRole.SYSTEM, 'be brief', MessageType.SYSTEM),
makeMessage('u1', MessageRole.USER, 'first'),
makeMessage('a1', MessageRole.ASSISTANT, 'calling'),
makeMessage('t1', MessageRole.TOOL, 'result'),
makeMessage('u2', MessageRole.USER, 'second'),
makeMessage('a2', MessageRole.ASSISTANT, 'answer'),
makeCompactionNode('c1', 'summary', 3),
makeMessage('u3', MessageRole.USER, 'next')
];
expect(ids(sliceAtLastCompaction(messages))).toEqual([
's1',
'c1',
'a1',
't1',
'u2',
'a2',
'u3'
]);
});
it('counts the kept steps across an older node, which it drops', () => {
const messages = [
makeMessage('u1', MessageRole.USER, 'first'),
makeMessage('a1', MessageRole.ASSISTANT, 'one'),
makeCompactionNode('c1', 'first summary', 1),
makeMessage('u2', MessageRole.USER, 'second'),
makeMessage('a2', MessageRole.ASSISTANT, 'two'),
makeCompactionNode('c2', 'second summary', 3),
makeMessage('u3', MessageRole.USER, 'next')
];
expect(ids(sliceAtLastCompaction(messages))).toEqual(['c2', 'a1', 'u2', 'a2', 'u3']);
});
it('keeps every step when it keeps more than there are, and none without kept', () => {
const messages = [
makeMessage('u1', MessageRole.USER, 'first'),
makeMessage('a1', MessageRole.ASSISTANT, 'one'),
makeCompactionNode('c1', 'summary', 9)
];
expect(ids(sliceAtLastCompaction(messages))).toEqual(['c1', 'u1', 'a1']);
expect(
ids(sliceAtLastCompaction([...messages.slice(0, 2), makeCompactionNode('c1', 's')]))
).toEqual(['c1']);
});
});
describe('compactionCut', () => {
const view = [
makeMessage('s1', MessageRole.SYSTEM, 'be brief', MessageType.SYSTEM),
makeMessage('u1', MessageRole.USER, 'first'),
makeMessage('a1', MessageRole.ASSISTANT, 'calling'),
makeMessage('t1', MessageRole.TOOL, 'result'),
makeMessage('u2', MessageRole.USER, 'second'),
makeMessage('a2', MessageRole.ASSISTANT, 'answer')
];
it('cuts before the preserved steps, never between a call and its result', () => {
expect(compactionCut(view, 2)).toBe(4);
expect(compactionCut(view, 3)).toBe(2);
expect(compactionCut(view, 0)).toBe(view.length);
});
it('cuts nowhere when no step lies before the preserved ones', () => {
expect(compactionCut(view, 4)).toBeNull();
expect(compactionCut(view, 5)).toBeNull();
});
it('leaves the preserved steps as the model reads them once compacted', () => {
const cut = compactionCut(view, 2)!;
const compacted = sliceAtLastCompaction([...view, makeCompactionNode('c1', 'summary', 2)]);
expect(compacted.slice(2)).toEqual(view.slice(cut));
expect(compacted.slice(0, 2).map((m) => m.id)).toEqual(['s1', 'c1']);
});
});
describe('canCompactMessages', () => {
it('rejects a branch with no step before the preserved ones', () => {
expect(canCompactMessages([makeMessage('u1', MessageRole.USER, 'hi')], 1)).toBe(false);
});
it('accepts a branch with a step before the preserved ones', () => {
const messages = [
makeMessage('u1', MessageRole.USER, 'hi'),
makeMessage('a1', MessageRole.ASSISTANT, 'hello')
];
expect(canCompactMessages(messages, 1)).toBe(true);
expect(canCompactMessages(messages, 2)).toBe(false);
});
it('rejects a branch with nothing new since the last compaction', () => {
const messages = [
makeMessage('u1', MessageRole.USER, 'hi'),
makeMessage('a1', MessageRole.ASSISTANT, 'hello'),
makeCompactionNode('c1', 'summary', 1)
];
expect(canCompactMessages(messages, 1)).toBe(false);
});
it('accepts a compacted branch once a new step exists past the preserved ones', () => {
const messages = [
makeCompactionNode('c1', 'summary'),
makeMessage('u1', MessageRole.USER, 'next'),
makeMessage('a1', MessageRole.ASSISTANT, 'reply')
];
expect(canCompactMessages(messages, 1)).toBe(true);
});
it('accepts a branch whose trailing compaction node is empty', () => {
const messages = [
makeMessage('u1', MessageRole.USER, 'hi'),
makeMessage('a1', MessageRole.ASSISTANT, 'hello'),
makeCompactionNode('c1', '')
];
expect(canCompactMessages(messages, 1)).toBe(true);
});
});
describe('compactionTokensBefore', () => {
function withTimings(
message: DatabaseMessage,
timings: { cache_n?: number; predicted_n?: number; prompt_n?: number }
): DatabaseMessage {
return { ...message, timings } as DatabaseMessage;
}
it('reports the context the branch carried before the compaction', () => {
const previous = withTimings(makeMessage('a1', MessageRole.ASSISTANT, 'hello'), {
cache_n: 200,
predicted_n: 118,
prompt_n: 42000
});
const node = makeCompactionNode('c1', 'summary');
expect(compactionTokensBefore([previous, node], node)).toBe(42318);
});
it('ignores the node own stamped timings', () => {
const previous = withTimings(makeMessage('a1', MessageRole.ASSISTANT, 'hello'), {
prompt_n: 42000
});
const node = withTimings(makeCompactionNode('c1', 'summary'), { prompt_n: 3204 });
expect(compactionTokensBefore([previous, node], node)).toBe(42000);
});
it('reports nothing while no message ahead of the node is stamped', () => {
const previous = makeMessage('a1', MessageRole.ASSISTANT, 'hello');
const node = withTimings(makeCompactionNode('c1', 'summary'), { prompt_n: 3204 });
expect(compactionTokensBefore([previous, node], node)).toBeNull();
});
it('reports nothing when the node is the first message', () => {
expect(
compactionTokensBefore(
[makeCompactionNode('c1', 'summary')],
makeCompactionNode('c1', 'summary')
)
).toBeNull();
});
it('falls back to the previous compaction node when no turn follows it', () => {
const earlier = withTimings(makeCompactionNode('c0', 'older'), { prompt_n: 2800 });
const node = makeCompactionNode('c1', 'summary');
expect(compactionTokensBefore([earlier, node], node)).toBe(2800);
});
});

Xet Storage Details

Size:
9.41 kB
·
Xet hash:
fe6303dcab50988e34026e5f77a5b8c9a5b54047066c2bde6016f3d0cce4fc3c

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.