Skip to content

Commit b9a679c

Browse files
committed
feat(store): ManagedStoreInterface commands added
1 parent f9be079 commit b9a679c

File tree

13 files changed

+748
-2
lines changed

13 files changed

+748
-2
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: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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\Command\DropStoreCommand;
30+
use Symfony\AI\Store\Command\SetupStoreCommand;
31+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
32+
use Symfony\Component\Console\Application;
33+
use Symfony\Component\Console\Input\ArrayInput;
34+
use Symfony\Component\Console\Output\ConsoleOutput;
35+
use Symfony\Component\DependencyInjection\ServiceLocator;
36+
use Symfony\Component\HttpClient\HttpClient;
37+
38+
$factories = [
39+
'cache' => static fn (): CacheStore => new CacheStore(new ArrayAdapter(), cacheKey: '_commands'),
40+
'clickhouse' => static fn (): ClickHouseStore => new ClickHouseStore(
41+
HttpClient::createForBaseUri(env('CLICKHOUSE_HOST')),
42+
env('CLICKHOUSE_DATABASE'),
43+
env('CLICKHOUSE_TABLE'),
44+
),
45+
'mariadb' => static fn (): MariaDbStore => MariaDbStore::fromDbal(
46+
DriverManager::getConnection((new DsnParser())->parse(env('MARIADB_URI'))),
47+
'my_table_for_commands',
48+
'my_commands_index',
49+
),
50+
'memory' => static fn (): InMemoryStore => new InMemoryStore(),
51+
'meilisearch' => static fn (): MeilisearchStore => new MeilisearchStore(
52+
http_client(),
53+
env('MEILISEARCH_HOST'),
54+
env('MEILISEARCH_API_KEY'),
55+
'symfony',
56+
),
57+
'milvus' => static fn (): MilvusStore => new MilvusStore(
58+
http_client(),
59+
env('MILVUS_HOST'),
60+
env('MILVUS_API_KEY'),
61+
env('MILVUS_DATABASE'),
62+
'symfony',
63+
),
64+
'mongodb' => static fn (): MongoDbStore => new MongoDbStore(
65+
client: new MongoDbClient(env('MONGODB_URI')),
66+
databaseName: 'my-database',
67+
collectionName: 'my-collection',
68+
indexName: 'my-index',
69+
vectorFieldName: 'vector',
70+
),
71+
'neo4j' => static fn (): Neo4jStore => new Neo4jStore(
72+
httpClient: http_client(),
73+
endpointUrl: env('NEO4J_HOST'),
74+
username: env('NEO4J_USERNAME'),
75+
password: env('NEO4J_PASSWORD'),
76+
databaseName: env('NEO4J_DATABASE'),
77+
vectorIndexName: 'Commands',
78+
nodeName: 'symfony',
79+
),
80+
'postgres' => static fn (): PostgresStore => PostgresStore::fromDbal(
81+
DriverManager::getConnection((new DsnParser())->parse(env('POSTGRES_URI'))),
82+
'my_table',
83+
),
84+
'qdrant' => static fn (): QdrantStore => new QdrantStore(
85+
http_client(),
86+
env('QDRANT_HOST'),
87+
env('QDRANT_SERVICE_API_KEY'),
88+
'symfony',
89+
),
90+
'surrealdb' => static fn (): SurrealDbStore => new SurrealDbStore(
91+
httpClient: http_client(),
92+
endpointUrl: env('SURREALDB_HOST'),
93+
user: env('SURREALDB_USER'),
94+
password: env('SURREALDB_PASS'),
95+
namespace: 'default',
96+
database: 'symfony',
97+
table: 'symfony',
98+
),
99+
'typesense' => static fn (): TypesenseStore => new TypesenseStore(
100+
http_client(),
101+
env('TYPESENSE_HOST'),
102+
env('TYPESENSE_API_KEY'),
103+
'symfony',
104+
),
105+
];
106+
107+
$storesIds = array_keys($factories);
108+
109+
$application = new Application();
110+
$application->setAutoExit(false);
111+
$application->setCatchExceptions(false);
112+
$application->add(new SetupStoreCommand(new ServiceLocator($factories)));
113+
$application->add(new DropStoreCommand(new ServiceLocator($factories)));
114+
115+
foreach ($storesIds as $store) {
116+
$setupOutputCode = $application->run(new ArrayInput([
117+
'command' => 'ai:store:setup',
118+
'store' => $store,
119+
]), new ConsoleOutput());
120+
121+
$dropOutputCode = $application->run(new ArrayInput([
122+
'command' => 'ai:store:drop',
123+
'store' => $store,
124+
'--force' => true,
125+
]), new ConsoleOutput());
126+
}

examples/compose.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ services:
6868
MINIO_SECRET_KEY: minioadmin
6969
ports:
7070
- '9001:9001'
71-
- '9000:9000'
7271
volumes:
7372
- minio_vlm:/minio_data
7473
command: minio server /minio_data --console-address ":9001"

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
@@ -133,11 +133,18 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
133133
foreach ($config['store'] ?? [] as $type => $store) {
134134
$this->processStoreConfig($type, $store, $builder);
135135
}
136+
136137
$stores = array_keys($builder->findTaggedServiceIds('ai.store'));
138+
137139
if (1 === \count($stores)) {
138140
$builder->setAlias(StoreInterface::class, reset($stores));
139141
}
140142

143+
if ([] === $stores) {
144+
$builder->removeDefinition('ai.command.setup_store');
145+
$builder->removeDefinition('ai.command.drop_store');
146+
}
147+
141148
foreach ($config['indexer'] as $indexerName => $indexer) {
142149
$this->processIndexerConfig($indexerName, $indexer, $builder);
143150
}
@@ -608,6 +615,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
608615
->setArguments($arguments);
609616

610617
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
618+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
611619
}
612620
}
613621

@@ -639,6 +647,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
639647
->setArguments($arguments);
640648

641649
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
650+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
642651
}
643652
}
644653

@@ -653,6 +662,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
653662
->addTag('ai.store');
654663

655664
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
665+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
656666
}
657667
}
658668

@@ -679,6 +689,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
679689
;
680690

681691
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
692+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
682693
}
683694
}
684695

@@ -709,6 +720,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
709720
->setArguments($arguments);
710721

711722
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
723+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
712724
}
713725
}
714726

@@ -733,6 +745,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
733745
->setArguments($arguments);
734746

735747
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
748+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
736749
}
737750
}
738751

@@ -764,6 +777,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
764777
->setArguments($arguments);
765778

766779
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
780+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
767781
}
768782
}
769783

@@ -790,6 +804,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
790804
->setArguments($arguments);
791805

792806
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
807+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
793808
}
794809
}
795810

@@ -827,6 +842,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
827842
->setArguments($arguments);
828843

829844
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
845+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
830846
}
831847
}
832848

@@ -851,6 +867,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
851867
->setArguments($arguments);
852868

853869
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
870+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
854871
}
855872
}
856873

@@ -877,6 +894,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
877894
->setArguments($arguments);
878895

879896
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
897+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
880898
}
881899
}
882900

@@ -917,6 +935,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
917935
->setArguments($arguments);
918936

919937
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
938+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
920939
}
921940
}
922941

@@ -943,6 +962,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
943962
->setArguments($arguments);
944963

945964
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
965+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
946966
}
947967
}
948968
}

0 commit comments

Comments
 (0)