diff --git a/src/ai-bundle/composer.json b/src/ai-bundle/composer.json index 5c92c4743..313a847fa 100644 --- a/src/ai-bundle/composer.json +++ b/src/ai-bundle/composer.json @@ -33,6 +33,7 @@ "phpstan/phpstan": "^2.1", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^11.5", + "probots-io/pinecone-php": "^1.0", "symfony/expression-language": "^7.3|^8.0", "symfony/security-core": "^7.3|^8.0", "symfony/translation": "^7.3|^8.0" diff --git a/src/ai-bundle/config/options.php b/src/ai-bundle/config/options.php index a21b35562..be760c478 100644 --- a/src/ai-bundle/config/options.php +++ b/src/ai-bundle/config/options.php @@ -576,8 +576,11 @@ ->stringNode('account_id')->cannotBeEmpty()->end() ->stringNode('api_key')->cannotBeEmpty()->end() ->stringNode('index_name')->cannotBeEmpty()->end() - ->integerNode('dimensions')->end() - ->stringNode('metric')->end() + ->integerNode('dimensions')->isRequired()->end() + ->stringNode('metric') + ->cannotBeEmpty() + ->defaultValue('cosine') + ->end() ->stringNode('endpoint_url')->end() ->end() ->end() @@ -588,14 +591,30 @@ ->children() ->stringNode('endpoint')->cannotBeEmpty()->end() ->stringNode('table')->cannotBeEmpty()->end() - ->stringNode('field')->end() - ->stringNode('type')->end() - ->stringNode('similarity')->end() - ->integerNode('dimensions')->end() + ->stringNode('field')->cannotBeEmpty()->end() + ->stringNode('type')->cannotBeEmpty()->end() + ->stringNode('similarity')->cannotBeEmpty()->end() + ->integerNode('dimensions')->isRequired()->end() ->stringNode('quantization')->end() ->end() ->end() ->end() + ->arrayNode('mariadb') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->stringNode('connection')->cannotBeEmpty()->end() + ->stringNode('table_name')->cannotBeEmpty()->end() + ->stringNode('index_name')->cannotBeEmpty()->end() + ->stringNode('vector_field_name')->cannotBeEmpty()->end() + ->arrayNode('setup_options') + ->children() + ->integerNode('dimensions')->end() + ->end() + ->end() + ->end() + ->end() + ->end() ->arrayNode('meilisearch') ->useAttributeAsKey('name') ->arrayPrototype() @@ -603,9 +622,9 @@ ->stringNode('endpoint')->cannotBeEmpty()->end() ->stringNode('api_key')->cannotBeEmpty()->end() ->stringNode('index_name')->cannotBeEmpty()->end() - ->stringNode('embedder')->end() - ->stringNode('vector_field')->end() - ->integerNode('dimensions')->end() + ->stringNode('embedder')->cannotBeEmpty()->end() + ->stringNode('vector_field')->cannotBeEmpty()->end() + ->integerNode('dimensions')->isRequired()->end() ->floatNode('semantic_ratio') ->info('The ratio between semantic (vector) and full-text search (0.0 to 1.0). Default: 1.0 (100% semantic)') ->defaultValue(1.0) @@ -623,22 +642,6 @@ ->end() ->end() ->end() - ->arrayNode('mariadb') - ->useAttributeAsKey('name') - ->arrayPrototype() - ->children() - ->stringNode('connection')->cannotBeEmpty()->end() - ->stringNode('table_name')->cannotBeEmpty()->end() - ->stringNode('index_name')->cannotBeEmpty()->end() - ->stringNode('vector_field_name')->cannotBeEmpty()->end() - ->arrayNode('setup_options') - ->children() - ->integerNode('dimensions')->end() - ->end() - ->end() - ->end() - ->end() - ->end() ->arrayNode('milvus') ->useAttributeAsKey('name') ->arrayPrototype() @@ -647,8 +650,8 @@ ->stringNode('api_key')->isRequired()->end() ->stringNode('database')->isRequired()->end() ->stringNode('collection')->isRequired()->end() - ->stringNode('vector_field')->end() - ->integerNode('dimensions')->end() + ->stringNode('vector_field')->isRequired()->end() + ->integerNode('dimensions')->isRequired()->end() ->stringNode('metric_type')->end() ->end() ->end() @@ -664,7 +667,7 @@ ->stringNode('database')->isRequired()->end() ->stringNode('collection')->isRequired()->end() ->stringNode('index_name')->isRequired()->end() - ->stringNode('vector_field')->end() + ->stringNode('vector_field')->isRequired()->end() ->booleanNode('bulk_write')->end() ->end() ->end() @@ -679,9 +682,9 @@ ->stringNode('database')->cannotBeEmpty()->end() ->stringNode('vector_index_name')->cannotBeEmpty()->end() ->stringNode('node_name')->cannotBeEmpty()->end() - ->stringNode('vector_field')->end() - ->integerNode('dimensions')->end() - ->stringNode('distance')->end() + ->stringNode('vector_field')->isRequired()->end() + ->integerNode('dimensions')->isRequired()->end() + ->stringNode('distance')->isRequired()->end() ->booleanNode('quantization')->end() ->end() ->end() @@ -696,12 +699,40 @@ ->end() ->stringNode('namespace')->end() ->arrayNode('filter') - ->scalarPrototype()->end() + ->scalarPrototype() + ->defaultValue([]) + ->end() ->end() ->integerNode('top_k')->end() ->end() ->end() ->end() + ->arrayNode('postgres') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->stringNode('dsn')->cannotBeEmpty()->end() + ->stringNode('username')->end() + ->stringNode('password')->end() + ->stringNode('table_name')->isRequired()->end() + ->stringNode('vector_field')->isRequired()->end() + ->enumNode('distance') + ->info('Distance metric to use for vector similarity search') + ->enumFqcn(PostgresDistance::class) + ->defaultValue(PostgresDistance::L2) + ->end() + ->stringNode('dbal_connection')->cannotBeEmpty()->end() + ->end() + ->validate() + ->ifTrue(static fn (array $v): bool => !isset($v['dsn']) && !isset($v['dbal_connection'])) + ->thenInvalid('Either "dsn" or "dbal_connection" must be configured.') + ->end() + ->validate() + ->ifTrue(static fn (array $v): bool => isset($v['dsn'], $v['dbal_connection'])) + ->thenInvalid('Either "dsn" or "dbal_connection" can be configured, but not both.') + ->end() + ->end() + ->end() ->arrayNode('qdrant') ->useAttributeAsKey('name') ->arrayPrototype() @@ -709,8 +740,8 @@ ->stringNode('endpoint')->cannotBeEmpty()->end() ->stringNode('api_key')->cannotBeEmpty()->end() ->stringNode('collection_name')->cannotBeEmpty()->end() - ->integerNode('dimensions')->end() - ->stringNode('distance')->end() + ->integerNode('dimensions')->isRequired()->end() + ->stringNode('distance')->isRequired()->end() ->booleanNode('async')->end() ->end() ->end() @@ -736,32 +767,15 @@ ->end() ->end() ->validate() - ->ifTrue(static fn ($v) => !isset($v['connection_parameters']) && !isset($v['client'])) + ->ifTrue(static fn (array $v): bool => !isset($v['connection_parameters']) && !isset($v['client'])) ->thenInvalid('Either "connection_parameters" or "client" must be configured.') ->end() ->validate() - ->ifTrue(static fn ($v) => isset($v['connection_parameters']) && isset($v['client'])) + ->ifTrue(static fn (array $v): bool => isset($v['connection_parameters']) && isset($v['client'])) ->thenInvalid('Either "connection_parameters" or "client" can be configured, but not both.') ->end() ->end() ->end() - ->arrayNode('surreal_db') - ->useAttributeAsKey('name') - ->arrayPrototype() - ->children() - ->stringNode('endpoint')->cannotBeEmpty()->end() - ->stringNode('username')->cannotBeEmpty()->end() - ->stringNode('password')->cannotBeEmpty()->end() - ->stringNode('namespace')->cannotBeEmpty()->end() - ->stringNode('database')->cannotBeEmpty()->end() - ->stringNode('table')->end() - ->stringNode('vector_field')->end() - ->stringNode('strategy')->end() - ->integerNode('dimensions')->end() - ->booleanNode('namespaced_user')->end() - ->end() - ->end() - ->end() ->arrayNode('supabase') ->useAttributeAsKey('name') ->arrayPrototype() @@ -780,51 +794,42 @@ ->end() ->end() ->end() - ->arrayNode('typesense') + ->arrayNode('surrealdb') ->useAttributeAsKey('name') ->arrayPrototype() ->children() ->stringNode('endpoint')->cannotBeEmpty()->end() - ->stringNode('api_key')->isRequired()->end() - ->stringNode('collection')->isRequired()->end() - ->stringNode('vector_field')->end() - ->integerNode('dimensions')->end() + ->stringNode('username')->cannotBeEmpty()->end() + ->stringNode('password')->cannotBeEmpty()->end() + ->stringNode('namespace')->cannotBeEmpty()->end() + ->stringNode('database')->cannotBeEmpty()->end() + ->stringNode('table')->isRequired()->end() + ->stringNode('vector_field')->isRequired()->end() + ->stringNode('strategy')->isRequired()->end() + ->integerNode('dimensions')->isRequired()->end() + ->booleanNode('namespaced_user')->end() ->end() ->end() ->end() - ->arrayNode('weaviate') + ->arrayNode('typesense') ->useAttributeAsKey('name') ->arrayPrototype() ->children() ->stringNode('endpoint')->cannotBeEmpty()->end() ->stringNode('api_key')->isRequired()->end() ->stringNode('collection')->isRequired()->end() + ->stringNode('vector_field')->isRequired()->end() + ->integerNode('dimensions')->isRequired()->end() ->end() ->end() ->end() - ->arrayNode('postgres') + ->arrayNode('weaviate') ->useAttributeAsKey('name') ->arrayPrototype() ->children() - ->stringNode('dsn')->cannotBeEmpty()->end() - ->stringNode('username')->end() - ->stringNode('password')->end() - ->stringNode('table_name')->isRequired()->end() - ->stringNode('vector_field')->end() - ->enumNode('distance') - ->info('Distance metric to use for vector similarity search') - ->enumFqcn(PostgresDistance::class) - ->defaultValue(PostgresDistance::L2) - ->end() - ->stringNode('dbal_connection')->cannotBeEmpty()->end() - ->end() - ->validate() - ->ifTrue(static fn (array $v): bool => !isset($v['dsn']) && !isset($v['dbal_connection'])) - ->thenInvalid('Either "dsn" or "dbal_connection" must be configured.') - ->end() - ->validate() - ->ifTrue(static fn (array $v): bool => isset($v['dsn'], $v['dbal_connection'])) - ->thenInvalid('Either "dsn" or "dbal_connection" can be configured, but not both.') + ->stringNode('endpoint')->cannotBeEmpty()->end() + ->stringNode('api_key')->isRequired()->end() + ->stringNode('collection')->isRequired()->end() ->end() ->end() ->end() @@ -933,7 +938,7 @@ ->end() ->end() ->end() - ->arrayNode('surreal_db') + ->arrayNode('surrealdb') ->useAttributeAsKey('name') ->arrayPrototype() ->children() diff --git a/src/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index a0ccf2de0..c60a15668 100644 --- a/src/ai-bundle/src/AiBundle.php +++ b/src/ai-bundle/src/AiBundle.php @@ -79,10 +79,10 @@ use Symfony\AI\Store\Bridge\ChromaDb\Store as ChromaDbStore; use Symfony\AI\Store\Bridge\ClickHouse\Store as ClickHouseStore; use Symfony\AI\Store\Bridge\Cloudflare\Store as CloudflareStore; -use Symfony\AI\Store\Bridge\Local\CacheStore; +use Symfony\AI\Store\Bridge\Local\CacheStore as LocalCacheStore; use Symfony\AI\Store\Bridge\Local\DistanceCalculator; use Symfony\AI\Store\Bridge\Local\DistanceStrategy; -use Symfony\AI\Store\Bridge\Local\InMemoryStore; +use Symfony\AI\Store\Bridge\Local\InMemoryStore as LocalInMemoryStore; use Symfony\AI\Store\Bridge\Manticore\Store as ManticoreStore; use Symfony\AI\Store\Bridge\MariaDb\Store as MariaDbStore; use Symfony\AI\Store\Bridge\Meilisearch\Store as MeilisearchStore; @@ -971,16 +971,17 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde ]; if (\array_key_exists('strategy', $store) && null !== $store['strategy']) { - $distanceCalculatorDefinition = new Definition(DistanceCalculator::class); - $distanceCalculatorDefinition->setLazy(true); - $distanceCalculatorDefinition->setArgument(0, DistanceStrategy::from($store['strategy'])); + if (!$container->hasDefinition('ai.store.distance_calculator.'.$name)) { + $distanceCalculatorDefinition = new Definition(DistanceCalculator::class); + $distanceCalculatorDefinition->setLazy(true); + $distanceCalculatorDefinition->setArgument(0, DistanceStrategy::from($store['strategy'])); $container->setDefinition('ai.store.distance_calculator.'.$name, $distanceCalculatorDefinition); $arguments[1] = new Reference('ai.store.distance_calculator.'.$name); } - $definition = new Definition(CacheStore::class); + $definition = new Definition(LocalCacheStore::class); $definition ->setLazy(true) ->setArguments($arguments) @@ -1018,6 +1019,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde } else { $httpClient = new Definition(HttpClientInterface::class); $httpClient + ->setLazy(true) ->setFactory([HttpClient::class, 'createForBaseUri']) ->setArguments([$store['dsn']]); } @@ -1046,18 +1048,12 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $store['account_id'], $store['api_key'], $store['index_name'], + $store['dimensions'], + $store['metric'], ]; - if (\array_key_exists('dimensions', $store)) { - $arguments[4] = $store['dimensions']; - } - - if (\array_key_exists('metric', $store)) { - $arguments[5] = $store['metric']; - } - - if (\array_key_exists('endpoint', $store)) { - $arguments[6] = $store['endpoint']; + if (\array_key_exists('endpoint_url', $store)) { + $arguments[6] = $store['endpoint_url']; } $definition = new Definition(CloudflareStore::class); @@ -1079,24 +1075,12 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde new Reference('http_client'), $store['endpoint'], $store['table'], + $store['field'], + $store['type'], + $store['similarity'], + $store['dimensions'], ]; - if (\array_key_exists('field', $store)) { - $arguments[3] = $store['field']; - } - - if (\array_key_exists('type', $store)) { - $arguments[4] = $store['type']; - } - - if (\array_key_exists('similarity', $store)) { - $arguments[5] = $store['similarity']; - } - - if (\array_key_exists('dimensions', $store)) { - $arguments[6] = $store['dimensions']; - } - if (\array_key_exists('quantization', $store)) { $arguments[7] = $store['quantization']; } @@ -1104,9 +1088,9 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $definition = new Definition(ManticoreStore::class); $definition ->setLazy(true) - ->addTag('ai.store') + ->setArguments($arguments) ->addTag('proxy', ['interface' => StoreInterface::class]) - ->setArguments($arguments); + ->addTag('ai.store'); $container->setDefinition('ai.store.'.$type.'.'.$name, $definition); $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $name); @@ -1116,27 +1100,24 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde if ('mariadb' === $type) { foreach ($stores as $name => $store) { - $arguments = [ - new Reference(\sprintf('doctrine.dbal.%s_connection', $store['connection'])), - $store['table_name'], - $store['index_name'], - $store['vector_field_name'], - ]; - $definition = new Definition(MariaDbStore::class); - $definition->setFactory([MariaDbStore::class, 'fromDbal']); $definition ->setLazy(true) - ->setArguments($arguments) + ->setFactory([MariaDbStore::class, 'fromDbal']) + ->setArguments([ + new Reference(\sprintf('doctrine.dbal.%s_connection', $store['connection'])), + $store['table_name'], + $store['index_name'], + $store['vector_field_name'], + ]) ->addTag('proxy', ['interface' => StoreInterface::class]) ->addTag('ai.store'); - $serviceId = 'ai.store.'.$type.'.'.$name; - $container->setDefinition($serviceId, $definition); - $container->registerAliasForArgument($serviceId, StoreInterface::class, $name); - $container->registerAliasForArgument($serviceId, StoreInterface::class, $type.'_'.$name); + $container->setDefinition('ai.store.'.$type.'.'.$name, $definition); + $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $name); + $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name); - $setupStoresOptions[$serviceId] = $store['setup_options'] ?? []; + $setupStoresOptions['ai.store.'.$type.'.'.$name] = $store['setup_options'] ?? []; } } @@ -1147,20 +1128,11 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $store['endpoint'], $store['api_key'], $store['index_name'], + $store['embedder'], + $store['vector_field'], + $store['dimensions'], ]; - if (\array_key_exists('embedder', $store)) { - $arguments[4] = $store['embedder']; - } - - if (\array_key_exists('vector_field', $store)) { - $arguments[5] = $store['vector_field']; - } - - if (\array_key_exists('dimensions', $store)) { - $arguments[6] = $store['dimensions']; - } - if (\array_key_exists('semantic_ratio', $store)) { $arguments[7] = $store['semantic_ratio']; } @@ -1187,6 +1159,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde if (\array_key_exists('strategy', $store) && null !== $store['strategy']) { if (!$container->hasDefinition('ai.store.distance_calculator.'.$name)) { $distanceCalculatorDefinition = new Definition(DistanceCalculator::class); + $distanceCalculatorDefinition->setLazy(true); $distanceCalculatorDefinition->setArgument(0, DistanceStrategy::from($store['strategy'])); $container->setDefinition('ai.store.distance_calculator.'.$name, $distanceCalculatorDefinition); @@ -1195,7 +1168,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $arguments[0] = new Reference('ai.store.distance_calculator.'.$name); } - $definition = new Definition(InMemoryStore::class); + $definition = new Definition(LocalInMemoryStore::class); $definition ->setLazy(true) ->setArguments($arguments) @@ -1216,16 +1189,10 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $store['api_key'], $store['database'], $store['collection'], + $store['vector_field'], + $store['dimensions'], ]; - if (\array_key_exists('vector_field', $store)) { - $arguments[5] = $store['vector_field']; - } - - if (\array_key_exists('dimensions', $store)) { - $arguments[6] = $store['dimensions']; - } - if (\array_key_exists('metric_type', $store)) { $arguments[7] = $store['metric_type']; } @@ -1250,12 +1217,9 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $store['database'], $store['collection'], $store['index_name'], + $store['vector_field'], ]; - if (\array_key_exists('vector_field', $store)) { - $arguments[4] = $store['vector_field']; - } - if (\array_key_exists('bulk_write', $store)) { $arguments[5] = $store['bulk_write']; } @@ -1283,20 +1247,11 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $store['database'], $store['vector_index_name'], $store['node_name'], + $store['vector_field'], + $store['dimensions'], + $store['distance'], ]; - if (\array_key_exists('vector_field', $store)) { - $arguments[7] = $store['vector_field']; - } - - if (\array_key_exists('dimensions', $store)) { - $arguments[8] = $store['dimensions']; - } - - if (\array_key_exists('distance', $store)) { - $arguments[9] = $store['distance']; - } - if (\array_key_exists('quantization', $store)) { $arguments[10] = $store['quantization']; } @@ -1319,12 +1274,9 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $arguments = [ new Reference($store['client']), $store['namespace'], + $store['filter'], ]; - if (\array_key_exists('filter', $store)) { - $arguments[2] = $store['filter']; - } - if (\array_key_exists('top_k', $store)) { $arguments[3] = $store['top_k']; } @@ -1342,6 +1294,48 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde } } + if ('postgres' === $type) { + foreach ($stores as $name => $store) { + $definition = new Definition(PostgresStore::class); + + if (\array_key_exists('dbal_connection', $store)) { + $definition->setFactory([PostgresStore::class, 'fromDbal']); + $arguments = [ + new Reference($store['dbal_connection']), + $store['table_name'], + $store['vector_field'], + ]; + } else { + $pdo = new Definition(\PDO::class); + $pdo->setArguments([ + $store['dsn'], + $store['username'] ?? null, + $store['password'] ?? null], + ); + + $arguments = [ + $pdo, + $store['table_name'], + $store['vector_field'], + ]; + } + + if (\array_key_exists('distance', $store)) { + $arguments[3] = $store['distance']; + } + + $definition + ->setLazy(true) + ->setArguments($arguments) + ->addTag('proxy', ['interface' => StoreInterface::class]) + ->addTag('ai.store'); + + $container->setDefinition('ai.store.'.$type.'.'.$name, $definition); + $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $name); + $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name); + } + } + if ('qdrant' === $type) { foreach ($stores as $name => $store) { $arguments = [ @@ -1349,16 +1343,10 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $store['endpoint'], $store['api_key'], $store['collection_name'], + $store['dimensions'], + $store['distance'], ]; - if (\array_key_exists('dimensions', $store)) { - $arguments[4] = $store['dimensions']; - } - - if (\array_key_exists('distance', $store)) { - $arguments[5] = $store['distance']; - } - if (\array_key_exists('async', $store)) { $arguments[6] = $store['async']; } @@ -1398,71 +1386,59 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde ->addTag('ai.store'); $container->setDefinition('ai.store.'.$type.'.'.$name, $definition); + $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $name); + $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name); } } - if ('surreal_db' === $type) { + if ('supabase' === $type) { foreach ($stores as $name => $store) { $arguments = [ - new Reference('http_client'), - $store['endpoint'], - $store['username'], - $store['password'], - $store['namespace'], - $store['database'], + new Reference($store['http_client']), + $store['url'], + $store['api_key'], + $store['table'] ?? $name, + $store['vector_field'], + $store['vector_dimension'], ]; - if (\array_key_exists('table', $store)) { - $arguments[6] = $store['table']; - } - - if (\array_key_exists('vector_field', $store)) { - $arguments[7] = $store['vector_field']; - } - - if (\array_key_exists('strategy', $store)) { - $arguments[8] = $store['strategy']; - } - - if (\array_key_exists('dimensions', $store)) { - $arguments[9] = $store['dimensions']; - } - - if (\array_key_exists('namespaced_user', $store)) { - $arguments[10] = $store['namespaced_user']; + if (\array_key_exists('function_name', $store)) { + $arguments[6] = $store['function_name']; } - $definition = new Definition(SurrealDbStore::class); + $definition = new Definition(SupabaseStore::class); $definition ->setLazy(true) ->setArguments($arguments) ->addTag('proxy', ['interface' => StoreInterface::class]) ->addTag('ai.store'); - $container->setDefinition('ai.store.'.$type.'.'.$name, $definition); + $container->setDefinition('ai.store.supabase.'.$name, $definition); $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $name); $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name); } } - if ('typesense' === $type) { + if ('surrealdb' === $type) { foreach ($stores as $name => $store) { $arguments = [ new Reference('http_client'), $store['endpoint'], - $store['api_key'], - $store['collection'], + $store['username'], + $store['password'], + $store['namespace'], + $store['database'], + $store['table'] ?? $name, + $store['vector_field'], + $store['strategy'], + $store['dimensions'], ]; - if (\array_key_exists('vector_field', $store)) { - $arguments[4] = $store['vector_field']; - } - - if (\array_key_exists('dimensions', $store)) { - $arguments[5] = $store['dimensions']; + if (\array_key_exists('namespaced_user', $store)) { + $arguments[10] = $store['namespaced_user']; } - $definition = new Definition(TypesenseStore::class); + $definition = new Definition(SurrealDbStore::class); $definition ->setLazy(true) ->setArguments($arguments) @@ -1475,9 +1451,9 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde } } - if ('weaviate' === $type) { + if ('typesense' === $type) { foreach ($stores as $name => $store) { - $definition = new Definition(WeaviateStore::class); + $definition = new Definition(TypesenseStore::class); $definition ->setLazy(true) ->setArguments([ @@ -1485,6 +1461,8 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $store['endpoint'], $store['api_key'], $store['collection'], + $store['vector_field'], + $store['dimensions'], ]) ->addTag('proxy', ['interface' => StoreInterface::class]) ->addTag('ai.store'); @@ -1495,41 +1473,17 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde } } - if ('postgres' === $type) { + if ('weaviate' === $type) { foreach ($stores as $name => $store) { - $definition = new Definition(PostgresStore::class); - - if (\array_key_exists('dbal_connection', $store)) { - $definition->setFactory([PostgresStore::class, 'fromDbal']); - $arguments = [ - new Reference($store['dbal_connection']), - $store['table_name'], - ]; - } else { - $pdo = new Definition(\PDO::class); - $pdo->setArguments([ - $store['dsn'], - $store['username'] ?? null, - $store['password'] ?? null], - ); - - $arguments = [ - $pdo, - $store['table_name'], - ]; - } - - if (\array_key_exists('vector_field', $store)) { - $arguments[2] = $store['vector_field']; - } - - if (\array_key_exists('distance', $store)) { - $arguments[3] = $store['distance']; - } - + $definition = new Definition(WeaviateStore::class); $definition ->setLazy(true) - ->setArguments($arguments) + ->setArguments([ + new Reference('http_client'), + $store['endpoint'], + $store['api_key'], + $store['collection'], + ]) ->addTag('proxy', ['interface' => StoreInterface::class]) ->addTag('ai.store'); @@ -1538,43 +1492,6 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name); } } - - if ('supabase' === $type) { - foreach ($stores as $name => $store) { - $arguments = [ - isset($store['http_client']) ? new Reference($store['http_client']) : new Definition(HttpClientInterface::class), - $store['url'], - $store['api_key'], - ]; - - if (\array_key_exists('table', $store)) { - $arguments[3] = $store['table']; - } - - if (\array_key_exists('vector_field', $store)) { - $arguments[4] = $store['vector_field']; - } - - if (\array_key_exists('vector_dimension', $store)) { - $arguments[5] = $store['vector_dimension']; - } - - if (\array_key_exists('function_name', $store)) { - $arguments[6] = $store['function_name']; - } - - $definition = new Definition(SupabaseStore::class); - $definition - ->setLazy(true) - ->setArguments($arguments) - ->addTag('proxy', ['interface' => StoreInterface::class]) - ->addTag('ai.store'); - - $container->setDefinition('ai.store.supabase.'.$name, $definition); - $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $name); - $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name); - } - } } /** @@ -1649,7 +1566,7 @@ private function processMessageStoreConfig(string $type, array $messageStores, C if ('memory' === $type) { foreach ($messageStores as $name => $messageStore) { - $definition = new Definition(InMemoryStore::class); + $definition = new Definition(LocalInMemoryStore::class); $definition ->setLazy(true) ->setArgument(0, $messageStore['identifier']) @@ -1747,7 +1664,7 @@ private function processMessageStoreConfig(string $type, array $messageStores, C } } - if ('surreal_db' === $type) { + if ('surrealdb' === $type) { foreach ($messageStores as $name => $messageStore) { $arguments = [ new Reference('http_client'), diff --git a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php index 9c0ea37b6..c738f50c0 100644 --- a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php +++ b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php @@ -11,12 +11,13 @@ namespace Symfony\AI\AiBundle\Tests\DependencyInjection; -use Codewithkyrian\ChromaDB\Client; +use Codewithkyrian\ChromaDB\Client as ChromaDbClient; use MongoDB\Client as MongoDbClient; use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; +use Probots\Pinecone\Client as PineconeClient; use Symfony\AI\Agent\AgentInterface; use Symfony\AI\Agent\Memory\MemoryInputProcessor; use Symfony\AI\Agent\Memory\StaticMemoryProvider; @@ -31,9 +32,27 @@ use Symfony\AI\Store\Bridge\Azure\SearchStore as AzureStore; use Symfony\AI\Store\Bridge\ChromaDb\Store as ChromaDbStore; use Symfony\AI\Store\Bridge\ClickHouse\Store as ClickhouseStore; -use Symfony\AI\Store\Bridge\Local\CacheStore; +use Symfony\AI\Store\Bridge\Cloudflare\Store as CloudflareStore; +use Symfony\AI\Store\Bridge\Local\CacheStore as LocalCacheStore; use Symfony\AI\Store\Bridge\Local\DistanceCalculator; use Symfony\AI\Store\Bridge\Local\DistanceStrategy; +use Symfony\AI\Store\Bridge\Local\InMemoryStore as LocalInMemoryStoreAlias; +use Symfony\AI\Store\Bridge\Manticore\Store as ManticoreStore; +use Symfony\AI\Store\Bridge\MariaDb\Store as MariaDbStore; +use Symfony\AI\Store\Bridge\Meilisearch\Store as MeilisearchStore; +use Symfony\AI\Store\Bridge\Milvus\Store as MilvusStore; +use Symfony\AI\Store\Bridge\MongoDb\Store as MongoDbStore; +use Symfony\AI\Store\Bridge\Neo4j\Store as Neo4jStore; +use Symfony\AI\Store\Bridge\Pinecone\Store as PineconeStore; +use Symfony\AI\Store\Bridge\Postgres\Distance; +use Symfony\AI\Store\Bridge\Postgres\Store as PostgresStore; +use Symfony\AI\Store\Bridge\Qdrant\Store as QdrantStore; +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; +use Symfony\AI\Store\Bridge\SurrealDb\Store as SurrealDbStore; +use Symfony\AI\Store\Bridge\Typesense\Store as TypesenseStore; +use Symfony\AI\Store\Bridge\Weaviate\Store as WeaviateStore; use Symfony\AI\Store\Document\Filter\TextContainsFilter; use Symfony\AI\Store\Document\Loader\InMemoryLoader; use Symfony\AI\Store\Document\Transformer\TextTrimTransformer; @@ -41,7 +60,6 @@ use Symfony\AI\Store\Document\VectorizerInterface; use Symfony\AI\Store\IndexerInterface; use Symfony\AI\Store\StoreInterface; -use Symfony\Component\Clock\ClockInterface; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -500,10 +518,10 @@ public function testCacheStoreCanBeConfigured() ]); $this->assertTrue($container->hasDefinition('ai.store.cache.my_cache_store')); - $this->assertTrue($container->hasDefinition('ai.store.distance_calculator.my_cache_store')); + $this->assertFalse($container->hasDefinition('ai.store.distance_calculator.my_cache_store')); $definition = $container->getDefinition('ai.store.cache.my_cache_store'); - $this->assertSame(CacheStore::class, $definition->getClass()); + $this->assertSame(LocalCacheStore::class, $definition->getClass()); $this->assertTrue($definition->isLazy()); $this->assertCount(3, $definition->getArguments()); @@ -511,9 +529,6 @@ public function testCacheStoreCanBeConfigured() $this->assertSame('cache.system', (string) $definition->getArgument(0)); $this->assertSame('my_cache_store', $definition->getArgument(2)); - $strategyDefinition = $container->getDefinition('ai.store.distance_calculator.my_cache_store'); - $this->assertTrue($strategyDefinition->isLazy()); - $this->assertTrue($definition->hasTag('proxy')); $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); $this->assertTrue($definition->hasTag('ai.store')); @@ -544,6 +559,7 @@ public function testCacheStoreWithCustomKeyCanBeConfigured() $definition = $container->getDefinition('ai.store.cache.my_cache_store_with_custom_key'); + $this->assertTrue($definition->isLazy()); $this->assertCount(3, $definition->getArguments()); $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); $this->assertSame('cache.system', (string) $definition->getArgument(0)); @@ -736,149 +752,1258 @@ public function testClickhouseStoreWithCustomHttpClientCanBeConfigured() ], ]); - $this->assertTrue($container->hasDefinition('ai.store.clickhouse.my_clickhouse_store')); + $this->assertTrue($container->hasDefinition('ai.store.clickhouse.my_clickhouse_store')); + + $definition = $container->getDefinition('ai.store.clickhouse.my_clickhouse_store'); + $this->assertSame(ClickhouseStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(3, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('clickhouse.http_client', (string) $definition->getArgument(0)); + $this->assertSame('my_db', (string) $definition->getArgument(1)); + $this->assertSame('my_table', (string) $definition->getArgument(2)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_clickhouse_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myClickhouseStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $clickhouseMyClickhouseStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testClickhouseStoreWithCustomDsnCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'clickhouse' => [ + 'my_clickhouse_store' => [ + 'dsn' => 'http://foo:bar@1.2.3.4:9999', + 'database' => 'my_db', + 'table' => 'my_table', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.clickhouse.my_clickhouse_store')); + + $definition = $container->getDefinition('ai.store.clickhouse.my_clickhouse_store'); + $this->assertSame(ClickhouseStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(3, $definition->getArguments()); + $this->assertInstanceOf(Definition::class, $definition->getArgument(0)); + $this->assertSame(HttpClientInterface::class, $definition->getArgument(0)->getClass()); + $this->assertSame([HttpClient::class, 'createForBaseUri'], $definition->getArgument(0)->getFactory()); + $this->assertSame(['http://foo:bar@1.2.3.4:9999'], $definition->getArgument(0)->getArguments()); + $this->assertSame('my_db', (string) $definition->getArgument(1)); + $this->assertSame('my_table', (string) $definition->getArgument(2)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_clickhouse_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myClickhouseStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $clickhouseMyClickhouseStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testInMemoryStoreWithoutCustomStrategyCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'memory' => [ + 'my_memory_store_with_custom_strategy' => [], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.memory.my_memory_store_with_custom_strategy')); + + $definition = $container->getDefinition('ai.store.memory.my_memory_store_with_custom_strategy'); + $this->assertSame(LocalInMemoryStoreAlias::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(1, $definition->getArguments()); + $this->assertInstanceOf(Definition::class, $definition->getArgument(0)); + $this->assertSame(DistanceCalculator::class, (string) $definition->getArgument(0)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_memory_store_with_custom_strategy')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myMemoryStoreWithCustomStrategy')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $memory_my_memory_store_with_custom_strategy')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $memoryMyMemoryStoreWithCustomStrategy')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testInMemoryStoreWithCustomStrategyCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'memory' => [ + 'my_memory_store_with_custom_strategy' => [ + 'strategy' => 'chebyshev', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.memory.my_memory_store_with_custom_strategy')); + $this->assertTrue($container->hasDefinition('ai.store.distance_calculator.my_memory_store_with_custom_strategy')); + + $definition = $container->getDefinition('ai.store.memory.my_memory_store_with_custom_strategy'); + $this->assertSame(LocalInMemoryStoreAlias::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(1, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('ai.store.distance_calculator.my_memory_store_with_custom_strategy', (string) $definition->getArgument(0)); + + $strategyDefinition = $container->getDefinition('ai.store.distance_calculator.my_memory_store_with_custom_strategy'); + $this->assertSame(DistanceStrategy::CHEBYSHEV_DISTANCE, $strategyDefinition->getArgument(0)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_memory_store_with_custom_strategy')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myMemoryStoreWithCustomStrategy')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $memory_my_memory_store_with_custom_strategy')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $memoryMyMemoryStoreWithCustomStrategy')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testMilvusStoreCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'milvus' => [ + 'my_milvus_store' => [ + 'endpoint' => 'http://127.0.0.1:19530', + 'api_key' => 'foo', + 'database' => 'test', + 'collection' => 'default', + 'vector_field' => '_vectors', + 'dimensions' => 768, + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.milvus.my_milvus_store')); + + $definition = $container->getDefinition('ai.store.milvus.my_milvus_store'); + $this->assertSame(MilvusStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(7, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('http_client', (string) $definition->getArgument(0)); + $this->assertSame('http://127.0.0.1:19530', $definition->getArgument(1)); + $this->assertSame('foo', $definition->getArgument(2)); + $this->assertSame('test', $definition->getArgument(3)); + $this->assertSame('default', $definition->getArgument(4)); + $this->assertSame('_vectors', $definition->getArgument(5)); + $this->assertSame(768, $definition->getArgument(6)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_milvus_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myMilvusStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $milvus_my_milvus_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $milvusMyMilvusStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testMilvusStoreWithCustomMetricsCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'milvus' => [ + 'my_milvus_store' => [ + 'endpoint' => 'http://127.0.0.1:19530', + 'api_key' => 'foo', + 'database' => 'test', + 'collection' => 'default', + 'vector_field' => '_vectors', + 'dimensions' => 768, + 'metric_type' => 'COSINE', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.milvus.my_milvus_store')); + + $definition = $container->getDefinition('ai.store.milvus.my_milvus_store'); + $this->assertSame(MilvusStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(8, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('http_client', (string) $definition->getArgument(0)); + $this->assertSame('http://127.0.0.1:19530', $definition->getArgument(1)); + $this->assertSame('foo', $definition->getArgument(2)); + $this->assertSame('test', $definition->getArgument(3)); + $this->assertSame('default', $definition->getArgument(4)); + $this->assertSame('_vectors', $definition->getArgument(5)); + $this->assertSame(768, $definition->getArgument(6)); + $this->assertSame('COSINE', $definition->getArgument(7)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_milvus_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myMilvusStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $milvus_my_milvus_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $milvusMyMilvusStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testMongoDbStoreCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'mongodb' => [ + 'my_mongo_store' => [ + 'database' => 'my_db', + 'collection' => 'my_collection', + 'index_name' => 'vector_index', + 'vector_field' => 'embedding', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.mongodb.my_mongo_store')); + + $definition = $container->getDefinition('ai.store.mongodb.my_mongo_store'); + $this->assertSame(MongoDbStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(5, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame(MongoDbClient::class, (string) $definition->getArgument(0)); + $this->assertSame('my_db', $definition->getArgument(1)); + $this->assertSame('my_collection', $definition->getArgument(2)); + $this->assertSame('vector_index', $definition->getArgument(3)); + $this->assertSame('embedding', $definition->getArgument(4)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_mongo_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myMongoStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $mongodb_my_mongo_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $mongodbMyMongoStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testMongoDbStoreWithBulkWriteCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'mongodb' => [ + 'my_mongo_store' => [ + 'database' => 'my_db', + 'collection' => 'my_collection', + 'index_name' => 'vector_index', + 'vector_field' => 'embedding', + 'bulk_write' => true, + ], + ], + ], + ], + ]); + $this->assertTrue($container->hasDefinition('ai.store.mongodb.my_mongo_store')); + + $definition = $container->getDefinition('ai.store.mongodb.my_mongo_store'); + $this->assertSame(MongoDbStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(6, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame(MongoDbClient::class, (string) $definition->getArgument(0)); + $this->assertSame('my_db', $definition->getArgument(1)); + $this->assertSame('my_collection', $definition->getArgument(2)); + $this->assertSame('vector_index', $definition->getArgument(3)); + $this->assertSame('embedding', $definition->getArgument(4)); + $this->assertTrue($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('.Symfony\AI\Store\StoreInterface $my_mongo_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myMongoStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $mongodb_my_mongo_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $mongodbMyMongoStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testNeo4jStoreCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'neo4j' => [ + 'my_neo4j_store' => [ + 'endpoint' => 'http://127.0.0.1:8000', + 'username' => 'test', + 'password' => 'test', + 'database' => 'foo', + 'vector_index_name' => 'test', + 'node_name' => 'foo', + 'vector_field' => '_vectors', + 'dimensions' => 768, + 'distance' => 'cosine', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.neo4j.my_neo4j_store')); + + $definition = $container->getDefinition('ai.store.neo4j.my_neo4j_store'); + $this->assertSame(Neo4jStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(10, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('http_client', (string) $definition->getArgument(0)); + $this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1)); + $this->assertSame('test', $definition->getArgument(2)); + $this->assertSame('test', $definition->getArgument(3)); + $this->assertSame('foo', $definition->getArgument(4)); + $this->assertSame('test', $definition->getArgument(5)); + $this->assertSame('foo', $definition->getArgument(6)); + $this->assertSame('_vectors', $definition->getArgument(7)); + $this->assertSame(768, $definition->getArgument(8)); + $this->assertSame('cosine', $definition->getArgument(9)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_neo4j_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myNeo4jStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $neo4j_my_neo4j_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $neo4jMyNeo4jStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testNeo4jStoreWithQuantizationCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'neo4j' => [ + 'my_neo4j_store' => [ + 'endpoint' => 'http://127.0.0.1:8000', + 'username' => 'test', + 'password' => 'test', + 'database' => 'foo', + 'vector_index_name' => 'test', + 'node_name' => 'foo', + 'vector_field' => '_vectors', + 'dimensions' => 768, + 'distance' => 'cosine', + 'quantization' => true, + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.neo4j.my_neo4j_store')); + + $definition = $container->getDefinition('ai.store.neo4j.my_neo4j_store'); + $this->assertSame(Neo4jStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(11, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('http_client', (string) $definition->getArgument(0)); + $this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1)); + $this->assertSame('test', $definition->getArgument(2)); + $this->assertSame('test', $definition->getArgument(3)); + $this->assertSame('foo', $definition->getArgument(4)); + $this->assertSame('test', $definition->getArgument(5)); + $this->assertSame('foo', $definition->getArgument(6)); + $this->assertSame('_vectors', $definition->getArgument(7)); + $this->assertSame(768, $definition->getArgument(8)); + $this->assertSame('cosine', $definition->getArgument(9)); + $this->assertTrue($definition->getArgument(10)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_neo4j_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myNeo4jStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $neo4j_my_neo4j_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $neo4jMyNeo4jStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testPineconeStoreCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'pinecone' => [ + 'my_pinecone_store' => [ + 'namespace' => 'my_namespace', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.pinecone.my_pinecone_store')); + + $definition = $container->getDefinition('ai.store.pinecone.my_pinecone_store'); + $this->assertSame(PineconeStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(3, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame(PineconeClient::class, (string) $definition->getArgument(0)); + $this->assertSame('my_namespace', $definition->getArgument(1)); + $this->assertSame([], $definition->getArgument(2)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_pinecone_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myPineconeStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $pinecone_my_pinecone_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $pineconeMyPineconeStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testPineconeStoreWithFilterCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'pinecone' => [ + 'my_pinecone_store' => [ + 'namespace' => 'my_namespace', + 'filter' => ['category' => 'books'], + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.pinecone.my_pinecone_store')); + + $definition = $container->getDefinition('ai.store.pinecone.my_pinecone_store'); + $this->assertSame(PineconeStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(3, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame(PineconeClient::class, (string) $definition->getArgument(0)); + $this->assertSame('my_namespace', $definition->getArgument(1)); + $this->assertSame(['category' => 'books'], $definition->getArgument(2)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_pinecone_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myPineconeStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $pinecone_my_pinecone_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $pineconeMyPineconeStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testPineconeStoreWithTopKCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'pinecone' => [ + 'my_pinecone_store' => [ + 'namespace' => 'my_namespace', + 'top_k' => 10, + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.pinecone.my_pinecone_store')); + + $definition = $container->getDefinition('ai.store.pinecone.my_pinecone_store'); + $this->assertSame(PineconeStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(4, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame(PineconeClient::class, (string) $definition->getArgument(0)); + $this->assertSame('my_namespace', $definition->getArgument(1)); + $this->assertSame([], $definition->getArgument(2)); + $this->assertSame(10, $definition->getArgument(3)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_pinecone_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myPineconeStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $pinecone_my_pinecone_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $pineconeMyPineconeStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testPostgresStoreWithDifferentConnectionCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'postgres' => [ + 'db' => [ + 'dsn' => 'pgsql:host=localhost;port=5432;dbname=testdb;user=app;password=mypass', + 'table_name' => 'vectors', + 'vector_field' => 'foo', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.postgres.db')); + + $definition = $container->getDefinition('ai.store.postgres.db'); + $this->assertSame(PostgresStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(4, $definition->getArguments()); + $this->assertInstanceOf(Definition::class, $definition->getArgument(0)); + $this->assertSame(\PDO::class, $definition->getArgument(0)->getClass()); + $this->assertSame('vectors', $definition->getArgument(1)); + $this->assertSame('foo', $definition->getArgument(2)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $db')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $postgres_db')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $postgresDb')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'postgres' => [ + 'db' => [ + 'dsn' => 'pgsql:host=localhost;port=5432;dbname=testdb', + 'username' => 'foo', + 'password' => 'bar', + 'table_name' => 'vectors', + 'vector_field' => 'foo', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.postgres.db')); + + $definition = $container->getDefinition('ai.store.postgres.db'); + $this->assertSame(PostgresStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(4, $definition->getArguments()); + $this->assertInstanceOf(Definition::class, $definition->getArgument(0)); + $this->assertSame(\PDO::class, $definition->getArgument(0)->getClass()); + $this->assertSame(['pgsql:host=localhost;port=5432;dbname=testdb', 'foo', 'bar'], $definition->getArgument(0)->getArguments()); + $this->assertSame('vectors', $definition->getArgument(1)); + $this->assertSame('foo', $definition->getArgument(2)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $db')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $postgres_db')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $postgresDb')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'postgres' => [ + 'db' => [ + 'dbal_connection' => 'my_connection', + 'table_name' => 'vectors', + 'vector_field' => 'foo', + ], + ], + ], + ], + ]); + + $definition = $container->getDefinition('ai.store.postgres.db'); + $this->assertSame(PostgresStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(4, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('my_connection', (string) $definition->getArgument(0)); + $this->assertSame('vectors', $definition->getArgument(1)); + $this->assertSame('foo', $definition->getArgument(2)); + $this->assertSame(Distance::L2, $definition->getArgument(3)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $db')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $postgres_db')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $postgresDb')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'postgres' => [ + 'db' => [ + 'dbal_connection' => 'my_connection', + 'table_name' => 'vectors', + 'vector_field' => 'foo', + 'distance' => Distance::L1->value, + ], + ], + ], + ], + ]); + + $definition = $container->getDefinition('ai.store.postgres.db'); + $this->assertSame(PostgresStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(4, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('my_connection', (string) $definition->getArgument(0)); + $this->assertSame('vectors', $definition->getArgument(1)); + $this->assertSame('foo', $definition->getArgument(2)); + $this->assertSame(Distance::L1, $definition->getArgument(3)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $db')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $postgres_db')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $postgresDb')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testQdrantStoreCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'qdrant' => [ + 'my_qdrant_store' => [ + 'endpoint' => 'http://127.0.0.1:8000', + 'api_key' => 'test', + 'collection_name' => 'foo', + 'dimensions' => 768, + 'distance' => 'Cosine', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store')); + + $definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store'); + $this->assertSame(QdrantStore::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('http://127.0.0.1:8000', $definition->getArgument(1)); + $this->assertSame('test', $definition->getArgument(2)); + $this->assertSame('foo', $definition->getArgument(3)); + $this->assertSame(768, $definition->getArgument(4)); + $this->assertSame('Cosine', $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('.Symfony\AI\Store\StoreInterface $my_qdrant_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myQdrantStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $qdrant_my_qdrant_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $qdrantMyQdrantStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testQdrantStoreWithAsyncCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'qdrant' => [ + 'my_qdrant_store' => [ + 'endpoint' => 'http://127.0.0.1:8000', + 'api_key' => 'test', + 'collection_name' => 'foo', + 'dimensions' => 768, + 'distance' => 'Cosine', + 'async' => true, + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.qdrant.my_qdrant_store')); + + $definition = $container->getDefinition('ai.store.qdrant.my_qdrant_store'); + $this->assertSame(QdrantStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(7, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('http_client', (string) $definition->getArgument(0)); + $this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1)); + $this->assertSame('test', $definition->getArgument(2)); + $this->assertSame('foo', $definition->getArgument(3)); + $this->assertSame(768, $definition->getArgument(4)); + $this->assertSame('Cosine', $definition->getArgument(5)); + $this->assertTrue($definition->getArgument(6)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_qdrant_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myQdrantStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $qdrant_my_qdrant_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $qdrantMyQdrantStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testRedisStoreCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'redis' => [ + 'my_redis_store' => [ + 'connection_parameters' => [ + 'host' => '1.2.3.4', + 'port' => 6379, + ], + 'index_name' => 'my_vector_index', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.redis.my_redis_store')); + + $definition = $container->getDefinition('ai.store.redis.my_redis_store'); + $this->assertSame(RedisStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(4, $definition->getArguments()); + $this->assertInstanceOf(Definition::class, $definition->getArgument(0)); + $this->assertSame(\Redis::class, $definition->getArgument(0)->getClass()); + $this->assertSame('my_vector_index', $definition->getArgument(1)); + $this->assertSame('vector:', $definition->getArgument(2)); + $this->assertSame(RedisDistance::Cosine, $definition->getArgument(3)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_redis_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myRedisStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $redis_my_redis_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $redisMyRedisStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testRedisStoreWithCustomClientCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'redis' => [ + 'my_redis_store' => [ + 'client' => 'foo', + 'index_name' => 'my_vector_index', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.redis.my_redis_store')); + + $definition = $container->getDefinition('ai.store.redis.my_redis_store'); + $this->assertSame(RedisStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(4, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('foo', (string) $definition->getArgument(0)); + $this->assertSame('my_vector_index', $definition->getArgument(1)); + $this->assertSame('vector:', $definition->getArgument(2)); + $this->assertSame(RedisDistance::Cosine, $definition->getArgument(3)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_redis_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myRedisStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $redis_my_redis_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $redisMyRedisStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testRedisStoreWithCustomKeyPrefixCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'redis' => [ + 'my_redis_store' => [ + 'client' => 'foo', + 'index_name' => 'my_vector_index', + 'key_prefix' => 'foo:', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.redis.my_redis_store')); + + $definition = $container->getDefinition('ai.store.redis.my_redis_store'); + $this->assertSame(RedisStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(4, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('foo', (string) $definition->getArgument(0)); + $this->assertSame('my_vector_index', $definition->getArgument(1)); + $this->assertSame('foo:', $definition->getArgument(2)); + $this->assertSame(RedisDistance::Cosine, $definition->getArgument(3)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_redis_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myRedisStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $redis_my_redis_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $redisMyRedisStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testRedisStoreWithCustomDistanceCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'redis' => [ + 'my_redis_store' => [ + 'client' => 'foo', + 'index_name' => 'my_vector_index', + 'distance' => RedisDistance::L2, + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.redis.my_redis_store')); + + $definition = $container->getDefinition('ai.store.redis.my_redis_store'); + $this->assertSame(RedisStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(4, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('foo', (string) $definition->getArgument(0)); + $this->assertSame('my_vector_index', $definition->getArgument(1)); + $this->assertSame('vector:', $definition->getArgument(2)); + $this->assertSame(RedisDistance::L2, $definition->getArgument(3)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_redis_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myRedisStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $redis_my_redis_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $redisMyRedisStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testSupabaseStoreCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'supabase' => [ + 'my_supabase_store' => [ + 'url' => 'https://test.supabase.co', + 'api_key' => 'supabase_test_key', + 'table' => 'my_supabase_table', + 'vector_field' => 'my_embedding', + 'vector_dimension' => 1024, + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.supabase.my_supabase_store')); + + $definition = $container->getDefinition('ai.store.supabase.my_supabase_store'); + $this->assertSame(SupabaseStore::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://test.supabase.co', $definition->getArgument(1)); + $this->assertSame('supabase_test_key', $definition->getArgument(2)); + $this->assertSame('my_supabase_table', $definition->getArgument(3)); + $this->assertSame('my_embedding', $definition->getArgument(4)); + $this->assertSame(1024, $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('.Symfony\AI\Store\StoreInterface $my_supabase_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $mySupabaseStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $supabase_my_supabase_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $supabaseMySupabaseStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testSupabaseStoreWithCustomHttpClientCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'supabase' => [ + 'my_supabase_store' => [ + 'http_client' => 'foo', + 'url' => 'https://test.supabase.co', + 'api_key' => 'supabase_test_key', + 'table' => 'my_supabase_table', + 'vector_field' => 'my_embedding', + 'vector_dimension' => 1024, + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.supabase.my_supabase_store')); - $definition = $container->getDefinition('ai.store.clickhouse.my_clickhouse_store'); - $this->assertSame(ClickhouseStore::class, $definition->getClass()); + $definition = $container->getDefinition('ai.store.supabase.my_supabase_store'); + $this->assertSame(SupabaseStore::class, $definition->getClass()); $this->assertTrue($definition->isLazy()); - $this->assertCount(3, $definition->getArguments()); + $this->assertCount(6, $definition->getArguments()); $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); - $this->assertSame('clickhouse.http_client', (string) $definition->getArgument(0)); - $this->assertSame('my_db', (string) $definition->getArgument(1)); - $this->assertSame('my_table', (string) $definition->getArgument(2)); + $this->assertSame('foo', (string) $definition->getArgument(0)); + $this->assertSame('https://test.supabase.co', $definition->getArgument(1)); + $this->assertSame('supabase_test_key', $definition->getArgument(2)); + $this->assertSame('my_supabase_table', $definition->getArgument(3)); + $this->assertSame('my_embedding', $definition->getArgument(4)); + $this->assertSame(1024, $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('.Symfony\AI\Store\StoreInterface $my_clickhouse_store')); - $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myClickhouseStore')); - $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $clickhouseMyClickhouseStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_supabase_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $mySupabaseStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $supabase_my_supabase_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $supabaseMySupabaseStore')); $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); } - public function testClickhouseStoreWithCustomDsnCanBeConfigured() + public function testSupabaseStoreWithCustomFunctionCanBeConfigured() { $container = $this->buildContainer([ 'ai' => [ 'store' => [ - 'clickhouse' => [ - 'my_clickhouse_store' => [ - 'dsn' => 'http://foo:bar@1.2.3.4:9999', - 'database' => 'my_db', - 'table' => 'my_table', + 'supabase' => [ + 'my_supabase_store' => [ + 'url' => 'https://test.supabase.co', + 'api_key' => 'supabase_test_key', + 'table' => 'my_supabase_table', + 'vector_field' => 'my_embedding', + 'vector_dimension' => 1024, + 'function_name' => 'my_custom_function', ], ], ], ], ]); - $this->assertTrue($container->hasDefinition('ai.store.clickhouse.my_clickhouse_store')); + $this->assertTrue($container->hasDefinition('ai.store.supabase.my_supabase_store')); - $definition = $container->getDefinition('ai.store.clickhouse.my_clickhouse_store'); - $this->assertSame(ClickhouseStore::class, $definition->getClass()); + $definition = $container->getDefinition('ai.store.supabase.my_supabase_store'); + $this->assertSame(SupabaseStore::class, $definition->getClass()); $this->assertTrue($definition->isLazy()); - $this->assertCount(3, $definition->getArguments()); - $this->assertInstanceOf(Definition::class, $definition->getArgument(0)); - $this->assertSame(HttpClientInterface::class, $definition->getArgument(0)->getClass()); - $this->assertSame([HttpClient::class, 'createForBaseUri'], $definition->getArgument(0)->getFactory()); - $this->assertSame(['http://foo:bar@1.2.3.4:9999'], $definition->getArgument(0)->getArguments()); - $this->assertSame('my_db', (string) $definition->getArgument(1)); - $this->assertSame('my_table', (string) $definition->getArgument(2)); + $this->assertCount(7, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('http_client', (string) $definition->getArgument(0)); + $this->assertSame('https://test.supabase.co', $definition->getArgument(1)); + $this->assertSame('supabase_test_key', $definition->getArgument(2)); + $this->assertSame('my_supabase_table', $definition->getArgument(3)); + $this->assertSame('my_embedding', $definition->getArgument(4)); + $this->assertSame(1024, $definition->getArgument(5)); + $this->assertSame('my_custom_function', $definition->getArgument(6)); $this->assertTrue($definition->hasTag('proxy')); $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); $this->assertTrue($definition->hasTag('ai.store')); - $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_clickhouse_store')); - $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myClickhouseStore')); - $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $clickhouseMyClickhouseStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_supabase_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $mySupabaseStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $supabase_my_supabase_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $supabaseMySupabaseStore')); $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); } - public function testInMemoryStoreWithoutCustomStrategyCanBeConfigured() + public function testSurrealDbStoreCanBeConfigured() { $container = $this->buildContainer([ 'ai' => [ 'store' => [ - 'memory' => [ - 'my_memory_store_with_custom_strategy' => [], + 'surrealdb' => [ + 'my_surrealdb_store' => [ + 'endpoint' => 'http://127.0.0.1:8000', + 'username' => 'test', + 'password' => 'test', + 'namespace' => 'foo', + 'database' => 'bar', + 'table' => 'bar', + 'vector_field' => '_vectors', + 'strategy' => 'cosine', + 'dimensions' => 768, + ], ], ], ], ]); - $this->assertTrue($container->hasDefinition('ai.store.memory.my_memory_store_with_custom_strategy')); + $this->assertTrue($container->hasDefinition('ai.store.surrealdb.my_surrealdb_store')); - $definition = $container->getDefinition('ai.store.memory.my_memory_store_with_custom_strategy'); - $this->assertCount(1, $definition->getArguments()); - $this->assertInstanceOf(Definition::class, $definition->getArgument(0)); - $this->assertSame(DistanceCalculator::class, $definition->getArgument(0)->getClass()); + $definition = $container->getDefinition('ai.store.surrealdb.my_surrealdb_store'); + $this->assertSame(SurrealDbStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(10, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('http_client', (string) $definition->getArgument(0)); + $this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1)); + $this->assertSame('test', $definition->getArgument(2)); + $this->assertSame('test', $definition->getArgument(3)); + $this->assertSame('foo', $definition->getArgument(4)); + $this->assertSame('bar', $definition->getArgument(5)); + $this->assertSame('bar', $definition->getArgument(6)); + $this->assertSame('_vectors', $definition->getArgument(7)); + $this->assertSame('cosine', $definition->getArgument(8)); + $this->assertSame(768, $definition->getArgument(9)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_surrealdb_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $mySurrealdbStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $surrealdb_my_surrealdb_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $surrealdbMySurrealdbStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); } - public function testInMemoryStoreWithCustomStrategyCanBeConfigured() + public function testSurrealDbStoreWithNamespacedUserCanBeConfigured() { $container = $this->buildContainer([ 'ai' => [ 'store' => [ - 'memory' => [ - 'my_memory_store_with_custom_strategy' => [ - 'strategy' => 'chebyshev', + 'surrealdb' => [ + 'my_surrealdb_store' => [ + 'endpoint' => 'http://127.0.0.1:8000', + 'username' => 'test', + 'password' => 'test', + 'namespace' => 'foo', + 'database' => 'bar', + 'table' => 'bar', + 'vector_field' => '_vectors', + 'strategy' => 'cosine', + 'dimensions' => 768, + 'namespaced_user' => true, ], ], ], ], ]); - $this->assertTrue($container->hasDefinition('ai.store.memory.my_memory_store_with_custom_strategy')); - $this->assertTrue($container->hasDefinition('ai.store.distance_calculator.my_memory_store_with_custom_strategy')); + $this->assertTrue($container->hasDefinition('ai.store.surrealdb.my_surrealdb_store')); - $definition = $container->getDefinition('ai.store.memory.my_memory_store_with_custom_strategy'); + $definition = $container->getDefinition('ai.store.surrealdb.my_surrealdb_store'); + $this->assertSame(SurrealDbStore::class, $definition->getClass()); - $this->assertCount(1, $definition->getArguments()); + $this->assertTrue($definition->isLazy()); + $this->assertCount(11, $definition->getArguments()); $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); - $this->assertSame('ai.store.distance_calculator.my_memory_store_with_custom_strategy', (string) $definition->getArgument(0)); + $this->assertSame('http_client', (string) $definition->getArgument(0)); + $this->assertSame('http://127.0.0.1:8000', $definition->getArgument(1)); + $this->assertSame('test', $definition->getArgument(2)); + $this->assertSame('test', $definition->getArgument(3)); + $this->assertSame('foo', $definition->getArgument(4)); + $this->assertSame('bar', $definition->getArgument(5)); + $this->assertSame('bar', $definition->getArgument(6)); + $this->assertSame('_vectors', $definition->getArgument(7)); + $this->assertSame('cosine', $definition->getArgument(8)); + $this->assertSame(768, $definition->getArgument(9)); + $this->assertTrue($definition->getArgument(10)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_surrealdb_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $mySurrealdbStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $surrealdb_my_surrealdb_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $surrealdbMySurrealdbStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); } - public function testPostgresStoreWithDifferentConnectionCanBeConfigured() + public function testTypesenseStoreCanBeConfigured() { $container = $this->buildContainer([ 'ai' => [ 'store' => [ - 'postgres' => [ - 'db' => [ - 'dsn' => 'pgsql:host=localhost;port=5432;dbname=testdb;user=app;password=mypass', - 'table_name' => 'vectors', + 'typesense' => [ + 'my_typesense_store' => [ + 'endpoint' => 'http://localhost:8108', + 'api_key' => 'foo', + 'collection' => 'my_collection', + 'vector_field' => 'vector', + 'dimensions' => 768, ], ], ], ], ]); - $this->assertTrue($container->hasDefinition('ai.store.postgres.db')); + $this->assertTrue($container->hasDefinition('ai.store.typesense.my_typesense_store')); - $definition = $container->getDefinition('ai.store.postgres.db'); - $this->assertCount(3, $definition->getArguments()); - $this->assertInstanceOf(Definition::class, $definition->getArgument(0)); + $definition = $container->getDefinition('ai.store.typesense.my_typesense_store'); + $this->assertSame(TypesenseStore::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('http://localhost:8108', $definition->getArgument(1)); + $this->assertSame('foo', $definition->getArgument(2)); + $this->assertSame('my_collection', $definition->getArgument(3)); + $this->assertSame('vector', $definition->getArgument(4)); + $this->assertSame(768, $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('.Symfony\AI\Store\StoreInterface $my_typesense_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myTypesenseStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $typesense_my_typesense_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $typesenseMyTypesenseStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + public function testWevaviateStoreCanBeConfigured() + { $container = $this->buildContainer([ 'ai' => [ 'store' => [ - 'postgres' => [ - 'db' => [ - 'dbal_connection' => 'my_connection', - 'table_name' => 'vectors', + 'weaviate' => [ + 'my_weaviate_store' => [ + 'endpoint' => 'http://localhost:8080', + 'api_key' => 'bar', + 'collection' => 'my_weaviate_collection', ], ], ], ], ]); - $definition = $container->getDefinition('ai.store.postgres.db'); - $this->assertCount(3, $definition->getArguments()); + $this->assertTrue($container->hasDefinition('ai.store.weaviate.my_weaviate_store')); + + $definition = $container->getDefinition('ai.store.weaviate.my_weaviate_store'); + $this->assertSame(WeaviateStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(4, $definition->getArguments()); $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('http_client', (string) $definition->getArgument(0)); + $this->assertSame('http://localhost:8080', $definition->getArgument(1)); + $this->assertSame('bar', $definition->getArgument(2)); + $this->assertSame('my_weaviate_collection', $definition->getArgument(3)); + + $this->assertTrue($definition->hasTag('proxy')); + $this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy')); + $this->assertTrue($definition->hasTag('ai.store')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_weaviate_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myWeaviateStore')); + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $weaviate_my_weaviate_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $weaviateMyWeaviateStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); } public function testConfigurationWithUseAttributeAsKeyWorksWithoutNormalizeKeys() @@ -908,6 +2033,7 @@ public function testConfigurationWithUseAttributeAsKeyWorksWithoutNormalizeKeys( 'database' => 'test_db', 'collection' => 'test_collection', 'index_name' => 'test_index', + 'vector_field' => 'foo', ], ], ], @@ -3410,63 +4536,6 @@ public function testDoctrineDbalMessageStoreWithCustomTableNameCanBeConfiguredWi $this->assertTrue($doctrineDbalDefaultMessageStoreDefinition->hasTag('ai.message_store')); } - public function testMeilisearchMessageStoreIsConfigured() - { - $container = $this->buildContainer([ - 'ai' => [ - 'message_store' => [ - 'meilisearch' => [ - 'custom' => [ - 'endpoint' => 'http://127.0.0.1:7700', - 'api_key' => 'foo', - 'index_name' => 'test', - ], - ], - ], - ], - ]); - - $meilisearchMessageStoreDefinition = $container->getDefinition('ai.message_store.meilisearch.custom'); - - $this->assertTrue($meilisearchMessageStoreDefinition->isLazy()); - $this->assertCount(5, $meilisearchMessageStoreDefinition->getArguments()); - $this->assertSame('http://127.0.0.1:7700', $meilisearchMessageStoreDefinition->getArgument(0)); - $this->assertSame('foo', $meilisearchMessageStoreDefinition->getArgument(1)); - $this->assertInstanceOf(Reference::class, $meilisearchMessageStoreDefinition->getArgument(2)); - $this->assertSame(ClockInterface::class, (string) $meilisearchMessageStoreDefinition->getArgument(2)); - $this->assertSame('test', $meilisearchMessageStoreDefinition->getArgument(3)); - $this->assertInstanceOf(Reference::class, $meilisearchMessageStoreDefinition->getArgument(4)); - $this->assertSame('serializer', (string) $meilisearchMessageStoreDefinition->getArgument(4)); - - $this->assertTrue($meilisearchMessageStoreDefinition->hasTag('proxy')); - $this->assertSame([['interface' => MessageStoreInterface::class]], $meilisearchMessageStoreDefinition->getTag('proxy')); - $this->assertTrue($meilisearchMessageStoreDefinition->hasTag('ai.message_store')); - } - - #[TestDox('Meilisearch store with custom semantic_ratio can be configured')] - public function testMeilisearchStoreWithCustomSemanticRatioCanBeConfigured() - { - $container = $this->buildContainer([ - 'ai' => [ - 'store' => [ - 'meilisearch' => [ - 'test_store' => [ - 'endpoint' => 'http://127.0.0.1:7700', - 'api_key' => 'test_key', - 'index_name' => 'test_index', - 'semantic_ratio' => 0.5, - ], - ], - ], - ], - ]); - - $this->assertTrue($container->hasDefinition('ai.store.meilisearch.test_store')); - $definition = $container->getDefinition('ai.store.meilisearch.test_store'); - $arguments = $definition->getArguments(); - $this->assertSame(0.5, $arguments[7]); - } - public function testMemoryMessageStoreCanBeConfiguredWithCustomKey() { $container = $this->buildContainer([ @@ -3680,7 +4749,7 @@ public function testSurrealDbMessageStoreIsConfiguredWithoutCustomTable() $container = $this->buildContainer([ 'ai' => [ 'message_store' => [ - 'surreal_db' => [ + 'surrealdb' => [ 'custom' => [ 'endpoint' => 'http://127.0.0.1:8000', 'username' => 'test', @@ -3693,7 +4762,7 @@ public function testSurrealDbMessageStoreIsConfiguredWithoutCustomTable() ], ]); - $surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom'); + $surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surrealdb.custom'); $this->assertTrue($surrealDbMessageStoreDefinition->isLazy()); $this->assertCount(8, $surrealDbMessageStoreDefinition->getArguments()); @@ -3718,7 +4787,7 @@ public function testSurrealDbMessageStoreIsConfiguredWithCustomTable() $container = $this->buildContainer([ 'ai' => [ 'message_store' => [ - 'surreal_db' => [ + 'surrealdb' => [ 'custom' => [ 'endpoint' => 'http://127.0.0.1:8000', 'username' => 'test', @@ -3732,7 +4801,7 @@ public function testSurrealDbMessageStoreIsConfiguredWithCustomTable() ], ]); - $surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom'); + $surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surrealdb.custom'); $this->assertTrue($surrealDbMessageStoreDefinition->isLazy()); $this->assertCount(8, $surrealDbMessageStoreDefinition->getArguments()); @@ -3757,7 +4826,7 @@ public function testSurrealDbMessageStoreIsConfiguredWithNamespacedUser() $container = $this->buildContainer([ 'ai' => [ 'message_store' => [ - 'surreal_db' => [ + 'surrealdb' => [ 'custom' => [ 'endpoint' => 'http://127.0.0.1:8000', 'username' => 'test', @@ -3771,7 +4840,7 @@ public function testSurrealDbMessageStoreIsConfiguredWithNamespacedUser() ], ]); - $surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom'); + $surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surrealdb.custom'); $this->assertTrue($surrealDbMessageStoreDefinition->isLazy()); $this->assertCount(9, $surrealDbMessageStoreDefinition->getArguments()); @@ -4102,6 +5171,14 @@ private function getFullConfig(): array 'type' => 'hnsw', 'similarity' => 'cosine', 'dimensions' => 768, + ], + 'my_manticore_store_with_quantization' => [ + 'endpoint' => 'http://127.0.0.1:9306', + 'table' => 'test', + 'field' => 'foo_vector', + 'type' => 'hnsw', + 'similarity' => 'cosine', + 'dimensions' => 768, 'quantization' => '1bit', ], ], @@ -4172,6 +5249,24 @@ private function getFullConfig(): array 'filter' => ['category' => 'books'], 'top_k' => 10, ], + 'my_pinecone_store_with_filter' => [ + 'namespace' => 'my_namespace', + 'filter' => ['category' => 'books'], + ], + 'my_pinecone_store_with_top_k' => [ + 'namespace' => 'my_namespace', + 'filter' => ['category' => 'books'], + 'top_k' => 10, + ], + ], + 'postgres' => [ + 'my_postgres_store' => [ + 'dsn' => 'pgsql:host=127.0.0.1;port=5432;dbname=postgresql_db', + 'username' => 'postgres', + 'password' => 'pass', + 'table_name' => 'my_table', + 'vector_field' => 'my_embedding', + ], ], 'qdrant' => [ 'my_qdrant_store' => [ @@ -4187,17 +5282,21 @@ private function getFullConfig(): array 'api_key' => 'test', 'collection_name' => 'foo', 'dimensions' => 768, + 'distance' => 'Cosine', ], 'my_custom_distance_qdrant_store' => [ 'endpoint' => 'http://127.0.0.1:8000', 'api_key' => 'test', 'collection_name' => 'foo', + 'dimensions' => 768, 'distance' => 'Cosine', ], 'my_async_qdrant_store' => [ 'endpoint' => 'http://127.0.0.1:8000', 'api_key' => 'test', 'collection_name' => 'foo', + 'dimensions' => 768, + 'distance' => 'Cosine', 'async' => false, ], ], @@ -4209,9 +5308,49 @@ private function getFullConfig(): array ], 'index_name' => 'my_vector_index', ], + 'my_redis_store_with_custom_client' => [ + 'client' => 'foo', + 'index_name' => 'my_vector_index', + ], + 'my_redis_store_with_custom_key_prefix' => [ + 'client' => 'foo', + 'index_name' => 'my_vector_index', + 'key_prefix' => 'foo:', + ], + 'my_redis_store_with_custom_distance' => [ + 'client' => 'foo', + 'index_name' => 'my_vector_index', + 'distance' => RedisDistance::L2, + ], + ], + 'supabase' => [ + 'my_supabase_store' => [ + 'url' => 'https://test.supabase.co', + 'api_key' => 'supabase_test_key', + 'table' => 'my_supabase_table', + 'vector_field' => 'my_embedding', + 'vector_dimension' => 1024, + 'function_name' => 'my_match_function', + ], + 'my_supabase_store_with_custom_http_client' => [ + 'http_client' => 'foo', + 'url' => 'https://test.supabase.co', + 'api_key' => 'supabase_test_key', + 'table' => 'my_supabase_table', + 'vector_field' => 'my_embedding', + 'vector_dimension' => 1024, + ], + 'my_supabase_store_with_custom_function' => [ + 'url' => 'https://test.supabase.co', + 'api_key' => 'supabase_test_key', + 'table' => 'my_supabase_table', + 'vector_field' => 'my_embedding', + 'vector_dimension' => 1024, + 'function_name' => 'foo', + ], ], - 'surreal_db' => [ - 'my_surreal_db_store' => [ + 'surrealdb' => [ + 'my_surrealdb_store' => [ 'endpoint' => 'http://127.0.0.1:8000', 'username' => 'test', 'password' => 'test', @@ -4224,16 +5363,6 @@ private function getFullConfig(): array 'namespaced_user' => true, ], ], - 'supabase' => [ - 'my_supabase_store' => [ - 'url' => 'https://test.supabase.co', - 'api_key' => 'supabase_test_key', - 'table' => 'my_supabase_table', - 'vector_field' => 'my_embedding', - 'vector_dimension' => 1024, - 'function_name' => 'my_match_function', - ], - ], 'typesense' => [ 'my_typesense_store' => [ 'endpoint' => 'http://localhost:8108', @@ -4250,15 +5379,6 @@ private function getFullConfig(): array 'collection' => 'my_weaviate_collection', ], ], - 'postgres' => [ - 'my_postgres_store' => [ - 'dsn' => 'pgsql:host=127.0.0.1;port=5432;dbname=postgresql_db', - 'username' => 'postgres', - 'password' => 'pass', - 'table_name' => 'my_table', - 'vector_field' => 'my_embedding', - ], - ], ], 'message_store' => [ 'cache' => [ @@ -4322,8 +5442,8 @@ private function getFullConfig(): array 'identifier' => 'session', ], ], - 'surreal_db' => [ - 'my_surreal_db_message_store' => [ + 'surrealdb' => [ + 'my_surrealdb_message_store' => [ 'endpoint' => 'http://127.0.0.1:8000', 'username' => 'test', 'password' => 'test', @@ -4331,7 +5451,7 @@ private function getFullConfig(): array 'database' => 'bar', 'namespaced_user' => true, ], - 'my_surreal_db_message_store_with_custom_table' => [ + 'my_surrealdb_message_store_with_custom_table' => [ 'endpoint' => 'http://127.0.0.1:8000', 'username' => 'test', 'password' => 'test',