diff --git a/js/plugins/pinecone/package.json b/js/plugins/pinecone/package.json index 1117174b21..e274616e25 100644 --- a/js/plugins/pinecone/package.json +++ b/js/plugins/pinecone/package.json @@ -20,7 +20,8 @@ "compile": "tsup-node", "build:clean": "rimraf ./lib", "build": "npm-run-all build:clean check compile", - "build:watch": "tsup-node --watch" + "build:watch": "tsup-node --watch", + "test": "node --import tsx --test tests/*_test.ts" }, "repository": { "type": "git", diff --git a/js/plugins/pinecone/src/index.ts b/js/plugins/pinecone/src/index.ts index 3f86f364d9..b91629e437 100644 --- a/js/plugins/pinecone/src/index.ts +++ b/js/plugins/pinecone/src/index.ts @@ -31,6 +31,7 @@ import { retrieverRef, } from 'genkit/retriever'; import { Md5 } from 'ts-md5'; +import { toPineconeQuery } from './query.js'; const SparseVectorSchema = z .object({ @@ -185,12 +186,13 @@ export function configurePineconeRetriever< const scopedIndex = !!options.namespace ? index.namespace(options.namespace) : index; - const response = await scopedIndex.query({ - topK: options.k, - vector: queryEmbeddings[0].embedding, - includeValues: false, - includeMetadata: true, - }); + const embedding = queryEmbeddings[0]?.embedding; + if (!embedding) { + throw new Error('No embedding returned from the embedder.'); + } + const response = await scopedIndex.query( + toPineconeQuery(options, embedding) + ); return { documents: response.matches .map((m) => m.metadata) diff --git a/js/plugins/pinecone/src/query.ts b/js/plugins/pinecone/src/query.ts new file mode 100644 index 0000000000..af126b06fc --- /dev/null +++ b/js/plugins/pinecone/src/query.ts @@ -0,0 +1,41 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Builds the Pinecone query request from retriever options. + */ +export function toPineconeQuery( + options: { + k: number; + filter?: Record; + sparseVector?: { + indices: number[]; + values: number[]; + }; + }, + embedding: number[] +) { + return { + topK: options.k, + vector: embedding, + includeValues: false as const, + includeMetadata: true as const, + ...(options.filter !== undefined ? { filter: options.filter } : {}), + ...(options.sparseVector !== undefined + ? { sparseVector: options.sparseVector } + : {}), + }; +} diff --git a/js/plugins/pinecone/tests/retriever_test.ts b/js/plugins/pinecone/tests/retriever_test.ts new file mode 100644 index 0000000000..5ae84d1490 --- /dev/null +++ b/js/plugins/pinecone/tests/retriever_test.ts @@ -0,0 +1,52 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from 'assert'; +import { describe, it } from 'node:test'; +import { toPineconeQuery } from '../src/query.js'; + +describe('toPineconeQuery', () => { + it('includes filter when provided', () => { + const filter = { category: { $eq: 'appliances' } }; + assert.deepStrictEqual(toPineconeQuery({ k: 10, filter }, [0.1, 0.2]), { + topK: 10, + vector: [0.1, 0.2], + includeValues: false, + includeMetadata: true, + filter, + }); + }); + + it('omits filter when not provided', () => { + assert.deepStrictEqual(toPineconeQuery({ k: 5 }, [0.3]), { + topK: 5, + vector: [0.3], + includeValues: false, + includeMetadata: true, + }); + }); + + it('includes sparseVector when provided', () => { + const sparseVector = { indices: [1, 2], values: [0.1, 0.2] }; + assert.deepStrictEqual(toPineconeQuery({ k: 5, sparseVector }, [0.3]), { + topK: 5, + vector: [0.3], + includeValues: false, + includeMetadata: true, + sparseVector, + }); + }); +});