Tools and the agent loop

This is the biggest conceptual jump in the course. Up to now the assistant only talks. An agent can act. It decides to run a command, read a file, or search the web, and then it reads the result and decides what to do next. That decision loop is what turns a chatbot into an agent.

The agent loop

The model keeps calling tools until it decides it has enough information to reply.

04-tools-agent-loop/bot.py
python
TOOLS = [
    {
        'name': 'run_command',
        'description': 'Run a shell command and return stdout plus stderr.',
        'input_schema': {
            'type': 'object',
            'properties': {
                'command': {'type': 'string'},
            },
            'required': ['command'],
        },
    },
    {
        'name': 'read_file',
        'description': 'Read the contents of a file.',
        'input_schema': {
            'type': 'object',
            'properties': {'path': {'type': 'string'}},
            'required': ['path'],
        },
    },
    {
        'name': 'write_file',
        'description': 'Write content to a file, creating directories as needed.',
        'input_schema': {
            'type': 'object',
            'properties': {
                'path': {'type': 'string'},
                'content': {'type': 'string'},
            },
            'required': ['path', 'content'],
        },
    },
]

Each tool has a name, a plain language description, and an input schema. The model reads these to decide which one to call and with what arguments.

04-tools-agent-loop/bot.py
python
def run_agent_turn(user_id, user_text):
    messages = load_session(user_id)
    messages.append({'role': 'user', 'content': user_text})

    while True:
        response = client.messages.create(
            model=MODEL,
            max_tokens=4096,
            system=SOUL,
            tools=TOOLS,
            messages=messages,
        )

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

        if response.stop_reason != 'tool_use':
            save_session(user_id, messages)
            return extract_text(response)

        tool_results = []
        for block in response.content:
            if block.type == 'tool_use':
                result = execute_tool(block.name, block.input)
                tool_results.append({
                    'type': 'tool_result',
                    'tool_use_id': block.id,
                    'content': result,
                })

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

The loop is short on purpose. If stop_reason is tool_use, run the tools, append the results as a user turn, and call the API again. Otherwise return the text.

The model itself stops when it decides it has answered. In practice this happens within a handful of iterations. For safety you can add a max iteration cap, a timeout, or a guard on total tokens used. In this workshop we trust the model and keep the code short.

Ordering exercise: Order the steps of one tool-using turn

Loading practice…

Quiz: Quiz

Loading practice…

AI prompt: Try it: ask your agent to work

Loading practice…