Skip to content

Commit b898b14

Browse files
committed
feat(store): ManagedStoreInterface commands added
1 parent 38afc15 commit b898b14

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
@@ -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
}
@@ -620,6 +627,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
620627
->setArguments($arguments);
621628

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

@@ -651,6 +659,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
651659
->setArguments($arguments);
652660

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

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

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

@@ -691,6 +701,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
691701
;
692702

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

@@ -751,6 +762,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
751762
->setArguments($arguments);
752763

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

@@ -775,6 +787,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
775787
->setArguments($arguments);
776788

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

@@ -806,6 +819,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
806819
->setArguments($arguments);
807820

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

@@ -832,6 +846,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
832846
->setArguments($arguments);
833847

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

@@ -869,6 +884,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
869884
->setArguments($arguments);
870885

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

@@ -893,6 +909,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
893909
->setArguments($arguments);
894910

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

@@ -919,6 +936,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
919936
->setArguments($arguments);
920937

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

@@ -959,6 +977,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
959977
->setArguments($arguments);
960978

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

@@ -985,6 +1004,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
9851004
->setArguments($arguments);
9861005

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

0 commit comments

Comments
 (0)