Adds @vectorize-io/hindsight-chat, a wrapper for the Vercel Chat SDK that gives any chat bot (Slack, Discord, Teams, etc.) long-term memory via Hindsight. Includes withHindsightChat() handler wrapper with auto-recall, auto-retain, and memoriesAsSystemPrompt() formatting. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
127 lines
4 KiB
TypeScript
127 lines
4 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { formatMemoriesAsSystemPrompt } from './format.js';
|
|
import type { RecallResult, EntityState } from './types.js';
|
|
|
|
function makeMemory(overrides: Partial<RecallResult> = {}): RecallResult {
|
|
return {
|
|
id: 'mem-1',
|
|
text: 'User prefers dark mode',
|
|
type: 'experience',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeEntities(): Record<string, EntityState> {
|
|
return {
|
|
'ent-1': {
|
|
entity_id: 'ent-1',
|
|
canonical_name: 'Alice',
|
|
observations: [
|
|
{ text: 'Works at Acme Corp' },
|
|
{ text: 'Prefers TypeScript', mentioned_at: '2025-01-01T00:00:00Z' },
|
|
],
|
|
},
|
|
};
|
|
}
|
|
|
|
describe('formatMemoriesAsSystemPrompt', () => {
|
|
it('returns empty string when no memories and no entities', () => {
|
|
expect(formatMemoriesAsSystemPrompt([], null)).toBe('');
|
|
expect(formatMemoriesAsSystemPrompt([], {})).toBe('');
|
|
expect(formatMemoriesAsSystemPrompt([], undefined)).toBe('');
|
|
});
|
|
|
|
it('formats memories with default preamble', () => {
|
|
const result = formatMemoriesAsSystemPrompt(
|
|
[makeMemory(), makeMemory({ id: 'mem-2', text: 'Likes coffee', type: 'world' })],
|
|
null
|
|
);
|
|
|
|
expect(result).toContain(
|
|
'You have access to the following memories about this user'
|
|
);
|
|
expect(result).toContain('<memories>');
|
|
expect(result).toContain('- User prefers dark mode [experience]');
|
|
expect(result).toContain('- Likes coffee [world]');
|
|
expect(result).toContain('</memories>');
|
|
expect(result).not.toContain('<entity_observations>');
|
|
});
|
|
|
|
it('formats memories without type suffix when type is null', () => {
|
|
const result = formatMemoriesAsSystemPrompt(
|
|
[makeMemory({ type: null })],
|
|
null
|
|
);
|
|
expect(result).toContain('- User prefers dark mode\n');
|
|
expect(result).not.toContain('[');
|
|
});
|
|
|
|
it('includes entity observations', () => {
|
|
const result = formatMemoriesAsSystemPrompt(
|
|
[makeMemory()],
|
|
makeEntities()
|
|
);
|
|
|
|
expect(result).toContain('<memories>');
|
|
expect(result).toContain('<entity_observations>');
|
|
expect(result).toContain('## Alice');
|
|
expect(result).toContain('- Works at Acme Corp');
|
|
expect(result).toContain('- Prefers TypeScript');
|
|
expect(result).toContain('</entity_observations>');
|
|
});
|
|
|
|
it('shows only entities when no memories', () => {
|
|
const result = formatMemoriesAsSystemPrompt([], makeEntities());
|
|
|
|
expect(result).not.toContain('<memories>');
|
|
expect(result).toContain('<entity_observations>');
|
|
expect(result).toContain('## Alice');
|
|
});
|
|
|
|
it('uses custom preamble', () => {
|
|
const result = formatMemoriesAsSystemPrompt(
|
|
[makeMemory()],
|
|
null,
|
|
{ preamble: 'Here is what I know:' }
|
|
);
|
|
expect(result.startsWith('Here is what I know:')).toBe(true);
|
|
});
|
|
|
|
it('limits memories with maxMemories', () => {
|
|
const memories = [
|
|
makeMemory({ id: '1', text: 'First' }),
|
|
makeMemory({ id: '2', text: 'Second' }),
|
|
makeMemory({ id: '3', text: 'Third' }),
|
|
];
|
|
const result = formatMemoriesAsSystemPrompt(memories, null, {
|
|
maxMemories: 2,
|
|
});
|
|
expect(result).toContain('First');
|
|
expect(result).toContain('Second');
|
|
expect(result).not.toContain('Third');
|
|
});
|
|
|
|
it('filters by includeTypes', () => {
|
|
const memories = [
|
|
makeMemory({ id: '1', text: 'World fact', type: 'world' }),
|
|
makeMemory({ id: '2', text: 'Experience', type: 'experience' }),
|
|
makeMemory({ id: '3', text: 'Observation', type: 'observation' }),
|
|
];
|
|
const result = formatMemoriesAsSystemPrompt(memories, null, {
|
|
includeTypes: ['world', 'observation'],
|
|
});
|
|
expect(result).toContain('World fact');
|
|
expect(result).not.toContain('Experience');
|
|
expect(result).toContain('Observation');
|
|
});
|
|
|
|
it('excludes entities when includeEntities is false', () => {
|
|
const result = formatMemoriesAsSystemPrompt(
|
|
[makeMemory()],
|
|
makeEntities(),
|
|
{ includeEntities: false }
|
|
);
|
|
expect(result).toContain('<memories>');
|
|
expect(result).not.toContain('<entity_observations>');
|
|
});
|
|
});
|