Skip to content

LearningAdvisor Agent

Filelib/agents/learningAgent.ts (runLearningAgent)
Triggered byapp/api/learning-recommendation/route.ts (on dashboard load)
Modelgemini-2.5-pro (Vertex AI)

What it does

Analyzes the child's quiz history across all stories they've read and generates:

  1. A difficulty recommendation — should the next story be easier, same, or harder?
  2. 3–5 personalized story theme suggestions based on what they've enjoyed reading
  3. Encouragement text — warm, specific praise shown on the dashboard
  4. A reading insight — one sentence about their strengths or growth areas

Grounding with private data (RAG)

This agent uses Retrieval-Augmented Generation with private Firestore data:

  1. API route reads the last 100 stories from users/{uid}/stories, filters to those with a lastQuizScore field, then takes the 10 most recent
  2. Extracts: story title, prompt, score, total, language, date
  3. Formats this as a structured context block passed to the agent
  4. Agent reasons over the child's actual history — not generic advice

Tools

ToolParameters
submit_recommendationdifficulty, suggestedThemes[], encouragement, readingInsight, topicFocus?

Difficulty logic

Average scoreRecommendation
≥ 80%"harder" — child is excelling
50–79%"same" — child is on track
< 50%"easier" — child needs support

Recent scores are weighted more than older ones.


Caching — avoid repeat Gemini calls

Every dashboard visit does a cheap Firestore read. The Gemini call is skipped if nothing has changed:

  1. Client reads sessionStorage key learning_rec_{uid} → shows cached recommendation instantly
  2. Client sends lastKnownQuizDate (the ISO date of the last quiz when the recommendation was cached) to the API
  3. API compares it against Firestore's latest lastQuizDate
  4. If they match → returns { upToDate: true } (no Gemini call)
  5. If a new quiz was completed → runs Gemini, returns fresh recommendation, client updates cache

Cost per repeat visit: 1 Firestore read (~0ms) + 0 Gemini tokens.


Flow

Dashboard loads


Read sessionStorage cache → show instantly if present


POST /api/learning-recommendation { userId, language, lastKnownQuizDate }

    ├─ Fetch last 100 stories → filter to quizzed → take 10 most recent

    ├─ latestQuizDate === lastKnownQuizDate?
    │       │ YES → return { upToDate: true }  (0 Gemini tokens)
    │       │
    │       ▼ NO
    │   LearningAdvisor LLM call
    │       └── submit_recommendation({
    │               difficulty: "harder",
    │               suggestedThemes: ["A brave bunny...", ...],
    │               encouragement: "You got 4/5 on...",
    │               readingInsight: "You love adventure stories!"
    │           })


Dashboard shows "Personalized for You" section
    - Encouragement text
    - 3 clickable theme buttons (→ /create?prompt=...)
    - Difficulty badge (🚀 harder / ⭐ same / 🌱 easier)

Input / Output

typescript
// Input (QuizRecord[])
{
  storyTitle: string;
  storyPrompt: string;
  score: number;
  total: number;
  language: string;
  completedAt: string; // ISO string
}[]

// Output (LearningRecommendation)
{
  difficulty: "easier" | "same" | "harder";
  suggestedThemes: string[];     // 3-5 story ideas
  encouragement: string;         // shown on dashboard
  readingInsight: string;        // 1-sentence insight
  topicFocus?: string;           // optional focus area
}

Fallback

If no quiz history exists (quizHistory.length === 0), the agent is not called. A static fallback recommendation is returned instantly with 3 generic themes.


Observability span

Span name: agent/LearningAdvisor

AttributeExample value
agent.nameLearningAdvisor
agent.modelgemini-2.5-pro
agent.languageen
agent.quiz_count7
agent.avg_score72
agent.recommendationsame
agent.duration_ms3200

Released under the MIT License.