diff --git a/src/ai-bundle/composer.json b/src/ai-bundle/composer.json index 2ce1f3397..5c92c4743 100644 --- a/src/ai-bundle/composer.json +++ b/src/ai-bundle/composer.json @@ -27,6 +27,7 @@ "symfony/string": "^7.3|^8.0" }, "require-dev": { + "codewithkyrian/chromadb-php": "^0.2.1 || ^0.3 || ^0.4", "google/auth": "^1.47", "mongodb/mongodb": "^1.21 || ^2.0", "phpstan/phpstan": "^2.1", diff --git a/src/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index 0228d8c1b..145db9cd0 100644 --- a/src/ai-bundle/src/AiBundle.php +++ b/src/ai-bundle/src/AiBundle.php @@ -960,19 +960,23 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde if ('cache' === $type) { foreach ($stores as $name => $store) { + $distanceCalculatorDefinition = new Definition(DistanceCalculator::class); + $distanceCalculatorDefinition->setLazy(true); + + $container->setDefinition('ai.store.distance_calculator.'.$name, $distanceCalculatorDefinition); + $arguments = [ new Reference($store['service']), - new Definition(DistanceCalculator::class), + new Reference('ai.store.distance_calculator.'.$name), $store['cache_key'] ?? $name, ]; if (\array_key_exists('strategy', $store) && null !== $store['strategy']) { - if (!$container->hasDefinition('ai.store.distance_calculator.'.$name)) { - $distanceCalculatorDefinition = new Definition(DistanceCalculator::class); - $distanceCalculatorDefinition->setArgument(0, DistanceStrategy::from($store['strategy'])); + $distanceCalculatorDefinition = new Definition(DistanceCalculator::class); + $distanceCalculatorDefinition->setLazy(true); + $distanceCalculatorDefinition->setArgument(0, DistanceStrategy::from($store['strategy'])); - $container->setDefinition('ai.store.distance_calculator.'.$name, $distanceCalculatorDefinition); - } + $container->setDefinition('ai.store.distance_calculator.'.$name, $distanceCalculatorDefinition); $arguments[1] = new Reference('ai.store.distance_calculator.'.$name); } @@ -1016,8 +1020,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $httpClient = new Definition(HttpClientInterface::class); $httpClient ->setFactory([HttpClient::class, 'createForBaseUri']) - ->setArguments([$store['dsn']]) - ; + ->setArguments([$store['dsn']]); } $definition = new Definition(ClickHouseStore::class); @@ -1029,8 +1032,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde $store['table'], ]) ->addTag('proxy', ['interface' => StoreInterface::class]) - ->addTag('ai.store') - ; + ->addTag('ai.store'); $container->setDefinition('ai.store.'.$type.'.'.$name, $definition); $container->registerAliasForArgument('ai.store.'.$type.'.'.$name, StoreInterface::class, $name); diff --git a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php index abce25d2e..1bca5687a 100644 --- a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php +++ b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php @@ -11,6 +11,7 @@ namespace Symfony\AI\AiBundle\Tests\DependencyInjection; +use Codewithkyrian\ChromaDB\Client; use MongoDB\Client as MongoDbClient; use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use PHPUnit\Framework\Attributes\TestDox; @@ -27,7 +28,12 @@ use Symfony\AI\Platform\Bridge\Ollama\OllamaApiCatalog; use Symfony\AI\Platform\Capability; use Symfony\AI\Platform\Model; +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\Local\DistanceCalculator; +use Symfony\AI\Store\Bridge\Local\DistanceStrategy; use Symfony\AI\Store\Document\Filter\TextContainsFilter; use Symfony\AI\Store\Document\Loader\InMemoryLoader; use Symfony\AI\Store\Document\Transformer\TextTrimTransformer; @@ -42,6 +48,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\Translation\TranslatableMessage; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -386,13 +393,136 @@ public function testAgentsAsToolsCannotDefineService() ]); } + public function testAzureStoreCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'azure_search' => [ + 'my_azure_search_store' => [ + 'endpoint' => 'https://mysearch.search.windows.net', + 'api_key' => 'azure_search_key', + 'index_name' => 'my-documents', + 'api_version' => '2023-11-01', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.azure_search.my_azure_search_store')); + + $definition = $container->getDefinition('ai.store.azure_search.my_azure_search_store'); + $this->assertSame(AzureStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(5, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('http_client', (string) $definition->getArgument(0)); + $this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(1)); + $this->assertSame('azure_search_key', $definition->getArgument(2)); + $this->assertSame('my-documents', $definition->getArgument(3)); + $this->assertSame('2023-11-01', $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_azure_search_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myAzureSearchStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $azureSearchMyAzureSearchStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testAzureStoreCanBeConfiguredWithCustomVectorField() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'azure_search' => [ + 'my_azure_search_store' => [ + 'endpoint' => 'https://mysearch.search.windows.net', + 'api_key' => 'azure_search_key', + 'index_name' => 'my-documents', + 'api_version' => '2023-11-01', + 'vector_field' => 'foo', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.azure_search.my_azure_search_store')); + + $definition = $container->getDefinition('ai.store.azure_search.my_azure_search_store'); + $this->assertSame(AzureStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(6, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('http_client', (string) $definition->getArgument(0)); + $this->assertSame('https://mysearch.search.windows.net', $definition->getArgument(1)); + $this->assertSame('azure_search_key', $definition->getArgument(2)); + $this->assertSame('my-documents', $definition->getArgument(3)); + $this->assertSame('2023-11-01', $definition->getArgument(4)); + $this->assertSame('foo', $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_azure_search_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myAzureSearchStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $azureSearchMyAzureSearchStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testCacheStoreCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'cache' => [ + 'my_cache_store' => [ + 'service' => 'cache.system', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.cache.my_cache_store')); + $this->assertTrue($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->assertTrue($definition->isLazy()); + $this->assertCount(3, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $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')); + + $this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_cache_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myCacheStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $cacheMyCacheStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + public function testCacheStoreWithCustomKeyCanBeConfigured() { $container = $this->buildContainer([ 'ai' => [ 'store' => [ 'cache' => [ - 'my_cache_store_with_custom_strategy' => [ + 'my_cache_store_with_custom_key' => [ 'service' => 'cache.system', 'cache_key' => 'random', ], @@ -401,15 +531,27 @@ public function testCacheStoreWithCustomKeyCanBeConfigured() ], ]); - $this->assertTrue($container->hasDefinition('ai.store.cache.my_cache_store_with_custom_strategy')); - $this->assertFalse($container->hasDefinition('ai.store.distance_calculator.my_cache_store_with_custom_strategy')); + $this->assertTrue($container->hasDefinition('ai.store.cache.my_cache_store_with_custom_key')); + $this->assertTrue($container->hasDefinition('ai.store.distance_calculator.my_cache_store_with_custom_key')); - $definition = $container->getDefinition('ai.store.cache.my_cache_store_with_custom_strategy'); + $definition = $container->getDefinition('ai.store.cache.my_cache_store_with_custom_key'); $this->assertCount(3, $definition->getArguments()); $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); $this->assertSame('cache.system', (string) $definition->getArgument(0)); $this->assertSame('random', $definition->getArgument(2)); + + $strategyDefinition = $container->getDefinition('ai.store.distance_calculator.my_cache_store_with_custom_key'); + $this->assertTrue($strategyDefinition->isLazy()); + + $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 $cache_my_cache_store_with_custom_key')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myCacheStoreWithCustomKey')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $cacheMyCacheStoreWithCustomKey')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); } public function testCacheStoreWithCustomStrategyCanBeConfigured() @@ -431,13 +573,28 @@ public function testCacheStoreWithCustomStrategyCanBeConfigured() $this->assertTrue($container->hasDefinition('ai.store.distance_calculator.my_cache_store_with_custom_strategy')); $definition = $container->getDefinition('ai.store.cache.my_cache_store_with_custom_strategy'); + $this->assertSame(CacheStore::class, $definition->getClass()); + $this->assertTrue($definition->isLazy()); $this->assertCount(3, $definition->getArguments()); $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); $this->assertSame('cache.system', (string) $definition->getArgument(0)); $this->assertInstanceOf(Reference::class, $definition->getArgument(1)); $this->assertSame('ai.store.distance_calculator.my_cache_store_with_custom_strategy', (string) $definition->getArgument(1)); $this->assertSame('my_cache_store_with_custom_strategy', $definition->getArgument(2)); + + $strategyDefinition = $container->getDefinition('ai.store.distance_calculator.my_cache_store_with_custom_strategy'); + $this->assertTrue($strategyDefinition->isLazy()); + $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 $cache_my_cache_store_with_custom_strategy')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myCacheStoreWithCustomStrategy')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $cacheMyCacheStoreWithCustomStrategy')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); } public function testCacheStoreWithCustomStrategyAndKeyCanBeConfigured() @@ -446,7 +603,7 @@ public function testCacheStoreWithCustomStrategyAndKeyCanBeConfigured() 'ai' => [ 'store' => [ 'cache' => [ - 'my_cache_store_with_custom_strategy' => [ + 'my_cache_store_with_custom_strategy_and_custom_key' => [ 'service' => 'cache.system', 'cache_key' => 'random', 'strategy' => 'chebyshev', @@ -456,17 +613,181 @@ public function testCacheStoreWithCustomStrategyAndKeyCanBeConfigured() ], ]); - $this->assertTrue($container->hasDefinition('ai.store.cache.my_cache_store_with_custom_strategy')); - $this->assertTrue($container->hasDefinition('ai.store.distance_calculator.my_cache_store_with_custom_strategy')); + $this->assertTrue($container->hasDefinition('ai.store.cache.my_cache_store_with_custom_strategy_and_custom_key')); + $this->assertTrue($container->hasDefinition('ai.store.distance_calculator.my_cache_store_with_custom_strategy_and_custom_key')); - $definition = $container->getDefinition('ai.store.cache.my_cache_store_with_custom_strategy'); + $definition = $container->getDefinition('ai.store.cache.my_cache_store_with_custom_strategy_and_custom_key'); + $this->assertSame(CacheStore::class, $definition->getClass()); + $this->assertTrue($definition->isLazy()); $this->assertCount(3, $definition->getArguments()); $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); $this->assertSame('cache.system', (string) $definition->getArgument(0)); $this->assertInstanceOf(Reference::class, $definition->getArgument(1)); - $this->assertSame('ai.store.distance_calculator.my_cache_store_with_custom_strategy', (string) $definition->getArgument(1)); + $this->assertSame('ai.store.distance_calculator.my_cache_store_with_custom_strategy_and_custom_key', (string) $definition->getArgument(1)); $this->assertSame('random', $definition->getArgument(2)); + + $strategyDefinition = $container->getDefinition('ai.store.distance_calculator.my_cache_store_with_custom_strategy_and_custom_key'); + $this->assertTrue($strategyDefinition->isLazy()); + $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 $cache_my_cache_store_with_custom_strategy_and_custom_key')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myCacheStoreWithCustomStrategyAndCustomKey')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $cacheMyCacheStoreWithCustomStrategyAndCustomKey')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testChromaDbStoreCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'chroma_db' => [ + 'my_chroma_db_store' => [ + 'collection' => 'foo', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.chroma_db.my_chroma_db_store')); + + $definition = $container->getDefinition('ai.store.chroma_db.my_chroma_db_store'); + $this->assertSame(ChromaDbStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(2, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame(Client::class, (string) $definition->getArgument(0)); + $this->assertSame('foo', (string) $definition->getArgument(1)); + + $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 $chroma_db_my_chroma_db_store')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myChromaDbStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $chromaDbMyChromaDbStore')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testChromaDbStoreWithCustomClientCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'chroma_db' => [ + 'my_chroma_db_store_with_custom_client' => [ + 'client' => 'bar', + 'collection' => 'foo', + ], + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.store.chroma_db.my_chroma_db_store_with_custom_client')); + + $definition = $container->getDefinition('ai.store.chroma_db.my_chroma_db_store_with_custom_client'); + $this->assertSame(ChromaDbStore::class, $definition->getClass()); + + $this->assertTrue($definition->isLazy()); + $this->assertCount(2, $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $definition->getArgument(0)); + $this->assertSame('bar', (string) $definition->getArgument(0)); + $this->assertSame('foo', (string) $definition->getArgument(1)); + + $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 $chroma_db_my_chroma_db_store_with_custom_client')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myChromaDbStoreWithCustomClient')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $chromaDbMyChromaDbStoreWithCustomClient')); + $this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface')); + } + + public function testClickhouseStoreWithCustomHttpClientCanBeConfigured() + { + $container = $this->buildContainer([ + 'ai' => [ + 'store' => [ + 'clickhouse' => [ + 'my_clickhouse_store' => [ + 'http_client' => 'clickhouse.http_client', + '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(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() @@ -3743,6 +4064,10 @@ private function getFullConfig(): array 'my_chroma_store' => [ 'collection' => 'my_collection', ], + 'my_chroma_db_store_with_custom_client' => [ + 'client' => 'bar', + 'collection' => 'foo', + ], ], 'clickhouse' => [ 'my_clickhouse_store' => [