diff --git a/demo/src/Blog/Command/StreamCommand.php b/demo/src/Blog/Command/StreamCommand.php new file mode 100644 index 000000000..e6bac3fa6 --- /dev/null +++ b/demo/src/Blog/Command/StreamCommand.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Blog\Command; + +use Symfony\AI\Agent\AgentInterface; +use Symfony\AI\Platform\Message\Message; +use Symfony\AI\Platform\Message\MessageBag; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Style\SymfonyStyle; + +#[AsCommand('app:blog:stream', 'An example command to demonstrate streaming output.')] +final readonly class StreamCommand +{ + public function __construct( + private AgentInterface $blogAgent, + ) { + } + + public function __invoke(SymfonyStyle $io): int + { + $io->title('Stream Example Command'); + $io->text('This command demonstrates streaming output in the console.'); + + $io->comment('Make sure to have ChromaDB running and the blog indexed, see README.'); + $io->comment('You can use -vvv or --profile to get more insights into the execution.'); + + $question = $io->ask( + 'Ask a question about the content of the Symfony blog', + 'Tell me about the latest Symfony features.', + ); + $messages = new MessageBag(Message::ofUser($question)); + + $io->section('Agent Response:'); + $result = $this->blogAgent->call($messages, ['stream' => true]); + + foreach ($result->getContent() as $word) { + $io->write($word); + } + + $io->newLine(2); + $io->success('The command has completed successfully.'); + + return 0; + } +} diff --git a/demo/tests/Blog/Command/StreamCommandTest.php b/demo/tests/Blog/Command/StreamCommandTest.php new file mode 100644 index 000000000..5b186a849 --- /dev/null +++ b/demo/tests/Blog/Command/StreamCommandTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Tests\Blog\Command; + +use App\Blog\Command\StreamCommand; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Agent\AgentInterface; +use Symfony\AI\Platform\Message\MessageBag; +use Symfony\AI\Platform\Metadata\Metadata; +use Symfony\AI\Platform\Result\RawResultInterface; +use Symfony\AI\Platform\Result\ResultInterface; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Style\SymfonyStyle; + +class StreamCommandTest extends TestCase +{ + public function testStreamCommandOutputsStreamedContentAndSuccess() + { + $mockAgent = $this->createMock(AgentInterface::class); + $mockAgent + ->method('call') + ->with($this->isInstanceOf(MessageBag::class), ['stream' => true]) + ->willReturn(new class implements ResultInterface { + public function getContent(): iterable + { + yield 'Hello'; + yield ' '; + yield 'world'; + yield '!'; + } + + public function getMetadata(): Metadata + { + } + + public function getRawResult(): ?RawResultInterface + { + } + + public function setRawResult(RawResultInterface $rawResult): void + { + } + }); + + $input = new ArrayInput([]); + $input->setInteractive(false); + $io = new SymfonyStyle($input, $buffer = new BufferedOutput()); + $command = new StreamCommand($mockAgent); + $command->__invoke($io); + + $output = $buffer->fetch(); + + $this->assertStringContainsString('Stream Example Command', $output); + $this->assertStringContainsString('This command demonstrates streaming output', $output); + $this->assertStringContainsString('Agent Response:', $output); + $this->assertStringContainsString('Hello world!', $output); + $this->assertStringContainsString('The command has completed successfully.', $output); + } +}