Tools (also known as function calling) empower your AI agents to interact with external systems, APIs, and data sources. This is a core capability for building agentic systems that can take actions beyond generating text.
When you provide tools to an AI agent, the model can:
- Decide when a tool is needed to answer a query
- Select the appropriate tool
- Generate the correct input parameters
- Receive the tool's output
- Incorporate the results into its response
Use CallbackToolDefinition to define tools with callback functions:
<?php
use Soukicz\Llm\Tool\CallbackToolDefinition;
$tool = new CallbackToolDefinition(
name: 'tool_name',
description: 'Clear description of what the tool does and when to use it',
inputSchema: [
'type' => 'object',
'properties' => [
'param1' => ['type' => 'string', 'description' => 'Parameter description'],
'param2' => ['type' => 'number', 'description' => 'Another parameter'],
],
'required' => ['param1'],
],
handler: function (array $input) {
// Your tool logic here
return $result;
}
);<?php
use GuzzleHttp\Client;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Response;
use Soukicz\Llm\Cache\FileCache;
use Soukicz\Llm\Client\Anthropic\AnthropicClient;
use Soukicz\Llm\Client\Anthropic\Model\AnthropicClaude45Sonnet;
use Soukicz\Llm\Client\LLMAgentClient;
use Soukicz\Llm\Message\LLMMessage;
use Soukicz\Llm\Message\LLMMessageContents;
use Soukicz\Llm\LLMConversation;
use Soukicz\Llm\LLMRequest;
use Soukicz\Llm\Tool\CallbackToolDefinition;
require_once __DIR__ . '/vendor/autoload.php';
$cache = new FileCache(sys_get_temp_dir());
$anthropic = new AnthropicClient('sk-xxxxxx', $cache);
$agentClient = new LLMAgentClient();
$currencyTool = new CallbackToolDefinition(
name: 'currency_rates',
description: 'Tool for getting current currency rates. Required input is currency code of source currency and currency code of target currency.',
inputSchema: [
'type' => 'object',
'properties' => [
'source_currency' => ['type' => 'string'],
'target_currency' => ['type' => 'string'],
],
'required' => ['source_currency', 'target_currency'],
],
handler: function (array $input): PromiseInterface {
$client = new Client();
// Tool can return either a promise or a value
return $client->getAsync('https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/' . strtolower($input['source_currency']) . '.json')
->then(function (Response $response) use ($input) {
$data = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
return LLMMessageContents::fromArrayData([
'rate' => $data[strtolower($input['source_currency'])][strtolower($input['target_currency'])],
]);
});
}
);
$response = $agentClient->run(
client: $anthropic,
request: new LLMRequest(
model: new AnthropicClaude45Sonnet(AnthropicClaude45Sonnet::VERSION_20250929),
conversation: new LLMConversation([
LLMMessage::createFromUserString('How much is 100 USD in EUR today?')
]),
tools: [$currencyTool],
)
);
echo $response->getLastText();Tool handlers must return either:
- LLMMessageContents - Structured response data (recommended)
- Promises - For async operations (recommended for API calls)
<?php
// LLMMessageContents (recommended for synchronous operations)
handler: function (array $input): LLMMessageContents {
return LLMMessageContents::fromArrayData(['key' => 'value']);
}
// Promise (recommended for async operations like API calls)
handler: function (array $input): PromiseInterface {
return $httpClient->getAsync($url)
->then(function ($response) {
$data = json_decode($response->getBody(), true);
return LLMMessageContents::fromArrayData($data);
});
}Note: Tool handlers cannot return plain arrays or scalar values. Always wrap your results in LLMMessageContents::fromArrayData().
Tip: If you need to convert LLMMessageContents back to a plain array (e.g., for testing), use the toArray() method:
<?php
$result = $tool->handle(['input' => 'value']);
$array = $result->toArray(); // Converts to plain arrayFor building file-manipulation agents with Anthropic models, you can use the TextEditorTool. This tool requires a custom storage implementation.
Note: This is an advanced feature that requires implementing the TextEditorStorage interface to handle file operations securely.
<?php
use Soukicz\Llm\Tool\TextEditor\TextEditorTool;
use Soukicz\Llm\Tool\TextEditor\TextEditorStorage;
// You need to implement TextEditorStorage interface
class MyTextEditorStorage implements TextEditorStorage {
public function __construct(private string $basePath) {}
public function read(string $path): string {
// Implement secure file reading
return file_get_contents($this->basePath . '/' . $path);
}
public function write(string $path, string $content): void {
// Implement secure file writing
file_put_contents($this->basePath . '/' . $path, $content);
}
// Implement other required methods...
}
$storage = new MyTextEditorStorage('/path/to/working/directory');
$textEditorTool = new TextEditorTool($storage);
$request = new LLMRequest(
model: new AnthropicClaude45Sonnet(AnthropicClaude45Sonnet::VERSION_20250929),
conversation: new LLMConversation([
LLMMessage::createFromUserString('Create a new file called hello.txt with "Hello World"')
]),
tools: [$textEditorTool],
);The text editor tool supports:
view- View file contentscreate- Create new filesstr_replace- Replace text in filesinsert- Insert text at specific line numbers
Security Considerations: When implementing TextEditorStorage, ensure proper path validation, access controls, and sandboxing to prevent unauthorized file access.
The inputSchema follows JSON Schema specification. Common patterns:
<?php
'inputSchema' => [
'type' => 'object',
'properties' => [
'query' => ['type' => 'string', 'description' => 'Search query'],
'limit' => ['type' => 'integer', 'description' => 'Max results'],
],
'required' => ['query'],
]<?php
'properties' => [
'priority' => [
'type' => 'string',
'enum' => ['low', 'medium', 'high'],
'description' => 'Task priority level'
],
]<?php
'properties' => [
'tags' => [
'type' => 'array',
'items' => ['type' => 'string'],
'description' => 'List of tags'
],
]<?php
'properties' => [
'location' => [
'type' => 'object',
'properties' => [
'lat' => ['type' => 'number'],
'lng' => ['type' => 'number'],
],
'required' => ['lat', 'lng'],
],
]- Clear Descriptions - Provide detailed descriptions for both the tool and each parameter
- Use Promises for I/O - Return promises for async operations like API calls
- Validate Input - The schema helps, but add runtime validation if needed
- Error Handling - Handle errors gracefully and return meaningful error messages
- Keep Tools Focused - One tool should do one thing well
- Document Side Effects - If a tool modifies state, make it clear in the description
- ✅ Anthropic (Claude) - Full support, including native tools
- ✅ OpenAI (GPT) - Full function calling support
- ✅ Google Gemini - Full function calling support
⚠️ OpenAI-compatible - Depends on the underlying model
- Feedback Loops - Validate tool outputs
- Examples - More tool examples
- Provider Documentation - Provider-specific tool features