Skip to content

Commit 7f27fd9

Browse files
committed
feat(store): ManagedStoreInterface commands added
1 parent db5ea6e commit 7f27fd9

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 <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
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
@@ -135,11 +135,18 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
135135
foreach ($config['store'] ?? [] as $type => $store) {
136136
$this->processStoreConfig($type, $store, $builder);
137137
}
138+
138139
$stores = array_keys($builder->findTaggedServiceIds('ai.store'));
140+
139141
if (1 === \count($stores)) {
140142
$builder->setAlias(StoreInterface::class, reset($stores));
141143
}
142144

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

614621
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
622+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
615623
}
616624
}
617625

@@ -643,6 +651,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
643651
->setArguments($arguments);
644652

645653
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
654+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
646655
}
647656
}
648657

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

659668
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
669+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
660670
}
661671
}
662672

@@ -683,6 +693,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
683693
;
684694

685695
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
696+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
686697
}
687698
}
688699

@@ -743,6 +754,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
743754
->setArguments($arguments);
744755

745756
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
757+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
746758
}
747759
}
748760

@@ -767,6 +779,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
767779
->setArguments($arguments);
768780

769781
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
782+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
770783
}
771784
}
772785

@@ -798,6 +811,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
798811
->setArguments($arguments);
799812

800813
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
814+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
801815
}
802816
}
803817

@@ -824,6 +838,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
824838
->setArguments($arguments);
825839

826840
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
841+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
827842
}
828843
}
829844

@@ -861,6 +876,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
861876
->setArguments($arguments);
862877

863878
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
879+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
864880
}
865881
}
866882

@@ -885,6 +901,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
885901
->setArguments($arguments);
886902

887903
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
904+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
888905
}
889906
}
890907

@@ -911,6 +928,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
911928
->setArguments($arguments);
912929

913930
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
931+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
914932
}
915933
}
916934

@@ -951,6 +969,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
951969
->setArguments($arguments);
952970

953971
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
972+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
954973
}
955974
}
956975

@@ -977,6 +996,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
977996
->setArguments($arguments);
978997

979998
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
999+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
9801000
}
9811001
}
9821002

0 commit comments

Comments
 (0)