Skip to content

Commit 3f6ef49

Browse files
committed
feat(store): ManagedStoreInterface commands added
1 parent 631c339 commit 3f6ef49

File tree

10 files changed

+498
-23
lines changed

10 files changed

+498
-23
lines changed

src/ai-bundle/config/console.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\AI\Store\Command\DropStoreCommand;
15+
use Symfony\AI\Store\Command\SetupStoreCommand;
16+
17+
return static function (ContainerConfigurator $container): void {
18+
$container->services()
19+
->set('console.command.ai.setup_store', SetupStoreCommand::class)
20+
->args([
21+
service('ai.store_locator'),
22+
[], // Receiver names
23+
])
24+
->tag('console.command')
25+
->set('console.command.ai.drop_store', DropStoreCommand::class)
26+
->args([
27+
service('ai.store_locator'),
28+
[], // Receiver names
29+
])
30+
->tag('console.command')
31+
;
32+
};

src/ai-bundle/config/services.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Symfony\AI\Platform\Contract;
3232
use Symfony\AI\Platform\Contract\JsonSchema\DescriptionParser;
3333
use Symfony\AI\Platform\Contract\JsonSchema\Factory as SchemaFactory;
34+
use Symfony\Component\DependencyInjection\ServiceLocator;
3435

3536
return static function (ContainerConfigurator $container): void {
3637
$container->services()
@@ -131,5 +132,12 @@
131132
service('.inner'),
132133
])
133134
->tag('ai.traceable_toolbox')
135+
136+
// Discovery
137+
->set('ai.store_locator', ServiceLocator::class)
138+
->args([
139+
[],
140+
])
141+
->tag('container.service_locator')
134142
;
135143
};

src/ai-bundle/src/AiBundle.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public function configure(DefinitionConfigurator $definition): void
8989
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
9090
{
9191
$container->import('../config/services.php');
92+
$container->import('../config/console.php');
9293

9394
foreach ($config['platform'] ?? [] as $type => $platform) {
9495
$this->processPlatformConfig($type, $platform, $builder);
@@ -115,11 +116,18 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
115116
foreach ($config['store'] ?? [] as $type => $store) {
116117
$this->processStoreConfig($type, $store, $builder);
117118
}
119+
118120
$stores = array_keys($builder->findTaggedServiceIds('ai.store'));
119-
if (1 === \count($stores)) {
120-
$builder->setAlias(StoreInterface::class, reset($stores));
121+
122+
$references = [];
123+
foreach ($stores as $storeName) {
124+
$references[$storeName] = new Reference($storeName);
121125
}
122126

127+
$builder->getDefinition('ai.store_locator')->replaceArgument(0, $references);
128+
$builder->getDefinition('console.command.ai.setup_store')->replaceArgument(0, $stores);
129+
$builder->getDefinition('console.command.ai.drop_store')->replaceArgument(0, $stores);
130+
123131
foreach ($config['indexer'] as $indexerName => $indexer) {
124132
$this->processIndexerConfig($indexerName, $indexer, $builder);
125133
}
@@ -507,6 +515,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
507515
->setArguments($arguments);
508516

509517
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
518+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
510519
}
511520
}
512521

@@ -538,6 +547,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
538547
->setArguments($arguments);
539548

540549
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
550+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
541551
}
542552
}
543553

@@ -552,6 +562,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
552562
->addTag('ai.store');
553563

554564
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
565+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
555566
}
556567
}
557568

@@ -578,6 +589,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
578589
;
579590

580591
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
592+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
581593
}
582594
}
583595

@@ -608,6 +620,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
608620
->setArguments($arguments);
609621

610622
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
623+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
611624
}
612625
}
613626

@@ -632,6 +645,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
632645
->setArguments($arguments);
633646

634647
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
648+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
635649
}
636650
}
637651

@@ -663,6 +677,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
663677
->setArguments($arguments);
664678

665679
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
680+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
666681
}
667682
}
668683

@@ -689,6 +704,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
689704
->setArguments($arguments);
690705

691706
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
707+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
692708
}
693709
}
694710

@@ -726,6 +742,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
726742
->setArguments($arguments);
727743

728744
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
745+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
729746
}
730747
}
731748

@@ -750,6 +767,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
750767
->setArguments($arguments);
751768

752769
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
770+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
753771
}
754772
}
755773

@@ -776,6 +794,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
776794
->setArguments($arguments);
777795

778796
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
797+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
779798
}
780799
}
781800

@@ -816,6 +835,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
816835
->setArguments($arguments);
817836

818837
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
838+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
819839
}
820840
}
821841

@@ -842,6 +862,7 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
842862
->setArguments($arguments);
843863

844864
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
865+
$container->registerAliasForArgument('ai.store.'.$name, StoreInterface::class, (new Target($name.'Store'))->getParsedName());
845866
}
846867
}
847868
}

src/ai-bundle/tests/DependencyInjection/AiBundleTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,23 @@ public function testExtensionLoadDoesNotThrow()
3434
$this->buildContainer($this->getFullConfig());
3535
}
3636

37+
public function testCommandsAreDefined()
38+
{
39+
$container = $this->buildContainer($this->getFullConfig());
40+
41+
$this->assertTrue($container->hasDefinition('console.command.ai.setup_store'));
42+
43+
$setupStoreCommandDefinition = $container->getDefinition('console.command.ai.setup_store');
44+
$this->assertCount(2, $setupStoreCommandDefinition->getArguments());
45+
$this->assertArrayHasKey('console.command', $setupStoreCommandDefinition->getTags());
46+
47+
$this->assertTrue($container->hasDefinition('console.command.ai.drop_store'));
48+
49+
$dropStoreCommandDefinition = $container->getDefinition('console.command.ai.drop_store');
50+
$this->assertCount(2, $dropStoreCommandDefinition->getArguments());
51+
$this->assertArrayHasKey('console.command', $dropStoreCommandDefinition->getTags());
52+
}
53+
3754
public function testInjectionAgentAliasIsRegistered()
3855
{
3956
$container = $this->buildContainer([

src/store/composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@
4141
"phpstan/phpstan": "^2.0",
4242
"phpunit/phpunit": "^11.5",
4343
"probots-io/pinecone-php": "^1.0",
44-
"symfony/cache": "^7.3"
44+
"symfony/cache": "^6.4 || ^7.1",
45+
"symfony/console": "^6.4 || ^7.1",
46+
"symfony/dependency-injection": "^6.4 || ^7.1"
4547
},
4648
"config": {
4749
"sort-packages": true
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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+
namespace Symfony\AI\Store\Command;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\AI\Store\Exception\RuntimeException;
16+
use Symfony\AI\Store\ManagedStoreInterface;
17+
use Symfony\Component\Console\Attribute\AsCommand;
18+
use Symfony\Component\Console\Command\Command;
19+
use Symfony\Component\Console\Completion\CompletionInput;
20+
use Symfony\Component\Console\Completion\CompletionSuggestions;
21+
use Symfony\Component\Console\Input\InputArgument;
22+
use Symfony\Component\Console\Input\InputInterface;
23+
use Symfony\Component\Console\Output\OutputInterface;
24+
use Symfony\Component\Console\Style\SymfonyStyle;
25+
26+
/**
27+
* @author Guillaume Loulier <personal@guillaumeloulier.fr>
28+
*/
29+
#[AsCommand(name: 'ai:store:drop', description: 'Drop the required infrastructure for the store')]
30+
final class DropStoreCommand extends Command
31+
{
32+
/**
33+
* @param string[] $storeNames
34+
*/
35+
public function __construct(
36+
private readonly ContainerInterface $storeLocator,
37+
private readonly array $storeNames = [],
38+
) {
39+
parent::__construct();
40+
}
41+
42+
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
43+
{
44+
if ($input->mustSuggestArgumentValuesFor('store')) {
45+
$suggestions->suggestValues($this->storeNames);
46+
}
47+
}
48+
49+
protected function configure(): void
50+
{
51+
$this
52+
->addArgument('store', InputArgument::OPTIONAL, 'Name of the store to drop')
53+
->setHelp(<<<EOF
54+
The <info>%command.name%</info> command drops the stores:
55+
56+
<info>php %command.full_name%</info>
57+
58+
Or a specific store only:
59+
60+
<info>php %command.full_name% <store></info>
61+
EOF
62+
)
63+
;
64+
}
65+
66+
protected function execute(InputInterface $input, OutputInterface $output): int
67+
{
68+
$io = new SymfonyStyle($input, $output);
69+
70+
$storeNames = $this->storeNames;
71+
// do we want to set up only one store?
72+
if ($store = $input->getArgument('store')) {
73+
if (!$this->storeLocator->has($store)) {
74+
throw new RuntimeException(\sprintf('The "%s" store does not exist.', $store));
75+
}
76+
$storeNames = [$store];
77+
}
78+
79+
foreach ($storeNames as $storeName) {
80+
$store = $this->storeLocator->get($storeName);
81+
if (!$store instanceof ManagedStoreInterface) {
82+
$io->note(\sprintf('The "%s" store does not support drop.', $storeName));
83+
continue;
84+
}
85+
86+
try {
87+
$store->drop();
88+
$io->success(\sprintf('The "%s" store was dropped successfully.', $storeName));
89+
} catch (\Exception $e) {
90+
throw new RuntimeException(\sprintf('An error occurred while dropping the "%s" store: ', $storeName).$e->getMessage(), 0, $e);
91+
}
92+
}
93+
94+
return Command::SUCCESS;
95+
}
96+
}

0 commit comments

Comments
 (0)