Portfolio Projects · Project 2 of 10

Project 2: Multi-Agent Research Assistant (LangGraph)

🛠️ Intermediate⏱ 1–2 weekends📄 Resume lines included

Agents are the hottest interview topic of 2025–26. You'll build a supervisor + specialist agents system that researches a topic, writes a report with sources, and has a reviewer agent check it, with step limits, tracing and a human-approval step.

What you'll build

Architecture

User → Supervisor (LangGraph state machine)
          ├─ planner  → sub-questions
          ├─ researcher → tools: web_search, fetch_page, vector_search  (max 6 tool calls)
          ├─ writer   → draft report with [n] citations
          └─ reviewer → pass / revise (max 2 loops) → human approval → final report
State: {question, plan[], notes[], draft, review, steps, sources[]}

Tech stack

LangGraph, LangChain tools, Tavily or DuckDuckGo search, an LLM with tool calling, Pydantic for structured outputs, LangSmith or Langfuse for tracing, Streamlit for the UI.

Step by step

  1. Define typed state with Pydantic or a TypedDict.
  2. Planner node: structured output gives a list of 3–5 sub-questions.
  3. Researcher node: a tool-calling loop with a max-steps guard; save notes with source URLs.
  4. Writer node: compose the report using only the notes, citing [n].
  5. Reviewer node: a checklist of claims supported, coverage, clarity; it returns pass or revise with feedback.
  6. Conditional edges: revise goes back to the writer (at most 2 times); pass goes to human approval (an interrupt).
  7. Tracing: log tokens, latency and cost per node.
  8. Evaluate on 10 topics: citation accuracy, factual errors found, time and cost.

Starter code

from typing import TypedDict, List
from langgraph.graph import StateGraph, END
class S(TypedDict): question: str; plan: List[str]; notes: List[str]; draft: str; review: str; loops: int

def planner(s):    s["plan"] = plan_llm(s["question"]); return s
def researcher(s): s["notes"] = [research(q) for q in s["plan"]]; return s
def writer(s):     s["draft"] = write_llm(s["question"], s["notes"], s.get("review", "")); return s
def reviewer(s):   s["review"] = review_llm(s["draft"], s["notes"]); s["loops"] = s.get("loops", 0) + 1; return s
route = lambda s: "writer" if s["review"].startswith("REVISE") and s["loops"] < 2 else END

g = StateGraph(S)
for n, f in [("planner", planner), ("researcher", researcher), ("writer", writer), ("reviewer", reviewer)]: g.add_node(n, f)
g.set_entry_point("planner"); g.add_edge("planner", "researcher"); g.add_edge("researcher", "writer"); g.add_edge("writer", "reviewer")
g.add_conditional_edges("reviewer", route, {"writer": "writer", END: END})
app = g.compile()
print(app.invoke({"question": "Compare vector databases for a startup"})["draft"])

Deploy it

Use a Streamlit UI showing each agent's progress live (stream graph events), hosted on Hugging Face Spaces. Store API keys in secrets. Record a 60-second demo video for LinkedIn.

Put it on your resume

Interview questions you'll get

Practise these in Mock Interview → Agents & MCP.

Stretch goals

Created by Ajeetkumar