Skip to content

Commit 44e38d6

Browse files
committed
feat(store): support for ScopedHttpClient in AzureSearch
1 parent 070c478 commit 44e38d6

File tree

10 files changed

+257
-174
lines changed

10 files changed

+257
-174
lines changed

UPGRADE.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
UPGRADE FROM 0.5 to 0.6
2+
=======================
3+
4+
Store
5+
--------
6+
7+
* Add support for `ScopingHttpClient` in `AzureSearchStore`
8+
* The `endpointUrl` parameter for `AzureSearchStore` has been removed
9+
* The `apiKey` parameter for `AzureSearchStore` has been removed
10+
* The `apiVersion` parameter for `AzureSearchStore` has been removed
11+
* A `StoreFactory` has been introduced for `AzureSearchStore`
12+
113
UPGRADE FROM 0.4 to 0.5
214
=======================
315

examples/rag/azuresearch.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Agent\Bridge\SimilaritySearch\SimilaritySearch;
14+
use Symfony\AI\Agent\Toolbox\AgentProcessor;
15+
use Symfony\AI\Agent\Toolbox\Toolbox;
16+
use Symfony\AI\Fixtures\Movies;
17+
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
18+
use Symfony\AI\Platform\Message\Message;
19+
use Symfony\AI\Platform\Message\MessageBag;
20+
use Symfony\AI\Store\Bridge\AzureSearch\StoreFactory;
21+
use Symfony\AI\Store\Document\Metadata;
22+
use Symfony\AI\Store\Document\TextDocument;
23+
use Symfony\AI\Store\Document\Vectorizer;
24+
use Symfony\AI\Store\Indexer\DocumentIndexer;
25+
use Symfony\AI\Store\Indexer\DocumentProcessor;
26+
use Symfony\Component\Uid\Uuid;
27+
28+
require_once dirname(__DIR__).'/bootstrap.php';
29+
30+
// initialize the store
31+
$store = StoreFactory::create('movies');
32+
33+
// create embeddings and documents
34+
$documents = [];
35+
foreach (Movies::all() as $i => $movie) {
36+
$documents[] = new TextDocument(
37+
id: Uuid::v4(),
38+
content: 'Title: '.$movie['title'].\PHP_EOL.'Director: '.$movie['director'].\PHP_EOL.'Description: '.$movie['description'],
39+
metadata: new Metadata($movie),
40+
);
41+
}
42+
43+
// create embeddings for documents
44+
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
45+
$vectorizer = new Vectorizer($platform, 'text-embedding-3-small', logger());
46+
$indexer = new DocumentIndexer(new DocumentProcessor($vectorizer, $store, logger: logger()));
47+
$indexer->index($documents);
48+
49+
$similaritySearch = new SimilaritySearch($vectorizer, $store);
50+
$toolbox = new Toolbox([$similaritySearch], logger: logger());
51+
$processor = new AgentProcessor($toolbox);
52+
$agent = new Agent($platform, 'gpt-5-mini', [$processor], [$processor]);
53+
54+
$messages = new MessageBag(
55+
Message::forSystem('Please answer all user questions only using SimilaritySearch function.'),
56+
Message::ofUser('Which movie fits the theme of the mafia?')
57+
);
58+
$result = $agent->call($messages);
59+
60+
echo $result->getContent().\PHP_EOL;

src/ai-bundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* Add `TraceableAgent`
99
* Add `TraceableStore`
1010
* Add `setup_options` configuration for PostgreSQL store to pass extra fields to `ai:store:setup`
11+
* Add support for `ScopingHttpClient` usage in `AzureSearch` store
1112

1213
0.5
1314
---

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,17 @@
1717
->useAttributeAsKey('name')
1818
->arrayPrototype()
1919
->children()
20-
->stringNode('endpoint')->isRequired()->end()
21-
->stringNode('api_key')->isRequired()->end()
22-
->stringNode('index_name')->isRequired()->end()
23-
->stringNode('api_version')->isRequired()->end()
24-
->stringNode('vector_field')->end()
20+
->stringNode('endpoint')->end()
21+
->stringNode('api_key')->end()
22+
->stringNode('http_client')
23+
->defaultValue('http_client')
24+
->end()
25+
->stringNode('index_name')
26+
->info('The name of the store will be used if the "index_name" option is not set')
27+
->end()
28+
->stringNode('api_version')->end()
29+
->stringNode('vector_field')
30+
->defaultValue('vector')
31+
->end()
2532
->end()
2633
->end();

src/ai-bundle/src/AiBundle.php

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
use Symfony\AI\Platform\PlatformInterface;
8989
use Symfony\AI\Platform\ResultConverterInterface;
9090
use Symfony\AI\Store\Bridge\AzureSearch\SearchStore as AzureSearchStore;
91+
use Symfony\AI\Store\Bridge\AzureSearch\StoreFactory as AzureSearchStoreFactory;
9192
use Symfony\AI\Store\Bridge\Cache\Store as CacheStore;
9293
use Symfony\AI\Store\Bridge\ChromaDb\Store as ChromaDbStore;
9394
use Symfony\AI\Store\Bridge\ClickHouse\Store as ClickHouseStore;
@@ -1278,22 +1279,17 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
12781279
}
12791280

12801281
foreach ($stores as $name => $store) {
1281-
$arguments = [
1282-
new Reference('http_client'),
1283-
$store['endpoint'],
1284-
$store['api_key'],
1285-
$store['index_name'],
1286-
$store['api_version'],
1287-
];
1288-
1289-
if (\array_key_exists('vector_field', $store)) {
1290-
$arguments[5] = $store['vector_field'];
1291-
}
1292-
1293-
$definition = new Definition(AzureSearchStore::class);
1294-
$definition
1282+
$definition = (new Definition(AzureSearchStore::class))
1283+
->setFactory(AzureSearchStoreFactory::class.'::create')
12951284
->setLazy(true)
1296-
->setArguments($arguments)
1285+
->setArguments([
1286+
$store['index_name'] ?? $name,
1287+
$store['vector_field'],
1288+
$store['endpoint'] ?? null,
1289+
$store['api_key'] ?? null,
1290+
$store['api_version'] ?? null,
1291+
new Reference($store['http_client']),
1292+
])
12971293
->addTag('proxy', ['interface' => StoreInterface::class])
12981294
->addTag('ai.store');
12991295

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

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
use Symfony\AI\Platform\ModelCatalog\ModelCatalogInterface;
5252
use Symfony\AI\Platform\PlatformInterface;
5353
use Symfony\AI\Store\Bridge\AzureSearch\SearchStore as AzureStore;
54+
use Symfony\AI\Store\Bridge\AzureSearch\StoreFactory as AzureSearchStoreFactory;
5455
use Symfony\AI\Store\Bridge\Cache\Store as CacheStore;
5556
use Symfony\AI\Store\Bridge\ChromaDb\Store as ChromaDbStore;
5657
use Symfony\AI\Store\Bridge\ClickHouse\Store as ClickhouseStore;
@@ -66,7 +67,7 @@
6667
use Symfony\AI\Store\Bridge\Postgres\Distance as PostgresDistance;
6768
use Symfony\AI\Store\Bridge\Postgres\Store as PostgresStore;
6869
use Symfony\AI\Store\Bridge\Qdrant\Store as QdrantStore;
69-
use Symfony\AI\Store\Bridge\Qdrant\StoreFactory;
70+
use Symfony\AI\Store\Bridge\Qdrant\StoreFactory as QdrantStoreFactory;
7071
use Symfony\AI\Store\Bridge\Redis\Distance as RedisDistance;
7172
use Symfony\AI\Store\Bridge\Redis\Store as RedisStore;
7273
use Symfony\AI\Store\Bridge\Supabase\Store as SupabaseStore;
@@ -477,16 +478,18 @@ public function testAzureStoreCanBeConfigured()
477478
$this->assertTrue($container->hasDefinition('ai.store.azuresearch.my_azuresearch_store'));
478479

479480
$definition = $container->getDefinition('ai.store.azuresearch.my_azuresearch_store');
481+
$this->assertSame([AzureSearchStoreFactory::class, 'create'], $definition->getFactory());
480482
$this->assertSame(AzureStore::class, $definition->getClass());
481-
482483
$this->assertTrue($definition->isLazy());
483-
$this->assertCount(5, $definition->getArguments());
484-
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
485-
$this->assertSame('http_client', (string) $definition->getArgument(0));
486-
$this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(1));
487-
$this->assertSame('azure_search_key', $definition->getArgument(2));
488-
$this->assertSame('my-documents', $definition->getArgument(3));
484+
485+
$this->assertCount(6, $definition->getArguments());
486+
$this->assertSame('my-documents', (string) $definition->getArgument(0));
487+
$this->assertSame('vector', $definition->getArgument(1));
488+
$this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(2));
489+
$this->assertSame('azure_search_key', $definition->getArgument(3));
489490
$this->assertSame('2023-11-01', $definition->getArgument(4));
491+
$this->assertInstanceOf(Reference::class, $definition->getArgument(5));
492+
$this->assertSame('http_client', (string) $definition->getArgument(5));
490493

491494
$this->assertTrue($definition->hasTag('proxy'));
492495
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
@@ -496,10 +499,8 @@ public function testAzureStoreCanBeConfigured()
496499
$this->assertTrue($container->hasAlias(StoreInterface::class.' $myAzuresearchStore'));
497500
$this->assertTrue($container->hasAlias(StoreInterface::class.' $azuresearchMyAzuresearchStore'));
498501
$this->assertTrue($container->hasAlias(StoreInterface::class));
499-
}
500502

501-
public function testAzureStoreCanBeConfiguredWithCustomVectorField()
502-
{
503+
// Custom vector field
503504
$container = $this->buildContainer([
504505
'ai' => [
505506
'store' => [
@@ -519,17 +520,56 @@ public function testAzureStoreCanBeConfiguredWithCustomVectorField()
519520
$this->assertTrue($container->hasDefinition('ai.store.azuresearch.my_azuresearch_store'));
520521

521522
$definition = $container->getDefinition('ai.store.azuresearch.my_azuresearch_store');
523+
$this->assertSame([AzureSearchStoreFactory::class, 'create'], $definition->getFactory());
522524
$this->assertSame(AzureStore::class, $definition->getClass());
523-
524525
$this->assertTrue($definition->isLazy());
526+
525527
$this->assertCount(6, $definition->getArguments());
526-
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
527-
$this->assertSame('http_client', (string) $definition->getArgument(0));
528-
$this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(1));
529-
$this->assertSame('azure_search_key', $definition->getArgument(2));
530-
$this->assertSame('my-documents', $definition->getArgument(3));
528+
$this->assertSame('my-documents', (string) $definition->getArgument(0));
529+
$this->assertSame('foo', $definition->getArgument(1));
530+
$this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(2));
531+
$this->assertSame('azure_search_key', $definition->getArgument(3));
531532
$this->assertSame('2023-11-01', $definition->getArgument(4));
532-
$this->assertSame('foo', $definition->getArgument(5));
533+
$this->assertInstanceOf(Reference::class, $definition->getArgument(5));
534+
$this->assertSame('http_client', (string) $definition->getArgument(5));
535+
536+
$this->assertTrue($definition->hasTag('proxy'));
537+
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
538+
$this->assertTrue($definition->hasTag('ai.store'));
539+
540+
$this->assertTrue($container->hasAlias('.'.StoreInterface::class.' $my_azuresearch_store'));
541+
$this->assertTrue($container->hasAlias(StoreInterface::class.' $myAzuresearchStore'));
542+
$this->assertTrue($container->hasAlias(StoreInterface::class.' $azuresearchMyAzuresearchStore'));
543+
$this->assertTrue($container->hasAlias(StoreInterface::class));
544+
545+
// Scoped HttpClient
546+
$container = $this->buildContainer([
547+
'ai' => [
548+
'store' => [
549+
'azuresearch' => [
550+
'my_azuresearch_store' => [
551+
'http_client' => 'scoped_http_client',
552+
],
553+
],
554+
],
555+
],
556+
]);
557+
558+
$this->assertTrue($container->hasDefinition('ai.store.azuresearch.my_azuresearch_store'));
559+
560+
$definition = $container->getDefinition('ai.store.azuresearch.my_azuresearch_store');
561+
$this->assertSame([AzureSearchStoreFactory::class, 'create'], $definition->getFactory());
562+
$this->assertSame(AzureStore::class, $definition->getClass());
563+
$this->assertTrue($definition->isLazy());
564+
565+
$this->assertCount(6, $definition->getArguments());
566+
$this->assertSame('my_azuresearch_store', (string) $definition->getArgument(0));
567+
$this->assertSame('vector', $definition->getArgument(1));
568+
$this->assertNull($definition->getArgument(2));
569+
$this->assertNull($definition->getArgument(3));
570+
$this->assertNull($definition->getArgument(4));
571+
$this->assertInstanceOf(Reference::class, $definition->getArgument(5));
572+
$this->assertSame('scoped_http_client', (string) $definition->getArgument(5));
533573

534574
$this->assertTrue($definition->hasTag('proxy'));
535575
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
@@ -2670,7 +2710,7 @@ public function testQdrantStoreCanBeConfigured()
26702710
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
26712711

26722712
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2673-
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2713+
$this->assertSame([QdrantStoreFactory::class, 'create'], $definition->getFactory());
26742714
$this->assertSame(QdrantStore::class, $definition->getClass());
26752715
$this->assertTrue($definition->isLazy());
26762716

@@ -2714,7 +2754,7 @@ public function testQdrantStoreCanBeConfigured()
27142754
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
27152755

27162756
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2717-
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2757+
$this->assertSame([QdrantStoreFactory::class, 'create'], $definition->getFactory());
27182758
$this->assertSame(QdrantStore::class, $definition->getClass());
27192759
$this->assertTrue($definition->isLazy());
27202760

@@ -2759,7 +2799,7 @@ public function testQdrantStoreCanBeConfigured()
27592799
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
27602800

27612801
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2762-
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2802+
$this->assertSame([QdrantStoreFactory::class, 'create'], $definition->getFactory());
27632803
$this->assertSame(QdrantStore::class, $definition->getClass());
27642804
$this->assertTrue($definition->isLazy());
27652805

@@ -2805,7 +2845,7 @@ public function testQdrantStoreCanBeConfigured()
28052845
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
28062846

28072847
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2808-
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2848+
$this->assertSame([QdrantStoreFactory::class, 'create'], $definition->getFactory());
28092849
$this->assertSame(QdrantStore::class, $definition->getClass());
28102850
$this->assertTrue($definition->isLazy());
28112851

@@ -2848,7 +2888,7 @@ public function testQdrantStoreCanBeConfigured()
28482888
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
28492889

28502890
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2851-
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2891+
$this->assertSame([QdrantStoreFactory::class, 'create'], $definition->getFactory());
28522892
$this->assertSame(QdrantStore::class, $definition->getClass());
28532893
$this->assertTrue($definition->isLazy());
28542894

@@ -7912,6 +7952,9 @@ private function getFullConfig(): array
79127952
'api_version' => '2023-11-01',
79137953
'vector_field' => 'contentVector',
79147954
],
7955+
'my_azuresearch_store_with_scoped_http_client' => [
7956+
'http_client' => 'scoped_http_client',
7957+
],
79157958
],
79167959
'cache' => [
79177960
'my_cache_store' => [

src/store/src/Bridge/AzureSearch/CHANGELOG.md

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

4+
0.6
5+
---
6+
7+
* [BC BREAK] Add support for `ScopingHttpClient` in `SearchStore`
8+
* [BC BREAK] The `endpointUrl` parameter for `SearchStore` has been removed
9+
* [BC BREAK] The `apiKey` parameter for `SearchStore` has been removed
10+
* [BC BREAK] The `apiVersion` parameter for `SearchStore` has been removed
11+
412
0.4
513
---
614

0 commit comments

Comments
 (0)