NovuSpark
All articles

March 20, 2026 · NovuSpark Team

LangChain Agents and Tools Explained

This is the fourth post in our LangChain fundamentals series, building on chains, RAG, and memory.

Every chain built so far in this series runs the same fixed sequence of steps, every single time — retrieve, then prompt, then generate, always in that order. That's genuinely the right shape for a huge share of real applications. It stops being sufficient the moment the correct sequence of steps actually depends on what's being asked: one question needs a database lookup, another needs a web search, a third needs both, in an order that isn't knowable in advance. An agent is LangChain's answer to that specific problem.

Tools: giving the model real capabilities to choose from

from langchain_core.tools import tool
 
@tool
def get_course_availability(course_name: str) -> str:
    """Check available seats for a specific training course."""
    availability = {"Kubernetes Fundamentals": 4, "Terraform Fundamentals": 0}
    seats = availability.get(course_name, "unknown")
    return f"{course_name}: {seats} seats available"
 
@tool
def get_current_date() -> str:
    """Get today's date."""
    from datetime import date
    return date.today().isoformat()
 
tools = [get_course_availability, get_current_date]

The @tool decorator turns an ordinary Python function into something an agent can choose to call — the function's docstring becomes the description the model actually uses to decide when this tool is the right one to reach for, which makes writing a clear, specific docstring a real design decision, not just documentation for other developers.

Building an agent

from langchain.agents import create_tool_calling_agent, AgentExecutor
 
agent = create_tool_calling_agent(model, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
 
result = executor.invoke({
    "input": "Is there space in the Kubernetes course, and what's today's date?"
})
print(result["output"])
> Entering new AgentExecutor chain...
Invoking: `get_course_availability` with `{'course_name': 'Kubernetes Fundamentals'}`
Kubernetes Fundamentals: 4 seats available
Invoking: `get_current_date` with `{}`
2026-03-20

Yes, there are 4 seats available in the Kubernetes Fundamentals course.
Today's date is March 20, 2026.
> Finished chain.

This single question needed two separate tool calls, in an order the agent decided for itself — not a sequence anyone hardcoded into a fixed chain. That's the actual mechanism underneath: on each step, the model examines the available tools, the conversation so far, and decides whether to call a tool, and which one, repeating until it has what it needs to produce a final answer. This is the same tool-calling mechanism covered directly against the OpenAI API in our earlier series — LangChain's AgentExecutor is managing the actual loop (call a tool, feed the result back, decide the next step, repeat) that you would otherwise have to write by hand.

Why this needs more caution than a fixed chain

A fixed chain's behavior is fully predictable before you ever run it — the same steps, in the same order, every time. An agent's actual sequence of tool calls is decided by the model, at run time, which means it's not fully predictable in the same way. Two immediate, practical consequences:

  • Cost and latency become variable, not fixed. A question resolved in one tool call costs meaningfully less than one requiring four — and you generally can't know in advance which a given user's question will turn out to need.
  • A tool that takes a real, consequential action needs its own safeguards, independent of trusting the agent's judgment. A send_refund tool that an agent can call needs its own validation and limits — a maximum refund amount, a required human approval step for anything above a threshold — the same defense-in-depth principle behind least-privilege IAM policies covered throughout this blog, applied here to what an autonomous agent is actually permitted to do, not just what it's technically capable of requesting.

Setting real boundaries on an agent

executor = AgentExecutor(
    agent=agent,
    tools=tools,
    max_iterations=5,
    max_execution_time=30,
)

max_iterations caps how many tool-calling steps an agent can take before being forced to stop and return whatever it has — a genuine safeguard against a agent looping unproductively on a question it can't actually resolve with the tools it has. max_execution_time is a wall-clock backstop for the same underlying risk: bounding worst-case cost and latency, rather than trusting the agent to always converge quickly on its own.

When a fixed chain is still the better choice

Agents are not a strict upgrade over the fixed chains covered earlier in this series — they're the right tool for a genuinely different problem. If the actual sequence of steps a task needs is always the same, a fixed chain is more predictable, faster, cheaper, and meaningfully easier to debug than an agent making the same decision fresh on every single run. Reach for an agent specifically when the right sequence of steps genuinely depends on the specific input — not as a default, more sophisticated-sounding upgrade to every chain in an application.

What to actually remember from this post

  • An agent decides its own sequence of tool calls, at run time — the right tool when the correct sequence genuinely depends on the specific question, not a universal upgrade over fixed chains.
  • A tool's docstring is what the model uses to decide when to call it — write it as a real design decision, not just documentation.
  • Cost and latency become variable with an agent, not fixed — a direct consequence of the model choosing its own number of steps.
  • max_iterations and max_execution_time are real safeguards worth setting explicitly, and any consequential tool needs its own independent validation, not just trust in the agent's judgment.

Next in the series: Debugging and Observability in LangChain Applications, the final post — covering how you actually see what a chain or agent did, after the fact, when something goes wrong.

Ready when you are

Want training built around your team's real work?

Tell us about your team and what you're trying to solve — we'll recommend a program that fits.