|
13 | 13 |
|
14 | 14 | use PHPUnit\Framework\Attributes\CoversClass;
|
15 | 15 | use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
|
| 16 | +use PHPUnit\Framework\Attributes\TestDox; |
16 | 17 | use PHPUnit\Framework\Attributes\TestWith;
|
17 | 18 | use PHPUnit\Framework\Attributes\UsesClass;
|
18 | 19 | use PHPUnit\Framework\TestCase;
|
@@ -303,6 +304,191 @@ public function testConfigurationWithUseAttributeAsKeyWorksWithoutNormalizeKeys(
|
303 | 304 | $this->assertTrue($container->hasDefinition('ai.store.mongodb.Production_DB-v3'));
|
304 | 305 | }
|
305 | 306 |
|
| 307 | + /** |
| 308 | + * Tests that processor tags use the full agent ID (ai.agent.my_agent) instead of just the agent name (my_agent). |
| 309 | + * This regression test prevents issues where processors would not be correctly associated with their agents. |
| 310 | + */ |
| 311 | + #[TestDox('Processor tags use the full agent ID instead of just the agent name')] |
| 312 | + public function testProcessorTagsUseFullAgentId() |
| 313 | + { |
| 314 | + $container = $this->buildContainer([ |
| 315 | + 'ai' => [ |
| 316 | + 'agent' => [ |
| 317 | + 'test_agent' => [ |
| 318 | + 'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'], |
| 319 | + 'tools' => [ |
| 320 | + ['service' => 'some_tool', 'description' => 'Test tool'], |
| 321 | + ], |
| 322 | + 'structured_output' => true, |
| 323 | + 'system_prompt' => 'You are a test assistant.', |
| 324 | + ], |
| 325 | + ], |
| 326 | + ], |
| 327 | + ]); |
| 328 | + |
| 329 | + $agentId = 'ai.agent.test_agent'; |
| 330 | + |
| 331 | + // Test tool processor tags |
| 332 | + $toolProcessorDefinition = $container->getDefinition('ai.tool.agent_processor.test_agent'); |
| 333 | + $toolProcessorTags = $toolProcessorDefinition->getTag('ai.agent.input_processor'); |
| 334 | + $this->assertNotEmpty($toolProcessorTags, 'Tool processor should have input processor tags'); |
| 335 | + $this->assertSame($agentId, $toolProcessorTags[0]['agent'], 'Tool input processor tag should use full agent ID'); |
| 336 | + |
| 337 | + $outputTags = $toolProcessorDefinition->getTag('ai.agent.output_processor'); |
| 338 | + $this->assertNotEmpty($outputTags, 'Tool processor should have output processor tags'); |
| 339 | + $this->assertSame($agentId, $outputTags[0]['agent'], 'Tool output processor tag should use full agent ID'); |
| 340 | + |
| 341 | + // Test structured output processor tags |
| 342 | + $structuredOutputTags = $container->getDefinition('ai.agent.structured_output_processor') |
| 343 | + ->getTag('ai.agent.input_processor'); |
| 344 | + $this->assertNotEmpty($structuredOutputTags, 'Structured output processor should have input processor tags'); |
| 345 | + |
| 346 | + // Find the tag for our specific agent |
| 347 | + $foundAgentTag = false; |
| 348 | + foreach ($structuredOutputTags as $tag) { |
| 349 | + if (($tag['agent'] ?? '') === $agentId) { |
| 350 | + $foundAgentTag = true; |
| 351 | + break; |
| 352 | + } |
| 353 | + } |
| 354 | + $this->assertTrue($foundAgentTag, 'Structured output processor should have tag with full agent ID'); |
| 355 | + |
| 356 | + // Test system prompt processor tags |
| 357 | + $systemPromptDefinition = $container->getDefinition('ai.agent.test_agent.system_prompt_processor'); |
| 358 | + $systemPromptTags = $systemPromptDefinition->getTag('ai.agent.input_processor'); |
| 359 | + $this->assertNotEmpty($systemPromptTags, 'System prompt processor should have input processor tags'); |
| 360 | + $this->assertSame($agentId, $systemPromptTags[0]['agent'], 'System prompt processor tag should use full agent ID'); |
| 361 | + } |
| 362 | + |
| 363 | + #[TestDox('Processors work correctly with multiple agents')] |
| 364 | + public function testMultipleAgentsWithProcessors() |
| 365 | + { |
| 366 | + $container = $this->buildContainer([ |
| 367 | + 'ai' => [ |
| 368 | + 'agent' => [ |
| 369 | + 'first_agent' => [ |
| 370 | + 'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'], |
| 371 | + 'tools' => [ |
| 372 | + ['service' => 'tool_one', 'description' => 'Tool for first agent'], |
| 373 | + ], |
| 374 | + 'system_prompt' => 'First agent prompt', |
| 375 | + ], |
| 376 | + 'second_agent' => [ |
| 377 | + 'model' => ['class' => 'Symfony\AI\Platform\Bridge\Anthropic\Claude'], |
| 378 | + 'tools' => [ |
| 379 | + ['service' => 'tool_two', 'description' => 'Tool for second agent'], |
| 380 | + ], |
| 381 | + 'system_prompt' => 'Second agent prompt', |
| 382 | + ], |
| 383 | + ], |
| 384 | + ], |
| 385 | + ]); |
| 386 | + |
| 387 | + // Check that each agent has its own properly tagged processors |
| 388 | + $firstAgentId = 'ai.agent.first_agent'; |
| 389 | + $secondAgentId = 'ai.agent.second_agent'; |
| 390 | + |
| 391 | + // First agent tool processor |
| 392 | + $firstToolProcessor = $container->getDefinition('ai.tool.agent_processor.first_agent'); |
| 393 | + $firstToolTags = $firstToolProcessor->getTag('ai.agent.input_processor'); |
| 394 | + $this->assertSame($firstAgentId, $firstToolTags[0]['agent']); |
| 395 | + |
| 396 | + // Second agent tool processor |
| 397 | + $secondToolProcessor = $container->getDefinition('ai.tool.agent_processor.second_agent'); |
| 398 | + $secondToolTags = $secondToolProcessor->getTag('ai.agent.input_processor'); |
| 399 | + $this->assertSame($secondAgentId, $secondToolTags[0]['agent']); |
| 400 | + |
| 401 | + // First agent system prompt processor |
| 402 | + $firstSystemPrompt = $container->getDefinition('ai.agent.first_agent.system_prompt_processor'); |
| 403 | + $firstSystemTags = $firstSystemPrompt->getTag('ai.agent.input_processor'); |
| 404 | + $this->assertSame($firstAgentId, $firstSystemTags[0]['agent']); |
| 405 | + |
| 406 | + // Second agent system prompt processor |
| 407 | + $secondSystemPrompt = $container->getDefinition('ai.agent.second_agent.system_prompt_processor'); |
| 408 | + $secondSystemTags = $secondSystemPrompt->getTag('ai.agent.input_processor'); |
| 409 | + $this->assertSame($secondAgentId, $secondSystemTags[0]['agent']); |
| 410 | + } |
| 411 | + |
| 412 | + #[TestDox('Processors work correctly when using the default toolbox')] |
| 413 | + public function testDefaultToolboxProcessorTags() |
| 414 | + { |
| 415 | + $container = $this->buildContainer([ |
| 416 | + 'ai' => [ |
| 417 | + 'agent' => [ |
| 418 | + 'agent_with_default_toolbox' => [ |
| 419 | + 'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'], |
| 420 | + 'tools' => true, |
| 421 | + ], |
| 422 | + ], |
| 423 | + ], |
| 424 | + ]); |
| 425 | + |
| 426 | + $agentId = 'ai.agent.agent_with_default_toolbox'; |
| 427 | + |
| 428 | + // When using default toolbox, the ai.tool.agent_processor service gets the tags |
| 429 | + $defaultToolProcessor = $container->getDefinition('ai.tool.agent_processor'); |
| 430 | + $inputTags = $defaultToolProcessor->getTag('ai.agent.input_processor'); |
| 431 | + $outputTags = $defaultToolProcessor->getTag('ai.agent.output_processor'); |
| 432 | + |
| 433 | + // Find tags for our specific agent |
| 434 | + $foundInput = false; |
| 435 | + $foundOutput = false; |
| 436 | + |
| 437 | + foreach ($inputTags as $tag) { |
| 438 | + if (($tag['agent'] ?? '') === $agentId) { |
| 439 | + $foundInput = true; |
| 440 | + break; |
| 441 | + } |
| 442 | + } |
| 443 | + |
| 444 | + foreach ($outputTags as $tag) { |
| 445 | + if (($tag['agent'] ?? '') === $agentId) { |
| 446 | + $foundOutput = true; |
| 447 | + break; |
| 448 | + } |
| 449 | + } |
| 450 | + |
| 451 | + $this->assertTrue($foundInput, 'Default tool processor should have input tag with full agent ID'); |
| 452 | + $this->assertTrue($foundOutput, 'Default tool processor should have output tag with full agent ID'); |
| 453 | + } |
| 454 | + |
| 455 | + #[TestDox('Token usage processor tags use the correct agent ID')] |
| 456 | + public function testTokenUsageProcessorTags() |
| 457 | + { |
| 458 | + $container = $this->buildContainer([ |
| 459 | + 'ai' => [ |
| 460 | + 'platform' => [ |
| 461 | + 'openai' => [ |
| 462 | + 'api_key' => 'test_key', |
| 463 | + ], |
| 464 | + ], |
| 465 | + 'agent' => [ |
| 466 | + 'tracked_agent' => [ |
| 467 | + 'platform' => 'ai.platform.openai', |
| 468 | + 'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'], |
| 469 | + 'track_token_usage' => true, |
| 470 | + ], |
| 471 | + ], |
| 472 | + ], |
| 473 | + ]); |
| 474 | + |
| 475 | + $agentId = 'ai.agent.tracked_agent'; |
| 476 | + |
| 477 | + // Token usage processor must exist for OpenAI platform |
| 478 | + $tokenUsageProcessor = $container->getDefinition('ai.platform.token_usage_processor.openai'); |
| 479 | + $outputTags = $tokenUsageProcessor->getTag('ai.agent.output_processor'); |
| 480 | + |
| 481 | + $foundTag = false; |
| 482 | + foreach ($outputTags as $tag) { |
| 483 | + if (($tag['agent'] ?? '') === $agentId) { |
| 484 | + $foundTag = true; |
| 485 | + break; |
| 486 | + } |
| 487 | + } |
| 488 | + |
| 489 | + $this->assertTrue($foundTag, 'Token usage processor should have output tag with full agent ID'); |
| 490 | + } |
| 491 | + |
306 | 492 | private function buildContainer(array $configuration): ContainerBuilder
|
307 | 493 | {
|
308 | 494 | $container = new ContainerBuilder();
|
|
0 commit comments