From f1062747293fb00a16230702cb7a7a40e8637512 Mon Sep 17 00:00:00 2001 From: Diego Aguiar Date: Thu, 13 Nov 2025 10:05:47 -0700 Subject: [PATCH 1/3] Add Pinecone ManagedStore --- .../src/Bridge/Pinecone/ManagedStore.php | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/store/src/Bridge/Pinecone/ManagedStore.php diff --git a/src/store/src/Bridge/Pinecone/ManagedStore.php b/src/store/src/Bridge/Pinecone/ManagedStore.php new file mode 100644 index 000000000..86d916a61 --- /dev/null +++ b/src/store/src/Bridge/Pinecone/ManagedStore.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Store\Bridge\Pinecone; + +use Probots\Pinecone\Client; +use Symfony\AI\Store\Exception\RuntimeException; +use Symfony\AI\Store\ManagedStoreInterface; + +final class ManagedStore implements ManagedStoreInterface +{ + public function __construct( + private readonly Client $pinecone, + private readonly string $indexName, + private readonly int $dimension, + private readonly string $metric, + private readonly string $cloud, + private readonly string $region, + ) { + if (!class_exists(Client::class)) { + throw new RuntimeException('For using the Pinecone as retrieval vector store, the probots-io/pinecone-php package is required. Try running "composer require probots-io/pinecone-php".'); + } + } + + public function setup(array $options = []): void + { + $this->pinecone + ->control() + ->index($this->indexName) + ->createServerless($this->dimension, $this->metric, $this->cloud, $this->region); + } + + public function drop(): void + { + $this->pinecone + ->control() + ->index($this->indexName) + ->delete(); + } +} From 741a7503c1a8ff64a129ba7381dc3db09dc27990 Mon Sep 17 00:00:00 2001 From: Diego Aguiar Date: Fri, 12 Dec 2025 11:53:09 -0700 Subject: [PATCH 2/3] Implement Pinecone managed store and update ManagedStoreInterface::drop() --- src/store/src/Bridge/Cache/Store.php | 2 +- src/store/src/Bridge/ClickHouse/Store.php | 2 +- src/store/src/Bridge/Cloudflare/Store.php | 2 +- .../src/Bridge/ManticoreSearch/Store.php | 2 +- src/store/src/Bridge/MariaDb/Store.php | 2 +- src/store/src/Bridge/Meilisearch/Store.php | 2 +- src/store/src/Bridge/Milvus/Store.php | 2 +- src/store/src/Bridge/MongoDb/Store.php | 2 +- src/store/src/Bridge/Neo4j/Store.php | 2 +- src/store/src/Bridge/OpenSearch/Store.php | 2 +- .../src/Bridge/Pinecone/ManagedStore.php | 48 ------------- src/store/src/Bridge/Pinecone/Store.php | 33 ++++++++- .../src/Bridge/Pinecone/Tests/StoreTest.php | 72 +++++++++++++++++++ src/store/src/Bridge/Postgres/Store.php | 2 +- src/store/src/Bridge/Qdrant/Store.php | 2 +- src/store/src/Bridge/Redis/Store.php | 2 +- src/store/src/Bridge/SurrealDb/Store.php | 2 +- src/store/src/Bridge/Typesense/Store.php | 2 +- src/store/src/Bridge/Weaviate/Store.php | 2 +- src/store/src/ManagedStoreInterface.php | 2 +- 20 files changed, 121 insertions(+), 66 deletions(-) delete mode 100644 src/store/src/Bridge/Pinecone/ManagedStore.php diff --git a/src/store/src/Bridge/Cache/Store.php b/src/store/src/Bridge/Cache/Store.php index ce6e12a99..c994531c0 100644 --- a/src/store/src/Bridge/Cache/Store.php +++ b/src/store/src/Bridge/Cache/Store.php @@ -91,7 +91,7 @@ public function query(Vector $vector, array $options = []): iterable yield from $this->distanceCalculator->calculate($vectorDocuments, $vector, $options['maxItems'] ?? null); } - public function drop(): void + public function drop(array $options = []): void { $this->cache->clear(); } diff --git a/src/store/src/Bridge/ClickHouse/Store.php b/src/store/src/Bridge/ClickHouse/Store.php index 1b5a06623..dc2b783bc 100644 --- a/src/store/src/Bridge/ClickHouse/Store.php +++ b/src/store/src/Bridge/ClickHouse/Store.php @@ -48,7 +48,7 @@ public function setup(array $options = []): void $this->execute('POST', $sql); } - public function drop(): void + public function drop(array $options = []): void { $this->execute('POST', 'DROP TABLE IF EXISTS {{ table }}'); } diff --git a/src/store/src/Bridge/Cloudflare/Store.php b/src/store/src/Bridge/Cloudflare/Store.php index 446c161ba..8cd1e5943 100644 --- a/src/store/src/Bridge/Cloudflare/Store.php +++ b/src/store/src/Bridge/Cloudflare/Store.php @@ -52,7 +52,7 @@ public function setup(array $options = []): void ]); } - public function drop(): void + public function drop(array $options = []): void { $this->request('DELETE', \sprintf('vectorize/v2/indexes/%s', $this->index)); } diff --git a/src/store/src/Bridge/ManticoreSearch/Store.php b/src/store/src/Bridge/ManticoreSearch/Store.php index 1583d6e4f..6875b8d4b 100644 --- a/src/store/src/Bridge/ManticoreSearch/Store.php +++ b/src/store/src/Bridge/ManticoreSearch/Store.php @@ -50,7 +50,7 @@ public function setup(array $options = []): void )); } - public function drop(): void + public function drop(array $options = []): void { $this->request('cli', \sprintf('DROP TABLE %s', $this->table)); } diff --git a/src/store/src/Bridge/MariaDb/Store.php b/src/store/src/Bridge/MariaDb/Store.php index 0219fe8f1..3c9668ad1 100644 --- a/src/store/src/Bridge/MariaDb/Store.php +++ b/src/store/src/Bridge/MariaDb/Store.php @@ -76,7 +76,7 @@ public function setup(array $options = []): void ); } - public function drop(): void + public function drop(array $options = []): void { $this->connection->exec(\sprintf('DROP TABLE IF EXISTS %s', $this->tableName)); } diff --git a/src/store/src/Bridge/Meilisearch/Store.php b/src/store/src/Bridge/Meilisearch/Store.php index 6f59c7e81..4a3ce409e 100644 --- a/src/store/src/Bridge/Meilisearch/Store.php +++ b/src/store/src/Bridge/Meilisearch/Store.php @@ -101,7 +101,7 @@ public function query(Vector $vector, array $options = []): iterable } } - public function drop(): void + public function drop(array $options = []): void { $this->request('DELETE', \sprintf('indexes/%s', $this->indexName), []); } diff --git a/src/store/src/Bridge/Milvus/Store.php b/src/store/src/Bridge/Milvus/Store.php index 13dc16bc9..bd9549407 100644 --- a/src/store/src/Bridge/Milvus/Store.php +++ b/src/store/src/Bridge/Milvus/Store.php @@ -122,7 +122,7 @@ public function query(Vector $vector, array $options = []): iterable } } - public function drop(): void + public function drop(array $options = []): void { $this->request('POST', 'v2/vectordb/databases/drop', [ 'dbName' => $this->database, diff --git a/src/store/src/Bridge/MongoDb/Store.php b/src/store/src/Bridge/MongoDb/Store.php index 09d1c0b46..1d407c2d6 100644 --- a/src/store/src/Bridge/MongoDb/Store.php +++ b/src/store/src/Bridge/MongoDb/Store.php @@ -99,7 +99,7 @@ public function setup(array $options = []): void } } - public function drop(): void + public function drop(array $options = []): void { $this->getCollection()->drop(); } diff --git a/src/store/src/Bridge/Neo4j/Store.php b/src/store/src/Bridge/Neo4j/Store.php index 1662ddee2..8007e450d 100644 --- a/src/store/src/Bridge/Neo4j/Store.php +++ b/src/store/src/Bridge/Neo4j/Store.php @@ -79,7 +79,7 @@ public function query(Vector $vector, array $options = []): iterable } } - public function drop(): void + public function drop(array $options = []): void { $this->request('POST', \sprintf('db/%s/query/v2', $this->databaseName), [ 'statement' => 'MATCH (n) DETACH DELETE n', diff --git a/src/store/src/Bridge/OpenSearch/Store.php b/src/store/src/Bridge/OpenSearch/Store.php index 4fc658e8b..af9228b81 100644 --- a/src/store/src/Bridge/OpenSearch/Store.php +++ b/src/store/src/Bridge/OpenSearch/Store.php @@ -60,7 +60,7 @@ public function setup(array $options = []): void ]); } - public function drop(): void + public function drop(array $options = []): void { $indexExistResponse = $this->httpClient->request('HEAD', \sprintf('%s/%s', $this->endpoint, $this->indexName)); diff --git a/src/store/src/Bridge/Pinecone/ManagedStore.php b/src/store/src/Bridge/Pinecone/ManagedStore.php deleted file mode 100644 index 86d916a61..000000000 --- a/src/store/src/Bridge/Pinecone/ManagedStore.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\AI\Store\Bridge\Pinecone; - -use Probots\Pinecone\Client; -use Symfony\AI\Store\Exception\RuntimeException; -use Symfony\AI\Store\ManagedStoreInterface; - -final class ManagedStore implements ManagedStoreInterface -{ - public function __construct( - private readonly Client $pinecone, - private readonly string $indexName, - private readonly int $dimension, - private readonly string $metric, - private readonly string $cloud, - private readonly string $region, - ) { - if (!class_exists(Client::class)) { - throw new RuntimeException('For using the Pinecone as retrieval vector store, the probots-io/pinecone-php package is required. Try running "composer require probots-io/pinecone-php".'); - } - } - - public function setup(array $options = []): void - { - $this->pinecone - ->control() - ->index($this->indexName) - ->createServerless($this->dimension, $this->metric, $this->cloud, $this->region); - } - - public function drop(): void - { - $this->pinecone - ->control() - ->index($this->indexName) - ->delete(); - } -} diff --git a/src/store/src/Bridge/Pinecone/Store.php b/src/store/src/Bridge/Pinecone/Store.php index deabd8bbb..e5003ca82 100644 --- a/src/store/src/Bridge/Pinecone/Store.php +++ b/src/store/src/Bridge/Pinecone/Store.php @@ -16,13 +16,15 @@ use Symfony\AI\Platform\Vector\Vector; use Symfony\AI\Store\Document\Metadata; use Symfony\AI\Store\Document\VectorDocument; +use Symfony\AI\Store\Exception\InvalidArgumentException; +use Symfony\AI\Store\ManagedStoreInterface; use Symfony\AI\Store\StoreInterface; use Symfony\Component\Uid\Uuid; /** * @author Christopher Hertel */ -final class Store implements StoreInterface +final class Store implements StoreInterface, ManagedStoreInterface { /** * @param array $filter @@ -35,6 +37,23 @@ public function __construct( ) { } + public function setup(array $options = []): void + { + if (false === isset($options['indexName']) || false === isset($options['dimension'])) { + throw new InvalidArgumentException('No supported options.'); + } + + $this->pinecone + ->control() + ->index($options['indexName']) + ->createServerless( + $options['dimension'], + $options['metric'] ?? null, + $options['cloud'] ?? null, + $options['region'] ?? null, + ); + } + public function add(VectorDocument ...$documents): void { $vectors = []; @@ -73,6 +92,18 @@ public function query(Vector $vector, array $options = []): iterable } } + public function drop(array $options = []): void + { + if (false === isset($options['indexName'])) { + throw new InvalidArgumentException('No supported options.'); + } + + $this->pinecone + ->control() + ->index($options['indexName']) + ->delete(); + } + private function getVectors(): VectorResource { return $this->pinecone->data()->vectors(); diff --git a/src/store/src/Bridge/Pinecone/Tests/StoreTest.php b/src/store/src/Bridge/Pinecone/Tests/StoreTest.php index a3fb23bde..6674a6902 100644 --- a/src/store/src/Bridge/Pinecone/Tests/StoreTest.php +++ b/src/store/src/Bridge/Pinecone/Tests/StoreTest.php @@ -13,6 +13,8 @@ use PHPUnit\Framework\TestCase; use Probots\Pinecone\Client; +use Probots\Pinecone\Resources\Control\IndexResource; +use Probots\Pinecone\Resources\ControlResource; use Probots\Pinecone\Resources\Data\VectorResource; use Probots\Pinecone\Resources\DataResource; use Saloon\Http\Response; @@ -24,6 +26,76 @@ final class StoreTest extends TestCase { + public function testStoreCantSetupWithInvalidOptions() + { + $pinecone = $this->createMock(Client::class); + $store = new Store($pinecone); + + $this->expectException(\InvalidArgumentException::class); + + $store->setup(); + } + + public function testStoreCanSetup() + { + $pinecone = $this->createMock(Client::class); + $indexResource = $this->createMock(IndexResource::class); + $controlResource = $this->createMock(ControlResource::class); + + $pinecone->expects($this->once()) + ->method('control') + ->willReturn($controlResource); + + $controlResource->expects($this->once()) + ->method('index') + ->with('test-index') + ->willReturn($indexResource); + + $indexResource->expects($this->once()) + ->method('createServerless') + ->with(1536, null, null, null); + + $store = new Store($pinecone); + $store->setup([ + 'indexName' => 'test-index', + 'dimension' => 1536, + ]); + } + + public function testStoreCantDropWithInvalidOptions() + { + $pinecone = $this->createMock(Client::class); + $store = new Store($pinecone); + + $this->expectException(\InvalidArgumentException::class); + + $store->drop(); + } + + public function testStoreCanDrop() + { + $pinecone = $this->createMock(Client::class); + $indexResource = $this->createMock(IndexResource::class); + $controlResource = $this->createMock(ControlResource::class); + + $pinecone->expects($this->once()) + ->method('control') + ->willReturn($controlResource); + + $controlResource->expects($this->once()) + ->method('index') + ->with('test-index') + ->willReturn($indexResource); + + $indexResource->expects($this->once()) + ->method('delete'); + + $store = new Store($pinecone); + $store->drop([ + 'indexName' => 'test-index', + ]); + } + public function testAddSingleDocument() { $vectorResource = $this->createMock(VectorResource::class); diff --git a/src/store/src/Bridge/Postgres/Store.php b/src/store/src/Bridge/Postgres/Store.php index 9294ca2ac..92001d04b 100644 --- a/src/store/src/Bridge/Postgres/Store.php +++ b/src/store/src/Bridge/Postgres/Store.php @@ -75,7 +75,7 @@ public function setup(array $options = []): void ); } - public function drop(): void + public function drop(array $options = []): void { $this->connection->exec(\sprintf('DROP TABLE IF EXISTS %s', $this->tableName)); } diff --git a/src/store/src/Bridge/Qdrant/Store.php b/src/store/src/Bridge/Qdrant/Store.php index 6d6e4bbbb..deb4d760c 100644 --- a/src/store/src/Bridge/Qdrant/Store.php +++ b/src/store/src/Bridge/Qdrant/Store.php @@ -103,7 +103,7 @@ public function query(Vector $vector, array $options = []): iterable } } - public function drop(): void + public function drop(array $options = []): void { $this->request('DELETE', \sprintf('collections/%s', $this->collectionName)); } diff --git a/src/store/src/Bridge/Redis/Store.php b/src/store/src/Bridge/Redis/Store.php index 85b43aa8e..e15c6569a 100644 --- a/src/store/src/Bridge/Redis/Store.php +++ b/src/store/src/Bridge/Redis/Store.php @@ -67,7 +67,7 @@ public function setup(array $options = []): void $this->redis->clearLastError(); } - public function drop(): void + public function drop(array $options = []): void { try { $this->redis->rawCommand('FT.DROPINDEX', $this->indexName); diff --git a/src/store/src/Bridge/SurrealDb/Store.php b/src/store/src/Bridge/SurrealDb/Store.php index 8c5da67c6..660edc95a 100644 --- a/src/store/src/Bridge/SurrealDb/Store.php +++ b/src/store/src/Bridge/SurrealDb/Store.php @@ -75,7 +75,7 @@ public function query(Vector $vector, array $options = []): iterable } } - public function drop(): void + public function drop(array $options = []): void { $this->authenticate(); diff --git a/src/store/src/Bridge/Typesense/Store.php b/src/store/src/Bridge/Typesense/Store.php index f501ef99b..16c44ca4d 100644 --- a/src/store/src/Bridge/Typesense/Store.php +++ b/src/store/src/Bridge/Typesense/Store.php @@ -86,7 +86,7 @@ public function query(Vector $vector, array $options = []): iterable } } - public function drop(): void + public function drop(array $options = []): void { $this->request('DELETE', \sprintf('collections/%s', $this->collection), []); } diff --git a/src/store/src/Bridge/Weaviate/Store.php b/src/store/src/Bridge/Weaviate/Store.php index 07efcccd2..787a1fb80 100644 --- a/src/store/src/Bridge/Weaviate/Store.php +++ b/src/store/src/Bridge/Weaviate/Store.php @@ -78,7 +78,7 @@ public function query(Vector $vector, array $options = []): iterable } } - public function drop(): void + public function drop(array $options = []): void { $this->request('DELETE', \sprintf('v1/schema/%s', $this->collection), []); } diff --git a/src/store/src/ManagedStoreInterface.php b/src/store/src/ManagedStoreInterface.php index 3a2f3c4b5..981d1e75e 100644 --- a/src/store/src/ManagedStoreInterface.php +++ b/src/store/src/ManagedStoreInterface.php @@ -21,5 +21,5 @@ interface ManagedStoreInterface */ public function setup(array $options = []): void; - public function drop(): void; + public function drop(array $options = []): void; } From cd9853bf714f111ff49f9691eec75f57cb9f2eae Mon Sep 17 00:00:00 2001 From: Diego Aguiar Date: Fri, 12 Dec 2025 12:35:50 -0700 Subject: [PATCH 3/3] fix PHP CS --- src/store/src/InMemory/Store.php | 2 +- src/store/src/ManagedStoreInterface.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/store/src/InMemory/Store.php b/src/store/src/InMemory/Store.php index f6eca29da..a46ad085f 100644 --- a/src/store/src/InMemory/Store.php +++ b/src/store/src/InMemory/Store.php @@ -65,7 +65,7 @@ public function query(Vector $vector, array $options = []): iterable yield from $this->distanceCalculator->calculate($documents, $vector, $options['maxItems'] ?? null); } - public function drop(): void + public function drop(array $options = []): void { $this->documents = []; } diff --git a/src/store/src/ManagedStoreInterface.php b/src/store/src/ManagedStoreInterface.php index 981d1e75e..ac6399521 100644 --- a/src/store/src/ManagedStoreInterface.php +++ b/src/store/src/ManagedStoreInterface.php @@ -21,5 +21,8 @@ interface ManagedStoreInterface */ public function setup(array $options = []): void; + /** + * @param array $options + */ public function drop(array $options = []): void; }