Initial commit: Brachnha Insight project setup
- Next.js 14+ with App Router and TypeScript - Tailwind CSS and ShadCN UI styling - Zustand state management - Dexie.js for IndexedDB (local-first data) - Auth.js v5 for authentication - BMAD framework integration Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
430
src/lib/llm/prompt-engine.test.ts
Normal file
430
src/lib/llm/prompt-engine.test.ts
Normal file
@@ -0,0 +1,430 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { generateTeacherPrompt, generateGhostwriterPrompt } from './prompt-engine';
|
||||
import type { ChatMessage } from '../db';
|
||||
|
||||
describe('Prompt Engine - Teacher Agent', () => {
|
||||
describe('generateTeacherPrompt - Venting Intent', () => {
|
||||
it('should generate empathetic venting prompt with user input', () => {
|
||||
const userInput = "I'm so frustrated with this bug";
|
||||
const chatHistory: ChatMessage[] = [];
|
||||
const intent = 'venting';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result).toContain(userInput);
|
||||
expect(result.toLowerCase()).toContain('empath');
|
||||
expect(result.toLowerCase()).toContain('validate');
|
||||
});
|
||||
|
||||
it('should include probing question instruction for venting', () => {
|
||||
const userInput = "This keeps breaking";
|
||||
const chatHistory: ChatMessage[] = [];
|
||||
const intent = 'venting';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result.toLowerCase()).toContain('probing');
|
||||
expect(result.toLowerCase()).toContain('question');
|
||||
});
|
||||
|
||||
it('should be supportive and encouraging for venting', () => {
|
||||
const userInput = "I don't understand why this isn't working";
|
||||
const chatHistory: ChatMessage[] = [];
|
||||
const intent = 'venting';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result.toLowerCase()).toContain('supportive');
|
||||
expect(result.toLowerCase()).toContain('encourag');
|
||||
});
|
||||
|
||||
it('should specify concise response (2-3 sentences) for venting', () => {
|
||||
const userInput = "Stuck on this problem";
|
||||
const chatHistory: ChatMessage[] = [];
|
||||
const intent = 'venting';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result).toContain('2-3 sentences');
|
||||
});
|
||||
|
||||
it('should include chat history when available for venting', () => {
|
||||
const userInput = "Still having issues";
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Having trouble with React hooks', timestamp: Date.now() },
|
||||
{ role: 'assistant', content: 'What specific hook are you struggling with?', timestamp: Date.now() },
|
||||
];
|
||||
const intent = 'venting';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result).toContain('Previous context');
|
||||
expect(result).toContain('React hooks');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateTeacherPrompt - Insight Intent', () => {
|
||||
it('should generate curious insight prompt with user input', () => {
|
||||
const userInput = "I finally understand closures";
|
||||
const chatHistory: ChatMessage[] = [];
|
||||
const intent = 'insight';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result).toContain(userInput);
|
||||
expect(result.toLowerCase()).toContain('curious');
|
||||
expect(result.toLowerCase()).toContain('celebrate');
|
||||
});
|
||||
|
||||
it('should include deepening question instruction for insight', () => {
|
||||
const userInput = "The key was using useMemo";
|
||||
const chatHistory: ChatMessage[] = [];
|
||||
const intent = 'insight';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result.toLowerCase()).toContain('deepen');
|
||||
expect(result.toLowerCase()).toContain('expand');
|
||||
});
|
||||
|
||||
it('should be encouraging for insight', () => {
|
||||
const userInput = "It just clicked";
|
||||
const chatHistory: ChatMessage[] = [];
|
||||
const intent = 'insight';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result.toLowerCase()).toContain('encourag');
|
||||
});
|
||||
|
||||
it('should specify concise response (2-3 sentences) for insight', () => {
|
||||
const userInput = "Solved the problem";
|
||||
const chatHistory: ChatMessage[] = [];
|
||||
const intent = 'insight';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result).toContain('2-3 sentences');
|
||||
});
|
||||
|
||||
it('should include chat history when available for insight', () => {
|
||||
const userInput = "Got it working now";
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Trying to fix the async issue', timestamp: Date.now() },
|
||||
{ role: 'assistant', content: 'Have you tried using await?', timestamp: Date.now() },
|
||||
];
|
||||
const intent = 'insight';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result).toContain('Previous context');
|
||||
expect(result).toContain('async');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateTeacherPrompt - Edge Cases', () => {
|
||||
it('should handle empty chat history gracefully', () => {
|
||||
const userInput = "Help me debug";
|
||||
const chatHistory: ChatMessage[] = [];
|
||||
const intent = 'venting';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result).toContain('Previous context:');
|
||||
expect(result).toContain('(No previous messages)');
|
||||
});
|
||||
|
||||
it('should handle very long user input', () => {
|
||||
const userInput = "I".repeat(1000) + " frustrated";
|
||||
const chatHistory: ChatMessage[] = [];
|
||||
const intent = 'venting';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
expect(result).toContain('...');
|
||||
});
|
||||
|
||||
it('should handle multiple messages in chat history', () => {
|
||||
const userInput = "Still confused";
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'First question', timestamp: Date.now() },
|
||||
{ role: 'assistant', content: 'First answer', timestamp: Date.now() },
|
||||
{ role: 'user', content: 'Second question', timestamp: Date.now() },
|
||||
{ role: 'assistant', content: 'Second answer', timestamp: Date.now() },
|
||||
];
|
||||
const intent = 'venting';
|
||||
|
||||
const result = generateTeacherPrompt(userInput, chatHistory, intent);
|
||||
|
||||
expect(result).toContain('First question');
|
||||
expect(result).toContain('Second question');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateTeacherPrompt - Format and Structure', () => {
|
||||
it('should return non-empty string', () => {
|
||||
const result = generateTeacherPrompt("Test", [], 'venting');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should include system role definition', () => {
|
||||
const result = generateTeacherPrompt("Test", [], 'venting');
|
||||
expect(result).toContain('You are');
|
||||
});
|
||||
|
||||
it('should be clearly formatted for LLM consumption', () => {
|
||||
const result = generateTeacherPrompt("Test input", [], 'insight');
|
||||
expect(result).toMatch(/user input|your role|response/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Prompt Engine - Ghostwriter Agent', () => {
|
||||
describe('generateGhostwriterPrompt - Basic Functionality', () => {
|
||||
it('should generate Ghostwriter prompt with chat history', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'I struggled with dependency injection today', timestamp: Date.now() },
|
||||
{ role: 'assistant', content: 'What aspect was challenging?', timestamp: Date.now() },
|
||||
{ role: 'user', content: 'Understanding how to inject dependencies without tight coupling', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'venting');
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should include Ghostwriter Agent role definition', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'I learned something cool about React', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'insight');
|
||||
|
||||
expect(result).toContain('Ghostwriter');
|
||||
expect(result.toLowerCase()).toContain('agent');
|
||||
});
|
||||
|
||||
it('should include intent context in prompt', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Frustrated with debugging', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'venting');
|
||||
|
||||
expect(result).toContain('venting');
|
||||
});
|
||||
|
||||
it('should handle insight intent context', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Finally understand closures', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'insight');
|
||||
|
||||
expect(result).toContain('insight');
|
||||
});
|
||||
|
||||
it('should handle unknown intent gracefully', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Some message', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, undefined);
|
||||
|
||||
expect(result).toContain('unknown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateGhostwriterPrompt - Output Format', () => {
|
||||
it('should specify Markdown output format', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Learned about React hooks', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'insight');
|
||||
|
||||
expect(result.toLowerCase()).toContain('markdown');
|
||||
expect(result.toLowerCase()).toContain('title');
|
||||
expect(result.toLowerCase()).toContain('body');
|
||||
expect(result.toLowerCase()).toContain('tags');
|
||||
});
|
||||
|
||||
it('should include code block format specification', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Struggled with async/await', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'venting');
|
||||
|
||||
expect(result).toContain('```');
|
||||
});
|
||||
|
||||
it('should specify professional LinkedIn-style tone', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Had a breakthrough', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'insight');
|
||||
|
||||
expect(result.toLowerCase()).toContain('professional');
|
||||
expect(result.toLowerCase()).toContain('linkedin');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateGhostwriterPrompt - Content Requirements', () => {
|
||||
it('should include constraint to avoid hallucinations', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Fixed a bug', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'insight');
|
||||
|
||||
expect(result.toLowerCase()).toContain('hallucinat');
|
||||
});
|
||||
|
||||
it('should specify grounded in user input requirement', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Learned TypeScript', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'insight');
|
||||
|
||||
expect(result.toLowerCase()).toContain('grounded');
|
||||
expect(result.toLowerCase()).toContain('user input');
|
||||
});
|
||||
|
||||
it('should include transformation focus instruction', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'This was hard but I learned a lot', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'venting');
|
||||
|
||||
expect(result.toLowerCase()).toContain('transformation');
|
||||
});
|
||||
|
||||
it('should specify word count constraint', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Understanding React now', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'insight');
|
||||
|
||||
expect(result).toMatch(/\d{2,3}\s*words/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateGhostwriterPrompt - Edge Cases', () => {
|
||||
it('should handle empty chat history gracefully', () => {
|
||||
const chatHistory: ChatMessage[] = [];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'venting');
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should handle very long chat history', () => {
|
||||
const longHistory: ChatMessage[] = Array.from({ length: 50 }, (_, i) => ({
|
||||
role: i % 2 === 0 ? 'user' : 'assistant',
|
||||
content: `Message ${i} with some content to make it longer`,
|
||||
timestamp: Date.now() + i * 1000,
|
||||
}));
|
||||
|
||||
const result = generateGhostwriterPrompt(longHistory, 'venting');
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
// Should truncate to avoid token limits
|
||||
expect(result.length).toBeLessThan(10000);
|
||||
});
|
||||
|
||||
it('should handle short chat history', () => {
|
||||
const shortHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Hi', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(shortHistory, 'venting');
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should handle chat history with only user messages', () => {
|
||||
const userOnlyHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'First thought', timestamp: Date.now() },
|
||||
{ role: 'user', content: 'Second thought', timestamp: Date.now() + 1000 },
|
||||
{ role: 'user', content: 'Third thought', timestamp: Date.now() + 2000 },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(userOnlyHistory, 'venting');
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateGhostwriterPrompt - Intent-Specific Behavior', () => {
|
||||
it('should emphasize reframing struggle for venting intent', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'This error is driving me crazy', timestamp: Date.now() },
|
||||
{ role: 'assistant', content: 'What error are you seeing?', timestamp: Date.now() },
|
||||
{ role: 'user', content: 'Cannot read property of undefined', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'venting');
|
||||
|
||||
expect(result.toLowerCase()).toContain('struggle');
|
||||
expect(result.toLowerCase()).toContain('lesson');
|
||||
});
|
||||
|
||||
it('should emphasize articulating breakthrough for insight intent', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'I finally got it!', timestamp: Date.now() },
|
||||
{ role: 'assistant', content: 'What did you discover?', timestamp: Date.now() },
|
||||
{ role: 'user', content: 'The key was understanding the render cycle', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'insight');
|
||||
|
||||
expect(result.toLowerCase()).toContain('breakthrough');
|
||||
expect(result.toLowerCase()).toContain('articulat');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateGhostwriterPrompt - Structure and Formatting', () => {
|
||||
it('should have clear section headers', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Test content', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'venting');
|
||||
|
||||
expect(result).toMatch(/context|requirements|output format/i);
|
||||
});
|
||||
|
||||
it('should be properly formatted for LLM consumption', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'Test message', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'venting');
|
||||
|
||||
// Should have numbered lists or clear structure
|
||||
expect(result).toMatch(/\d+\./);
|
||||
});
|
||||
|
||||
it('should include chat history formatted properly', () => {
|
||||
const chatHistory: ChatMessage[] = [
|
||||
{ role: 'user', content: 'User message here', timestamp: Date.now() },
|
||||
{ role: 'assistant', content: 'Teacher response here', timestamp: Date.now() },
|
||||
];
|
||||
|
||||
const result = generateGhostwriterPrompt(chatHistory, 'venting');
|
||||
|
||||
expect(result).toContain('Chat History');
|
||||
expect(result).toContain('User message here');
|
||||
expect(result).toContain('Teacher response here');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user