Skip to content

Commit 8c104c2

Browse files
committed
[Store] Move RetrieveCommand from demo to store component
The RetrieveCommand is now part of the Store component like IndexCommand, SetupStoreCommand, and DropStoreCommand. It is registered in the AI Bundle with a ServiceLocator for retrievers.
1 parent 9e38e9a commit 8c104c2

File tree

3 files changed

+151
-62
lines changed

3 files changed

+151
-62
lines changed

demo/src/Blog/Command/RetrieveCommand.php

Lines changed: 0 additions & 62 deletions
This file was deleted.

src/ai-bundle/config/services.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
use Symfony\AI\Platform\StructuredOutput\ResponseFormatFactoryInterface;
6969
use Symfony\AI\Store\Command\DropStoreCommand;
7070
use Symfony\AI\Store\Command\IndexCommand;
71+
use Symfony\AI\Store\Command\RetrieveCommand;
7172
use Symfony\AI\Store\Command\SetupStoreCommand;
7273

7374
return static function (ContainerConfigurator $container): void {
@@ -220,6 +221,11 @@
220221
tagged_locator('ai.indexer', 'name'),
221222
])
222223
->tag('console.command')
224+
->set('ai.command.retrieve', RetrieveCommand::class)
225+
->args([
226+
tagged_locator('ai.retriever', 'name'),
227+
])
228+
->tag('console.command')
223229
->set('ai.command.platform_invoke', PlatformInvokeCommand::class)
224230
->args([
225231
tagged_locator('ai.platform', 'name'),
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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+
namespace Symfony\AI\Store\Command;
13+
14+
use Symfony\AI\Store\Exception\RuntimeException;
15+
use Symfony\AI\Store\RetrieverInterface;
16+
use Symfony\Component\Console\Attribute\AsCommand;
17+
use Symfony\Component\Console\Command\Command;
18+
use Symfony\Component\Console\Completion\CompletionInput;
19+
use Symfony\Component\Console\Completion\CompletionSuggestions;
20+
use Symfony\Component\Console\Input\InputArgument;
21+
use Symfony\Component\Console\Input\InputInterface;
22+
use Symfony\Component\Console\Input\InputOption;
23+
use Symfony\Component\Console\Output\OutputInterface;
24+
use Symfony\Component\Console\Style\SymfonyStyle;
25+
use Symfony\Component\DependencyInjection\ServiceLocator;
26+
27+
/**
28+
* @author Oskar Stark <[email protected]>
29+
*/
30+
#[AsCommand(
31+
name: 'ai:store:retrieve',
32+
description: 'Retrieve documents from a store',
33+
)]
34+
final class RetrieveCommand extends Command
35+
{
36+
/**
37+
* @param ServiceLocator<RetrieverInterface> $retrievers
38+
*/
39+
public function __construct(
40+
private readonly ServiceLocator $retrievers,
41+
) {
42+
parent::__construct();
43+
}
44+
45+
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
46+
{
47+
if ($input->mustSuggestArgumentValuesFor('retriever')) {
48+
$suggestions->suggestValues(array_keys($this->retrievers->getProvidedServices()));
49+
}
50+
}
51+
52+
protected function configure(): void
53+
{
54+
$this
55+
->addArgument('retriever', InputArgument::REQUIRED, 'Name of the retriever to use')
56+
->addArgument('query', InputArgument::OPTIONAL, 'Search query')
57+
->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Maximum number of results to return', '10')
58+
->setHelp(<<<'EOF'
59+
The <info>%command.name%</info> command retrieves documents from a store using the specified retriever.
60+
61+
Basic usage:
62+
<info>php %command.full_name% blog "search query"</info>
63+
64+
Interactive mode (prompts for query):
65+
<info>php %command.full_name% blog</info>
66+
67+
Limit results:
68+
<info>php %command.full_name% blog "search query" --limit=5</info>
69+
EOF
70+
)
71+
;
72+
}
73+
74+
protected function execute(InputInterface $input, OutputInterface $output): int
75+
{
76+
$io = new SymfonyStyle($input, $output);
77+
78+
$retriever = $input->getArgument('retriever');
79+
80+
if (!$this->retrievers->has($retriever)) {
81+
throw new RuntimeException(\sprintf('The "%s" retriever does not exist.', $retriever));
82+
}
83+
84+
$query = $input->getArgument('query');
85+
if (null === $query) {
86+
$query = $io->ask('What do you want to search for?');
87+
if (null === $query || '' === $query) {
88+
$io->error('A search query is required.');
89+
90+
return Command::FAILURE;
91+
}
92+
}
93+
94+
$limit = (int) $input->getOption('limit');
95+
96+
$io->title(\sprintf('Retrieving documents using "%s" retriever', $retriever));
97+
$io->comment(\sprintf('Searching for: "%s"', $query));
98+
99+
try {
100+
$retrieverService = $this->retrievers->get($retriever);
101+
$documents = $retrieverService->retrieve($query, ['maxItems' => $limit]);
102+
103+
$count = 0;
104+
foreach ($documents as $document) {
105+
++$count;
106+
$io->section(\sprintf('Result #%d', $count));
107+
108+
$tableData = [
109+
['ID', (string) $document->id],
110+
['Score', $document->score ?? 'n/a'],
111+
];
112+
113+
if ($document->metadata->hasSource()) {
114+
$tableData[] = ['Source', $document->metadata->getSource()];
115+
}
116+
117+
if ($document->metadata->hasText()) {
118+
$text = $document->metadata->getText();
119+
if (\strlen($text) > 200) {
120+
$text = substr($text, 0, 200).'...';
121+
}
122+
$tableData[] = ['Text', $text];
123+
}
124+
125+
$io->table([], $tableData);
126+
127+
if ($count >= $limit) {
128+
break;
129+
}
130+
}
131+
132+
if (0 === $count) {
133+
$io->warning('No results found.');
134+
135+
return Command::SUCCESS;
136+
}
137+
138+
$io->success(\sprintf('Found %d result(s) using "%s" retriever.', $count, $retriever));
139+
} catch (\Exception $e) {
140+
throw new RuntimeException(\sprintf('An error occurred while retrieving with "%s": ', $retriever).$e->getMessage(), previous: $e);
141+
}
142+
143+
return Command::SUCCESS;
144+
}
145+
}

0 commit comments

Comments
 (0)