Skip to content

Commit 1802f99

Browse files
committed
feat(store): Introduce support
1 parent 95751b8 commit 1802f99

File tree

12 files changed

+799
-0
lines changed

12 files changed

+799
-0
lines changed

docs/components/store.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Similarity Search Examples
7070
* `Similarity Search with Milvus (RAG)`_
7171
* `Similarity Search with MongoDB (RAG)`_
7272
* `Similarity Search with Neo4j (RAG)`_
73+
* `Similarity Search with OpenSearch (RAG)`_
7374
* `Similarity Search with Pinecone (RAG)`_
7475
* `Similarity Search with Qdrant (RAG)`_
7576
* `Similarity Search with SurrealDB (RAG)`_
@@ -97,6 +98,7 @@ Supported Stores
9798
* `Milvus`_
9899
* `MongoDB Atlas`_ (requires ``mongodb/mongodb`` as additional dependency)
99100
* `Neo4j`_
101+
* `OpenSearch`_
100102
* `Pinecone`_ (requires ``probots-io/pinecone-php`` as additional dependency)
101103
* `Postgres`_ (requires ``ext-pdo``)
102104
* `Qdrant`_
@@ -165,6 +167,7 @@ This leads to a store implementing two methods::
165167
.. _`Similarity Search with Milvus (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/milvus.php
166168
.. _`Similarity Search with MongoDB (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/mongodb.php
167169
.. _`Similarity Search with Neo4j (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/neo4j.php
170+
.. _`Similarity Search with OpenSearch (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/opensearch.php
168171
.. _`Similarity Search with Pinecone (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/pinecone.php
169172
.. _`Similarity Search with Symfony Cache (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/cache.php
170173
.. _`Similarity Search with Qdrant (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/qdrant.php
@@ -186,6 +189,7 @@ This leads to a store implementing two methods::
186189
.. _`InMemory`: https://www.php.net/manual/en/language.types.array.php
187190
.. _`Qdrant`: https://qdrant.tech/
188191
.. _`Neo4j`: https://neo4j.com/
192+
.. _`OpenSearch`: https://opensearch.org/
189193
.. _`Typesense`: https://typesense.org/
190194
.. _`Symfony Cache`: https://symfony.com/doc/current/components/cache.html
191195
.. _`Weaviate`: https://weaviate.io/

examples/.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,6 @@ REDIS_HOST=localhost
182182

183183
# Manticore (store)
184184
MANTICORE_HOST=http://127.0.0.1:9308
185+
186+
# OpenSearch (store)
187+
OPENSEARCH_ENDPOINT=http://127.0.0.1:9200

examples/commands/message-stores.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@
103103
new DropStoreCommand(new ServiceLocator($factories)),
104104
]);
105105

106+
$clock = new MonotonicClock();
107+
$clock->sleep(10);
108+
106109
foreach ($storesIds as $store) {
107110
$setupOutputCode = $application->run(new ArrayInput([
108111
'command' => 'ai:message-store:setup',

examples/commands/stores.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\AI\Store\Bridge\Milvus\Store as MilvusStore;
2424
use Symfony\AI\Store\Bridge\MongoDb\Store as MongoDbStore;
2525
use Symfony\AI\Store\Bridge\Neo4j\Store as Neo4jStore;
26+
use Symfony\AI\Store\Bridge\OpenSearch\Store as OpenSearchStore;
2627
use Symfony\AI\Store\Bridge\Postgres\Store as PostgresStore;
2728
use Symfony\AI\Store\Bridge\Qdrant\Store as QdrantStore;
2829
use Symfony\AI\Store\Bridge\Redis\Store as RedisStore;
@@ -32,6 +33,7 @@
3233
use Symfony\AI\Store\Command\DropStoreCommand;
3334
use Symfony\AI\Store\Command\SetupStoreCommand;
3435
use Symfony\Component\Cache\Adapter\ArrayAdapter;
36+
use Symfony\Component\Clock\MonotonicClock;
3537
use Symfony\Component\Console\Application;
3638
use Symfony\Component\Console\Input\ArrayInput;
3739
use Symfony\Component\Console\Output\ConsoleOutput;
@@ -86,6 +88,11 @@
8688
vectorIndexName: 'Commands',
8789
nodeName: 'symfony',
8890
),
91+
'opensearch' => static fn (): OpenSearchStore => new OpenSearchStore(
92+
http_client(),
93+
env('OPENSEARCH_ENDPOINT'),
94+
'symfony',
95+
),
8996
'postgres' => static fn (): PostgresStore => PostgresStore::fromDbal(
9097
DriverManager::getConnection((new DsnParser())->parse(env('POSTGRES_URI'))),
9198
'my_table',
@@ -133,6 +140,9 @@
133140
new DropStoreCommand(new ServiceLocator($factories)),
134141
]);
135142

143+
$clock = new MonotonicClock();
144+
$clock->sleep(10);
145+
136146
foreach ($storesIds as $store) {
137147
$setupOutputCode = $application->run(new ArrayInput([
138148
'command' => 'ai:store:setup',

examples/compose.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,31 @@ services:
124124
- '7474:7474'
125125
- '7687:7687'
126126

127+
opensearch:
128+
image: opensearchproject/opensearch
129+
environment:
130+
discovery.type: 'single-node'
131+
bootstrap.memory_lock: true
132+
indices.requests.cache.maximum_cacheable_size: 256
133+
DISABLE_SECURITY_PLUGIN: true
134+
OPENSEARCH_JAVA_OPTS: '-Xms512m -Xmx512m'
135+
ulimits:
136+
memlock:
137+
soft: -1
138+
hard: -1
139+
nofile:
140+
soft: 65536
141+
hard: 65536
142+
healthcheck:
143+
test: [ 'CMD', 'curl', "-f", "http://127.0.0.1:9200" ]
144+
interval: 30s
145+
start_period: 120s
146+
timeout: 20s
147+
retries: 3
148+
ports:
149+
- '9200:9200'
150+
- '9600:9600'
151+
127152
pogocache:
128153
image: pogocache/pogocache
129154
command: [ 'pogocache', '--auth', 'symfony' ]

examples/rag/opensearch.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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\OpenSearch\Store;
21+
use Symfony\AI\Store\Document\Loader\InMemoryLoader;
22+
use Symfony\AI\Store\Document\Metadata;
23+
use Symfony\AI\Store\Document\TextDocument;
24+
use Symfony\AI\Store\Document\Vectorizer;
25+
use Symfony\AI\Store\Indexer;
26+
use Symfony\Component\Uid\Uuid;
27+
28+
require_once dirname(__DIR__).'/bootstrap.php';
29+
30+
// initialize the store
31+
$store = new Store(
32+
httpClient: http_client(),
33+
endpoint: env('OPENSEARCH_ENDPOINT'),
34+
indexName: 'movies',
35+
);
36+
37+
// create embeddings and documents
38+
$documents = [];
39+
foreach (Movies::all() as $i => $movie) {
40+
$documents[] = new TextDocument(
41+
id: Uuid::v4(),
42+
content: 'Title: '.$movie['title'].\PHP_EOL.'Director: '.$movie['director'].\PHP_EOL.'Description: '.$movie['description'],
43+
metadata: new Metadata($movie),
44+
);
45+
}
46+
47+
// initialize the index
48+
$store->setup();
49+
50+
// create embeddings for documents
51+
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
52+
$vectorizer = new Vectorizer($platform, 'text-embedding-3-small', logger());
53+
$indexer = new Indexer(new InMemoryLoader($documents), $vectorizer, $store, logger: logger());
54+
$indexer->index($documents);
55+
56+
$similaritySearch = new SimilaritySearch($vectorizer, $store);
57+
$toolbox = new Toolbox([$similaritySearch], logger: logger());
58+
$processor = new AgentProcessor($toolbox);
59+
$agent = new Agent($platform, 'gpt-4o-mini', [$processor], [$processor]);
60+
61+
$messages = new MessageBag(
62+
Message::forSystem('Please answer all user questions only using SimilaritySearch function.'),
63+
Message::ofUser('Which movie fits the theme of technology?')
64+
);
65+
$result = $agent->call($messages);
66+
67+
echo $result->getContent().\PHP_EOL;

src/ai-bundle/config/options.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,27 @@
722722
->end()
723723
->end()
724724
->end()
725+
->arrayNode('opensearch')
726+
->useAttributeAsKey('name')
727+
->arrayPrototype()
728+
->children()
729+
->stringNode('endpoint')->cannotBeEmpty()->end()
730+
->stringNode('index_name')->end()
731+
->stringNode('vectors_field')
732+
->defaultValue('_vectors')
733+
->end()
734+
->integerNode('dimensions')
735+
->defaultValue(1536)
736+
->end()
737+
->stringNode('space_type')
738+
->defaultValue('l2')
739+
->end()
740+
->stringNode('http_client')
741+
->defaultValue('http_client')
742+
->end()
743+
->end()
744+
->end()
745+
->end()
725746
->arrayNode('pinecone')
726747
->useAttributeAsKey('name')
727748
->arrayPrototype()

src/ai-bundle/src/AiBundle.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
use Symfony\AI\Store\Bridge\Milvus\Store as MilvusStore;
9292
use Symfony\AI\Store\Bridge\MongoDb\Store as MongoDbStore;
9393
use Symfony\AI\Store\Bridge\Neo4j\Store as Neo4jStore;
94+
use Symfony\AI\Store\Bridge\OpenSearch\Store as OpenSearchStore;
9495
use Symfony\AI\Store\Bridge\Pinecone\Store as PineconeStore;
9596
use Symfony\AI\Store\Bridge\Postgres\Store as PostgresStore;
9697
use Symfony\AI\Store\Bridge\Qdrant\Store as QdrantStore;
@@ -1277,6 +1278,29 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
12771278
}
12781279
}
12791280

1281+
if ('opensearch' === $type) {
1282+
foreach ($stores as $name => $store) {
1283+
$definition = new Definition(OpenSearchStore::class);
1284+
$definition
1285+
->setLazy(true)
1286+
->setArguments([
1287+
new Reference($store['http_client']),
1288+
$store['endpoint'],
1289+
$store['index_name'] ?? $name,
1290+
$store['vectors_field'],
1291+
$store['dimensions'],
1292+
$store['space_type'],
1293+
])
1294+
->addTag('proxy', ['interface' => StoreInterface::class])
1295+
->addTag('proxy', ['interface' => ManagedStoreInterface::class])
1296+
->addTag('ai.store');
1297+
1298+
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
1299+
$container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $name);
1300+
$container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name);
1301+
}
1302+
}
1303+
12801304
if ('pinecone' === $type) {
12811305
foreach ($stores as $name => $store) {
12821306
$arguments = [

0 commit comments

Comments
 (0)