Skip to content

Commit

Permalink
tests: Add ChainProcessorTest (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
OskarStark authored Oct 3, 2024
1 parent 62238b5 commit a63da44
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 0 deletions.
23 changes: 23 additions & 0 deletions tests/Double/ConfigurableResponseFormatFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Tests\Double;

use PhpLlm\LlmChain\StructuredOutput\ResponseFormatFactoryInterface;

final readonly class ConfigurableResponseFormatFactory implements ResponseFormatFactoryInterface
{
/**
* @param array<mixed> $responseFormat
*/
public function __construct(
private array $responseFormat = [],
) {
}

public function create(string $responseClass): array
{
return $this->responseFormat;
}
}
10 changes: 10 additions & 0 deletions tests/Fixture/SomeStructure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Tests\Fixture;

final class SomeStructure
{
public string $some;
}
119 changes: 119 additions & 0 deletions tests/ToolBox/ChainProcessorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Tests\ToolBox;

use PhpLlm\LlmChain\Chain\Input;
use PhpLlm\LlmChain\Chain\Output;
use PhpLlm\LlmChain\Exception\MissingModelSupport;
use PhpLlm\LlmChain\LanguageModel;
use PhpLlm\LlmChain\Message\MessageBag;
use PhpLlm\LlmChain\Response\Choice;
use PhpLlm\LlmChain\Response\Response;
use PhpLlm\LlmChain\StructuredOutput\ChainProcessor;
use PhpLlm\LlmChain\Tests\Double\ConfigurableResponseFormatFactory;
use PhpLlm\LlmChain\Tests\Fixture\SomeStructure;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\SerializerInterface;

#[CoversClass(ChainProcessor::class)]
final class ChainProcessorTest extends TestCase
{
#[Test]
public function processInputWithOutputStructure(): void
{
$responseFormatFactory = new ConfigurableResponseFormatFactory(['some' => 'format']);
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
$chainProcessor = new ChainProcessor($responseFormatFactory, $serializer);

$llm = $this->createMock(LanguageModel::class);
$llm->method('supportsStructuredOutput')->willReturn(true);

$input = new Input($llm, new MessageBag(), ['output_structure' => 'SomeStructure']);

$chainProcessor->processInput($input);

self::assertSame(['response_format' => ['some' => 'format']], $input->getOptions());
}

#[Test]
public function processInputWithoutOutputStructure(): void
{
$responseFormatFactory = new ConfigurableResponseFormatFactory();
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
$chainProcessor = new ChainProcessor($responseFormatFactory, $serializer);

$llm = $this->createMock(LanguageModel::class);
$input = new Input($llm, new MessageBag(), []);

$chainProcessor->processInput($input);

self::assertSame([], $input->getOptions());
}

#[Test]
public function processInputThrowsExceptionWhenLlmDoesNotSupportStructuredOutput(): void
{
$this->expectException(MissingModelSupport::class);

$responseFormatFactory = new ConfigurableResponseFormatFactory();
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
$chainProcessor = new ChainProcessor($responseFormatFactory, $serializer);

$llm = $this->createMock(LanguageModel::class);
$llm->method('supportsStructuredOutput')->willReturn(false);

$input = new Input($llm, new MessageBag(), ['output_structure' => 'SomeStructure']);

$chainProcessor->processInput($input);
}

#[Test]
public function processOutputWithResponseFormat(): void
{
$responseFormatFactory = new ConfigurableResponseFormatFactory(['some' => 'format']);
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
$chainProcessor = new ChainProcessor($responseFormatFactory, $serializer);

$llm = $this->createMock(LanguageModel::class);
$llm->method('supportsStructuredOutput')->willReturn(true);

$options = ['output_structure' => SomeStructure::class];
$input = new Input($llm, new MessageBag(), $options);
$chainProcessor->processInput($input);

$choice = new Choice('{"some": "data"}');
$response = new Response($choice);

$output = new Output($llm, $response, new MessageBag(), $options);

$result = $chainProcessor->processOutput($output);

self::assertInstanceOf(SomeStructure::class, $result);
self::assertSame('data', $result->some);
}

#[Test]
public function processOutputWithoutResponseFormat(): void
{
$responseFormatFactory = new ConfigurableResponseFormatFactory();
$serializer = $this->createMock(SerializerInterface::class);
$chainProcessor = new ChainProcessor($responseFormatFactory, $serializer);

$llm = $this->createMock(LanguageModel::class);
$choice = new Choice('');
$response = new Response($choice);

$output = new Output($llm, $response, new MessageBag(), []);

$result = $chainProcessor->processOutput($output);

self::assertNull($result);
}
}

0 comments on commit a63da44

Please sign in to comment.