Skip to content

Commit 1a443c7

Browse files
committed
feat(store): support for ScopedHttpClient in AzureSearch
1 parent fed4a1b commit 1a443c7

File tree

11 files changed

+291
-174
lines changed

11 files changed

+291
-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('api_version')->end()
23+
->stringNode('index_name')
24+
->info('The name of the store will be used if the "index_name" option is not set')
25+
->end()
26+
->stringNode('http_client')
27+
->defaultValue('http_client')
28+
->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;
@@ -485,16 +486,18 @@ public function testAzureStoreCanBeConfigured()
485486
$this->assertTrue($container->hasDefinition('ai.store.azuresearch.my_azuresearch_store'));
486487

487488
$definition = $container->getDefinition('ai.store.azuresearch.my_azuresearch_store');
489+
$this->assertSame([AzureSearchStoreFactory::class, 'create'], $definition->getFactory());
488490
$this->assertSame(AzureStore::class, $definition->getClass());
489-
490491
$this->assertTrue($definition->isLazy());
491-
$this->assertCount(5, $definition->getArguments());
492-
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
493-
$this->assertSame('http_client', (string) $definition->getArgument(0));
494-
$this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(1));
495-
$this->assertSame('azure_search_key', $definition->getArgument(2));
496-
$this->assertSame('my-documents', $definition->getArgument(3));
492+
493+
$this->assertCount(6, $definition->getArguments());
494+
$this->assertSame('my-documents', (string) $definition->getArgument(0));
495+
$this->assertSame('vector', $definition->getArgument(1));
496+
$this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(2));
497+
$this->assertSame('azure_search_key', $definition->getArgument(3));
497498
$this->assertSame('2023-11-01', $definition->getArgument(4));
499+
$this->assertInstanceOf(Reference::class, $definition->getArgument(5));
500+
$this->assertSame('http_client', (string) $definition->getArgument(5));
498501

499502
$this->assertTrue($definition->hasTag('proxy'));
500503
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
@@ -504,10 +507,8 @@ public function testAzureStoreCanBeConfigured()
504507
$this->assertTrue($container->hasAlias(StoreInterface::class.' $myAzuresearchStore'));
505508
$this->assertTrue($container->hasAlias(StoreInterface::class.' $azuresearchMyAzuresearchStore'));
506509
$this->assertTrue($container->hasAlias(StoreInterface::class));
507-
}
508510

509-
public function testAzureStoreCanBeConfiguredWithCustomVectorField()
510-
{
511+
// Custom vector field
511512
$container = $this->buildContainer([
512513
'ai' => [
513514
'store' => [
@@ -527,17 +528,56 @@ public function testAzureStoreCanBeConfiguredWithCustomVectorField()
527528
$this->assertTrue($container->hasDefinition('ai.store.azuresearch.my_azuresearch_store'));
528529

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

542582
$this->assertTrue($definition->hasTag('proxy'));
543583
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
@@ -2678,7 +2718,7 @@ public function testQdrantStoreCanBeConfigured()
26782718
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
26792719

26802720
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2681-
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2721+
$this->assertSame([QdrantStoreFactory::class, 'create'], $definition->getFactory());
26822722
$this->assertSame(QdrantStore::class, $definition->getClass());
26832723
$this->assertTrue($definition->isLazy());
26842724

@@ -2722,7 +2762,7 @@ public function testQdrantStoreCanBeConfigured()
27222762
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
27232763

27242764
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2725-
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2765+
$this->assertSame([QdrantStoreFactory::class, 'create'], $definition->getFactory());
27262766
$this->assertSame(QdrantStore::class, $definition->getClass());
27272767
$this->assertTrue($definition->isLazy());
27282768

@@ -2767,7 +2807,7 @@ public function testQdrantStoreCanBeConfigured()
27672807
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
27682808

27692809
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2770-
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2810+
$this->assertSame([QdrantStoreFactory::class, 'create'], $definition->getFactory());
27712811
$this->assertSame(QdrantStore::class, $definition->getClass());
27722812
$this->assertTrue($definition->isLazy());
27732813

@@ -2813,7 +2853,7 @@ public function testQdrantStoreCanBeConfigured()
28132853
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
28142854

28152855
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2816-
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2856+
$this->assertSame([QdrantStoreFactory::class, 'create'], $definition->getFactory());
28172857
$this->assertSame(QdrantStore::class, $definition->getClass());
28182858
$this->assertTrue($definition->isLazy());
28192859

@@ -2856,7 +2896,7 @@ public function testQdrantStoreCanBeConfigured()
28562896
$this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store'));
28572897

28582898
$definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store');
2859-
$this->assertSame([StoreFactory::class, 'create'], $definition->getFactory());
2899+
$this->assertSame([QdrantStoreFactory::class, 'create'], $definition->getFactory());
28602900
$this->assertSame(QdrantStore::class, $definition->getClass());
28612901
$this->assertTrue($definition->isLazy());
28622902

@@ -7920,6 +7960,9 @@ private function getFullConfig(): array
79207960
'api_version' => '2023-11-01',
79217961
'vector_field' => 'contentVector',
79227962
],
7963+
'my_azuresearch_store_with_scoped_http_client' => [
7964+
'http_client' => 'scoped_http_client',
7965+
],
79237966
],
79247967
'cache' => [
79257968
'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)