mirror of
https://github.com/symfony/ai-agent.git
synced 2026-03-23 23:12:11 +01:00
Keep lazy iterator alive
This commit is contained in:
@@ -9,6 +9,7 @@ parameters:
|
||||
- tests/
|
||||
excludePaths:
|
||||
- src/Bridge/
|
||||
treatPhpDocTypesAsCertain: false
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^Method .*::test.*\\(\\) has no return type specified\\.$#"
|
||||
|
||||
@@ -23,16 +23,6 @@ use Symfony\AI\Platform\Result\ResultInterface;
|
||||
*/
|
||||
final class Agent implements AgentInterface
|
||||
{
|
||||
/**
|
||||
* @var InputProcessorInterface[]
|
||||
*/
|
||||
private readonly array $inputProcessors;
|
||||
|
||||
/**
|
||||
* @var OutputProcessorInterface[]
|
||||
*/
|
||||
private readonly array $outputProcessors;
|
||||
|
||||
/**
|
||||
* @param InputProcessorInterface[] $inputProcessors
|
||||
* @param OutputProcessorInterface[] $outputProcessors
|
||||
@@ -41,12 +31,10 @@ final class Agent implements AgentInterface
|
||||
public function __construct(
|
||||
private readonly PlatformInterface $platform,
|
||||
private readonly string $model,
|
||||
iterable $inputProcessors = [],
|
||||
iterable $outputProcessors = [],
|
||||
private readonly iterable $inputProcessors = [],
|
||||
private readonly iterable $outputProcessors = [],
|
||||
private readonly string $name = 'agent',
|
||||
) {
|
||||
$this->inputProcessors = $this->initializeProcessors($inputProcessors, InputProcessorInterface::class);
|
||||
$this->outputProcessors = $this->initializeProcessors($outputProcessors, OutputProcessorInterface::class);
|
||||
}
|
||||
|
||||
public function getModel(): string
|
||||
@@ -69,7 +57,17 @@ final class Agent implements AgentInterface
|
||||
public function call(MessageBag $messages, array $options = []): ResultInterface
|
||||
{
|
||||
$input = new Input($this->getModel(), $messages, $options);
|
||||
array_map(static fn (InputProcessorInterface $processor) => $processor->processInput($input), $this->inputProcessors);
|
||||
foreach ($this->inputProcessors as $inputProcessor) {
|
||||
if (!$inputProcessor instanceof InputProcessorInterface) {
|
||||
throw new InvalidArgumentException(\sprintf('Input processor "%s" must implement "%s".', $inputProcessor::class, InputProcessorInterface::class));
|
||||
}
|
||||
|
||||
if ($inputProcessor instanceof AgentAwareInterface) {
|
||||
$inputProcessor->setAgent($this);
|
||||
}
|
||||
|
||||
$inputProcessor->processInput($input);
|
||||
}
|
||||
|
||||
$model = $input->getModel();
|
||||
$messages = $input->getMessageBag();
|
||||
@@ -78,29 +76,18 @@ final class Agent implements AgentInterface
|
||||
$result = $this->platform->invoke($model, $messages, $options)->getResult();
|
||||
|
||||
$output = new Output($model, $result, $messages, $options);
|
||||
array_map(static fn (OutputProcessorInterface $processor) => $processor->processOutput($output), $this->outputProcessors);
|
||||
foreach ($this->outputProcessors as $outputProcessor) {
|
||||
if (!$outputProcessor instanceof OutputProcessorInterface) {
|
||||
throw new InvalidArgumentException(\sprintf('Output processor "%s" must implement "%s".', $outputProcessor::class, OutputProcessorInterface::class));
|
||||
}
|
||||
|
||||
if ($outputProcessor instanceof AgentAwareInterface) {
|
||||
$outputProcessor->setAgent($this);
|
||||
}
|
||||
|
||||
$outputProcessor->processOutput($output);
|
||||
}
|
||||
|
||||
return $output->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputProcessorInterface[]|OutputProcessorInterface[] $processors
|
||||
* @param class-string $interface
|
||||
*
|
||||
* @return InputProcessorInterface[]|OutputProcessorInterface[]
|
||||
*/
|
||||
private function initializeProcessors(iterable $processors, string $interface): array
|
||||
{
|
||||
foreach ($processors as $processor) {
|
||||
if (!$processor instanceof $interface) {
|
||||
throw new InvalidArgumentException(\sprintf('Processor "%s" must implement "%s".', $processor::class, $interface));
|
||||
}
|
||||
|
||||
if ($processor instanceof AgentAwareInterface) {
|
||||
$processor->setAgent($this);
|
||||
}
|
||||
}
|
||||
|
||||
return $processors instanceof \Traversable ? iterator_to_array($processors) : $processors;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,17 +19,12 @@ use Symfony\AI\Agent\Toolbox\ToolFactoryInterface;
|
||||
*/
|
||||
final class ChainFactory implements ToolFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @var list<ToolFactoryInterface>
|
||||
*/
|
||||
private readonly array $factories;
|
||||
|
||||
/**
|
||||
* @param iterable<ToolFactoryInterface> $factories
|
||||
*/
|
||||
public function __construct(iterable $factories)
|
||||
{
|
||||
$this->factories = $factories instanceof \Traversable ? iterator_to_array($factories) : $factories;
|
||||
public function __construct(
|
||||
private readonly iterable $factories,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getTool(string $reference): iterable
|
||||
|
||||
@@ -31,13 +31,6 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
*/
|
||||
final class Toolbox implements ToolboxInterface
|
||||
{
|
||||
/**
|
||||
* List of executable tools.
|
||||
*
|
||||
* @var list<object>
|
||||
*/
|
||||
private readonly array $tools;
|
||||
|
||||
/**
|
||||
* List of tool metadata objects.
|
||||
*
|
||||
@@ -49,13 +42,12 @@ final class Toolbox implements ToolboxInterface
|
||||
* @param iterable<object> $tools
|
||||
*/
|
||||
public function __construct(
|
||||
iterable $tools,
|
||||
private readonly iterable $tools,
|
||||
private readonly ToolFactoryInterface $toolFactory = new ReflectionToolFactory(),
|
||||
private readonly ToolCallArgumentResolverInterface $argumentResolver = new ToolCallArgumentResolver(),
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
private readonly ?EventDispatcherInterface $eventDispatcher = null,
|
||||
) {
|
||||
$this->tools = $tools instanceof \Traversable ? iterator_to_array($tools) : $tools;
|
||||
}
|
||||
|
||||
public function getTools(): array
|
||||
|
||||
@@ -30,6 +30,7 @@ use Symfony\AI\Platform\PlatformInterface;
|
||||
use Symfony\AI\Platform\Result\DeferredResult;
|
||||
use Symfony\AI\Platform\Result\RawResultInterface;
|
||||
use Symfony\AI\Platform\Result\ResultInterface;
|
||||
use Symfony\AI\Platform\Test\InMemoryPlatform;
|
||||
|
||||
final class AgentTest extends TestCase
|
||||
{
|
||||
@@ -53,10 +54,17 @@ final class AgentTest extends TestCase
|
||||
$this->assertInstanceOf(AgentInterface::class, $agent);
|
||||
}
|
||||
|
||||
public function testConstructorSetsAgentOnAgentAwareProcessors()
|
||||
public function testAgentExposesHisModel()
|
||||
{
|
||||
$platform = $this->createMock(PlatformInterface::class);
|
||||
|
||||
$agent = new Agent($platform, 'gpt-4o');
|
||||
|
||||
$this->assertSame('gpt-4o', $agent->getModel());
|
||||
}
|
||||
|
||||
public function testSetsAgentOnAgentAwareProcessors()
|
||||
{
|
||||
$agentAwareProcessor = new class implements InputProcessorInterface, AgentAwareInterface {
|
||||
public ?AgentInterface $agent = null;
|
||||
|
||||
@@ -70,42 +78,30 @@ final class AgentTest extends TestCase
|
||||
}
|
||||
};
|
||||
|
||||
$agent = new Agent($platform, 'gpt-4o', [$agentAwareProcessor]);
|
||||
$agent = new Agent(new InMemoryPlatform('Hi'), 'gpt-4o', [$agentAwareProcessor]);
|
||||
$agent->call(new MessageBag());
|
||||
|
||||
$this->assertSame($agent, $agentAwareProcessor->agent);
|
||||
}
|
||||
|
||||
public function testConstructorThrowsExceptionForInvalidInputProcessor()
|
||||
{
|
||||
$platform = $this->createMock(PlatformInterface::class);
|
||||
$invalidProcessor = new \stdClass();
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage(\sprintf('Processor "stdClass" must implement "%s".', InputProcessorInterface::class));
|
||||
$this->expectExceptionMessage(\sprintf('Input processor "stdClass" must implement "%s".', InputProcessorInterface::class));
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
new Agent($platform, 'gpt-4o', [$invalidProcessor]);
|
||||
/** @phpstan-ignore-next-line argument.type */
|
||||
$agent = new Agent(new InMemoryPlatform('Hi'), 'gpt-4o', [new \stdClass()]);
|
||||
$agent->call(new MessageBag());
|
||||
}
|
||||
|
||||
public function testConstructorThrowsExceptionForInvalidOutputProcessor()
|
||||
{
|
||||
$platform = $this->createMock(PlatformInterface::class);
|
||||
$invalidProcessor = new \stdClass();
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage(\sprintf('Processor "stdClass" must implement "%s".', OutputProcessorInterface::class));
|
||||
$this->expectExceptionMessage(\sprintf('Output processor "stdClass" must implement "%s".', OutputProcessorInterface::class));
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
new Agent($platform, 'gpt-4o', [], [$invalidProcessor]);
|
||||
}
|
||||
|
||||
public function testAgentExposesHisModel()
|
||||
{
|
||||
$platform = $this->createMock(PlatformInterface::class);
|
||||
|
||||
$agent = new Agent($platform, 'gpt-4o');
|
||||
|
||||
$this->assertSame('gpt-4o', $agent->getModel());
|
||||
/** @phpstan-ignore-next-line argument.type */
|
||||
$agent = new Agent(new InMemoryPlatform('Hi'), 'gpt-4o', [], [new \stdClass()]);
|
||||
$agent->call(new MessageBag());
|
||||
}
|
||||
|
||||
public function testCallProcessesInputThroughProcessors()
|
||||
|
||||
Reference in New Issue
Block a user