diff --git a/src/ai-bundle/config/options.php b/src/ai-bundle/config/options.php index 52dd9119c..af08abe5a 100644 --- a/src/ai-bundle/config/options.php +++ b/src/ai-bundle/config/options.php @@ -607,6 +607,7 @@ ->stringNode('collection_name')->cannotBeEmpty()->end() ->integerNode('dimensions')->end() ->stringNode('distance')->end() + ->booleanNode('async')->defaultFalse()->end() ->end() ->end() ->end() diff --git a/src/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index ad056c551..56dbe9ce5 100644 --- a/src/ai-bundle/src/AiBundle.php +++ b/src/ai-bundle/src/AiBundle.php @@ -1102,6 +1102,10 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $arguments[5] = $store['distance']; } + if (\array_key_exists('async', $store)) { + $arguments[6] = $store['async']; + } + $definition = new Definition(QdrantStore::class); $definition ->addTag('ai.store') diff --git a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php index 3e975cbdb..6d32a11d1 100644 --- a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php +++ b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php @@ -2942,6 +2942,7 @@ private function getFullConfig(): array 'collection_name' => 'foo', 'dimensions' => 768, 'distance' => 'Cosine', + 'async' => false, ], ], 'redis' => [ diff --git a/src/store/src/Bridge/Qdrant/Store.php b/src/store/src/Bridge/Qdrant/Store.php index 8d6fa146e..e0682b39e 100644 --- a/src/store/src/Bridge/Qdrant/Store.php +++ b/src/store/src/Bridge/Qdrant/Store.php @@ -33,6 +33,7 @@ public function __construct( private readonly string $collectionName, private readonly int $embeddingsDimension = 1536, private readonly string $embeddingsDistance = 'Cosine', + private readonly bool $async = false, ) { } @@ -58,9 +59,14 @@ public function setup(array $options = []): void public function add(VectorDocument ...$documents): void { - $this->request('PUT', \sprintf('collections/%s/points', $this->collectionName), [ - 'points' => array_map($this->convertToIndexableArray(...), $documents), - ]); + $this->request( + 'PUT', + \sprintf('collections/%s/points', $this->collectionName), + [ + 'points' => array_map($this->convertToIndexableArray(...), $documents), + ], + ['wait' => $this->async ? 'false' : 'true'], + ); } /** @@ -102,10 +108,11 @@ public function drop(): void /** * @param array $payload + * @param array $queryParameters * * @return array */ - private function request(string $method, string $endpoint, array $payload = []): array + private function request(string $method, string $endpoint, array $payload = [], array $queryParameters = []): array { $url = \sprintf('%s/%s', $this->endpointUrl, $endpoint); @@ -113,6 +120,7 @@ private function request(string $method, string $endpoint, array $payload = []): 'headers' => [ 'api-key' => $this->apiKey, ], + 'query' => $queryParameters, 'json' => $payload, ]); diff --git a/src/store/tests/Bridge/Qdrant/StoreTest.php b/src/store/tests/Bridge/Qdrant/StoreTest.php index b186a837e..37db2b5db 100644 --- a/src/store/tests/Bridge/Qdrant/StoreTest.php +++ b/src/store/tests/Bridge/Qdrant/StoreTest.php @@ -145,15 +145,52 @@ public function testStoreCannotAddOnInvalidResponse() $store = new Store($httpClient, 'http://127.0.0.1:6333', 'test', 'test'); $this->expectException(ClientException::class); - $this->expectExceptionMessage('HTTP 400 returned for "http://127.0.0.1:6333/collections/test/points".'); + $this->expectExceptionMessage('HTTP 400 returned for "http://127.0.0.1:6333/collections/test/points?wait=true".'); $this->expectExceptionCode(400); $store->add(new VectorDocument(Uuid::v4(), new Vector([0.1, 0.2, 0.3]))); } public function testStoreCanAdd() { - $httpClient = new MockHttpClient([ - new JsonMockResponse([ + $document = new VectorDocument(Uuid::v4(), new Vector([0.1, 0.2, 0.3])); + + $httpClient = new MockHttpClient(static function (string $method, string $url, array $options) use ($document): JsonMockResponse { + self::assertArrayHasKey('wait', $options['query']); + self::assertEquals('true', $options['query']['wait']); + + return new JsonMockResponse([ + 'time' => 0.002, + 'status' => 'ok', + 'result' => [ + 'points' => [ + [ + 'id' => (string) $document->id, + 'payload' => (array) $document->metadata, + 'vector' => $document->vector->getData(), + ], + ], + ], + ], [ + 'http_code' => 200, + ]); + }, 'http://127.0.0.1:6333'); + + $store = new Store($httpClient, 'http://127.0.0.1:6333', 'test', 'test'); + + $store->add($document); + + $this->assertSame(1, $httpClient->getRequestsCount()); + } + + public function testStoreCanAddAsynchronously() + { + $document = new VectorDocument(Uuid::v4(), new Vector([0.1, 0.2, 0.3])); + + $httpClient = new MockHttpClient(static function (string $method, string $url, array $options): JsonMockResponse { + self::assertArrayHasKey('wait', $options['query']); + self::assertEquals('false', $options['query']['wait']); + + return new JsonMockResponse([ 'time' => 0.002, 'status' => 'ok', 'result' => [ @@ -162,12 +199,12 @@ public function testStoreCanAdd() ], ], [ 'http_code' => 200, - ]), - ], 'http://127.0.0.1:6333'); + ]); + }, 'http://127.0.0.1:6333'); - $store = new Store($httpClient, 'http://127.0.0.1:6333', 'test', 'test'); + $store = new Store($httpClient, 'http://127.0.0.1:6333', 'test', 'test', async: true); - $store->add(new VectorDocument(Uuid::v4(), new Vector([0.1, 0.2, 0.3]))); + $store->add($document); $this->assertSame(1, $httpClient->getRequestsCount()); }