Skip to content

Commit 776c9b5

Browse files
committed
feat(store): support for Vektor
1 parent beba7c9 commit 776c9b5

File tree

21 files changed

+688
-1
lines changed

21 files changed

+688
-1
lines changed

examples/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"symfony/ai-tavily-tool": "^0.4",
7676
"symfony/ai-transformers-php-platform": "^0.4",
7777
"symfony/ai-typesense-store": "^0.4",
78+
"symfony/ai-vektor-store": "^0.5",
7879
"symfony/ai-vertex-ai-platform": "^0.4",
7980
"symfony/ai-voyage-platform": "^0.4",
8081
"symfony/ai-weaviate-store": "^0.4",

examples/rag/vektor.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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\Vektor\Store;
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 = new Store(sys_get_temp_dir());
32+
33+
// initialize the index
34+
$store->setup();
35+
36+
// create embeddings and documents
37+
$documents = [];
38+
foreach (Movies::all() as $i => $movie) {
39+
$documents[] = new TextDocument(
40+
id: Uuid::v4(),
41+
content: 'Title: '.$movie['title'].\PHP_EOL.'Director: '.$movie['director'].\PHP_EOL.'Description: '.$movie['description'],
42+
metadata: new Metadata($movie),
43+
);
44+
}
45+
46+
// create embeddings for documents
47+
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
48+
$vectorizer = new Vectorizer($platform, 'text-embedding-3-small', logger());
49+
$indexer = new DocumentIndexer(new DocumentProcessor($vectorizer, $store, logger: logger()));
50+
$indexer->index($documents);
51+
52+
$similaritySearch = new SimilaritySearch($vectorizer, $store);
53+
$toolbox = new Toolbox([$similaritySearch], logger: logger());
54+
$processor = new AgentProcessor($toolbox);
55+
$agent = new Agent($platform, 'gpt-5-mini', [$processor], [$processor]);
56+
57+
$messages = new MessageBag(
58+
Message::forSystem('Please answer all user questions only using SimilaritySearch function.'),
59+
Message::ofUser('Which movie fits the theme of technology?')
60+
);
61+
$result = $agent->call($messages);
62+
63+
echo $result->getContent().\PHP_EOL;

splitsh.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
"ai-supabase-store": "src/store/src/Bridge/Supabase",
9595
"ai-surreal-db-store": "src/store/src/Bridge/SurrealDb",
9696
"ai-typesense-store": "src/store/src/Bridge/Typesense",
97-
"ai-weaviate-store": "src/store/src/Bridge/Weaviate"
97+
"ai-weaviate-store": "src/store/src/Bridge/Weaviate",
98+
"ai-vektor-store": "src/store/src/Bridge/Vektor"
9899
}
99100
}

src/ai-bundle/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"symfony/ai-surreal-db-store": "^0.4",
9494
"symfony/ai-transformers-php-platform": "^0.4",
9595
"symfony/ai-typesense-store": "^0.4",
96+
"symfony/ai-vektor-store": "^0.5",
9697
"symfony/ai-vertex-ai-platform": "^0.4",
9798
"symfony/ai-voyage-platform": "^0.4",
9899
"symfony/ai-weaviate-store": "^0.4",

src/ai-bundle/config/options.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@
364364
->append($import('store/surrealdb'))
365365
->append($import('store/typesense'))
366366
->append($import('store/weaviate'))
367+
->append($import('store/vektor'))
367368
->end()
368369
->end()
369370
->arrayNode('message_store')
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
namespace Symfony\Component\Config\Definition\Configurator;
13+
14+
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
15+
16+
return (new ArrayNodeDefinition('vektor'))
17+
->useAttributeAsKey('name')
18+
->arrayPrototype()
19+
->children()
20+
->stringNode('storage_path')
21+
->defaultValue('%kernel.project_dir%/var/share')
22+
->end()
23+
->integerNode('dimensions')
24+
->defaultValue(1536)
25+
->end()
26+
->end()
27+
->end();

src/ai-bundle/src/AiBundle.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
use Symfony\AI\Store\Bridge\Supabase\Store as SupabaseStore;
111111
use Symfony\AI\Store\Bridge\SurrealDb\Store as SurrealDbStore;
112112
use Symfony\AI\Store\Bridge\Typesense\Store as TypesenseStore;
113+
use Symfony\AI\Store\Bridge\Vektor\Store as VektorStore;
113114
use Symfony\AI\Store\Bridge\Weaviate\Store as WeaviateStore;
114115
use Symfony\AI\Store\Distance\DistanceCalculator;
115116
use Symfony\AI\Store\Distance\DistanceStrategy;
@@ -1985,6 +1986,30 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
19851986
$container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name);
19861987
}
19871988
}
1989+
1990+
if ('vektor' === $type) {
1991+
if (!ContainerBuilder::willBeAvailable('symfony/ai-vektor-store', VektorStore::class, ['symfony/ai-bundle'])) {
1992+
throw new RuntimeException('Vektor store configuration requires "symfony/ai-vektor-store" package. Try running "composer require symfony/ai-vektor-store".');
1993+
}
1994+
1995+
foreach ($stores as $name => $store) {
1996+
$definition = new Definition(VektorStore::class);
1997+
$definition
1998+
->setLazy(true)
1999+
->setArguments([
2000+
$store['storage_path'],
2001+
$store['dimensions'],
2002+
new Reference('filesystem'),
2003+
])
2004+
->addTag('proxy', ['interface' => StoreInterface::class])
2005+
->addTag('proxy', ['interface' => ManagedStoreInterface::class])
2006+
->addTag('ai.store');
2007+
2008+
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
2009+
$container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $name);
2010+
$container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name);
2011+
}
2012+
}
19882013
}
19892014

19902015
/**

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

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
use Symfony\AI\Store\Bridge\Supabase\Store as SupabaseStore;
7171
use Symfony\AI\Store\Bridge\SurrealDb\Store as SurrealDbStore;
7272
use Symfony\AI\Store\Bridge\Typesense\Store as TypesenseStore;
73+
use Symfony\AI\Store\Bridge\Vektor\Store as VektorStore;
7374
use Symfony\AI\Store\Bridge\Weaviate\Store as WeaviateStore;
7475
use Symfony\AI\Store\Distance\DistanceCalculator;
7576
use Symfony\AI\Store\Distance\DistanceStrategy;
@@ -3503,6 +3504,115 @@ public function testWevaviateStoreWithCustomCollectionCanBeConfigured()
35033504
$this->assertTrue($container->hasAlias(StoreInterface::class));
35043505
}
35053506

3507+
public function testVektorStoreCanBeConfigured()
3508+
{
3509+
$container = $this->buildContainer([
3510+
'ai' => [
3511+
'store' => [
3512+
'vektor' => [
3513+
'main' => [],
3514+
],
3515+
],
3516+
],
3517+
]);
3518+
3519+
$this->assertTrue($container->hasDefinition('ai.store.vektor.main'));
3520+
3521+
$definition = $container->getDefinition('ai.store.vektor.main');
3522+
$this->assertSame(VektorStore::class, $definition->getClass());
3523+
$this->assertTrue($definition->isLazy());
3524+
3525+
$this->assertCount(3, $definition->getArguments());
3526+
$this->assertSame('%kernel.project_dir%/var/share', $definition->getArgument(0));
3527+
$this->assertSame(1536, $definition->getArgument(1));
3528+
$this->assertInstanceOf(Reference::class, $definition->getArgument(2));
3529+
$this->assertSame('filesystem', (string) $definition->getArgument(2));
3530+
3531+
$this->assertTrue($definition->hasTag('proxy'));
3532+
$this->assertSame([
3533+
['interface' => StoreInterface::class],
3534+
['interface' => ManagedStoreInterface::class],
3535+
], $definition->getTag('proxy'));
3536+
$this->assertTrue($definition->hasTag('ai.store'));
3537+
3538+
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $main'));
3539+
$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $vektor_main'));
3540+
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $vektorMain'));
3541+
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface'));
3542+
3543+
$container = $this->buildContainer([
3544+
'ai' => [
3545+
'store' => [
3546+
'vektor' => [
3547+
'main' => [
3548+
'storage_path' => '%kernel.project_dir%/var/share/vektor',
3549+
],
3550+
],
3551+
],
3552+
],
3553+
]);
3554+
3555+
$this->assertTrue($container->hasDefinition('ai.store.vektor.main'));
3556+
3557+
$definition = $container->getDefinition('ai.store.vektor.main');
3558+
$this->assertSame(VektorStore::class, $definition->getClass());
3559+
$this->assertTrue($definition->isLazy());
3560+
3561+
$this->assertCount(3, $definition->getArguments());
3562+
$this->assertSame('%kernel.project_dir%/var/share/vektor', $definition->getArgument(0));
3563+
$this->assertSame(1536, $definition->getArgument(1));
3564+
$this->assertInstanceOf(Reference::class, $definition->getArgument(2));
3565+
$this->assertSame('filesystem', (string) $definition->getArgument(2));
3566+
3567+
$this->assertTrue($definition->hasTag('proxy'));
3568+
$this->assertSame([
3569+
['interface' => StoreInterface::class],
3570+
['interface' => ManagedStoreInterface::class],
3571+
], $definition->getTag('proxy'));
3572+
$this->assertTrue($definition->hasTag('ai.store'));
3573+
3574+
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $main'));
3575+
$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $vektor_main'));
3576+
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $vektorMain'));
3577+
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface'));
3578+
3579+
$container = $this->buildContainer([
3580+
'ai' => [
3581+
'store' => [
3582+
'vektor' => [
3583+
'main' => [
3584+
'dimensions' => 764,
3585+
],
3586+
],
3587+
],
3588+
],
3589+
]);
3590+
3591+
$this->assertTrue($container->hasDefinition('ai.store.vektor.main'));
3592+
3593+
$definition = $container->getDefinition('ai.store.vektor.main');
3594+
$this->assertSame(VektorStore::class, $definition->getClass());
3595+
$this->assertTrue($definition->isLazy());
3596+
3597+
$this->assertCount(3, $definition->getArguments());
3598+
$this->assertSame('%kernel.project_dir%/var/share', $definition->getArgument(0));
3599+
$this->assertSame(764, $definition->getArgument(1));
3600+
$this->assertInstanceOf(Reference::class, $definition->getArgument(2));
3601+
$this->assertSame('filesystem', (string) $definition->getArgument(2));
3602+
3603+
$this->assertTrue($definition->hasTag('proxy'));
3604+
$this->assertSame([
3605+
['interface' => StoreInterface::class],
3606+
['interface' => ManagedStoreInterface::class],
3607+
], $definition->getTag('proxy'));
3608+
$this->assertTrue($definition->hasTag('ai.store'));
3609+
3610+
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $main'));
3611+
$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $vektor_main'));
3612+
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $vektorMain'));
3613+
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface'));
3614+
}
3615+
35063616
public function testConfigurationWithUseAttributeAsKeyWorksWithoutNormalizeKeys()
35073617
{
35083618
// Test that configurations using useAttributeAsKey work correctly
@@ -7993,6 +8103,15 @@ private function getFullConfig(): array
79938103
'collection' => 'my_weaviate_collection',
79948104
],
79958105
],
8106+
'vektor' => [
8107+
'my_vektor_store' => [],
8108+
'my_vektor_store_with_custom_path' => [
8109+
'storage_path' => 'foo',
8110+
],
8111+
'my_vektor_store_with_custom_dimensions' => [
8112+
'dimensions' => 764,
8113+
],
8114+
],
79968115
],
79978116
'message_store' => [
79988117
'cache' => [
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/Tests export-ignore
2+
/phpunit.xml.dist export-ignore
3+
/.git* export-ignore
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Please do not submit any Pull Requests here. They will be closed.
2+
---
3+
4+
Please submit your PR here instead:
5+
https://github.com/symfony/ai
6+
7+
This repository is what we call a "subtree split": a read-only subset of that main repository.
8+
We're looking forward to your PR there!

0 commit comments

Comments
 (0)