LangChain (YT style)LangChain 1.4 · Python 3.12+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
46 small wins to finish your pathNext lesson →

Router: sending each question to one agent

A router is a classification step that sends each question to one specialist and then steps aside.

Last updated: 27 Sep, 2026 · LangChain 1.4

A router function between agents · from the Building Agents And Multi Agents With LangGraph, Part 3 · 27:22 to 30:55

A router picks the next step

A router is the simplest decision in a multi-agent system: look at the state and return the name of whoever should act next. In the multi-agent crash course, once the researcher, analyst and writer agents exist, the router is a plain function. It takes the supervisor state and reads next_agent, falling back to the supervisor. If next_agent is end, or the task is complete, it returns END. If it names the supervisor, the researcher, the analyst or the writer, it returns that name. Anything else goes back to the supervisor:

python
def router(state: SupervisorState) -> Literal["supervisor", "researcher", "analyst", "writer", "__end__"]:
    """Routes to next agent based on state"""
    
    next_agent = state.get("next_agent", "supervisor")
    
    if next_agent == "end" or state.get("task_complete", False):
        return END
        
    if next_agent in ["supervisor", "researcher", "analyst", "writer"]:
        return next_agent
        
    return "supervisor"

The workflow then wires it in: a graph over the supervisor state with four nodes (supervisor, researcher, analyst and writer), the supervisor as the entry point, and a conditional edge from each node that calls router and goes wherever it points. Invoked with "What are the benefits and risks of AI in healthcare?", the run went to the researcher, then the analyst, then the writer, and ended with the final report, though the question never reached the agents: graph.invoke was given a bare HumanMessage instead of {"messages": [...]}, so the researcher reported on "No task" and the report is a generic template. Pass the input as a dictionary with a messages list, as every example in this course does.

The return type lists every place the run can go, and END stops it. No model is called in the router; the supervisor already made the choice. The shop's router is a plain function too. The difference is what it reads: the customer's question itself, not a field a supervisor filled in. An order id sends it to orders, and anything else goes to policies. That makes it cheaper and more predictable than the supervisor in Subagents as tools, but it cannot combine answers.

The video's router lives inside a LangGraph graph and reads the name the supervisor wrote into the state. The shop's router below is a plain function too, but it reads the customer's question itself, so no supervisor is needed.

The two specialists come from the subagents lesson: orders_agent looks up orders and policies_agent answers from the policy documents, both real Groq agents in specialists.py. Here a router sends each question to one of them.

route() sends an order question to orders_agent and anything else to policies_agent; either one gives the answer.
The shop's router

The route function

python
def route(question):
    if some_rule(question):     # one classification step
        return "orders"
    return "policies"           # the default specialist

Routing by an order id

route picks a specialist by looking for an order id and returns its name. Nothing decides here except a regular expression. The code in this section goes in one file, router.py.

python
import re


def route(question):
    if re.search(r"\b[A-Z]\d+\b", question):   # an order id, like A17
        return "orders"
    return "policies"                           # everything else

The answer function

answer hands the question to the chosen agent and returns which one replied. It takes the agents as a dictionary instead of importing them, so router.py never builds a model itself; the testing lesson relies on that.

python
def answer(question, agents):
    name = route(question)                      # pick one specialist
    result = agents[name].invoke({"messages": [{"role": "user", "content": question}]})
    return name, result["messages"][-1].text    # the question went straight through
Project files used on this pageThis lesson builds on a project from earlier lessons. The code below imports these files. Click a file to see its code, or follow the link to the lesson that wrote it. To run the code yourself, keep them in the same folder.
View the code here
policies.py
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter

POLICIES = {
    "refunds.md": "Refunds go back to the card you paid with. They take up to 5 working days to arrive."
                  "\n\nYou can ask for a refund within 30 days of delivery. Opened items can be refunded if they are faulty.",
    "shipping.md": "Standard shipping takes 3 to 5 working days. Shipping is free on orders over 50 euros."
                   "\n\nExpress shipping arrives the next working day and costs 9 euros.",
    "accounts.md": "To reset your password, use the reset link on the sign-in page. Support staff never ask for your password.",
}

docs = [Document(page_content=text, metadata={"source": name}) for name, text in POLICIES.items()]
splitter = RecursiveCharacterTextSplitter(chunk_size=120, chunk_overlap=0, add_start_index=True)
chunks = splitter.split_documents(docs)
word_embeddings.py
import re
import zlib

from langchain_core.embeddings import Embeddings

COMMON = {"a", "an", "and", "are", "can", "do", "does", "for", "how", "i",
          "if", "is", "it", "my", "of", "on", "the", "to", "what", "with", "you", "your"}


class WordEmbeddings(Embeddings):
    def embed_query(self, text):
        vector = [0.0] * 256
        for word in re.findall(r"[a-z]+", text.lower()):
            if word not in COMMON:
                vector[zlib.crc32(word.rstrip("s").encode()) % 256] += 1.0
        return vector

    def embed_documents(self, texts):
        return [self.embed_query(text) for text in texts]
search.py
from langchain.tools import tool
from langchain_core.vectorstores import InMemoryVectorStore
from policies import chunks
from word_embeddings import WordEmbeddings

store = InMemoryVectorStore(WordEmbeddings())
store.add_documents(chunks)


@tool
def search_policies(query: str) -> str:
    """Search the shop's policies on refunds, shipping and accounts.
    Pass the customer's question, word for word, as the query."""
    found = [doc for doc, score in store.similarity_search_with_score(query, k=2) if score >= 0.3]
    if not found:
        return "No policy covers this."
    return "\n".join(f"[{doc.metadata['source']}] {doc.page_content}" for doc in found)
orders.py
from langchain.tools import tool

ORDERS = {"A17": "shipped on 3 March", "C40": "waiting for stock"}


@tool
def lookup_order(order_id: str) -> str:
    """Look up an order's shipping status by its id, such as A17."""
    status = ORDERS.get(order_id)
    return f"{order_id} {status}." if status else f"{order_id} is not an order we have."
specialists.py
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from orders import lookup_order
from search import search_policies

orders_agent = create_agent(init_chat_model("groq:openai/gpt-oss-120b", temperature=0),
    tools=[lookup_order], system_prompt="Answer the order question in one short sentence, using only what the lookup_order tool returned.")
policies_agent = create_agent(init_chat_model("groq:openai/gpt-oss-120b", temperature=0),
    tools=[search_policies], system_prompt="Answer in one short sentence of plain text, using only what the search_policies tool returned. Name the source file in square brackets, like [refunds.md]. If the tool finds nothing, say the policies do not cover it.")


def last_reply(agent, question):
    return agent.invoke({"messages": [{"role": "user", "content": question}]})["messages"][-1].text


@tool
def ask_orders(question: str) -> str:
    """Ask the orders specialist where an order is."""
    return last_reply(orders_agent, question)


@tool
def ask_policies(question: str) -> str:
    """Ask the policies specialist about refunds, shipping and accounts."""
    return last_reply(policies_agent, question)

Running three questions through the router

Three questions through the router, with the real specialists: two that fit one specialist, and one that needs two.

ExampleAPI key
from router import answer
from specialists import orders_agent, policies_agent

agents = {"orders": orders_agent, "policies": policies_agent}
for question in ["Where is A17?", "Is shipping free?", "Where is A17, and how long does a refund take?"]:
    name, reply = answer(question, agents)
    print(f"{name:<8} {reply}")

Where the router hands off, and where it slips

  • The first two questions reached the right agent with no model deciding anything; a rule did it.
  • The third shows the limit: it mentions an order, so the whole sentence went to the orders agent alone.
  • The orders agent answered the A17 half; the refund half was dropped. The orders agent has no policy tool, so the refund question never reached the policies.
  • A router makes one choice: a question that needs two agents needs the supervisor from the subagents lesson, or a router that fans out and merges the answers, which the docs build with LangGraph.

Router vs supervisor

RouterSupervisor
DecisionOne step, up frontThe model, every turn
CostOne call, or plain rulesA model call per turn
Two areas at onceCannot combineAsks each and joins
PredictableSame route every time with rulesDepends on the model

When a router beats a supervisor

  • Sending each question to exactly one specialist by its topic.
  • A cheap first pass that escalates only the hard cases to a supervisor.
Watch out. A router sends the whole question to one agent, so a question that spans two areas gets half an answer. When two areas can appear together, route to a supervisor instead.
Try it yourself
  • Route questions that contain "refund" and an order id to the policies agent, and test both kinds.
  • Add a third specialist for account questions and a rule for it.
  • Print how many model calls answer makes for one question.

Every expert started right here.