Module 8.4 — Multi-Agent Systems with LangGraph

Multiple specialized agents collaborating to solve complex tasks


Why Multi-Agent Systems

One agent with many tools gets confused. It tries to do everything and does nothing well.

Single agent approach:
Agent has 20 tools → confused about which to use
Long tasks → context window fills up with tool calls
Complex tasks → agent loses track of what it's doing
Quality suffers on every individual sub-task

Multi-agent approach:
Research Agent  → only knows how to search and gather info
Writer Agent    → only knows how to write and format
Math Agent      → only knows how to calculate
Supervisor      → decides who does what and when

Each agent is focused → better at its specific job
Complex tasks broken into parts → each part handled by specialist

The Supervisor Pattern — How It Works

User Task
    ↓
SUPERVISOR AGENT
→ Reads the task
→ Decides which specialist to delegate to
→ Sends task to specialist
    ↓
SPECIALIST AGENT (research / math / writing)
→ Does its specific job
→ Returns result to supervisor
    ↓
SUPERVISOR AGENT
→ Reviews result
→ Decides next step:
    → Send to another specialist?
    → Task is done → give final answer?
    ↓
Final Answer to User

Project Setup


    mkdir multi-agent-demo
    cd multi-agent-demo
    npm init -y

Update package.json:


  {
    "name": "multi-agent-demo",
    "version": "1.0.0",
    "type": "module",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "dependencies": {
      "@langchain/core": "^1.2.4",
      "@langchain/langgraph": "^1.4.9",
      "@langchain/langgraph-supervisor": "^1.1.1",
      "@langchain/openai": "^1.5.5",
      "dotenv": "^17.4.2",
      "langchain": "^1.5.4",
      "zod": "^4.4.3"
    }
  }

Install packages — note the exact imports from official docs:


    npm install @langchain/langgraph-supervisor @langchain/langgraph @langchain/core @langchain/openai langchain zod dotenv

@langchain/langgraph-supervisor → createSupervisor function
@langchain/langgraph/prebuilt   → createReactAgent for worker agents
@langchain/core/tools           → tool() function

Create .env:


    OPENAI_API_KEY=sk-proj-your-key-here


Part 1 — Basic Supervisor with Two Agents

Create src/01_basic_supervisor.js:


    import { ChatOpenAI } from "@langchain/openai";
    // ChatOpenAI = the LLM — used by both supervisor and worker agents

    import { createSupervisor } from "@langchain/langgraph-supervisor";
    // createSupervisor = creates a supervisor that routes between worker agents
    // returns a StateGraph (not yet compiled — call .compile() on it)

    import { createReactAgent } from "@langchain/langgraph/prebuilt";
    // createReactAgent = creates a worker agent with the ReAct pattern
    // each worker has its own tools and system prompt
    // note: this is DIFFERENT from createAgent from "langchain"
    // createReactAgent is from langgraph/prebuilt — more low-level, better for multi-agent

    import { tool } from "@langchain/core/tools";
    // tool = wraps a JS function into a LangChain tool
    // note: import from "@langchain/core/tools" not from "langchain"
    // both work but @langchain/core/tools is the canonical import in 2025

    import { z } from "zod";
    import { MemorySaver } from "@langchain/langgraph";
    import * as dotenv from "dotenv";
    dotenv.config();


    // ─────────────────────────────────────────
    // SHARED LLM
    // Both supervisor and worker agents use this
    // ─────────────────────────────────────────

    const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
    // temperature: 0 = deterministic — important for routing decisions
    // supervisor must consistently route to the right agent


    // ─────────────────────────────────────────
    // MATH AGENT TOOLS
    // Only math-related tools — no research
    // ─────────────────────────────────────────

    const addTool = tool(
        async (args) => {
            // args = { a: number, b: number }
            return String(args.a + args.b);
            // return string — LLM reads text, not numbers
            // example: args = { a: 25, b: 48 } → returns "73"
        },
        {
            name: "add",
            description: "Adds two numbers together.",
            schema: z.object({
                a: z.number().describe("first number"),
                b: z.number().describe("second number"),
            }),
        }
    );

    const multiplyTool = tool(
        async (args) => {
            return String(args.a * args.b);
            // example: args = { a: 1847, b: 23 } → returns "42481"
        },
        {
            name: "multiply",
            description: "Multiplies two numbers.",
            schema: z.object({
                a: z.number().describe("first number"),
                b: z.number().describe("second number"),
            }),
        }
    );

    const percentageTool = tool(
        async (args) => {
            const result = (args.value * args.percentage) / 100;
            return `${args.percentage}% of ${args.value} = ${result}`;
            // example: args = { value: 8500, percentage: 15 }
            // returns: "15% of 8500 = 1275"
        },
        {
            name: "calculate_percentage",
            description: "Calculates a percentage of a value.",
            schema: z.object({
                value: z.number().describe("the base value"),
                percentage: z.number().describe("the percentage to calculate"),
            }),
        }
    );


    // ─────────────────────────────────────────
    // RESEARCH AGENT TOOLS
    // Only research/lookup tools — no math
    // ─────────────────────────────────────────

    const searchTool = tool(
        async (args) => {
            // Simulated search — in production use Tavily, SerpAPI, etc.
            console.log(`   🔍 Research Agent searching: "${args.query}"`);

            const knowledge = {
                "FAANG salaries India 2025":
                    "Average FAANG software engineer salary in India: Google ₹45-85 LPA, Meta ₹40-75 LPA, Amazon ₹35-65 LPA, Apple ₹40-70 LPA, Netflix ₹50-90 LPA.",
                "LangChain vs LangGraph":
                    "LangChain provides high-level components (chains, agents, tools). LangGraph is a stateful graph framework for complex agent workflows. LangGraph is built by the same team and is now the recommended way to build production agents.",
                "AI agent market size":
                    "The AI agent market is projected to reach $28.5 billion by 2028, growing at 43% CAGR. Enterprise adoption is driven by automation and productivity gains.",
                "React vs Next.js":
                    "React is a UI library. Next.js is a full-stack framework built on React. Next.js adds server-side rendering, file-based routing, and API routes. Use Next.js for production web applications.",
            };

            // find a matching knowledge entry
            const matchKey = Object.keys(knowledge).find(k =>
                args.query.toLowerCase().includes(k.toLowerCase().split(" ")[0])
            );

            return knowledge[matchKey] || `Research results for "${args.query}": This topic involves multiple factors. Key points: [simulated research data about ${args.query}]`;
        },
        {
            name: "web_search",
            description: "Searches the web for factual information, current data, news, and research.",
            schema: z.object({
                query: z.string().describe("the search query"),
            }),
        }
    );

    const getFactTool = tool(
        async (args) => {
            console.log(`   📚 Research Agent looking up: "${args.topic}"`);
            return `Key facts about "${args.topic}": [simulated factual information retrieved from knowledge base about this topic]`;
        },
        {
            name: "get_fact",
            description: "Gets specific factual information about a topic from a knowledge base.",
            schema: z.object({
                topic: z.string().describe("topic to get facts about"),
            }),
        }
    );


    // ─────────────────────────────────────────
    // CREATE WORKER AGENTS
    // Each agent is specialized — narrow focus
    // ─────────────────────────────────────────

    const mathAgent = createReactAgent({
        // createReactAgent from "@langchain/langgraph/prebuilt"
        // creates a ReAct agent — think, act, observe loop
        // lower level than createAgent from "langchain"
        // required for multi-agent supervisor pattern

        llm: model,
        // the language model this agent uses

        tools: [addTool, multiplyTool, percentageTool],
        // ONLY math tools — this agent cannot search the web
        // focused agent = better at its specific job

        name: "math_expert",
        // name = how the supervisor refers to this agent
        // supervisor will say "I'll send this to math_expert"
        // must be unique across all agents

        prompt: "You are a math expert. Use tools to solve all calculations precisely. Always use a tool — never calculate in your head.",
        // focused system prompt — tells agent exactly what its job is
        // "always use a tool" = prevents mental math errors
    });
    // mathAgent = compiled ReAct agent ready to receive tasks
    // internally it's a LangGraph StateGraph

    const researchAgent = createReactAgent({
        llm: model,
        tools: [searchTool, getFactTool],
        // ONLY research tools — this agent cannot do math
        name: "research_expert",
        prompt: "You are a research expert. Search for accurate information to answer questions. Always search before answering — never make up facts.",
    });


    // ─────────────────────────────────────────
    // CREATE SUPERVISOR
    // Coordinates between worker agents
    // ─────────────────────────────────────────

    const supervisorWorkflow = createSupervisor({
        agents: [researchAgent, mathAgent],
        // list of worker agents the supervisor can delegate to
        // supervisor knows each agent's name and capabilities

        llm: model,
        // the LLM that powers the supervisor's reasoning
        // supervisor uses this to decide routing decisions

        prompt: `You are a team supervisor managing two specialist agents:
    - research_expert: Use for finding information, facts, data, current events
    - math_expert: Use for ALL calculations, percentages, arithmetic

    Your job:
    1. Read the user's task
    2. Decide which agent(s) should handle it
    3. Delegate to the right agent
    4. Collect results and provide a final comprehensive answer

    If a task needs both research AND math → use both agents in the right order.
    Always provide a clear final answer after all agents complete their work.`,
        // prompt = how the supervisor decides routing
        // clear agent descriptions = better routing decisions
    });

    // supervisorWorkflow = StateGraph (not compiled yet)
    // must call .compile() before using


    // ─────────────────────────────────────────
    // COMPILE WITH MEMORY
    // ─────────────────────────────────────────

    const checkpointer = new MemorySaver();

    const supervisorApp = supervisorWorkflow.compile({
        checkpointer,
        // adds conversation memory per thread_id
        // supervisor remembers previous turns in the same thread
    });
    // supervisorApp = compiled, ready to invoke
    // this is the final multi-agent system


    // ─────────────────────────────────────────
    // HELPER — run one task through the system
    // ─────────────────────────────────────────

    async function runTask(task, threadId = "default") {
        console.log("\n" + "=".repeat(55));
        console.log("Task:", task);
        console.log("=".repeat(55));

        const result = await supervisorApp.invoke(
            {
                messages: [{ role: "user", content: task }],
                // messages = the task sent to the supervisor
            },
            {
                configurable: { thread_id: threadId },
                // thread_id = which conversation thread to use
            }
        );

        // result.messages = all messages produced during this run
        // includes: supervisor thoughts, agent responses, tool calls
        const lastMessage = result.messages[result.messages.length - 1];
        // last message = supervisor's final answer to the user

        console.log("\n✅ Final Answer:");
        console.log("".repeat(55));
        console.log(lastMessage.content);
    }


    // ─────────────────────────────────────────
    // RUN EXAMPLES
    // ─────────────────────────────────────────

    async function main() {
        console.log("🤖 MULTI-AGENT SUPERVISOR DEMO\n");

        // Task 1 — Pure math → should go to math_expert only
        await runTask(
            "What is 15% of 85000, and then multiply that result by 12?",
            "thread_001"
        );
        // Expected routing: supervisor → math_expert → supervisor → answer

        // Task 2 — Pure research → should go to research_expert only
        await runTask(
            "What are the average FAANG salaries for software engineers in India in 2025?",
            "thread_002"
        );
        // Expected routing: supervisor → research_expert → supervisor → answer

        // Task 3 — Both research AND math
        await runTask(
            "Research the AI agent market size and then calculate what 43% annual growth from $5 billion would be after 3 years.",
            "thread_003"
        );
        // Expected routing: supervisor → research_expert → math_expert → supervisor → answer

        console.log("\n✅ All tasks complete!");
    }

    main().catch(console.error);

Run:

node src/01_basic_supervisor.js

Part 2 — Three-Agent Content Pipeline

A real-world example — research → write → review pipeline:

Create src/02_content_pipeline.js:


    import { ChatOpenAI } from "@langchain/openai";
    import { createSupervisor } from "@langchain/langgraph-supervisor";
    import { createReactAgent } from "@langchain/langgraph/prebuilt";
    import { tool } from "@langchain/core/tools";
    import { z } from "zod";
    import { MemorySaver } from "@langchain/langgraph";
    import * as dotenv from "dotenv";
    dotenv.config();

    const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0.2 });


    // ─────────────────────────────────────────
    // AGENT 1 — RESEARCHER
    // Gathers facts and information
    // ─────────────────────────────────────────

    const gatherFactsTool = tool(
        async (args) => {
            console.log(`   📚 Researcher gathering facts about: "${args.topic}"`);
            // Simulated research
            return `Research findings on "${args.topic}":
    - Key fact 1: ${args.topic} has been growing rapidly in 2025
    - Key fact 2: Major players include Google, Microsoft, Anthropic, OpenAI
    - Key fact 3: Enterprise adoption is accelerating with measurable ROI
    - Key statistic: Market projected at $500B by 2030
    - Recent development: Agentic AI is becoming the dominant paradigm`;
        },
        {
            name: "gather_facts",
            description: "Gathers facts and research on a topic.",
            schema: z.object({ topic: z.string() }),
        }
    );

    const researcherAgent = createReactAgent({
        llm: model,
        tools: [gatherFactsTool],
        name: "researcher",
        prompt: `You are a researcher. Your job is to gather accurate facts and information.
    When given a topic:
    1. Use gather_facts to collect research
    2. Organize the facts clearly
    3. Return a structured research summary

    Output format:
    KEY FACTS: [bullet points of main facts]
    STATISTICS: [any numbers or data points]
    CONTEXT: [background information]`,
    });


    // ─────────────────────────────────────────
    // AGENT 2 — WRITER
    // Takes research and writes content
    // ─────────────────────────────────────────

    const writeDraftTool = tool(
        async (args) => {
            console.log(`   ✍️  Writer creating ${args.type} about: "${args.topic}"`);
            return `[DRAFT ${args.type.toUpperCase()}]

    Title: ${args.topic} — A Complete Overview

    Introduction:
    ${args.topic} represents one of the most significant developments in technology today.
    Based on the research provided: ${args.research.substring(0, 100)}...

    Main Body:
    The landscape is rapidly evolving with key players driving innovation.
    Market statistics indicate strong growth momentum.

    Conclusion:
    Organizations that adopt ${args.topic} early will gain competitive advantages.

    [END DRAFT]`;
        },
        {
            name: "write_draft",
            description: "Writes a draft article, blog post, or report based on research.",
            schema: z.object({
                topic: z.string().describe("the topic to write about"),
                type: z.enum(["blog_post", "report", "summary"]).describe("type of content"),
                research: z.string().describe("the research findings to base the writing on"),
            }),
        }
    );

    const writerAgent = createReactAgent({
        llm: model,
        tools: [writeDraftTool],
        name: "writer",
        prompt: `You are a professional content writer.
    When given research findings:
    1. Use write_draft to create well-structured content
    2. Make it engaging and clear
    3. Ensure all facts from the research are included

    Always base your writing on the provided research — don't add unverified facts.`,
    });


    // ─────────────────────────────────────────
    // AGENT 3 — REVIEWER
    // Reviews content for quality and accuracy
    // ─────────────────────────────────────────

    const reviewContentTool = tool(
        async (args) => {
            console.log(`   🔍 Reviewer checking content quality...`);

            const wordCount = args.content.split(" ").length;
            // count words in the content

            const hasTitle = args.content.includes("Title:");
            const hasIntro = args.content.toLowerCase().includes("introduction");
            const hasConclusion = args.content.toLowerCase().includes("conclusion");

            const score = (hasTitle ? 30 : 0) + (hasIntro ? 35 : 0) + (hasConclusion ? 35 : 0);
            // calculate quality score based on structure

            const feedback = [];
            if (!hasTitle) feedback.push("Missing clear title");
            if (!hasIntro) feedback.push("Missing introduction section");
            if (!hasConclusion) feedback.push("Missing conclusion section");
            if (wordCount < 50) feedback.push("Content too short — needs more detail");

            return `REVIEW RESULTS:
    Quality Score: ${score}/100
    Word Count: ${wordCount}
    Structure Check: ${hasTitle && hasIntro && hasConclusion ? "PASS ✅" : "NEEDS IMPROVEMENT ⚠️"}
    Issues Found: ${feedback.length === 0 ? "None — content looks good!" : feedback.join(", ")}
    Recommendation: ${score >= 70 ? "APPROVED — ready to publish" : "REVISE — address the issues above"}`;
        },
        {
            name: "review_content",
            description: "Reviews content for quality, structure, and accuracy. Returns a quality score and feedback.",
            schema: z.object({
                content: z.string().describe("the content to review"),
            }),
        }
    );

    const reviewerAgent = createReactAgent({
        llm: model,
        tools: [reviewContentTool],
        name: "reviewer",
        prompt: `You are a content quality reviewer.
    When given content to review:
    1. Use review_content to assess quality
    2. Report the score and issues found
    3. Provide a clear APPROVED or REVISE recommendation

    Be thorough but constructive in your feedback.`,
    });


    // ─────────────────────────────────────────
    // SUPERVISOR — Manages the pipeline
    // ─────────────────────────────────────────

    const contentWorkflow = createSupervisor({
        agents: [researcherAgent, writerAgent, reviewerAgent],
        llm: model,
        prompt: `You are a content production supervisor managing a three-agent pipeline:
    - researcher: Gathers facts and information on any topic
    - writer:     Creates written content based on research
    - reviewer:   Reviews content for quality and gives approval

    PIPELINE ORDER — always follow this sequence:
    1. Send to researcher first → get facts
    2. Send research to writer → get draft
    3. Send draft to reviewer → get quality score
    4. Report final status to user with: research summary, draft, and review score

    Do not skip any step. Each agent must complete its task before moving to the next.`,
        outputMode: "last_message",
        // outputMode: "last_message" = only include final agent message in output
        // "full_history" = include all messages from all agents (more verbose)
    });

    const contentApp = contentWorkflow.compile({
        checkpointer: new MemorySaver(),
    });


    // ─────────────────────────────────────────
    // RUN THE PIPELINE
    // ─────────────────────────────────────────

    async function runContentPipeline(topic) {
        console.log("\n" + "=".repeat(55));
        console.log("Content Pipeline Task:", topic);
        console.log("Pipeline: researcher → writer → reviewer");
        console.log("=".repeat(55));

        const result = await contentApp.invoke(
            { messages: [{ role: "user", content: `Create a blog post about: ${topic}` }] },
            { configurable: { thread_id: `pipeline_${Date.now()}` } }
        );

        const lastMsg = result.messages[result.messages.length - 1];
        console.log("\n📋 Pipeline Complete — Final Report:");
        console.log("".repeat(55));
        console.log(lastMsg.content);
    }


    async function main() {
        console.log("🏭 CONTENT PIPELINE DEMO\n");
        console.log("Three agents: Researcher → Writer → Reviewer\n");

        await runContentPipeline("AI Agents in 2025");

        console.log("\n✅ Pipeline complete!");
    }

    main().catch(console.error);

Run:

node src/02_content_pipeline.js

Multi-Agent Architecture Patterns

SUPERVISOR (what we built):
          Supervisor
         /     |     \
      Agent1 Agent2 Agent3
      
Best for: tasks with clear roles
          predictable pipelines
          structured workflows


NETWORK / SWARM (agents call each other):
  Agent1 ←→ Agent2 ←→ Agent3
  
Best for: exploratory tasks
          no fixed order needed
          agents decide handoffs dynamically


HIERARCHICAL (supervisors managing supervisors):
         Top Supervisor
        /              \
   Sub-Supervisor    Sub-Supervisor
    /    \              /    \
 Agent1 Agent2      Agent3 Agent4

Best for: very large complex tasks
          enterprise systems
          multiple departments

When to Use Multi-Agent vs Single Agent

Use SINGLE AGENT when:
→ Task fits in one context window
→ 3-5 tools is enough
→ Task is straightforward
→ Speed matters more than quality
→ Cost is a concern
   (each agent handoff = extra LLM call = extra cost)

Use MULTI-AGENT when:
→ Task is too complex for one agent
→ Different parts need different expertise
→ Parallel work would speed things up
→ You need specialist quality on each sub-task
→ Pipeline has clear stages (research → write → review)

Warning from 2025 research:
→ Multi-agent = 5-10x more LLM calls than single agent
→ Start with single agent
→ Add agents only when quality genuinely improves

3-Line Summary

  1. Multi-agent systems use a Supervisor that reads the task, routes it to specialist Worker agents based on their descriptions, collects results, and compiles the final answer — each worker has a narrow focus (only math tools, only research tools) which makes it better at its specific job.
  2. createSupervisor from @langchain/langgraph-supervisor combined with createReactAgent from @langchain/langgraph/prebuilt is the 2025 standard pattern — createSupervisor returns a StateGraph that you compile with .compile({ checkpointer }) before using.
  3. Multi-agent systems cost 5-10x more in LLM calls than a single agent — always start with a single well-designed agent and only move to multi-agent when you genuinely need specialist quality on different parts of the task.

Module 8.4 — Complete ✅

Coming up — Module 8.5 — Project: Resume Analyzer Agent

A real project using everything from Phase 8 — an agent that reads a resume PDF, analyzes skills, matches against a job description, scores the candidate, and gives actionable improvement suggestions. End-to-end working project.

Module 8.4 — Multi-Agent Systems with LangGraph

Multiple specialized agents collaborating to solve complex tasks Why Multi-Agent Systems One agent with many tools gets confused. It trie...