#009: This Simple Python Script Will Help You Understand AI Agents Faster Than Anything
A lot of the content deals with high level concepts that are too abstract to understand easily. As developers we want code. We want SIMPLE code.
This python script will help you understand AI agents better than most other content sources out there.
A lot of the content deals with high level concepts that are too abstract to understand easily. As developers we want code. We want SIMPLE code.
And most importantly you will be able to build your own agent too!
AI agents are changing everything - and today, I'm going to show you just *how simple it is to create one yourself!
What Makes AI Agents So Powerful?
AI agents combine the reasoning capabilities of large language models with the ability to interact with their environment through tools. Tools can be anything you want. It can be a shell command, it can be a python function, it can be an API call to another software.
This creates a system that can:
Understand natural language instructions
Suggest the next action to execute.
Execute complex tasks step by step, iteratively.
Adapt to feedback and unexpected situations (like errors from tools)
And it's mindblowingly simply too!
Building Our Translation Agent Step by Step
We'll create an agent that:
Instructs the model using a "HumanMessage" to read content of
input.txt
fileTranslate it into Swedish
Save it into
output.txt
file.
We are NOT going to be manually writing the response of the model to the file. This is the game changer with LLMs today! We will simply provide the model with two tools to accomplish the task of writing arbitrary data to the file that model decides it wants to write.
Step 1: Setting Up Our Environment
First you will need to have your API key from Anthropic. Get it and export it into your environment.
export ANTHROPIC_API_KEY=...
Next we install the necessary python packages
pip3 install langchain langchain-anthropic langchain-community
First, we need to import the necessary libraries:
#!/usr/bin/env python3
import os
from pathlib import Path
from typing import Type
# Import LangChain components for working with Anthropic's Claude
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
from langchain_core.tools import BaseTool
# Pydantic for input validation
from pydantic import BaseModel, Field
Nothing too complex here - just standard Python imports. We're using LangChain to simplify working with Claude 3.7 Sonnet, and Pydantic for input validation.
Step 2: Creating Tools for Our Agent
Now comes the exciting part! We're about to give our AI agent the ability to interact with the file system. This is where the magic happens:
# Define the input schema for file tools
class FileToolInput(BaseModel):
"""Schema for file tool inputs."""
file_path: str = Field(..., description="The absolute path to the file to read.")
# Tool for reading files
class FileReadTool(BaseTool):
"""Tool that allows Claude to read files from the filesystem."""
name: str = "file_read"
description: str = """
Reads a file from the local filesystem. The file_path parameter must be an
absolute path, not a relative path.
"""
args_schema: Type[BaseModel] = FileToolInput
def _run(self, file_path: str) -> str:
with open(file_path) as f:
return f.read()
async def _arun(self, file_path: str) -> str:
"""Async implementation of the tool."""
return self._run(file_path)
# Tool for writing files
class FileWriteTool(BaseTool):
"""Tool that allows Claude to write files to the filesystem."""
name: str = "file_write"
description: str = """
Writes content to a file on the local filesystem. The file_path parameter must be an
absolute path, not a relative path.
"""
args_schema: Type[BaseModel] = FileToolInput
def _run(self, file_path: str, content: str) -> str:
with open(file_path, "w") as f:
f.write(content)
return f"Successfully wrote content to {file_path}"
async def _arun(self, file_path: str, content: str) -> str:
"""Async implementation of the tool."""
return self._run(file_path, content)
Look at how simple it is to give an AI new capabilities! With just these few lines of code, we've created two powerful tools:
A tool to read files from the filesystem
A tool to write content to files
These tools follow a simple pattern: they have a name, description, input schema, and implementation. The LLM will use the name and description to understand when and how to use each tool.
Step 3: Setting Up the Conversation
Now we'll configure our translation task and set up the initial conversation with Claude:
# Configuration for the translation task
file_path = "input.txt" # Source file to translate
translated_file_path = "output.txt" # Destination file for translated content
language = "Swedish" # Target language for translation
# Set up the conversation with Claude
messages = [
# System message defines Claude's role
SystemMessage(
content=[
{
"type": "text",
"text": "You are skilled translator.",
"cache_control": {"type": "ephemeral"},
}
]
),
# Inform Claude about the current working directory
HumanMessage(
content=f"Current working directory: {Path.cwd()}",
),
# Give Claude the translation task
HumanMessage(
content=f"Take the content of {file_path} and translate it to {language}. Write content to {translated_file_path}.",
),
]
Just a few lines to set up a powerful tool using agent!
We're:
Defining the files and language for our translation task
Creating a system message that tells Claude to act as a translator
Providing context about the working directory so that Claude can use it when calling our file writer tool!
Giving Claude clear instructions on what to do
Step 4: Initializing the Model and Tools
Next, we initialize the Claude model and connect our tools:
# Initialize the Claude model
llm = ChatAnthropic(
model="claude-3-7-sonnet-latest",
api_key=os.getenv("ANTHROPIC_API_KEY"),
temperature=0.3, # Lower temperature for more deterministic outputs
max_tokens=10000, # Allow for longer responses
)
# Create the tools Claude can use
tools = [
FileWriteTool(),
FileReadTool(),
]
# Create a lookup dictionary for tools by name
tools_by_name = {tool.name: tool for tool in tools}
# Bind the tools to the Claude model
llm = llm.bind_tools(tools)
With these few lines, we've:
Set up the Claude 3.7 Sonnet model with appropriate parameters
Created instances of our file tools
Made a lookup dictionary for easy tool access so we can retrieve them by name when model asks us to do it.
Bound the tools to the model so Claude can use them
Step 5: The Main Agent Loop
Now for the heart of our agent - the conversation loop that powers everything:
# Main conversation loop
while True:
# Get Claude's response
response = llm.invoke(messages)
# Add Claude's response to the conversation history
# We must do this first because we will call tools which will add additional
# tool response messages and we must make sure that AI message comes before
# the tool responses for next iteration.
messages.append(response)
# Check if the conversation should end
if response.response_metadata.get("stop_reason") == "end_turn":
break
# Handle any tool calls Claude makes
if response.tool_calls:
for tool_call in response.tool_calls:
# Get the tool by name
tool = tools_by_name[tool_call["name"]]
# Execute the tool with the provided arguments
result = tool._run(**tool_call["args"])
# Print information about the tool call
print(f"TOOL CALL: {tool_call['name']}", tool_call["args"])
# Add the tool result to the conversation
messages.append(
ToolMessage(
content=result,
name=tool_call["name"],
tool_call_id=tool_call["id"],
)
)
# Print Claude's response
print(response)
This simple loop is all it takes to create an agent that can reason and act! Here's what happens:
We send the current conversation to Claude
Claude responds, potentially calling tools
If Claude calls tools, we execute them and add the results to the conversation
We continue this loop until Claude indicates it's done
That's it! With this loop, Claude will:
Read the input file using the FileReadTool
Translate the content
Write the translated content using the FileWriteTool
Let us know it's finished
There is no interaction necessary with the user!
The Complete Code
Here's the complete code for our translation agent:
#!/usr/bin/env python3
"""
Translation Tool Demo
This script demonstrates how to use Claude 3.7 Sonnet with LangChain to:
1. Read a text file
2. Translate its contents to another language
3. Write the translated content to a new file
The script uses custom file reading and writing tools that Claude can invoke.
"""
import os
from pathlib import Path
from typing import Type
# Import LangChain components for working with Anthropic's Claude
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
from langchain_core.tools import BaseTool
# Pydantic for input validation
from pydantic import BaseModel, Field
# Define the input schema for file tools
class FileToolInput(BaseModel):
"""Schema for file tool inputs."""
file_path: str = Field(..., description="The absolute path to the file to read.")
# Tool for reading files
class FileReadTool(BaseTool):
"""Tool that allows Claude to read files from the filesystem."""
name: str = "file_read"
description: str = """
Reads a file from the local filesystem. The file_path parameter must be an
absolute path, not a relative path.
"""
args_schema: Type[BaseModel] = FileToolInput
def _run(self, file_path: str) -> str:
with open(file_path) as f:
return f.read()
async def _arun(self, file_path: str) -> str:
"""Async implementation of the tool."""
return self._run(file_path)
# Tool for writing files
class FileWriteTool(BaseTool):
"""Tool that allows Claude to write files to the filesystem."""
name: str = "file_write"
description: str = """
Writes content to a file on the local filesystem. The file_path parameter must be an
absolute path, not a relative path.
"""
args_schema: Type[BaseModel] = FileToolInput
def _run(self, file_path: str, content: str) -> str:
with open(file_path, "w") as f:
f.write(content)
return f"Successfully wrote content to {file_path}"
async def _arun(self, file_path: str, content: str) -> str:
"""Async implementation of the tool."""
return self._run(file_path, content)
# Configuration for the translation task
file_path = "input.txt" # Source file to translate
translated_file_path = "output.txt" # Destination file for translated content
language = "Swedish" # Target language for translation
# Set up the conversation with Claude
messages = [
# System message defines Claude's role
SystemMessage(
content=[
{
"type": "text",
"text": "You are skilled translator.",
"cache_control": {"type": "ephemeral"},
}
]
),
# Inform Claude about the current working directory
HumanMessage(
content=f"Current working directory: {Path.cwd()}",
),
# Give Claude the translation task
HumanMessage(
content=f"Take the content of {file_path} and translate it to {language}. Write content to {translated_file_path}.",
),
]
# Initialize the Claude model
llm = ChatAnthropic(
model="claude-3-7-sonnet-latest",
api_key=os.getenv("ANTHROPIC_API_KEY"),
temperature=0.3, # Lower temperature for more deterministic outputs
max_tokens=10000, # Allow for longer responses
)
# Create the tools Claude can use
tools = [
FileWriteTool(),
FileReadTool(),
]
# Create a lookup dictionary for tools by name
tools_by_name = {tool.name: tool for tool in tools}
# Bind the tools to the Claude model
llm = llm.bind_tools(tools)
# Main conversation loop
while True:
# Get Claude's response
response = llm.invoke(messages)
# Add Claude's response to the conversation history
messages.append(response)
# Check if the conversation should end
if response.response_metadata.get("stop_reason") == "end_turn":
break
# Handle any tool calls Claude makes
if response.tool_calls:
for tool_call in response.tool_calls:
# Get the tool by name
tool = tools_by_name[tool_call["name"]]
# Execute the tool with the provided arguments
result = tool._run(**tool_call["args"])
# Print information about the tool call
print(f"TOOL CALL: {tool_call['name']}", tool_call["args"])
# Add the tool result to the conversation
messages.append(
ToolMessage(
content=result,
name=tool_call["name"],
tool_call_id=tool_call["id"],
)
)
# Print Claude's response
print(response)
# The script ends when Claude indicates it's done with "end_turn"
Beyond Translation: What Will You Build?
The pattern we've just explored is incredibly versatile. With minor modifications, you could build:
Data analysis agents that process CSV files and generate reports
Customer service bots that access knowledge bases to answer questions
Research assistants that search the web and summarize findings
Content creation tools that generate blog posts, social media content, or marketing copy
Personal productivity assistants that manage your calendar, emails, and tasks
Code generation tools that write, explain, or refactor code
The possibilities are endless! By swapping out the tools and changing the system message, you can create agents for virtually any task that can be broken down into discrete steps.
Your Turn: Build an Agent in the Next Hour!
Now that you've seen how simple it is to build an AI agent, why not try it yourself? Here's a challenge: Build your own agent in the next hour!
Copy this code
Change the tools or add new ones
Modify the system message to create a different type of agent
Consider editing the chat history in each iteration to direct the action
Share what you build!
What will you create? A summarization agent? A data analysis tool? A personal assistant? The only limit is your imagination!
Remember: you don't need to be an AI expert to build powerful AI tools. With modern LLMs and the pattern shown here, anyone with basic Python skills can create agents that were impossible just a few years ago.