mirror of
https://github.com/symfony/ai-store.git
synced 2026-03-23 23:22:17 +01:00
refactor: rework LLM Chain into Symfony AI
This commit is contained in:
6
.gitattributes
vendored
Normal file
6
.gitattributes
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/.github export-ignore
|
||||
/tests export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
phpstan.dist.neon export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2025-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
70
composer.json
Normal file
70
composer.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "symfony/ai-store",
|
||||
"type": "library",
|
||||
"description": "PHP library for abstracting interaction with data stores in AI applications.",
|
||||
"keywords": [
|
||||
"ai",
|
||||
"mongodb",
|
||||
"pinecone",
|
||||
"chromadb"
|
||||
],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christopher Hertel",
|
||||
"email": "mail@christopher-hertel.de"
|
||||
},
|
||||
{
|
||||
"name": "Oskar Stark",
|
||||
"email": "oskarstark@googlemail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"ext-fileinfo": "*",
|
||||
"psr/log": "^3.0",
|
||||
"symfony/ai-platform": "@dev",
|
||||
"symfony/clock": "^6.4 || ^7.1",
|
||||
"symfony/http-client": "^6.4 || ^7.1",
|
||||
"symfony/uid": "^6.4 || ^7.1",
|
||||
"webmozart/assert": "^1.11"
|
||||
},
|
||||
"conflict": {
|
||||
"mongodb/mongodb": "<1.21"
|
||||
},
|
||||
"require-dev": {
|
||||
"codewithkyrian/chromadb-php": "^0.2.1 || ^0.3 || ^0.4",
|
||||
"mongodb/mongodb": "^1.21",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpstan/phpstan-webmozart-assert": "^2.0",
|
||||
"phpunit/phpunit": "^11.5",
|
||||
"probots-io/pinecone-php": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"codewithkyrian/chromadb-php": "For using the ChromaDB as retrieval vector store.",
|
||||
"mongodb/mongodb": "For using MongoDB Atlas as retrieval vector store.",
|
||||
"probots-io/pinecone-php": "For using the Pinecone as retrieval vector store."
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/ai",
|
||||
"url": "https://github.com/symfony/ai"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\AI\\Store\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Symfony\\AI\\Store\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
{"type": "path", "url": "../platform"}
|
||||
]
|
||||
}
|
||||
6
phpstan.dist.neon
Normal file
6
phpstan.dist.neon
Normal file
@@ -0,0 +1,6 @@
|
||||
parameters:
|
||||
level: 6
|
||||
paths:
|
||||
- src/
|
||||
- tests/
|
||||
treatPhpDocTypesAsCertain: false
|
||||
24
phpunit.xml.dist
Normal file
24
phpunit.xml.dist
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.5/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
colors="true"
|
||||
executionOrder="depends,defects"
|
||||
requireCoverageMetadata="true"
|
||||
beStrictAboutCoverageMetadata="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true">
|
||||
<testsuites>
|
||||
<testsuite name="default">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<source ignoreIndirectDeprecations="true" restrictNotices="true" restrictWarnings="true">
|
||||
<include>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
21
src/Document/Metadata.php
Normal file
21
src/Document/Metadata.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?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\Store\Document;
|
||||
|
||||
/**
|
||||
* @template-extends \ArrayObject<string, mixed>
|
||||
*
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class Metadata extends \ArrayObject
|
||||
{
|
||||
}
|
||||
29
src/Document/TextDocument.php
Normal file
29
src/Document/TextDocument.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?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\Store\Document;
|
||||
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final readonly class TextDocument
|
||||
{
|
||||
public function __construct(
|
||||
public Uuid $id,
|
||||
public string $content,
|
||||
public Metadata $metadata = new Metadata(),
|
||||
) {
|
||||
Assert::stringNotEmpty(trim($this->content));
|
||||
}
|
||||
}
|
||||
29
src/Document/VectorDocument.php
Normal file
29
src/Document/VectorDocument.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?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\Store\Document;
|
||||
|
||||
use Symfony\AI\Platform\Vector\VectorInterface;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final readonly class VectorDocument
|
||||
{
|
||||
public function __construct(
|
||||
public Uuid $id,
|
||||
public VectorInterface $vector,
|
||||
public Metadata $metadata = new Metadata(),
|
||||
public ?float $score = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
97
src/Embedder.php
Normal file
97
src/Embedder.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?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\Store;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\AI\Platform\Capability;
|
||||
use Symfony\AI\Platform\Model;
|
||||
use Symfony\AI\Platform\PlatformInterface;
|
||||
use Symfony\AI\Store\Document\TextDocument;
|
||||
use Symfony\AI\Store\Document\VectorDocument;
|
||||
use Symfony\Component\Clock\Clock;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final readonly class Embedder
|
||||
{
|
||||
private ClockInterface $clock;
|
||||
|
||||
public function __construct(
|
||||
private PlatformInterface $platform,
|
||||
private Model $model,
|
||||
private StoreInterface $store,
|
||||
?ClockInterface $clock = null,
|
||||
private LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
$this->clock = $clock ?? Clock::get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TextDocument|TextDocument[] $documents
|
||||
*/
|
||||
public function embed(TextDocument|array $documents, int $chunkSize = 0, int $sleep = 0): void
|
||||
{
|
||||
if ($documents instanceof TextDocument) {
|
||||
$documents = [$documents];
|
||||
}
|
||||
|
||||
if ([] === $documents) {
|
||||
$this->logger->debug('No documents to embed');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$chunks = 0 !== $chunkSize ? array_chunk($documents, $chunkSize) : [$documents];
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
$this->store->add(...$this->createVectorDocuments($chunk));
|
||||
|
||||
if (0 !== $sleep) {
|
||||
$this->clock->sleep($sleep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TextDocument[] $documents
|
||||
*
|
||||
* @return VectorDocument[]
|
||||
*/
|
||||
private function createVectorDocuments(array $documents): array
|
||||
{
|
||||
if ($this->model->supports(Capability::INPUT_MULTIPLE)) {
|
||||
$response = $this->platform->request($this->model, array_map(fn (TextDocument $document) => $document->content, $documents));
|
||||
|
||||
$vectors = $response->getContent();
|
||||
} else {
|
||||
$responses = [];
|
||||
foreach ($documents as $document) {
|
||||
$responses[] = $this->platform->request($this->model, $document->content);
|
||||
}
|
||||
|
||||
$vectors = [];
|
||||
foreach ($responses as $response) {
|
||||
$vectors = array_merge($vectors, $response->getContent());
|
||||
}
|
||||
}
|
||||
|
||||
$vectorDocuments = [];
|
||||
foreach ($documents as $i => $document) {
|
||||
$vectorDocuments[] = new VectorDocument($document->id, $vectors[$i], $document->metadata);
|
||||
}
|
||||
|
||||
return $vectorDocuments;
|
||||
}
|
||||
}
|
||||
19
src/Exception/ExceptionInterface.php
Normal file
19
src/Exception/ExceptionInterface.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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\Store\Exception;
|
||||
|
||||
/**
|
||||
* @author Oskar Stark <oskarstark@googlemail.com>
|
||||
*/
|
||||
interface ExceptionInterface extends \Throwable
|
||||
{
|
||||
}
|
||||
19
src/Exception/InvalidArgumentException.php
Normal file
19
src/Exception/InvalidArgumentException.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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\Store\Exception;
|
||||
|
||||
/**
|
||||
* @author Oskar Stark <oskarstark@googlemail.com>
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
19
src/Exception/RuntimeException.php
Normal file
19
src/Exception/RuntimeException.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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\Store\Exception;
|
||||
|
||||
/**
|
||||
* @author Oskar Stark <oskarstark@googlemail.com>
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
23
src/InitializableStoreInterface.php
Normal file
23
src/InitializableStoreInterface.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?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\Store;
|
||||
|
||||
/**
|
||||
* @author Oskar Stark <oskarstark@googlemail.com>
|
||||
*/
|
||||
interface InitializableStoreInterface extends StoreInterface
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $options
|
||||
*/
|
||||
public function initialize(array $options = []): void;
|
||||
}
|
||||
22
src/StoreInterface.php
Normal file
22
src/StoreInterface.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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\Store;
|
||||
|
||||
use Symfony\AI\Store\Document\VectorDocument;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
interface StoreInterface
|
||||
{
|
||||
public function add(VectorDocument ...$documents): void;
|
||||
}
|
||||
28
src/VectorStoreInterface.php
Normal file
28
src/VectorStoreInterface.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?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\Store;
|
||||
|
||||
use Symfony\AI\Platform\Vector\Vector;
|
||||
use Symfony\AI\Store\Document\VectorDocument;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
interface VectorStoreInterface extends StoreInterface
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*
|
||||
* @return VectorDocument[]
|
||||
*/
|
||||
public function query(Vector $vector, array $options = [], ?float $minScore = null): array;
|
||||
}
|
||||
57
tests/Double/PlatformTestHandler.php
Normal file
57
tests/Double/PlatformTestHandler.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?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\Store\Tests\Double;
|
||||
|
||||
use Symfony\AI\Platform\Model;
|
||||
use Symfony\AI\Platform\ModelClientInterface;
|
||||
use Symfony\AI\Platform\Platform;
|
||||
use Symfony\AI\Platform\Response\ResponseInterface;
|
||||
use Symfony\AI\Platform\Response\ResponseInterface as LlmResponse;
|
||||
use Symfony\AI\Platform\Response\VectorResponse;
|
||||
use Symfony\AI\Platform\ResponseConverterInterface;
|
||||
use Symfony\AI\Platform\Vector\Vector;
|
||||
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface as HttpResponse;
|
||||
|
||||
final class PlatformTestHandler implements ModelClientInterface, ResponseConverterInterface
|
||||
{
|
||||
public int $createCalls = 0;
|
||||
|
||||
public function __construct(
|
||||
private readonly ?ResponseInterface $create = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function createPlatform(?ResponseInterface $create = null): Platform
|
||||
{
|
||||
$handler = new self($create);
|
||||
|
||||
return new Platform([$handler], [$handler]);
|
||||
}
|
||||
|
||||
public function supports(Model $model): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function request(Model $model, array|string|object $payload, array $options = []): HttpResponse
|
||||
{
|
||||
++$this->createCalls;
|
||||
|
||||
return new MockResponse();
|
||||
}
|
||||
|
||||
public function convert(HttpResponse $response, array $options = []): LlmResponse
|
||||
{
|
||||
return $this->create ?? new VectorResponse(new Vector([1, 2, 3]));
|
||||
}
|
||||
}
|
||||
31
tests/Double/TestStore.php
Normal file
31
tests/Double/TestStore.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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\Store\Tests\Double;
|
||||
|
||||
use Symfony\AI\Store\Document\VectorDocument;
|
||||
use Symfony\AI\Store\StoreInterface;
|
||||
|
||||
final class TestStore implements StoreInterface
|
||||
{
|
||||
/**
|
||||
* @var VectorDocument[]
|
||||
*/
|
||||
public array $documents = [];
|
||||
|
||||
public int $addCalls = 0;
|
||||
|
||||
public function add(VectorDocument ...$documents): void
|
||||
{
|
||||
++$this->addCalls;
|
||||
$this->documents = array_merge($this->documents, $documents);
|
||||
}
|
||||
}
|
||||
138
tests/EmbedderTest.php
Normal file
138
tests/EmbedderTest.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?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\Store\Tests;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Medium;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\AI\Platform\Bridge\OpenAI\Embeddings;
|
||||
use Symfony\AI\Platform\Message\ToolCallMessage;
|
||||
use Symfony\AI\Platform\Platform;
|
||||
use Symfony\AI\Platform\Response\AsyncResponse;
|
||||
use Symfony\AI\Platform\Response\ToolCall;
|
||||
use Symfony\AI\Platform\Response\VectorResponse;
|
||||
use Symfony\AI\Platform\Vector\Vector;
|
||||
use Symfony\AI\Store\Document\Metadata;
|
||||
use Symfony\AI\Store\Document\TextDocument;
|
||||
use Symfony\AI\Store\Document\VectorDocument;
|
||||
use Symfony\AI\Store\Embedder;
|
||||
use Symfony\AI\Store\Tests\Double\PlatformTestHandler;
|
||||
use Symfony\AI\Store\Tests\Double\TestStore;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[CoversClass(Embedder::class)]
|
||||
#[Medium]
|
||||
#[UsesClass(TextDocument::class)]
|
||||
#[UsesClass(Vector::class)]
|
||||
#[UsesClass(VectorDocument::class)]
|
||||
#[UsesClass(ToolCallMessage::class)]
|
||||
#[UsesClass(ToolCall::class)]
|
||||
#[UsesClass(Embeddings::class)]
|
||||
#[UsesClass(Platform::class)]
|
||||
#[UsesClass(AsyncResponse::class)]
|
||||
#[UsesClass(VectorResponse::class)]
|
||||
final class EmbedderTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function embedSingleDocument(): void
|
||||
{
|
||||
$document = new TextDocument($id = Uuid::v4(), 'Test content');
|
||||
$vector = new Vector([0.1, 0.2, 0.3]);
|
||||
|
||||
$embedder = new Embedder(
|
||||
PlatformTestHandler::createPlatform(new VectorResponse($vector)),
|
||||
new Embeddings(),
|
||||
$store = new TestStore(),
|
||||
new MockClock(),
|
||||
);
|
||||
|
||||
$embedder->embed($document);
|
||||
|
||||
self::assertCount(1, $store->documents);
|
||||
self::assertInstanceOf(VectorDocument::class, $store->documents[0]);
|
||||
self::assertSame($id, $store->documents[0]->id);
|
||||
self::assertSame($vector, $store->documents[0]->vector);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function embedEmptyDocumentList(): void
|
||||
{
|
||||
$logger = self::createMock(LoggerInterface::class);
|
||||
$logger->expects(self::once())->method('debug')->with('No documents to embed');
|
||||
|
||||
$embedder = new Embedder(
|
||||
PlatformTestHandler::createPlatform(),
|
||||
new Embeddings(),
|
||||
$store = new TestStore(),
|
||||
new MockClock(),
|
||||
$logger,
|
||||
);
|
||||
|
||||
$embedder->embed([]);
|
||||
|
||||
self::assertSame([], $store->documents);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function embedDocumentWithMetadata(): void
|
||||
{
|
||||
$metadata = new Metadata(['key' => 'value']);
|
||||
$document = new TextDocument($id = Uuid::v4(), 'Test content', $metadata);
|
||||
$vector = new Vector([0.1, 0.2, 0.3]);
|
||||
|
||||
$embedder = new Embedder(
|
||||
PlatformTestHandler::createPlatform(new VectorResponse($vector)),
|
||||
new Embeddings(),
|
||||
$store = new TestStore(),
|
||||
new MockClock(),
|
||||
);
|
||||
|
||||
$embedder->embed($document);
|
||||
|
||||
self::assertSame(1, $store->addCalls);
|
||||
self::assertCount(1, $store->documents);
|
||||
self::assertInstanceOf(VectorDocument::class, $store->documents[0]);
|
||||
self::assertSame($id, $store->documents[0]->id);
|
||||
self::assertSame($vector, $store->documents[0]->vector);
|
||||
self::assertSame(['key' => 'value'], $store->documents[0]->metadata->getArrayCopy());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function embedWithSleep(): void
|
||||
{
|
||||
$vector1 = new Vector([0.1, 0.2, 0.3]);
|
||||
$vector2 = new Vector([0.4, 0.5, 0.6]);
|
||||
|
||||
$document1 = new TextDocument(Uuid::v4(), 'Test content 1');
|
||||
$document2 = new TextDocument(Uuid::v4(), 'Test content 2');
|
||||
|
||||
$embedder = new Embedder(
|
||||
PlatformTestHandler::createPlatform(new VectorResponse($vector1, $vector2)),
|
||||
new Embeddings(),
|
||||
$store = new TestStore(),
|
||||
$clock = new MockClock('2024-01-01 00:00:00'),
|
||||
);
|
||||
|
||||
$embedder->embed(
|
||||
documents: [$document1, $document2],
|
||||
sleep: 3
|
||||
);
|
||||
|
||||
self::assertSame(1, $store->addCalls);
|
||||
self::assertCount(2, $store->documents);
|
||||
self::assertSame('2024-01-01 00:00:03', $clock->now()->format('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user