Add Together embedder to TS SDK (#5989)
Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import { OpenAIEmbedder } from "./openai";
|
||||
import { EmbeddingConfig } from "../types";
|
||||
|
||||
const DEFAULT_BASE_URL = "https://api.together.ai/v1";
|
||||
const DEFAULT_MODEL = "intfloat/multilingual-e5-large-instruct";
|
||||
|
||||
export class TogetherEmbedder extends OpenAIEmbedder {
|
||||
constructor(config: EmbeddingConfig) {
|
||||
const openAICompatibleConfig = { ...config };
|
||||
delete openAICompatibleConfig.embeddingDims;
|
||||
|
||||
const apiKey = config.apiKey || process.env.TOGETHER_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error("Together API key is required");
|
||||
}
|
||||
|
||||
super({
|
||||
...openAICompatibleConfig,
|
||||
apiKey,
|
||||
baseURL: config.baseURL || config.url || DEFAULT_BASE_URL,
|
||||
model: config.model || DEFAULT_MODEL,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export * from "./embeddings/base";
|
||||
export * from "./embeddings/openai";
|
||||
export * from "./embeddings/ollama";
|
||||
export * from "./embeddings/lmstudio";
|
||||
export * from "./embeddings/together";
|
||||
export * from "./embeddings/google";
|
||||
export * from "./embeddings/azure";
|
||||
export * from "./embeddings/langchain";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { OpenAIEmbedder } from "../embeddings/openai";
|
||||
import { OllamaEmbedder } from "../embeddings/ollama";
|
||||
import { LMStudioEmbedder } from "../embeddings/lmstudio";
|
||||
import { TogetherEmbedder } from "../embeddings/together";
|
||||
import { OpenAILLM } from "../llms/openai";
|
||||
import { OpenAIStructuredLLM } from "../llms/openai_structured";
|
||||
import { AnthropicLLM } from "../llms/anthropic";
|
||||
@@ -51,6 +52,8 @@ export class EmbedderFactory {
|
||||
return new OllamaEmbedder(config);
|
||||
case "lmstudio":
|
||||
return new LMStudioEmbedder(config);
|
||||
case "together":
|
||||
return new TogetherEmbedder(config);
|
||||
case "google":
|
||||
case "gemini":
|
||||
return new GoogleEmbedder(config);
|
||||
|
||||
@@ -35,6 +35,11 @@ jest.mock("../src/embeddings/lmstudio", () => ({
|
||||
.fn()
|
||||
.mockImplementation((config) => ({ type: "lmstudio-embedder", config })),
|
||||
}));
|
||||
jest.mock("../src/embeddings/together", () => ({
|
||||
TogetherEmbedder: jest
|
||||
.fn()
|
||||
.mockImplementation((config) => ({ type: "together-embedder", config })),
|
||||
}));
|
||||
|
||||
jest.mock("../src/llms/openai", () => ({
|
||||
OpenAILLM: jest
|
||||
@@ -190,6 +195,7 @@ describe("EmbedderFactory", () => {
|
||||
["azure_openai"],
|
||||
["langchain"],
|
||||
["lmstudio"],
|
||||
["together"],
|
||||
])("creates embedder for provider '%s'", (provider) => {
|
||||
expect(() =>
|
||||
EmbedderFactory.create(provider, dummyEmbedConfig),
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/// <reference types="jest" />
|
||||
|
||||
const mockEmbeddingsCreate = jest.fn();
|
||||
const mockOpenAI = jest.fn().mockImplementation(() => ({
|
||||
embeddings: { create: mockEmbeddingsCreate },
|
||||
}));
|
||||
|
||||
jest.mock("openai", () => ({
|
||||
__esModule: true,
|
||||
default: mockOpenAI,
|
||||
}));
|
||||
|
||||
import { TogetherEmbedder } from "../src/embeddings/together";
|
||||
|
||||
const mockEmbedding = [0.1, 0.2, 0.3];
|
||||
const originalEnv = process.env;
|
||||
|
||||
describe("TogetherEmbedder (unit)", () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
process.env = { ...originalEnv };
|
||||
delete process.env.TOGETHER_API_KEY;
|
||||
mockOpenAI.mockClear();
|
||||
mockEmbeddingsCreate.mockReset();
|
||||
mockEmbeddingsCreate.mockResolvedValue({
|
||||
data: [{ index: 0, embedding: mockEmbedding }],
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
it("uses Together defaults with an API key from config", async () => {
|
||||
const embedder = new TogetherEmbedder({ apiKey: "test-key" });
|
||||
|
||||
await embedder.embed("hello");
|
||||
|
||||
expect(mockOpenAI).toHaveBeenCalledWith({
|
||||
apiKey: "test-key",
|
||||
baseURL: "https://api.together.ai/v1",
|
||||
});
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
model: "intfloat/multilingual-e5-large-instruct",
|
||||
input: "hello",
|
||||
encoding_format: "float",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses TOGETHER_API_KEY when config apiKey is not provided", async () => {
|
||||
process.env.TOGETHER_API_KEY = "env-key";
|
||||
|
||||
const embedder = new TogetherEmbedder({});
|
||||
|
||||
await embedder.embed("hello");
|
||||
|
||||
expect(mockOpenAI).toHaveBeenCalledWith({
|
||||
apiKey: "env-key",
|
||||
baseURL: "https://api.together.ai/v1",
|
||||
});
|
||||
});
|
||||
|
||||
it("supports custom model and baseURL without forwarding embeddingDims", async () => {
|
||||
const embedder = new TogetherEmbedder({
|
||||
apiKey: "test-key",
|
||||
model: "custom-together-embed",
|
||||
baseURL: "https://proxy.example.com/v1",
|
||||
embeddingDims: 512,
|
||||
});
|
||||
|
||||
await embedder.embed("hello");
|
||||
|
||||
expect(mockOpenAI).toHaveBeenCalledWith({
|
||||
apiKey: "test-key",
|
||||
baseURL: "https://proxy.example.com/v1",
|
||||
});
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
model: "custom-together-embed",
|
||||
input: "hello",
|
||||
encoding_format: "float",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses url as a baseURL fallback", async () => {
|
||||
const embedder = new TogetherEmbedder({
|
||||
apiKey: "test-key",
|
||||
url: "https://url-fallback.example.com/v1",
|
||||
});
|
||||
|
||||
await embedder.embed("hello");
|
||||
|
||||
expect(mockOpenAI).toHaveBeenCalledWith({
|
||||
apiKey: "test-key",
|
||||
baseURL: "https://url-fallback.example.com/v1",
|
||||
});
|
||||
});
|
||||
|
||||
it("sorts batch embeddings by response index", async () => {
|
||||
mockEmbeddingsCreate.mockResolvedValueOnce({
|
||||
data: [
|
||||
{ index: 1, embedding: [0.3, 0.4] },
|
||||
{ index: 0, embedding: [0.1, 0.2] },
|
||||
],
|
||||
});
|
||||
|
||||
const embedder = new TogetherEmbedder({ apiKey: "test-key" });
|
||||
|
||||
await expect(embedder.embedBatch(["first", "second"])).resolves.toEqual([
|
||||
[0.1, 0.2],
|
||||
[0.3, 0.4],
|
||||
]);
|
||||
});
|
||||
|
||||
it("throws when no API key is available", () => {
|
||||
expect(() => new TogetherEmbedder({})).toThrow(
|
||||
"Together API key is required",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user