Multi-Agent AI Pipeline with LangGraph

Orchestrate three AI agents in a self-correcting research pipeline.

Introduction

30 Second Summary

Think about how a real research team works. You don't hand one person the job of gathering data, evaluating its quality, AND writing the final report. You split those jobs across specialists who pass work between each other and flag when something isn't good enough.

In this project, you will build a multi-agent AI pipeline where three specialized agents (Researcher, Analyst, and Writer) collaborate through a LangGraph StateGraph to produce quality-checked research reports on any topic you give them.

What You'll Build

You type a research topic into your terminal, and three AI agents take over. You watch the Researcher gather information, the Analyst score it and demand improvements, and the Writer produce a polished report only after the research passes quality checks.

By the end of this project, you'll have:

  • A running pipeline where you enter any topic and watch three specialized agents collaborate in real-time, printing each agent's reasoning as it works.
  • A conditional feedback loop where the Analyst rejects weak research and sends the Researcher back for more detail, creating a self-correcting system.
  • Typed state flowing between agents so you can inspect exactly what each agent received and produced at every step of the graph.
  • Secret Mission: Add tool use to the Researcher agent so it can call a web search function using LangGraph's ToolNode pattern.

Are there any prerequisites?

You should have Python installed (3.10+), VS Code ready, and basic Python experience (functions, imports, and classes). You also need an Anthropic Console account to get an API key for Claude.

Before We Start

Before we write any code, let's get clear on what you're about to build and why it matters. This is your moment to commit to the goal and understand why a multi-agent pipeline demonstrates deeper engineering skills than a single LLM call.

Set Up Your Environment

A multi-agent pipeline has several moving parts: the graph framework that orchestrates your agents, the LLM integration that powers each agent's thinking, and credentials that connect you to the cloud AI service.

In this step, you will install LangGraph and the langchain-anthropic package, then set up your Anthropic API key so Claude can power your agents.

In this step, get ready to:
  • Create a project directory and Python virtual environment.
  • Install LangGraph and the Claude integration package.
  • Set up and verify your Anthropic API key.
Create your project directory and virtual environment

A virtual environment keeps your project's packages isolated from the rest of your system. This way, installing LangGraph here won't interfere with packages in other projects.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running this command:
mkdir multi-agent-pipeline && cd multi-agent-pipeline
  • Create and activate your virtual environment by running these commands:

macOS

python3 -m venv .venv && source .venv/bin/activate

Windows

python -m venv .venv
.venv\Scripts\activate
  • Check your terminal prompt. You should see (.venv) at the beginning of your command line.

What does (.venv) mean?

The (.venv) prefix means your virtual environment is active. Every pip install you run from now on installs packages inside this isolated environment, not system-wide.

Don't see (.venv) in your prompt?

Make sure you ran the activation command (source .venv/bin/activate on macOS or .venv\Scripts\activate on Windows). If you closed your terminal, you need to cd back into multi-agent-pipeline and run the activation command again.

help me activate my Python virtual environment

Install LangGraph and langchain-anthropic

LangGraph is the orchestration framework that wires your agents into a directed graph. The langchain-anthropic package connects LangGraph to Claude's API.

  • Install both packages by running this command:
pip install langgraph langchain-anthropic
  • Wait for the installation to complete. You should see Successfully installed followed by a list of packages at the end of the output.

Seeing a permissions error?

Make sure (.venv) appears in your terminal prompt. If it does not, your virtual environment is not active. Go back to the previous substep and run the activation command.

help me fix pip install errors in my virtual environment

Set up your Anthropic API key

Your agents need credentials to call Claude's API. You will generate an API key from the Anthropic Console and set it as an environment variable so the SDK can find it automatically.

  • Open console.anthropic.com in your browser and sign in.
  • Click Settings in the left sidebar.
  • Select API Keys.
  • Click Create Key.
  • Give your key a name (e.g. multi-agent-pipeline) and click Create Key again.
  • Copy the key immediately. It is only shown once.

What if I already have a key?

If you already have an Anthropic API key from a previous project, you can reuse it. Skip the key creation steps above and use your existing key in the export command below.

  • Set your API key as an environment variable by running this command (replace the placeholder with your actual key):

macOS

export ANTHROPIC_API_KEY="your-api-key-here"

Windows

$env:ANTHROPIC_API_KEY="your-api-key-here"

Keep this terminal open

The export command sets the key only for this terminal session. If you close and reopen your terminal, you will need to run the export command again.

  • Verify that Claude can respond by running this command:
python -c "from langchain_anthropic import ChatAnthropic; llm = ChatAnthropic(model='claude-sonnet-4-6'); print(llm.invoke('Say hello in 5 words').content)"
  • You should see a short five-word greeting from Claude printed in your terminal.

Seeing an authentication error?

Double-check that you copied your full API key without any extra spaces. Run the export command again with the correct key. Also confirm that your Anthropic account has credits or an active billing method.

help me fix my Anthropic API key authentication error

Now confirm that both packages are importable together. Before you run this, what do you expect the output to be?

  • Run this final verification command:
python -c "from langgraph.graph import StateGraph; from langchain_anthropic import ChatAnthropic; print('Ready')"
  • You should see Ready printed to your terminal. This confirms LangGraph and langchain-anthropic are installed correctly and your environment is fully set up.

Seeing ModuleNotFoundError?

This means the packages are not installed in your active environment. Make sure (.venv) appears in your prompt, then re-run pip install langgraph langchain-anthropic.

help me fix ModuleNotFoundError for langgraph

Your environment is locked and loaded. Next up, you will build your first agent node and watch Claude generate research output in real time.

Build Your First Agent Node

You have LangGraph and the Claude integration installed. Now you need to see a single agent node actually work before wiring up a complex multi-agent system.

In this step, you'll define the shared state that flows between agents, build your first agent node (the Researcher), and wire it into a minimal graph. By the end, you'll run the pipeline and watch Claude produce real research output in your terminal.

In this step, get ready to:
  • Define the shared state schema that all agents will read from and write to.
  • Build the Researcher agent node that calls Claude and returns structured output.
  • Wire a minimal StateGraph and run it to see Claude's research in your terminal.
Define the shared state and set up the LLM

Every node in a LangGraph pipeline receives a shared state object and returns updates to it. You define this state as a Python TypedDict so every agent knows exactly what data it can read and write.

  • Create a new file called pipeline.py inside your multi-agent-pipeline folder by running:
touch pipeline.py
  • Confirm the file exists by running:
ls

You should see pipeline.py listed in the output.

  • Open pipeline.py in VS Code, then copy the following code into it:
import operator
from typing import Annotated, TypedDict

from langchain_anthropic import ChatAnthropic
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver

# --- State Definition ---

class ResearchState(TypedDict):
    research_topic: str
    research_data: str
    analysis: str
    quality_score: float
    final_report: str
    iteration_count: int

# --- LLM Setup ---

llm = ChatAnthropic(model="claude-sonnet-4-6")

What does this code do?

The imports bring in LangGraph's graph primitives (StateGraph, START, END) and the Claude integration class ChatAnthropic.

ResearchState is the typed blueprint for data flowing through the pipeline. Each field maps to a specific agent's responsibility: research_data for the Researcher, analysis and quality_score for the Analyst (coming later), and final_report for the Writer.

The llm variable creates a reusable Claude client that every agent node will call.

  • Save pipeline.py.
  • Verify the imports resolve correctly by running:
python pipeline.py

You should see no output and no errors. This confirms all packages are installed and importable.

Seeing a ModuleNotFoundError?

Make sure your virtual environment is still active. You should see (.venv) at the start of your terminal prompt. If not, re-activate it:

source .venv/bin/activate

Then try running python pipeline.py again. Help me fix my import errors in pipeline.py

Build the Researcher agent node

Every agent node in LangGraph follows the same pattern: it receives the current state, does its work (in this case, calling Claude), and returns a dictionary with the fields it wants to update. The graph merges that dictionary back into the shared state automatically.

  • In pipeline.py, add the Researcher agent function below the llm line by copying this code:
# --- Agent Nodes ---

def researcher_node(state: ResearchState) -> dict:
    """Researcher agent: gathers information on the topic."""
    topic = state["research_topic"]
    previous_feedback = state.get("analysis", "")

    feedback_context = ""
    if previous_feedback:
        feedback_context = (
            f"\n\nPrevious research was deemed insufficient. "
            f"Analyst feedback:\n{previous_feedback}\n\n"
            f"Please address these gaps and provide more detailed research."
        )

    prompt = (
        f"You are a research specialist. Provide comprehensive, detailed research "
        f"on the following topic. Include specific facts, data points, examples, "
        f"and multiple perspectives.\n\n"
        f"Topic: {topic}{feedback_context}\n\n"
        f"Provide thorough research with at least 5 distinct points or findings."
    )

    response = llm.invoke(prompt)
    print(f"\n{'='*60}")
    print("RESEARCHER OUTPUT:")
    print(f"{'='*60}")
    print(response.content[:500] + "..." if len(response.content) > 500 else response.content)

    return {"research_data": response.content}

What does this code do?

The function reads research_topic from state and checks if any previous analysis feedback exists (it won't yet, but this prepares for the feedback loop later).

It builds a detailed prompt telling Claude to act as a research specialist, then calls llm.invoke(prompt) to get Claude's response. The print statements show the first 500 characters of the research so you can watch the agent work in real time.

Finally, it returns {"research_data": response.content}. LangGraph merges this into the shared state, making the research available to whatever node runs next.

  • Save pipeline.py.
Wire the graph and run the pipeline

A StateGraph connects nodes with edges to define execution order. You'll create the simplest possible graph: START flows to the Researcher, and the Researcher flows to END.

  • Add the graph construction and main execution block at the bottom of pipeline.py by copying this code:
# --- Graph Construction ---

def build_graph():
    graph = StateGraph(ResearchState)
    graph.add_node("researcher", researcher_node)
    graph.add_edge(START, "researcher")
    graph.add_edge("researcher", END)
    checkpointer = MemorySaver()
    return graph.compile(checkpointer=checkpointer)


if __name__ == "__main__":
    app = build_graph()
    topic = input("\nEnter a research topic: ")
    result = app.invoke(
        {"research_topic": topic, "iteration_count": 0},
        config={"configurable": {"thread_id": "research-001"}},
    )

What does this code do?

build_graph() registers the researcher_node function as a node, then draws edges from START to researcher to END. The MemorySaver checkpointer saves state between runs (useful for debugging and multi-turn interactions).

The if __name__ == "__main__" block asks for a topic, then invokes the compiled graph. The thread_id parameter tells the checkpointer which conversation to save state for.

  • Save pipeline.py.

Before you run this, what do you think will happen? The graph has one node (the Researcher) that calls Claude. Will you see raw research text, a formatted report, or something else?

  • Run the pipeline:
python pipeline.py
  • When prompted, type quantum computing applications in drug discovery and press Enter.

You should see Claude produce a detailed research summary printed under the RESEARCHER OUTPUT banner. The output shows the first 500 characters of the research followed by ... if it's longer.

Seeing an AuthenticationError?

This means your ANTHROPIC_API_KEY environment variable isn't set in your current terminal session. Re-export it:

export ANTHROPIC_API_KEY="your-api-key-here"

Then run python pipeline.py again. If you closed and reopened your terminal since Step 1, you'll need to re-activate the virtual environment too. Help me fix my Anthropic API authentication error

✔️ Awesome, I've got everything!

Great. Double check you've saved pipeline.py and confirmed Claude's research output in your terminal.

ⓧ I'd like to double check the full code

Your pipeline.py should contain these three sections in order. Scroll up and confirm each one is present:

  • Imports and State Definition (lines 1-20): The import statements, the ResearchState TypedDict class, and the llm = ChatAnthropic(model="claude-sonnet-4-6") line.
  • Researcher Agent Node: The full researcher_node function with the prompt, llm.invoke() call, print statements, and the return {"research_data": response.content} line.
  • Graph Construction and Main Block: The build_graph() function with add_node, add_edge, and compile, followed by the if __name__ == "__main__" block that invokes the graph.

Your first agent node is alive and producing real research output. Next up, you'll add a Writer agent to transform that raw research into a polished report.

Add the Writer and Run a Sequential Pipeline

Your Researcher agent produces raw data, but raw data is not a deliverable. A real pipeline transforms research into something polished and ready to share.

In this step, you will add a second agent (the Writer) and wire a sequential pipeline where the Researcher feeds into the Writer. You will also discover a gap in this approach that the next step fixes.

In this step, get ready to:
  • Build a Writer agent node that turns raw research into a formatted report.
  • Wire a two-agent sequential graph (Researcher then Writer).
  • Observe the quality gap when no one checks the research before writing.
Build the Writer agent node

The Writer follows the same pattern as the Researcher. It receives the shared state, reads the fields it needs, calls Claude with a writing-focused prompt, and returns an update to the state.

  • In pipeline.py, add this function directly below researcher_node (and above the # --- Graph Construction --- comment):
def writer_node(state: ResearchState) -> dict:
    """Writer agent: produces the final polished report."""
    research = state["research_data"]
    topic = state["research_topic"]

    prompt = (
        f"You are a professional report writer. Using the research below, "
        f"write a clear, well-structured report on '{topic}'.\n\n"
        f"Research material:\n{research}\n\n"
        f"Write a polished report with:\n"
        f"- A brief executive summary\n"
        f"- Key findings organized by theme\n"
        f"- A conclusion with implications\n\n"
        f"Keep it concise but comprehensive (400-600 words)."
    )

    response = llm.invoke(prompt)

    print(f"\n{'='*60}")
    print("WRITER OUTPUT (Final Report):")
    print(f"{'='*60}")
    print(response.content)

    return {"final_report": response.content}

What does this code do?

  • writer_node pulls research_data and research_topic from the shared state. It only reads what the Researcher already produced.
  • The prompt asks Claude to write a structured report with an executive summary, themed findings, and a conclusion.
  • It returns {"final_report": response.content} which updates just that one field in the shared state.
Wire the sequential graph and run the pipeline

Now you need to register the Writer as a node and connect it so the graph flows from Researcher to Writer to END.

  • In pipeline.py, find the # --- Graph Construction --- section. Replace everything from that comment to the end of the file with this code:
# --- Graph Construction ---

def build_graph():
    graph = StateGraph(ResearchState)
    graph.add_node("researcher", researcher_node)
    graph.add_node("writer", writer_node)
    graph.add_edge(START, "researcher")
    graph.add_edge("researcher", "writer")
    graph.add_edge("writer", END)
    checkpointer = MemorySaver()
    return graph.compile(checkpointer=checkpointer)


if __name__ == "__main__":
    app = build_graph()
    topic = input("\nEnter a research topic: ")
    result = app.invoke(
        {"research_topic": topic, "iteration_count": 0},
        config={"configurable": {"thread_id": "research-001"}},
    )
    print("\n" + "=" * 60)
    print("PIPELINE COMPLETE")
    print("=" * 60)

What changed in the graph?

  • graph.add_node("writer", writer_node) registers the Writer as a node the graph can visit.
  • The edge from researcher now points to writer instead of END. A new edge connects writer to END.
  • The flow is now: START -> researcher -> writer -> END. Each node runs in sequence, passing state forward.
  • Save pipeline.py.
  • Run the pipeline with a simple topic by running:
python pipeline.py
  • When prompted, enter a topic like quantum computing applications in drug discovery.

You should see two distinct output sections in your terminal: RESEARCHER OUTPUT followed by WRITER OUTPUT (Final Report), then PIPELINE COMPLETE at the end.

Only seeing RESEARCHER OUTPUT?

  • Check that you added graph.add_node("writer", writer_node) in build_graph. Without registering the node, the edge to it will fail silently.
  • Make sure writer_node is defined above build_graph. Python reads top to bottom, so the function must exist before it is referenced.
  • Confirm the edge reads graph.add_edge("researcher", "writer") not graph.add_edge("researcher", END).

help me debug why only the Researcher output is showing

✔️ Awesome, I've got everything!

Great. Make sure you have saved pipeline.py with both the new writer_node function and the updated graph.

ⓧ I'd like to double check the full code

Your pipeline.py should contain these sections in order. Scroll through your file and confirm each one is present:

  • Imports (operator, TypedDict, ChatAnthropic, StateGraph, START, END, MemorySaver).
  • ResearchState class with six fields (research_topic, research_data, analysis, quality_score, final_report, iteration_count).
  • llm = ChatAnthropic(model="claude-sonnet-4-6") line.
  • researcher_node function (the one you built in the previous step).
  • writer_node function (the new one from this step).
  • build_graph function with three add_edge calls: START->researcher, researcher->writer, writer->END.
  • if __name__ == "__main__" block ending with the PIPELINE COMPLETE print.
Observe the quality gap

The pipeline works. Two agents collaborate in sequence. But is the output consistently good?

  • Run the pipeline again with a more niche topic:
python pipeline.py
  • This time, try a narrow topic like microplastic filtration methods in municipal water treatment.

Before you read the output, ask yourself: do you think the final report will be thorough and well-sourced, or will it have gaps?

  • Read both the RESEARCHER OUTPUT and the WRITER OUTPUT carefully. Run it one more time with another topic of your choice.

You will notice that the Writer faithfully formats whatever the Researcher gives it. If the research is shallow or vague, the final report is shallow and vague too. The Writer has no way to push back.

Why does this matter?

This is the core problem with a sequential pipeline. The Writer blindly trusts the Researcher. There is no quality gate between raw research and the final deliverable.

In a real production system, you would never ship a report without someone reviewing the source material first. That reviewer role is exactly what the next step introduces.

Your two-agent pipeline runs end to end. But quality is a coin flip because nothing evaluates the research before the Writer uses it. In the next step, you will add an Analyst agent that scores research quality and sends weak research back to the Researcher for another pass.

Add the Analyst and Conditional Feedback Loop

In the previous step, you saw the problem first-hand: the Writer blindly accepts whatever research it receives. Sometimes the output is thorough, sometimes it's thin and vague.

The fix is a quality gate. You'll add an Analyst agent that scores research on a 1-10 scale, and a conditional edge that routes the pipeline back to the Researcher if the score is below 7. This self-correcting loop is the pattern interviewers ask about when they probe multi-agent design.

In this step, get ready to:
  • Build an Analyst agent that evaluates and scores research quality.
  • Create a routing function that decides whether to loop back or continue forward.
  • Rewire your graph with conditional edges to create the feedback loop.
Build the Analyst agent node

The Analyst sits between the Researcher and the Writer. It receives the raw research, asks Claude to evaluate it against four criteria, and extracts a numeric score from the response.

  • In pipeline.py, find the writer_node function.
  • Add the following analyst_node function directly above writer_node by pasting this code:
def analyst_node(state: ResearchState) -> dict:
    """Analyst agent: scores research quality and identifies gaps."""
    research = state["research_data"]
    topic = state["research_topic"]

    prompt = (
        f"You are a critical research analyst. Evaluate the following research "
        f"on '{topic}' for quality and completeness.\n\n"
        f"Research to evaluate:\n{research}\n\n"
        f"Score the research from 1-10 based on:\n"
        f"- Source diversity (multiple perspectives?)\n"
        f"- Data specificity (concrete numbers or vague claims?)\n"
        f"- Completeness (any obvious gaps?)\n"
        f"- Accuracy (any contradictions?)\n\n"
        f"Respond in this exact format:\n"
        f"SCORE: [number 1-10]\n"
        f"FEEDBACK: [specific gaps or issues to address, or 'Research is comprehensive']"
    )

    response = llm.invoke(prompt)
    response_text = response.content

What does this code do?

The Analyst's prompt asks Claude to score on four dimensions: source diversity, data specificity, completeness, and accuracy. It also enforces a strict response format (SCORE: followed by FEEDBACK:) so you can reliably parse the number out.

The response_text variable captures Claude's full evaluation. The next chunk will extract the numeric score from it.

  • Still inside the analyst_node function, add the score-parsing logic directly below the previous code:
    # Parse score from response
    score = 7.0
    try:
        for line in response_text.split("\n"):
            if line.strip().startswith("SCORE:"):
                score = float(line.split(":")[1].strip().split("/")[0].strip())
                break
    except (ValueError, IndexError):
        score = 7.0

    iteration = state.get("iteration_count", 0) + 1

    print(f"\n{'='*60}")
    print(f"ANALYST OUTPUT (Iteration {iteration}):")
    print(f"{'='*60}")
    print(f"Quality Score: {score}/10")
    print(response_text[:300])

    return {
        "analysis": response_text,
        "quality_score": score,
        "iteration_count": iteration,
    }

What does the parsing logic do?

The code loops through each line looking for one that starts with SCORE:. It splits on the colon and extracts the number. If Claude's response doesn't match the expected format, the fallback score of 7.0 lets the pipeline continue gracefully.

The return dict updates three state fields: analysis (the full feedback text), quality_score (the numeric rating), and iteration_count (how many research cycles have run).

  • Save pipeline.py.
  • Confirm there are no syntax errors by running:
python -c "from pipeline import analyst_node; print('Analyst ready')"

You should see Analyst ready printed with no errors.

Seeing an ImportError or SyntaxError?

  • Check that the analyst_node function is indented correctly. Every line inside it should be indented by 4 spaces.
  • Make sure you placed analyst_node above writer_node, not inside another function.
  • Verify you're running the command from inside your multi-agent-pipeline folder with your virtual environment active.

Help me debug my analyst_node import error.

Build the quality_router function

The router is a plain Python function that LangGraph calls after the Analyst finishes. It inspects the state and returns a string telling the graph which node to visit next.

  • In pipeline.py, find the # --- Graph Construction --- comment.
  • Add a new section heading and the quality_router function directly above it:
# --- Routing Logic ---

def quality_router(state: ResearchState) -> str:
    """Route based on research quality score."""
    score = state.get("quality_score", 0)
    iterations = state.get("iteration_count", 0)

    if score >= 7:
        print(f"\n>> Quality passed ({score}/10). Proceeding to Writer.")
        return "writer"
    elif iterations >= 3:
        print(f"\n>> Max iterations reached ({iterations}). Proceeding to Writer.")
        return "writer"
    else:
        print(f"\n>> Quality too low ({score}/10). Sending back to Researcher.")
        return "researcher"

What does this code do?

The router checks two conditions. If quality_score is 7 or above, the research is good enough and the pipeline moves to the Writer. If the score is below 7 but the pipeline has already looped 3 times, it moves forward anyway to avoid an infinite loop.

Otherwise, it returns "researcher" to send the pipeline back for another round of research. The Analyst's feedback is already stored in state, so the Researcher will see it on the next pass.

Rewire the graph with conditional edges

Now you'll replace the sequential graph with one that routes conditionally. LangGraph's add_conditional_edges method accepts a node name, a routing function, and a mapping of return values to next nodes.

  • In pipeline.py, find the existing build_graph function and replace it entirely with this version:
# --- Graph Construction ---

def build_graph():
    """Construct and compile the multi-agent research pipeline."""
    graph = StateGraph(ResearchState)

    # Register nodes
    graph.add_node("researcher", researcher_node)
    graph.add_node("analyst", analyst_node)
    graph.add_node("writer", writer_node)

    # Define edges
    graph.add_edge(START, "researcher")
    graph.add_edge("researcher", "analyst")

    # Conditional routing after analysis
    graph.add_conditional_edges(
        "analyst",
        quality_router,
        {
            "writer": "writer",
            "researcher": "researcher",
        },
    )

    graph.add_edge("writer", END)

    # Compile with checkpointing
    checkpointer = MemorySaver()
    return graph.compile(checkpointer=checkpointer)

What does this code do?

The graph now registers three nodes instead of two. The Researcher always flows into the Analyst via a fixed edge.

After the Analyst, add_conditional_edges calls quality_router to decide the next step. The mapping dict tells LangGraph that "writer" means the writer node and "researcher" means the researcher node. This creates the feedback loop.

  • Save pipeline.py.
  • Before you run the pipeline, think about this: if you enter a broad topic like "quantum computing", do you expect the Analyst to pass or reject the first research attempt?
  • Run the pipeline to test the feedback loop:
python pipeline.py
  • When prompted, enter a research topic like quantum computing applications in drug discovery.

You should see the Researcher produce output, then the Analyst print a quality score. If the score is below 7, you'll see the routing message and a second research pass.

Pipeline hangs or crashes?

  • If you see NameError: name 'analyst_node' is not defined, make sure analyst_node is defined above build_graph in the file.
  • If the pipeline loops forever, check that your quality_router includes the iterations >= 3 safety check.
  • If you get an API authentication error, confirm your ANTHROPIC_API_KEY environment variable is still set in this terminal session.

Help me debug my conditional feedback loop.

  • Now replace the if __name__ == "__main__": block at the bottom of pipeline.py with this updated version that prints the iteration summary:
# --- Main Execution ---

if __name__ == "__main__":
    app = build_graph()

    print("\n" + "=" * 60)
    print("MULTI-AGENT RESEARCH PIPELINE")
    print("=" * 60)

    topic = input("\nEnter a research topic: ")

    result = app.invoke(
        {"research_topic": topic, "iteration_count": 0},
        config={"configurable": {"thread_id": "research-001"}},
    )

    print("\n" + "=" * 60)
    print("PIPELINE COMPLETE")
    print(f"Total iterations: {result['iteration_count']}")
    print(f"Final quality score: {result['quality_score']}/10")
    print("=" * 60)

What changed in the main block?

The updated block adds a header banner so you can see the pipeline starting. At the end, it prints the total number of research iterations and the final quality score. This makes it easy to confirm the feedback loop ran the expected number of times.

  • Save pipeline.py and run the pipeline one more time:
python pipeline.py
  • Enter a topic and watch the full output. At the end, you should see the PIPELINE COMPLETE summary showing the total iterations and final score.

Compare your final report to the output from the previous step. If the Analyst triggered a feedback loop, the report should be noticeably more detailed because the Researcher addressed specific gaps.

Why is this pattern valuable in interviews?

The conditional feedback loop demonstrates that you understand evaluation-driven architectures. Production AI systems almost always need a quality gate between generation and output. Being able to explain add_conditional_edges, routing functions, and iteration caps puts you ahead of candidates who only know single-shot prompting.

✔️ Awesome, I've got everything!

Great. Double check you've saved pipeline.py and your pipeline runs end-to-end with the Analyst feedback loop.

ⓧ I'd like to double check the full code

Your pipeline.py should contain the following sections in order. Scroll up and confirm each is present:

  • Imports (operator, TypedDict, ChatAnthropic, StateGraph, START, END, MemorySaver).
  • State Definition: ResearchState TypedDict with six fields.
  • LLM Setup: llm = ChatAnthropic(model="claude-sonnet-4-6").
  • Agent Nodes: researcher_node, analyst_node, writer_node (in that order).
  • Routing Logic: quality_router function.
  • Graph Construction: build_graph with conditional edges.
  • Main Execution: if __name__ == "__main__" block with the pipeline summary output.

Your pipeline is now self-correcting. In the Secret Mission, you'll take it further by giving the Researcher actual tools to call, so it can gather real information instead of relying solely on Claude's training data.

Secret mission

Add Tool Use to the Researcher

Production AI agents don't just generate text from memory. They call external tools like search engines, databases, and APIs. Can you wire up LangGraph's ToolNode pattern so the Researcher agent calls a web search tool before writing its summary?

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs locally with a pay-per-use API key. There are no ongoing costs as long as you stop making API calls.

Resources you used:

  • multi-agent-pipeline project directory (contains your virtual environment, pipeline.py, and all installed packages).
  • Anthropic API key (stored in your Anthropic Console, charges only when you make API calls).

Keep everything running

No action needed. Choose this if you're still actively building or want to experiment with different research topics.

  • Your multi-agent-pipeline folder stays on your Desktop. You can re-run the pipeline anytime for a few cents.
  • Your Anthropic API key remains active but incurs no charges unless you make API calls.
  • The ANTHROPIC_API_KEY environment variable only persists in the terminal session where you exported it. Opening a new terminal requires re-exporting it.

Pause - I'll come back to this later

Shut down the virtual environment to free up your terminal. Your files and API key stay intact for next time.

  • Deactivate your virtual environment by running:
deactivate
  • Your API key stays in your Anthropic Console but incurs no charges unless used.
  • Next time you return, navigate back into multi-agent-pipeline and reactivate with source .venv/bin/activate (macOS/Linux) or .venv\Scripts\activate (Windows).

Delete - I don't want to use this again

Remove all project files and revoke your API key so nothing lingers on your machine or account.

  • Deactivate your virtual environment first (if it's still active):
deactivate
  • Delete the entire project directory by running:
cd ~/Desktop
rm -rf multi-agent-pipeline
  • Revoke your Anthropic API key so it cannot be used again.
  • Go to console.anthropic.com.
  • Navigate to Settings, then API Keys.
  • Find the key you created for this project and revoke it.

That's a Wrap!

That's a Wrap!

Nice work! You just built a production-style multi-agent research pipeline where three specialized AI agents collaborate through a LangGraph StateGraph to produce quality-checked research reports.

You've learned how to:

  • Build a multi-agent pipeline with three specialized agents (Researcher, Analyst, Writer) orchestrated through a directed StateGraph with typed state management.
  • Create a conditional feedback loop where an Analyst agent evaluates research quality and routes back to the Researcher for improvement when the score falls below a threshold.
  • Use LangGraph's core primitives including nodes, edges, conditional routing, and MemorySaver checkpointing connected to Claude's API via langchain-anthropic.
  • Secret Mission: Added tool use to the Researcher agent using LangGraph's ToolNode and tools_condition pattern, enabling agents to call external functions.

Ready to quiz yourself?