meezan / src /core /selfReflection.ts
agenthinkmesh's picture
Upload project files
de07704 verified
Raw History Blame Contribute Delete
10.6 kB
// ==========================================
// MEEZAN - ميزان Self-Reflection Engine
// After each session, Meezan reviews itself
// and generates lessons to improve
// Based on the "Reflexion" AI technique
// ==========================================
import Anthropic from '@anthropic-ai/sdk';
import * as fs from 'fs';
import * as path from 'path';
import { ReflectionNote, LearnedLesson, FeedbackSignal, KnowledgeGap } from '../types';
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
interface ConversationToReflect {
userId: string;
messages: Array<{ role: string; content: string }>;
feedbackSignals: FeedbackSignal[];
knowledgeGaps: KnowledgeGap[];
}
export class SelfReflectionEngine {
private dbPath: string;
private reflections: ReflectionNote[];
private lessons: LearnedLesson[];
private reflectionQueue: ConversationToReflect[];
constructor(dbPath: string = './data/reflections.json') {
this.dbPath = dbPath;
this.reflections = [];
this.lessons = [];
this.reflectionQueue = [];
this.load();
// Run reflection every 30 minutes on queued conversations
setInterval(() => this.processQueue(), 30 * 60 * 1000);
}
/**
* Queue a conversation for later reflection
* (non-blocking — runs in background)
*/
queueForReflection(conversation: ConversationToReflect): void {
this.reflectionQueue.push(conversation);
// Process immediately if queue gets large
if (this.reflectionQueue.length >= 5) {
this.processQueue();
}
}
/**
* Process queued conversations — runs in background
*/
async processQueue(): Promise<void> {
if (this.reflectionQueue.length === 0) return;
const toProcess = this.reflectionQueue.splice(0, 3); // Process 3 at a time
for (const conversation of toProcess) {
try {
await this.reflectOnConversation(conversation);
} catch (error) {
console.error('Reflection error:', error);
}
}
}
/**
* Core reflection — Claude analyzes its own conversation
*/
async reflectOnConversation(conversation: ConversationToReflect): Promise<ReflectionNote | null> {
if (conversation.messages.length < 2) return null;
const sessionId = `session_${Date.now()}`;
// Format conversation for analysis
const convText = conversation.messages
.map(m => `${m.role.toUpperCase()}: ${m.content}`)
.join('\n\n');
const feedbackSummary = conversation.feedbackSignals.length > 0
? conversation.feedbackSignals.map(f =>
`- Signal: ${f.signal} | Reason: ${f.reason}`
).join('\n')
: 'No explicit feedback signals detected.';
const gapsSummary = conversation.knowledgeGaps.length > 0
? conversation.knowledgeGaps.map(g =>
`- Topic: ${g.topic} (${g.category})`
).join('\n')
: 'No knowledge gaps detected.';
const reflectionPrompt = `You are Meezan's internal quality reviewer. Analyze this conversation and provide honest, structured feedback for self-improvement.
CONVERSATION:
${convText}
USER FEEDBACK SIGNALS:
${feedbackSummary}
DETECTED KNOWLEDGE GAPS:
${gapsSummary}
Analyze this conversation across these dimensions:
1. SHARIAH ACCURACY: Were all responses Shariah-compliant? Any errors or near-misses?
2. ARABIC QUALITY: Was the Arabic natural, appropriate register, correct Gulf dialect?
3. HELPFULNESS: Did Meezan actually solve the user's problem?
4. CLARITY: Were responses clear and well-structured?
5. ISLAMIC ADAB: Was the tone warm, respectful, Islamic in character?
6. MISSED OPPORTUNITIES: What could Meezan have proactively offered that it didn't?
Respond ONLY with this exact JSON format:
{
"strengths": ["list of 2-3 things done well"],
"weaknesses": ["list of 1-3 specific weaknesses"],
"missedOpportunities": ["things Meezan could have offered but didn't"],
"shariahErrors": ["any Shariah compliance issues found, empty array if none"],
"languageQuality": "excellent|good|poor",
"overallScore": 7,
"lessonsLearned": [
"Specific actionable lesson 1",
"Specific actionable lesson 2"
]
}`;
try {
const response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 800,
messages: [{ role: 'user', content: reflectionPrompt }]
});
const rawText = response.content[0].type === 'text' ? response.content[0].text : '';
// Parse JSON response
const jsonMatch = rawText.match(/\{[\s\S]*\}/);
if (!jsonMatch) return null;
const parsed = JSON.parse(jsonMatch[0]);
const note: ReflectionNote = {
id: `ref_${Date.now()}`,
sessionId,
userId: conversation.userId,
timestamp: new Date().toISOString(),
strengths: parsed.strengths || [],
weaknesses: parsed.weaknesses || [],
missedOpportunities: parsed.missedOpportunities || [],
shariahErrors: parsed.shariahErrors || [],
languageQuality: parsed.languageQuality || 'good',
overallScore: Math.min(10, Math.max(1, parsed.overallScore || 7)),
lessonsLearned: parsed.lessonsLearned || []
};
this.reflections.push(note);
// Extract and store lessons
for (const lesson of note.lessonsLearned) {
this.storelesson(lesson, 'reflection', note.id);
}
// Flag Shariah errors urgently
if (note.shariahErrors.length > 0) {
console.warn(`⚠️ SHARIAH REVIEW NEEDED: ${note.shariahErrors.join('; ')}`);
this.storeShariahErrorLesson(note.shariahErrors);
}
this.save();
console.log(`✅ Reflection complete for session ${sessionId}. Score: ${note.overallScore}/10`);
return note;
} catch (error) {
console.error('Self-reflection API error:', error);
return null;
}
}
/**
* Generate a comprehensive improvement summary
* Used to update the system prompt with learned lessons
*/
async generateImprovementContext(): Promise<string> {
if (this.lessons.length === 0) return '';
// Get top lessons by confidence and reinforcement
const topLessons = this.lessons
.filter(l => l.confidence >= 0.6)
.sort((a, b) => (b.confidence * b.reinforcementCount) - (a.confidence * a.reinforcementCount))
.slice(0, 10);
if (topLessons.length === 0) return '';
const lessonsList = topLessons
.map(l => `• ${l.lesson}`)
.join('\n');
return `\nLEARNED IMPROVEMENTS (from ${this.reflections.length} self-reflections):\n${lessonsList}\n`;
}
/**
* Get recent Shariah errors to always keep in system prompt
*/
getShariahErrorAlerts(): string[] {
return this.lessons
.filter(l => l.category === 'shariah_error' && l.confidence >= 0.8)
.map(l => l.lesson)
.slice(-5);
}
/**
* Get improvement stats for dashboard
*/
getStats(): {
totalReflections: number;
averageScore: number;
totalLessons: number;
shariahErrors: number;
trend: 'improving' | 'stable' | 'declining';
} {
const recent = this.reflections.slice(-10);
const older = this.reflections.slice(-20, -10);
const avgRecent = recent.length > 0
? recent.reduce((s, r) => s + r.overallScore, 0) / recent.length
: 0;
const avgOlder = older.length > 0
? older.reduce((s, r) => s + r.overallScore, 0) / older.length
: avgRecent;
const trend = avgRecent > avgOlder + 0.5 ? 'improving'
: avgRecent < avgOlder - 0.5 ? 'declining'
: 'stable';
return {
totalReflections: this.reflections.length,
averageScore: Math.round(avgRecent * 10) / 10,
totalLessons: this.lessons.length,
shariahErrors: this.lessons.filter(l => l.category === 'shariah_error').length,
trend
};
}
getRecentReflections(limit: number = 5): ReflectionNote[] {
return this.reflections.slice(-limit).reverse();
}
getLessons(): LearnedLesson[] {
return this.lessons.sort((a, b) => b.confidence - a.confidence);
}
private storelesson(lessonText: string, source: LearnedLesson['source'], sourceId: string): void {
// Check if similar lesson already exists
const existing = this.lessons.find(l =>
this.similarity(l.lesson, lessonText) > 0.7
);
if (existing) {
existing.reinforcementCount++;
existing.confidence = Math.min(1, existing.confidence + 0.1);
existing.lastReinforced = new Date().toISOString();
} else {
this.lessons.push({
id: `lesson_${Date.now()}_${Math.random().toString(36).slice(2)}`,
lesson: lessonText,
lessonAr: lessonText, // TODO: translate
category: 'general',
confidence: 0.5,
reinforcementCount: 1,
firstLearned: new Date().toISOString(),
lastReinforced: new Date().toISOString(),
source
});
}
}
private storeShariahErrorLesson(errors: string[]): void {
for (const error of errors) {
const lesson: LearnedLesson = {
id: `shariah_err_${Date.now()}`,
lesson: `SHARIAH ALERT: ${error}`,
lessonAr: `تنبيه شرعي: ${error}`,
category: 'shariah_error',
confidence: 0.9, // High confidence for Shariah errors
reinforcementCount: 1,
firstLearned: new Date().toISOString(),
lastReinforced: new Date().toISOString(),
source: 'reflection'
};
this.lessons.push(lesson);
}
}
private similarity(a: string, b: string): number {
const aWords = new Set(a.toLowerCase().split(/\s+/));
const bWords = new Set(b.toLowerCase().split(/\s+/));
const intersection = new Set([...aWords].filter(w => bWords.has(w)));
const union = new Set([...aWords, ...bWords]);
return intersection.size / union.size;
}
private load(): void {
const dir = path.dirname(this.dbPath);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
if (fs.existsSync(this.dbPath)) {
try {
const data = JSON.parse(fs.readFileSync(this.dbPath, 'utf-8'));
this.reflections = data.reflections || [];
this.lessons = data.lessons || [];
} catch { /* start fresh */ }
}
}
private save(): void {
fs.writeFileSync(
this.dbPath,
JSON.stringify({ reflections: this.reflections, lessons: this.lessons }, null, 2),
'utf-8'
);
}
}
export const selfReflection = new SelfReflectionEngine(
process.env.DB_PATH?.replace('memory.json', 'reflections.json') || './data/reflections.json'
);