fix: pass dimensions parameter to OpenAI embeddings API (#4632)
Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
co-authored by
kartik-mem0
parent
f94ea06588
commit
8ae7a06220
@@ -5,7 +5,7 @@ import { EmbeddingConfig } from "../types";
|
|||||||
export class AzureOpenAIEmbedder implements Embedder {
|
export class AzureOpenAIEmbedder implements Embedder {
|
||||||
private client: AzureOpenAI;
|
private client: AzureOpenAI;
|
||||||
private model: string;
|
private model: string;
|
||||||
private embeddingDims?: number;
|
private embeddingDims: number | undefined;
|
||||||
|
|
||||||
constructor(config: EmbeddingConfig) {
|
constructor(config: EmbeddingConfig) {
|
||||||
if (!config.apiKey || !config.modelProperties?.endpoint) {
|
if (!config.apiKey || !config.modelProperties?.endpoint) {
|
||||||
@@ -20,13 +20,16 @@ export class AzureOpenAIEmbedder implements Embedder {
|
|||||||
...rest,
|
...rest,
|
||||||
});
|
});
|
||||||
this.model = config.model || "text-embedding-3-small";
|
this.model = config.model || "text-embedding-3-small";
|
||||||
this.embeddingDims = config.embeddingDims || 1536;
|
this.embeddingDims = config.embeddingDims;
|
||||||
}
|
}
|
||||||
|
|
||||||
async embed(text: string): Promise<number[]> {
|
async embed(text: string): Promise<number[]> {
|
||||||
const response = await this.client.embeddings.create({
|
const response = await this.client.embeddings.create({
|
||||||
model: this.model,
|
model: this.model,
|
||||||
input: text,
|
input: text,
|
||||||
|
...(this.embeddingDims !== undefined && {
|
||||||
|
dimensions: this.embeddingDims,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
return response.data[0].embedding;
|
return response.data[0].embedding;
|
||||||
}
|
}
|
||||||
@@ -35,6 +38,9 @@ export class AzureOpenAIEmbedder implements Embedder {
|
|||||||
const response = await this.client.embeddings.create({
|
const response = await this.client.embeddings.create({
|
||||||
model: this.model,
|
model: this.model,
|
||||||
input: texts,
|
input: texts,
|
||||||
|
...(this.embeddingDims !== undefined && {
|
||||||
|
dimensions: this.embeddingDims,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
return response.data.map((item) => item.embedding);
|
return response.data.map((item) => item.embedding);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,21 +5,23 @@ import { EmbeddingConfig } from "../types";
|
|||||||
export class GoogleEmbedder implements Embedder {
|
export class GoogleEmbedder implements Embedder {
|
||||||
private google: GoogleGenAI;
|
private google: GoogleGenAI;
|
||||||
private model: string;
|
private model: string;
|
||||||
private embeddingDims?: number;
|
private embeddingDims: number | undefined;
|
||||||
|
|
||||||
constructor(config: EmbeddingConfig) {
|
constructor(config: EmbeddingConfig) {
|
||||||
this.google = new GoogleGenAI({
|
this.google = new GoogleGenAI({
|
||||||
apiKey: config.apiKey || process.env.GOOGLE_API_KEY,
|
apiKey: config.apiKey || process.env.GOOGLE_API_KEY,
|
||||||
});
|
});
|
||||||
this.model = config.model || "gemini-embedding-001";
|
this.model = config.model || "gemini-embedding-001";
|
||||||
this.embeddingDims = config.embeddingDims || 1536;
|
this.embeddingDims = config.embeddingDims;
|
||||||
}
|
}
|
||||||
|
|
||||||
async embed(text: string): Promise<number[]> {
|
async embed(text: string): Promise<number[]> {
|
||||||
const response = await this.google.models.embedContent({
|
const response = await this.google.models.embedContent({
|
||||||
model: this.model,
|
model: this.model,
|
||||||
contents: text,
|
contents: text,
|
||||||
config: { outputDimensionality: this.embeddingDims },
|
...(this.embeddingDims !== undefined && {
|
||||||
|
config: { outputDimensionality: this.embeddingDims },
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
return response.embeddings![0].values!;
|
return response.embeddings![0].values!;
|
||||||
}
|
}
|
||||||
@@ -28,7 +30,9 @@ export class GoogleEmbedder implements Embedder {
|
|||||||
const response = await this.google.models.embedContent({
|
const response = await this.google.models.embedContent({
|
||||||
model: this.model,
|
model: this.model,
|
||||||
contents: texts,
|
contents: texts,
|
||||||
config: { outputDimensionality: this.embeddingDims },
|
...(this.embeddingDims !== undefined && {
|
||||||
|
config: { outputDimensionality: this.embeddingDims },
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
return response.embeddings!.map((item) => item.values!);
|
return response.embeddings!.map((item) => item.values!);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { EmbeddingConfig } from "../types";
|
|||||||
export class OpenAIEmbedder implements Embedder {
|
export class OpenAIEmbedder implements Embedder {
|
||||||
private openai: OpenAI;
|
private openai: OpenAI;
|
||||||
private model: string;
|
private model: string;
|
||||||
private embeddingDims?: number;
|
private embeddingDims: number | undefined;
|
||||||
|
|
||||||
constructor(config: EmbeddingConfig) {
|
constructor(config: EmbeddingConfig) {
|
||||||
this.openai = new OpenAI({
|
this.openai = new OpenAI({
|
||||||
@@ -13,13 +13,16 @@ export class OpenAIEmbedder implements Embedder {
|
|||||||
baseURL: config.baseURL || config.url,
|
baseURL: config.baseURL || config.url,
|
||||||
});
|
});
|
||||||
this.model = config.model || "text-embedding-3-small";
|
this.model = config.model || "text-embedding-3-small";
|
||||||
this.embeddingDims = config.embeddingDims || 1536;
|
this.embeddingDims = config.embeddingDims;
|
||||||
}
|
}
|
||||||
|
|
||||||
async embed(text: string): Promise<number[]> {
|
async embed(text: string): Promise<number[]> {
|
||||||
const response = await this.openai.embeddings.create({
|
const response = await this.openai.embeddings.create({
|
||||||
model: this.model,
|
model: this.model,
|
||||||
input: text,
|
input: text,
|
||||||
|
...(this.embeddingDims !== undefined && {
|
||||||
|
dimensions: this.embeddingDims,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
return response.data[0].embedding;
|
return response.data[0].embedding;
|
||||||
}
|
}
|
||||||
@@ -28,6 +31,9 @@ export class OpenAIEmbedder implements Embedder {
|
|||||||
const response = await this.openai.embeddings.create({
|
const response = await this.openai.embeddings.create({
|
||||||
model: this.model,
|
model: this.model,
|
||||||
input: texts,
|
input: texts,
|
||||||
|
...(this.embeddingDims !== undefined && {
|
||||||
|
dimensions: this.embeddingDims,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
return response.data.map((item) => item.embedding);
|
return response.data.map((item) => item.embedding);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
/// <reference types="jest" />
|
||||||
|
/**
|
||||||
|
* Azure OpenAI Embedder — unit tests (mocked Azure OpenAI client).
|
||||||
|
* Verifies that the `dimensions` parameter is only passed to the API
|
||||||
|
* when the user explicitly configures `embeddingDims`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const mockEmbeddingsCreate = jest.fn();
|
||||||
|
|
||||||
|
jest.mock("openai", () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
AzureOpenAI: jest.fn().mockImplementation(() => ({
|
||||||
|
embeddings: { create: mockEmbeddingsCreate },
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import { AzureOpenAIEmbedder } from "../src/embeddings/azure";
|
||||||
|
|
||||||
|
const mockEmbedding = [0.1, 0.2, 0.3, 0.4, 0.5];
|
||||||
|
|
||||||
|
const baseConfig = {
|
||||||
|
apiKey: "test-key",
|
||||||
|
modelProperties: { endpoint: "https://test.openai.azure.com" },
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("AzureOpenAIEmbedder (unit)", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockEmbeddingsCreate.mockReset();
|
||||||
|
mockEmbeddingsCreate.mockResolvedValue({
|
||||||
|
data: [{ embedding: mockEmbedding }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("dimensions parameter", () => {
|
||||||
|
it("does NOT pass dimensions when embeddingDims is not set", async () => {
|
||||||
|
const embedder = new AzureOpenAIEmbedder(baseConfig);
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
expect(mockEmbeddingsCreate).toHaveBeenCalledTimes(1);
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs).not.toHaveProperty("dimensions");
|
||||||
|
expect(callArgs).toEqual({
|
||||||
|
model: "text-embedding-3-small",
|
||||||
|
input: "hello",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes dimensions when embeddingDims is explicitly set", async () => {
|
||||||
|
const embedder = new AzureOpenAIEmbedder({
|
||||||
|
...baseConfig,
|
||||||
|
embeddingDims: 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
expect(mockEmbeddingsCreate).toHaveBeenCalledTimes(1);
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs).toEqual({
|
||||||
|
model: "text-embedding-3-small",
|
||||||
|
input: "hello",
|
||||||
|
dimensions: 1024,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes dimensions=1536 when embeddingDims is explicitly set to 1536", async () => {
|
||||||
|
const embedder = new AzureOpenAIEmbedder({
|
||||||
|
...baseConfig,
|
||||||
|
embeddingDims: 1536,
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs).toHaveProperty("dimensions", 1536);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does NOT pass dimensions in embedBatch when embeddingDims is not set", async () => {
|
||||||
|
mockEmbeddingsCreate.mockResolvedValue({
|
||||||
|
data: [{ embedding: mockEmbedding }, { embedding: mockEmbedding }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const embedder = new AzureOpenAIEmbedder(baseConfig);
|
||||||
|
|
||||||
|
await embedder.embedBatch(["hello", "world"]);
|
||||||
|
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs).not.toHaveProperty("dimensions");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes dimensions in embedBatch when embeddingDims is explicitly set", async () => {
|
||||||
|
mockEmbeddingsCreate.mockResolvedValue({
|
||||||
|
data: [{ embedding: mockEmbedding }, { embedding: mockEmbedding }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const embedder = new AzureOpenAIEmbedder({
|
||||||
|
...baseConfig,
|
||||||
|
embeddingDims: 512,
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embedBatch(["hello", "world"]);
|
||||||
|
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs).toEqual({
|
||||||
|
model: "text-embedding-3-small",
|
||||||
|
input: ["hello", "world"],
|
||||||
|
dimensions: 512,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("basic functionality", () => {
|
||||||
|
it("embed() returns the embedding vector", async () => {
|
||||||
|
const embedder = new AzureOpenAIEmbedder(baseConfig);
|
||||||
|
|
||||||
|
const result = await embedder.embed("hello");
|
||||||
|
expect(result).toEqual(mockEmbedding);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("embedBatch() returns vectors for multiple inputs", async () => {
|
||||||
|
const batch = [
|
||||||
|
[0.1, 0.2],
|
||||||
|
[0.3, 0.4],
|
||||||
|
];
|
||||||
|
mockEmbeddingsCreate.mockResolvedValue({
|
||||||
|
data: batch.map((embedding) => ({ embedding })),
|
||||||
|
});
|
||||||
|
|
||||||
|
const embedder = new AzureOpenAIEmbedder(baseConfig);
|
||||||
|
|
||||||
|
const result = await embedder.embedBatch(["text1", "text2"]);
|
||||||
|
expect(result).toEqual(batch);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses custom model when provided", async () => {
|
||||||
|
const embedder = new AzureOpenAIEmbedder({
|
||||||
|
...baseConfig,
|
||||||
|
model: "text-embedding-3-large",
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs.model).toBe("text-embedding-3-large");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when API key is missing", () => {
|
||||||
|
expect(() => {
|
||||||
|
new AzureOpenAIEmbedder({
|
||||||
|
modelProperties: { endpoint: "https://test.openai.azure.com" },
|
||||||
|
});
|
||||||
|
}).toThrow("Azure OpenAI requires both API key and endpoint");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when endpoint is missing", () => {
|
||||||
|
expect(() => {
|
||||||
|
new AzureOpenAIEmbedder({ apiKey: "test-key" });
|
||||||
|
}).toThrow("Azure OpenAI requires both API key and endpoint");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
/// <reference types="jest" />
|
||||||
|
/**
|
||||||
|
* Google Embedder — unit tests (mocked Google GenAI client).
|
||||||
|
* Verifies that the `outputDimensionality` config is only passed to the API
|
||||||
|
* when the user explicitly configures `embeddingDims`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const mockEmbedContent = jest.fn();
|
||||||
|
|
||||||
|
jest.mock("@google/genai", () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
GoogleGenAI: jest.fn().mockImplementation(() => ({
|
||||||
|
models: { embedContent: mockEmbedContent },
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import { GoogleEmbedder } from "../src/embeddings/google";
|
||||||
|
|
||||||
|
const mockEmbedding = [0.1, 0.2, 0.3, 0.4, 0.5];
|
||||||
|
|
||||||
|
describe("GoogleEmbedder (unit)", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockEmbedContent.mockReset();
|
||||||
|
mockEmbedContent.mockResolvedValue({
|
||||||
|
embeddings: [{ values: mockEmbedding }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("outputDimensionality parameter", () => {
|
||||||
|
it("does NOT pass config when embeddingDims is not set", async () => {
|
||||||
|
const embedder = new GoogleEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
expect(mockEmbedContent).toHaveBeenCalledTimes(1);
|
||||||
|
const callArgs = mockEmbedContent.mock.calls[0][0];
|
||||||
|
expect(callArgs).not.toHaveProperty("config");
|
||||||
|
expect(callArgs).toEqual({
|
||||||
|
model: "gemini-embedding-001",
|
||||||
|
contents: "hello",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes outputDimensionality when embeddingDims is explicitly set", async () => {
|
||||||
|
const embedder = new GoogleEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
embeddingDims: 768,
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
expect(mockEmbedContent).toHaveBeenCalledTimes(1);
|
||||||
|
const callArgs = mockEmbedContent.mock.calls[0][0];
|
||||||
|
expect(callArgs).toEqual({
|
||||||
|
model: "gemini-embedding-001",
|
||||||
|
contents: "hello",
|
||||||
|
config: { outputDimensionality: 768 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes outputDimensionality=1536 when embeddingDims is explicitly set to 1536", async () => {
|
||||||
|
const embedder = new GoogleEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
embeddingDims: 1536,
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
const callArgs = mockEmbedContent.mock.calls[0][0];
|
||||||
|
expect(callArgs).toHaveProperty("config");
|
||||||
|
expect(callArgs.config).toEqual({ outputDimensionality: 1536 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does NOT pass config in embedBatch when embeddingDims is not set", async () => {
|
||||||
|
mockEmbedContent.mockResolvedValue({
|
||||||
|
embeddings: [{ values: mockEmbedding }, { values: mockEmbedding }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const embedder = new GoogleEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embedBatch(["hello", "world"]);
|
||||||
|
|
||||||
|
const callArgs = mockEmbedContent.mock.calls[0][0];
|
||||||
|
expect(callArgs).not.toHaveProperty("config");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes outputDimensionality in embedBatch when embeddingDims is explicitly set", async () => {
|
||||||
|
mockEmbedContent.mockResolvedValue({
|
||||||
|
embeddings: [{ values: mockEmbedding }, { values: mockEmbedding }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const embedder = new GoogleEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
embeddingDims: 512,
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embedBatch(["hello", "world"]);
|
||||||
|
|
||||||
|
const callArgs = mockEmbedContent.mock.calls[0][0];
|
||||||
|
expect(callArgs).toEqual({
|
||||||
|
model: "gemini-embedding-001",
|
||||||
|
contents: ["hello", "world"],
|
||||||
|
config: { outputDimensionality: 512 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("basic functionality", () => {
|
||||||
|
it("embed() returns the embedding vector", async () => {
|
||||||
|
const embedder = new GoogleEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await embedder.embed("hello");
|
||||||
|
expect(result).toEqual(mockEmbedding);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("embedBatch() returns vectors for multiple inputs", async () => {
|
||||||
|
const batch = [
|
||||||
|
[0.1, 0.2],
|
||||||
|
[0.3, 0.4],
|
||||||
|
];
|
||||||
|
mockEmbedContent.mockResolvedValue({
|
||||||
|
embeddings: batch.map((values) => ({ values })),
|
||||||
|
});
|
||||||
|
|
||||||
|
const embedder = new GoogleEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await embedder.embedBatch(["text1", "text2"]);
|
||||||
|
expect(result).toEqual(batch);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses custom model when provided", async () => {
|
||||||
|
const embedder = new GoogleEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
model: "text-embedding-004",
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
const callArgs = mockEmbedContent.mock.calls[0][0];
|
||||||
|
expect(callArgs.model).toBe("text-embedding-004");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/// <reference types="jest" />
|
||||||
|
/**
|
||||||
|
* OpenAI Embedder — unit tests (mocked OpenAI client).
|
||||||
|
* Verifies that the `dimensions` parameter is only passed to the API
|
||||||
|
* when the user explicitly configures `embeddingDims`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const mockEmbeddingsCreate = jest.fn();
|
||||||
|
|
||||||
|
jest.mock("openai", () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn().mockImplementation(() => ({
|
||||||
|
embeddings: { create: mockEmbeddingsCreate },
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import { OpenAIEmbedder } from "../src/embeddings/openai";
|
||||||
|
|
||||||
|
const mockEmbedding = [0.1, 0.2, 0.3, 0.4, 0.5];
|
||||||
|
|
||||||
|
describe("OpenAIEmbedder (unit)", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockEmbeddingsCreate.mockReset();
|
||||||
|
mockEmbeddingsCreate.mockResolvedValue({
|
||||||
|
data: [{ embedding: mockEmbedding }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("dimensions parameter", () => {
|
||||||
|
it("does NOT pass dimensions when embeddingDims is not set", async () => {
|
||||||
|
const embedder = new OpenAIEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
expect(mockEmbeddingsCreate).toHaveBeenCalledTimes(1);
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs).not.toHaveProperty("dimensions");
|
||||||
|
expect(callArgs).toEqual({
|
||||||
|
model: "text-embedding-3-small",
|
||||||
|
input: "hello",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes dimensions when embeddingDims is explicitly set", async () => {
|
||||||
|
const embedder = new OpenAIEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
embeddingDims: 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
expect(mockEmbeddingsCreate).toHaveBeenCalledTimes(1);
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs).toEqual({
|
||||||
|
model: "text-embedding-3-small",
|
||||||
|
input: "hello",
|
||||||
|
dimensions: 1024,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes dimensions=1536 when embeddingDims is explicitly set to 1536", async () => {
|
||||||
|
const embedder = new OpenAIEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
embeddingDims: 1536,
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs).toHaveProperty("dimensions", 1536);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does NOT pass dimensions in embedBatch when embeddingDims is not set", async () => {
|
||||||
|
mockEmbeddingsCreate.mockResolvedValue({
|
||||||
|
data: [{ embedding: mockEmbedding }, { embedding: mockEmbedding }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const embedder = new OpenAIEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embedBatch(["hello", "world"]);
|
||||||
|
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs).not.toHaveProperty("dimensions");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes dimensions in embedBatch when embeddingDims is explicitly set", async () => {
|
||||||
|
mockEmbeddingsCreate.mockResolvedValue({
|
||||||
|
data: [{ embedding: mockEmbedding }, { embedding: mockEmbedding }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const embedder = new OpenAIEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
embeddingDims: 512,
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embedBatch(["hello", "world"]);
|
||||||
|
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs).toEqual({
|
||||||
|
model: "text-embedding-3-small",
|
||||||
|
input: ["hello", "world"],
|
||||||
|
dimensions: 512,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("basic functionality", () => {
|
||||||
|
it("embed() returns the embedding vector", async () => {
|
||||||
|
const embedder = new OpenAIEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await embedder.embed("hello");
|
||||||
|
expect(result).toEqual(mockEmbedding);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("embedBatch() returns vectors for multiple inputs", async () => {
|
||||||
|
const batch = [
|
||||||
|
[0.1, 0.2],
|
||||||
|
[0.3, 0.4],
|
||||||
|
];
|
||||||
|
mockEmbeddingsCreate.mockResolvedValue({
|
||||||
|
data: batch.map((embedding) => ({ embedding })),
|
||||||
|
});
|
||||||
|
|
||||||
|
const embedder = new OpenAIEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await embedder.embedBatch(["text1", "text2"]);
|
||||||
|
expect(result).toEqual(batch);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses custom model when provided", async () => {
|
||||||
|
const embedder = new OpenAIEmbedder({
|
||||||
|
apiKey: "test-key",
|
||||||
|
model: "text-embedding-3-large",
|
||||||
|
});
|
||||||
|
|
||||||
|
await embedder.embed("hello");
|
||||||
|
|
||||||
|
const callArgs = mockEmbeddingsCreate.mock.calls[0][0];
|
||||||
|
expect(callArgs.model).toBe("text-embedding-3-large");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user