Day 54 of 80

Agents: Concept & Tool Use

Phase 6: Frameworks & Agents

What You'll Build Today

Welcome to Day 54! Today marks a massive shift in how you build with AI.

Up until now, we have been building Chains. A chain is like a train on a track. It goes from Station A to Station B to Station C. It’s reliable, but it can’t turn left if there is a sudden obstacle, and it can’t decide to skip Station B if it isn’t needed.

Today, we are building Agents. An agent is like a taxi driver. You give them a destination, and they decide the route. If there is traffic, they take a detour. If they need gas, they stop for gas. They have "agency."

We are going to build a Reasoning Math Agent.

Large Language Models (LLMs) are actually quite bad at math (they predict the next word, they don't calculate numbers). We will fix this by giving the LLM a "calculator" tool and teaching it how to use it.

Here is what you will learn:

* Tool Creation: How to write Python functions that an AI can "read" and execute.

* Tool Binding: How to connect these functions to the LLM so it knows they exist.

* The ReAct Pattern: The specific loop of Reasoning (thinking about what to do) and Acting (using a tool) that makes agents work.

* Dynamic Decision Making: Moving away from hardcoded steps to letting the AI decide the path.

Let's give your AI a brain upgrade.

The Problem

Let's look at why Chains aren't enough.

Imagine you are building a helper bot. You want it to handle general chat, but also do math, and maybe convert units (like feet to meters).

If you use a Chain, you have to hardcode the order of operations. But you don't know what the user will ask!

If the user asks "Hello, how are you?", you just want to chat.

If the user asks "What is 55 * 322?", you want to calculate.

To solve this without Agents, you end up writing "Spaghetti Code"—a mess of if statements trying to guess what the user wants.

Here is what that painful code looks like:

# The "Old Way" - Hardcoded Logic (Don't do this!)

def calculate(expression):

return eval(expression)

def unit_converter(value, from_unit, to_unit):

# Imagine complex logic here

return f"{value} {from_unit} is converted to {to_unit}"

user_input = "What is 25 times 48?"

# PAIN POINT: We have to manually guess intent using string matching

if "times" in user_input or "+" in user_input or "-" in user_input:

# We have to manually extract numbers. This is brittle! # If the user says "add five and ten", this logic breaks.

print(f"Attempting to calculate: {user_input}")

# result = calculate(...) # We'd have to write a parser here. Nightmare.

elif "convert" in user_input:

print("Attempting unit conversion...")

# result = unit_converter(...)

else:

print("Just sending to LLM for chat...")

# Result: This code is fragile. # It breaks if the user asks "Calculate the conversion of 5 feet to inches." # Because it matches both "calculate" and "convert", which 'if' statement wins?
The Frustration:
  • You are writing logic (if "times" in...) that the AI should be doing for you.
  • You can't handle complex requests like "Calculate 5 * 5 and then convert that from inches to centimeters."
  • Every time you add a new capability, you have to rewrite your main if/else loop.
  • There has to be a way to just give the functions to the LLM and say, "Here are your tools. You figure out when to use them."

    That is exactly what an Agent does.

    Let's Build It

    We are going to build an agent using LangChain. We will use the ReAct pattern (Reason + Act).

    The flow looks like this:

  • User: "What is 50 * 9?"
  • Agent (Thought): I need to calculate math. I have a 'calculator' tool. I should use it.
  • Agent (Action): Calls calculator(50, 9).
  • Tool (Observation): Returns 450.
  • Agent (Thought): I have the answer.
  • Agent (Final Answer): "The answer is 450."
  • Step 1: Setup and Imports

    We need the basic LangChain libraries. We will use OpenAI's GPT models because they are very good at following tool instructions.

    Note: Ensure you have your .env file with OPENAI_API_KEY.
    import os
    

    from dotenv import load_dotenv

    from langchain_openai import ChatOpenAI

    # Load environment variables

    load_dotenv()

    # Initialize the LLM # We use temperature=0 because when using tools (like math), # we want the model to be precise, not creative.

    llm = ChatOpenAI(model="gpt-4o", temperature=0)

    print("LLM Initialized.")

    Step 2: Define Your Tools

    This is the most critical part. We will create two Python functions.

    We use the @tool decorator. Pay close attention to the docstring (the text inside """ """). The LLM reads this text to understand what the tool does and when to use it. If your description is bad, the agent won't use the tool.

    from langchain_core.tools import tool
    
    

    @tool

    def calculator(expression: str) -> str:

    """

    Useful for performing mathematical calculations.

    Input should be a mathematical expression as a string, e.g., '2 + 2' or '5 * 10'.

    """

    try:

    # standard python eval is risky in production, but fine for this tutorial

    return str(eval(expression))

    except Exception as e:

    return f"Error calculating: {e}"

    @tool

    def unit_converter(value: float, from_unit: str, to_unit: str) -> str:

    """

    Useful for converting units.

    Supported conversions: meters to feet, feet to meters.

    """

    # Simple logic for demonstration

    if from_unit == "meters" and to_unit == "feet":

    return str(value * 3.28084)

    elif from_unit == "feet" and to_unit == "meters":

    return str(value / 3.28084)

    else:

    return "Conversion not supported yet."

    # Create a list of tools to pass to the agent

    tools = [calculator, unit_converter]

    print(f"Tools created: {[t.name for t in tools]}")

    Step 3: Bind Tools to the LLM

    Now we tell the LLM that these tools exist. This doesn't run the tools yet; it just makes the LLM aware of them.

    # Bind the tools to the LLM
    

    llm_with_tools = llm.bind_tools(tools)

    # Let's test if the LLM knows it should use a tool

    test_query = "What is 453 times 12?"

    response = llm_with_tools.invoke(test_query)

    print(f"Query: {test_query}")

    print(f"LLM Response Type: {type(response)}")

    print(f"Tool Calls: {response.tool_calls}")

    Output Explanation:

    You will notice the response.tool_calls is not empty! The LLM didn't return text like "The answer is..."; it returned a structured request to call the function calculator with specific arguments.

    Step 4: Create the Agent Loop

    Now we need a system that:

  • Takes the user input.
  • Checks if the LLM wants to use a tool.
  • If yes, runs the tool (executes the Python function).
  • Feeds the result back to the LLM.
  • Repeats until the LLM has a final answer.
  • LangChain provides a pre-built way to do this called the AgentExecutor.

    from langchain.agents import create_tool_calling_agent, AgentExecutor
    

    from langchain_core.prompts import ChatPromptTemplate

    # 1. Define the Prompt # We need a placeholder for "agent_scratchpad". # This is where the agent's history of "Thinking -> Action -> Observation" is stored.

    prompt = ChatPromptTemplate.from_messages([

    ("system", "You are a helpful mathematical assistant. Use your tools to answer questions."),

    ("human", "{input}"),

    ("placeholder", "{agent_scratchpad}"),

    ])

    # 2. Create the Agent # This combines the LLM, the tools, and the prompt into a reasoning engine

    agent = create_tool_calling_agent(llm, tools, prompt)

    # 3. Create the Executor # The executor is the runtime that actually calls the functions and loops

    agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

    print("Agent built and ready!")

    Step 5: Run the Agent

    Now, let's watch the magic happen. We enabled verbose=True so you can see the "brain" working in the console.

    print("--- TEST 1: Simple Math ---")
    

    agent_executor.invoke({"input": "What is 123 * 4?"})

    print("\n--- TEST 2: Multi-step Reasoning ---")

    # This requires the agent to calculate, remember the result, and then convert it.

    agent_executor.invoke({"input": "Calculate 50 * 3, and then tell me what that length is in feet if the result is in meters."})

    print("\n--- TEST 3: General Chat (No Tools) ---")

    agent_executor.invoke({"input": "Hi, my name is Alice."})

    What you should see in the output:

    For Test 2, you will see a chain of events:

  • Invoking: calculator with args {'expression': '50 * 3'}
  • Result: 150
  • Invoking: unit_converter with args {'value': 150, 'from_unit': 'meters', 'to_unit': 'feet'}
  • Result: 492.126
  • Final Answer: "50 * 3 is 150. Converting 150 meters to feet gives approximately 492.13 feet."
  • The agent figured out the sequence entirely on its own. We didn't write an if statement for "calculate then convert."

    Now You Try

    Experiment with your new agent to understand its capabilities and limits.

  • Add a String Tool:
  • Create a new tool called word_reverser(text: str). It should return the string backwards. Add it to the tools list, rebuild the agent, and ask: "Reverse the word 'Python' and then tell me how many letters are in the result."

  • Change the Persona:
  • Modify the system prompt in Step 4. Change "You are a helpful mathematical assistant" to "You are a grumpy math teacher who hates using calculators but does it anyway." Run the agent again. Notice how the style of the final answer changes, but the tool usage remains accurate.

  • Break the Logic:
  • Ask the agent: "What is the weather in Tokyo?"

    Since you have no weather tool, watch how it fails or politely declines. This teaches you that agents are limited by the tools you give them.

    Challenge Project: The Time Keeper

    One of the biggest limitations of LLMs is that they are frozen in time. They don't know what "today" is because their training data is from the past.

    Your challenge is to build an agent that knows the current time.

    Requirements:
  • Create a tool called get_current_time.
  • It should take no arguments (or ignore them).
  • It should return the current date and time as a string (hint: use Python's datetime library).
  • Bind this tool to a new agent.
  • Ask the agent: "What is the date today?" and "How many days until Christmas?"
  • Example Output:
    > Entering new AgentExecutor chain...
    

    Invoking: get_current_time with {}

    [Tool Output]: 2023-10-27 14:30:00

    The current date is October 27, 2023.

    Christmas is on December 25.

    Therefore, there are 59 days until Christmas.

    > Finished chain.

    Hint:

    When defining the tool, make sure to import datetime.

    import datetime
    
    

    @tool

    def get_current_time():

    """Returns the current date and time."""

    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    What You Learned

    Today you moved from static scripts to dynamic agents.

    * Tools: You learned that tools are just functions with good docstrings.

    * Binding: You learned that LLMs need to be explicitly told which tools are available.

    * Reasoning Loop: You saw the "Thought -> Action -> Observation" loop in action via the verbose=True output.

    * AgentExecutor: You used a high-level wrapper to manage the conversation history and tool execution automatically.

    Why This Matters:

    In the real world, AI applications aren't just chat bots. They are systems that do things. They search the web, query databases, send emails, and book appointments. The Agent architecture you built today is the foundation for all of those autonomous actions.

    Tomorrow:

    We are going to take the training wheels off. AgentExecutor is great for beginners, but it hides the logic. Tomorrow, we will use LangGraph to build complex, stateful agents where we control the loop explicitly. This will allow for human-in-the-loop approval and complex workflows.