Skip to content

Commit 0bd8d96

Browse files
committed
feature #335 [Store] Add commands to setup/drop a store (Guikingone)
This PR was merged into the main branch. Discussion ---------- [Store] Add commands to setup/drop a store | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Docs? | yes | Issues | #330 | License | MIT Hi 👋🏻 As discussed in #330, here's the commands required to setup/drop the stores, by default, I moved the commands in the `Store` component but wasn't sure about it. Commits ------- b898b14 feat(store): ManagedStoreInterface commands added
2 parents ebafb1f + b898b14 commit 0bd8d96

File tree

12 files changed

+755
-1
lines changed

12 files changed

+755
-1
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Integration Tests
2+
3+
on:
4+
push:
5+
paths-ignore:
6+
- 'src/*/doc/**'
7+
- 'src/**/*.md'
8+
pull_request:
9+
paths-ignore:
10+
- 'src/*/doc/**'
11+
- 'src/**/*.md'
12+
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
15+
cancel-in-progress: true
16+
17+
env:
18+
REQUIRED_PHP_EXTENSIONS: 'mongodb'
19+
20+
jobs:
21+
php:
22+
runs-on: ubuntu-latest
23+
strategy:
24+
fail-fast: false
25+
matrix:
26+
php-version: ['8.2', '8.3', '8.4']
27+
dependency-version: ['']
28+
symfony-version: ['']
29+
include:
30+
# lowest deps
31+
- php-version: '8.2'
32+
dependency-version: 'lowest'
33+
# LTS version of Symfony
34+
- php-version: '8.2'
35+
symfony-version: '6.4.*'
36+
37+
env:
38+
SYMFONY_REQUIRE: ${{ matrix.symfony-version || '>=6.4' }}
39+
40+
steps:
41+
- uses: actions/checkout@v5
42+
43+
- name: Up the examples services
44+
run: cd examples && docker compose up -d
45+
46+
- name: Configure environment
47+
run: |
48+
echo COLUMNS=120 >> $GITHUB_ENV
49+
echo COMPOSER_UP='composer update ${{ matrix.dependency-version == 'lowest' && '--prefer-lowest --prefer-stable' || '' }} --no-progress --no-interaction --ansi --ignore-platform-req=ext-mongodb' >> $GITHUB_ENV
50+
echo PHPUNIT='vendor/bin/phpunit' >> $GITHUB_ENV
51+
[ 'lowest' = '${{ matrix.dependency-version }}' ] && export SYMFONY_DEPRECATIONS_HELPER=weak
52+
53+
PACKAGES=$(find src/ -mindepth 2 -type f -name composer.json -not -path "*/vendor/*" -printf '%h\n' | sed 's/^src\///' | grep -Ev "examples" | sort | tr '\n' ' ')
54+
echo "Packages: $PACKAGES"
55+
echo "PACKAGES=$PACKAGES" >> $GITHUB_ENV
56+
57+
- name: Setup PHP
58+
uses: shivammathur/setup-php@v2
59+
with:
60+
php-version: ${{ matrix.php-version }}
61+
tools: flex
62+
extensions: "${{ env.REQUIRED_PHP_EXTENSIONS }}"
63+
64+
- name: Get composer cache directory
65+
id: composer-cache
66+
run: |
67+
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
68+
69+
- name: Cache packages dependencies
70+
uses: actions/cache@v4
71+
with:
72+
path: ${{ steps.composer-cache.outputs.dir }}
73+
key: ${{ runner.os }}-composer-packages-${{ matrix.php-version }}-${{ matrix.dependency-version }}-${{ matrix.symfony-version }}-${{ hashFiles('src/**/composer.json') }}
74+
restore-keys: |
75+
${{ runner.os }}-composer-packages-${{ matrix.php-version }}-${{ matrix.dependency-version }}-${{ matrix.symfony-version }}
76+
77+
- name: Install root dependencies
78+
uses: ramsey/composer-install@v3
79+
80+
- name: Install examples dependencies
81+
run: cd examples && composer install && ../link
82+
83+
- name: Run commands examples
84+
run: php examples/commands/stores.php

examples/commands/stores.php

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
require_once dirname(__DIR__).'/bootstrap.php';
13+
14+
use Doctrine\DBAL\DriverManager;
15+
use Doctrine\DBAL\Tools\DsnParser;
16+
use MongoDB\Client as MongoDbClient;
17+
use Symfony\AI\Store\Bridge\ClickHouse\Store as ClickHouseStore;
18+
use Symfony\AI\Store\Bridge\Local\CacheStore;
19+
use Symfony\AI\Store\Bridge\Local\InMemoryStore;
20+
use Symfony\AI\Store\Bridge\MariaDb\Store as MariaDbStore;
21+
use Symfony\AI\Store\Bridge\Meilisearch\Store as MeilisearchStore;
22+
use Symfony\AI\Store\Bridge\Milvus\Store as MilvusStore;
23+
use Symfony\AI\Store\Bridge\MongoDb\Store as MongoDbStore;
24+
use Symfony\AI\Store\Bridge\Neo4j\Store as Neo4jStore;
25+
use Symfony\AI\Store\Bridge\Postgres\Store as PostgresStore;
26+
use Symfony\AI\Store\Bridge\Qdrant\Store as QdrantStore;
27+
use Symfony\AI\Store\Bridge\SurrealDb\Store as SurrealDbStore;
28+
use Symfony\AI\Store\Bridge\Typesense\Store as TypesenseStore;
29+
use Symfony\AI\Store\Bridge\Weaviate\Store as WeaviateStore;
30+
use Symfony\AI\Store\Command\DropStoreCommand;
31+
use Symfony\AI\Store\Command\SetupStoreCommand;
32+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
33+
use Symfony\Component\Console\Application;
34+
use Symfony\Component\Console\Input\ArrayInput;
35+
use Symfony\Component\Console\Output\ConsoleOutput;
36+
use Symfony\Component\DependencyInjection\ServiceLocator;
37+
use Symfony\Component\HttpClient\HttpClient;
38+
39+
$factories = [
40+
'cache' => static fn (): CacheStore => new CacheStore(new ArrayAdapter(), cacheKey: 'symfony'),
41+
'clickhouse' => static fn (): ClickHouseStore => new ClickHouseStore(
42+
HttpClient::createForBaseUri(env('CLICKHOUSE_HOST')),
43+
env('CLICKHOUSE_DATABASE'),
44+
env('CLICKHOUSE_TABLE'),
45+
),
46+
'mariadb' => static fn (): MariaDbStore => MariaDbStore::fromDbal(
47+
DriverManager::getConnection((new DsnParser())->parse(env('MARIADB_URI'))),
48+
'my_table_for_commands',
49+
'my_commands_index',
50+
),
51+
'memory' => static fn (): InMemoryStore => new InMemoryStore(),
52+
'meilisearch' => static fn (): MeilisearchStore => new MeilisearchStore(
53+
http_client(),
54+
env('MEILISEARCH_HOST'),
55+
env('MEILISEARCH_API_KEY'),
56+
'symfony',
57+
),
58+
'milvus' => static fn (): MilvusStore => new MilvusStore(
59+
http_client(),
60+
env('MILVUS_HOST'),
61+
env('MILVUS_API_KEY'),
62+
env('MILVUS_DATABASE'),
63+
'symfony',
64+
),
65+
'mongodb' => static fn (): MongoDbStore => new MongoDbStore(
66+
client: new MongoDbClient(env('MONGODB_URI')),
67+
databaseName: 'my-database',
68+
collectionName: 'my-collection',
69+
indexName: 'my-index',
70+
vectorFieldName: 'vector',
71+
),
72+
'neo4j' => static fn (): Neo4jStore => new Neo4jStore(
73+
httpClient: http_client(),
74+
endpointUrl: env('NEO4J_HOST'),
75+
username: env('NEO4J_USERNAME'),
76+
password: env('NEO4J_PASSWORD'),
77+
databaseName: env('NEO4J_DATABASE'),
78+
vectorIndexName: 'Commands',
79+
nodeName: 'symfony',
80+
),
81+
'postgres' => static fn (): PostgresStore => PostgresStore::fromDbal(
82+
DriverManager::getConnection((new DsnParser())->parse(env('POSTGRES_URI'))),
83+
'my_table',
84+
),
85+
'qdrant' => static fn (): QdrantStore => new QdrantStore(
86+
http_client(),
87+
env('QDRANT_HOST'),
88+
env('QDRANT_SERVICE_API_KEY'),
89+
'symfony',
90+
),
91+
'surrealdb' => static fn (): SurrealDbStore => new SurrealDbStore(
92+
httpClient: http_client(),
93+
endpointUrl: env('SURREALDB_HOST'),
94+
user: env('SURREALDB_USER'),
95+
password: env('SURREALDB_PASS'),
96+
namespace: 'default',
97+
database: 'symfony',
98+
table: 'symfony',
99+
),
100+
'typesense' => static fn (): TypesenseStore => new TypesenseStore(
101+
http_client(),
102+
env('TYPESENSE_HOST'),
103+
env('TYPESENSE_API_KEY'),
104+
'symfony',
105+
),
106+
'weaviate' => static fn (): WeaviateStore => new WeaviateStore(
107+
http_client(),
108+
env('WEAVIATE_HOST'),
109+
env('WEAVIATE_API_KEY'),
110+
'symfony',
111+
),
112+
];
113+
114+
$storesIds = array_keys($factories);
115+
116+
$application = new Application();
117+
$application->setAutoExit(false);
118+
$application->setCatchExceptions(false);
119+
$application->add(new SetupStoreCommand(new ServiceLocator($factories)));
120+
$application->add(new DropStoreCommand(new ServiceLocator($factories)));
121+
122+
foreach ($storesIds as $store) {
123+
$setupOutputCode = $application->run(new ArrayInput([
124+
'command' => 'ai:store:setup',
125+
'store' => $store,
126+
]), new ConsoleOutput());
127+
128+
$dropOutputCode = $application->run(new ArrayInput([
129+
'command' => 'ai:store:drop',
130+
'store' => $store,
131+
'--force' => true,
132+
]), new ConsoleOutput());
133+
}

examples/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"symfony/cache": "^6.4 || ^7.0",
2222
"symfony/console": "^6.4 || ^7.0",
2323
"symfony/css-selector": "^6.4 || ^7.0",
24+
"symfony/dependency-injection": "^6.4 || ^7.0",
2425
"symfony/dom-crawler": "^6.4 || ^7.0",
2526
"symfony/dotenv": "^6.4 || ^7.0",
2627
"symfony/event-dispatcher": "^6.4 || ^7.0",

src/ai-bundle/config/services.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
use Symfony\AI\Platform\Contract;
3838
use Symfony\AI\Platform\Contract\JsonSchema\DescriptionParser;
3939
use Symfony\AI\Platform\Contract\JsonSchema\Factory as SchemaFactory;
40+
use Symfony\AI\Store\Command\DropStoreCommand;
41+
use Symfony\AI\Store\Command\SetupStoreCommand;
4042

4143
return static function (ContainerConfigurator $container): void {
4244
$container->services()
@@ -145,5 +147,15 @@
145147
tagged_locator('ai.agent', indexAttribute: 'name'),
146148
])
147149
->tag('console.command')
150+
->set('ai.command.setup_store', SetupStoreCommand::class)
151+
->args([
152+
tagged_locator('ai.store', indexAttribute: 'name'),
153+
])
154+
->tag('console.command')
155+
->set('ai.command.drop_store', DropStoreCommand::class)
156+
->args([
157+
tagged_locator('ai.store', indexAttribute: 'name'),
158+
])
159+
->tag('console.command')
148160
;
149161
};

src/ai-bundle/src/AiBundle.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,18 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
136136
foreach ($config['store'] ?? [] as $type => $store) {
137137
$this->processStoreConfig($type, $store, $builder);
138138
}
139+
139140
$stores = array_keys($builder->findTaggedServiceIds('ai.store'));
141+
140142
if (1 === \count($stores)) {
141143
$builder->setAlias(StoreInterface::class, reset($stores));
142144
}
143145

146+
if ([] === $stores) {
147+
$builder->removeDefinition('ai.command.setup_store');
148+
$builder->removeDefinition('ai.command.drop_store');
149+
}
150+
144151
foreach ($config['indexer'] as $indexerName => $indexer) {
145152
$this->processIndexerConfig($indexerName, $indexer, $builder);
146153
}
@@ -621,6 +628,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
621628
->setArguments($arguments);
622629

623630
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
631+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
624632
}
625633
}
626634

@@ -652,6 +660,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
652660
->setArguments($arguments);
653661

654662
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
663+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
655664
}
656665
}
657666

@@ -666,6 +675,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
666675
->addTag('ai.store');
667676

668677
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
678+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
669679
}
670680
}
671681

@@ -692,6 +702,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
692702
;
693703

694704
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
705+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
695706
}
696707
}
697708

@@ -752,6 +763,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
752763
->setArguments($arguments);
753764

754765
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
766+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
755767
}
756768
}
757769

@@ -776,6 +788,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
776788
->setArguments($arguments);
777789

778790
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
791+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
779792
}
780793
}
781794

@@ -807,6 +820,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
807820
->setArguments($arguments);
808821

809822
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
823+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
810824
}
811825
}
812826

@@ -833,6 +847,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
833847
->setArguments($arguments);
834848

835849
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
850+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
836851
}
837852
}
838853

@@ -870,6 +885,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
870885
->setArguments($arguments);
871886

872887
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
888+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
873889
}
874890
}
875891

@@ -894,6 +910,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
894910
->setArguments($arguments);
895911

896912
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
913+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
897914
}
898915
}
899916

@@ -920,6 +937,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
920937
->setArguments($arguments);
921938

922939
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
940+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
923941
}
924942
}
925943

@@ -960,6 +978,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
960978
->setArguments($arguments);
961979

962980
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
981+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
963982
}
964983
}
965984

@@ -986,6 +1005,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
9861005
->setArguments($arguments);
9871006

9881007
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
1008+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
9891009
}
9901010
}
9911011

0 commit comments

Comments
 (0)