Skip to content

Commit 6cc164c

Browse files
committed
feature #1633 [Store][Qdrant] Support for ScopingHttpClient improved (Guikingone)
This PR was merged into the main branch. Discussion ---------- [Store][Qdrant] Support for `ScopingHttpClient` improved | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Docs? | yes | Issues | -- | BC BREAK | Yes | License | MIT Commits ------- 16ef9fd refactor(store): Qdrant improved for ScopingHttpClient
2 parents d0d90ba + 16ef9fd commit 6cc164c

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;
@@ -1780,23 +1781,18 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
17801781
}
17811782

17821783
foreach ($stores as $name => $store) {
1783-
$arguments = [
1784-
new Reference('http_client'),
1785-
$store['endpoint'],
1786-
$store['api_key'],
1787-
$store['collection_name'] ?? $name,
1788-
$store['dimensions'],
1789-
$store['distance'],
1790-
];
1791-
1792-
if (\array_key_exists('async', $store)) {
1793-
$arguments[6] = $store['async'];
1794-
}
1795-
1796-
$definition = new Definition(QdrantStore::class);
1797-
$definition
1784+
$definition = (new Definition(QdrantStore::class))
1785+
->setFactory(StoreFactory::class.'::create')
17981786
->setLazy(true)
1799-
->setArguments($arguments)
1787+
->setArguments([
1788+
$store['collection_name'] ?? $name,
1789+
$store['endpoint'] ?? null,
1790+
$store['api_key'] ?? null,
1791+
new Reference($store['http_client']),
1792+
$store['dimensions'],
1793+
$store['distance'],
1794+
$store['async'],
1795+
])
18001796
->addTag('proxy', ['interface' => StoreInterface::class])
18011797
->addTag('proxy', ['interface' => ManagedStoreInterface::class])
18021798
->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;
@@ -2668,17 +2669,19 @@ public function testQdrantStoreCanBeConfigured()
26682669
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
26692670

26702671
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2672+
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
26712673
$this->assertSame(QdrantStore::class, $definition->getClass());
2672-
26732674
$this->assertTrue($definition->isLazy());
2674-
$this->assertCount(6, $definition->getArguments());
2675-
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
2676-
$this->assertSame('http_client', (string) $definition->getArgument(0));
2675+
2676+
$this->assertCount(7, $definition->getArguments());
2677+
$this->assertSame('my_qdrant_store', $definition->getArgument(0));
26772678
$this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1));
26782679
$this->assertSame('test', $definition->getArgument(2));
2679-
$this->assertSame('my_qdrant_store', $definition->getArgument(3));
2680+
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
2681+
$this->assertSame('http_client', (string) $definition->getArgument(3));
26802682
$this->assertSame(1536, $definition->getArgument(4));
26812683
$this->assertSame('Cosine', $definition->getArgument(5));
2684+
$this->assertFalse($definition->getArgument(6));
26822685

26832686
$this->assertTrue($definition->hasTag('proxy'));
26842687
$this->assertSame([
@@ -2692,10 +2695,7 @@ public function testQdrantStoreCanBeConfigured()
26922695
$this->assertTrue($container->hasAlias('.'.StoreInterface::class.' $qdrant_my_qdrant_store'));
26932696
$this->assertTrue($container->hasAlias(StoreInterface::class.' $qdrantMyQdrantStore'));
26942697
$this->assertTrue($container->hasAlias(StoreInterface::class));
2695-
}
26962698

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

27152715
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2716+
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
27162717
$this->assertSame(QdrantStore::class, $definition->getClass());
2717-
27182718
$this->assertTrue($definition->isLazy());
2719-
$this->assertCount(6, $definition->getArguments());
2720-
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
2721-
$this->assertSame('http_client', (string) $definition->getArgument(0));
2719+
2720+
$this->assertCount(7, $definition->getArguments());
2721+
$this->assertSame('foo', $definition->getArgument(0));
27222722
$this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1));
27232723
$this->assertSame('test', $definition->getArgument(2));
2724-
$this->assertSame('foo', $definition->getArgument(3));
2724+
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
2725+
$this->assertSame('http_client', (string) $definition->getArgument(3));
27252726
$this->assertSame(1536, $definition->getArgument(4));
27262727
$this->assertSame('Cosine', $definition->getArgument(5));
2728+
$this->assertFalse($definition->getArgument(6));
27272729

27282730
$this->assertTrue($definition->hasTag('proxy'));
27292731
$this->assertSame([
@@ -2737,10 +2739,7 @@ public function testQdrantStoreWithCustomCollectionCanBeConfigured()
27372739
$this->assertTrue($container->hasAlias('.'.StoreInterface::class.' $qdrant_my_qdrant_store'));
27382740
$this->assertTrue($container->hasAlias(StoreInterface::class.' $qdrantMyQdrantStore'));
27392741
$this->assertTrue($container->hasAlias(StoreInterface::class));
2740-
}
27412742

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

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

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

27892878
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)