refactor: consolidate agent/editor plugins under integrations/ (#5491)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { generateText, streamText } from "ai";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { simulateStreamingMiddleware, wrapLanguageModel } from 'ai';
|
||||
import { addMemories } from "../src";
|
||||
import { testConfig } from "../config/test-config";
|
||||
|
||||
interface Provider {
|
||||
name: string;
|
||||
activeModel: string;
|
||||
apiKey: string | undefined;
|
||||
}
|
||||
|
||||
describe.each(testConfig.providers)('TESTS: Generate/Stream Text with model %s', (provider: Provider) => {
|
||||
const { userId } = testConfig;
|
||||
let mem0: ReturnType<typeof testConfig.createTestClient>;
|
||||
jest.setTimeout(50000);
|
||||
|
||||
beforeEach(() => {
|
||||
mem0 = testConfig.createTestClient(provider);
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
// Add some test memories before all tests
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "I love red cars." },
|
||||
{ type: "text", text: "I like Toyota Cars." },
|
||||
{ type: "text", text: "I prefer SUVs." },
|
||||
],
|
||||
}
|
||||
];
|
||||
await addMemories(messages, { user_id: userId });
|
||||
});
|
||||
|
||||
it("should generate text using mem0 model", async () => {
|
||||
const { text } = await generateText({
|
||||
model: mem0(provider.activeModel, {
|
||||
user_id: userId,
|
||||
}),
|
||||
prompt: "Suggest me a good car to buy!",
|
||||
});
|
||||
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate text using provider with memories", async () => {
|
||||
const { text } = await generateText({
|
||||
model: mem0(provider.activeModel, {
|
||||
user_id: userId,
|
||||
}),
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: "Write only the car name and it's color." },
|
||||
]
|
||||
}
|
||||
],
|
||||
});
|
||||
// Expect text to be a string
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should stream text using Mem0 provider with new streaming approach", async () => {
|
||||
// Create the base model
|
||||
const baseModel = mem0(provider.activeModel, {
|
||||
user_id: userId,
|
||||
});
|
||||
|
||||
// Wrap with streaming middleware using the new Vercel AI SDK 5.0 approach
|
||||
const model = wrapLanguageModel({
|
||||
model: baseModel,
|
||||
middleware: simulateStreamingMiddleware(),
|
||||
});
|
||||
|
||||
const { textStream } = streamText({
|
||||
model,
|
||||
prompt: "Suggest me a good car to buy! Write only the car name and it's color.",
|
||||
});
|
||||
|
||||
// Collect streamed text parts
|
||||
let streamedText = '';
|
||||
for await (const textPart of textStream) {
|
||||
streamedText += textPart;
|
||||
}
|
||||
|
||||
// Ensure the streamed text is a string
|
||||
expect(typeof streamedText).toBe('string');
|
||||
expect(streamedText.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { createMem0, retrieveMemories } from "../../src";
|
||||
import { generateText } from "ai";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { testConfig } from "../../config/test-config";
|
||||
import { createCohere } from "@ai-sdk/cohere";
|
||||
|
||||
describe("COHERE MEM0 Tests", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(30000);
|
||||
let mem0: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mem0 = createMem0({
|
||||
provider: "cohere",
|
||||
apiKey: process.env.COHERE_API_KEY,
|
||||
mem0Config: {
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve memories and generate text using COHERE provider", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: " Write only the car name and it's color." },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: mem0("command-r-plus"),
|
||||
messages: messages
|
||||
});
|
||||
|
||||
// Expect text to be a string
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate text using COHERE provider with memories", async () => {
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: mem0("command-r-plus"),
|
||||
prompt: prompt
|
||||
});
|
||||
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { createMem0 } from "../../src";
|
||||
import { generateText } from "ai";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { testConfig } from "../../config/test-config";
|
||||
|
||||
describe("GOOGLE MEM0 Tests", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(50000);
|
||||
|
||||
let mem0: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mem0 = createMem0({
|
||||
provider: "google",
|
||||
apiKey: process.env.GOOGLE_API_KEY,
|
||||
mem0Config: {
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve memories and generate text using Google provider", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: " Write only the car name and it's color." },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: mem0("gemini-1.5-flash"),
|
||||
messages: messages
|
||||
});
|
||||
|
||||
// Expect text to be a string
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate text using Google provider with memories", async () => {
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: mem0("gemini-1.5-flash"),
|
||||
prompt: prompt
|
||||
});
|
||||
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { createMem0, retrieveMemories } from "../../src";
|
||||
import { generateText } from "ai";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { testConfig } from "../../config/test-config";
|
||||
import { createGroq } from "@ai-sdk/groq";
|
||||
|
||||
describe("GROQ MEM0 Tests", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(30000);
|
||||
|
||||
let mem0: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mem0 = createMem0({
|
||||
provider: "groq",
|
||||
apiKey: process.env.GROQ_API_KEY,
|
||||
mem0Config: {
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve memories and generate text using GROQ provider", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: " Write only the car name and it's color." },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: mem0("llama3-8b-8192"),
|
||||
messages: messages
|
||||
});
|
||||
|
||||
// Expect text to be a string
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate text using GROQ provider with memories", async () => {
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: mem0("llama3-8b-8192"),
|
||||
prompt: prompt
|
||||
});
|
||||
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { generateObject } from "ai";
|
||||
import { testConfig } from "../../config/test-config";
|
||||
import { z } from "zod";
|
||||
|
||||
interface Provider {
|
||||
name: string;
|
||||
activeModel: string;
|
||||
apiKey: string | undefined;
|
||||
}
|
||||
|
||||
const provider: Provider = {
|
||||
name: "openai",
|
||||
activeModel: "gpt-4o-mini",
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
}
|
||||
describe("OPENAI Structured Outputs", () => {
|
||||
const { userId } = testConfig;
|
||||
let mem0: ReturnType<typeof testConfig.createTestClient>;
|
||||
jest.setTimeout(30000);
|
||||
|
||||
beforeEach(() => {
|
||||
mem0 = testConfig.createTestClient(provider);
|
||||
});
|
||||
|
||||
describe("openai Object Generation Tests", () => {
|
||||
// Test 1: Generate a car preference object
|
||||
it("should generate a car preference object with name and steps", async () => {
|
||||
const { object } = await generateObject({
|
||||
model: mem0(provider.activeModel, {
|
||||
user_id: userId,
|
||||
}),
|
||||
schema: z.object({
|
||||
car: z.object({
|
||||
name: z.string(),
|
||||
steps: z.array(z.string()),
|
||||
}),
|
||||
}),
|
||||
prompt: "Which car would I like?",
|
||||
});
|
||||
|
||||
expect(object.car).toBeDefined();
|
||||
expect(typeof object.car.name).toBe("string");
|
||||
expect(Array.isArray(object.car.steps)).toBe(true);
|
||||
expect(object.car.steps.every((step) => typeof step === "string")).toBe(true);
|
||||
});
|
||||
|
||||
// Test 2: Generate an array of car objects
|
||||
it("should generate an array of three car objects with name, class, and description", async () => {
|
||||
const { object } = await generateObject({
|
||||
model: mem0(provider.activeModel, {
|
||||
user_id: userId,
|
||||
}),
|
||||
output: "array",
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
class: z.string().describe('Cars should be "SUV", "Sedan", or "Hatchback"'),
|
||||
description: z.string(),
|
||||
}),
|
||||
prompt: "Write name of three cars that I would like.",
|
||||
});
|
||||
|
||||
expect(Array.isArray(object)).toBe(true);
|
||||
expect(object.length).toBe(3);
|
||||
object.forEach((car) => {
|
||||
expect(car).toHaveProperty("name");
|
||||
expect(typeof car.name).toBe("string");
|
||||
expect(car).toHaveProperty("class");
|
||||
expect(typeof car.class).toBe("string");
|
||||
expect(car).toHaveProperty("description");
|
||||
expect(typeof car.description).toBe("string");
|
||||
});
|
||||
});
|
||||
|
||||
// Test 3: Generate an enum for movie genre classification
|
||||
it("should classify the genre of a movie plot", async () => {
|
||||
const { object } = await generateObject({
|
||||
model: mem0(provider.activeModel, {
|
||||
user_id: userId,
|
||||
}),
|
||||
output: "enum",
|
||||
enum: ["action", "comedy", "drama", "horror", "sci-fi"],
|
||||
prompt: 'Classify the genre of this movie plot: "A group of astronauts travel through a wormhole in search of a new habitable planet for humanity."',
|
||||
});
|
||||
|
||||
expect(object).toBeDefined();
|
||||
expect(object).toBe("sci-fi");
|
||||
});
|
||||
|
||||
// Test 4: Generate an object of car names without schema
|
||||
it("should generate an object with car names", async () => {
|
||||
const { object } = await generateObject({
|
||||
model: mem0(provider.activeModel, {
|
||||
user_id: userId,
|
||||
}),
|
||||
output: "no-schema",
|
||||
prompt: "Write name of 3 cars that I would like in JSON format.",
|
||||
});
|
||||
|
||||
// The response structure might vary, so let's be more flexible
|
||||
expect(object).toBeDefined();
|
||||
expect(typeof object).toBe("object");
|
||||
|
||||
// Check if it has cars property or if it's an array
|
||||
if (object && typeof object === "object" && "cars" in object && Array.isArray((object as any).cars)) {
|
||||
const cars = (object as any).cars;
|
||||
expect(cars.length).toBe(3);
|
||||
expect(cars.every((car: any) => typeof car === "string")).toBe(true);
|
||||
} else if (object && Array.isArray(object)) {
|
||||
expect(object.length).toBe(3);
|
||||
expect(object.every((car: any) => typeof car === "string")).toBe(true);
|
||||
} else if (object && typeof object === "object") {
|
||||
// If it's a different structure, just check it's valid
|
||||
expect(Object.keys(object as object).length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { createMem0 } from "../../src";
|
||||
import { generateText } from "ai";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { testConfig } from "../../config/test-config";
|
||||
|
||||
describe("OPENAI MEM0 Tests", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(30000);
|
||||
let mem0: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mem0 = createMem0({
|
||||
provider: "openai",
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
mem0Config: {
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve memories and generate text using Mem0 OpenAI provider", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: " Write only the car name and it's color." },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const { text } = await generateText({
|
||||
model: mem0("gpt-4-turbo"),
|
||||
messages: messages
|
||||
});
|
||||
|
||||
// Expect text to be a string
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate text using openai provider with memories", async () => {
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
|
||||
const { text } = await generateText({
|
||||
model: mem0("gpt-4-turbo"),
|
||||
prompt: prompt
|
||||
});
|
||||
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { createMem0, retrieveMemories } from "../../src";
|
||||
import { generateText } from "ai";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { testConfig } from "../../config/test-config";
|
||||
import { createAnthropic } from "@ai-sdk/anthropic";
|
||||
|
||||
describe("ANTHROPIC MEM0 Tests", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(30000);
|
||||
|
||||
let mem0: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mem0 = createMem0({
|
||||
provider: "anthropic",
|
||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
||||
mem0Config: {
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve memories and generate text using ANTHROPIC provider", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: " Write only the car name and it's color." },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: mem0("claude-3-haiku-20240307"),
|
||||
messages: messages,
|
||||
});
|
||||
|
||||
// Expect text to be a string
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate text using ANTHROPIC provider with memories", async () => {
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: mem0("claude-3-haiku-20240307"),
|
||||
prompt: prompt,
|
||||
});
|
||||
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { addMemories, createMem0 } from "../src";
|
||||
import { generateText, tool } from "ai";
|
||||
import { testConfig } from "../config/test-config";
|
||||
import { z } from "zod";
|
||||
|
||||
describe("Tool Calls Tests", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(30000);
|
||||
|
||||
beforeEach(async () => {
|
||||
await addMemories([{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "I live in Mumbai" }],
|
||||
}], { user_id: userId });
|
||||
});
|
||||
|
||||
it("should Execute a Tool Call Using OpenAI", async () => {
|
||||
const mem0OpenAI = createMem0({
|
||||
provider: "openai",
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
mem0Config: {
|
||||
user_id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await generateText({
|
||||
model: mem0OpenAI("gpt-4o"),
|
||||
tools: {
|
||||
weather: tool({
|
||||
description: "Get the weather in a location",
|
||||
inputSchema: z.object({
|
||||
location: z.string().describe("The location to get the weather for"),
|
||||
}),
|
||||
execute: async ({ location }) => ({
|
||||
location,
|
||||
temperature: 72 + Math.floor(Math.random() * 21) - 10,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
prompt: "What is the temperature in the city that I live in?",
|
||||
});
|
||||
|
||||
// Check if the response is valid
|
||||
expect(result).toHaveProperty('text');
|
||||
expect(typeof result.text).toBe("string");
|
||||
|
||||
// For tool calls, we should have either text response or tool call results
|
||||
if (result.text && result.text.length > 0) {
|
||||
expect(result.text.length).toBeGreaterThan(0);
|
||||
// Check if the response mentions weather or temperature
|
||||
expect(result.text.toLowerCase()).toMatch(/(weather|temperature|mumbai)/);
|
||||
} else {
|
||||
// If text is empty, check if there are tool call results
|
||||
expect(result).toHaveProperty('toolResults');
|
||||
expect(Array.isArray(result.toolResults)).toBe(true);
|
||||
expect(result.toolResults.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it("should Execute a Tool Call Using Anthropic", async () => {
|
||||
const mem0Anthropic = createMem0({
|
||||
provider: "anthropic",
|
||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
||||
mem0Config: {
|
||||
user_id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await generateText({
|
||||
model: mem0Anthropic("claude-3-haiku-20240307"),
|
||||
tools: {
|
||||
weather: tool({
|
||||
description: "Get the weather in a location",
|
||||
inputSchema: z.object({
|
||||
location: z.string().describe("The location to get the weather for"),
|
||||
}),
|
||||
execute: async ({ location }) => ({
|
||||
location,
|
||||
temperature: 72 + Math.floor(Math.random() * 21) - 10,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
prompt: "What is the temperature in the city that I live in?",
|
||||
});
|
||||
|
||||
// Check if the response is valid
|
||||
expect(result).toHaveProperty('text');
|
||||
expect(typeof result.text).toBe("string");
|
||||
|
||||
if (result.text && result.text.length > 0) {
|
||||
expect(result.text.length).toBeGreaterThan(0);
|
||||
// Check if the response mentions weather or temperature
|
||||
expect(result.text.toLowerCase()).toMatch(/(weather|temperature|mumbai)/);
|
||||
} else {
|
||||
// If text is empty, check if there are tool call results
|
||||
expect(result).toHaveProperty('toolResults');
|
||||
expect(Array.isArray(result.toolResults)).toBe(true);
|
||||
expect(result.toolResults.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { addMemories, retrieveMemories } from "../src";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { testConfig } from "../config/test-config";
|
||||
|
||||
describe("Memory Core Functions", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(20000);
|
||||
|
||||
describe("addMemories", () => {
|
||||
it("should successfully add memories and return correct format", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "I love red cars." },
|
||||
{ type: "text", text: "I like Toyota Cars." },
|
||||
{ type: "text", text: "I prefer SUVs." },
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
const response = await addMemories(messages, { user_id: userId });
|
||||
|
||||
expect(Array.isArray(response)).toBe(true);
|
||||
response.forEach((memory: { event: any; }) => {
|
||||
expect(memory).toHaveProperty('id');
|
||||
expect(memory).toHaveProperty('data');
|
||||
expect(memory).toHaveProperty('event');
|
||||
expect(memory.event).toBe('ADD');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("retrieveMemories", () => {
|
||||
beforeEach(async () => {
|
||||
// Add some test memories before each retrieval test
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "I love red cars." },
|
||||
{ type: "text", text: "I like Toyota Cars." },
|
||||
{ type: "text", text: "I prefer SUVs." },
|
||||
],
|
||||
}
|
||||
];
|
||||
await addMemories(messages, { user_id: userId });
|
||||
});
|
||||
|
||||
it("should retrieve memories with string prompt", async () => {
|
||||
const prompt = "Which car would I prefer?";
|
||||
const response = await retrieveMemories(prompt, { user_id: userId });
|
||||
|
||||
expect(typeof response).toBe('string');
|
||||
expect(response.match(/Memory:/g)?.length).toBeGreaterThan(2);
|
||||
});
|
||||
|
||||
it("should retrieve memories with array of prompts", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Which car would I prefer?" },
|
||||
{ type: "text", text: "Suggest me some cars" },
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
const response = await retrieveMemories(messages, { user_id: userId });
|
||||
|
||||
expect(typeof response).toBe('string');
|
||||
expect(response.match(/Memory:/g)?.length).toBeGreaterThan(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import { generateText, streamText } from "ai";
|
||||
import { testConfig } from "../config/test-config";
|
||||
|
||||
interface Provider {
|
||||
name: string;
|
||||
activeModel: string;
|
||||
apiKey: string | undefined;
|
||||
}
|
||||
|
||||
describe.each(testConfig.providers)('TEXT/STREAM PROPERTIES: Tests with model %s', (provider: Provider) => {
|
||||
const { userId } = testConfig;
|
||||
let mem0: ReturnType<typeof testConfig.createTestClient>;
|
||||
jest.setTimeout(50000);
|
||||
|
||||
beforeEach(() => {
|
||||
mem0 = testConfig.createTestClient(provider);
|
||||
});
|
||||
|
||||
it("should stream text with onChunk handler", async () => {
|
||||
const chunkTexts: string[] = [];
|
||||
const { textStream } = streamText({
|
||||
model: mem0(provider.activeModel, {
|
||||
user_id: userId, // Use the uniform userId
|
||||
}),
|
||||
prompt: "Write only the name of the car I prefer and its color.",
|
||||
});
|
||||
|
||||
// Wait for the stream to complete
|
||||
for await (const _ of textStream) {
|
||||
chunkTexts.push(_);
|
||||
}
|
||||
|
||||
// Ensure chunks are collected
|
||||
expect(chunkTexts.length).toBeGreaterThan(0);
|
||||
expect(chunkTexts.every((text) => typeof text === "string" || typeof text === "object")).toBe(true);
|
||||
});
|
||||
|
||||
it("should call onFinish handler without throwing an error", async () => {
|
||||
streamText({
|
||||
model: mem0(provider.activeModel, {
|
||||
user_id: userId, // Use the uniform userId
|
||||
}),
|
||||
prompt: "Write only the name of the car I prefer and its color.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should generate fullStream with expected usage", async () => {
|
||||
const {
|
||||
text, // combined text
|
||||
usage, // combined usage of all steps
|
||||
} = await generateText({
|
||||
model: mem0.completion(provider.activeModel, {
|
||||
user_id: userId,
|
||||
}), // Ensure the model name is correct
|
||||
prompt:
|
||||
"Suggest me some good cars to buy. Each response MUST HAVE at least 200 words.",
|
||||
});
|
||||
|
||||
// Ensure text is a string
|
||||
expect(typeof text).toBe("string");
|
||||
|
||||
// Check usage
|
||||
expect(usage.inputTokens).toBeGreaterThanOrEqual(10);
|
||||
expect(usage.inputTokens).toBeLessThanOrEqual(500);
|
||||
expect(usage.outputTokens).toBeGreaterThanOrEqual(10);
|
||||
expect(usage.totalTokens).toBeGreaterThan(10);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { retrieveMemories } from "../../src";
|
||||
import { generateText } from "ai";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { testConfig } from "../../config/test-config";
|
||||
import { createAnthropic } from "@ai-sdk/anthropic";
|
||||
|
||||
describe("ANTHROPIC Integration Tests", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(30000);
|
||||
|
||||
let anthropic: any;
|
||||
|
||||
beforeEach(() => {
|
||||
anthropic = createAnthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve memories and generate text using ANTHROPIC provider", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: " Write only the car name and it's color." },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Retrieve memories based on previous messages
|
||||
const memories = await retrieveMemories(messages, { user_id: userId });
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: anthropic("claude-3-haiku-20240307"),
|
||||
messages: messages,
|
||||
system: memories.length > 0 ? memories : "No Memories Found"
|
||||
});
|
||||
|
||||
// Expect text to be a string
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate text using ANTHROPIC provider with memories", async () => {
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
const memories = await retrieveMemories(prompt, { user_id: userId });
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: anthropic("claude-3-haiku-20240307"),
|
||||
prompt: prompt,
|
||||
system: memories.length > 0 ? memories : "No Memories Found"
|
||||
});
|
||||
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { retrieveMemories } from "../../src";
|
||||
import { generateText } from "ai";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { testConfig } from "../../config/test-config";
|
||||
import { createCohere } from "@ai-sdk/cohere";
|
||||
|
||||
describe("COHERE Integration Tests", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(30000);
|
||||
let cohere: any;
|
||||
|
||||
beforeEach(() => {
|
||||
cohere = createCohere({
|
||||
apiKey: process.env.COHERE_API_KEY,
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve memories and generate text using COHERE provider", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: " Write only the car name and it's color." },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Retrieve memories based on previous messages
|
||||
const memories = await retrieveMemories(messages, { user_id: userId });
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: cohere("command-r-plus"),
|
||||
messages: messages,
|
||||
system: memories,
|
||||
});
|
||||
|
||||
// Expect text to be a string
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate text using COHERE provider with memories", async () => {
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
const memories = await retrieveMemories(prompt, { user_id: userId });
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: cohere("command-r-plus"),
|
||||
prompt: prompt,
|
||||
system: memories
|
||||
});
|
||||
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { retrieveMemories } from "../../src";
|
||||
import { generateText } from "ai";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { testConfig } from "../../config/test-config";
|
||||
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
||||
|
||||
describe("GOOGLE Integration Tests", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(30000);
|
||||
let google: any;
|
||||
|
||||
beforeEach(() => {
|
||||
google = createGoogleGenerativeAI({
|
||||
apiKey: process.env.GOOGLE_API_KEY,
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve memories and generate text using Google provider", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: " Write only the car name and it's color." },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Retrieve memories based on previous messages
|
||||
const memories = await retrieveMemories(messages, { user_id: userId });
|
||||
|
||||
const { text } = await generateText({
|
||||
model: google("gemini-1.5-flash"),
|
||||
messages: messages,
|
||||
system: memories,
|
||||
});
|
||||
|
||||
// Expect text to be a string
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate text using Google provider with memories", async () => {
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
const memories = await retrieveMemories(prompt, { user_id: userId });
|
||||
|
||||
const { text } = await generateText({
|
||||
model: google("gemini-1.5-flash"),
|
||||
prompt: prompt,
|
||||
system: memories
|
||||
});
|
||||
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { retrieveMemories } from "../../src";
|
||||
import { generateText } from "ai";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { testConfig } from "../../config/test-config";
|
||||
import { createGroq } from "@ai-sdk/groq";
|
||||
|
||||
describe("GROQ Integration Tests", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(30000);
|
||||
|
||||
let groq: any;
|
||||
|
||||
beforeEach(() => {
|
||||
groq = createGroq({
|
||||
apiKey: process.env.GROQ_API_KEY,
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve memories and generate text using GROQ provider", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: " Write only the car name and it's color." },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Retrieve memories based on previous messages
|
||||
const memories = await retrieveMemories(messages, { user_id: userId });
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: groq("llama3-8b-8192"),
|
||||
messages: messages,
|
||||
system: memories,
|
||||
});
|
||||
|
||||
// Expect text to be a string
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate text using GROQ provider with memories", async () => {
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
const memories = await retrieveMemories(prompt, { user_id: userId });
|
||||
|
||||
const { text } = await generateText({
|
||||
// @ts-ignore
|
||||
model: groq("llama3-8b-8192"),
|
||||
prompt: prompt,
|
||||
system: memories
|
||||
});
|
||||
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { retrieveMemories } from "../../src";
|
||||
import { generateText } from "ai";
|
||||
import { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
||||
import { testConfig } from "../../config/test-config";
|
||||
import { createOpenAI } from "@ai-sdk/openai";
|
||||
|
||||
describe("OPENAI Integration Tests", () => {
|
||||
const { userId } = testConfig;
|
||||
jest.setTimeout(30000);
|
||||
let openai: any;
|
||||
|
||||
beforeEach(() => {
|
||||
openai = createOpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve memories and generate text using OpenAI provider", async () => {
|
||||
const messages: LanguageModelV3Prompt = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: " Write only the car name and it's color." },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Retrieve memories based on previous messages
|
||||
const memories = await retrieveMemories(messages, { user_id: userId });
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai("gpt-4-turbo"),
|
||||
messages: messages,
|
||||
system: memories,
|
||||
});
|
||||
|
||||
// Expect text to be a string
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate text using openai provider with memories", async () => {
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
const memories = await retrieveMemories(prompt, { user_id: userId });
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai("gpt-4-turbo"),
|
||||
prompt: prompt,
|
||||
system: memories
|
||||
});
|
||||
|
||||
expect(typeof text).toBe('string');
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,486 @@
|
||||
import { createMem0, Mem0Provider } from "../src";
|
||||
import { Mem0GenericLanguageModel } from "../src/mem0-generic-language-model";
|
||||
import { Mem0ClassSelector } from "../src/mem0-provider-selector";
|
||||
import Mem0AITextGenerator from "../src/provider-response-provider";
|
||||
import { Mem0ConfigSettings } from "../src/mem0-types";
|
||||
|
||||
// Mock fetch globally for memory API tests
|
||||
const mockFetch = jest.fn();
|
||||
global.fetch = mockFetch;
|
||||
|
||||
describe("V3 Provider Contract", () => {
|
||||
beforeEach(() => {
|
||||
mockFetch.mockReset();
|
||||
});
|
||||
|
||||
describe("Mem0Provider factory", () => {
|
||||
let provider: Mem0Provider;
|
||||
|
||||
beforeEach(() => {
|
||||
provider = createMem0({
|
||||
provider: "openai",
|
||||
mem0ApiKey: "test-key",
|
||||
apiKey: "test-openai-key",
|
||||
});
|
||||
});
|
||||
|
||||
it("should have specificationVersion v3", () => {
|
||||
expect(provider.specificationVersion).toBe("v3");
|
||||
});
|
||||
|
||||
it("should expose languageModel, chat, and completion methods", () => {
|
||||
expect(typeof provider.languageModel).toBe("function");
|
||||
expect(typeof provider.chat).toBe("function");
|
||||
expect(typeof provider.completion).toBe("function");
|
||||
});
|
||||
|
||||
it("should be callable as a function", () => {
|
||||
const model = provider("gpt-4o");
|
||||
expect(model).toBeDefined();
|
||||
expect(model.specificationVersion).toBe("v3");
|
||||
});
|
||||
|
||||
it("should throw when called with new keyword", () => {
|
||||
expect(() => new (provider as any)("gpt-4o")).toThrow(
|
||||
"The Mem0 model function cannot be called with the new keyword."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Mem0GenericLanguageModel V3 interface", () => {
|
||||
let model: Mem0GenericLanguageModel;
|
||||
|
||||
beforeEach(() => {
|
||||
model = new Mem0GenericLanguageModel(
|
||||
"gpt-4o",
|
||||
{ user_id: "test-user" },
|
||||
{
|
||||
provider: "openai",
|
||||
mem0ApiKey: "test-key",
|
||||
apiKey: "test-openai-key",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should implement specificationVersion v3", () => {
|
||||
expect(model.specificationVersion).toBe("v3");
|
||||
});
|
||||
|
||||
it("should not have deprecated V2 properties", () => {
|
||||
expect((model as any).defaultObjectGenerationMode).toBeUndefined();
|
||||
expect((model as any).supportsImageUrls).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should have supportedUrls as a record", () => {
|
||||
expect(model.supportedUrls).toBeDefined();
|
||||
expect(typeof model.supportedUrls).toBe("object");
|
||||
expect(model.supportedUrls["*"]).toBeDefined();
|
||||
expect(Array.isArray(model.supportedUrls["*"])).toBe(true);
|
||||
});
|
||||
|
||||
it("should have doGenerate and doStream methods", () => {
|
||||
expect(typeof model.doGenerate).toBe("function");
|
||||
expect(typeof model.doStream).toBe("function");
|
||||
});
|
||||
|
||||
it("should have provider and modelId properties", () => {
|
||||
expect(model.provider).toBe("openai");
|
||||
expect(model.modelId).toBe("gpt-4o");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Mem0ClassSelector", () => {
|
||||
const supportedProviders = ["openai", "anthropic", "cohere", "groq", "google", "gemini"];
|
||||
|
||||
it.each(supportedProviders)("should accept %s as a valid provider", (providerName) => {
|
||||
const selector = new Mem0ClassSelector(
|
||||
"test-model",
|
||||
{ provider: providerName, apiKey: "test-key" }
|
||||
);
|
||||
expect(selector).toBeDefined();
|
||||
});
|
||||
|
||||
it("should throw for unsupported provider", () => {
|
||||
expect(
|
||||
() => new Mem0ClassSelector("test-model", { provider: "invalid-provider" })
|
||||
).toThrow("Model not supported: invalid-provider");
|
||||
});
|
||||
|
||||
it("should create a V3 provider instance", () => {
|
||||
const selector = new Mem0ClassSelector(
|
||||
"gpt-4o",
|
||||
{ provider: "openai", apiKey: "test-key" }
|
||||
);
|
||||
const provider = selector.createProvider();
|
||||
expect(provider.specificationVersion).toBe("v3");
|
||||
expect(typeof provider.doGenerate).toBe("function");
|
||||
expect(typeof provider.doStream).toBe("function");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Provider modelType routing", () => {
|
||||
it("should create chat model with chat modelType", () => {
|
||||
const provider = createMem0({
|
||||
provider: "openai",
|
||||
mem0ApiKey: "test-key",
|
||||
apiKey: "test-openai-key",
|
||||
});
|
||||
|
||||
const chatModel = provider.chat("gpt-4o");
|
||||
expect(chatModel).toBeDefined();
|
||||
expect(chatModel.specificationVersion).toBe("v3");
|
||||
expect((chatModel as any).config.modelType).toBe("chat");
|
||||
});
|
||||
|
||||
it("should create completion model with completion modelType", () => {
|
||||
const provider = createMem0({
|
||||
provider: "openai",
|
||||
mem0ApiKey: "test-key",
|
||||
apiKey: "test-openai-key",
|
||||
});
|
||||
|
||||
const completionModel = provider.completion("gpt-4o");
|
||||
expect(completionModel).toBeDefined();
|
||||
expect(completionModel.specificationVersion).toBe("v3");
|
||||
expect((completionModel as any).config.modelType).toBe("completion");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Default baseURL", () => {
|
||||
it("should default to https, not http", () => {
|
||||
const provider = createMem0({
|
||||
provider: "openai",
|
||||
mem0ApiKey: "test-key",
|
||||
});
|
||||
const model = provider("gpt-4o") as Mem0GenericLanguageModel;
|
||||
expect((model as any).config.baseURL).toMatch(/^https:\/\//);
|
||||
});
|
||||
});
|
||||
|
||||
describe("google/gemini alias routing", () => {
|
||||
it("should route google and gemini to the same provider", () => {
|
||||
const googleModel = new Mem0AITextGenerator(
|
||||
"gemini-2.0-flash",
|
||||
{ provider: "google", apiKey: "test-key" },
|
||||
{}
|
||||
);
|
||||
const geminiModel = new Mem0AITextGenerator(
|
||||
"gemini-2.0-flash",
|
||||
{ provider: "gemini", apiKey: "test-key" },
|
||||
{}
|
||||
);
|
||||
expect(googleModel.specificationVersion).toBe("v3");
|
||||
expect(geminiModel.specificationVersion).toBe("v3");
|
||||
expect(googleModel.modelId).toBe(geminiModel.modelId);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Graph memory removal", () => {
|
||||
it("should not have enable_graph in Mem0ConfigSettings type", () => {
|
||||
const config: Mem0ConfigSettings = {
|
||||
user_id: "test-user",
|
||||
mem0ApiKey: "test-key",
|
||||
};
|
||||
expect((config as any).enable_graph).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("processMemories prompt cloning", () => {
|
||||
it("should not mutate the original prompt array", async () => {
|
||||
const model = new Mem0GenericLanguageModel(
|
||||
"gpt-4o",
|
||||
{ user_id: "test-user" },
|
||||
{
|
||||
provider: "openai",
|
||||
mem0ApiKey: "test-key",
|
||||
apiKey: "test-openai-key",
|
||||
}
|
||||
);
|
||||
|
||||
const originalPrompt = [
|
||||
{ role: "user" as const, content: [{ type: "text" as const, text: "Hello" }] },
|
||||
];
|
||||
const originalLength = originalPrompt.length;
|
||||
|
||||
// Mock both Mem0 API calls — addMemories (POST /v3/memories/add/) and getMemories (POST /v3/memories/search/)
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({ results: [] }),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [
|
||||
{ memory: "User likes TypeScript" },
|
||||
],
|
||||
});
|
||||
|
||||
// Access processMemories via reflection
|
||||
const processMemories = (model as any).processMemories.bind(model);
|
||||
await processMemories(originalPrompt, { mem0ApiKey: "test-key", user_id: "test-user" });
|
||||
|
||||
// Original prompt should NOT have been mutated
|
||||
expect(originalPrompt.length).toBe(originalLength);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMemories normalization", () => {
|
||||
it("should return a flat array when API returns a flat array", async () => {
|
||||
const { getMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [
|
||||
{ memory: "User prefers dark mode" },
|
||||
{ memory: "User likes React" },
|
||||
],
|
||||
});
|
||||
|
||||
const result = await getMemories("test query", { mem0ApiKey: "test-key", user_id: "test-user" });
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].memory).toBe("User prefers dark mode");
|
||||
});
|
||||
|
||||
it("should return a flat array when API returns { results: [...] }", async () => {
|
||||
const { getMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
results: [
|
||||
{ memory: "User prefers dark mode" },
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await getMemories("test query", { mem0ApiKey: "test-key", user_id: "test-user" });
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return empty array when API returns empty results", async () => {
|
||||
const { getMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [],
|
||||
});
|
||||
|
||||
const result = await getMemories("test query", { mem0ApiKey: "test-key", user_id: "test-user" });
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("retrieveMemories normalization", () => {
|
||||
it("should return empty string when no memories exist", async () => {
|
||||
const { retrieveMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [],
|
||||
});
|
||||
|
||||
const result = await retrieveMemories("test query", { mem0ApiKey: "test-key", user_id: "test-user" });
|
||||
expect(result).toBe("");
|
||||
});
|
||||
|
||||
it("should return formatted string when memories exist as flat array", async () => {
|
||||
const { retrieveMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [
|
||||
{ memory: "User likes pizza" },
|
||||
],
|
||||
});
|
||||
|
||||
const result = await retrieveMemories("test query", { mem0ApiKey: "test-key", user_id: "test-user" });
|
||||
expect(result).toContain("System Message:");
|
||||
expect(result).toContain("User likes pizza");
|
||||
});
|
||||
|
||||
it("should return formatted string when memories come as { results: [...] }", async () => {
|
||||
const { retrieveMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
results: [{ memory: "User likes pizza" }],
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await retrieveMemories("test query", { mem0ApiKey: "test-key", user_id: "test-user" });
|
||||
expect(result).toContain("System Message:");
|
||||
expect(result).toContain("User likes pizza");
|
||||
});
|
||||
});
|
||||
|
||||
describe("top_k zero handling", () => {
|
||||
it("should respect top_k: 0 and not fall back to default", async () => {
|
||||
const { searchMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [],
|
||||
});
|
||||
|
||||
await searchMemories("test query", {
|
||||
mem0ApiKey: "test-key",
|
||||
user_id: "test-user",
|
||||
top_k: 0,
|
||||
});
|
||||
|
||||
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(body.top_k).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Mem0 v3 API migration", () => {
|
||||
it("should call /v3/memories/search/ endpoint for search", async () => {
|
||||
const { searchMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [],
|
||||
});
|
||||
|
||||
await searchMemories("test query", { mem0ApiKey: "test-key", user_id: "test-user" });
|
||||
|
||||
const url = mockFetch.mock.calls[0][0];
|
||||
expect(url).toBe("https://api.mem0.ai/v3/memories/search/");
|
||||
});
|
||||
|
||||
it("should call /v3/memories/add/ endpoint for add", async () => {
|
||||
const { addMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({ message: "ok", status: "PENDING", event_id: "test" }),
|
||||
});
|
||||
|
||||
await addMemories(
|
||||
[{ role: "user" as const, content: [{ type: "text" as const, text: "Hello" }] }],
|
||||
{ mem0ApiKey: "test-key", user_id: "test-user" }
|
||||
);
|
||||
|
||||
const url = mockFetch.mock.calls[0][0];
|
||||
expect(url).toBe("https://api.mem0.ai/v3/memories/add/");
|
||||
});
|
||||
|
||||
it("should put entity IDs inside filters for search (not top-level)", async () => {
|
||||
const { searchMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [],
|
||||
});
|
||||
|
||||
await searchMemories("test query", {
|
||||
mem0ApiKey: "test-key",
|
||||
user_id: "user-123",
|
||||
agent_id: "agent-456",
|
||||
});
|
||||
|
||||
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(body.filters.user_id).toBe("user-123");
|
||||
expect(body.filters.agent_id).toBe("agent-456");
|
||||
// Should NOT be top-level
|
||||
expect(body.user_id).toBeUndefined();
|
||||
expect(body.agent_id).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not send deprecated v2 params (version, output_format, org_id, etc.)", async () => {
|
||||
const { searchMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [],
|
||||
});
|
||||
|
||||
await searchMemories("test query", { mem0ApiKey: "test-key", user_id: "test-user" });
|
||||
|
||||
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(body.version).toBeUndefined();
|
||||
expect(body.output_format).toBeUndefined();
|
||||
expect(body.org_id).toBeUndefined();
|
||||
expect(body.project_id).toBeUndefined();
|
||||
expect(body.org_name).toBeUndefined();
|
||||
expect(body.project_name).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not send deprecated params in add body", async () => {
|
||||
const { addMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({ message: "ok", status: "PENDING", event_id: "test" }),
|
||||
});
|
||||
|
||||
await addMemories(
|
||||
[{ role: "user" as const, content: [{ type: "text" as const, text: "Hello" }] }],
|
||||
{ mem0ApiKey: "test-key", user_id: "test-user" }
|
||||
);
|
||||
|
||||
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(body.version).toBeUndefined();
|
||||
expect(body.async_mode).toBeUndefined();
|
||||
expect(body.output_format).toBeUndefined();
|
||||
expect(body.enable_graph).toBeUndefined();
|
||||
expect(body.filter_memories).toBeUndefined();
|
||||
expect(body.org_id).toBeUndefined();
|
||||
expect(body.project_id).toBeUndefined();
|
||||
// Should have entity ID at top-level for add endpoint
|
||||
expect(body.user_id).toBe("test-user");
|
||||
});
|
||||
|
||||
it("should not have deprecated fields in Mem0ConfigSettings type", () => {
|
||||
const config: Mem0ConfigSettings = {
|
||||
user_id: "test-user",
|
||||
mem0ApiKey: "test-key",
|
||||
};
|
||||
expect((config as any).org_id).toBeUndefined();
|
||||
expect((config as any).project_id).toBeUndefined();
|
||||
expect((config as any).org_name).toBeUndefined();
|
||||
expect((config as any).project_name).toBeUndefined();
|
||||
expect((config as any).output_format).toBeUndefined();
|
||||
expect((config as any).filter_memories).toBeUndefined();
|
||||
expect((config as any).async_mode).toBeUndefined();
|
||||
expect((config as any).enable_graph).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should default top_k to 10 (v3 default) when not provided", async () => {
|
||||
const { searchMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [],
|
||||
});
|
||||
|
||||
await searchMemories("test query", { mem0ApiKey: "test-key", user_id: "test-user" });
|
||||
|
||||
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(body.top_k).toBe(10);
|
||||
});
|
||||
|
||||
it("should merge user-provided filters with entity ID filters", async () => {
|
||||
const { searchMemories } = require("../src/mem0-utils");
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [],
|
||||
});
|
||||
|
||||
await searchMemories("test query", {
|
||||
mem0ApiKey: "test-key",
|
||||
user_id: "user-123",
|
||||
filters: { category: "preferences" },
|
||||
});
|
||||
|
||||
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(body.filters.user_id).toBe("user-123");
|
||||
expect(body.filters.category).toBe("preferences");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user