bug #1436 [Platform][Perplexity] Fix Metadata handling on Perplexity streams (chr-hertel)

This PR was merged into the main branch.

Discussion
----------

[Platform][Perplexity] Fix Metadata handling on Perplexity streams

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| Docs?         | no
| Issues        |
| License       | MIT

A bit weird tho with the metadata handling on the result instance in the example - `DeferredResult` is not working correct

Commits
-------

9072e72c Fix Metadata handling on Perplexity streams
This commit is contained in:
Christopher Hertel
2026-01-23 20:19:07 +01:00
9 changed files with 154 additions and 42 deletions

View File

@@ -27,5 +27,5 @@ $result = $platform->invoke('sonar', $messages, [
echo $result->asText().\PHP_EOL;
echo \PHP_EOL;
print_search_results($result->getMetadata());
print_citations($result->getMetadata());
print_search_results($result->getMetadata()->get('search_results'));
print_citations($result->getMetadata()->get('citations'));

View File

@@ -9,18 +9,13 @@
* file that was distributed with this source code.
*/
use Symfony\AI\Platform\Metadata\Metadata;
require_once dirname(__DIR__).'/bootstrap.php';
function print_search_results(Metadata $metadata): void
/**
* @param array<int, array<string, string>> $searchResults
*/
function print_search_results(array $searchResults): void
{
$searchResults = $metadata->get('search_results');
if (null === $searchResults) {
return;
}
echo 'Search results:'.\PHP_EOL;
if (0 === count($searchResults)) {
@@ -40,14 +35,11 @@ function print_search_results(Metadata $metadata): void
}
}
function print_citations(Metadata $metadata): void
/**
* @param array<int, string> $citations
*/
function print_citations(array $citations): void
{
$citations = $metadata->get('citations');
if (null === $citations) {
return;
}
echo 'Citations:'.\PHP_EOL;
if (0 === count($citations)) {

View File

@@ -29,5 +29,5 @@ $result = $platform->invoke('sonar', $messages);
echo $result->asText().\PHP_EOL;
print_search_results($result->getMetadata());
print_citations($result->getMetadata());
print_search_results($result->getMetadata()->get('search_results'));
print_citations($result->getMetadata()->get('citations'));

View File

@@ -28,5 +28,5 @@ $result = $platform->invoke('sonar', $messages);
echo $result->asText().\PHP_EOL;
print_search_results($result->getMetadata());
print_citations($result->getMetadata());
print_search_results($result->getMetadata()->get('search_results'));
print_citations($result->getMetadata()->get('citations'));

View File

@@ -30,5 +30,5 @@ foreach ($result->asStream() as $word) {
}
echo \PHP_EOL;
print_search_results($result->getMetadata());
print_citations($result->getMetadata());
print_search_results($result->getResult()->getMetadata()->get('search_results'));
print_citations($result->getResult()->getMetadata()->get('citations'));

View File

@@ -30,5 +30,5 @@ $result = $platform->invoke('sonar', $messages, [
echo $result->asText().\PHP_EOL;
echo \PHP_EOL;
print_search_results($result->getMetadata());
print_citations($result->getMetadata());
print_search_results($result->getMetadata()->get('search_results'));
print_citations($result->getMetadata()->get('citations'));

View File

@@ -12,7 +12,6 @@
namespace Symfony\AI\Platform\Bridge\Perplexity;
use Symfony\AI\Platform\Exception\RuntimeException;
use Symfony\AI\Platform\Metadata\Metadata;
use Symfony\AI\Platform\Model;
use Symfony\AI\Platform\Result\ChoiceResult;
use Symfony\AI\Platform\Result\RawResultInterface;
@@ -34,7 +33,7 @@ final class ResultConverter implements ResultConverterInterface
public function convert(RawResultInterface $result, array $options = []): ResultInterface
{
if ($options['stream'] ?? false) {
return new StreamResult($this->convertStream($result));
return new StreamResult($this->convertStream($result), [new StreamListener()]);
}
$data = $result->getData();
@@ -67,26 +66,19 @@ final class ResultConverter implements ResultConverterInterface
private function convertStream(RawResultInterface $result): \Generator
{
$searchResults = $citations = [];
/** @var Metadata $metadata */
$metadata = yield;
foreach ($result->getDataStream() as $data) {
if (isset($data['choices'][0]['delta']['content'])) {
yield $data['choices'][0]['delta']['content'];
}
if (isset($data['search_results'])) {
$searchResults = $data['search_results'];
}
if (isset($data['citations'])) {
$citations = $data['citations'];
}
}
$metadata->add('search_results', $searchResults);
$metadata->add('citations', $citations);
if (isset($data['search_results'])) {
yield ['search_results' => $data['search_results']];
}
if (isset($data['citations'])) {
yield ['citations' => $data['citations']];
}
}
/**

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\AI\Platform\Bridge\Perplexity;
use Symfony\AI\Platform\Result\Stream\AbstractStreamListener;
use Symfony\AI\Platform\Result\Stream\ChunkEvent;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final class StreamListener extends AbstractStreamListener
{
public function onChunk(ChunkEvent $event): void
{
$chunk = $event->getChunk();
if (!\is_array($chunk)) {
return;
}
if (isset($chunk['search_results'])) {
$event->getMetadata()->add('search_results', $chunk['search_results']);
$event->skipChunk();
}
if (isset($chunk['citations'])) {
$event->getMetadata()->add('citations', $chunk['citations']);
$event->skipChunk();
}
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\AI\Platform\Bridge\Perplexity\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\AI\Platform\Bridge\Perplexity\StreamListener;
use Symfony\AI\Platform\Result\StreamResult;
final class StreamListenerTest extends TestCase
{
public function testSearchResultsAreAddedToMetadataAndChunkIsSkipped()
{
$searchResults = [['url' => 'https://example.com', 'title' => 'Example']];
$streamResult = new StreamResult((static function () use ($searchResults): \Generator {
yield ['search_results' => $searchResults];
yield 'Hello World';
})());
$streamResult->addListener(new StreamListener());
$chunks = iterator_to_array($streamResult->getContent());
$this->assertSame(['Hello World'], $chunks);
$this->assertTrue($streamResult->getMetadata()->has('search_results'));
$this->assertSame($searchResults, $streamResult->getMetadata()->get('search_results'));
}
public function testCitationsAreAddedToMetadataAndChunkIsSkipped()
{
$citations = ['https://example.com/1', 'https://example.com/2'];
$streamResult = new StreamResult((static function () use ($citations): \Generator {
yield ['citations' => $citations];
yield 'Hello World';
})());
$streamResult->addListener(new StreamListener());
$chunks = iterator_to_array($streamResult->getContent());
$this->assertSame(['Hello World'], $chunks);
$this->assertTrue($streamResult->getMetadata()->has('citations'));
$this->assertSame($citations, $streamResult->getMetadata()->get('citations'));
}
public function testNonArrayChunksAreNotProcessed()
{
$streamResult = new StreamResult((static function (): \Generator {
yield 'Hello ';
yield 'World';
})());
$streamResult->addListener(new StreamListener());
$chunks = iterator_to_array($streamResult->getContent());
$this->assertSame(['Hello ', 'World'], $chunks);
$this->assertFalse($streamResult->getMetadata()->has('search_results'));
$this->assertFalse($streamResult->getMetadata()->has('citations'));
}
public function testBothSearchResultsAndCitationsAreProcessed()
{
$searchResults = [['url' => 'https://example.com']];
$citations = ['https://example.com/1'];
$streamResult = new StreamResult((static function () use ($searchResults, $citations): \Generator {
yield ['search_results' => $searchResults];
yield 'Content';
yield ['citations' => $citations];
})());
$streamResult->addListener(new StreamListener());
$chunks = iterator_to_array($streamResult->getContent());
$this->assertSame(['Content'], $chunks);
$this->assertSame($searchResults, $streamResult->getMetadata()->get('search_results'));
$this->assertSame($citations, $streamResult->getMetadata()->get('citations'));
}
}