TalkingBot/chat-engine/tests/prompts.test.js

106 lines
4.3 KiB
JavaScript

const { normalizeRefs } = require('../src/prompt/normalizeRefs');
const { buildFullContext, buildHybrid, prefixBeforeCheckpoint } = require('../src/prompt/build');
const { sampleBook } = require('./fixture');
const chapters = [
{ chapterNo: 0, title: 'Introduction', startPage: 1, endPage: 1 },
{ chapterNo: 1, title: 'Opening', startPage: 2, endPage: 2 },
{ chapterNo: 3, title: 'Workshop', startPage: 3, endPage: 4 },
];
describe('normalizeRefs', () => {
it('maps spoken chapter references', () => {
expect(normalizeRefs('summarize chapter three', chapters).hints[0].chapterNo).toBe(3);
expect(normalizeRefs('the third chapter', chapters).hints[0].chapterNo).toBe(3);
expect(normalizeRefs('chapter III', chapters).hints[0].chapterNo).toBe(3);
expect(normalizeRefs('the last chapter', chapters).hints[0].chapterNo).toBe(3);
expect(normalizeRefs('first chapter', chapters).hints[0].chapterNo).toBe(0);
expect(normalizeRefs('the introduction', chapters).hints[0].chapterNo).toBe(0);
});
});
describe('prompt prefix', () => {
it('keeps the cached prefix identical and puts hints after it', async () => {
const logger = { info() {}, warn() {} };
const book = sampleBook();
const first = await buildFullContext({
persona: 'companion',
channel: 'chat',
userText: 'hello',
book,
history: [],
rollingSummary: '',
cacheMinTokens: 4096,
cacheTtl: '5m',
logger,
});
const second = await buildFullContext({
persona: 'companion',
channel: 'chat',
userText: 'summarize chapter three',
book,
history: [{ role: 'user', text: 'earlier' }, { role: 'assistant', text: 'answer' }],
rollingSummary: 'old',
cacheMinTokens: 4096,
cacheTtl: '5m',
logger,
});
const prefixA = JSON.stringify(prefixBeforeCheckpoint(first.system));
const prefixB = JSON.stringify(prefixBeforeCheckpoint(second.system));
expect(prefixA).toBe(prefixB);
expect(prefixA).not.toContain('chapter three');
expect(JSON.stringify(second.system)).toContain('chapter three');
expect(second.system.some((block) => block.cachePoint && block.cachePoint.type === 'default')).toBe(true);
});
it('skips the checkpoint when the prefix is short', async () => {
const events = [];
const logger = { info(obj, msg) { events.push(msg || obj); }, warn() {} };
const built = await buildFullContext({
persona: 'companion',
channel: 'chat',
userText: 'hello',
book: sampleBook('Hi'),
history: [],
rollingSummary: '',
cacheMinTokens: 4096,
cacheTtl: '5m',
logger,
});
expect(built.system.some((block) => block.cachePoint)).toBe(false);
expect(events).toContain('cache_skipped_below_min');
});
it('keeps source-marker rules inside the cached prefix for both channels', async () => {
const logger = { info() {}, warn() {} };
const book = sampleBook();
const base = {
persona: 'companion',
userText: 'hello',
book,
history: [],
rollingSummary: '',
cacheMinTokens: 4096,
cacheTtl: '5m',
logger,
};
const chat = await buildFullContext({ ...base, channel: 'chat' });
const voice = await buildFullContext({ ...base, channel: 'voice' });
const chatPrefix = JSON.stringify(prefixBeforeCheckpoint(chat.system));
const voicePrefix = JSON.stringify(prefixBeforeCheckpoint(voice.system));
expect(chatPrefix).toBe(voicePrefix);
expect(chatPrefix).toContain('[[p:47]]');
expect(chatPrefix).not.toContain('Written reply style');
expect(chatPrefix).not.toContain('Spoken reply style');
expect(JSON.stringify(chat.system)).toContain('Markdown is allowed');
expect(JSON.stringify(voice.system)).toContain('in chapter three');
const hybridChat = await buildHybrid({ ...base, channel: 'chat' });
const hybridVoice = await buildHybrid({ ...base, channel: 'voice' });
expect(JSON.stringify(hybridChat.system)).toBe(JSON.stringify(hybridVoice.system));
expect(JSON.stringify(hybridChat.system)).toContain('[[p:47]]');
expect(JSON.stringify(hybridChat.system)).toContain('Cite only pages and chapters');
expect(JSON.stringify(hybridVoice.messages)).toContain('stripped before speech');
expect(JSON.stringify(hybridChat.messages)).toContain('Markdown is allowed');
});
});