Skip to content

Commit 77fa8b6

Browse files
committed
bug #293 [AIBundle] Cache store configuration improved (Guikingone)
This PR was squashed before being merged into the main branch. Discussion ---------- [AIBundle] Cache store configuration improved | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Docs? | yes | Issues | #257 | License | MIT Hi 👋🏻 The configuration wasn't complete enough to handle custom strategy / key for the cache store, here's the fix, tests are validated (both in the project and in a custom project too). Commits ------- 6e1c982 [AIBundle] Cache store configuration improved
2 parents a64f388 + 6e1c982 commit 77fa8b6

File tree

6 files changed

+190
-12
lines changed

6 files changed

+190
-12
lines changed

src/ai-bundle/config/options.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@
173173
->arrayPrototype()
174174
->children()
175175
->scalarNode('service')->cannotBeEmpty()->defaultValue('cache.app')->end()
176+
->scalarNode('cache_key')->end()
177+
->scalarNode('strategy')->end()
176178
->end()
177179
->end()
178180
->end()
@@ -221,7 +223,7 @@
221223
->useAttributeAsKey('name')
222224
->arrayPrototype()
223225
->children()
224-
->scalarNode('distance')->cannotBeEmpty()->end()
226+
->scalarNode('strategy')->cannotBeEmpty()->end()
225227
->end()
226228
->end()
227229
->end()

src/ai-bundle/doc/index.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ Configuration
101101
# multiple collections possible per type
102102
default:
103103
collection: 'my_collection'
104+
cache:
105+
research:
106+
service: 'cache.app'
107+
cache_key: 'research'
108+
strategy: 'chebyshev'
109+
memory:
110+
ollama:
111+
strategy: 'manhattan'
104112
indexer:
105113
default:
106114
# platform: 'ai.platform.mistral'

src/ai-bundle/src/AiBundle.php

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
use Symfony\AI\Store\Bridge\ChromaDb\Store as ChromaDbStore;
4545
use Symfony\AI\Store\Bridge\ClickHouse\Store as ClickHouseStore;
4646
use Symfony\AI\Store\Bridge\Local\CacheStore;
47+
use Symfony\AI\Store\Bridge\Local\DistanceCalculator;
48+
use Symfony\AI\Store\Bridge\Local\DistanceStrategy;
4749
use Symfony\AI\Store\Bridge\Local\InMemoryStore;
4850
use Symfony\AI\Store\Bridge\Meilisearch\Store as MeilisearchStore;
4951
use Symfony\AI\Store\Bridge\MongoDb\Store as MongoDbStore;
@@ -511,8 +513,24 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
511513
foreach ($stores as $name => $store) {
512514
$arguments = [
513515
new Reference($store['service']),
516+
new Definition(DistanceCalculator::class),
514517
];
515518

519+
if (\array_key_exists('cache_key', $store) && null !== $store['cache_key']) {
520+
$arguments[2] = $store['cache_key'];
521+
}
522+
523+
if (\array_key_exists('strategy', $store) && null !== $store['strategy']) {
524+
if (!$container->hasDefinition('ai.store.distance_calculator.'.$name)) {
525+
$distanceCalculatorDefinition = new Definition(DistanceCalculator::class);
526+
$distanceCalculatorDefinition->setArgument(0, DistanceStrategy::from($store['strategy']));
527+
528+
$container->setDefinition('ai.store.distance_calculator.'.$name, $distanceCalculatorDefinition);
529+
}
530+
531+
$arguments[1] = new Reference('ai.store.distance_calculator.'.$name);
532+
}
533+
516534
$definition = new Definition(CacheStore::class);
517535
$definition
518536
->addTag('ai.store')
@@ -594,9 +612,18 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
594612

595613
if ('memory' === $type) {
596614
foreach ($stores as $name => $store) {
597-
$arguments = [
598-
$store['distance'],
599-
];
615+
$arguments = [];
616+
617+
if (\array_key_exists('strategy', $store) && null !== $store['strategy']) {
618+
if (!$container->hasDefinition('ai.store.distance_calculator.'.$name)) {
619+
$distanceCalculatorDefinition = new Definition(DistanceCalculator::class);
620+
$distanceCalculatorDefinition->setArgument(0, DistanceStrategy::from($store['strategy']));
621+
622+
$container->setDefinition('ai.store.distance_calculator.'.$name, $distanceCalculatorDefinition);
623+
}
624+
625+
$arguments[0] = new Reference('ai.store.distance_calculator.'.$name);
626+
}
600627

601628
$definition = new Definition(InMemoryStore::class);
602629
$definition

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

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@
1919
use Symfony\AI\AiBundle\AiBundle;
2020
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
2121
use Symfony\Component\DependencyInjection\ContainerBuilder;
22+
use Symfony\Component\DependencyInjection\Definition;
23+
use Symfony\Component\DependencyInjection\Reference;
2224

2325
#[CoversClass(AiBundle::class)]
2426
#[UsesClass(ContainerBuilder::class)]
27+
#[UsesClass(Definition::class)]
28+
#[UsesClass(Reference::class)]
2529
class AiBundleTest extends TestCase
2630
{
2731
#[DoesNotPerformAssertions]
@@ -120,6 +124,130 @@ public function testAgentsAsToolsCannotDefineService()
120124
]);
121125
}
122126

127+
public function testCacheStoreWithCustomKeyCanBeConfigured()
128+
{
129+
$container = $this->buildContainer([
130+
'ai' => [
131+
'store' => [
132+
'cache' => [
133+
'my_cache_store_with_custom_strategy' => [
134+
'service' => 'cache.system',
135+
'cache_key' => 'random',
136+
],
137+
],
138+
],
139+
],
140+
]);
141+
142+
$this->assertTrue($container->hasDefinition('ai.store.cache.my_cache_store_with_custom_strategy'));
143+
$this->assertFalse($container->hasDefinition('ai.store.distance_calculator.my_cache_store_with_custom_strategy'));
144+
145+
$definition = $container->getDefinition('ai.store.cache.my_cache_store_with_custom_strategy');
146+
147+
$this->assertCount(3, $definition->getArguments());
148+
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
149+
$this->assertSame('cache.system', (string) $definition->getArgument(0));
150+
$this->assertSame('random', $definition->getArgument(2));
151+
}
152+
153+
public function testCacheStoreWithCustomStrategyCanBeConfigured()
154+
{
155+
$container = $this->buildContainer([
156+
'ai' => [
157+
'store' => [
158+
'cache' => [
159+
'my_cache_store_with_custom_strategy' => [
160+
'service' => 'cache.system',
161+
'strategy' => 'chebyshev',
162+
],
163+
],
164+
],
165+
],
166+
]);
167+
168+
$this->assertTrue($container->hasDefinition('ai.store.cache.my_cache_store_with_custom_strategy'));
169+
$this->assertTrue($container->hasDefinition('ai.store.distance_calculator.my_cache_store_with_custom_strategy'));
170+
171+
$definition = $container->getDefinition('ai.store.cache.my_cache_store_with_custom_strategy');
172+
173+
$this->assertCount(2, $definition->getArguments());
174+
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
175+
$this->assertSame('cache.system', (string) $definition->getArgument(0));
176+
$this->assertInstanceOf(Reference::class, $definition->getArgument(1));
177+
$this->assertSame('ai.store.distance_calculator.my_cache_store_with_custom_strategy', (string) $definition->getArgument(1));
178+
}
179+
180+
public function testCacheStoreWithCustomStrategyAndKeyCanBeConfigured()
181+
{
182+
$container = $this->buildContainer([
183+
'ai' => [
184+
'store' => [
185+
'cache' => [
186+
'my_cache_store_with_custom_strategy' => [
187+
'service' => 'cache.system',
188+
'cache_key' => 'random',
189+
'strategy' => 'chebyshev',
190+
],
191+
],
192+
],
193+
],
194+
]);
195+
196+
$this->assertTrue($container->hasDefinition('ai.store.cache.my_cache_store_with_custom_strategy'));
197+
$this->assertTrue($container->hasDefinition('ai.store.distance_calculator.my_cache_store_with_custom_strategy'));
198+
199+
$definition = $container->getDefinition('ai.store.cache.my_cache_store_with_custom_strategy');
200+
201+
$this->assertCount(3, $definition->getArguments());
202+
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
203+
$this->assertSame('cache.system', (string) $definition->getArgument(0));
204+
$this->assertSame('random', $definition->getArgument(2));
205+
$this->assertInstanceOf(Reference::class, $definition->getArgument(1));
206+
$this->assertSame('ai.store.distance_calculator.my_cache_store_with_custom_strategy', (string) $definition->getArgument(1));
207+
}
208+
209+
public function testInMemoryStoreWithoutCustomStrategyCanBeConfigured()
210+
{
211+
$container = $this->buildContainer([
212+
'ai' => [
213+
'store' => [
214+
'memory' => [
215+
'my_memory_store_with_custom_strategy' => [],
216+
],
217+
],
218+
],
219+
]);
220+
221+
$this->assertTrue($container->hasDefinition('ai.store.memory.my_memory_store_with_custom_strategy'));
222+
223+
$definition = $container->getDefinition('ai.store.memory.my_memory_store_with_custom_strategy');
224+
$this->assertCount(0, $definition->getArguments());
225+
}
226+
227+
public function testInMemoryStoreWithCustomStrategyCanBeConfigured()
228+
{
229+
$container = $this->buildContainer([
230+
'ai' => [
231+
'store' => [
232+
'memory' => [
233+
'my_memory_store_with_custom_strategy' => [
234+
'strategy' => 'chebyshev',
235+
],
236+
],
237+
],
238+
],
239+
]);
240+
241+
$this->assertTrue($container->hasDefinition('ai.store.memory.my_memory_store_with_custom_strategy'));
242+
$this->assertTrue($container->hasDefinition('ai.store.distance_calculator.my_memory_store_with_custom_strategy'));
243+
244+
$definition = $container->getDefinition('ai.store.memory.my_memory_store_with_custom_strategy');
245+
246+
$this->assertCount(1, $definition->getArguments());
247+
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
248+
$this->assertSame('ai.store.distance_calculator.my_memory_store_with_custom_strategy', (string) $definition->getArgument(0));
249+
}
250+
123251
private function buildContainer(array $configuration): ContainerBuilder
124252
{
125253
$container = new ContainerBuilder();
@@ -224,6 +352,19 @@ private function getFullConfig(): array
224352
'my_cache_store' => [
225353
'service' => 'cache.system',
226354
],
355+
'my_cache_store_with_custom_key' => [
356+
'service' => 'cache.system',
357+
'cache_key' => 'bar',
358+
],
359+
'my_cache_store_with_custom_strategy' => [
360+
'service' => 'cache.system',
361+
'strategy' => 'chebyshev',
362+
],
363+
'my_cache_store_with_custom_strategy_and_custom_key' => [
364+
'service' => 'cache.system',
365+
'cache_key' => 'bar',
366+
'strategy' => 'chebyshev',
367+
],
227368
],
228369
'chroma_db' => [
229370
'my_chroma_store' => [
@@ -249,7 +390,7 @@ private function getFullConfig(): array
249390
],
250391
'memory' => [
251392
'my_memory_store' => [
252-
'distance' => 'cosine',
393+
'strategy' => 'cosine',
253394
],
254395
],
255396
'mongodb' => [

src/store/doc/index.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ You can find more advanced usage in combination with an Agent using the store fo
4343
* `Similarity Search with MongoDB (RAG)`_
4444
* `Similarity Search with Neo4j (RAG)`_
4545
* `Similarity Search with Pinecone (RAG)`_
46-
* `Similarity Search with PSR-6 Cache (RAG)`_
4746
* `Similarity Search with Qdrant (RAG)`_
4847
* `Similarity Search with SurrealDB (RAG)`_
48+
* `Similarity Search with Symfony Cache (RAG)`_
4949
* `Similarity Search with Typesense (RAG)`_
5050

5151
.. note::
@@ -66,9 +66,9 @@ Supported Stores
6666
* `Neo4j`_
6767
* `Pinecone`_ (requires `probots-io/pinecone-php` as additional dependency)
6868
* `Postgres`_ (requires `ext-pdo`)
69-
* `PSR-6 Cache`_
7069
* `Qdrant`_
7170
* `SurrealDB`_
71+
* `Symfony Cache`_
7272
* `Typesense`_
7373

7474
.. note::
@@ -106,7 +106,7 @@ This leads to a store implementing two methods::
106106
.. _`Similarity Search with memory storage (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/in-memory.php
107107
.. _`Similarity Search with Neo4j (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/neo4j.php
108108
.. _`Similarity Search with Pinecone (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/pinecone.php
109-
.. _`Similarity Search with PSR-6 Cache (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/cache.php
109+
.. _`Similarity Search with Symfony Cache (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/cache.php
110110
.. _`Similarity Search with Qdrant (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/qdrant.php
111111
.. _`Similarity Search with SurrealDB (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/surrealdb.php
112112
.. _`Similarity Search with Typesense (RAG)`: https://github.com/symfony/ai/blob/main/examples/rag/typesense.php
@@ -123,4 +123,4 @@ This leads to a store implementing two methods::
123123
.. _`Neo4j`: https://neo4j.com/
124124
.. _`Typesense`: https://typesense.org/
125125
.. _`GitHub`: https://github.com/symfony/ai/issues/16
126-
.. _`PSR-6 Cache`: https://www.php-fig.org/psr/psr-6/
126+
.. _`Symfony Cache`: https://symfony.com/doc/current/components/cache.html

src/store/src/Bridge/Local/CacheStore.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public function __construct(
3030
private DistanceCalculator $distanceCalculator = new DistanceCalculator(),
3131
private string $cacheKey = '_vectors',
3232
) {
33-
if (!interface_exists(CacheItemPoolInterface::class)) {
34-
throw new RuntimeException('For using the CacheStore as vector store, a PSR-6 cache implementation is required. Try running "composer require symfony/cache" or another PSR-6 compatible cache.');
33+
if (!interface_exists(CacheInterface::class)) {
34+
throw new RuntimeException('For using the CacheStore as vector store, a symfony/contracts cache implementation is required. Try running "composer require symfony/cache" or another symfony/contracts compatible cache.');
3535
}
3636
}
3737

@@ -62,7 +62,7 @@ public function add(VectorDocument ...$documents): void
6262
*/
6363
public function query(Vector $vector, array $options = []): array
6464
{
65-
$documents = $this->cache->getItem($this->cacheKey)->get() ?? [];
65+
$documents = $this->cache->get($this->cacheKey, static fn (): array => []);
6666

6767
$vectorDocuments = array_map(static fn (array $document): VectorDocument => new VectorDocument(
6868
id: Uuid::fromString($document['id']),

0 commit comments

Comments
 (0)