Saturday, April 25, 2026

Building Your First AI Agent from Scratch

 Building Your First AI Agent from Scratch 

                                                                                              AI agents are one of the most exciting developments in software right now. But if you cut t  through the hype, the core idea is surprisingly simple. In this post, I'll explain what agents actually are, how they work, and walk you through building one from scratch in Python.                                                                                                  

What Is an AI Agent?             

An agent is an AI system that can autonomously plan, reason, and take actions to accomplish a goal — going beyond simple Q&A to actually do things on your behalf.


The key difference from a chatbot: agents take actions and iterate. A chatbot answers your question once and stops. An agent keeps working — using the result of one action to decide what to do   next — until the task is complete.                                                                  

Every agent, no matter how complex, is built on four core concepts:


  - Perceive — take in inputs: user messages, tool results, environment state                 - Reason — decide what to do next

  - Act — call a tool, run code, hit an API                                                   - Loop — repeat until the task is done



The Agent Loop


  User Input

     

  ┌─────────────────────────────────┐

    LLM thinks: "What should I do?" │

                                 

    Call a tool? → Get result      

                                 

    Call another tool? → Get result │

                                 

    Done? → Return final answer    

  └─────────────────────────────────┘

     

  Final Response


  This loop is the heartbeat of every agent. Let's build one.                                                                                                                                           

  Building the Agent


  Prerequisites


  You'll need Python installed and an Anthropic API key from https://console.anthropic.com.


  mkdir my-agent && cd my-agent

  python3 -m venv venv

  source venv/bin/activate

  pip install anthropic

  export ANTHROPIC_API_KEY="sk-ant-your-key-here"


 Step 1 Define the Tools


 Tools are regular Python functions — the agent can call them to take actions. Here we'll give our agent two abilities: a calculator and a weather lookup.                                              

   

  def calculator(operation: str, a: float, b: float) -> float:                                                                                                                                           

      ops = {                                                                                                                                                                                            

          "add": a + b,                                                                                                                                                                                  

          "subtract": a - b,                                                                                                                                                                             

          "multiply": a * b,                                                                                                                                                                             

          "divide": a / b if b != 0 else "Error: division by zero",                                                                                                                                      

      }                                                                                                                                                                                                  

      return ops.get(operation, "Unknown operation")                                                                                                                                                     

                                                                                                                                                                                                         

  def get_weather(city: str) -> str:                                                                                                                                                                     

      # In production, call a real weather API here                                                                                                                                                      

      data = {                                                                                                                                                                                           

          "london": "15°C, cloudy",                                                                                                                                                                      

          "new york": "22°C, sunny",                                                                                                                                                                     

          "tokyo": "28°C, humid",                                                                                                                                                                        

      }                                                                                                                                                                                                  

      return data.get(city.lower(), f"No weather data for {city}")                                                                                                                                       

                                                                                                                                                                                                         

Step 2 — Describe the Tools to the LLM                                                                                                                                                                 

The LLM never sees your Python functions directly. You give it a description — a menu of what's available, what each tool does, and what inputs it expects. The LLM reads this to decide when and how  

  to use each tool.

                                                                                                                                                                                                         

  TOOLS = [                                                                                                                                                                                              

      {

          "name": "calculator",                                                                                                                                                                          

          "description": "Perform basic arithmetic. Use this for any math.",                                                                                                                             

          "input_schema": {                                                                                                                                                                              

              "type": "object",                                                                                                                                                                          

              "properties": {                                                                                                                                                                            

                  "operation": {                                                                                                                                                                         

                      "type": "string",                                                                                                                                                                  

                      "enum": ["add", "subtract", "multiply", "divide"],                                                                                                                                 

                  },                                                                                                                                                                                     

                  "a": {"type": "number"},                                                                                                                                                               

                  "b": {"type": "number"},                                                                                                                                                               

              },                                                                                                                                                                                         

              "required": ["operation", "a", "b"],                                                                                                                                                       

          },                                                                                                                                                                                             

      },                                                                                                                                                                                                 

      {                                                                                                                                                                                                  

          "name": "get_weather",                                                                                                                                                                         

          "description": "Get the current weather for a city.",                                                                                                                                          

          "input_schema": {                                                                                                                                                                              

              "type": "object",                                                                                                                                                                          

              "properties": {                                                                                                                                                                            

                  "city": {"type": "string"},                                                                                                                                                            

              },                                                                                                                                                                                         

              "required": ["city"],                                                                                                                                                                      

          },                                                                                                                                                                                             

      },                                                                                                                                                                                                 

  ]                                                                                                                                                                                                      

                                                                                                                                                                                                         

Step 3 — The Tool Dispatcher                                                                               

When the LLM says "call calculator with these inputs", we need something to route that request to the right Python function. That's the dispatcher:                                                    

                  

  def run_tool(name: str, inputs: dict):                                                                                                                                                                 

      if name == "calculator":                                                                                                                                                                           

          return calculator(**inputs)                                                                                                                                                                    

      elif name == "get_weather":                                                                                                                                                                        

          return get_weather(**inputs)                                                                                                                                                                   

      else:                                                                                                                                                                                              

          return f"Unknown tool: {name}"                                                                                                                                                                 

                                                                                                                                                                                                         

Think of it as a switchboard — it takes the LLM's instruction and hands it off to the right function.                                                                                                                                                                                                

  Step 4 — The Agent Loop                                                                                  

                  

  This is where everything comes together. The loop is the agent.                                          

                  

  import anthropic                                                                                         

  client = anthropic.Anthropic()                                                                           

  def run_agent(user_message: str):                                                                                                                                                                      

  print(f"\nUser: {user_message}")

  messages = [{"role": "user", "content": user_message}]                                               

      while True:                                                                                          

          response = client.messages.create(                                                               

              model="claude-sonnet-4-6",                                                                    max_tokens=1024,

              tools=TOOLS,                                                                                  messages=messages,

          )                                                                                                                                                                                                   

          # Agent is done — return the final answer                                                     if response.stop_reason == "end_turn":

              final = next(b.text for b in response.content if hasattr(b, "text"))                         

              print(f"\nAgent: {final}")                                                                                                                                                                  return final                                                                                 

          # Agent wants to use tools                                                                       

          if response.stop_reason == "tool_use":                                                           

              messages.append({"role": "assistant", "content": response.content})                          

              tool_results = []                                                                            

              for block in response.content:                                                               

                  if block.type == "tool_use":                                                             

                      print(f"  [tool call] {block.name}({block.input})")                                  

                      result = run_tool(block.name, block.input)                                           

                      print(f"  [tool result] {result}")                                                   

                      tool_results.append({                                                                

                          "type": "tool_result",                                                           

                          "tool_use_id": block.id,                                                         

                          "content": str(result),                                                          

                      })                                                                                                                                                                                 

              messages.append({"role": "user", "content": tool_results})                                                                                                                                  # Loop continues — agent reasons again with new information                                                                                                                                                                                                                              

  run_agent("What's the weather in London and Tokyo? Also, what's 847 times 23?")                          

  ---             

  What Happens When You Run It

                                                                                                                                                                                                         

  User: What's the weather in London and Tokyo? Also, what's 847 times 23?

                                                                                                                                                                                                         

    [tool call] get_weather({'city': 'London'})                                                                                                                                                          

    [tool result] 15°C, cloudy                                                                                                                                                                           

    [tool call] get_weather({'city': 'Tokyo'})                                                                                                                                                           

    [tool result] 28°C, humid                                                                                                                                                                            

    [tool call] calculator({'operation': 'multiply', 'a': 847, 'b': 23})                                                                                                                                 

    [tool result] 19481.0                                                                                                                                                                                

                                                                                                                                                                                                         

  Agent: Here's what I found:                                                                                                                                                                            

  - London: 15°C, cloudy                                                                                                                                                                                 

  - Tokyo: 28°C, humid                                                                                                                                                                                   

  - 847 × 23 = 19,481                                                                                      

The agent autonomously decided which tools to call, called them in the right order, and synthesized the results into a final answer.                                                                   

How the Four Agent Concepts Map to the Code

                                                                                                           

  Perceive

                                                                                                messages = [{"role": "user", "content": user_message}]

  # ...                                                                                                                                                                                                  

  messages.append({"role": "user", "content": tool_results})                                                                                                                                             

The messages list is everything the agent knows — the user's question, tool results, conversation history. It grows every iteration. The agent perceives again after every action, which is what makes it an agent rather than a chatbot.                                                                                                                                                   

  Reason                                                                                                                                                                                    response = client.messages.create(

      model="claude-sonnet-4-6",                                                                           

      tools=TOOLS,                                                                                         

      messages=messages,                                                                        )                                                                                                                                                                                                      

  Claude looks at the full history and available tools and decides: Do I have enough to answer? (end_turn) or Do I need more information? (tool_use). The TOOLS list is the agent's awareness of its own

  capabilities.                                                                                                                                                                                          

                  

  Act                                                                                                      

                  

  result = run_tool(block.name, block.input)

  

This is the agent doing something in the world. Here it's calling a calculator. In a more powerful agent it could be browsing the web, writing and running code, sending an email, or updating a database. The pattern is identical regardless.                                                   

  Loop                                                                                                                                                                                                        

  while True:

      # reason                                                                                             

      # act                                                                                                                                                                                       # perceive result                                                                                    

      # repeat                                                                                                                                                                                           

The while True is the agent. A chatbot stops after one LLM call. An agent keeps looping until it decides it's done.                                                                                    

                                                                                                           

The One-Line Summary                                                                                                                                                                      The while True loop is the agent. Everything else is just giving it eyes (perceive), a brain (reason), and hands (act).

                                                                                                  

The full code is available above. To run it, save it as agent.py, set your ANTHROPIC_API_KEY, and run python3 agent.py.

No comments:

Post a Comment