Skip to content

Repository files navigation

Autonomous Curriculum Generator

A multi-agent system that autonomously generates CEFR-aligned language learning curricula (Vocabulary + Grammar) as strictly-typed JSON. Built with LangGraph, grounded via a RAG pipeline backed by Qdrant.

Input: target language + CEFR level (e.g. Spanish, A2)
Output: vocabulary.json + grammar.json — clean, deduplicated, Pydantic-validated


Quick Start

# 1. Clone and install
git clone <repo-url>
cd autonomous-curriculum-generator
pip install -r requirements.txt

# 2. Configure
cp .env.example .env
# Add your GEMINI_API_KEY (free at https://aistudio.google.com)

# 3. Ingest mock CEFR data into Qdrant (one-time setup)
python scripts/ingest_qdrant.py

# 4. Run
python main.py --language Spanish --level A2

# Output files:
#   outputs/Spanish_A2_vocabulary.json
#   outputs/Spanish_A2_grammar.json

Architecture Diagram

flowchart TD
    INPUT(["`**Input**
    language + level`"]) --> CR

    CR["**1 · Context Retriever**
    Qdrant RAG
    ─────────────────
    embed query → filter by
    language + level → cosine
    rerank → top-4 snippets"]

    CR --> CA

    CA["**2 · Curriculum Architect**
    gemini-1.5-pro
    ─────────────────
    Draft vocabulary + grammar
    from RAG context only"]

    CA --> PR

    PR["**3 · Pedagogical Reviewer**
    gemini-1.5-pro
    ─────────────────
    Check level appropriateness
    Validate sequencing
    Flag negative constraints"]

    PR -->|"pedagogy_errors ≠ ∅
    AND iter ≤ 3"| RF
    PR -->|"no errors"| FG

    FG["**4 · Format & Quality Guard**
    gemini-1.5-flash + Pydantic
    ─────────────────
    Parse + validate schema
    Deduplicate items
    Verify prerequisite DAG"]

    FG -->|"format_errors ≠ ∅
    AND iter ≤ 3"| RF
    FG -->|"all clear"| END

    RF["**5 · Refiner**
    gemini-1.5-pro
    ─────────────────
    Patch listed errors only
    Preserve base curriculum
    iter_count += 1"]

    RF --> PR

    PR -..->|"iter > 3 · fallback"| END
    FG -..->|"iter > 3 · fallback"| END

    END(["**Output**
    vocabulary.json
    grammar.json"])

    style INPUT fill:#E1F5EE,stroke:#0F6E56,color:#085041
    style END   fill:#E1F5EE,stroke:#0F6E56,color:#085041
    style CR    fill:#EAF3DE,stroke:#3B6D11,color:#173404
    style CA    fill:#EEEDFE,stroke:#534AB7,color:#26215C
    style PR    fill:#EEEDFE,stroke:#534AB7,color:#26215C
    style FG    fill:#FAEEDA,stroke:#854F0B,color:#412402
    style RF    fill:#EEEDFE,stroke:#534AB7,color:#26215C
Loading

Agent Responsibilities

Agent Model Responsibility
Context Retriever Embeds query, filters Qdrant by language + level, cosine-reranks to top-4 snippets
Curriculum Architect gemini-1.5-pro Drafts the full vocabulary and grammar curriculum grounded in RAG context
Pedagogical Reviewer gemini-1.5-pro Validates level appropriateness; catches hallucinated upper-level structures (e.g. C1 subjunctive in an A2 task)
Format & Quality Guard gemini-1.5-flash + Pydantic Enforces strict JSON schema, deduplication, and prerequisite DAG validity
Refiner gemini-1.5-pro Surgically patches flagged errors without discarding the base curriculum

Routing Logic

  • Happy path: Retriever → Architect → Reviewer (no errors) → Guard (no errors) → END
  • Pedagogy error: → Refiner → Reviewer (re-evaluated)
  • Format error: → Refiner → Reviewer → Guard (re-evaluated)
  • Safety fallback: After 3 refinement iterations, the system exits with the last Pydantic-valid JSON — preferring a structurally sound output over an infinite loop.

Engineering Justification

LangGraph was chosen over alternatives such as CrewAI and Claude Agent SDK because it offers first-class support for typed, inspectable state machines — a critical property when the system must make routing decisions based on structured error lists rather than free-form agent dialogue. Unlike CrewAI's agent-to-agent communication model, LangGraph's explicit conditional edges make the fallback logic (escape after three iterations) trivially verifiable and independently testable. The framework's native integration with LangChain's google-generativeai adapter also allowed us to configure different model tiers per agent (Pro for reasoning, Flash for schema enforcement) without restructuring the graph.

Context window pressure is managed in two ways: the RAG pipeline distils authoritative CEFR references into 3–4 tightly filtered snippets — avoiding full-document injection — and each agent receives only the state fields it needs rather than the full conversation history. JSON output integrity is guaranteed through a two-layer defence: the Curriculum Architect is prompted to respond exclusively in a schema-anchored JSON block, and the Format & Quality Guard independently parses that block against Pydantic models with strict field validation, deduplication checks, and DAG cycle detection; any violation raises a structured format_errors list that routes back to the Refiner, ensuring the final output is always Pydantic-valid before it reaches END.


Production Architecture Vision

The demo uses a mock CSV and Google's gemini-embedding-001 to stay within free-tier limits. A production deployment would use the following pipeline:

Stage 1 — Extract (Authoritative Sources)

Language Source
Spanish Instituto Cervantes — Plan Curricular (PCIC)
English Cambridge EVP / EGP
French Alliance Française reference documents
German Goethe-Institut level lists
All Council of Europe — CEFR Companion Volume

Stage 2 — Transform (Unified Schema)

All sources are normalised to a single payload schema before ingestion:

{
  "id": "e4b3d-...",
  "vector": [...],
  "payload": {
    "language": "es",
    "level": "A2",
    "domain": "grammar",
    "topic": "past_tense",
    "content": "Pretérito Indefinido: Expresses completed past actions with regular verbs.",
    "source": "instituto_cervantes",
    "is_negative_constraint": false
  }
}

Stage 3 — Multilingual Embedding

  • Model: Qwen3-Embedding-4B served via vLLM (replaces gemini-embedding-001)
  • Vector space: single Qdrant collection cefr_global_standards — all languages co-located, partitioned strictly via metadata filters (no separate collections per language)

Stage 4 — Retrieval with Strict Metadata Filtering

results = client.search(
    collection_name="cefr_global_standards",
    query_vector=embedding_vector,
    query_filter=models.Filter(
        must=[
            models.FieldCondition(key="language", match=models.MatchValue(value="es")),
            models.FieldCondition(key="level",    match=models.MatchValue(value="A2")),
        ]
    ),
    limit=20  # top-20 → Qwen3-Reranker-4B → top-4
)

Semantic search alone is insufficient for a deterministic system. Metadata filtering guarantees that an A2 Spanish generation task never receives B1 Spanish grammar or English C1 vocabulary as context.


Output Format

outputs/{Language}_{Level}_vocabulary.json

{
  "language": "Spanish",
  "level": "A2",
  "generated_at": "2026-04-01T14:32:00Z",
  "total_items": 60,
  "items": [
    {
      "word": "hablar",
      "part_of_speech": "verb",
      "translation_en": "to speak / to talk",
      "example_sentence": "Ella habla español muy bien.",
      "topic_domain": "communication",
      "cefr_level": "A2"
    }
  ]
}

outputs/{Language}_{Level}_grammar.json

{
  "language": "Spanish",
  "level": "A2",
  "generated_at": "2026-04-01T14:32:00Z",
  "total_points": 12,
  "points": [
    {
      "id": "es_a2_grammar_001",
      "title": "Presente de Indicativo — Regular Verbs",
      "description": "Describes habitual actions and current states using regular -ar, -er, -ir verbs.",
      "prerequisite_ids": [],
      "examples": ["Yo hablo español todos los días.", "Ella come una manzana."],
      "negative_constraints": ["No subjunctive", "No irregular stem-changing verbs (B1+)"],
      "cefr_level": "A2"
    }
  ]
}

Project Structure

.
├── CLAUDE.md                  ← Claude Code guide
├── README.md                  ← this file
├── requirements.txt
├── .env.example
├── config.py                  ← all settings; nothing hardcoded in agents
├── main.py                    ← entrypoint
├── schema/models.py           ← Pydantic models (VocabularyCurriculum, GrammarCurriculum)
├── agent/
│   ├── state.py               ← CurriculumState TypedDict
│   ├── nodes.py               ← 5 agent functions
│   └── graph.py               ← LangGraph graph + routing
├── rag/retriever.py           ← embed → filter → rerank
├── scripts/ingest_qdrant.py   ← one-time data ingestion
├── data/mock_cefr.csv         ← mock CEFR reference data (Spanish A2)
├── outputs/                   ← generated JSONs (git-ignored)
└── tests/
    ├── test_models.py         ← Pydantic validation tests
    └── test_graph.py          ← routing logic tests (mocked LLMs)

Running Tests

# All tests (no API calls — LLMs are mocked)
pytest tests/ -v

# Pydantic model validation only
pytest tests/test_models.py -v

# Graph routing logic only
pytest tests/test_graph.py -v

About

LangGraph multi-agent system that generates CEFR-aligned language curricula as strictly-typed JSON, grounded by RAG over Qdrant.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages