Download src/core/selfReflection.ts from agenthinkmesh/meezan: direct link, hf CLI and curl.
- Browser
- Download file 10.6 kB
-
https://huggingface.co/agenthinkmesh/meezan/resolve/main/src/core/selfReflection.ts
- Command line
-
hf download hf://agenthinkmesh/meezan/src/core/selfReflection.ts
-
curl -L -o selfReflection.ts https://huggingface.co/agenthinkmesh/meezan/resolve/main/src/core/selfReflection.ts
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' | |
| ); | |