Skip to content

LearnTutorials

Build your first AI agent in Python with Claude

Build a Python AI agent on the Claude API in 22 minutes, with memory across turns, a tool it calls on its own and long chats it sums up.

By Recaply EditorsAdvanced22 min5 stepsUpdated

A hand drops a small wrench into an open toolbox next to a spiral notebook

What you need

  1. Step 1: Set up the project

    5 min

    Your API key lets the code call Claude, and a .env file keeps the key out of your code.

    1. Go to platform.claude.com/settings/keys, create an API key and copy it.
    2. Run the commands below in a terminal. On Windows, activate the environment with .venv\Scripts\activate instead.
    3. In the new folder, create a file named .env with one line: ANTHROPIC_API_KEY= followed by your key.
    4. Add .env to your .gitignore file, so the key never ends up in Git.
    Run this
    mkdir my-agent && cd my-agent
    python3 -m venv .venv && source .venv/bin/activate
    pip install anthropic python-dotenv tzdata

    You should see the 3 packages installed with no errors, and a .env file in your my-agent folder.

    pip says the anthropic package needs a newer Python?

    The SDK needs Python 3.10 or later. Install a newer Python, delete the .venv folder and create it again with the new version.

    Keep the key out of chats, screenshots and shared files. If it leaks, delete it in the Console and create a new one.

  2. Step 2: Write a tool Claude can call

    4 min

    A tool is a plain Python function plus a short description. Claude reads the description and decides when to use it.

    1. Create agent.py in the project folder and paste in the code below.
    2. Read the tool's description. It tells Claude when the tool helps, so write your own just as plainly.
    3. To add a tool later, write the function, add its entry to TOOLS and add its name to FUNCTIONS.
    Your agent.py, part 1
    from datetime import datetime
    from zoneinfo import ZoneInfo
    
    import anthropic
    from dotenv import load_dotenv
    
    load_dotenv()
    
    
    def get_time(timezone):
        try:
            return datetime.now(ZoneInfo(timezone)).strftime("%A %H:%M")
        except Exception:
            return f"Unknown time zone: {timezone}. Use a name like Europe/Paris."
    
    
    TOOLS = [
        {
            "name": "get_time",
            "description": "Get the current day and time in a time zone. Use an IANA name such as Europe/Paris or America/New_York.",
            "input_schema": {
                "type": "object",
                "properties": {"timezone": {"type": "string", "description": "IANA time zone name"}},
                "required": ["timezone"],
            },
        }
    ]
    FUNCTIONS = {"get_time": get_time}
    

    You should see a saved agent.py with the get_time function and its matching entry in TOOLS.

    The tzdata package you installed gives Windows the time zone names that ZoneInfo needs.

  3. Step 3: Give the agent memory and a tool loop

    6 min

    The API doesn't remember past turns, so the agent sends the whole chat each time and loops until Claude stops calling tools.

    1. Paste the class below under the code from step 2.
    2. self.memory keeps every turn: your messages, Claude's replies and the tool results.
    3. When stop_reason is tool_use, the loop runs each tool and sends the result back with the same tool_use_id.
    Your agent.py, part 2
    class Agent:
        def __init__(self, model="claude-opus-5-5"):
            self.client = anthropic.Anthropic()
            self.model = model
            self.memory = []
    
        def chat(self, text):
            self.memory.append({"role": "user", "content": text})
            while True:
                response = self.client.messages.create(
                    model=self.model,
                    max_tokens=16000,
                    system="You are a helpful assistant. Use your tools when they help.",
                    tools=TOOLS,
                    messages=self.memory,
                )
                self.memory.append({"role": "assistant", "content": response.content})
                if response.stop_reason != "tool_use":
                    return "".join(b.text for b in response.content if b.type == "text")
                results = []
                for block in response.content:
                    if block.type == "tool_use":
                        output = FUNCTIONS[block.name](**block.input)
                        results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
                self.memory.append({"role": "user", "content": results})
    

    You should see an Agent class with a chat() method below your tool code.

    The API returns a 400 error about a tool_result block?

    Each tool_use block needs a tool_result with the same ID in the very next message. Append Claude's full response.content, then send all the results together.

    Anthropic suggests Claude Opus 5.5 as the model to start with. For cheap, simple chats, create the agent with Agent(model="claude-haiku-5-5").

  4. Step 4: Chat with your agent

    3 min

    A short loop at the end of the file turns the class into a chat you can run in the terminal.

    1. Paste the code below at the end of agent.py.
    2. Run python agent.py and ask: What time is it in Tokyo right now?
    3. Then ask a follow-up, such as "And in Paris?", to check that it remembers what you asked.
    Your agent.py, part 3
    if __name__ == "__main__":
        agent = Agent()
        print("Chat with your agent. Type quit to stop.")
        while True:
            text = input("You: ").strip()
            if text.lower() in ("quit", "exit"):
                break
            if text:
                print("Agent:", agent.chat(text))
    

    You should see the current time in Tokyo, then in Paris, without you repeating the question.

    You get an authentication error?

    Check that .env is in the folder you run the script from, with no quotes or spaces around the key. Then run the script from inside that folder.

  5. Step 5: Keep long chats inside the context window

    4 min

    Every turn resends the whole chat, so long sessions get slower and cost more unless older turns are summed up.

    1. In chat(), replace the whole self.client.messages.create(...) call with the one below.
    2. Leave the rest of the loop as it is. It already saves Claude's full reply, which is where the summary arrives.
    3. Keep the trigger at 50,000 tokens or more. That's the lowest value the API accepts.
    Replace the create call
                response = self.client.beta.messages.create(
                    betas=["compact-2026-01-12"],
                    context_management={"edits": [{
                        "type": "compact_20260112",
                        "trigger": {"type": "input_tokens", "value": 50000},
                    }]},
                    model=self.model,
                    max_tokens=16000,
                    system="You are a helpful assistant. Use your tools when they help.",
                    tools=TOOLS,
                    messages=self.memory,
                )

    You should see replies as before. Once a chat passes the trigger, one reply starts with a compaction block that holds the summary.

    You trimmed old messages yourself and now get a 400 error about a thinking block?

    On accounts created on or after August 31, 2026, Claude's thinking blocks only stay valid if earlier messages never change. Undo the trimming and let compaction shorten the chat.

    Compaction is in beta, so the call goes through client.beta.messages with the compact-2026-01-12 beta flag.

You get

A Python agent that remembers the conversation, calls your own tool and sums up long chats on its own

Did this guide work for you?

Sources