Skip to content

Commit 9eb3ccd

Browse files
committed
[Store][Pinecone] Add ManagedStoreInterface support
1 parent c600c34 commit 9eb3ccd

File tree

9 files changed

+151
-112
lines changed

9 files changed

+151
-112
lines changed

examples/.env

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ MAPBOX_ACCESS_TOKEN=
8080
MONGODB_URI=mongodb://symfony:symfony@127.0.0.1:27017
8181

8282
# For using Pinecone (store)
83-
PINECONE_API_KEY=
84-
PINECONE_HOST=
83+
PINECONE_API_KEY=pclocal
84+
PINECONE_HOST=http://127.0.0.1:5080
8585

8686
# For using Postgres (store)
8787
POSTGRES_URI=pdo-pgsql://postgres:postgres@127.0.1:5432/my_database

examples/commands/stores.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Doctrine\DBAL\DriverManager;
1515
use Doctrine\DBAL\Tools\DsnParser;
1616
use MongoDB\Client as MongoDbClient;
17+
use Probots\Pinecone\Client as PineconeClient;
1718
use Symfony\AI\Store\Bridge\Cache\Store as CacheStore;
1819
use Symfony\AI\Store\Bridge\ClickHouse\Store as ClickHouseStore;
1920
use Symfony\AI\Store\Bridge\Elasticsearch\Store as ElasticsearchStore;
@@ -24,6 +25,7 @@
2425
use Symfony\AI\Store\Bridge\MongoDb\Store as MongoDbStore;
2526
use Symfony\AI\Store\Bridge\Neo4j\Store as Neo4jStore;
2627
use Symfony\AI\Store\Bridge\OpenSearch\Store as OpenSearchStore;
28+
use Symfony\AI\Store\Bridge\Pinecone\Store as PineconeStore;
2729
use Symfony\AI\Store\Bridge\Postgres\Store as PostgresStore;
2830
use Symfony\AI\Store\Bridge\Qdrant\Store as QdrantStore;
2931
use Symfony\AI\Store\Bridge\Redis\Store as RedisStore;
@@ -99,6 +101,10 @@
99101
// env('OPENSEARCH_ENDPOINT'),
100102
// 'symfony',
101103
// ),
104+
// 'pinecone' => static fn (): PineconeStore => new PineconeStore(
105+
// new PineconeClient(env('PINECONE_API_KEY'), env('PINECONE_HOST')),
106+
// 'symfony',
107+
// ),
102108
'postgres' => static fn (): PostgresStore => PostgresStore::fromDbal(
103109
DriverManager::getConnection((new DsnParser())->parse(env('POSTGRES_URI'))),
104110
'my_table',

examples/compose.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,15 @@ services:
146146
ports:
147147
- '9201:9200'
148148

149+
pinecone:
150+
image: ghcr.io/pinecone-io/pinecone-local:latest
151+
platform: linux/amd64
152+
environment:
153+
PORT: 5080
154+
PINECONE_HOST: localhost
155+
ports:
156+
- '5080-5090:5080-5090'
157+
149158
opensearch:
150159
image: opensearchproject/opensearch
151160
environment:

examples/rag/pinecone.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
require_once dirname(__DIR__).'/bootstrap.php';
3030

3131
// initialize the store
32-
$store = new Store(Pinecone::client(env('PINECONE_API_KEY'), env('PINECONE_HOST')));
32+
$store = new Store(Pinecone::client(env('PINECONE_API_KEY'), env('PINECONE_HOST')), 'symfony');
3333

3434
// create embeddings and documents
3535
$documents = [];

src/ai-bundle/config/options.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,7 @@
800800
->cannotBeEmpty()
801801
->defaultValue(PineconeClient::class)
802802
->end()
803+
->stringNode('index_name')->isRequired()->end()
803804
->stringNode('namespace')->end()
804805
->arrayNode('filter')
805806
->scalarPrototype()

src/ai-bundle/src/AiBundle.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1519,19 +1519,21 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
15191519
foreach ($stores as $name => $store) {
15201520
$arguments = [
15211521
new Reference($store['client']),
1522+
$store['index_name'],
15221523
$store['namespace'] ?? $name,
15231524
$store['filter'],
15241525
];
15251526

15261527
if (\array_key_exists('top_k', $store)) {
1527-
$arguments[3] = $store['top_k'];
1528+
$arguments[4] = $store['top_k'];
15281529
}
15291530

15301531
$definition = new Definition(PineconeStore::class);
15311532
$definition
15321533
->setLazy(true)
15331534
->setArguments($arguments)
15341535
->addTag('proxy', ['interface' => StoreInterface::class])
1536+
->addTag('proxy', ['interface' => ManagedStoreInterface::class])
15351537
->addTag('ai.store');
15361538

15371539
$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);

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

Lines changed: 15 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -2370,48 +2370,13 @@ public function testOpenSearchStoreWithCustomHttpClientCanBeConfigured()
23702370
}
23712371

23722372
public function testPineconeStoreCanBeConfigured()
2373-
{
2374-
$container = $this->buildContainer([
2375-
'ai' => [
2376-
'store' => [
2377-
'pinecone' => [
2378-
'my_pinecone_store' => [],
2379-
],
2380-
],
2381-
],
2382-
]);
2383-
2384-
$this->assertTrue($container->hasDefinition('ai.store.pinecone.my_pinecone_store'));
2385-
2386-
$definition = $container->getDefinition('ai.store.pinecone.my_pinecone_store');
2387-
$this->assertSame(PineconeStore::class, $definition->getClass());
2388-
2389-
$this->assertTrue($definition->isLazy());
2390-
$this->assertCount(3, $definition->getArguments());
2391-
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
2392-
$this->assertSame(PineconeClient::class, (string) $definition->getArgument(0));
2393-
$this->assertSame('my_pinecone_store', $definition->getArgument(1));
2394-
$this->assertSame([], $definition->getArgument(2));
2395-
2396-
$this->assertTrue($definition->hasTag('proxy'));
2397-
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
2398-
$this->assertTrue($definition->hasTag('ai.store'));
2399-
2400-
$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_pinecone_store'));
2401-
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myPineconeStore'));
2402-
$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $pinecone_my_pinecone_store'));
2403-
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $pineconeMyPineconeStore'));
2404-
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface'));
2405-
}
2406-
2407-
public function testPineconeStoreWithCustomNamespaceCanBeConfigured()
24082373
{
24092374
$container = $this->buildContainer([
24102375
'ai' => [
24112376
'store' => [
24122377
'pinecone' => [
24132378
'my_pinecone_store' => [
2414-
'namespace' => 'my_namespace',
2379+
'index_name' => 'my_index',
24152380
],
24162381
],
24172382
],
@@ -2424,14 +2389,15 @@ public function testPineconeStoreWithCustomNamespaceCanBeConfigured()
24242389
$this->assertSame(PineconeStore::class, $definition->getClass());
24252390

24262391
$this->assertTrue($definition->isLazy());
2427-
$this->assertCount(3, $definition->getArguments());
2392+
$this->assertCount(4, $definition->getArguments());
24282393
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
24292394
$this->assertSame(PineconeClient::class, (string) $definition->getArgument(0));
2430-
$this->assertSame('my_namespace', $definition->getArgument(1));
2431-
$this->assertSame([], $definition->getArgument(2));
2395+
$this->assertSame('my_index', $definition->getArgument(1));
2396+
$this->assertSame('my_pinecone_store', $definition->getArgument(2));
2397+
$this->assertSame([], $definition->getArgument(3));
24322398

24332399
$this->assertTrue($definition->hasTag('proxy'));
2434-
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
2400+
$this->assertSame([['interface' => StoreInterface::class], ['interface' => ManagedStoreInterface::class]], $definition->getTag('proxy'));
24352401
$this->assertTrue($definition->hasTag('ai.store'));
24362402

24372403
$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_pinecone_store'));
@@ -2441,14 +2407,14 @@ public function testPineconeStoreWithCustomNamespaceCanBeConfigured()
24412407
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface'));
24422408
}
24432409

2444-
public function testPineconeStoreWithCustomClientCanBeConfigured()
2410+
public function testPineconeStoreWithCustomIndexNameCanBeConfigured()
24452411
{
24462412
$container = $this->buildContainer([
24472413
'ai' => [
24482414
'store' => [
24492415
'pinecone' => [
24502416
'my_pinecone_store' => [
2451-
'client' => 'foo',
2417+
'index_name' => 'custom_index',
24522418
'namespace' => 'my_namespace',
24532419
],
24542420
],
@@ -2461,54 +2427,16 @@ public function testPineconeStoreWithCustomClientCanBeConfigured()
24612427
$definition = $container->getDefinition('ai.store.pinecone.my_pinecone_store');
24622428
$this->assertSame(PineconeStore::class, $definition->getClass());
24632429

2464-
$this->assertTrue($definition->isLazy());
2465-
$this->assertCount(3, $definition->getArguments());
2466-
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
2467-
$this->assertSame('foo', (string) $definition->getArgument(0));
2468-
$this->assertSame('my_namespace', $definition->getArgument(1));
2469-
$this->assertSame([], $definition->getArgument(2));
2470-
2471-
$this->assertTrue($definition->hasTag('proxy'));
2472-
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
2473-
$this->assertTrue($definition->hasTag('ai.store'));
2474-
2475-
$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_pinecone_store'));
2476-
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myPineconeStore'));
2477-
$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $pinecone_my_pinecone_store'));
2478-
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $pineconeMyPineconeStore'));
2479-
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface'));
2480-
}
2481-
2482-
public function testPineconeStoreWithTopKCanBeConfigured()
2483-
{
2484-
$container = $this->buildContainer([
2485-
'ai' => [
2486-
'store' => [
2487-
'pinecone' => [
2488-
'my_pinecone_store' => [
2489-
'namespace' => 'my_namespace',
2490-
'top_k' => 100,
2491-
],
2492-
],
2493-
],
2494-
],
2495-
]);
2496-
2497-
$this->assertTrue($container->hasDefinition('ai.store.pinecone.my_pinecone_store'));
2498-
2499-
$definition = $container->getDefinition('ai.store.pinecone.my_pinecone_store');
2500-
$this->assertSame(PineconeStore::class, $definition->getClass());
2501-
25022430
$this->assertTrue($definition->isLazy());
25032431
$this->assertCount(4, $definition->getArguments());
25042432
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
25052433
$this->assertSame(PineconeClient::class, (string) $definition->getArgument(0));
2506-
$this->assertSame('my_namespace', $definition->getArgument(1));
2507-
$this->assertSame([], $definition->getArgument(2));
2508-
$this->assertSame(100, $definition->getArgument(3));
2434+
$this->assertSame('custom_index', $definition->getArgument(1));
2435+
$this->assertSame('my_namespace', $definition->getArgument(2));
2436+
$this->assertSame([], $definition->getArgument(3));
25092437

25102438
$this->assertTrue($definition->hasTag('proxy'));
2511-
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
2439+
$this->assertSame([['interface' => StoreInterface::class], ['interface' => ManagedStoreInterface::class]], $definition->getTag('proxy'));
25122440
$this->assertTrue($definition->hasTag('ai.store'));
25132441

25142442
$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_pinecone_store'));
@@ -7317,15 +7245,18 @@ private function getFullConfig(): array
73177245
],
73187246
'pinecone' => [
73197247
'my_pinecone_store' => [
7248+
'index_name' => 'my_index',
73207249
'namespace' => 'my_namespace',
73217250
'filter' => ['category' => 'books'],
73227251
'top_k' => 10,
73237252
],
73247253
'my_pinecone_store_with_filter' => [
7254+
'index_name' => 'my_index',
73257255
'namespace' => 'my_namespace',
73267256
'filter' => ['category' => 'books'],
73277257
],
73287258
'my_pinecone_store_with_top_k' => [
7259+
'index_name' => 'my_index',
73297260
'namespace' => 'my_namespace',
73307261
'filter' => ['category' => 'books'],
73317262
'top_k' => 10,

src/store/src/Bridge/Pinecone/Store.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,53 @@
1616
use Symfony\AI\Platform\Vector\Vector;
1717
use Symfony\AI\Store\Document\Metadata;
1818
use Symfony\AI\Store\Document\VectorDocument;
19+
use Symfony\AI\Store\Exception\InvalidArgumentException;
20+
use Symfony\AI\Store\ManagedStoreInterface;
1921
use Symfony\AI\Store\StoreInterface;
2022
use Symfony\Component\Uid\Uuid;
2123

2224
/**
2325
* @author Christopher Hertel <mail@christopher-hertel.de>
2426
*/
25-
final class Store implements StoreInterface
27+
final class Store implements ManagedStoreInterface, StoreInterface
2628
{
2729
/**
2830
* @param array<string, mixed> $filter
2931
*/
3032
public function __construct(
3133
private readonly Client $pinecone,
34+
private readonly string $indexName,
3235
private readonly ?string $namespace = null,
3336
private readonly array $filter = [],
3437
private readonly int $topK = 3,
3538
) {
3639
}
3740

41+
/**
42+
* @param array{
43+
* dimension?: int,
44+
* metric?: string,
45+
* cloud?: string,
46+
* region?: string,
47+
* } $options
48+
*/
49+
public function setup(array $options = []): void
50+
{
51+
if (false === isset($options['dimension'])) {
52+
throw new InvalidArgumentException('The "dimension" option is required.');
53+
}
54+
55+
$this->pinecone
56+
->control()
57+
->index($this->indexName)
58+
->createServerless(
59+
$options['dimension'],
60+
$options['metric'] ?? null,
61+
$options['cloud'] ?? null,
62+
$options['region'] ?? null,
63+
);
64+
}
65+
3866
public function add(VectorDocument ...$documents): void
3967
{
4068
$vectors = [];
@@ -73,6 +101,14 @@ public function query(Vector $vector, array $options = []): iterable
73101
}
74102
}
75103

104+
public function drop(array $options = []): void
105+
{
106+
$this->pinecone
107+
->control()
108+
->index($this->indexName)
109+
->delete();
110+
}
111+
76112
private function getVectors(): VectorResource
77113
{
78114
return $this->pinecone->data()->vectors();

0 commit comments

Comments
 (0)