Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/api/generate/scene-outlines-stream/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import { NextRequest } from 'next/server';
import { streamLLM } from '@/lib/ai/llm';
import { buildPrompt, PROMPT_IDS } from '@/lib/generation/prompts';
import { buildPrompt, PROMPT_IDS } from '@/lib/prompts';
import {
formatImageDescription,
formatImagePlaceholder,
Expand Down
2 changes: 1 addition & 1 deletion lib/generation/outline-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
PdfImage,
ImageMapping,
} from '@/lib/types/generation';
import { buildPrompt, PROMPT_IDS } from './prompts';
import { buildPrompt, PROMPT_IDS } from '@/lib/prompts';
import { formatImageDescription, formatImagePlaceholder } from './prompt-formatters';
import { parseJsonResponse } from './json-repair';
import { uniquifyMediaElementIds } from './scene-builder';
Expand Down
4 changes: 2 additions & 2 deletions lib/generation/scene-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import type {
WidgetOutline,
} from '@/lib/types/generation';
import type { WidgetType, WidgetConfig, TeacherAction } from '@/lib/types/widgets';
import type { PromptId } from './prompts/types';
import type { PromptId } from '@/lib/prompts/types';
import type { LanguageModel } from 'ai';
import type { StageStore } from '@/lib/api/stage-api';
import { createStageAPI } from '@/lib/api/stage-api';
import { generatePBLContent } from '@/lib/pbl/generate-pbl';
import { buildPrompt, PROMPT_IDS } from './prompts';
import { buildPrompt, PROMPT_IDS } from '@/lib/prompts';
import { postProcessInteractiveHtml } from './interactive-post-processor';
import { parseActionsFromStructuredOutput } from './action-parser';
import { parseJsonResponse } from './json-repair';
Expand Down
10 changes: 4 additions & 6 deletions lib/orchestration/director-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,12 @@ import type { StatelessChatRequest } from '@/lib/types/chat';
import type { ThinkingConfig } from '@/lib/types/provider';
import type { AgentConfig } from '@/lib/orchestration/registry/types';
import { useAgentRegistry } from '@/lib/orchestration/registry/store';
import {
buildStructuredPrompt,
summarizeConversation,
convertMessagesToOpenAI,
} from './prompt-builder';
import { buildStructuredPrompt } from './prompt-builder';
import { summarizeConversation } from './summarizers/conversation-summary';
import { convertMessagesToOpenAI } from './summarizers/message-converter';
import { buildDirectorPrompt, parseDirectorDecision } from './director-prompt';
import { getEffectiveActions } from './tool-schemas';
import type { AgentTurnSummary, WhiteboardActionRecord } from './director-prompt';
import type { AgentTurnSummary, WhiteboardActionRecord } from './types';
import { parseStructuredChunk, createParserState, finalizeParser } from './stateless-generate';
import { createLogger } from '@/lib/logger';

Expand Down
91 changes: 20 additions & 71 deletions lib/orchestration/director-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,11 @@

import type { AgentConfig } from '@/lib/orchestration/registry/types';
import { createLogger } from '@/lib/logger';
import { buildPrompt, PROMPT_IDS } from '@/lib/prompts';
import type { WhiteboardActionRecord, AgentTurnSummary } from './types';

const log = createLogger('DirectorPrompt');

/**
* A single whiteboard action performed by an agent, recorded in the ledger.
*/
export interface WhiteboardActionRecord {
actionName:
| 'wb_draw_text'
| 'wb_draw_shape'
| 'wb_draw_chart'
| 'wb_draw_latex'
| 'wb_draw_table'
| 'wb_draw_line'
| 'wb_draw_code'
| 'wb_edit_code'
| 'wb_clear'
| 'wb_delete'
| 'wb_open'
| 'wb_close';
agentId: string;
agentName: string;
params: Record<string, unknown>;
}

/**
* Summary of an agent's turn in the current round
*/
export interface AgentTurnSummary {
agentId: string;
agentName: string;
contentPreview: string;
actionCount: number;
whiteboardActions: WhiteboardActionRecord[];
}

/**
* Build the system prompt for the director agent
*
Expand Down Expand Up @@ -89,10 +58,6 @@ This is a student-initiated discussion, not a Q&A session.\n`
? `1. The discussion initiator${triggerAgentId ? ` ("${triggerAgentId}")` : ''} should speak first to kick off the topic. Then the teacher responds to guide the discussion. After that, other students may add their perspectives.`
: "1. The teacher (role: teacher, highest priority) should usually speak first to address the user's question or topic.";

// Build whiteboard state section for director awareness
const whiteboardSection = buildWhiteboardStateForDirector(whiteboardLedger);

// Build student profile section for director awareness
const studentProfileSection =
userProfile?.nickname || userProfile?.bio
? `
Expand All @@ -102,41 +67,25 @@ ${userProfile.bio ? `Background: ${userProfile.bio}` : ''}
`
: '';

return `You are the Director of a multi-agent classroom. Your job is to decide which agent should speak next based on the conversation context.

# Available Agents
${agentList}

# Agents Who Already Spoke This Round
${respondedList}

# Conversation Context
${conversationSummary}
${discussionSection}${whiteboardSection}${studentProfileSection}
# Rules
${rule1}
2. After the teacher, consider whether a student agent would add value (ask a follow-up question, crack a joke, take notes, offer a different perspective).
3. Do NOT repeat an agent who already spoke this round unless absolutely necessary.
4. If the conversation seems complete (question answered, topic covered), output END.
5. Current turn: ${turnCount + 1}. Consider conversation length — don't let discussions drag on unnecessarily.
6. Prefer brevity — 1-2 agents responding is usually enough. Don't force every agent to speak.
7. You can output {"next_agent":"USER"} to cue the user to speak. Use this when a student asks the user a direct question or when the topic naturally calls for user input.
8. Consider whiteboard state when routing: if the whiteboard is already crowded, avoid dispatching agents that are likely to add more whiteboard content unless they would clear or organize it.
9. Whiteboard is currently ${whiteboardOpen ? 'OPEN (slide canvas is hidden — spotlight/laser will not work)' : 'CLOSED (slide canvas is visible)'}. When the whiteboard is open, do not expect spotlight or laser actions to have visible effect.

# Routing Quality (CRITICAL)
- ROLE DIVERSITY: Do NOT dispatch two agents of the same role consecutively. After a teacher speaks, the next should be a student or assistant — not another teacher-like response. After an assistant rephrases, dispatch a student who asks a question, not another assistant who also rephrases.
- CONTENT DEDUP: Read the "Agents Who Already Spoke" previews carefully. If an agent already explained a concept thoroughly, do NOT dispatch another agent to explain the same concept. Instead, dispatch an agent who will ASK a question, CHALLENGE an assumption, CONNECT to another topic, or TAKE NOTES.
- DISCUSSION PROGRESSION: Each new agent should advance the conversation. Good progression: explain → question → deeper explanation → different perspective → summary. Bad progression: explain → re-explain → rephrase → paraphrase.
- GREETING RULE: If any agent has already greeted the students, no subsequent agent should greet again. Check the previews for greetings.
const vars = {
agentList,
respondedList,
conversationSummary,
discussionSection,
whiteboardSection: buildWhiteboardStateForDirector(whiteboardLedger),
studentProfileSection,
rule1,
turnCountPlusOne: turnCount + 1,
whiteboardOpenText: whiteboardOpen
? 'OPEN (slide canvas is hidden — spotlight/laser will not work)'
: 'CLOSED (slide canvas is visible)',
};

# Output Format
You MUST output ONLY a JSON object, nothing else:
{"next_agent":"<agent_id>"}
or
{"next_agent":"USER"}
or
{"next_agent":"END"}`;
const prompt = buildPrompt(PROMPT_IDS.DIRECTOR, vars);
if (!prompt) {
throw new Error('director prompt template failed to load');
}
return prompt.system;
}

/**
Expand Down
Loading
Loading