arnaud-delgerie/symfony-ai-tool-agent
Install the Bundle
composer require arnaud-delgerie/symfony-ai-tool-agent
Register the bundle in config/bundles.php:
return [
// ...
ArnaudDelgerie\AiToolAgentBundle\AiToolAgentBundle::class => ['all' => true],
];
Configure API Credentials
Add your API key (OpenAI, Mistral, or Anthropic) to .env:
AI_TOOL_AGENT_API_KEY=your_api_key_here
Create a Basic Tool Function Manager
Implement ToolFunctionManagerInterface for a simple use case (e.g., text summarization):
namespace App\ToolFunctionManager;
use ArnaudDelgerie\AiToolAgent\DTO\ToolFunction;
use ArnaudDelgerie\AiToolAgent\Util\ToolResponse;
use ArnaudDelgerie\AiToolAgent\Interface\ToolFunctionManagerInterface;
class SummarizerToolFunctionManager implements ToolFunctionManagerInterface {
public static function getName(): string { return 'summarize'; }
public function getToolFunction(array $context): ToolFunction {
return (new ToolFunction())
->setName(self::getName())
->setDescription('Summarizes text content')
->addProperty('summary', (new ToolFunctionProperty())
->setType(ToolFunctionPropertyTypeEnum::String)
->setDescription('Concise summary of the input text'));
}
public function execute(array $args, array $context, array $responseContent): ToolResponse {
// Custom logic (e.g., call an external API or process locally)
$responseContent['summary'] = $args['summary'];
return new ToolResponse($responseContent, "", true);
}
}
First Use Case: Summarize Text Create a service to leverage the tool agent:
namespace App\Service;
use ArnaudDelgerie\AiToolAgent\Util\ToolAgentProvider;
use ArnaudDelgerie\AiToolAgent\Util\Config\AgentConfig;
use ArnaudDelgerie\AiToolAgent\Util\Config\ClientConfig;
use ArnaudDelgerie\AiToolAgent\Enum\ClientEnum;
class TextSummarizer {
public function __construct(private ToolAgentProvider $toolAgentProvider) {}
public function summarize(string $text): string {
$clientConfig = new ClientConfig(ClientEnum::Mistral, $_ENV['AI_TOOL_AGENT_API_KEY'], 'mistral-small-latest', 0.1);
$systemPrompt = "You are a text summarizer. Use the 'summarize' function to return a concise summary.";
$agentConfig = new AgentConfig($systemPrompt, ['summarize'], []);
$toolAgent = $this->toolAgentProvider->createToolAgent($clientConfig, $agentConfig);
$response = $toolAgent->addUserMessage($text)->run();
return $response->content['summarize']['summary'] ?? '';
}
}
Tool Function Development
ToolFunctionManager.
TopicClassification, ContentModeration, and ImageAnalysis.availableTopics, userId) via the $context array to the getToolFunction() method.
$context = ['userId' => $user->getId(), 'allowedActions' => ['edit', 'delete']];
Agent Configuration
/prompts/summarizer.txt) and load them dynamically:
$systemPrompt = file_get_contents($kernelProjectDir . '/prompts/summarizer.txt');
$agentConfig = new AgentConfig($systemPrompt, ['summarize'], $context);
AgentConfig class to inherit from for shared configurations (e.g., temperature, model).Console Integration
ConsoleToolAgent for interactive CLI tools:
use ArnaudDelgerie\AiToolAgent\Util\ConsoleToolAgent;
class DebugAgentCommand extends Command {
protected function execute(InputInterface $input, OutputInterface $output): int {
$consoleAgent = new ConsoleToolAgent($clientConfig, $agentConfig);
$response = $consoleAgent->run();
$output->writeln($response->content);
return Command::SUCCESS;
}
}
Dependency Injection
ToolFunctionManager services as lazy services in services.yaml:
services:
App\ToolFunctionManager\SummarizerToolFunctionManager:
tags: ['ai.tool_function_manager']
ToolAgentProvider and ClientConfig in services.Error Handling
try {
$response = $toolAgent->run();
} catch (ApiException $e) {
$this->logger->error('AI API Error: ' . $e->getMessage());
throw new RuntimeException('Failed to process request');
}
Event-Driven Workflows
post.publish):
$dispatcher->addListener('post.publish', function (PostEvent $event) {
$summarizer->summarize($event->getPost()->getContent());
});
Queue Background Jobs
$message = new SummarizeTextMessage($text);
$bus->dispatch($message);
Caching Responses
$cacheKey = 'summary_' . md5($text);
if (!$summary = $cache->get($cacheKey)) {
$summary = $summarizer->summarize($text);
$cache->set($cacheKey, $summary, '1 hour');
}
Testing
ToolAgentProvider and ClientConfig in unit tests:
$this->mockToolAgentProvider()
->expects($this->once())
->method('createToolAgent')
->willReturn($this->createMockToolAgent());
API Rate Limits
use Symfony\Component\HttpClient\RetryableHttpClient;
$client = new RetryableHttpClient(
new HttpClient(),
['max_retries' => 3, 'delay' => 1000]
);
Context Leakage
$context passed to the AI.$safeContext = array_filter($context, fn($key) => !str_starts_with($key, 'secret_'), ARRAY_FILTER_USE_KEY);
Tool Function Naming Collisions
user.topic_classification):
public static function getName(): string { return 'user.topic_classification'; }
Prompt Design Flaws
if ($response->content['topic_classification']['topic'] === 'unknown') {
$this->logger->warning('Misclassified content: ' . $text);
}
Alpha Software Risks
CHANGELOG.md and wrap bundle calls in version checks:
if (version_compare(\ArnaudDelgerie\AiToolAgentBundle\AiToolAgentBundle::VERSION, '1.0.0', '<')) {
// Fallback logic for alpha versions
}
# config/packages/monolog.yaml
handlers:
ai_tool_agent:
type: stream
path: "%kernel.logs_dir%/ai_tool_agent.log"
level: debug
channels:
How can I help you explore Laravel packages today?