AI EngineeringZero to ProductionHome·About·Contact
LangChain & LangGraph · Chapter L3

LangChain Agents & Tool Use

A chain runs a fixed path (L2). An agent lets the model choose the path — which tools to call, in what order, when to stop. This chapter defines tools the LangChain way, runs a tool-calling agent, and shows why the modern answer to "build an agent" points at LangGraph's prebuilt ReAct agent.

⏱️ ~55 min🧪 3 labs🎯 Intermediate
⚙️ To run this for realThe code here is complete and correct as written. To actually execute it you'll need:
  • an Anthropic API key (ANTHROPIC_API_KEY) + pip install anthropic
Reading and learning works without any of this — run when you're ready. IDs/ARNs in examples are placeholders; swap in your own.

Learning objectives

  • Define tools with the @tool decorator and clear schemas/descriptions.
  • Bind tools to a model and read back tool-call requests.
  • Run a tool-calling agent and trace its reason→act→observe loop.
  • Understand why LangGraph's create_react_agent is the current recommended path.
  • Add safety: approval gates, error handling, and step limits.
This is L1's ReAct + C2's tool loop, framework-wrappedYou already know the mechanics: the model asks for a tool, you run it, feed the result back, repeat (C2). LangChain gives that loop reusable parts — a tool abstraction and an agent runtime — so you write the tools and it drives the loop.

Defining tools intermediate

A LangChain tool is a function plus a schema and a description. The @tool decorator builds all three from a typed function with a docstring.

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Lab L3.1
tools.pyfrom langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a city. Use when the user asks about weather."""
    return f"18°C and clear in {city}"

@tool
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

print(get_weather.name)          # "get_weather"
print(get_weather.description)   # from the docstring — drives tool selection
print(get_weather.args)          # {"city": {"type": "string"}} — from the type hints
▶ How this works

A tool is just a normal Python function that you let the model call. The @tool decorator sits on top of the function and, from the function itself, builds the three things the model needs to use it: a name, a description, and a list of arguments with their types. You write one plain function; LangChain turns it into something an agent can reach for.

  1. @tool is a decorator — a label placed on the line above a function. It wraps get_weather so that instead of a plain function you get a LangChain tool object, without changing the code inside.
  2. The function body is ordinary Python: get_weather takes a city string and returns a weather sentence; add takes two integers and returns their sum. This is the real work the tool does when called.
  3. The triple-quoted line under each def is the docstring. LangChain uses it as the tool's description — the text the model reads to decide when to call the tool. Notice it says when to use it ("Use when the user asks about weather"), not just what it does.
  4. The three print lines read back what the decorator built: .name is the function name, .description comes from the docstring, and .args is inferred from the city: str type hint — you didn't write the schema by hand, the type hints did it for you.

What the output means: Three lines print: get_weather (the name), the docstring text (the description), and {"city": {"type": "string"}} (the argument schema). Together these are exactly what the model sees when deciding whether to use this tool.

Try this: Change the docstring to something vague like "Gets weather." and imagine you are the model choosing a tool — the clear "Use when…" version is much easier to pick correctly. A vague description is the #1 reason an agent ignores or misuses a tool.

The docstring is the tool's promptThe model picks tools by their description. Be prescriptive about when to call it ("Use when the user asks about weather"), not just what it does — the same lesson as C2's tool descriptions. A vague docstring is the #1 cause of an agent ignoring or misusing a tool.

Binding tools to a model intermediate

Before an agent, understand the primitive under it: bind_tools. It tells the model which tools exist; the model responds with tool-call requests you then execute — exactly the C2 pattern, one layer down.

Requires: pip install langchain-anthropic

bind.pyfrom langchain_anthropic import ChatAnthropic

model = ChatAnthropic(model="claude-opus-4-8", max_tokens=512)
model_with_tools = model.bind_tools([get_weather, add])

msg = model_with_tools.invoke("What's the weather in Paris?")
print(msg.tool_calls)   # [{'name': 'get_weather', 'args': {'city': 'Paris'}, 'id': '...'}]
The model requests; it never executesJust like C2, bind_tools gets you a request — name + args + id. Running the function and returning the result is still your job. An agent is the thing that automates that request→run→feed-back loop for you.
▶ How this works

This is the primitive underneath an agent: bind_tools. It hands the model the list of tools that exist, then the model replies with a request to call one — it does not run the tool itself. Running it and feeding the result back is still your job. An agent (next lab) is just the thing that automates that request→run→feed-back loop.

  1. ChatAnthropic(model="claude-opus-4-8", max_tokens=512) creates the model client. model picks which Claude to use; max_tokens caps how long the reply can be.
  2. model.bind_tools([get_weather, add]) makes a new model handle that knows about those two tools. It doesn't call them — it just makes them available for the model to request.
  3. .invoke("What's the weather in Paris?") sends the question. Because the question is about weather, the model responds by asking to call get_weather with city="Paris".
  4. msg.tool_calls is that request: a list with the tool name, the args the model filled in, and an id. This is a plan to call a tool, not the tool's answer.

What the output means: msg.tool_calls prints something like [{'name': 'get_weather', 'args': {'city': 'Paris'}, 'id': '...'}] — the model chose the right tool and extracted Paris from the sentence on its own. There is no weather text yet because nothing has actually run the function.

Try this: Ask "What is 12 plus 30?" instead and look at tool_calls — the model should now request add with {'a': 12, 'b': 30}. Same mechanism, different tool, chosen from the descriptions.

Lab L3.2 · A tool-calling agent intermediate

The agent runtime closes the loop: it calls the model, executes any requested tools, feeds results back, and repeats until the model answers. The modern, recommended way to get one is LangGraph's create_react_agent — a prebuilt ReAct agent (L1) that LangChain now points to as the default.

Model tool_calls? Run tools results fed back as messages answer no tool_calls The runtime is your C2 while loop. Model emits tool calls → runtime executes them → results go back as messages → model decides again. It exits when the model returns a plain answer with no tool calls. You supply model + tools; the runtime supplies the loop.
🗺️ How to read this diagram

This picture is the agent loop — what create_react_agent does for you in the next lab. It's a cycle between the model and your tools that repeats until the model has a final answer.

  • Start at the Model box on the left. The model looks at the conversation and decides: do I need a tool? The label tool_calls? on the arrow is that decision point.
  • If it wants a tool (top arrow), control moves right to Run tools — the runtime actually executes the requested function (the step bind_tools left up to you).
  • The curved arrow back to Model is labelled results fed back as messages: the tool's output is added to the conversation and handed back to the model, which can now decide again. That back-and-forth is the loop.
  • If it wants no tool (the no tool_calls arrow on the far right), the loop exits and the model returns a plain answer. That's how the agent knows it's done.

In short: You supply the model and the tools; the runtime supplies the loop. It is the exact "ask for a tool → run it → feed the result back → ask again" cycle, done automatically until the model stops asking for tools.

Lab L3.2
shellpip install langgraph langchain-anthropic

Requires: pip install langchain-anthropic langgraph

agent.pyfrom langgraph.prebuilt import create_react_agent
from langchain_anthropic import ChatAnthropic

model = ChatAnthropic(model="claude-opus-4-8", max_tokens=1024)

agent = create_react_agent(model, tools=[get_weather, add])   # prebuilt ReAct loop

result = agent.invoke({"messages": [("user", "Weather in Paris, and what's 12 + 30?")]})
for m in result["messages"]:
    m.pretty_print()   # see the full reason→act→observe trace
▶ How this works

Here the loop from the diagram becomes one line of code. create_react_agent gives you a ready-made agent that wraps a model and a set of tools and runs the whole call-model → run-tools → repeat cycle for you until the model produces a final answer.

  1. The two import lines bring in create_react_agent (the prebuilt agent from LangGraph) and ChatAnthropic (the model, same as the previous lab).
  2. create_react_agent(model, tools=[get_weather, add]) builds the agent. You pass it the model and the tools; it supplies the loop. Nothing has run yet — this just assembles the agent.
  3. agent.invoke({"messages": [("user", "...")]}) starts it with a question that needs two tools (weather and arithmetic). The agent will call each tool as needed and keep going until it can answer.
  4. The for m in result["messages"] loop with m.pretty_print() prints every step of the conversation — the model's tool requests, the tool results, and the final reply — so you can watch the agent reason, act, and observe.

What the output means: You see the full trace: the agent asking to call get_weather('Paris'), the tool result coming back, a call to add(12, 30), its result, and finally a plain-language answer combining both. That sequence of messages is the loop from the diagram, printed out.

Try this: Ask a question that needs only one tool ("What's the weather in Tokyo?") and read the trace — you'll see just one tool call, then the answer. The agent uses only the tools the question actually requires.

Old AgentExecutor tutorials are legacyYou'll find lots of examples using initialize_agent / AgentExecutor and "agent types" like ZERO_SHOT_REACT_DESCRIPTION. Those are the older LangChain agent API. The current guidance is the LangGraph-based create_react_agent shown here — it's more controllable and it's the bridge into L4/L5. Learn this one.

Why the agent lives in LangGraph now advanced

Notice the import: the recommended agent comes from langgraph, not langchain. That's deliberate. A real agent needs things a plain chain can't express:

NeedWhy a chain can't & a graph can
CyclesThe loop runs an unknown number of times — chains are acyclic; graphs allow cycles (L4/L5)
StateMessages, scratchpad, counters persist across steps — graphs carry explicit state (L4)
Branching"If tool call → run tools, else → end" is conditional routing (L4)
Human-in-the-loopPause for approval before a risky tool, then resume — needs interrupts + persistence (L5)
L3 is the on-ramp to L4/L5create_react_agent is a prebuilt graph — convenient, but a black box. The next two chapters open it up: L4 builds the state + routing from scratch, L5 adds cycles, human approval, and durable persistence. When the prebuilt agent isn't enough, you'll build your own.

Lab L3.3 · Making the agent safe advanced

An agent that can call tools can cause effects. The same discipline from Chapter 4 and Claude Code (C3) applies here.

Lab L3.3

Requires: pip install langchain-core

safe_tools.pyfrom langchain_core.tools import tool

@tool
def delete_record(record_id: str) -> str:
    """Delete a record by id. Destructive — requires confirmation."""
    if not _approved(record_id):          # human-in-the-loop gate
        return "BLOCKED: awaiting human approval."
    try:
        _do_delete(record_id)
        return f"Deleted {record_id}."
    except Exception as e:
        return f"ERROR: {e}"          # return errors so the agent can adapt
▶ How this works

A tool that can change things (delete, send, pay) can cause real damage if the model calls it wrongly. This lab shows the safety pattern for such tools: check for approval before acting, and return errors as text instead of crashing. Both keep the agent controllable.

  1. delete_record is a normal @tool, but its docstring flags it as destructive — a signal to both the reader and the model that it needs care.
  2. if not _approved(record_id): is the approval gate. Before doing anything, the tool checks whether a human has approved this action; if not, it returns "BLOCKED: awaiting human approval." and stops. Nothing is deleted.
  3. Only after approval does the try: block run the real delete via _do_delete. Wrapping it in try means a failure won't crash the whole agent.
  4. except Exception as e: catches any failure and returns it as a string ("ERROR: {e}") instead of raising. The agent reads that error like any other tool result and can adapt — an uncaught exception would kill the run instead.

What the output means: If the record isn't approved, the tool returns BLOCKED and nothing happens. If it is approved and the delete works, it returns Deleted {record_id}.. If the delete throws, the agent receives an ERROR: string it can react to — the run keeps going either way.

Try this: Trace what happens when _approved returns False: the very first if returns the BLOCKED message, so _do_delete is never reached. That single guard line is what stands between the model and an irreversible action.

GuardrailHow
Approval gateDestructive tools check for confirmation before acting (real gate: L5 interrupts)
Least privilegeOnly give the agent tools it needs; prefer read/notify over write/delete
Return errors, don't raiseA tool that returns an error string lets the agent recover; an uncaught exception kills the run
Step limitCap iterations (a recursion/step limit) so a confused agent can't loop forever
Tool inputs are untrusted model outputThe agent's tool args come from the model, which can be steered by injected content in retrieved docs or tool results (T1). Validate inputs inside each tool, sandbox anything that runs commands, and never wire a raw destructive action to an ungated tool.

Common pitfalls expert

PitfallFix
Following AgentExecutor/initialize_agent tutorialsUse LangGraph's create_react_agent (current path)
Vague tool docstringsState when to call the tool, not just what it does
Tools that raise on errorReturn an error string so the agent can adapt
No step limit → infinite loopsSet a recursion/step cap
Ungated destructive toolsAdd an approval gate (real one in L5)
Reaching for a prebuilt agent when a chain fitsIf the path is fixed, use a chain (L2)

Exercises expert

Exercise L3.1 — Three-tool agent

Context: Reading a trace to confirm which tools fired is how you verify an agent actually reasoned rather than guessed.

Your task: Give create_react_agent three tools — get_weather, add, and a search_docs tool backed by your L2 retriever — then ask a question that needs two of them.

Requirements:

  • Wire three distinct tools, including one backed by your earlier retriever
  • Ask a question that requires two of the three
  • Read the trace to confirm both were actually called
  • Confirm the agent chose them itself

💡 Hint: Craft the question so a single tool can't answer it — that forces the multi-tool path you want to observe.

Exercise L3.2 — Same agent, raw SDK

Context: When the framework is worth it — versus a clearer raw loop — depends on how many tools and how much custom gating you need.

Your task: Rebuild a framework agent using only the raw Chapter-2 manual tool loop, then compare line count and control.

Requirements:

  • Reproduce the agent with a hand-written tool loop and the raw SDK
  • Compare line count against the framework version
  • Compare how much control each gives you
  • State when the framework is worth it and when the raw loop is clearer

💡 Hint: The raw loop stays clear for one or two tools with no gating; the framework pulls ahead as tools and custom control multiply.

Exercise L3.3 — Gate a destructive tool

Context: A tool-level flag can refuse an action, but it cannot pause the whole run for a human and resume — which is exactly what L5's interrupts provide.

Your task: Add a send_email tool that returns "BLOCKED" unless an approved=True flag is set, prompt the agent to send an email, and confirm it's blocked.

Requirements:

  • The tool refuses unless an approval flag is set
  • Prompt the agent to use it and confirm the refusal
  • Explain why a real gate must pause the run for a human
  • Connect that to LangGraph interrupts + a checkpointer (L5)

💡 Hint: A flag can say no, but only a checkpointed interrupt can stop, surface the pending action, and resume on approval — the production-grade gate.

Show the connection

A tool-level flag can refuse, but it can't pause and wait for a human then resume where it left off. LangGraph interrupts + a checkpointer (L5) stop the graph, surface the pending action, and resume on approval — the production-grade version of this gate.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · A tool = function + schema + descriptionBeginner

Context: LangChain's @tool builds a name, a description, and an argument schema from a typed function with a docstring — because the model never sees your code, only that packaged interface.

Your task: Model @tool: a decorator that captures the name, description, and arg schema from any typed function.

Requirements:

  • Capture the function name as the tool name
  • Take the description from the docstring
  • Derive the arg schema from the type hints (via inspect)
  • Attach all three to the function and show them

💡 Hint: inspect.signature gives you the parameters and their annotations; the docstring drives selection, the hints drive the schema.

Show solution

The docstring drives selection; the type hints drive the schema. Model @tool in stdlib:

import inspect

def tool(fn):
    fn.tool_name = fn.__name__
    fn.description = (fn.__doc__ or "").strip()
    sig = inspect.signature(fn)
    fn.args = {p.name: (p.annotation.__name__ if p.annotation != inspect._empty else "any")
               for p in sig.parameters.values()}
    return fn

@tool
def get_weather(city: str) -> str:
    "Get the current weather for a city."
    return f"18C and clear in {city}"

print(get_weather.tool_name)    # get_weather
print(get_weather.description)  # from the docstring
print(get_weather.args)         # {'city': 'str'}

The model never sees your code — only the name, description, and arg schema. That is what @tool packages.

Exercise 2 · Bind tools and read back a tool-call requestIntermediate

Context: Binding tools tells the model the schemas so it can emit a structured call. The model requests a call; you still run the tool and feed the result back.

Your task: Model a tool-bound model: given a user message, a stub model returns a tool-call request, which you then dispatch to the real tool.

Requirements:

  • The stub model decides whether to request a tool and with what arguments
  • Return either a tool-call request or direct text
  • A dispatcher runs the requested tool with its args, or returns the text
  • Show one message that triggers a tool and one that doesn't

💡 Hint: Separate "the model asks for a call" from "you execute it" — binding only communicates the schema; execution is still your job.

Show solution

The model requests a call; you dispatch it. Runnable:

TOOLS = {"get_weather": lambda city: f"18C in {city}",
         "add": lambda a, b: a + b}

def model_with_tools(msg):
    # stub: decide which tool the model would ask for
    if "weather" in msg.lower():
        return {"tool_call": {"name": "get_weather", "args": {"city": "Paris"}}}
    return {"text": "I can answer directly."}

def dispatch(resp):
    if "tool_call" in resp:
        tc = resp["tool_call"]
        return TOOLS[tc["name"]](**tc["args"])
    return resp["text"]

print(dispatch(model_with_tools("what's the weather?")))   # 18C in Paris
print(dispatch(model_with_tools("hi")))                     # I can answer directly.

Binding tools = telling the model the schemas so it can emit a structured call. You still run the tool and feed the result back.

Exercise 3 · The full reason->act->observe agent loopAdvanced

Context: The agent runtime is the reason→act→observe loop: the model requests tools, you execute and append observations, repeat until a final answer — exactly what create_react_agent compiles.

Your task: Model a tool-calling agent loop offline on a multi-step task.

Requirements:

  • The model reads the message history to decide the next tool call or a final answer
  • Execute each requested tool and append the result as a tool message
  • Loop until the model returns a final answer
  • Bound the loop with a max-steps cap
  • Show it completing a task that needs more than one tool call

💡 Hint: Append tool results as messages the model reads next round; a multi-step task (e.g. add then multiply) exercises the loop properly.

Show solution

The agent runtime is this loop. Runnable stdlib:

TOOLS = {"add": lambda a, b: a + b, "mul": lambda a, b: a * b}

def model(msgs):
    # stub reasoner: (2+3)*4 in two tool calls, then answer
    done = [m for m in msgs if m["role"] == "tool"]
    if len(done) == 0:
        return {"call": ("add", {"a": 2, "b": 3})}
    if len(done) == 1:
        return {"call": ("mul", {"a": done[0]["content"], "b": 4})}
    return {"final": done[-1]["content"]}

def run(task, max_steps=6):
    msgs = [{"role": "user", "content": task}]
    for _ in range(max_steps):
        resp = model(msgs)
        if "final" in resp:
            return resp["final"]
        name, args = resp["call"]
        result = TOOLS[name](**args)
        msgs.append({"role": "tool", "content": result})
    return "step limit"

print(run("compute (2+3)*4"))   # 20

This reason->act->observe loop is exactly what LangGraph's create_react_agent compiles for you.

Exercise 4 · Add safety: approval gate + step limit + error handlingExpert

Context: Production agents need brakes: a step limit, error isolation around each tool, and an approval gate before a dangerous tool — the L5 human-in-the-loop pattern applied per tool.

Your task: Wrap the tool-call step with a step limit, a try/except around each tool, and an approval gate that blocks dangerous tools.

Requirements:

  • A step limit stops runaway loops
  • A try/except keeps one failing tool from crashing the run
  • An approval gate blocks tools on a dangerous list unless approved
  • Return a structured result distinguishing result / blocked / error
  • Show an allowed call, a blocked call, and a caught error

💡 Hint: Route every tool call through one guarded function that checks the gate, then runs inside try/except; a denied dangerous tool returns a block, not a result.

Show solution

Three brakes around the loop. Runnable:

DANGEROUS = {"delete_file"}
TOOLS = {"read_file": lambda p: "contents", "delete_file": lambda p: "deleted"}

def guarded_call(name, args, approve):
    if name in DANGEROUS and not approve(name, args):
        return {"blocked": f"approval denied for {name}"}
    try:
        return {"result": TOOLS[name](**args)}
    except Exception as e:
        return {"error": repr(e)}       # errors don't crash the loop

def auto_deny(name, args):
    return False                        # a human would decide here

print(guarded_call("read_file", {"p": "a.txt"}, auto_deny))     # result
print(guarded_call("delete_file", {"p": "a.txt"}, auto_deny))   # blocked
print(guarded_call("read_file", {"nope": 1}, auto_deny))        # error, caught

Step limits stop runaways, try/except keeps one bad tool from killing the run, and the approval gate is the L5 human-in-the-loop pattern applied per tool.

Exercise 5 · Why create_react_agent is the recommended pathProfessional

Context: The modern answer to "build an agent" points at LangGraph's prebuilt create_react_agent — itself a compiled graph — and you drop to raw LangGraph only for the control it hides.

Your task: Write a decision helper for when to use the prebuilt ReAct agent versus raw LangGraph.

Requirements:

  • Default to create_react_agent for a standard ReAct loop
  • Drop to raw LangGraph for custom routing, extra state, human pauses, or multi-agent
  • Take those needs as parameters
  • Show a case choosing the prebuilt and a case choosing raw LangGraph

💡 Hint: If any of custom-routing / extra-state / HITL / multi-agent is true, you've outgrown the prebuilt; otherwise don't hand-write the loop.

Show solution

Use the prebuilt until you need control it hides. Runnable:

def agent_choice(needs_custom_routing, needs_extra_state,
                 needs_human_pause, needs_multi_agent):
    if any([needs_custom_routing, needs_extra_state,
            needs_human_pause, needs_multi_agent]):
        return "raw LangGraph -- you need control create_react_agent hides"
    return "create_react_agent -- prebuilt ReAct loop, don't hand-write it"

print(agent_choice(False, False, False, False))  # prebuilt
print(agent_choice(True,  False, True,  False))  # raw LangGraph

create_react_agent is itself a compiled LangGraph graph — start there, and drop to raw nodes/edges only for custom routing, extra state, HITL, or multi-agent (L4/L5).

Exercise 6 · The real tool-calling agent (needs langchain installed)Industry scenario

Context: The production tool-calling agent is the prebuilt create_react_agent driving real @tool functions against a Claude model — the loop you built by hand, now compiled.

Your task: Write the real prebuilt agent with a Claude model and at least one @tool function, using documented APIs. (Needs the libraries installed.)

Requirements:

  • Define a tool with the real @tool decorator (typed + docstring)
  • Create a ChatAnthropic model
  • Build the agent with create_react_agent(model, tools=[...])
  • Invoke it with a messages input and read the final answer
  • Note that interrupt_before adds the L5 approval gate; label it as needing langgraph/langchain-anthropic + a key

💡 Hint: You supply the tools; the prebuilt agent drives the reason→act→observe loop you modeled by hand — verify imports against your installed version.

Show solution

Correct prebuilt agent. Needs pip install langgraph langchain-anthropic + ANTHROPIC_API_KEY:

from langchain_core.tools import tool
from langchain_anthropic import ChatAnthropic
from langgraph.prebuilt import create_react_agent

@tool
def get_weather(city: str) -> str:
    "Get the current weather for a city."
    return f"18C and clear in {city}"

model = ChatAnthropic(model="claude-opus-4-8", max_tokens=512)
agent = create_react_agent(model, tools=[get_weather])

result = agent.invoke(
    {"messages": [{"role": "user", "content": "weather in Paris?"}]}
)
print(result["messages"][-1].content)   # model answers using the tool

You write the tools; the prebuilt agent drives the reason->act->observe loop you modeled by hand above. Add interrupt_before for the L5 approval gate. Verify imports against your installed version.

✓ Checkpoint — you can move on when you can…

  • Define a tool with @tool and explain why the docstring matters.
  • Use bind_tools and read back tool_calls.
  • Run create_react_agent and read its message trace.
  • Explain why the recommended agent lives in LangGraph.
  • Add approval gates, error handling, and a step limit.
🏗️ Toward the capstoneThe DevOps agent's tools — terraform_plan, kubectl_get, describe_pod — are exactly these LangChain tools, each with a description that tells the model when to reach for it and a gate on the destructive ones. Whether you drive them with create_react_agent or a custom LangGraph graph (L4/L5) is the choice the next two chapters equip you to make. See the tools + agent-loop build →

Knowledge check check yourself

✓ Knowledge check

With bind_tools, what does the model actually return, and whose job is it to run the tool?

Show answer
bind_tools only tells the model which tools exist; the model responds with a tool-call request (name + args + id) in msg.tool_calls — it never executes anything. Running the function and feeding the result back is still your job; an agent runtime is what automates that request→run→feed-back loop.
✓ Knowledge check

Why does the recommended create_react_agent live in langgraph rather than langchain?

Show answer
A real agent needs capabilities a plain (acyclic) chain can't express: cycles (loop an unknown number of times), explicit persisted state, conditional branching, and human-in-the-loop pause/resume. LangGraph provides those, so the prebuilt ReAct agent is a compiled LangGraph graph and the on-ramp to L4/L5.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in