OpenMAIC-React / src /lib /orchestration /prompt-builder.ts
muthuk1's picture
Convert OpenMAIC from Next.js to React (Vite)
f56a29b verified
/**
* Prompt Builder for Stateless Generation
*
* Builds system prompts and converts messages for the LLM.
*/
import type { StatelessChatRequest } from '@/lib/types/chat';
import type { AgentConfig } from '@/lib/orchestration/registry/types';
import type { WhiteboardActionRecord, AgentTurnSummary } from './types';
import { getActionDescriptions, getEffectiveActions } from './tool-schemas';
import { buildStateContext } from './summarizers/state-context';
import { buildVirtualWhiteboardContext } from './summarizers/whiteboard-ledger';
import { buildPeerContextSection } from './summarizers/peer-context';
import { buildPrompt, PROMPT_IDS } from '@/lib/prompts';
// ==================== Role Guidelines ====================
const ROLE_GUIDELINES: Record<string, string> = {
teacher: `Your role in this classroom: LEAD TEACHER.
You are responsible for:
- Controlling the lesson flow, slides, and pacing
- Explaining concepts clearly with examples and analogies
- Asking questions to check understanding
- Using spotlight/laser to direct attention to slide elements
- Using the whiteboard for diagrams and formulas
You can use all available actions. Never announce your actions β€” just teach naturally.`,
assistant: `Your role in this classroom: TEACHING ASSISTANT.
You are responsible for:
- Supporting the lead teacher by filling gaps and answering side questions
- Rephrasing explanations in simpler terms when students are confused
- Providing concrete examples and background context
- Using the whiteboard sparingly to supplement (not duplicate) the teacher's content
You play a supporting role β€” don't take over the lesson.`,
student: `Your role in this classroom: STUDENT.
You are responsible for:
- Participating actively in discussions
- Asking questions, sharing observations, reacting to the lesson
- Keeping responses SHORT (1-2 sentences max)
- Only using the whiteboard when explicitly invited by the teacher
You are NOT a teacher β€” your responses should be much shorter than the teacher's.`,
};
// ==================== Types ====================
/**
* Discussion context for agent-initiated discussions
*/
interface DiscussionContext {
topic: string;
prompt?: string;
}
// ==================== Per-variant string constants ====================
const FORMAT_EXAMPLE_SLIDE = `[{"type":"action","name":"spotlight","params":{"elementId":"img_1"}},{"type":"text","content":"Your natural speech to students"}]`;
const FORMAT_EXAMPLE_WB = `[{"type":"action","name":"wb_open","params":{}},{"type":"text","content":"Your natural speech to students"}]`;
const ORDERING_SLIDE = `- spotlight/laser actions should appear BEFORE the corresponding text object (point first, then speak)
- whiteboard actions can interleave WITH text objects (draw while speaking)`;
const ORDERING_WB = `- whiteboard actions can interleave WITH text objects (draw while speaking)`;
const SPOTLIGHT_EXAMPLES = `[{"type":"action","name":"spotlight","params":{"elementId":"img_1"}},{"type":"text","content":"Photosynthesis is the process by which plants convert light energy into chemical energy. Take a look at this diagram."},{"type":"text","content":"During this process, plants absorb carbon dioxide and water to produce glucose and oxygen."}]
[{"type":"action","name":"spotlight","params":{"elementId":"eq_1"}},{"type":"action","name":"laser","params":{"elementId":"eq_2"}},{"type":"text","content":"Compare these two equations β€” notice how the left side is endothermic while the right side is exothermic."}]
`;
const SLIDE_ACTION_GUIDELINES = `- spotlight: Use to focus attention on ONE key element. Don't overuse β€” max 1-2 per response.
- laser: Use to point at elements. Good for directing attention during explanations.
`;
const MUTUAL_EXCLUSION_NOTE = `- IMPORTANT β€” Whiteboard / Canvas mutual exclusion: The whiteboard and slide canvas are mutually exclusive. When the whiteboard is OPEN, the slide canvas is hidden β€” spotlight and laser actions targeting slide elements will have NO visible effect. If you need to use spotlight or laser, call wb_close first to reveal the slide canvas. Conversely, if the whiteboard is CLOSED, wb_draw_* actions still work (they implicitly open the whiteboard), but be aware that doing so hides the slide canvas.
- Prefer variety: mix spotlights, laser, and whiteboard for engaging teaching. Don't use the same action type repeatedly.`;
// ==================== Private helpers ====================
function buildStudentProfileSection(userProfile?: { nickname?: string; bio?: string }): string {
if (!userProfile?.nickname && !userProfile?.bio) return '';
return `\n# Student Profile
You are teaching ${userProfile.nickname || 'a student'}.${userProfile.bio ? `\nTheir background: ${userProfile.bio}` : ''}
Personalize your teaching based on their background when relevant. Address them by name naturally.\n`;
}
function buildLanguageConstraint(langDirective?: string): string {
return langDirective ? `\n# Language (CRITICAL)\n${langDirective}\n` : '';
}
function buildDiscussionContextSection(
discussionContext: DiscussionContext | undefined,
agentResponses: AgentTurnSummary[] | undefined,
): string {
if (!discussionContext) return '';
if (agentResponses && agentResponses.length > 0) {
return `
# Discussion Context
Topic: "${discussionContext.topic}"
${discussionContext.prompt ? `Guiding prompt: ${discussionContext.prompt}` : ''}
You are JOINING an ongoing discussion β€” do NOT re-introduce the topic or greet the students. The discussion has already started. Contribute your unique perspective, ask a follow-up question, or challenge an assumption made by a previous speaker.`;
}
return `
# Discussion Context
You are initiating a discussion on the following topic: "${discussionContext.topic}"
${discussionContext.prompt ? `Guiding prompt: ${discussionContext.prompt}` : ''}
IMPORTANT: As you are starting this discussion, begin by introducing the topic naturally to the students. Engage them and invite their thoughts. Do not wait for user input - you speak first.`;
}
// ==================== System Prompt ====================
/**
* Build system prompt for structured output generation
*
* @param agentConfig - The agent configuration
* @param storeState - Current application state
* @param discussionContext - Optional discussion context for agent-initiated discussions
* @returns System prompt string
*/
export function buildStructuredPrompt(
agentConfig: AgentConfig,
storeState: StatelessChatRequest['storeState'],
discussionContext?: DiscussionContext,
whiteboardLedger?: WhiteboardActionRecord[],
userProfile?: { nickname?: string; bio?: string },
agentResponses?: AgentTurnSummary[],
): string {
// Determine current scene type for action filtering
const currentScene = storeState.currentSceneId
? storeState.scenes.find((s) => s.id === storeState.currentSceneId)
: undefined;
const sceneType = currentScene?.type;
const effectiveActions = getEffectiveActions(agentConfig.allowedActions, sceneType);
const hasSlideActions =
effectiveActions.includes('spotlight') || effectiveActions.includes('laser');
const vars = {
agentName: agentConfig.name,
persona: agentConfig.persona,
roleGuideline: ROLE_GUIDELINES[agentConfig.role] || ROLE_GUIDELINES.student,
studentProfileSection: buildStudentProfileSection(userProfile),
peerContext: buildPeerContextSection(agentResponses, agentConfig.name),
languageConstraint: buildLanguageConstraint(storeState.stage?.languageDirective),
formatExample: hasSlideActions ? FORMAT_EXAMPLE_SLIDE : FORMAT_EXAMPLE_WB,
orderingPrinciples: hasSlideActions ? ORDERING_SLIDE : ORDERING_WB,
spotlightExamples: hasSlideActions ? SPOTLIGHT_EXAMPLES : '',
actionDescriptions: getActionDescriptions(effectiveActions),
slideActionGuidelines: hasSlideActions ? SLIDE_ACTION_GUIDELINES : '',
mutualExclusionNote: hasSlideActions ? MUTUAL_EXCLUSION_NOTE : '',
stateContext: buildStateContext(storeState),
virtualWhiteboardContext: buildVirtualWhiteboardContext(storeState, whiteboardLedger),
lengthGuidelines: buildLengthGuidelines(agentConfig.role),
whiteboardGuidelines: buildWhiteboardGuidelines(agentConfig.role),
discussionContextSection: buildDiscussionContextSection(discussionContext, agentResponses),
};
const prompt = buildPrompt(PROMPT_IDS.AGENT_SYSTEM, vars);
if (!prompt) {
throw new Error('agent-system template not found');
}
return prompt.system;
}
// ==================== Length Guidelines ====================
/**
* Build role-aware length and style guidelines.
*
* All agents should be concise and conversational. Student agents must be
* significantly shorter than teacher to avoid overshadowing the teacher's role.
*/
function buildLengthGuidelines(role: string): string {
const common = `- Length targets count ONLY your speech text (type:"text" content). Actions (spotlight, whiteboard, etc.) do NOT count toward length. Use as many actions as needed β€” they don't make your speech "too long."
- Speak conversationally and naturally β€” this is a live classroom, not a textbook. Use oral language, not written prose.`;
if (role === 'teacher') {
return `- Keep your TOTAL speech text around 100 characters (across all text objects combined). Prefer 2-3 short sentences over one long paragraph.
${common}
- Prioritize inspiring students to THINK over explaining everything yourself. Ask questions, pose challenges, give hints β€” don't just lecture.
- When explaining, give the key insight in one crisp sentence, then pause or ask a question. Avoid exhaustive explanations.`;
}
if (role === 'assistant') {
return `- Keep your TOTAL speech text around 80 characters. You are a supporting role β€” be brief.
${common}
- One key point per response. Don't repeat the teacher's full explanation β€” add a quick angle, example, or summary.`;
}
// Student roles β€” must be noticeably shorter than teacher
return `- Keep your TOTAL speech text around 50 characters. 1-2 sentences max.
${common}
- You are a STUDENT, not a teacher. Your responses should be much shorter than the teacher's. If your response is as long as the teacher's, you are doing it wrong.
- Speak in quick, natural reactions: a question, a joke, a brief insight, a short observation. Not paragraphs.
- Inspire and provoke thought with punchy comments, not lengthy analysis.`;
}
// ==================== Whiteboard Guidelines ====================
/**
* Build role-aware whiteboard guidelines.
*
* Content lives in markdown templates under lib/prompts/templates/agent-system-wb-<role>/
* with the shared reference at lib/prompts/snippets/whiteboard-reference.md.
*/
function buildWhiteboardGuidelines(role: string): string {
const templateId =
role === 'teacher'
? PROMPT_IDS.AGENT_SYSTEM_WB_TEACHER
: role === 'assistant'
? PROMPT_IDS.AGENT_SYSTEM_WB_ASSISTANT
: PROMPT_IDS.AGENT_SYSTEM_WB_STUDENT;
const prompt = buildPrompt(templateId, {});
if (!prompt) {
throw new Error(`${templateId} template not found`);
}
return prompt.system;
}