TechByteByByte

Ten Progressive Agent Patterns

Put create_agent through its paces with ten small, realistic agents — from a simple calculator to a full research assistant — each one teaching you one new, recognizable shape.

#LangChain#Agents#create_agent#Patterns

You understand create_agent properly now — what it’s built on, what it’s hiding, and why it’s trustworthy. This module isn’t about learning new mechanics. It’s about pattern recognition: seeing ten small, realistic agent shapes, back to back, so that when a real project lands in front of you, you recognize which of these shapes it actually is.

Each one stays deliberately small. The goal isn’t impressive code — it’s building a mental library of recognizable agent shapes you can reach for.

Agent 1: the calculator agent

The simplest possible real agent — one tool, one clear job.

from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain.agents import create_agent

@tool
def calculate(expression: str) -> str:
    """Evaluate a simple arithmetic expression, like '238 * 47'."""
    try:
        return str(eval(expression, {"__builtins__": {}}))
    except Exception as e:
        return f"Couldn't evaluate that: {e}"

agent = create_agent(model=init_chat_model("openai:gpt-4o-mini"), tools=[calculate])
result = agent.invoke({"messages": [{"role": "user", "content": "What is 238 times 47?"}]})
print(result["messages"][-1].content)

What’s new here: eval(expression, {"__builtins__": {}}) deliberately strips away access to Python’s built-in functions, so the model can’t trick the tool into doing anything beyond basic arithmetic — a small, real, worthwhile safety habit whenever a tool evaluates any kind of expression.

Agent 2: the weather agent, asked about several cities at once

from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain.agents import create_agent

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a given city."""
    return f"It's 22°C and sunny in {city}."

agent = create_agent(model=init_chat_model("openai:gpt-4o-mini"), tools=[get_weather])
result = agent.invoke({"messages": [{"role": "user", "content": "What's the weather in Nairobi and in Oslo?"}]})
print(result["messages"][-1].content)

What’s new here: a single question triggering the same tool twice, with different arguments each time. Recall Module 13’s lesson about looping over every entry in tool_callscreate_agent is doing exactly that internally, handling both calls correctly without you writing any of that logic yourself.

Agent 3: multiple, genuinely different tools

from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain.agents import create_agent

@tool
def calculate(expression: str) -> str:
    """Evaluate a simple arithmetic expression."""
    try:
        return str(eval(expression, {"__builtins__": {}}))
    except Exception as e:
        return f"Couldn't evaluate that: {e}"

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a given city."""
    return f"It's 22°C and sunny in {city}."

agent = create_agent(model=init_chat_model("openai:gpt-4o-mini"), tools=[calculate, get_weather])
result = agent.invoke({"messages": [{"role": "user", "content": "What's 15% of 240, and what's the weather in Tokyo?"}]})
print(result["messages"][-1].content)

What’s new here: two genuinely unrelated tools, and one question that needs both. This is the real test of good tool descriptions from Module 12 — the model has to correctly recognize that this single question actually contains two separate sub-tasks.

Agent 4: a search agent

from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain.agents import create_agent

@tool
def web_search(query: str) -> str:
    """Search the web for current information on a topic."""
    # a stand-in for a real search API call
    return f"Top result for '{query}': LangChain 1.0 was released with a redesigned agent architecture."

agent = create_agent(model=init_chat_model("openai:gpt-4o-mini"), tools=[web_search])
result = agent.invoke({"messages": [{"role": "user", "content": "What's new in LangChain?"}]})
print(result["messages"][-1].content)

What’s new here: this is the shape behind every real search-augmented agent you’ve used — a tool standing in for a genuine search API. In a real application, you’d replace this function’s body with an actual API call (using the same try/except pattern from Module 12’s tool examples), but the pattern — one tool, wrapping a lookup for current information — stays identical.

Agent 5: a database lookup agent

from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain.agents import create_agent

USERS = {"u_1": {"name": "Amara", "plan": "Pro"}, "u_2": {"name": "Diego", "plan": "Free"}}

@tool
def get_user(user_id: str) -> str:
    """Look up a user's account details by their user ID."""
    user = USERS.get(user_id)
    return f"{user['name']}{user['plan']} plan" if user else f"No user found with ID {user_id}."

agent = create_agent(model=init_chat_model("openai:gpt-4o-mini"), tools=[get_user])
result = agent.invoke({"messages": [{"role": "user", "content": "What plan is user u_1 on?"}]})
print(result["messages"][-1].content)

What’s new here: genuinely nothing mechanically — but it’s worth naming the pattern explicitly, since it’s one of the most common real agent shapes in production: a tool wrapping a real, internal database or API, exactly like Module 12’s check_order_status example, now inside a full agent rather than tested in isolation.

Agent 6: a simple knowledge-base search tool — a preview of RAG-as-a-tool

from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain.agents import create_agent

KNOWLEDGE_BASE = [
    "Our return policy allows returns within 30 days of purchase.",
    "Shipping typically takes 3-5 business days within the country.",
    "Gift cards do not expire and cannot be redeemed for cash.",
]

@tool
def search_knowledge_base(query: str) -> str:
    """Search the company knowledge base for relevant policy information."""
    matches = [doc for doc in KNOWLEDGE_BASE if any(word in doc.lower() for word in query.lower().split())]
    return "\n".join(matches) if matches else "No relevant information found."

agent = create_agent(model=init_chat_model("openai:gpt-4o-mini"), tools=[search_knowledge_base])
result = agent.invoke({"messages": [{"role": "user", "content": "What's your return policy?"}]})
print(result["messages"][-1].content)

What’s new here — and worth being honest about: search_knowledge_base uses a genuinely crude matching technique, just checking whether any word overlaps. This is deliberately simplified. The real, production version of this pattern — using embeddings and a real vector store to find semantically relevant documents, not just overlapping words — is exactly what the upcoming Retrieval phase of this course covers in full depth. What matters right now is the shape: a tool whose entire job is searching a knowledge source and returning relevant text for the model to use. Once you’ve learned real retrieval, you’ll simply swap this crude search function for a genuine retriever, and the rest of this agent pattern stays identical.

Agent 7: a business tool suite, working together

from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain.agents import create_agent

CUSTOMERS = {"c_1": {"name": "Priya"}}
ORDERS = {"o_1": {"customer_id": "c_1", "item": "Headphones", "days_since_purchase": 12}}

@tool
def get_customer(customer_id: str) -> str:
    """Look up a customer's name by their customer ID."""
    c = CUSTOMERS.get(customer_id)
    return c["name"] if c else "Customer not found."

@tool
def get_order(order_id: str) -> str:
    """Look up order details, including customer ID and days since purchase."""
    o = ORDERS.get(order_id)
    return str(o) if o else "Order not found."

@tool
def check_refund_policy(days_since_purchase: int) -> str:
    """Check whether an order is still eligible for a refund (must be within 30 days)."""
    return "Eligible for refund." if days_since_purchase <= 30 else "Not eligible — past the 30-day window."

@tool
def create_refund_request(order_id: str) -> str:
    """File a refund request for the given order ID."""
    return f"Refund request filed for order {order_id}."

tools = [get_customer, get_order, check_refund_policy, create_refund_request]
agent = create_agent(model=init_chat_model("openai:gpt-4o-mini"), tools=tools)

result = agent.invoke({"messages": [{"role": "user", "content": "Can order o_1 be refunded? If so, please file the refund."}]})
print(result["messages"][-1].content)

What’s new here: four tools that genuinely depend on each other in sequence — look up the order, check eligibility using information from that lookup, then act on the result. This is a real, multi-step business workflow, and it’s the agent’s own loop, from Module 14, that makes chaining these four separate tool calls together possible without you writing any of that sequencing logic by hand.

Agent 8: a research assistant

from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain.agents import create_agent

@tool
def web_search(query: str) -> str:
    """Search the web for information on a topic."""
    return f"Search result for '{query}': Retrieval-augmented generation reduces hallucination by grounding answers in real documents."

agent = create_agent(
    model=init_chat_model("openai:gpt-4o-mini"),
    tools=[web_search],
    system_prompt="You are a research assistant. Search for information, then summarize it clearly in two sentences.",
)

result = agent.invoke({"messages": [{"role": "user", "content": "What is RAG and why does it help?"}]})
print(result["messages"][-1].content)

What’s new here: system_prompt, from Module 15, doing real, deliberate work — explicitly instructing the agent to search and then summarize, shaping not just tone but the actual multi-step behavior expected of it.

Agent 9: a full support agent, tying several ideas together

from langchain.chat_models import init_chat_model
from langchain.agents import create_agent

agent = create_agent(
    model=init_chat_model("openai:gpt-4o-mini"),
    tools=[get_customer, get_order, check_refund_policy, create_refund_request],
    system_prompt=(
        "You are a warm, professional customer support agent. "
        "Always check policy eligibility before filing any refund request."
    ),
)

result = agent.invoke({"messages": [{"role": "user", "content": "Hi, can you check if I can get a refund on order o_1?"}]})
print(result["messages"][-1].content)

What’s new here: this is genuinely just Agent 7, reused, with a system_prompt shaping tone and enforcing a specific business rule (“always check eligibility before filing”). This is worth noticing explicitly — a lot of “different” real agents are really the same tool suite, wearing a different system_prompt.

Agent 10: a code-assistance agent

from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain.agents import create_agent

@tool
def count_lines(code: str) -> str:
    """Count the number of non-empty lines in a given block of code."""
    lines = [line for line in code.splitlines() if line.strip()]
    return f"{len(lines)} non-empty lines."

@tool
def check_syntax(code: str) -> str:
    """Check whether a block of Python code is syntactically valid."""
    try:
        compile(code, "<string>", "exec")
        return "Syntax is valid."
    except SyntaxError as e:
        return f"Syntax error: {e}"

agent = create_agent(model=init_chat_model("openai:gpt-4o-mini"), tools=[count_lines, check_syntax])
result = agent.invoke({"messages": [{"role": "user", "content": "Is this valid Python? def add(a, b):\\n    return a + b"}]})
print(result["messages"][-1].content)

What’s new here: compile(code, "<string>", "exec") is a genuine, safe way to check Python syntax without actually running the code — a real, important distinction from Agent 1’s eval, which does execute. Checking syntax and executing code are different levels of risk, and a well-designed tool should only take on the level of risk it actually needs for its job.

Common mistakes worth avoiding

Using eval for anything beyond simple, trusted arithmetic. Recall Agent 1 — even with {"__builtins__": {}} stripped away, eval genuinely executes code, which is a real, meaningfully different risk than Agent 10’s compile-only syntax check. Reach for eval only when the input is genuinely constrained to simple expressions, never for anything resembling free-form code.

Giving an agent tools with overlapping responsibilities. Recall Module 12’s warning about confusingly similar tool descriptions — Agent 3’s calculate and get_weather work well together specifically because they’re obviously, unambiguously different. Two tools that could both plausibly handle the same request make the model’s job — and yours, when debugging — genuinely harder.

Treating Agent 6’s crude search as good enough for a real deployment. It’s a genuinely useful placeholder for learning the pattern, but word-overlap matching will miss real, semantically relevant content the moment a user phrases a question differently than the source document does. Module 25’s real retriever is the version worth actually shipping.

What you should take away from this module

  • The same handful of real shapes — single tool, multiple unrelated tools, a chain of dependent tools, a knowledge-search tool — cover the overwhelming majority of real agents you’ll ever build.
  • A tool wrapping search, a database, or a knowledge base all share the identical underlying pattern, even though they look different on the surface.
  • system_prompt genuinely shapes multi-step behavior, not just tone — Agent 8 and Agent 9 both proved this directly.
  • Many “different” agents in a real codebase are often the same tool suite, reused with a different system_prompt for a different context.
  • Tools that evaluate or check code should be deliberate about how much risk they actually take on — eval genuinely executes; compile only checks.

Where this goes next

The next module opens up create_agent’s internals properly — the actual loop, its termination conditions, and what happens when a tool fails partway through, going one level deeper than Module 14’s simplified version to show you exactly how the production version handles the messy edge cases real applications run into.

Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed