Skip to content

Commit 2ef922e

Browse files
committed
refactor(store): Qdrant improved for ScopingHttpClient
1 parent 3fd26c2 commit 2ef922e

File tree

10 files changed

+265
-91
lines changed

10 files changed

+265
-91
lines changed

examples/rag/qdrant.php

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
1818
use Symfony\AI\Platform\Message\Message;
1919
use Symfony\AI\Platform\Message\MessageBag;
20-
use Symfony\AI\Store\Bridge\Qdrant\Store;
20+
use Symfony\AI\Store\Bridge\Qdrant\StoreFactory;
2121
use Symfony\AI\Store\Document\Metadata;
2222
use Symfony\AI\Store\Document\TextDocument;
2323
use Symfony\AI\Store\Document\Vectorizer;
@@ -28,12 +28,7 @@
2828
require_once dirname(__DIR__).'/bootstrap.php';
2929

3030
// initialize the store
31-
$store = new Store(
32-
http_client(),
33-
env('QDRANT_HOST'),
34-
env('QDRANT_SERVICE_API_KEY'),
35-
'movies',
36-
);
31+
$store = StoreFactory::create('movies', env('QDRANT_HOST'), env('QDRANT_SERVICE_API_KEY'));
3732

3833
// initialize the collection (needs to be called before the indexer)
3934
$store->setup();

src/ai-bundle/config/store/qdrant.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,22 @@
1717
->useAttributeAsKey('name')
1818
->arrayPrototype()
1919
->children()
20-
->stringNode('endpoint')->cannotBeEmpty()->end()
21-
->stringNode('api_key')->cannotBeEmpty()->end()
22-
->stringNode('collection_name')->end()
20+
->stringNode('endpoint')->end()
21+
->stringNode('api_key')->end()
22+
->stringNode('collection_name')
23+
->info('The name of the store will be used if the "collection_name" is not set')
24+
->end()
25+
->stringNode('http_client')
26+
->defaultValue('http_client')
27+
->end()
2328
->integerNode('dimensions')
2429
->defaultValue(1536)
2530
->end()
2631
->stringNode('distance')
2732
->defaultValue('Cosine')
2833
->end()
29-
->booleanNode('async')->end()
34+
->booleanNode('async')
35+
->defaultValue(false)
36+
->end()
3037
->end()
3138
->end();

src/ai-bundle/src/AiBundle.php

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
use Symfony\AI\Store\Bridge\Postgres\Distance as PostgresDistance;
104104
use Symfony\AI\Store\Bridge\Postgres\Store as PostgresStore;
105105
use Symfony\AI\Store\Bridge\Qdrant\Store as QdrantStore;
106+
use Symfony\AI\Store\Bridge\Qdrant\StoreFactory;
106107
use Symfony\AI\Store\Bridge\Redis\Distance as RedisDistance;
107108
use Symfony\AI\Store\Bridge\Redis\Store as RedisStore;
108109
use Symfony\AI\Store\Bridge\S3Vectors\Store as S3VectorsStore;
@@ -1779,23 +1780,18 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
17791780
}
17801781

17811782
foreach ($stores as $name => $store) {
1782-
$arguments = [
1783-
new Reference('http_client'),
1784-
$store['endpoint'],
1785-
$store['api_key'],
1786-
$store['collection_name'] ?? $name,
1787-
$store['dimensions'],
1788-
$store['distance'],
1789-
];
1790-
1791-
if (\array_key_exists('async', $store)) {
1792-
$arguments[6] = $store['async'];
1793-
}
1794-
1795-
$definition = new Definition(QdrantStore::class);
1796-
$definition
1783+
$definition = (new Definition(QdrantStore::class))
1784+
->setFactory(StoreFactory::class.'::create')
17971785
->setLazy(true)
1798-
->setArguments($arguments)
1786+
->setArguments([
1787+
$store['collection_name'] ?? $name,
1788+
$store['endpoint'] ?? null,
1789+
$store['api_key'] ?? null,
1790+
new Reference($store['http_client']),
1791+
$store['dimensions'],
1792+
$store['distance'],
1793+
$store['async'],
1794+
])
17991795
->addTag('proxy', ['interface' => StoreInterface::class])
18001796
->addTag('proxy', ['interface' => ManagedStoreInterface::class])
18011797
->addTag('ai.store');

src/ai-bundle/tests/DependencyInjection/AiBundleTest.php

Lines changed: 108 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
use Symfony\AI\Store\Bridge\Postgres\Distance as PostgresDistance;
6767
use Symfony\AI\Store\Bridge\Postgres\Store as PostgresStore;
6868
use Symfony\AI\Store\Bridge\Qdrant\Store as QdrantStore;
69+
use Symfony\AI\Store\Bridge\Qdrant\StoreFactory;
6970
use Symfony\AI\Store\Bridge\Redis\Distance as RedisDistance;
7071
use Symfony\AI\Store\Bridge\Redis\Store as RedisStore;
7172
use Symfony\AI\Store\Bridge\Supabase\Store as SupabaseStore;
@@ -2667,17 +2668,19 @@ public function testQdrantStoreCanBeConfigured()
26672668
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
26682669

26692670
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2671+
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
26702672
$this->assertSame(QdrantStore::class, $definition->getClass());
2671-
26722673
$this->assertTrue($definition->isLazy());
2673-
$this->assertCount(6, $definition->getArguments());
2674-
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
2675-
$this->assertSame('http_client', (string) $definition->getArgument(0));
2674+
2675+
$this->assertCount(7, $definition->getArguments());
2676+
$this->assertSame('my_qdrant_store', $definition->getArgument(0));
26762677
$this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1));
26772678
$this->assertSame('test', $definition->getArgument(2));
2678-
$this->assertSame('my_qdrant_store', $definition->getArgument(3));
2679+
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
2680+
$this->assertSame('http_client', (string) $definition->getArgument(3));
26792681
$this->assertSame(1536, $definition->getArgument(4));
26802682
$this->assertSame('Cosine', $definition->getArgument(5));
2683+
$this->assertFalse($definition->getArgument(6));
26812684

26822685
$this->assertTrue($definition->hasTag('proxy'));
26832686
$this->assertSame([
@@ -2691,10 +2694,7 @@ public function testQdrantStoreCanBeConfigured()
26912694
$this->assertTrue($container->hasAlias('.'.StoreInterface::class.' $qdrant_my_qdrant_store'));
26922695
$this->assertTrue($container->hasAlias(StoreInterface::class.' $qdrantMyQdrantStore'));
26932696
$this->assertTrue($container->hasAlias(StoreInterface::class));
2694-
}
26952697

2696-
public function testQdrantStoreWithCustomCollectionCanBeConfigured()
2697-
{
26982698
$container = $this->buildContainer([
26992699
'ai' => [
27002700
'store' => [
@@ -2712,17 +2712,19 @@ public function testQdrantStoreWithCustomCollectionCanBeConfigured()
27122712
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
27132713

27142714
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2715+
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
27152716
$this->assertSame(QdrantStore::class, $definition->getClass());
2716-
27172717
$this->assertTrue($definition->isLazy());
2718-
$this->assertCount(6, $definition->getArguments());
2719-
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
2720-
$this->assertSame('http_client', (string) $definition->getArgument(0));
2718+
2719+
$this->assertCount(7, $definition->getArguments());
2720+
$this->assertSame('foo', $definition->getArgument(0));
27212721
$this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1));
27222722
$this->assertSame('test', $definition->getArgument(2));
2723-
$this->assertSame('foo', $definition->getArgument(3));
2723+
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
2724+
$this->assertSame('http_client', (string) $definition->getArgument(3));
27242725
$this->assertSame(1536, $definition->getArgument(4));
27252726
$this->assertSame('Cosine', $definition->getArgument(5));
2727+
$this->assertFalse($definition->getArgument(6));
27262728

27272729
$this->assertTrue($definition->hasTag('proxy'));
27282730
$this->assertSame([
@@ -2736,10 +2738,7 @@ public function testQdrantStoreWithCustomCollectionCanBeConfigured()
27362738
$this->assertTrue($container->hasAlias('.'.StoreInterface::class.' $qdrant_my_qdrant_store'));
27372739
$this->assertTrue($container->hasAlias(StoreInterface::class.' $qdrantMyQdrantStore'));
27382740
$this->assertTrue($container->hasAlias(StoreInterface::class));
2739-
}
27402741

2741-
public function testQdrantStoreWithAsyncCanBeConfigured()
2742-
{
27432742
$container = $this->buildContainer([
27442743
'ai' => [
27452744
'store' => [
@@ -2758,15 +2757,62 @@ public function testQdrantStoreWithAsyncCanBeConfigured()
27582757
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
27592758

27602759
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2760+
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
27612761
$this->assertSame(QdrantStore::class, $definition->getClass());
2762+
$this->assertTrue($definition->isLazy());
2763+
2764+
$this->assertCount(7, $definition->getArguments());
2765+
$this->assertSame('foo', $definition->getArgument(0));
2766+
$this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1));
2767+
$this->assertSame('test', $definition->getArgument(2));
2768+
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
2769+
$this->assertSame('http_client', (string) $definition->getArgument(3));
2770+
$this->assertSame(1536, $definition->getArgument(4));
2771+
$this->assertSame('Cosine', $definition->getArgument(5));
2772+
$this->assertTrue($definition->getArgument(6));
2773+
2774+
$this->assertTrue($definition->hasTag('proxy'));
2775+
$this->assertSame([
2776+
['interface' => StoreInterface::class],
2777+
['interface' => ManagedStoreInterface::class],
2778+
], $definition->getTag('proxy'));
2779+
$this->assertTrue($definition->hasTag('ai.store'));
2780+
2781+
$this->assertTrue($container->hasAlias('.'.StoreInterface::class.' $my_qdrant_store'));
2782+
$this->assertTrue($container->hasAlias(StoreInterface::class.' $myQdrantStore'));
2783+
$this->assertTrue($container->hasAlias('.'.StoreInterface::class.' $qdrant_my_qdrant_store'));
2784+
$this->assertTrue($container->hasAlias(StoreInterface::class.' $qdrantMyQdrantStore'));
2785+
$this->assertTrue($container->hasAlias(StoreInterface::class));
2786+
2787+
$container = $this->buildContainer([
2788+
'ai' => [
2789+
'store' => [
2790+
'qdrant' => [
2791+
'my_qdrant_store' => [
2792+
'endpoint' => 'http://127.0.0.1:8000',
2793+
'api_key' => 'test',
2794+
'collection_name' => 'foo',
2795+
'async' => true,
2796+
'http_client' => 'http_client',
2797+
],
2798+
],
2799+
],
2800+
],
2801+
]);
2802+
2803+
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
27622804

2805+
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2806+
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2807+
$this->assertSame(QdrantStore::class, $definition->getClass());
27632808
$this->assertTrue($definition->isLazy());
2809+
27642810
$this->assertCount(7, $definition->getArguments());
2765-
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
2766-
$this->assertSame('http_client', (string) $definition->getArgument(0));
2811+
$this->assertSame('foo', $definition->getArgument(0));
27672812
$this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1));
27682813
$this->assertSame('test', $definition->getArgument(2));
2769-
$this->assertSame('foo', $definition->getArgument(3));
2814+
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
2815+
$this->assertSame('http_client', (string) $definition->getArgument(3));
27702816
$this->assertSame(1536, $definition->getArgument(4));
27712817
$this->assertSame('Cosine', $definition->getArgument(5));
27722818
$this->assertTrue($definition->getArgument(6));
@@ -2783,6 +2829,49 @@ public function testQdrantStoreWithAsyncCanBeConfigured()
27832829
$this->assertTrue($container->hasAlias('.'.StoreInterface::class.' $qdrant_my_qdrant_store'));
27842830
$this->assertTrue($container->hasAlias(StoreInterface::class.' $qdrantMyQdrantStore'));
27852831
$this->assertTrue($container->hasAlias(StoreInterface::class));
2832+
2833+
$container = $this->buildContainer([
2834+
'ai' => [
2835+
'store' => [
2836+
'qdrant' => [
2837+
'my_qdrant_store' => [
2838+
'collection_name' => 'foo',
2839+
'http_client' => 'scoped_http_client',
2840+
],
2841+
],
2842+
],
2843+
],
2844+
]);
2845+
2846+
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
2847+
2848+
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2849+
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2850+
$this->assertSame(QdrantStore::class, $definition->getClass());
2851+
$this->assertTrue($definition->isLazy());
2852+
2853+
$this->assertCount(7, $definition->getArguments());
2854+
$this->assertSame('foo', $definition->getArgument(0));
2855+
$this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1));
2856+
$this->assertNull($definition->getArgument(2));
2857+
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
2858+
$this->assertSame('scoped_http_client', (string) $definition->getArgument(3));
2859+
$this->assertSame(1536, $definition->getArgument(4));
2860+
$this->assertSame('Cosine', $definition->getArgument(5));
2861+
$this->assertFalse($definition->getArgument(6));
2862+
2863+
$this->assertTrue($definition->hasTag('proxy'));
2864+
$this->assertSame([
2865+
['interface' => StoreInterface::class],
2866+
['interface' => ManagedStoreInterface::class],
2867+
], $definition->getTag('proxy'));
2868+
$this->assertTrue($definition->hasTag('ai.store'));
2869+
2870+
$this->assertTrue($container->hasAlias('.'.StoreInterface::class.' $my_qdrant_store'));
2871+
$this->assertTrue($container->hasAlias(StoreInterface::class.' $myQdrantStore'));
2872+
$this->assertTrue($container->hasAlias('.'.StoreInterface::class.' $qdrant_my_qdrant_store'));
2873+
$this->assertTrue($container->hasAlias(StoreInterface::class.' $qdrantMyQdrantStore'));
2874+
$this->assertTrue($container->hasAlias(StoreInterface::class));
27862875
}
27872876

27882877
public function testRedisStoreCanBeConfigured()

src/store/src/Bridge/Qdrant/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
0.6
5+
---
6+
7+
* [BC BREAK] The `endpointUrl` parameter for `Store` has been removed
8+
* [BC BREAK] The `apiKey` parameter for `Store` has been removed
9+
410
0.1
511
---
612

src/store/src/Bridge/Qdrant/Store.php

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ final class Store implements ManagedStoreInterface, StoreInterface
3030
{
3131
public function __construct(
3232
private readonly HttpClientInterface $httpClient,
33-
private readonly string $endpointUrl,
34-
#[\SensitiveParameter] private readonly string $apiKey,
3533
private readonly string $collectionName,
3634
private readonly int $embeddingsDimension = 1536,
3735
private readonly string $embeddingsDistance = 'Cosine',
@@ -69,7 +67,11 @@ public function add(VectorDocument|array $documents): void
6967
'PUT',
7068
\sprintf('collections/%s/points', $this->collectionName),
7169
[
72-
'points' => array_map($this->convertToIndexableArray(...), $documents),
70+
'points' => array_map(static fn (VectorDocument $document): array => [
71+
'id' => $document->getId(),
72+
'vector' => $document->getVector()->getData(),
73+
'payload' => $document->getMetadata()->getArrayCopy(),
74+
], $documents),
7375
],
7476
['wait' => $this->async ? 'false' : 'true'],
7577
);
@@ -147,31 +149,14 @@ public function drop(array $options = []): void
147149
*/
148150
private function request(string $method, string $endpoint, array $payload = [], array $queryParameters = []): array
149151
{
150-
$url = \sprintf('%s/%s', $this->endpointUrl, $endpoint);
151-
152-
$response = $this->httpClient->request($method, $url, [
153-
'headers' => [
154-
'api-key' => $this->apiKey,
155-
],
152+
$response = $this->httpClient->request($method, $endpoint, [
156153
'query' => $queryParameters,
157154
'json' => $payload,
158155
]);
159156

160157
return $response->toArray();
161158
}
162159

163-
/**
164-
* @return array<string, mixed>
165-
*/
166-
private function convertToIndexableArray(VectorDocument $document): array
167-
{
168-
return [
169-
'id' => $document->getId(),
170-
'vector' => $document->getVector()->getData(),
171-
'payload' => $document->getMetadata()->getArrayCopy(),
172-
];
173-
}
174-
175160
/**
176161
* @param array<string, mixed> $data
177162
*/

0 commit comments

Comments
 (0)