Agentic AIPHPverifiedVerified
ReAct Agent Pattern in PHP
Interleaves chain-of-thought Reasoning with Action execution, enabling LLMs to dynamically plan, act, and observe in a loop.
How to Implement the ReAct Agent Pattern in PHP
1Step 1: Define the Tool interface and AgentStep value object
interface Tool
{
public function getName(): string;
public function getDescription(): string;
public function execute(string $input): string;
}
class AgentStep
{
public function __construct(
public readonly string $thought,
public readonly string $action,
public readonly string $actionInput,
public readonly string $observation,
) {}
}2Step 2: Implement the ReAct reasoning loop
const MAX_STEPS = 10;
function reactLoop(string $query, array $tools, callable $llm): string
{
/** @var AgentStep[] $steps */
$steps = [];
for ($i = 0; $i < MAX_STEPS; $i++) {
// Reason about what to do next
$prompt = buildPrompt($query, $tools, $steps);
$response = $llm($prompt);
// Parse thought and action from response
$parsed = parseResponse($response);
if ($parsed['isFinal']) {
return $parsed['finalAnswer'];
}
// Execute the chosen tool
$tool = findTool($tools, $parsed['action']);
if ($tool === null) {
throw new \RuntimeException("Unknown tool: {$parsed['action']}");
}
$observation = $tool->execute($parsed['actionInput']);
$steps[] = new AgentStep(
thought: $parsed['thought'],
action: $parsed['action'],
actionInput: $parsed['actionInput'],
observation: $observation,
);
}
return 'Max steps reached without final answer.';
}3Step 3: Build the prompt and parse LLM responses
function buildPrompt(string $query, array $tools, array $steps): string
{
$toolNames = array_map(fn(Tool $t) => $t->getName(), $tools);
return "Query: {$query}\nTools: " . implode(', ', $toolNames);
}
function parseResponse(string $response): array
{
return [
'thought' => '', 'action' => '', 'actionInput' => '',
'isFinal' => false, 'finalAnswer' => '',
];
}
function findTool(array $tools, string $name): ?Tool
{
foreach ($tools as $tool) {
if ($tool->getName() === $name) return $tool;
}
return null;
}<?php
declare(strict_types=1);
// [step] Define strict interfaces and value objects
interface ToolInterface
{
public function getName(): string;
public function getDescription(): string;
/** @return array<string, string> */
public function getParameters(): array;
/** @param array<string, string> $input */
public function execute(array $input): ToolResult;
}
final readonly class ToolResult
{
public function __construct(
public bool $success,
public string $data,
/** @var array<string, mixed> */
public array $metadata = [],
) {}
}
final readonly class AgentStep
{
public function __construct(
public string $thought,
public string $action,
/** @var array<string, string> */
public array $actionInput,
public ToolResult $observation,
public float $timestamp,
) {}
}
final readonly class AgentConfig
{
/** @param ToolInterface[] $tools */
public function __construct(
public int $maxSteps = 10,
public string $model = 'default',
public float $temperature = 0.3,
public array $tools = [],
public string $systemPrompt = '',
) {}
}
final readonly class AgentResult
{
/** @param AgentStep[] $steps */
public function __construct(
public string $answer,
public array $steps,
public int $totalTokens,
public float $durationMs,
) {}
}
// [step] Implement the ReAct agent with full error handling
final class ReActAgent
{
/** @var AgentStep[] */
private array $steps = [];
/** @var array<string, ToolInterface> */
private array $toolMap = [];
public function __construct(
private readonly AgentConfig $config,
private readonly LoggerInterface $logger,
) {
foreach ($config->tools as $tool) {
$this->toolMap[$tool->getName()] = $tool;
}
}
public function run(string $query): AgentResult
{
$startTime = hrtime(true);
$this->steps = [];
$totalTokens = 0;
for ($i = 0; $i < $this->config->maxSteps; $i++) {
$this->logger->info("Step {step}", ['step' => $i + 1]);
// Build messages with full history
$prompt = $this->buildPrompt($query);
$response = $this->callLLM($prompt);
$totalTokens += $response['tokens'];
if ($response['isFinalAnswer']) {
return new AgentResult(
answer: $response['finalAnswer'],
steps: $this->steps,
totalTokens: $totalTokens,
durationMs: (hrtime(true) - $startTime) / 1e6,
);
}
// Execute the chosen tool
$toolName = $response['action'];
if (!isset($this->toolMap[$toolName])) {
throw new \RuntimeException("Unknown tool: {$toolName}");
}
try {
$result = $this->toolMap[$toolName]->execute($response['actionInput']);
} catch (\Throwable $e) {
$this->logger->warning('Tool execution failed', [
'tool' => $toolName,
'error' => $e->getMessage(),
]);
$result = new ToolResult(success: false, data: $e->getMessage());
}
$this->steps[] = new AgentStep(
thought: $response['thought'],
action: $toolName,
actionInput: $response['actionInput'],
observation: $result,
timestamp: microtime(true),
);
}
return new AgentResult(
answer: 'Max steps reached without final answer.',
steps: $this->steps,
totalTokens: $totalTokens,
durationMs: (hrtime(true) - $startTime) / 1e6,
);
}
private function buildPrompt(string $query): string
{
$toolDescriptions = [];
foreach ($this->toolMap as $name => $tool) {
$toolDescriptions[] = "- {$name}: {$tool->getDescription()}";
}
return implode("\n", [
$this->config->systemPrompt,
"Tools:\n" . implode("\n", $toolDescriptions),
"Query: {$query}",
]);
}
/** @return array{thought: string, action: string, actionInput: array<string, string>, isFinalAnswer: bool, finalAnswer: string, tokens: int} */
private function callLLM(string $prompt): array
{
return [
'thought' => '', 'action' => '', 'actionInput' => [],
'isFinalAnswer' => false, 'finalAnswer' => '', 'tokens' => 0,
];
}
}
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
public function warning(string $message, array $context = []): void;
}ReAct Agent Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
ReAct Agent Pattern in the Real World
“Like a detective investigating a case: they form a hypothesis (Thought), gather evidence by interviewing witnesses or examining clues (Action), analyze what they found (Observation), and then refine their theory. They keep investigating until they solve the case.”