Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
UPGRADE FROM 0.5 to 0.6
=======================

Store
--------

* Add support for `ScopingHttpClient` in `AzureSearchStore`
* The `endpointUrl` parameter for `AzureSearchStore` has been removed
* The `apiKey` parameter for `AzureSearchStore` has been removed
* The `apiVersion` parameter for `AzureSearchStore` has been removed
* A `StoreFactory` has been introduced for `AzureSearchStore`

UPGRADE FROM 0.4 to 0.5
=======================

Expand Down
60 changes: 60 additions & 0 deletions examples/rag/azuresearch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\AI\Agent\Agent;
use Symfony\AI\Agent\Bridge\SimilaritySearch\SimilaritySearch;
use Symfony\AI\Agent\Toolbox\AgentProcessor;
use Symfony\AI\Agent\Toolbox\Toolbox;
use Symfony\AI\Fixtures\Movies;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\AI\Store\Bridge\AzureSearch\StoreFactory;
use Symfony\AI\Store\Document\Metadata;
use Symfony\AI\Store\Document\TextDocument;
use Symfony\AI\Store\Document\Vectorizer;
use Symfony\AI\Store\Indexer\DocumentIndexer;
use Symfony\AI\Store\Indexer\DocumentProcessor;
use Symfony\Component\Uid\Uuid;

require_once dirname(__DIR__).'/bootstrap.php';

// initialize the store
$store = StoreFactory::create('movies');

// create embeddings and documents
$documents = [];
foreach (Movies::all() as $i => $movie) {
$documents[] = new TextDocument(
id: Uuid::v4(),
content: 'Title: '.$movie['title'].\PHP_EOL.'Director: '.$movie['director'].\PHP_EOL.'Description: '.$movie['description'],
metadata: new Metadata($movie),
);
}

// create embeddings for documents
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
$vectorizer = new Vectorizer($platform, 'text-embedding-3-small', logger());
$indexer = new DocumentIndexer(new DocumentProcessor($vectorizer, $store, logger: logger()));
$indexer->index($documents);

$similaritySearch = new SimilaritySearch($vectorizer, $store);
$toolbox = new Toolbox([$similaritySearch], logger: logger());
$processor = new AgentProcessor($toolbox);
$agent = new Agent($platform, 'gpt-5-mini', [$processor], [$processor]);

$messages = new MessageBag(
Message::forSystem('Please answer all user questions only using SimilaritySearch function.'),
Message::ofUser('Which movie fits the theme of the mafia?')
);
$result = $agent->call($messages);

echo $result->getContent().\PHP_EOL;
1 change: 1 addition & 0 deletions src/ai-bundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CHANGELOG
* Add `TraceableAgent`
* Add `TraceableStore`
* Add `setup_options` configuration for PostgreSQL store to pass extra fields to `ai:store:setup`
* Add support for `ScopingHttpClient` usage in `AzureSearch` store

0.5
---
Expand Down
17 changes: 12 additions & 5 deletions src/ai-bundle/config/store/azuresearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@
->useAttributeAsKey('name')
->arrayPrototype()
->children()
->stringNode('endpoint')->isRequired()->end()
->stringNode('api_key')->isRequired()->end()
->stringNode('index_name')->isRequired()->end()
->stringNode('api_version')->isRequired()->end()
->stringNode('vector_field')->end()
->stringNode('endpoint')->end()
->stringNode('api_key')->end()
->stringNode('api_version')->end()
->stringNode('index_name')
->info('The name of the store will be used if the "index_name" option is not set')
->end()
->stringNode('http_client')
->defaultValue('http_client')
->end()
->stringNode('vector_field')
->defaultValue('vector')
->end()
->end()
->end();
26 changes: 11 additions & 15 deletions src/ai-bundle/src/AiBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
use Symfony\AI\Platform\PlatformInterface;
use Symfony\AI\Platform\ResultConverterInterface;
use Symfony\AI\Store\Bridge\AzureSearch\SearchStore as AzureSearchStore;
use Symfony\AI\Store\Bridge\AzureSearch\StoreFactory as AzureSearchStoreFactory;
use Symfony\AI\Store\Bridge\Cache\Store as CacheStore;
use Symfony\AI\Store\Bridge\ChromaDb\Store as ChromaDbStore;
use Symfony\AI\Store\Bridge\ClickHouse\Store as ClickHouseStore;
Expand Down Expand Up @@ -1278,22 +1279,17 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
}

foreach ($stores as $name => $store) {
$arguments = [
new Reference('http_client'),
$store['endpoint'],
$store['api_key'],
$store['index_name'],
$store['api_version'],
];

if (\array_key_exists('vector_field', $store)) {
$arguments[5] = $store['vector_field'];
}

$definition = new Definition(AzureSearchStore::class);
$definition
$definition = (new Definition(AzureSearchStore::class))
->setFactory(AzureSearchStoreFactory::class.'::create')
->setLazy(true)
->setArguments($arguments)
->setArguments([
$store['index_name'] ?? $name,
$store['vector_field'],
$store['endpoint'] ?? null,
$store['api_key'] ?? null,
$store['api_version'] ?? null,
new Reference($store['http_client']),
])
->addTag('proxy', ['interface' => StoreInterface::class])
->addTag('ai.store');

Expand Down
89 changes: 66 additions & 23 deletions src/ai-bundle/tests/DependencyInjection/AiBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
use Symfony\AI\Platform\ModelCatalog\ModelCatalogInterface;
use Symfony\AI\Platform\PlatformInterface;
use Symfony\AI\Store\Bridge\AzureSearch\SearchStore as AzureStore;
use Symfony\AI\Store\Bridge\AzureSearch\StoreFactory as AzureSearchStoreFactory;
use Symfony\AI\Store\Bridge\Cache\Store as CacheStore;
use Symfony\AI\Store\Bridge\ChromaDb\Store as ChromaDbStore;
use Symfony\AI\Store\Bridge\ClickHouse\Store as ClickhouseStore;
Expand All @@ -66,7 +67,7 @@
use Symfony\AI\Store\Bridge\Postgres\Distance as PostgresDistance;
use Symfony\AI\Store\Bridge\Postgres\Store as PostgresStore;
use Symfony\AI\Store\Bridge\Qdrant\Store as QdrantStore;
use Symfony\AI\Store\Bridge\Qdrant\StoreFactory;
use Symfony\AI\Store\Bridge\Qdrant\StoreFactory as QdrantStoreFactory;
use Symfony\AI\Store\Bridge\Redis\Distance as RedisDistance;
use Symfony\AI\Store\Bridge\Redis\Store as RedisStore;
use Symfony\AI\Store\Bridge\Supabase\Store as SupabaseStore;
Expand Down Expand Up @@ -485,16 +486,18 @@ public function testAzureStoreCanBeConfigured()
$this->assertTrue($container->hasDefinition('ai.store.azuresearch.my_azuresearch_store'));

$definition = $container->getDefinition('ai.store.azuresearch.my_azuresearch_store');
$this->assertSame([AzureSearchStoreFactory::class, 'create'], $definition->getFactory());
$this->assertSame(AzureStore::class, $definition->getClass());

$this->assertTrue($definition->isLazy());
$this->assertCount(5, $definition->getArguments());
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
$this->assertSame('http_client', (string) $definition->getArgument(0));
$this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(1));
$this->assertSame('azure_search_key', $definition->getArgument(2));
$this->assertSame('my-documents', $definition->getArgument(3));

$this->assertCount(6, $definition->getArguments());
$this->assertSame('my-documents', (string) $definition->getArgument(0));
$this->assertSame('vector', $definition->getArgument(1));
$this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(2));
$this->assertSame('azure_search_key', $definition->getArgument(3));
$this->assertSame('2023-11-01', $definition->getArgument(4));
$this->assertInstanceOf(Reference::class, $definition->getArgument(5));
$this->assertSame('http_client', (string) $definition->getArgument(5));

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

public function testAzureStoreCanBeConfiguredWithCustomVectorField()
{
// Custom vector field
$container = $this->buildContainer([
'ai' => [
'store' => [
Expand All @@ -527,17 +528,56 @@ public function testAzureStoreCanBeConfiguredWithCustomVectorField()
$this->assertTrue($container->hasDefinition('ai.store.azuresearch.my_azuresearch_store'));

$definition = $container->getDefinition('ai.store.azuresearch.my_azuresearch_store');
$this->assertSame([AzureSearchStoreFactory::class, 'create'], $definition->getFactory());
$this->assertSame(AzureStore::class, $definition->getClass());

$this->assertTrue($definition->isLazy());

$this->assertCount(6, $definition->getArguments());
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
$this->assertSame('http_client', (string) $definition->getArgument(0));
$this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(1));
$this->assertSame('azure_search_key', $definition->getArgument(2));
$this->assertSame('my-documents', $definition->getArgument(3));
$this->assertSame('my-documents', (string) $definition->getArgument(0));
$this->assertSame('foo', $definition->getArgument(1));
$this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(2));
$this->assertSame('azure_search_key', $definition->getArgument(3));
$this->assertSame('2023-11-01', $definition->getArgument(4));
$this->assertSame('foo', $definition->getArgument(5));
$this->assertInstanceOf(Reference::class, $definition->getArgument(5));
$this->assertSame('http_client', (string) $definition->getArgument(5));

$this->assertTrue($definition->hasTag('proxy'));
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
$this->assertTrue($definition->hasTag('ai.store'));

$this->assertTrue($container->hasAlias('.'.StoreInterface::class.' $my_azuresearch_store'));
$this->assertTrue($container->hasAlias(StoreInterface::class.' $myAzuresearchStore'));
$this->assertTrue($container->hasAlias(StoreInterface::class.' $azuresearchMyAzuresearchStore'));
$this->assertTrue($container->hasAlias(StoreInterface::class));

// Scoped HttpClient
$container = $this->buildContainer([
'ai' => [
'store' => [
'azuresearch' => [
'my_azuresearch_store' => [
'http_client' => 'scoped_http_client',
],
],
],
],
]);

$this->assertTrue($container->hasDefinition('ai.store.azuresearch.my_azuresearch_store'));

$definition = $container->getDefinition('ai.store.azuresearch.my_azuresearch_store');
$this->assertSame([AzureSearchStoreFactory::class, 'create'], $definition->getFactory());
$this->assertSame(AzureStore::class, $definition->getClass());
$this->assertTrue($definition->isLazy());

$this->assertCount(6, $definition->getArguments());
$this->assertSame('my_azuresearch_store', (string) $definition->getArgument(0));
$this->assertSame('vector', $definition->getArgument(1));
$this->assertNull($definition->getArgument(2));
$this->assertNull($definition->getArgument(3));
$this->assertNull($definition->getArgument(4));
$this->assertInstanceOf(Reference::class, $definition->getArgument(5));
$this->assertSame('scoped_http_client', (string) $definition->getArgument(5));

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

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

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

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

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

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

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

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

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

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

Expand Down Expand Up @@ -7920,6 +7960,9 @@ private function getFullConfig(): array
'api_version' => '2023-11-01',
'vector_field' => 'contentVector',
],
'my_azuresearch_store_with_scoped_http_client' => [
'http_client' => 'scoped_http_client',
],
],
'cache' => [
'my_cache_store' => [
Expand Down
8 changes: 8 additions & 0 deletions src/store/src/Bridge/AzureSearch/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
CHANGELOG
=========

0.6
---

* [BC BREAK] Add support for `ScopingHttpClient` in `SearchStore`
* [BC BREAK] The `endpointUrl` parameter for `SearchStore` has been removed
* [BC BREAK] The `apiKey` parameter for `SearchStore` has been removed
* [BC BREAK] The `apiVersion` parameter for `SearchStore` has been removed

0.4
---

Expand Down
Loading
Loading