An AI agent that generates a personalized, week-by-week learning path for any topic, and iteratively critiques and refines its own output before presenting it to the user. Built with LangGraph to explore stateful, cyclical agent orchestration (as opposed to a simple one-shot LLM call).
A second implementation of the same system, built in CrewAI, lives alongside this one as a framework comparison exercise — see Framework Comparison below, and COMPARISON.md for the full write-up.
Given a topic, a time budget (in weeks), a level (beginner / intermediate / expert), and a goal (e.g. job hunting, general learning), the system:
- Searches the web for real, current learning resources on the topic
- Plans a week-by-week sequence of those resources, respecting the time budget and level
- Generates a polished, human-readable write-up of the plan
- Critiques its own output against the original constraints, and either:
- Approves it and finalizes, or
- Sends it back to re-plan (if pacing/sequencing is the problem), or
- Sends it back to re-search (if the resources themselves are inadequate)
This loop repeats (capped at a max iteration count) until the plan is genuinely good, or the cap is hit — in which case the final output is still returned, with a disclaimer noting it may need manual review.
START → search → plan → generate → critique → (conditional routing)
↑ ↑ ├─ approve → finalize → END
└───────────┴────── replan/research ──┤
└─ max iterations → finalize → END
Built as a StateGraph with 5 nodes (search, plan, generate, critique, finalize) and one conditional edge that routes based on the critique agent's verdict.
1. Clone the repo and install dependencies
git clone https://github.com/Parul671992/Learning-path-generator.git
cd Learning-path-generator
pip install -r requirements.txt2. Get free API keys
Both notebooks call out to an LLM and a web search tool — you'll need a key for each:
| Key | Used by | Get it at |
|---|---|---|
GROQ_API_KEY |
Both notebooks (LLM inference) | console.groq.com — no credit card required |
TAVILY_API_KEY |
LangGraph notebook's search tool | tavily.com |
SERPER_API_KEY |
CrewAI notebook's search tool | serper.dev |
3. Create a .env file in the project root
GROQ_API_KEY=your_groq_key_here
TAVILY_API_KEY=your_tavily_key_here
SERPER_API_KEY=your_serper_key_here
(.env is gitignored — never commit real API keys.)
4. Run the notebooks
learning_path_generator.ipynb— the LangGraph versionlearning_path_generator_crewai.ipynb— the CrewAI version
Each is self-contained; open either in Jupyter and run all cells top to bottom. Neither depends on the other having been run first.
A few deliberate choices worth calling out — and what I learned building this:
-
Temperature varies by node role.
planandcritiqueusetemperature=0(deterministic, evaluative tasks);generateusestemperature=0.7(creative, user-facing writing). One LLM config for every node would have been simpler but worse — matching temperature to the node's job produces more reliable structured output where it matters and more natural prose where it doesn't. -
Structured output (Pydantic schemas) for anything a downstream node consumes; free text for anything a human reads.
searchandplanboth use.with_structured_output()so their results are type-safe and machine-parseable.generate's output is deliberately free text, since it's the final human-facing artifact. -
Critique calibration was the hardest part of this project, and the most instructive. My first version of the critique prompt said "be critical, not lenient" with no defined bar for "good enough" — this caused the critique agent to never approve a plan, hitting the max-iteration cap on every run. The fix was adding explicit approval criteria ("approve unless there's a genuine, significant mismatch — minor imperfections are expected"). This is a real, generalizable lesson: vague qualifiers given to an LLM produce inconsistent behavior; explicit, bounded instructions produce reliable behavior. The same fix pattern applied to
generate's tone (a soft "use emojis sparingly" instruction was replaced with a hard "do not use emojis" instruction, which was followed consistently). Iteration counts across debugging: 5/5 (never converged) → 3/3 (still capping) → 2/3 (genuine replan then approve, converges naturally). -
Search results replace rather than append on a re-search loop. When critique routes back to
searchwith feedback about poor resource quality, the new search results replace the old ones entirely rather than merging. This is a simplifying v1 assumption — a v2 could compare old vs. new and keep the better ones. -
A retry wrapper around structured-output calls. LLM outputs are probabilistic — even with a tight schema, a call can occasionally fail validation (e.g., the model returning a list where a string was expected). Rather than fixing this at the prompt level alone, I added a small retry wrapper (
invoke_with_retry) with logging, so occasional validation failures self-heal instead of crashing the whole run. This is standard practice for any system built on non-deterministic model output. -
Checkpointing (LangGraph's
SqliteSaver). The graph is compiled with a checkpointer, so state is saved after every node execution, keyed by athread_id. This isn't just plumbing — it's what would enable resuming a long-running plan generation, inspecting intermediate state, or building a human-in-the-loop review step later.
For the CrewAI-specific lessons (including five real framework bugs found and fixed), see COMPARISON.md.
resource_typeon search results is left as"unknown"— classifying it properly would need an extra LLM call per search, which I skipped for cost/latency reasons given search can run multiple times per session.- Checkpointing currently uses in-memory SQLite (
:memory:), so state doesn't persist across kernel restarts. Switching to a file-based DB is a one-line change if cross-session persistence is needed. - Single LLM provider (Groq). No fallback if Groq's API is unavailable.
- No human-in-the-loop step yet — the critique loop is fully autonomous. A natural v2 addition, enabled by the checkpointing already in place.
- Search result count is fixed at 5 regardless of the time budget requested — kept fixed intentionally to keep the LangGraph and CrewAI versions a fair, like-for-like comparison.
- LangGraph — stateful agent orchestration (LangGraph notebook)
- CrewAI — role-based agent orchestration (CrewAI notebook)
- Groq (
openai/gpt-oss-120b) — LLM inference, free tier, used by both notebooks - Tavily — web search (LangGraph notebook)
- Serper — web search (CrewAI notebook)
- Pydantic — structured output schemas
Both versions of this project are in this repo: learning_path_generator.ipynb (LangGraph) and learning_path_generator_crewai.ipynb (CrewAI).
Quick summary — full details in COMPARISON.md:
- Looping/routing: LangGraph has native conditional edges for cycles. CrewAI's sequential mode has no native equivalent — replicating the critique loop needed a manual outer Python loop. CrewAI's hierarchical mode looked like a natural fit but hit a documented, unresolved framework bug where the manager agent executes work itself instead of delegating.
- Control-flow guarantees: in both frameworks, reliable stopping/routing logic needs to live in code, not in an LLM's judgment — a lesson that showed up independently three separate times across both builds.
- Bugs found: the CrewAI build surfaced 5 distinct real bugs (a Groq compatibility issue, a loose tool schema, the hierarchical delegation bug above, a broken structured-output mechanism, and a stale task-caching bug that silently broke the critique loop for several iterations before being diagnosed).
- Persistence: LangGraph has built-in checkpointing and can resume a failed run from where it left off. CrewAI has no equivalent — a failed run restarts the entire pipeline from task 1.
See COMPARISON.md for the full architecture comparison, the debugging log, and the final verdict.
