What this reveals
The LangChain course's create_agent felt almost magical: hand it a model and some tools, and it handles an entire loop of deciding whether to call a tool, running it, and asking again, until it's ready to answer. It isn't magic. It's a small graph: a model node, a tool-execution node, and one conditional edge connecting them in a loop. This lesson builds that graph by hand, with the exact same primitives you already know from Beginner Lesson 7, StateGraph, MessagesState, ToolNode, tools_condition, just with a second tool added so it feels like a real, general-purpose agent instead of a toy.
Binding tools to the model
model_with_tools = model.bind_tools(tools)bind_tools attaches the tools' schemas (their names, descriptions, and argument shapes) to every request sent to the model, so the model can respond with a request to call one of them instead of plain text. This single line is the part create_agent never shows you, it's doing this exact call internally, once, when you pass it a tools list.
The model node
def call_model(state: MessagesState) -> dict: response = model_with_tools.invoke(state["messages"]) return {"messages": [response]}Same shape as every model node since Beginner Lesson 6: read the accumulated messages, call the model, return the reply. The only difference from a plain chatbot node is that this model was bound to tools, so its reply might contain tool_calls instead of, or alongside, plain text.
Wiring the loop
builder.add_node("agent", call_model)builder.add_node("tools", ToolNode(tools))builder.add_edge(START, "agent")builder.add_conditional_edges("agent", tools_condition)builder.add_edge("tools", "agent")Three pieces make this a loop rather than a straight line:
ToolNode(tools): inspects the last message'stool_calls, runs the matching Python function(s), and wraps each result in aToolMessageautomatically. You never write "if the model asked forcalculator, callcalculator" yourself.tools_condition: a ready-made routing function. If the last message has pending tool calls, it routes to"tools". Otherwise, it routes toEND.builder.add_edge("tools", "agent"): after a tool runs, control goes straight back to the model, so it can either call another tool or, once it has enough information, answer in plain text.
That's the entire ReAct loop, four lines of graph wiring. Everything create_agent does for you is contained in this shape.
Checkpoint
bind_tools: attaches tool schemas to every model request, the stepcreate_agentperforms invisibly.ToolNode: runs whichever tool(s) the model's last message requested, and packages the result as aToolMessage.tools_condition: routes to"tools"if tool calls are pending, otherwise toEND.- the loop:
agent -> tools -> agent -> ... -> END, a conditional edge plus one edge back, is the entire mechanismcreate_agentautomates.
If anything here still feels unclear, ask before moving to Lesson 24.