Agentic AIPHPverifiedVerified
Tool Use Agent Pattern in PHP
Augment an LLM with callable external tools — APIs, code interpreters, databases — so it can take actions and retrieve real-time information beyond its training data.
How to Implement the Tool Use Agent Pattern in PHP
1Step 1: Define tool schema and registry
class ToolSchema
{
public function __construct(
public readonly string $name,
public readonly string $description,
/** @var array<string, string> */
public readonly array $parameters,
) {}
}
interface ToolExecutor
{
public function execute(string $name, array $args): string;
}2Step 2: Implement a simple tool registry
class ToolRegistry implements ToolExecutor
{
/** @var array<string, callable> */
private array $handlers = [];
/** @var ToolSchema[] */
private array $schemas = [];
public function register(ToolSchema $schema, callable $handler): void
{
$this->schemas[$schema->name] = $schema;
$this->handlers[$schema->name] = $handler;
}
public function execute(string $name, array $args): string
{
if (!isset($this->handlers[$name])) {
throw new \RuntimeException("Tool not found: {$name}");
}
return ($this->handlers[$name])($args);
}
/** @return ToolSchema[] */
public function getSchemas(): array
{
return array_values($this->schemas);
}
}3Step 3: Wire tools to an LLM interaction loop
function toolUseLoop(string $query, ToolRegistry $registry, callable $llm): string
{
$schemas = $registry->getSchemas();
$prompt = "Query: {$query}\nAvailable tools: " .
implode(', ', array_map(fn(ToolSchema $s) => $s->name, $schemas));
$response = $llm($prompt);
$parsed = json_decode($response, true);
if (isset($parsed['tool'])) {
$result = $registry->execute($parsed['tool'], $parsed['args'] ?? []);
return $llm("Tool result: {$result}\nProvide final answer.");
}
return $response;
}<?php
declare(strict_types=1);
// [step] Define strict tool interfaces and result types
final readonly class ToolParameter
{
public function __construct(
public string $name,
public string $type,
public string $description,
public bool $required = true,
) {}
}
final readonly class ToolDefinition
{
/** @param ToolParameter[] $parameters */
public function __construct(
public string $name,
public string $description,
public array $parameters,
) {}
}
final readonly class ToolCallResult
{
public function __construct(
public bool $success,
public mixed $data,
public ?string $error = null,
public float $durationMs = 0,
) {}
}
interface ToolHandlerInterface
{
public function getDefinition(): ToolDefinition;
/** @param array<string, mixed> $args */
public function handle(array $args): ToolCallResult;
}
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
public function error(string $message, array $context = []): void;
}
// [step] Implement a validated tool registry with middleware
final class ToolRegistry
{
/** @var array<string, ToolHandlerInterface> */
private array $handlers = [];
/** @var array<callable> */
private array $middleware = [];
public function __construct(
private readonly LoggerInterface $logger,
) {}
public function register(ToolHandlerInterface $handler): self
{
$def = $handler->getDefinition();
$this->handlers[$def->name] = $handler;
$this->logger->info("Registered tool: {$def->name}");
return $this;
}
public function addMiddleware(callable $middleware): self
{
$this->middleware[] = $middleware;
return $this;
}
/** @param array<string, mixed> $args */
public function execute(string $name, array $args): ToolCallResult
{
if (!isset($this->handlers[$name])) {
return new ToolCallResult(success: false, data: null, error: "Unknown tool: {$name}");
}
$handler = $this->handlers[$name];
$definition = $handler->getDefinition();
// Validate required parameters
foreach ($definition->parameters as $param) {
if ($param->required && !array_key_exists($param->name, $args)) {
return new ToolCallResult(
success: false,
data: null,
error: "Missing required parameter: {$param->name}",
);
}
}
// Apply middleware
foreach ($this->middleware as $mw) {
$args = $mw($name, $args);
}
$start = hrtime(true);
try {
$result = $handler->handle($args);
$durationMs = (hrtime(true) - $start) / 1e6;
$this->logger->info("Tool executed", [
'tool' => $name,
'durationMs' => $durationMs,
'success' => $result->success,
]);
return new ToolCallResult(
success: $result->success,
data: $result->data,
error: $result->error,
durationMs: $durationMs,
);
} catch (\Throwable $e) {
$this->logger->error("Tool failed", [
'tool' => $name,
'error' => $e->getMessage(),
]);
return new ToolCallResult(
success: false,
data: null,
error: $e->getMessage(),
durationMs: (hrtime(true) - $start) / 1e6,
);
}
}
/** @return ToolDefinition[] */
public function getDefinitions(): array
{
return array_map(
fn(ToolHandlerInterface $h) => $h->getDefinition(),
array_values($this->handlers),
);
}
}Tool Use Agent Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Tool Use Agent Pattern in the Real World
“A lawyer (the LLM) in a courtroom knows the law but needs a paralegal team (the tools) to pull case files, run searches, and retrieve exhibits. The lawyer directs which file to fetch, the paralegal returns it, and the lawyer integrates that information into their argument — the lawyer's intelligence is amplified by the support staff's ability to reach into the real world.”