@machinespirits/eval 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/config/eval-settings.yaml +18 -0
- package/config/evaluation-rubric-learner.yaml +277 -0
- package/config/evaluation-rubric.yaml +613 -0
- package/config/interaction-eval-scenarios.yaml +93 -50
- package/config/learner-agents.yaml +124 -193
- package/config/machinespirits-eval.code-workspace +11 -0
- package/config/providers.yaml +60 -0
- package/config/suggestion-scenarios.yaml +1399 -0
- package/config/tutor-agents.yaml +716 -0
- package/docs/EVALUATION-VARIABLES.md +589 -0
- package/docs/REPLICATION-PLAN.md +577 -0
- package/index.js +15 -6
- package/package.json +16 -22
- package/routes/evalRoutes.js +88 -36
- package/scripts/analyze-judge-reliability.js +401 -0
- package/scripts/analyze-run.js +97 -0
- package/scripts/analyze-run.mjs +282 -0
- package/scripts/analyze-validation-failures.js +141 -0
- package/scripts/check-run.mjs +17 -0
- package/scripts/code-impasse-strategies.js +1132 -0
- package/scripts/compare-runs.js +44 -0
- package/scripts/compare-suggestions.js +80 -0
- package/scripts/compare-transformation.js +116 -0
- package/scripts/dig-into-run.js +158 -0
- package/scripts/eval-cli.js +2626 -0
- package/scripts/generate-paper-figures.py +452 -0
- package/scripts/qualitative-analysis-ai.js +1313 -0
- package/scripts/qualitative-analysis.js +688 -0
- package/scripts/seed-db.js +87 -0
- package/scripts/show-failed-suggestions.js +64 -0
- package/scripts/validate-content.js +192 -0
- package/server.js +3 -2
- package/services/__tests__/evalConfigLoader.test.js +338 -0
- package/services/anovaStats.js +499 -0
- package/services/contentResolver.js +407 -0
- package/services/dialogueTraceAnalyzer.js +454 -0
- package/services/evalConfigLoader.js +625 -0
- package/services/evaluationRunner.js +2171 -270
- package/services/evaluationStore.js +564 -29
- package/services/learnerConfigLoader.js +75 -5
- package/services/learnerRubricEvaluator.js +284 -0
- package/services/learnerTutorInteractionEngine.js +375 -0
- package/services/processUtils.js +18 -0
- package/services/progressLogger.js +98 -0
- package/services/promptRecommendationService.js +31 -26
- package/services/promptRewriter.js +427 -0
- package/services/rubricEvaluator.js +543 -70
- package/services/streamingReporter.js +104 -0
- package/services/turnComparisonAnalyzer.js +494 -0
- package/components/MobileEvalDashboard.tsx +0 -267
- package/components/comparison/DeltaAnalysisTable.tsx +0 -137
- package/components/comparison/ProfileComparisonCard.tsx +0 -176
- package/components/comparison/RecognitionABMode.tsx +0 -385
- package/components/comparison/RecognitionMetricsPanel.tsx +0 -135
- package/components/comparison/WinnerIndicator.tsx +0 -64
- package/components/comparison/index.ts +0 -5
- package/components/mobile/BottomSheet.tsx +0 -233
- package/components/mobile/DimensionBreakdown.tsx +0 -210
- package/components/mobile/DocsView.tsx +0 -363
- package/components/mobile/LogsView.tsx +0 -481
- package/components/mobile/PsychodynamicQuadrant.tsx +0 -261
- package/components/mobile/QuickTestView.tsx +0 -1098
- package/components/mobile/RecognitionTypeChart.tsx +0 -124
- package/components/mobile/RecognitionView.tsx +0 -809
- package/components/mobile/RunDetailView.tsx +0 -261
- package/components/mobile/RunHistoryView.tsx +0 -367
- package/components/mobile/ScoreRadial.tsx +0 -211
- package/components/mobile/StreamingLogPanel.tsx +0 -230
- package/components/mobile/SynthesisStrategyChart.tsx +0 -140
- package/docs/research/ABLATION-DIALOGUE-ROUNDS.md +0 -52
- package/docs/research/ABLATION-MODEL-SELECTION.md +0 -53
- package/docs/research/ADVANCED-EVAL-ANALYSIS.md +0 -60
- package/docs/research/ANOVA-RESULTS-2026-01-14.md +0 -257
- package/docs/research/COMPREHENSIVE-EVALUATION-PLAN.md +0 -586
- package/docs/research/COST-ANALYSIS.md +0 -56
- package/docs/research/CRITICAL-REVIEW-RECOGNITION-TUTORING.md +0 -340
- package/docs/research/DYNAMIC-VS-SCRIPTED-ANALYSIS.md +0 -291
- package/docs/research/EVAL-SYSTEM-ANALYSIS.md +0 -306
- package/docs/research/FACTORIAL-RESULTS-2026-01-14.md +0 -301
- package/docs/research/IMPLEMENTATION-PLAN-CRITIQUE-RESPONSE.md +0 -1988
- package/docs/research/LONGITUDINAL-DYADIC-EVALUATION.md +0 -282
- package/docs/research/MULTI-JUDGE-VALIDATION-2026-01-14.md +0 -147
- package/docs/research/PAPER-EXTENSION-DYADIC.md +0 -204
- package/docs/research/PAPER-UNIFIED.md +0 -659
- package/docs/research/PAPER-UNIFIED.pdf +0 -0
- package/docs/research/PROMPT-IMPROVEMENTS-2026-01-14.md +0 -356
- package/docs/research/SESSION-NOTES-2026-01-11-RECOGNITION-EVAL.md +0 -419
- package/docs/research/apa.csl +0 -2133
- package/docs/research/archive/PAPER-DRAFT-RECOGNITION-TUTORING.md +0 -1637
- package/docs/research/archive/paper-multiagent-tutor.tex +0 -978
- package/docs/research/paper-draft/full-paper.md +0 -136
- package/docs/research/paper-draft/images/pasted-image-2026-01-24T03-47-47-846Z-d76a7ae2.png +0 -0
- package/docs/research/paper-draft/references.bib +0 -515
- package/docs/research/transcript-baseline.md +0 -139
- package/docs/research/transcript-recognition-multiagent.md +0 -187
- package/hooks/useEvalData.ts +0 -625
- package/server-init.js +0 -45
- package/services/benchmarkService.js +0 -1892
- package/types.ts +0 -165
- package/utils/haptics.ts +0 -45
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Rewriter Service
|
|
3
|
+
*
|
|
4
|
+
* Synthesizes session evolution directives for dynamic prompt rewriting.
|
|
5
|
+
* Two strategies available:
|
|
6
|
+
*
|
|
7
|
+
* 1. Template-based (synthesizeDirectives): Deterministic, cheap, pattern-matching
|
|
8
|
+
* 2. LLM-based (synthesizeDirectivesLLM): Uses superego model for rich, contextual directives
|
|
9
|
+
*
|
|
10
|
+
* Both analyze turn results, dialogue traces, and conversation history to
|
|
11
|
+
* generate session-specific directives that are prepended to ego's system prompt.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { unifiedAIProvider } from '@machinespirits/tutor-core';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Synthesize directives from accumulated turn data.
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} options
|
|
20
|
+
* @param {Array} options.turnResults - Results from previous turns
|
|
21
|
+
* @param {Array} options.consolidatedTrace - Full dialogue trace so far
|
|
22
|
+
* @param {Array} options.conversationHistory - Conversation history entries
|
|
23
|
+
* @returns {string|null} XML directive block to prepend, or null if no directives
|
|
24
|
+
*/
|
|
25
|
+
export function synthesizeDirectives({ turnResults = [], consolidatedTrace = [], conversationHistory = [] }) {
|
|
26
|
+
if (turnResults.length === 0) return null;
|
|
27
|
+
|
|
28
|
+
const directives = [];
|
|
29
|
+
|
|
30
|
+
// 1. Score trajectory — detect quality decline
|
|
31
|
+
const scoreTrajectory = analyzeScoreTrajectory(turnResults);
|
|
32
|
+
if (scoreTrajectory) directives.push(scoreTrajectory);
|
|
33
|
+
|
|
34
|
+
// 2. Superego feedback — extract last critique
|
|
35
|
+
const superegoFeedback = extractSuperegoFeedback(consolidatedTrace, turnResults.length);
|
|
36
|
+
if (superegoFeedback) directives.push(superegoFeedback);
|
|
37
|
+
|
|
38
|
+
// 3. Learner question detection
|
|
39
|
+
const learnerQuestions = detectLearnerQuestions(conversationHistory);
|
|
40
|
+
if (learnerQuestions) directives.push(learnerQuestions);
|
|
41
|
+
|
|
42
|
+
// 4. Strategy stagnation — repeated suggestion types
|
|
43
|
+
const stagnation = detectStrategyStagnation(turnResults);
|
|
44
|
+
if (stagnation) directives.push(stagnation);
|
|
45
|
+
|
|
46
|
+
// 5. Recognition signals — learner contributions to build on
|
|
47
|
+
const recognitionSignals = detectRecognitionSignals(conversationHistory, turnResults);
|
|
48
|
+
if (recognitionSignals) directives.push(recognitionSignals);
|
|
49
|
+
|
|
50
|
+
if (directives.length === 0) return null;
|
|
51
|
+
|
|
52
|
+
const numbered = directives.map((d, i) => `${i + 1}. ${d}`).join('\n');
|
|
53
|
+
return `<session_evolution>
|
|
54
|
+
Based on the dialogue so far, prioritize the following in your next response:
|
|
55
|
+
|
|
56
|
+
${numbered}
|
|
57
|
+
</session_evolution>`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Analyze score trajectory across turns. If quality decreased, generate directive.
|
|
62
|
+
*/
|
|
63
|
+
function analyzeScoreTrajectory(turnResults) {
|
|
64
|
+
if (turnResults.length < 2) return null;
|
|
65
|
+
|
|
66
|
+
const scores = turnResults.filter(t => t.turnScore !== null && t.turnScore !== undefined).map(t => t.turnScore);
|
|
67
|
+
if (scores.length < 2) return null;
|
|
68
|
+
|
|
69
|
+
const last = scores[scores.length - 1];
|
|
70
|
+
const prev = scores[scores.length - 2];
|
|
71
|
+
const delta = last - prev;
|
|
72
|
+
|
|
73
|
+
if (delta < -5) {
|
|
74
|
+
// Find what scored well in the earlier turn
|
|
75
|
+
const bestPrevDim = findBestDimension(turnResults[turnResults.length - 2]);
|
|
76
|
+
const worstCurrDim = findWorstDimension(turnResults[turnResults.length - 1]);
|
|
77
|
+
|
|
78
|
+
let directive = `Quality declined from the previous turn (${prev.toFixed(0)} → ${last.toFixed(0)}).`;
|
|
79
|
+
if (bestPrevDim) directive += ` Your ${bestPrevDim} was strongest before — maintain that approach.`;
|
|
80
|
+
if (worstCurrDim) directive += ` Focus on improving ${worstCurrDim}.`;
|
|
81
|
+
return directive;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Extract the most recent superego feedback from the dialogue trace.
|
|
89
|
+
*/
|
|
90
|
+
function extractSuperegoFeedback(consolidatedTrace, turnCount) {
|
|
91
|
+
if (!consolidatedTrace || consolidatedTrace.length === 0) return null;
|
|
92
|
+
|
|
93
|
+
// Find the last superego entry from the most recent turn
|
|
94
|
+
const superegoEntries = consolidatedTrace.filter(
|
|
95
|
+
entry => entry.agent === 'superego' && entry.action !== 'deliberation'
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (superegoEntries.length === 0) return null;
|
|
99
|
+
|
|
100
|
+
const lastEntry = superegoEntries[superegoEntries.length - 1];
|
|
101
|
+
const feedback = lastEntry.contextSummary || lastEntry.detail;
|
|
102
|
+
if (!feedback) return null;
|
|
103
|
+
|
|
104
|
+
// Look for rejection or revision feedback
|
|
105
|
+
const detail = lastEntry.detail || '';
|
|
106
|
+
const isRejection = detail.includes('"approved": false') || detail.includes('"approved":false');
|
|
107
|
+
const hasRevisions = detail.includes('"specificRevisions"') || detail.includes('"revise"');
|
|
108
|
+
|
|
109
|
+
if (isRejection || hasRevisions) {
|
|
110
|
+
// Extract the feedback text
|
|
111
|
+
const feedbackMatch = detail.match(/"feedback"\s*:\s*"([^"]+)"/);
|
|
112
|
+
if (feedbackMatch) {
|
|
113
|
+
return `The internal critic flagged an issue in your last response: "${feedbackMatch[1].substring(0, 150)}". Address this concern in your next suggestion.`;
|
|
114
|
+
}
|
|
115
|
+
return `The internal critic requested revisions on your last response. Ensure your next suggestion addresses its feedback.`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Detect if the learner asked direct questions that need answering.
|
|
123
|
+
*/
|
|
124
|
+
function detectLearnerQuestions(conversationHistory) {
|
|
125
|
+
if (conversationHistory.length === 0) return null;
|
|
126
|
+
|
|
127
|
+
const lastEntry = conversationHistory[conversationHistory.length - 1];
|
|
128
|
+
const message = lastEntry.learnerMessage || '';
|
|
129
|
+
|
|
130
|
+
if (!message) return null;
|
|
131
|
+
|
|
132
|
+
// Check for question marks or question-like patterns
|
|
133
|
+
const hasQuestionMark = message.includes('?');
|
|
134
|
+
const hasQuestionWords = /\b(what|why|how|when|where|which|can|could|would|should|do|does|is|are)\b/i.test(message);
|
|
135
|
+
|
|
136
|
+
if (hasQuestionMark || (hasQuestionWords && message.length > 20)) {
|
|
137
|
+
// Extract the likely question
|
|
138
|
+
const sentences = message.split(/[.!?]+/).filter(s => s.trim().length > 0);
|
|
139
|
+
const questions = sentences.filter(s => s.includes('?') || /^\s*(what|why|how|when|where|which)\b/i.test(s));
|
|
140
|
+
|
|
141
|
+
if (questions.length > 0) {
|
|
142
|
+
const firstQuestion = questions[0].trim().substring(0, 120);
|
|
143
|
+
return `The learner asked a direct question: "${firstQuestion}". Your response must address this question specifically before suggesting new content.`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Detect if the tutor is repeating the same suggestion strategy.
|
|
152
|
+
*/
|
|
153
|
+
function detectStrategyStagnation(turnResults) {
|
|
154
|
+
if (turnResults.length < 3) return null;
|
|
155
|
+
|
|
156
|
+
const recentTypes = turnResults.slice(-3).map(t => t.suggestion?.type).filter(Boolean);
|
|
157
|
+
if (recentTypes.length < 3) return null;
|
|
158
|
+
|
|
159
|
+
const allSame = recentTypes.every(t => t === recentTypes[0]);
|
|
160
|
+
if (allSame) {
|
|
161
|
+
return `You have suggested "${recentTypes[0]}" type content for the last ${recentTypes.length} turns. Consider varying your approach — try a different suggestion type (e.g., reflection, simulation, review) to maintain engagement.`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check if action targets are too similar (same lecture family)
|
|
165
|
+
const recentTargets = turnResults.slice(-3).map(t => t.suggestion?.actionTarget).filter(Boolean);
|
|
166
|
+
if (recentTargets.length >= 3) {
|
|
167
|
+
const targetPrefixes = recentTargets.map(t => t.split('-').slice(0, 2).join('-'));
|
|
168
|
+
if (targetPrefixes.every(p => p === targetPrefixes[0])) {
|
|
169
|
+
return `Your last ${recentTargets.length} suggestions all pointed to the same course section. Consider broadening — connect to related content in other courses or suggest a different modality (simulation, journal, text analysis).`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Detect recognition signals from the learner worth building on.
|
|
178
|
+
*/
|
|
179
|
+
function detectRecognitionSignals(conversationHistory, turnResults) {
|
|
180
|
+
if (conversationHistory.length === 0) return null;
|
|
181
|
+
|
|
182
|
+
const lastEntry = conversationHistory[conversationHistory.length - 1];
|
|
183
|
+
const message = lastEntry.learnerMessage || '';
|
|
184
|
+
if (!message) return null;
|
|
185
|
+
|
|
186
|
+
const signals = [];
|
|
187
|
+
|
|
188
|
+
// Check for learner offering their own interpretation/metaphor
|
|
189
|
+
const interpretationPatterns = [
|
|
190
|
+
/\bi think\b/i,
|
|
191
|
+
/\bit seems like\b/i,
|
|
192
|
+
/\bmaybe\s+it['']?s\b/i,
|
|
193
|
+
/\bwhat if\b/i,
|
|
194
|
+
/\bkind of like\b/i,
|
|
195
|
+
/\bsort of like\b/i,
|
|
196
|
+
/\breminds me of\b/i,
|
|
197
|
+
/\bis like\b/i,
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
const hasInterpretation = interpretationPatterns.some(p => p.test(message));
|
|
201
|
+
if (hasInterpretation) {
|
|
202
|
+
signals.push('offered their own interpretation');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Check for learner pushback / critique
|
|
206
|
+
const pushbackPatterns = [
|
|
207
|
+
/\bbut\s+(what about|doesn['']t|isn['']t|that doesn['']t)\b/i,
|
|
208
|
+
/\bi disagree\b/i,
|
|
209
|
+
/\bi don['']t think\b/i,
|
|
210
|
+
/\bthat['']s not\b/i,
|
|
211
|
+
/\bdoesn['']t (apply|work|make sense)\b/i,
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
const hasPushback = pushbackPatterns.some(p => p.test(message));
|
|
215
|
+
if (hasPushback) {
|
|
216
|
+
signals.push('pushed back with a substantive critique');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Check for concept connections
|
|
220
|
+
const connectionPatterns = [
|
|
221
|
+
/\bconnects to\b/i,
|
|
222
|
+
/\brelated to\b/i,
|
|
223
|
+
/\bsimilar to\b/i,
|
|
224
|
+
/\bjust like\b/i,
|
|
225
|
+
/\bthis is like\b/i,
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const hasConnection = connectionPatterns.some(p => p.test(message));
|
|
229
|
+
if (hasConnection) {
|
|
230
|
+
signals.push('connected concepts across topics');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (signals.length === 0) return null;
|
|
234
|
+
|
|
235
|
+
const signalList = signals.join(', ');
|
|
236
|
+
const snippet = message.substring(0, 100);
|
|
237
|
+
return `The learner ${signalList} ("${snippet}..."). Build on their contribution — acknowledge their specific language and develop it further rather than redirecting.`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Find the best-scoring dimension for a turn result.
|
|
242
|
+
*/
|
|
243
|
+
function findBestDimension(turnResult) {
|
|
244
|
+
if (!turnResult?.scores) return null;
|
|
245
|
+
let best = null;
|
|
246
|
+
let bestScore = -Infinity;
|
|
247
|
+
for (const [dim, score] of Object.entries(turnResult.scores)) {
|
|
248
|
+
if (score != null && score > bestScore) {
|
|
249
|
+
bestScore = score;
|
|
250
|
+
best = dim;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return best;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Find the worst-scoring dimension for a turn result.
|
|
258
|
+
*/
|
|
259
|
+
function findWorstDimension(turnResult) {
|
|
260
|
+
if (!turnResult?.scores) return null;
|
|
261
|
+
let worst = null;
|
|
262
|
+
let worstScore = Infinity;
|
|
263
|
+
for (const [dim, score] of Object.entries(turnResult.scores)) {
|
|
264
|
+
if (score != null && score < worstScore) {
|
|
265
|
+
worstScore = score;
|
|
266
|
+
worst = dim;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return worst;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// LLM-Based Directive Synthesis
|
|
274
|
+
// ============================================================================
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Synthesize directives using an LLM for contextually rich evolution guidance.
|
|
278
|
+
*
|
|
279
|
+
* Unlike the template-based approach, this uses the superego model to analyze
|
|
280
|
+
* the full dialogue context and generate targeted, non-generic directives.
|
|
281
|
+
*
|
|
282
|
+
* @param {Object} options
|
|
283
|
+
* @param {Array} options.turnResults - Results from previous turns
|
|
284
|
+
* @param {Array} options.consolidatedTrace - Full dialogue trace so far
|
|
285
|
+
* @param {Array} options.conversationHistory - Conversation history entries
|
|
286
|
+
* @param {Object} options.config - Profile config containing model info
|
|
287
|
+
* @returns {Promise<string|null>} XML directive block to prepend, or null if synthesis fails
|
|
288
|
+
*/
|
|
289
|
+
export async function synthesizeDirectivesLLM({
|
|
290
|
+
turnResults = [],
|
|
291
|
+
consolidatedTrace = [],
|
|
292
|
+
conversationHistory = [],
|
|
293
|
+
config = {},
|
|
294
|
+
}) {
|
|
295
|
+
if (turnResults.length === 0) return null;
|
|
296
|
+
|
|
297
|
+
// Build context summary for the LLM
|
|
298
|
+
const contextSummary = buildContextSummaryForLLM(turnResults, consolidatedTrace, conversationHistory);
|
|
299
|
+
|
|
300
|
+
// Determine model to use (superego model from profile, or fallback)
|
|
301
|
+
const superegoModel = config.superego?.model || 'moonshotai/kimi-k2.5';
|
|
302
|
+
const provider = config.superego?.provider || 'openrouter';
|
|
303
|
+
|
|
304
|
+
const systemPrompt = `You are a pedagogical analyst reviewing an ongoing tutoring dialogue. Your task is to synthesize 2-5 specific, actionable directives that will help the tutor improve in the next turn.
|
|
305
|
+
|
|
306
|
+
CRITICAL RULES:
|
|
307
|
+
- Directives must be SPECIFIC to this dialogue — reference actual learner statements, scores, and patterns
|
|
308
|
+
- Avoid generic advice like "be more engaging" or "personalize responses"
|
|
309
|
+
- Each directive should address a concrete issue or opportunity observed in the data
|
|
310
|
+
- Directives should build on what's working, not just fix problems
|
|
311
|
+
- If the dialogue is going well, focus on deepening rather than correcting
|
|
312
|
+
|
|
313
|
+
OUTPUT FORMAT:
|
|
314
|
+
Return ONLY a numbered list of 2-5 directives, one per line. No preamble, no explanation after.
|
|
315
|
+
|
|
316
|
+
Example output:
|
|
317
|
+
1. The learner's analogy comparing dialectics to "debugging code" in turn 2 shows technical framing — extend this metaphor when introducing Aufhebung.
|
|
318
|
+
2. Score trajectory shows personalization dropping (87→71). The last response addressed "students" generally — use the learner's name and reference their earlier question about AI ethics.
|
|
319
|
+
3. Superego flagged lack of curriculum grounding. The learner is exploring emergence — connect to 479-lecture-5 which covers complexity and emergent properties.`;
|
|
320
|
+
|
|
321
|
+
const userMessage = `Analyze this tutoring dialogue and generate evolution directives:
|
|
322
|
+
|
|
323
|
+
${contextSummary}
|
|
324
|
+
|
|
325
|
+
Generate 2-5 specific directives for the next turn:`;
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
const response = await unifiedAIProvider.call({
|
|
329
|
+
provider,
|
|
330
|
+
model: superegoModel,
|
|
331
|
+
systemPrompt,
|
|
332
|
+
messages: [{ role: 'user', content: userMessage }],
|
|
333
|
+
preset: 'deliberation',
|
|
334
|
+
config: {
|
|
335
|
+
temperature: 0.3, // Lower temp for focused analysis
|
|
336
|
+
maxTokens: 500,
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const directives = response.content?.trim();
|
|
341
|
+
if (!directives || directives.length < 20) {
|
|
342
|
+
console.log('[promptRewriter] LLM returned empty or too-short directives');
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Wrap in session_evolution XML block
|
|
347
|
+
return `<session_evolution>
|
|
348
|
+
Based on analysis of the dialogue so far, prioritize the following in your next response:
|
|
349
|
+
|
|
350
|
+
${directives}
|
|
351
|
+
</session_evolution>`;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error('[promptRewriter] LLM directive synthesis failed:', error.message);
|
|
354
|
+
// Fallback to template-based directives
|
|
355
|
+
return synthesizeDirectives({ turnResults, consolidatedTrace, conversationHistory });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Build a structured context summary for the LLM to analyze.
|
|
361
|
+
*/
|
|
362
|
+
function buildContextSummaryForLLM(turnResults, consolidatedTrace, conversationHistory) {
|
|
363
|
+
const parts = [];
|
|
364
|
+
|
|
365
|
+
// 1. Score trajectory
|
|
366
|
+
const scores = turnResults
|
|
367
|
+
.filter(t => t.turnScore !== null && t.turnScore !== undefined)
|
|
368
|
+
.map((t, i) => `Turn ${i + 1}: ${t.turnScore.toFixed(1)}`);
|
|
369
|
+
if (scores.length > 0) {
|
|
370
|
+
parts.push(`## Score Trajectory\n${scores.join(' → ')}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 2. Dimension breakdown for last turn
|
|
374
|
+
const lastTurn = turnResults[turnResults.length - 1];
|
|
375
|
+
if (lastTurn?.scores) {
|
|
376
|
+
const dimScores = Object.entries(lastTurn.scores)
|
|
377
|
+
.filter(([_, v]) => v != null)
|
|
378
|
+
.map(([k, v]) => ` - ${k}: ${v}`)
|
|
379
|
+
.join('\n');
|
|
380
|
+
if (dimScores) {
|
|
381
|
+
parts.push(`## Last Turn Dimension Scores\n${dimScores}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// 3. Superego feedback from trace
|
|
386
|
+
const superegoFeedback = consolidatedTrace
|
|
387
|
+
.filter(e => e.agent === 'superego')
|
|
388
|
+
.slice(-3) // Last 3 superego entries
|
|
389
|
+
.map(e => {
|
|
390
|
+
const summary = e.contextSummary || '';
|
|
391
|
+
const detail = e.detail || '';
|
|
392
|
+
// Extract key feedback
|
|
393
|
+
const feedbackMatch = detail.match(/"feedback"\s*:\s*"([^"]+)"/);
|
|
394
|
+
return feedbackMatch ? feedbackMatch[1].substring(0, 200) : summary.substring(0, 200);
|
|
395
|
+
})
|
|
396
|
+
.filter(Boolean);
|
|
397
|
+
if (superegoFeedback.length > 0) {
|
|
398
|
+
parts.push(`## Recent Superego Feedback\n${superegoFeedback.map((f, i) => `${i + 1}. ${f}`).join('\n')}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 4. Conversation history (learner messages)
|
|
402
|
+
const learnerMsgs = conversationHistory
|
|
403
|
+
.filter(h => h.learnerMessage)
|
|
404
|
+
.slice(-3) // Last 3 learner messages
|
|
405
|
+
.map((h, i) => `Turn ${h.turnIndex + 1}: "${h.learnerMessage.substring(0, 150)}${h.learnerMessage.length > 150 ? '...' : ''}"`);
|
|
406
|
+
if (learnerMsgs.length > 0) {
|
|
407
|
+
parts.push(`## Recent Learner Messages\n${learnerMsgs.join('\n')}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 5. Tutor suggestion types
|
|
411
|
+
const suggTypes = turnResults
|
|
412
|
+
.filter(t => t.suggestion?.type)
|
|
413
|
+
.map((t, i) => `Turn ${i + 1}: ${t.suggestion.type}${t.suggestion.actionTarget ? ` (${t.suggestion.actionTarget})` : ''}`);
|
|
414
|
+
if (suggTypes.length > 0) {
|
|
415
|
+
parts.push(`## Tutor Suggestion Types\n${suggTypes.join('\n')}`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 6. Learner emotional state if available
|
|
419
|
+
const emotionalStates = turnResults
|
|
420
|
+
.filter(t => t.learnerEmotionalState)
|
|
421
|
+
.map((t, i) => `Turn ${i + 1}: ${t.learnerEmotionalState}`);
|
|
422
|
+
if (emotionalStates.length > 0) {
|
|
423
|
+
parts.push(`## Learner Emotional States\n${emotionalStates.join('\n')}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return parts.join('\n\n');
|
|
427
|
+
}
|