LangChain LangGraph
Module 1 Module 2 Module 3
Module 2 · Tools, State and Multi-Agent
1 2.1 MCP
2 2.1 Travel Agent
3 2.2 Runtime Context
4 2.2 State
5 2.3 Multi Agent
6 2.4 Wedding Planners
7 Bonus: RAG
8 Bonus: SQL
SUM Module Summary
HomeLangChainModule 2 · Tools, State and Multi-Agent2.4 Wedding Planners
📓 Notebook 📖 Explained
LANGCHAIN MODULE 2 · LESSON 2.4

Wedding planning multi-agent project
Flights · Venues · Playlist · Coordinator

In this section, the MCP, tools, state, and sub-Agents learned earlier are arranged together to build a coordinator agent that can plan a destination wedding.

1 Project structure: one coordinator, three specialists

This notebook is a comprehensive practice of Module 2. The main Agent plays the role of wedding coordinator. It first collects necessary fields and writes them into the state, and then entrusts three expert Agents to handle flights, venues and playlists respectively.

RoleTool sourceResponsible for content
Travel agentKiwi MCP toolsSearch for flights to and from your destination
Venue agentweb_searchSearch for wedding venues that match location and number of people
Playlist agentquery_playlist_dbQuery the Chinook SQLite database and plan a playlist
CoordinatorWrapped expert tools + update_stateCollecting requirements, dispatching tasks and combining results

2 Add a retry interceptor for remote MCP

Real remote tools fail. The notebook first defines RetryMCPInterceptor to retry transient MCP errors and convert unrecoverable errors into readable messages.

Python
RETRYABLE_MCP_CODES = {-32603}

class RetryMCPInterceptor:
    """Intercept MCP tool calls: retry transient failures, surface all errors gracefully."""

client = MultiServerMCPClient(
    {
        "travel_server": {
            "transport": "streamable_http",
            "url": "https://mcp.kiwi.com",
        }
    },
    tool_interceptors=[RetryMCPInterceptor()],
)

tools = await client.get_tools()
Production mindset

The earlier lessons focused on "can it call the tool?" This lesson starts handling "what if the call fails?" Remote MCP, search and databases all need explicit error-handling boundaries.

3 Prepare Web Search and SQL tools

The venue specialist uses Tavily search, and the playlist specialist queries the local Chinook database. Both tools are exposed to their corresponding expert Agents through @tool.

Python
tavily_client = TavilyClient()
search_count = 0

@tool
def web_search(query: str) -> Dict[str, Any]:
    """Search the web for information."""
    # call Tavily and return search results


db = SQLDatabase.from_uri("sqlite:///resources/Chinook.db")

@tool
def query_playlist_db(query: str) -> str:
    """Query the database for playlist information"""
    try:
        return db.run(query)
    except Exception as e:
        return f"Error: {e}"
Tool descriptions matter

In multi-agent scenarios, the tool docstring is the model's operating instruction. Clearer descriptions make expert Agents less likely to call tools with wrong parameters.

4 Define WeddingState

The coordinator first extracts wedding requirements from the user input and writes them into state. The notebook's WeddingState includes origin, destination, guest count and music genre.

Python
class WeddingState(AgentState):
    origin: str
    destination: str
    guest_count: str
    genre: str
origin

Where the couple is departing from.

destination

The wedding destination.

guest_count

The venue capacity requirement.

genre

The playlist style.

5 Create three expert Agents

Each expert Agent receives only its own tools and system prompt. This is easier to control than making one large Agent understand flights, venues and SQL at the same time.

Python
today = date.today().isoformat()

travel_agent = create_agent(
    model=deepseek_model(),
    tools=tools,
    system_prompt=f"""
    You are a travel agent. Search for flights to the desired destination wedding location.
    Today is {today}. Any departureDate you pass to tools must be strictly after {today}.
    You are not allowed to ask any more follow up questions.
    """,
)

venue_agent = create_agent(
    model=deepseek_model(),
    tools=[web_search],
    system_prompt="""You are a venue specialist. Search for venues in the desired location.""",
)

playlist_agent = create_agent(
    model=deepseek_model(),
    tools=[query_playlist_db],
    system_prompt="""You are a playlist specialist. Query the SQL database and curate a wedding playlist.""",
)
Responsibility isolation

Expert Agent prompts can be shorter and more specific; the main Agent only collects information, delegates and summarizes.

6 The coordinator reads state through ToolRuntime

The coordinator's tools do not search directly. They read structured fields from runtime.state, then call the corresponding expert Agent.

Python
@tool
async def search_flights(runtime: ToolRuntime) -> str:
    """Travel agent searches for flights to the desired destination wedding location."""
    origin = runtime.state["origin"]
    destination = runtime.state["destination"]
    response = await travel_agent.ainvoke(
        {"messages": [HumanMessage(content=f"Find flights from {origin} to {destination}")]}
    )
    return response["messages"][-1].content

@tool
def search_venues(runtime: ToolRuntime) -> str:
    guest_count = runtime.state["guest_count"]
    destination = runtime.state["destination"]
    response = venue_agent.invoke(
        {"messages": [HumanMessage(content=f"Find venues in {destination} for {guest_count} guests")]}
    )
    return response["messages"][-1].content
Why write state first

If requirements are not saved in structured state first, every expert has to extract fields again from the raw user sentence, which raises the error rate.

7 Run and stream output

Finally, the notebook creates the coordinator and uses a higher recursion_limit to leave room for multiple tool-call rounds. It demonstrates both ordinary ainvoke and streaming output with astream.

Python
coordinator = create_agent(
    model=deepseek_model(),
    tools=[search_flights, search_venues, suggest_playlist, update_state],
    state_schema=WeddingState,
    system_prompt="""
    You are a wedding coordinator.
    First find all the information you need to update the state.
    Then delegate tasks to your specialists for flights, venues, and playlists.
    """,
)

response = await coordinator.ainvoke(
    {
        "messages": [
            HumanMessage(content="I'm from London and I'd like a wedding in Paris for 100 guests, jazz-genre")
        ]
    },
    config={"recursion_limit": 40},
)
Overall takeaway

This lesson ties together Module 2's main themes: external tools come from MCP/search/SQL, temporary requirements go into state, expert Agents solve subtasks, and the coordinator organizes the final answer.