diff --git a/libs/langchain-pinecone/jest.config.cjs b/libs/langchain-pinecone/jest.config.cjs index a49a8832a349..daf6c5865cbe 100644 --- a/libs/langchain-pinecone/jest.config.cjs +++ b/libs/langchain-pinecone/jest.config.cjs @@ -17,5 +17,5 @@ module.exports = { setupFiles: ["dotenv/config"], testTimeout: 20_000, passWithNoTests: true, - collectCoverageFrom: ["src/**/*.ts"] -}; + collectCoverageFrom: ["src/**/*.ts"], + }; diff --git a/libs/langchain-pinecone/src/client.ts b/libs/langchain-pinecone/src/client.ts index b251bb06f5de..f1eabb825db5 100644 --- a/libs/langchain-pinecone/src/client.ts +++ b/libs/langchain-pinecone/src/client.ts @@ -2,12 +2,15 @@ import { Pinecone, PineconeConfiguration } from "@pinecone-database/pinecone"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; export function getPineconeClient(config?: PineconeConfiguration): Pinecone { - if (getEnvironmentVariable("PINECONE_API_KEY") === undefined) { + if ( + getEnvironmentVariable("PINECONE_API_KEY") === undefined || + getEnvironmentVariable("PINECONE_API_KEY") === "" + ) { throw new Error("PINECONE_API_KEY must be set in environment"); } if (!config) { - return new Pinecone() + return new Pinecone(); } else { return new Pinecone(config); } -} \ No newline at end of file +} diff --git a/libs/langchain-pinecone/src/embeddings.ts b/libs/langchain-pinecone/src/embeddings.ts index 6299dd398629..a5c9350cbdec 100644 --- a/libs/langchain-pinecone/src/embeddings.ts +++ b/libs/langchain-pinecone/src/embeddings.ts @@ -1,3 +1,5 @@ +/* eslint-disable arrow-body-style */ + import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings"; import { EmbeddingsList, @@ -57,7 +59,6 @@ export class PineconeEmbeddings } } - // @returns A promise that resolves to an array of vectors for each document. async embedDocuments(texts: string[]): Promise { if (texts.length === 0) { throw new Error( @@ -65,15 +66,25 @@ export class PineconeEmbeddings ); } - let embeddings: EmbeddingsList; + let embeddings; if (this.params) { - embeddings = await this.client.inference.embed( - this.model, - texts, - this.params - ); + embeddings = await this.caller.call(async () => { + const result: EmbeddingsList = await this.client.inference.embed( + this.model, + texts, + this.params + ); + return result; + }); } else { - embeddings = await this.client.inference.embed(this.model, texts, {}); + embeddings = await this.caller.call(async () => { + const result: EmbeddingsList = await this.client.inference.embed( + this.model, + texts, + {} + ); + return result; + }); } const embeddingsList: number[][] = []; @@ -95,13 +106,17 @@ export class PineconeEmbeddings } let embeddings: EmbeddingsList; if (this.params) { - embeddings = await this.client.inference.embed( - this.model, - [text], - this.params - ); + embeddings = await this.caller.call(async () => { + return await this.client.inference.embed( + this.model, + [text], + this.params + ); + }); } else { - embeddings = await this.client.inference.embed(this.model, [text], {}); + embeddings = await this.caller.call(async () => { + return await this.client.inference.embed(this.model, [text], {}); + }); } if (embeddings[0].values) { return embeddings[0].values as number[]; diff --git a/libs/langchain-pinecone/src/tests/client.int.test.ts b/libs/langchain-pinecone/src/tests/client.int.test.ts new file mode 100644 index 000000000000..b52beb2cf2b8 --- /dev/null +++ b/libs/langchain-pinecone/src/tests/client.int.test.ts @@ -0,0 +1,39 @@ +import { Pinecone } from "@pinecone-database/pinecone"; +import { getPineconeClient } from "../client.js"; + +describe("Tests for getPineconeClient", () => { + test("Happy path for getPineconeClient with and without `config` obj passed", async () => { + const client = getPineconeClient(); + expect(client).toBeInstanceOf(Pinecone); + expect(client).toHaveProperty("config"); // Config is always set to *at least* the user's api key + + const clientWithConfig = getPineconeClient({ + // eslint-disable-next-line no-process-env + apiKey: process.env.PINECONE_API_KEY!, + additionalHeaders: { header: "value" }, + }); + expect(clientWithConfig).toBeInstanceOf(Pinecone); + expect(client).toHaveProperty("config"); // Unfortunately cannot assert on contents of config b/c it's a private + // attribute of the Pinecone class + }); + + test("Unhappy path: expect getPineconeClient to throw error if reset PINECONE_API_KEY to empty string", async () => { + // eslint-disable-next-line no-process-env + const originalApiKey = process.env.PINECONE_API_KEY; + try { + // eslint-disable-next-line no-process-env + process.env.PINECONE_API_KEY = ""; + const errorThrown = async () => { + getPineconeClient(); + }; + await expect(errorThrown).rejects.toThrow(Error); + await expect(errorThrown).rejects.toThrow( + "PINECONE_API_KEY must be set in environment" + ); + } finally { + // Restore the original value of PINECONE_API_KEY + // eslint-disable-next-line no-process-env + process.env.PINECONE_API_KEY = originalApiKey; + } + }); +}); diff --git a/libs/langchain-pinecone/src/tests/client.test.ts b/libs/langchain-pinecone/src/tests/client.test.ts new file mode 100644 index 000000000000..e25582ddbfa0 --- /dev/null +++ b/libs/langchain-pinecone/src/tests/client.test.ts @@ -0,0 +1,43 @@ +import { Pinecone, PineconeConfiguration } from "@pinecone-database/pinecone"; +import { jest } from "@jest/globals"; +import { getPineconeClient } from "../client.js"; + +// Mock the Pinecone class +jest.mock("@pinecone-database/pinecone", () => ({ + Pinecone: jest.fn().mockImplementation((config) => ({ + config, + inference: { embed: jest.fn() }, + })), +})); + +describe("Tests for getPineconeClient", () => { + test("Confirm getPineconeClient throws error when PINECONE_API_KEY is not set", async () => { + /* eslint-disable-next-line no-process-env */ + process.env.PINECONE_API_KEY = ""; + const errorThrown = async () => { + getPineconeClient(); + }; + await expect(errorThrown).rejects.toThrow(Error); + await expect(errorThrown).rejects.toThrow( + "PINECONE_API_KEY must be set in environment" + ); + }); + + test("Confirm getPineconeClient calls Pinecone class (mocked) with and without config", async () => { + /* eslint-disable-next-line no-process-env */ + process.env.PINECONE_API_KEY = "some-valid-api-key"; + + // With config + // Note: cannot assert on config contents themselves b/c `config` is a private attribute of the Pinecone class + const config: PineconeConfiguration = { + apiKey: "some-valid-api-key", + additionalHeaders: { header: "value" }, + }; + getPineconeClient(config); + expect(Pinecone).toHaveBeenCalledWith(config); + + // Without config + getPineconeClient(); + expect(Pinecone).toHaveBeenCalledWith(); + }); +}); diff --git a/libs/langchain-pinecone/src/tests/translator.int.test.ts b/libs/langchain-pinecone/src/tests/translator.int.test.ts index dc12b025ea83..ad720ae7cfa9 100644 --- a/libs/langchain-pinecone/src/tests/translator.int.test.ts +++ b/libs/langchain-pinecone/src/tests/translator.int.test.ts @@ -114,9 +114,7 @@ describe("Pinecone self query", () => { // !process.env.PINECONE_ENVIRONMENT || !testIndexName ) { - throw new Error( - "PINECONE_API_KEY and PINECONE_INDEX must be set" - ); + throw new Error("PINECONE_API_KEY and PINECONE_INDEX must be set"); } const embeddings = new OpenAIEmbeddings(); @@ -273,9 +271,7 @@ describe("Pinecone self query", () => { // !process.env.PINECONE_ENVIRONMENT || !testIndexName ) { - throw new Error( - "PINECONE_API_KEY and PINECONE_INDEX must be set" - ); + throw new Error("PINECONE_API_KEY and PINECONE_INDEX must be set"); } const embeddings = new OpenAIEmbeddings();