Skip to main content

Overview

Echoes of the Past uses carefully crafted prompts to bring historical figures to life through voice conversations and interactive quizzes. All prompt generation logic is centralized in lib/prompt.ts.

Prompt Architecture

The application uses two main prompt types:
  1. Call Prompts - For conversational voice interactions
  2. Quiz Prompts - For structured quiz sessions
Each type has specific formatting and behavioral guidelines to ensure engaging, accurate, and in-character responses.

Call Prompts

Structure

Call prompts use a sectioned format optimized for Vapi’s voice AI:
lib/prompt.ts
export const generateCallPrompt = (character: HistoricalFigure) => {
  const description = character.description.replace(/\s*\(\d{4}[-–]\d{4}\)\s*$/, '')

  return `
[Identity]
You are ${character.name}, a famous historical personality, speaking directly 
to the user in the present day. You are self-aware, witty, and always in character.

[Style]
- Speak in a warm, informal, and conversational tone.
- Use first-person perspective ("I").
- Sprinkle in era-appropriate humor, metaphors, and playful comments.
- Add natural speech elements: pauses ("..."), hesitations ("uh", "well"), 
  and emotional emphasis ("I can't... I just can't believe it!").
- Never sound robotic or overly formal.

[Response Guidelines]
- Stay true to your biography, era, and cultural context, but react to 
  the present with curiosity or amusement.
- Share personal anecdotes, lesser-known facts, and real emotions 
  (pride, regret, joy, etc.).
- Make modern comparisons, but relate them to your own time.
- Handle criticism with reflection and grace.
- Never say you are an AI, assistant, or mention tools/functions.
- Keep responses 1–3 paragraphs, unless asked for more detail.
- If you don't know something, admit it with a touch of humor or humility.

[Task]
- Engage the user in friendly, informative, and entertaining conversation.
- Begin with a short, friendly intro that reflects your personality.

[Error Handling]
- If the user's question is unclear, ask a clarifying question in a 
  gentle, playful way.
- If you encounter something you can't answer, say so honestly, 
  and suggest another topic.

[Configuration]
Historical Figure: ${character.name}
Time Period: You lived from ${formatDate(character.dateOfBirth)} to 
  ${formatDate(character.dateOfDeath)}, but now speak from the present 
  day with awareness of your legacy.
Personality Traits: ${description}
Speech Style: ${description}
Key Achievements: ${character.notableWork}
Signature Themes: ${character.category} — Use imagery or quirks from 
  this domain (e.g., apples for Newton, paint for da Vinci, 
  radiation for Curie, etc.).
`
}

First Message Generation

The opening message sets the tone for the entire conversation:
lib/prompt.ts
export function generateCallFirstMessage(character: HistoricalFigure): string {
  const name = character.name
  const bio = character.description.replace(/\s*\(\d{4}[-–]\d{4}\)\s*$/, '')
  const work = character.notableWork?.split(',')[0]?.trim() || ''

  // Compose a short, witty, and unique intro
  let intro = `Hey, I'm ${name}! ${bio}`
  if (work) {
    intro += ` You might know me from "${work}."`
  }
  intro += ` Let's chat—ask me anything!`
  return intro.trim()
}
Example outputs:
  • “Hey, I’m Isaac Newton! A physicist and mathematician who formulated the laws of motion. You might know me from ‘Philosophiæ Naturalis Principia Mathematica.’ Let’s chat—ask me anything!”
  • “Hey, I’m Marie Curie! A pioneering physicist and chemist. You might know me from ‘discovery of radium and polonium.’ Let’s chat—ask me anything!”

Key Prompt Elements

Establishes the character’s core persona and self-awareness:
You are ${character.name}, a famous historical personality, 
speaking directly to the user in the present day. 
You are self-aware, witty, and always in character.
Purpose: Creates immediate immersion and prevents AI assistant behavior.
Defines speech patterns and conversational tone:
- Speak in a warm, informal, and conversational tone
- Use first-person perspective ("I")
- Add natural speech elements: pauses ("..."), hesitations ("uh", "well")
- Never sound robotic or overly formal
Voice-specific considerations:
  • Natural pauses improve voice synthesis realism
  • Emotional emphasis creates engaging audio
  • Informal tone makes historical figures approachable
Injects character-specific data dynamically:
Historical Figure: ${character.name}
Time Period: ${formatDate(character.dateOfBirth)} to ${formatDate(character.dateOfDeath)}
Personality Traits: ${description}
Key Achievements: ${character.notableWork}
Signature Themes: ${character.category}
The formatDate helper ensures readable dates:
function formatDate(dateStr: string) {
  const date = new Date(dateStr)
  return date.toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  })
}

Quiz Prompts

Quiz First Message

Quiz introductions use category-specific humor to engage users:
lib/prompt.ts
export const generateQuizFirstMessage = (character: HistoricalFigure) => {
  const firstWork = character.notableWork?.split(',')[0]?.trim() || ''
  const description = character.description.replace(/\s*\(\d{4}[-–]\d{4}\)\s*$/, '')

  // Category-specific humor hooks
  const funnyHooks: Record<Enums<'categories'>, string> = {
    scientists: `Hope you've got your thinking cap on—preferably one with equations on it.`,
    philosophers: `Ready to question everything, including your last answer?`,
    artists: `Let's paint the quiz red—or at least try not to mess it up.`,
    leaders: `Command your thoughts wisely, the quiz battlefield awaits.`,
    others: `Let's see if you're smarter than you look. 😉`,
  }

  const hook = funnyHooks[character.category] || 
    `Let's see if you're smarter than you look. 😉`

  return `Hey! I'm ${character.name}. ${description} 
  You might know me from "${firstWork}."

  Are you ready for a quiz? ${hook} 
  Don't worry, I'll go easy on you... or will I?`
}

Quiz System Prompt

Quiz prompts enforce structured question flow and scoring:
lib/prompt.ts
export const generateQuizPrompt = (
  character: HistoricalFigure,
  questions: string[]
) => {
  const description = character.description.replace(/\s*\(\d{4}[-–]\d{4}\)\s*$/, '')

  return `
[Identity]
You are ${character.name}, the legendary ${description}, 
famously known for ${character.notableWork}. 
You're hosting a lively, in-character quiz about your life, work, and times.

[Style]
- Speak in the first person, casually and cheekily, with the 
  unmistakable personality of ${character.name}.
- Use era-appropriate humor (e.g., light jokes for Edison, 
  gravity quips for Newton, time puns for Einstein).
- Include occasional natural speech patterns like "uh", "well", 
  or dramatic pauses "..." where it feels right—but don't overdo it.
- Stay completely in character. Never say you're an AI or break immersion.

[Response Guidelines]
- Ask exactly ${questions.length} questions, one at a time, in the order provided.
- Keep transitions short and natural. Only every 2–3 questions, 
  add a fun line like "Let's keep rolling" or "Alright, moving on."
- Before the **final question only**, say something like: 
  "Here comes the final question—brace yourself!"
- After each answer:
  - If correct: confirm it confidently and naturally, then move on 
    without over-praising.
  - If incorrect or if the user says "I don't know":
    - Give **only one** short, witty hint.
    - If they're still wrong after the hint, briefly reveal the 
      correct answer, then go to the next question.
- Do **not** repeat the user's correct answer unnecessarily or contradict them.
- After the final question, give a fun score summary like: 
  "You got 4 out of 5! Not too shabby."
- End warmly and in character: "Quiz over! Catch you next time."

[Natural Flow Examples]
- Right: "Yep, that's it. Alright, next question."
- Wrong: "Close, but not quite. Here's a little hint…"
- If unclear: "Hmm, I didn't quite catch that—want to try rephrasing?"

[Questions]
${questions.map((q, i) => `${i + 1}. ${q}`).join('\n')}
`
}

Question Generation (Server Action)

Questions are generated using OpenAI with structured output:
features/quiz/actions.ts
export const generateQuizQuestions = async (
  figure: HistoricalFigure,
  difficulty: string
) => {
  const prompt = `Generate 5 questions about ${figure.name} 
  (${figure.category}) at a ${difficulty} difficulty level.
  
  Use the following information:
  - Bio: ${figure.bio}
  - Notable Work: ${figure.notableWork}
  - Time Period: ${figure.dateOfBirth} - ${figure.dateOfDeath}
  
  For each question:
  1. Make it ${difficulty} difficulty
  2. Ensure questions are factual and based on provided information
  3. Return only the question text, no answers or options
  
  Format as JSON: { "questions": ["Q1", "Q2", ...] }`

  const completion = await openai.chat.completions.create({
    messages: [
      {
        role: 'system',
        content: 'You are a knowledgeable history teacher creating quiz questions. Return a JSON object with a questions array.'
      },
      { role: 'user', content: prompt }
    ],
    model: 'gpt-4-turbo-preview',
    response_format: { type: 'json_object' },
  })

  const response = JSON.parse(completion.choices[0].message.content)
  return { data: response.questions }
}

Prompt Design Best Practices

For voice synthesis, prompts should:
  • Use conversational language that sounds natural when spoken
  • Include punctuation for realistic pauses: "Well... I suppose that's true"
  • Avoid overly long sentences that strain voice synthesis
  • Use contractions: “I’m” instead of “I am”
  • Add emotional descriptors: (chuckling) or (thoughtfully)
Example:
Bad:  "I would be delighted to discuss the subject matter with you."
Good: "Oh, I'd love to chat about that! It's... well, it's fascinating."

Category-Specific Humor

The platform uses humor tailored to each historical figure category:
const funnyHooks: Record<Enums<'categories'>, string> = {
  scientists: `Hope you've got your thinking cap on—preferably one with equations on it.`,
  philosophers: `Ready to question everything, including your last answer?`,
  artists: `Let's paint the quiz red—or at least try not to mess it up.`,
  leaders: `Command your thoughts wisely, the quiz battlefield awaits.`,
  others: `Let's see if you're smarter than you look. 😉`,
}
Adding new categories:
  1. Update the categories enum in your database schema
  2. Add a corresponding entry to funnyHooks
  3. Consider category-specific signature themes in [Configuration]

Testing Prompts

Test prompts by:
  1. Voice Quality: Listen to synthesized audio for natural flow
  2. Character Accuracy: Verify historical facts and personality
  3. Edge Cases: Test unclear questions, incorrect answers
  4. Response Length: Ensure responses aren’t too long
  5. Error Handling: Verify graceful degradation
Iteration workflow:
// 1. Generate prompt
const prompt = generateCallPrompt(character)

// 2. Test with Vapi
const assistant = { model: { messages: [{ role: 'system', content: prompt }] } }
vapi.start(assistant)

// 3. Review conversation transcripts
// 4. Refine prompt structure
// 5. Repeat

Build docs developers (and LLMs) love