import * as db from './db'; import { callLLM, cachedSystem } from './llm'; import { EMIT_THEME_SESSION_TOOL } from './llmTools'; const THEME_SESSION_SYSTEM = `You are an expert learning content writer for Respellion, an internal IT company. You produce theme-level weekly learning sessions for the perpetual 26-week curriculum. A weekly session is a single, coherent piece of learning that covers every topic the curriculum has slotted for that week's theme — not a list of separate articles. Write in clear, professional English. Each topic section must be detailed (at least four sentences) and include a concrete workplace example or application. Emit content only through the emit_theme_session tool.`; function buildUserPrompt(weekContent) { const topicLines = weekContent.topics .map((t, idx) => `${idx + 1}. id=${t.id} | label="${t.label}" | type=${t.type} | description=${t.description}`) .join('\n'); return `Theme: ${weekContent.theme} Week ${weekContent.weekNumber} of the 26-week curriculum. Estimated duration: ${weekContent.estimatedDuration || 30} minutes. Rationale for this week's position: ${weekContent.rationale || '—'} Topics in this theme this week (you MUST cover every one of them, one section each, reusing the exact topic_id): ${topicLines} Produce a detailed theme session that synthesises these topics into a single coherent learning experience. Cover every topic in its own section, finish with a "how these connect" paragraph, and at least 3 takeaways for the theme.`; } /** * Get the cached theme session for the active curriculum version + week, * or generate one with the LLM and cache it. * * @param {string} curriculumVersionId — id of the active curriculum_versions row * @param {object} weekContent — output of getCurrentWeekContent: { weekNumber, theme, topics[], estimatedDuration, rationale } * @param {object} [opts] * @param {boolean} [opts.force=false] — regenerate even if cached */ export async function getOrGenerateThemeSession(curriculumVersionId, weekContent, { force = false } = {}) { if (!curriculumVersionId) throw new Error('Theme session requires an active curriculum version.'); if (!weekContent || !Array.isArray(weekContent.topics) || weekContent.topics.length === 0) { throw new Error('Theme session requires at least one topic for the week.'); } if (!force) { const cached = await db.getThemeSession(curriculumVersionId, weekContent.weekNumber); if (cached) return cached; } const result = await callLLM({ task: 'theme_session.generate', tier: 'standard', system: cachedSystem(THEME_SESSION_SYSTEM), user: buildUserPrompt(weekContent), tools: [EMIT_THEME_SESSION_TOOL], toolChoice: { type: 'tool', name: EMIT_THEME_SESSION_TOOL.name }, maxTokens: 8192, timeoutMs: 180_000, }); const emitted = result.toolUses[0]?.input; if (!emitted) throw new Error('AI did not return a theme session. Please try again.'); return db.setThemeSession(curriculumVersionId, weekContent.weekNumber, weekContent.theme, emitted); }