Time: 30 minutes | Difficulty: Beginner
Now that you understand the concepts, let's build your first working AI agent! In this tutorial, we'll create a simple agent with a single tool and walk through every step of the process.
By the end of this tutorial, you'll be able to:
- Define a tool with proper input schemas
- Send requests with tools to Claude
- Handle tool use requests from Claude
- Execute tools and return results
- Complete the full agent interaction cycle
- Debug agent behavior
We'll create a Calculator Agent that can:
- Receive math questions from users
- Recognize when calculation is needed
- Use a calculator tool to compute exact answers
- Respond with the results
This simple agent demonstrates the complete Request → Tool Call → Execute → Response flow.
Make sure you have:
- Completed Tutorial 0: Introduction to Agentic AI
- PHP 8.1+ installed
- Anthropic API key configured in
.env - Claude PHP SDK installed
Before we code, let's visualize what happens:
┌─────────────────────────────────────────────────────────────┐
│ 1. User Request │
│ "What is 157 × 89?" │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. Your Code: Send to Claude with Tools │
│ POST /v1/messages │
│ { │
│ "messages": [...], │
│ "tools": [calculator_tool] │
│ } │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. Claude: Analyzes & Decides │
│ "I need to calculate 157 × 89" │
│ Returns: stop_reason='tool_use' │
│ Tool request: calculate("157 * 89") │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. Your Code: Execute Tool │
│ result = 157 * 89 = 13,973 │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. Your Code: Return Result to Claude │
│ POST /v1/messages │
│ { │
│ "messages": [ │
│ ...previous messages..., │
│ { "role": "user", │
│ "content": [{"type": "tool_result", ...}] } │
│ ] │
│ } │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 6. Claude: Formulates Final Response │
│ "157 × 89 equals 13,973" │
│ Returns: stop_reason='end_turn' │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 7. Your Code: Display to User │
│ "157 × 89 equals 13,973" │
└─────────────────────────────────────────────────────────────┘
A tool definition tells Claude three things:
- Name: What to call it
- Description: What it does (helps Claude decide when to use it)
- Input Schema: What parameters it needs
$calculatorTool = [
'name' => 'calculate',
'description' => 'Perform precise mathematical calculations. ' .
'Supports basic arithmetic operations: ' .
'addition (+), subtraction (-), ' .
'multiplication (*), division (/), ' .
'and parentheses for order of operations.',
'input_schema' => [
'type' => 'object',
'properties' => [
'expression' => [
'type' => 'string',
'description' => 'The mathematical expression to evaluate. ' .
'Examples: "2 + 2", "15 * 8", "(100 - 25) / 5"'
]
],
'required' => ['expression']
]
];- Good descriptions help Claude choose the right tool at the right time
- Input schema follows JSON Schema format
- Required fields ensure Claude provides all necessary parameters
- Parameter descriptions guide Claude on formatting
Send the user's question along with available tools:
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-5',
'max_tokens' => 1024,
'messages' => [
['role' => 'user', 'content' => 'What is 157 × 89?']
],
'tools' => [$calculatorTool] // Provide the tool
]);Claude's response will include:
// Check what Claude wants to do
$stopReason = $response->stop_reason;
if ($stopReason === 'tool_use') {
// Claude wants to use a tool!
} elseif ($stopReason === 'end_turn') {
// Claude has a final answer (no tool needed)
} elseif ($stopReason === 'max_tokens') {
// Response was truncated (increase max_tokens)
}| Value | Meaning | Action |
|---|---|---|
tool_use |
Claude wants to execute a tool | Extract tool use block and execute |
end_turn |
Claude finished its response | Display response to user |
max_tokens |
Hit token limit | Increase max_tokens or handle continuation |
When stop_reason === 'tool_use', extract the tool request:
$toolUse = null;
foreach ($response->content as $block) {
if ($block['type'] === 'tool_use') {
$toolUse = $block;
// $toolUse contains:
// - 'id': Unique identifier for this tool call
// - 'name': The tool name ('calculate')
// - 'input': Parameters (e.g., ['expression' => '157 * 89'])
break;
}
}Now run the actual tool function:
if ($toolUse) {
$toolName = $toolUse['name'];
$expression = $toolUse['input']['expression'];
// Execute the calculator
if ($toolName === 'calculate') {
try {
// In production, use a safe math parser library
// eval() is used here for demonstration only!
$result = eval("return {$expression};");
} catch (Exception $e) {
$result = "Error: " . $e->getMessage();
}
}
}In production code, never use eval() with user input! Use a proper math expression parser like:
mossadal/math-parsernxp/math-executor- Or implement safe parsing
Create a tool_result and send it back:
$messages = [
// Original user message
['role' => 'user', 'content' => 'What is 157 × 89?'],
// Claude's response (with tool_use)
['role' => 'assistant', 'content' => $response->content],
// Tool result
[
'role' => 'user',
'content' => [
[
'type' => 'tool_result',
'tool_use_id' => $toolUse['id'], // Must match!
'content' => (string)$result // Result as string
]
]
]
];
// Send back to Claude
$finalResponse = $client->messages()->create([
'model' => 'claude-sonnet-4-5',
'max_tokens' => 1024,
'messages' => $messages,
'tools' => [$calculatorTool] // Tools still available
]);- tool_use_id must match - This is how Claude knows which tool call you're responding to
- Content must be a string - Convert numbers/objects to strings
- Preserve conversation history - Include all previous messages
- Keep tools available - Claude might want to use them again
Now Claude has the tool result and can formulate a final answer:
foreach ($finalResponse->content as $block) {
if ($block['type'] === 'text') {
echo $block['text'] . "\n";
// Output: "157 × 89 equals 13,973"
}
}Possible causes:
- Tool description doesn't match the task
- Claude can answer without the tool
- Tool name/description too vague
Fix: Make the description more specific about when to use it.
Cause: The tool_use_id in your tool_result doesn't match the one from Claude.
Fix: Save the ID from Claude's response and use it exactly:
$toolUseId = $toolUse['id']; // Save this
// Later...
'tool_use_id' => $toolUseId // Use exact same valueCheck:
- Does the tool name match exactly?
- Is the input schema valid?
- Are required parameters provided?
- Check
$response->stop_reason
Always check token usage:
echo "Tokens used:\n";
echo " Input: {$response->usage->input_tokens}\n";
echo " Output: {$response->usage->output_tokens}\n";
echo " Total: " . ($response->usage->input_tokens + $response->usage->output_tokens) . "\n";For each tool use request:
- Tool definitions (~50-200 tokens depending on complexity)
- System prompt for tool use (~350 tokens)
- Your messages
- Claude's response
Here's the full flow in one place:
// 1. Define tool
$tool = [...];
// 2. First request
$response = $client->messages()->create([
'messages' => [['role' => 'user', 'content' => 'Calculate 157 × 89']],
'tools' => [$tool]
]);
// 3. Extract tool use
$toolUse = /* extract from $response->content */;
// 4. Execute tool
$result = /* execute based on $toolUse */;
// 5. Return result
$finalResponse = $client->messages()->create([
'messages' => [
['role' => 'user', 'content' => 'Calculate 157 × 89'],
['role' => 'assistant', 'content' => $response->content],
['role' => 'user', 'content' => [
['type' => 'tool_result', 'tool_use_id' => $toolUse['id'], 'content' => $result]
]]
],
'tools' => [$tool]
]);
// 6. Display
echo extractTextContent($finalResponse);Run the complete working example:
php tutorials/01-first-agent/simple_agent.phpThe script demonstrates:
- ✅ Basic calculator agent
- ✅ Multiple calculations in sequence
- ✅ Error handling
- ✅ Debug output
- ✅ Token usage tracking
Before moving on, make sure you understand:
- How to define a tool with input_schema
- How to send tools with your request
- What
stop_reasonmeans - How to extract tool_use blocks
- How to format tool_result
- Why tool_use_id must match
Congratulations! You've built your first agent. But it only handles one tool call per task. What if the task requires multiple steps?
Learn how to implement the ReAct loop for multi-step reasoning!
function executeTool($toolUse) {
$name = $toolUse['name'];
$input = $toolUse['input'];
return match($name) {
'calculate' => calculate($input['expression']),
'get_weather' => getWeather($input['location']),
default => "Unknown tool: {$name}"
};
}function executeToolSafely($toolUse) {
try {
$result = executeTool($toolUse);
return [
'type' => 'tool_result',
'tool_use_id' => $toolUse['id'],
'content' => $result
];
} catch (Exception $e) {
return [
'type' => 'tool_result',
'tool_use_id' => $toolUse['id'],
'content' => "Error: " . $e->getMessage(),
'is_error' => true // Signal this is an error
];
}
}