fix(sdk): removing deprecating param from our sdk and docs changes with it (#4740)
This commit is contained in:
@@ -3,14 +3,17 @@ import type * as MemoryTypes from "./mem0.types";
|
||||
|
||||
// Re-export all types from mem0.types
|
||||
export type {
|
||||
MemoryOptions,
|
||||
EntityOptions,
|
||||
AddMemoryOptions,
|
||||
SearchMemoryOptions,
|
||||
GetAllMemoryOptions,
|
||||
DeleteAllMemoryOptions,
|
||||
ProjectOptions,
|
||||
Memory,
|
||||
MemoryHistory,
|
||||
MemoryUpdateBody,
|
||||
ProjectResponse,
|
||||
PromptUpdatePayload,
|
||||
SearchOptions,
|
||||
Webhook,
|
||||
WebhookCreatePayload,
|
||||
WebhookUpdatePayload,
|
||||
@@ -19,6 +22,8 @@ export type {
|
||||
AllUsers,
|
||||
User,
|
||||
FeedbackPayload,
|
||||
CreateMemoryExportPayload,
|
||||
GetMemoryExportPayload,
|
||||
} from "./mem0.types";
|
||||
|
||||
// Re-export enums as values (not type-only)
|
||||
|
||||
+57
-205
@@ -4,11 +4,13 @@ import {
|
||||
ProjectOptions,
|
||||
Memory,
|
||||
MemoryHistory,
|
||||
MemoryOptions,
|
||||
AddMemoryOptions,
|
||||
SearchMemoryOptions,
|
||||
GetAllMemoryOptions,
|
||||
DeleteAllMemoryOptions,
|
||||
MemoryUpdateBody,
|
||||
ProjectResponse,
|
||||
PromptUpdatePayload,
|
||||
SearchOptions,
|
||||
Webhook,
|
||||
WebhookCreatePayload,
|
||||
WebhookUpdatePayload,
|
||||
@@ -30,19 +32,13 @@ class APIError extends Error {
|
||||
interface ClientOptions {
|
||||
apiKey: string;
|
||||
host?: string;
|
||||
organizationName?: string;
|
||||
projectName?: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
export default class MemoryClient {
|
||||
apiKey: string;
|
||||
host: string;
|
||||
organizationName: string | null;
|
||||
projectName: string | null;
|
||||
organizationId: string | number | null;
|
||||
projectId: string | number | null;
|
||||
private organizationId: string | number | null;
|
||||
private projectId: string | number | null;
|
||||
headers: Record<string, string>;
|
||||
client: any;
|
||||
telemetryId: string;
|
||||
@@ -59,35 +55,11 @@ export default class MemoryClient {
|
||||
}
|
||||
}
|
||||
|
||||
_validateOrgProject(): void {
|
||||
// Check for organizationName/projectName pair
|
||||
if (
|
||||
(this.organizationName === null && this.projectName !== null) ||
|
||||
(this.organizationName !== null && this.projectName === null)
|
||||
) {
|
||||
console.warn(
|
||||
"Warning: Both organizationName and projectName must be provided together when using either. This will be removed from version 1.0.40. Note that organizationName/projectName are being deprecated in favor of organizationId/projectId.",
|
||||
);
|
||||
}
|
||||
|
||||
// Check for organizationId/projectId pair
|
||||
if (
|
||||
(this.organizationId === null && this.projectId !== null) ||
|
||||
(this.organizationId !== null && this.projectId === null)
|
||||
) {
|
||||
console.warn(
|
||||
"Warning: Both organizationId and projectId must be provided together when using either. This will be removed from version 1.0.40.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(options: ClientOptions) {
|
||||
this.apiKey = options.apiKey;
|
||||
this.host = options.host || "https://api.mem0.ai";
|
||||
this.organizationName = options.organizationName || null;
|
||||
this.projectName = options.projectName || null;
|
||||
this.organizationId = options.organizationId || null;
|
||||
this.projectId = options.projectId || null;
|
||||
this.organizationId = null;
|
||||
this.projectId = null;
|
||||
|
||||
this.headers = {
|
||||
Authorization: `Token ${this.apiKey}`,
|
||||
@@ -101,28 +73,19 @@ export default class MemoryClient {
|
||||
});
|
||||
|
||||
this._validateApiKey();
|
||||
|
||||
// Initialize with a temporary ID that will be updated
|
||||
this.telemetryId = "";
|
||||
|
||||
// Initialize the client
|
||||
this._initializeClient();
|
||||
}
|
||||
|
||||
private async _initializeClient() {
|
||||
try {
|
||||
// Generate telemetry ID
|
||||
await this.ping();
|
||||
|
||||
if (!this.telemetryId) {
|
||||
this.telemetryId = generateHash(this.apiKey);
|
||||
}
|
||||
|
||||
this._validateOrgProject();
|
||||
|
||||
// Capture initialization event
|
||||
captureClientEvent("init", this, {
|
||||
api_version: "v1",
|
||||
client_type: "MemoryClient",
|
||||
}).catch((error: any) => {
|
||||
console.error("Failed to capture event:", error);
|
||||
@@ -163,13 +126,16 @@ export default class MemoryClient {
|
||||
return jsonResponse;
|
||||
}
|
||||
|
||||
_preparePayload(messages: Array<Message>, options: MemoryOptions): object {
|
||||
_preparePayload(
|
||||
messages: Array<Message>,
|
||||
options: Record<string, any>,
|
||||
): object {
|
||||
const payload: any = {};
|
||||
payload.messages = messages;
|
||||
return { ...payload, ...options };
|
||||
}
|
||||
|
||||
_prepareParams(options: MemoryOptions): object {
|
||||
_prepareParams(options: Record<string, any>): object {
|
||||
return Object.fromEntries(
|
||||
Object.entries(options).filter(([_, v]) => v != null),
|
||||
);
|
||||
@@ -197,9 +163,8 @@ export default class MemoryClient {
|
||||
|
||||
const { org_id, project_id, user_email } = response;
|
||||
|
||||
// Only update if values are actually present
|
||||
if (org_id && !this.organizationId) this.organizationId = org_id;
|
||||
if (project_id && !this.projectId) this.projectId = project_id;
|
||||
if (org_id) this.organizationId = org_id;
|
||||
if (project_id) this.projectId = project_id;
|
||||
if (user_email) this.telemetryId = user_email;
|
||||
} catch (error: any) {
|
||||
// Pass through structured exceptions and APIError
|
||||
@@ -215,30 +180,11 @@ export default class MemoryClient {
|
||||
|
||||
async add(
|
||||
messages: Array<Message>,
|
||||
options: MemoryOptions & Record<string, any> = {},
|
||||
options: AddMemoryOptions & Record<string, any> = {},
|
||||
): Promise<Array<Memory>> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
options.org_name = this.organizationName;
|
||||
options.project_name = this.projectName;
|
||||
}
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
options.org_id = this.organizationId;
|
||||
options.project_id = this.projectId;
|
||||
|
||||
if (options.org_name) delete options.org_name;
|
||||
if (options.project_name) delete options.project_name;
|
||||
}
|
||||
|
||||
if (options.api_version) {
|
||||
options.version = options.api_version.toString() || "v2";
|
||||
}
|
||||
|
||||
const payload = this._preparePayload(messages, options);
|
||||
|
||||
// get payload keys whose value is not null or undefined
|
||||
const payloadKeys = Object.keys(payload);
|
||||
this._captureEvent("add", [payloadKeys]);
|
||||
|
||||
@@ -276,7 +222,6 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
const payload: Record<string, any> = {};
|
||||
if (text !== undefined) payload.text = text;
|
||||
if (metadata !== undefined) payload.metadata = metadata;
|
||||
@@ -307,87 +252,53 @@ export default class MemoryClient {
|
||||
);
|
||||
}
|
||||
|
||||
async getAll(options?: SearchOptions): Promise<Array<Memory>> {
|
||||
async getAll(options?: GetAllMemoryOptions): Promise<Array<Memory>> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("get_all", [payloadKeys]);
|
||||
const { api_version, page, page_size, ...otherOptions } = options ?? {};
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
otherOptions.org_name = this.organizationName;
|
||||
otherOptions.project_name = this.projectName;
|
||||
}
|
||||
|
||||
let appendedParams = "";
|
||||
let paginated_response = false;
|
||||
const { page, page_size, ...rest } = options ?? {};
|
||||
const body: Record<string, any> = {
|
||||
output_format: "v1.1",
|
||||
...rest,
|
||||
};
|
||||
|
||||
let url = `${this.host}/v2/memories/`;
|
||||
if (page && page_size) {
|
||||
appendedParams += `page=${page}&page_size=${page_size}`;
|
||||
paginated_response = true;
|
||||
url += `?page=${page}&page_size=${page_size}`;
|
||||
}
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
otherOptions.org_id = this.organizationId;
|
||||
otherOptions.project_id = this.projectId;
|
||||
|
||||
if (otherOptions.org_name) delete otherOptions.org_name;
|
||||
if (otherOptions.project_name) delete otherOptions.project_name;
|
||||
}
|
||||
|
||||
if (api_version === "v2") {
|
||||
let url = paginated_response
|
||||
? `${this.host}/v2/memories/?${appendedParams}`
|
||||
: `${this.host}/v2/memories/`;
|
||||
return this._fetchWithErrorHandling(url, {
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(otherOptions),
|
||||
});
|
||||
} else {
|
||||
// @ts-ignore
|
||||
const params = new URLSearchParams(this._prepareParams(otherOptions));
|
||||
const url = paginated_response
|
||||
? `${this.host}/v1/memories/?${params}&${appendedParams}`
|
||||
: `${this.host}/v1/memories/?${params}`;
|
||||
return this._fetchWithErrorHandling(url, {
|
||||
headers: this.headers,
|
||||
});
|
||||
}
|
||||
const response = await this._fetchWithErrorHandling(url, {
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
// Unwrap v1.1 format: { results: [...] } → [...]
|
||||
return Array.isArray(response) ? response : (response?.results ?? response);
|
||||
}
|
||||
|
||||
async search(
|
||||
query: string,
|
||||
options?: SearchOptions & Record<string, any>,
|
||||
options?: SearchMemoryOptions,
|
||||
): Promise<Array<Memory>> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("search", [payloadKeys]);
|
||||
const { api_version, ...otherOptions } = options ?? {};
|
||||
const payload = { query, ...otherOptions };
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
payload.org_name = this.organizationName;
|
||||
payload.project_name = this.projectName;
|
||||
}
|
||||
const payload: Record<string, any> = {
|
||||
query,
|
||||
output_format: "v1.1",
|
||||
...(options ?? {}),
|
||||
};
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
payload.org_id = this.organizationId;
|
||||
payload.project_id = this.projectId;
|
||||
|
||||
if (payload.org_name) delete payload.org_name;
|
||||
if (payload.project_name) delete payload.project_name;
|
||||
}
|
||||
const endpoint =
|
||||
api_version === "v2" ? "/v2/memories/search/" : "/v1/memories/search/";
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}${endpoint}`,
|
||||
`${this.host}/v2/memories/search/`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
// Unwrap v1.1 format: { results: [...] } → [...]
|
||||
return Array.isArray(response) ? response : (response?.results ?? response);
|
||||
}
|
||||
|
||||
async delete(memoryId: string): Promise<{ message: string }> {
|
||||
@@ -402,23 +313,12 @@ export default class MemoryClient {
|
||||
);
|
||||
}
|
||||
|
||||
async deleteAll(options: MemoryOptions = {}): Promise<{ message: string }> {
|
||||
async deleteAll(
|
||||
options: DeleteAllMemoryOptions = {},
|
||||
): Promise<{ message: string }> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("delete_all", [payloadKeys]);
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
options.org_name = this.organizationName;
|
||||
options.project_name = this.projectName;
|
||||
}
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
options.org_id = this.organizationId;
|
||||
options.project_id = this.projectId;
|
||||
|
||||
if (options.org_name) delete options.org_name;
|
||||
if (options.project_name) delete options.project_name;
|
||||
}
|
||||
// @ts-ignore
|
||||
const params = new URLSearchParams(this._prepareParams(options));
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
@@ -443,31 +343,20 @@ export default class MemoryClient {
|
||||
return response;
|
||||
}
|
||||
|
||||
async users(): Promise<AllUsers> {
|
||||
async users(options?: {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}): Promise<AllUsers> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
this._captureEvent("users", []);
|
||||
const options: MemoryOptions = {};
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
options.org_name = this.organizationName;
|
||||
options.project_name = this.projectName;
|
||||
}
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
options.org_id = this.organizationId;
|
||||
options.project_id = this.projectId;
|
||||
|
||||
if (options.org_name) delete options.org_name;
|
||||
if (options.project_name) delete options.project_name;
|
||||
}
|
||||
// @ts-ignore
|
||||
const params = new URLSearchParams(options);
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/entities/?${params}`,
|
||||
{
|
||||
headers: this.headers,
|
||||
},
|
||||
);
|
||||
let url = `${this.host}/v1/entities/`;
|
||||
const params: string[] = [];
|
||||
if (options?.page) params.push(`page=${options.page}`);
|
||||
if (options?.page_size) params.push(`page_size=${options.page_size}`);
|
||||
if (params.length) url += `?${params.join("&")}`;
|
||||
const response = await this._fetchWithErrorHandling(url, {
|
||||
headers: this.headers,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -502,7 +391,6 @@ export default class MemoryClient {
|
||||
} = {},
|
||||
): Promise<{ message: string }> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
|
||||
let to_delete: Array<{ type: string; name: string }> = [];
|
||||
const { user_id, agent_id, app_id, run_id } = params;
|
||||
@@ -527,29 +415,9 @@ export default class MemoryClient {
|
||||
throw new Error("No entities to delete");
|
||||
}
|
||||
|
||||
const requestOptions: MemoryOptions = {};
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
requestOptions.org_name = this.organizationName;
|
||||
requestOptions.project_name = this.projectName;
|
||||
}
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
requestOptions.org_id = this.organizationId;
|
||||
requestOptions.project_id = this.projectId;
|
||||
|
||||
if (requestOptions.org_name) delete requestOptions.org_name;
|
||||
if (requestOptions.project_name) delete requestOptions.project_name;
|
||||
}
|
||||
|
||||
// Delete each entity and handle errors
|
||||
for (const entity of to_delete) {
|
||||
try {
|
||||
await this.client.delete(
|
||||
`/v2/entities/${entity.type}/${entity.name}/`,
|
||||
{
|
||||
params: requestOptions,
|
||||
},
|
||||
);
|
||||
await this.client.delete(`/v2/entities/${entity.type}/${entity.name}/`);
|
||||
} catch (error: any) {
|
||||
throw new APIError(
|
||||
`Failed to delete ${entity.type} ${entity.name}: ${error.message}`,
|
||||
@@ -558,13 +426,7 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
this._captureEvent("delete_users", [
|
||||
{
|
||||
user_id: user_id,
|
||||
agent_id: agent_id,
|
||||
app_id: app_id,
|
||||
run_id: run_id,
|
||||
sync_type: "sync",
|
||||
},
|
||||
{ user_id, agent_id, app_id, run_id, sync_type: "sync" },
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -612,7 +474,6 @@ export default class MemoryClient {
|
||||
|
||||
async getProject(options: ProjectOptions): Promise<ProjectResponse> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("get_project", [payloadKeys]);
|
||||
const { fields } = options;
|
||||
@@ -639,7 +500,6 @@ export default class MemoryClient {
|
||||
prompts: PromptUpdatePayload,
|
||||
): Promise<Record<string, any>> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
this._captureEvent("update_project", []);
|
||||
if (!(this.organizationId && this.projectId)) {
|
||||
throw new Error(
|
||||
@@ -748,15 +608,10 @@ export default class MemoryClient {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._captureEvent("create_memory_export", []);
|
||||
|
||||
// Return if missing filters or schema
|
||||
if (!data.filters || !data.schema) {
|
||||
throw new Error("Missing filters or schema");
|
||||
}
|
||||
|
||||
// Add Org and Project ID
|
||||
data.org_id = this.organizationId?.toString() || null;
|
||||
data.project_id = this.projectId?.toString() || null;
|
||||
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/exports/`,
|
||||
{
|
||||
@@ -779,9 +634,6 @@ export default class MemoryClient {
|
||||
throw new Error("Missing memory_export_id or filters");
|
||||
}
|
||||
|
||||
data.org_id = this.organizationId?.toString() || "";
|
||||
data.project_id = this.projectId?.toString() || "";
|
||||
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/exports/get/`,
|
||||
{
|
||||
|
||||
@@ -1,59 +1,70 @@
|
||||
interface Common {
|
||||
project_id?: string | null;
|
||||
org_id?: string | null;
|
||||
}
|
||||
|
||||
export interface MemoryOptions {
|
||||
api_version?: API_VERSION | string;
|
||||
version?: API_VERSION | string;
|
||||
// ─── Entity Options (for add/delete — top-level identity) ───
|
||||
export interface EntityOptions {
|
||||
user_id?: string;
|
||||
agent_id?: string;
|
||||
app_id?: string;
|
||||
run_id?: string;
|
||||
}
|
||||
|
||||
// ─── Per-Method Options ─────────────────────────────────────
|
||||
export interface AddMemoryOptions extends EntityOptions {
|
||||
metadata?: Record<string, any>;
|
||||
filters?: Record<string, any>;
|
||||
org_name?: string | null; // Deprecated
|
||||
project_name?: string | null; // Deprecated
|
||||
org_id?: string | number | null;
|
||||
project_id?: string | number | null;
|
||||
infer?: boolean;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
includes?: string;
|
||||
excludes?: string;
|
||||
enable_graph?: boolean;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
custom_categories?: custom_categories[];
|
||||
custom_instructions?: string;
|
||||
timestamp?: number;
|
||||
output_format?: string | OutputFormat;
|
||||
async_mode?: boolean;
|
||||
filter_memories?: boolean;
|
||||
immutable?: boolean;
|
||||
structured_data_schema?: Record<string, any>;
|
||||
enable_graph?: boolean;
|
||||
}
|
||||
|
||||
export interface SearchMemoryOptions {
|
||||
filters?: Record<string, any>;
|
||||
metadata?: Record<string, any>;
|
||||
top_k?: number;
|
||||
threshold?: number;
|
||||
rerank?: boolean;
|
||||
fields?: string[];
|
||||
categories?: string[];
|
||||
enable_graph?: boolean;
|
||||
}
|
||||
|
||||
export interface GetAllMemoryOptions {
|
||||
filters?: Record<string, any>;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
categories?: string[];
|
||||
enable_graph?: boolean;
|
||||
}
|
||||
|
||||
export interface DeleteAllMemoryOptions extends EntityOptions {}
|
||||
|
||||
// ─── Project Options ────────────────────────────────────────
|
||||
export interface ProjectOptions {
|
||||
fields?: string[];
|
||||
}
|
||||
|
||||
export enum OutputFormat {
|
||||
V1 = "v1.0",
|
||||
V1_1 = "v1.1",
|
||||
}
|
||||
|
||||
export enum API_VERSION {
|
||||
V1 = "v1",
|
||||
V2 = "v2",
|
||||
export interface PromptUpdatePayload {
|
||||
custom_instructions?: string;
|
||||
custom_categories?: custom_categories[];
|
||||
retrieval_criteria?: any[];
|
||||
enable_graph?: boolean;
|
||||
version?: string;
|
||||
memory_depth?: string | null;
|
||||
usecase_setting?: string | number;
|
||||
multilingual?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// ─── Enums ──────────────────────────────────────────────────
|
||||
export enum Feedback {
|
||||
POSITIVE = "POSITIVE",
|
||||
NEGATIVE = "NEGATIVE",
|
||||
VERY_NEGATIVE = "VERY_NEGATIVE",
|
||||
}
|
||||
|
||||
// ─── Message Types ──────────────────────────────────────────
|
||||
export interface MultiModalMessages {
|
||||
type: "image_url";
|
||||
image_url: {
|
||||
@@ -68,30 +79,9 @@ export interface Messages {
|
||||
|
||||
export interface Message extends Messages {}
|
||||
|
||||
export interface MemoryHistory {
|
||||
id: string;
|
||||
memory_id: string;
|
||||
input: Array<Messages>;
|
||||
old_memory: string | null;
|
||||
new_memory: string | null;
|
||||
user_id: string;
|
||||
categories: Array<string>;
|
||||
event: Event | string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface SearchOptions extends MemoryOptions {
|
||||
api_version?: API_VERSION | string;
|
||||
limit?: number;
|
||||
enable_graph?: boolean;
|
||||
threshold?: number;
|
||||
top_k?: number;
|
||||
only_metadata_based_search?: boolean;
|
||||
keyword_search?: boolean;
|
||||
fields?: string[];
|
||||
categories?: string[];
|
||||
rerank?: boolean;
|
||||
// ─── Response Types (reflect API shapes, unchanged) ─────────
|
||||
export interface MemoryData {
|
||||
memory: string;
|
||||
}
|
||||
|
||||
enum Event {
|
||||
@@ -101,10 +91,6 @@ enum Event {
|
||||
NOOP = "NOOP",
|
||||
}
|
||||
|
||||
export interface MemoryData {
|
||||
memory: string;
|
||||
}
|
||||
|
||||
export interface Memory {
|
||||
id: string;
|
||||
messages?: Array<Messages>;
|
||||
@@ -125,6 +111,19 @@ export interface Memory {
|
||||
run_id?: string | null;
|
||||
}
|
||||
|
||||
export interface MemoryHistory {
|
||||
id: string;
|
||||
memory_id: string;
|
||||
input: Array<Messages>;
|
||||
old_memory: string | null;
|
||||
new_memory: string | null;
|
||||
user_id: string;
|
||||
categories: Array<string>;
|
||||
event: Event | string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface MemoryUpdateBody {
|
||||
memoryId: string;
|
||||
text: string;
|
||||
@@ -157,20 +156,7 @@ interface custom_categories {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface PromptUpdatePayload {
|
||||
custom_instructions?: string;
|
||||
custom_categories?: custom_categories[];
|
||||
retrieval_criteria?: any[];
|
||||
enable_graph?: boolean;
|
||||
version?: string;
|
||||
inclusion_prompt?: string;
|
||||
exclusion_prompt?: string;
|
||||
memory_depth?: string | null;
|
||||
usecase_setting?: string | number;
|
||||
multilingual?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// ─── Webhook Types ──────────────────────────────────────────
|
||||
export enum WebhookEvent {
|
||||
MEMORY_ADDED = "memory_add",
|
||||
MEMORY_UPDATED = "memory_update",
|
||||
@@ -202,19 +188,20 @@ export interface WebhookUpdatePayload {
|
||||
eventTypes?: WebhookEvent[];
|
||||
}
|
||||
|
||||
// ─── Feedback & Export Types ────────────────────────────────
|
||||
export interface FeedbackPayload {
|
||||
memory_id: string;
|
||||
feedback?: Feedback | null;
|
||||
feedback_reason?: string | null;
|
||||
}
|
||||
|
||||
export interface CreateMemoryExportPayload extends Common {
|
||||
export interface CreateMemoryExportPayload {
|
||||
schema: Record<string, any>;
|
||||
filters: Record<string, any>;
|
||||
export_instructions?: string;
|
||||
}
|
||||
|
||||
export interface GetMemoryExportPayload extends Common {
|
||||
export interface GetMemoryExportPayload {
|
||||
filters?: Record<string, any>;
|
||||
memory_export_id?: string;
|
||||
}
|
||||
|
||||
@@ -122,7 +122,9 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
// ─── Get all ──────────────────────────────────────────────
|
||||
describe("get all memories", () => {
|
||||
test("returns all memories for test user", async () => {
|
||||
const memories = await client.getAll({ user_id: TEST_USER_ID });
|
||||
const memories = await client.getAll({
|
||||
filters: { user_id: TEST_USER_ID },
|
||||
});
|
||||
|
||||
expect(Array.isArray(memories)).toBe(true);
|
||||
expect(memories.length).toBeGreaterThanOrEqual(memoryIds.length);
|
||||
@@ -135,7 +137,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
|
||||
test("returns paginated results with page and page_size", async () => {
|
||||
const page1 = await client.getAll({
|
||||
user_id: TEST_USER_ID,
|
||||
filters: { user_id: TEST_USER_ID },
|
||||
page: 1,
|
||||
page_size: 1,
|
||||
});
|
||||
@@ -200,7 +202,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
|
||||
test("getAll for non-existent user returns empty array", async () => {
|
||||
const memories = await client.getAll({
|
||||
user_id: `nonexistent-user-${randomUUID()}`,
|
||||
filters: { user_id: `nonexistent-user-${randomUUID()}` },
|
||||
});
|
||||
|
||||
expect(Array.isArray(memories)).toBe(true);
|
||||
|
||||
@@ -63,7 +63,9 @@ export async function waitForMemories(
|
||||
maxRetries = 4,
|
||||
): Promise<Memory[]> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
const memories = await withRetry(() => client.getAll({ user_id: userId }));
|
||||
const memories = await withRetry(() =>
|
||||
client.getAll({ filters: { user_id: userId } }),
|
||||
);
|
||||
if (Array.isArray(memories) && memories.length >= minCount) {
|
||||
return memories;
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ describeIntegration("MemoryClient Integration — Initialization", () => {
|
||||
|
||||
afterAll(() => cleanup());
|
||||
|
||||
test("client pings successfully and resolves org/project", async () => {
|
||||
test("client pings successfully", async () => {
|
||||
await client.ping();
|
||||
expect(client.organizationId).toBeTruthy();
|
||||
expect(client.projectId).toBeTruthy();
|
||||
// org/project are now resolved internally from the API key
|
||||
expect(client.telemetryId).toBeTruthy();
|
||||
});
|
||||
|
||||
test("get with invalid ID throws ValidationError", async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Integration tests: Search and history operations.
|
||||
*
|
||||
* Tests search v1, search v2, and memory history against the real API.
|
||||
* Tests search, filtered search, and memory history against the real API.
|
||||
*
|
||||
* Run: MEM0_API_KEY=your-key npx jest search.test.ts --forceExit
|
||||
*/
|
||||
@@ -36,14 +36,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
// ─── Search v1 ────────────────────────────────────────────
|
||||
describe("search v1", () => {
|
||||
// ─── Search ─────────────────────────────────────────────
|
||||
describe("search", () => {
|
||||
test("searches memories by user_id and returns results with scores", async () => {
|
||||
// Search index may lag behind listing index — poll until ready
|
||||
const results = await waitForSearchResults(
|
||||
client,
|
||||
"What is my favorite color?",
|
||||
{ user_id: TEST_USER_ID },
|
||||
{ filters: { user_id: TEST_USER_ID } },
|
||||
);
|
||||
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
@@ -57,15 +57,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Search v2 ────────────────────────────────────────────
|
||||
describe("search v2", () => {
|
||||
// ─── Search with filters ─────────────────────────────────
|
||||
describe("search with filters", () => {
|
||||
test("searches with OR filters and returns results", async () => {
|
||||
const results = await waitForSearchResults(
|
||||
client,
|
||||
"What do you know about me?",
|
||||
{
|
||||
filters: { OR: [{ user_id: TEST_USER_ID }] },
|
||||
api_version: "v2",
|
||||
},
|
||||
);
|
||||
|
||||
@@ -110,19 +109,19 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
describe("edge cases", () => {
|
||||
test("search for non-existent user returns empty results", async () => {
|
||||
const results = await client.search("anything", {
|
||||
user_id: `nonexistent-user-${randomUUID()}`,
|
||||
filters: { user_id: `nonexistent-user-${randomUUID()}` },
|
||||
});
|
||||
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
expect(results.length).toBe(0);
|
||||
});
|
||||
|
||||
test("search with limit param does not throw", async () => {
|
||||
test("search with top_k param does not throw", async () => {
|
||||
const results = await client.search(
|
||||
"Tell me about integration test user",
|
||||
{
|
||||
user_id: TEST_USER_ID,
|
||||
limit: 1,
|
||||
filters: { user_id: TEST_USER_ID },
|
||||
top_k: 1,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
/**
|
||||
* MemoryClient unit tests — add, get, getAll, update, delete, deleteAll, history.
|
||||
* MemoryClient unit tests — add, get, update, delete, deleteAll, history.
|
||||
* Tests verify request construction, not mock response echo.
|
||||
*/
|
||||
import { MemoryClient } from "../mem0";
|
||||
import type { Memory, MemoryHistory } from "../mem0.types";
|
||||
import type { MemoryHistory } from "../mem0.types";
|
||||
import {
|
||||
createMockMemory,
|
||||
createMockMemoryHistory,
|
||||
TEST_API_KEY,
|
||||
TEST_ORG_ID,
|
||||
TEST_PROJECT_ID,
|
||||
} from "./helpers";
|
||||
import {
|
||||
setupMockFetch,
|
||||
@@ -61,40 +59,6 @@ describe("MemoryClient - add()", () => {
|
||||
expect(getFetchBody(call!).user_id).toBe("user_1");
|
||||
});
|
||||
|
||||
test("attaches org_id from constructor to payload", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
await client.add([{ role: "user", content: "test" }], { user_id: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/", "POST");
|
||||
const body = getFetchBody(call!);
|
||||
expect(body.org_id).toBe(TEST_ORG_ID);
|
||||
});
|
||||
|
||||
test("attaches project_id from constructor to payload", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
await client.add([{ role: "user", content: "test" }], { user_id: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/", "POST");
|
||||
const body = getFetchBody(call!);
|
||||
expect(body.project_id).toBe(TEST_PROJECT_ID);
|
||||
});
|
||||
|
||||
test("sends empty messages array without crashing", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/", { status: 200, body: [] });
|
||||
@@ -142,67 +106,6 @@ describe("MemoryClient - get()", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── getAll() ────────────────────────────────────────────
|
||||
|
||||
describe("MemoryClient - getAll()", () => {
|
||||
test("uses v2 POST endpoint when api_version=v2", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getAll({ user_id: "u1", api_version: "v2" });
|
||||
|
||||
expect(findFetchCall(mock, "/v2/memories/", "POST")).toBeDefined();
|
||||
});
|
||||
|
||||
test("uses v1 GET endpoint by default with user_id as query param", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getAll({ user_id: "u1" });
|
||||
|
||||
const call = mock.mock.calls.find(
|
||||
(c: [string, RequestInit]) =>
|
||||
c[0].includes("/v1/memories/?") && !c[1]?.method,
|
||||
);
|
||||
expect(call).toBeDefined();
|
||||
expect(call![0]).toContain("user_id=u1");
|
||||
});
|
||||
|
||||
test("appends page and page_size to URL as query params", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getAll({
|
||||
user_id: "u1",
|
||||
api_version: "v2",
|
||||
page: 2,
|
||||
page_size: 25,
|
||||
});
|
||||
|
||||
const call = mock.mock.calls.find((c: [string, RequestInit]) =>
|
||||
c[0].includes("page="),
|
||||
);
|
||||
expect(call![0]).toContain("page=2");
|
||||
expect(call![0]).toContain("page_size=25");
|
||||
});
|
||||
|
||||
test("does not crash when called without options", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/", { status: 200, body: [] });
|
||||
setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
const result: Memory[] = await client.getAll();
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── update() ────────────────────────────────────────────
|
||||
|
||||
describe("MemoryClient - update()", () => {
|
||||
|
||||
@@ -7,13 +7,7 @@ import {
|
||||
ValidationError,
|
||||
MemoryError,
|
||||
} from "../../common/exceptions";
|
||||
import {
|
||||
createMockFetch,
|
||||
TEST_API_KEY,
|
||||
TEST_HOST,
|
||||
TEST_ORG_ID,
|
||||
TEST_PROJECT_ID,
|
||||
} from "./helpers";
|
||||
import { createMockFetch, TEST_API_KEY, TEST_HOST } from "./helpers";
|
||||
import {
|
||||
setupMockFetch,
|
||||
installConsoleSuppression,
|
||||
@@ -55,24 +49,6 @@ describe("MemoryClient - Initialization", () => {
|
||||
expect(client.host).toBe(TEST_HOST);
|
||||
});
|
||||
|
||||
test("sets organizationId from constructor", () => {
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
expect(client.organizationId).toBe(TEST_ORG_ID);
|
||||
});
|
||||
|
||||
test("sets projectId from constructor", () => {
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
expect(client.projectId).toBe(TEST_PROJECT_ID);
|
||||
});
|
||||
|
||||
test("sets Authorization header with Token prefix", () => {
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
expect(client.headers["Authorization"]).toBe(`Token ${TEST_API_KEY}`);
|
||||
@@ -87,20 +63,6 @@ describe("MemoryClient - Initialization", () => {
|
||||
// ─── Ping ────────────────────────────────────────────────
|
||||
|
||||
describe("MemoryClient - ping()", () => {
|
||||
test("sets organizationId from ping response", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.ping();
|
||||
expect(client.organizationId).toBe(TEST_ORG_ID);
|
||||
});
|
||||
|
||||
test("sets projectId from ping response", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.ping();
|
||||
expect(client.projectId).toBe(TEST_PROJECT_ID);
|
||||
});
|
||||
|
||||
test("sets telemetryId from user_email in response", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
@@ -108,28 +70,6 @@ describe("MemoryClient - ping()", () => {
|
||||
expect(client.telemetryId).toBe("test@example.com");
|
||||
});
|
||||
|
||||
test("preserves constructor organizationId over ping response", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: "my_org",
|
||||
projectId: "my_proj",
|
||||
});
|
||||
await client.ping();
|
||||
expect(client.organizationId).toBe("my_org");
|
||||
});
|
||||
|
||||
test("preserves constructor projectId over ping response", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: "my_org",
|
||||
projectId: "my_proj",
|
||||
});
|
||||
await client.ping();
|
||||
expect(client.projectId).toBe("my_proj");
|
||||
});
|
||||
|
||||
test("throws AuthenticationError on 401 response", async () => {
|
||||
const { AuthenticationError } = await import("../../common/exceptions");
|
||||
const responses = new Map<string, { status: number; body: unknown }>();
|
||||
|
||||
@@ -4,12 +4,7 @@
|
||||
*/
|
||||
import { MemoryClient } from "../mem0";
|
||||
import { Feedback } from "../mem0.types";
|
||||
import {
|
||||
createMockFetch,
|
||||
TEST_API_KEY,
|
||||
TEST_ORG_ID,
|
||||
TEST_PROJECT_ID,
|
||||
} from "./helpers";
|
||||
import { createMockFetch, TEST_API_KEY } from "./helpers";
|
||||
import {
|
||||
setupMockFetch,
|
||||
findFetchCall,
|
||||
@@ -22,7 +17,7 @@ installConsoleSuppression();
|
||||
// ─── getProject() ───────────────────────────────────────
|
||||
|
||||
describe("MemoryClient - getProject()", () => {
|
||||
test("throws when organizationId and projectId not set", async () => {
|
||||
test("throws when organizationId and projectId not set (ping returns no org)", async () => {
|
||||
const responses = new Map<string, { status: number; body: unknown }>();
|
||||
responses.set("/v1/ping/", { status: 200, body: { status: "ok" } });
|
||||
global.fetch = createMockFetch(responses);
|
||||
@@ -47,11 +42,9 @@ describe("MemoryClient - getProject()", () => {
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
// org/project come from ping mock response
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.ping();
|
||||
await client.getProject({ fields: ["custom_instructions"] });
|
||||
|
||||
const call = mock.mock.calls.find(
|
||||
@@ -74,11 +67,8 @@ describe("MemoryClient - updateProject()", () => {
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.ping();
|
||||
await client.updateProject({
|
||||
custom_instructions: "Updated instructions",
|
||||
});
|
||||
@@ -95,11 +85,8 @@ describe("MemoryClient - updateProject()", () => {
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.ping();
|
||||
await client.updateProject({
|
||||
custom_instructions: "Updated instructions",
|
||||
});
|
||||
@@ -161,11 +148,7 @@ describe("MemoryClient - feedback()", () => {
|
||||
describe("MemoryClient - Memory Exports", () => {
|
||||
test("createMemoryExport throws when missing filters or schema", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await expect(
|
||||
client.createMemoryExport({
|
||||
filters: null as never,
|
||||
@@ -182,11 +165,7 @@ describe("MemoryClient - Memory Exports", () => {
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.createMemoryExport({
|
||||
schema: { fields: ["memory", "user_id"] },
|
||||
filters: { user_id: "u1" },
|
||||
@@ -195,37 +174,9 @@ describe("MemoryClient - Memory Exports", () => {
|
||||
expect(findFetchCall(mock, "/v1/exports/", "POST")).toBeDefined();
|
||||
});
|
||||
|
||||
test("createMemoryExport attaches org_id and project_id to body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/exports/", {
|
||||
status: 200,
|
||||
body: { message: "Created", id: "exp_1" },
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
await client.createMemoryExport({
|
||||
schema: { fields: ["memory"] },
|
||||
filters: { user_id: "u1" },
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v1/exports/", "POST");
|
||||
const body = getFetchBody(call!);
|
||||
expect(body.org_id).toBe(TEST_ORG_ID);
|
||||
expect(body.project_id).toBe(TEST_PROJECT_ID);
|
||||
});
|
||||
|
||||
test("getMemoryExport throws when missing both id and filters", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await expect(client.getMemoryExport({} as never)).rejects.toThrow(
|
||||
"Missing memory_export_id or filters",
|
||||
);
|
||||
@@ -239,11 +190,7 @@ describe("MemoryClient - Memory Exports", () => {
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getMemoryExport({ memory_export_id: "exp_123" });
|
||||
|
||||
expect(findFetchCall(mock, "/v1/exports/get/", "POST")).toBeDefined();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* MemoryClient unit tests — search (v1/v2 routing, filters).
|
||||
* MemoryClient unit tests — search (v2 default, filters).
|
||||
* Tests verify request construction, not mock response echo.
|
||||
*/
|
||||
import { MemoryClient } from "../mem0";
|
||||
@@ -15,60 +15,52 @@ import {
|
||||
installConsoleSuppression();
|
||||
|
||||
describe("MemoryClient - search()", () => {
|
||||
test("sends POST to /v1/memories/search/ by default", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("What is my name?", { user_id: "u1" });
|
||||
|
||||
expect(findFetchCall(mock, "/v1/memories/search/", "POST")).toBeDefined();
|
||||
});
|
||||
|
||||
test("includes query in request body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("What is my name?", { user_id: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/search/", "POST");
|
||||
expect(getFetchBody(call!).query).toBe("What is my name?");
|
||||
});
|
||||
|
||||
test("includes user_id in request body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("test", { user_id: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/search/", "POST");
|
||||
expect(getFetchBody(call!).user_id).toBe("u1");
|
||||
});
|
||||
|
||||
test("uses /v2/memories/search/ when api_version=v2", async () => {
|
||||
test("sends POST to /v2/memories/search/ by default", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("test", { user_id: "u1", api_version: "v2" });
|
||||
await client.search("What is my name?", {
|
||||
filters: { user_id: "u1" },
|
||||
});
|
||||
|
||||
expect(findFetchCall(mock, "/v2/memories/search/", "POST")).toBeDefined();
|
||||
});
|
||||
|
||||
test("passes filters through to the v2 API body", async () => {
|
||||
test("includes query in request body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("What is my name?", {
|
||||
filters: { user_id: "u1" },
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
|
||||
expect(getFetchBody(call!).query).toBe("What is my name?");
|
||||
});
|
||||
|
||||
test("passes filters through to the API body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("test", { filters: { user_id: "u1" } });
|
||||
|
||||
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
|
||||
expect(getFetchBody(call!).filters).toEqual({ user_id: "u1" });
|
||||
});
|
||||
|
||||
test("passes complex OR filters through to the API body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("query", {
|
||||
api_version: "v2",
|
||||
filters: { OR: [{ user_id: "u1" }, { agent_id: "a1" }] },
|
||||
});
|
||||
|
||||
@@ -81,7 +73,7 @@ describe("MemoryClient - search()", () => {
|
||||
|
||||
test("does not crash when called without options", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/search/", { status: 200, body: [] });
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
@@ -91,12 +83,12 @@ describe("MemoryClient - search()", () => {
|
||||
|
||||
test("handles empty results array", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/search/", { status: 200, body: [] });
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
const result: Memory[] = await client.search("nonexistent query", {
|
||||
user_id: "u1",
|
||||
filters: { user_id: "u1" },
|
||||
});
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
/**
|
||||
* MemoryClient unit tests — users, deleteUser, deleteUsers.
|
||||
* MemoryClient unit tests — users, deleteUser.
|
||||
* Tests verify entity type routing and request construction.
|
||||
*/
|
||||
import { MemoryClient } from "../mem0";
|
||||
import {
|
||||
createMockUser,
|
||||
createMockAllUsers,
|
||||
TEST_API_KEY,
|
||||
TEST_ORG_ID,
|
||||
TEST_PROJECT_ID,
|
||||
} from "./helpers";
|
||||
import { createMockUser, createMockAllUsers, TEST_API_KEY } from "./helpers";
|
||||
import {
|
||||
setupMockFetch,
|
||||
findFetchCall,
|
||||
@@ -40,111 +34,6 @@ describe("MemoryClient - users()", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── deleteUsers() ──────────────────────────────────────
|
||||
|
||||
describe("MemoryClient - deleteUsers()", () => {
|
||||
function createClientWithMockedAxios() {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const axiosDeleteMock = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ data: { message: "Deleted" } });
|
||||
client.client.delete = axiosDeleteMock;
|
||||
return { client, axiosDeleteMock };
|
||||
}
|
||||
|
||||
test("routes user_id to DELETE /v2/entities/user/:name/", async () => {
|
||||
const { client, axiosDeleteMock } = createClientWithMockedAxios();
|
||||
await client.deleteUsers({ user_id: "u1" });
|
||||
|
||||
expect(axiosDeleteMock).toHaveBeenCalledWith("/v2/entities/user/u1/", {
|
||||
params: expect.objectContaining({
|
||||
org_id: TEST_ORG_ID,
|
||||
project_id: TEST_PROJECT_ID,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
test("routes agent_id to DELETE /v2/entities/agent/:name/", async () => {
|
||||
const { client, axiosDeleteMock } = createClientWithMockedAxios();
|
||||
await client.deleteUsers({ agent_id: "agent_1" });
|
||||
|
||||
expect(axiosDeleteMock).toHaveBeenCalledWith(
|
||||
"/v2/entities/agent/agent_1/",
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
test("routes app_id to DELETE /v2/entities/app/:name/", async () => {
|
||||
const { client, axiosDeleteMock } = createClientWithMockedAxios();
|
||||
await client.deleteUsers({ app_id: "app_1" });
|
||||
|
||||
expect(axiosDeleteMock).toHaveBeenCalledWith(
|
||||
"/v2/entities/app/app_1/",
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
test("routes run_id to DELETE /v2/entities/run/:name/", async () => {
|
||||
const { client, axiosDeleteMock } = createClientWithMockedAxios();
|
||||
await client.deleteUsers({ run_id: "run_1" });
|
||||
|
||||
expect(axiosDeleteMock).toHaveBeenCalledWith(
|
||||
"/v2/entities/run/run_1/",
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
test("returns 'Entity deleted successfully.' for single entity", async () => {
|
||||
const { client } = createClientWithMockedAxios();
|
||||
const result = await client.deleteUsers({ user_id: "u1" });
|
||||
expect(result.message).toBe("Entity deleted successfully.");
|
||||
});
|
||||
|
||||
test("returns 'All users, agents, apps and runs deleted.' when no params given", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/entities/", {
|
||||
status: 200,
|
||||
body: createMockAllUsers([createMockUser({ name: "u1", type: "user" })]),
|
||||
});
|
||||
setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
client.client.delete = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ data: { message: "Deleted" } });
|
||||
|
||||
const result = await client.deleteUsers();
|
||||
expect(result.message).toBe("All users, agents, apps and runs deleted.");
|
||||
});
|
||||
|
||||
test("throws when no entities exist to delete", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/entities/", {
|
||||
status: 200,
|
||||
body: createMockAllUsers([]),
|
||||
});
|
||||
setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
client.client.delete = jest.fn();
|
||||
|
||||
await expect(client.deleteUsers()).rejects.toThrow("No entities to delete");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── deleteUser() (deprecated) ──────────────────────────
|
||||
|
||||
describe("MemoryClient - deleteUser() (deprecated)", () => {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
import { MemoryClient } from "../mem0";
|
||||
import { WebhookEvent } from "../mem0.types";
|
||||
import { TEST_API_KEY, TEST_ORG_ID, TEST_PROJECT_ID } from "./helpers";
|
||||
import { TEST_API_KEY } from "./helpers";
|
||||
import {
|
||||
setupMockFetch,
|
||||
findFetchCall,
|
||||
@@ -23,8 +23,6 @@ function webhookMock(extra?: Map<string, { status: number; body: unknown }>) {
|
||||
function createClient() {
|
||||
return new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,14 +98,6 @@ describe("MemoryClient - createWebhook", () => {
|
||||
expect(body.eventTypes).toBeUndefined();
|
||||
});
|
||||
|
||||
test("body does not contain projectId", async () => {
|
||||
const mock = await callCreate();
|
||||
const body = getFetchBody(
|
||||
findFetchCall(mock, "/api/v1/webhooks/", "POST")!,
|
||||
);
|
||||
expect(body.projectId).toBeUndefined();
|
||||
});
|
||||
|
||||
test("body does not contain webhookId", async () => {
|
||||
const mock = await callCreate();
|
||||
const body = getFetchBody(
|
||||
@@ -173,22 +163,6 @@ describe("MemoryClient - updateWebhook", () => {
|
||||
expect(body.eventTypes).toBeUndefined();
|
||||
});
|
||||
|
||||
test("body does not contain project_id", async () => {
|
||||
const mock = await callUpdate();
|
||||
const body = getFetchBody(
|
||||
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
|
||||
);
|
||||
expect(body.project_id).toBeUndefined();
|
||||
});
|
||||
|
||||
test("body does not contain projectId", async () => {
|
||||
const mock = await callUpdate();
|
||||
const body = getFetchBody(
|
||||
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
|
||||
);
|
||||
expect(body.projectId).toBeUndefined();
|
||||
});
|
||||
|
||||
test("body does not contain webhookId", async () => {
|
||||
const mock = await callUpdate();
|
||||
const body = getFetchBody(
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { MemoryClient } from "mem0ai";
|
||||
import type { Memory, MemoryOptions, SearchOptions } from "mem0ai";
|
||||
import type {
|
||||
Memory,
|
||||
AddMemoryOptions,
|
||||
SearchMemoryOptions,
|
||||
GetAllMemoryOptions,
|
||||
} from "mem0ai";
|
||||
|
||||
import {
|
||||
InputValues,
|
||||
@@ -102,10 +107,6 @@ export const mem0MemoryToMessages = (memories: Memory[]): BaseMessage[] => {
|
||||
export interface ClientOptions {
|
||||
apiKey: string;
|
||||
host?: string;
|
||||
organizationName?: string;
|
||||
projectName?: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +118,7 @@ export interface Mem0MemoryInput extends BaseChatMemoryInput {
|
||||
apiKey: string;
|
||||
humanPrefix?: string;
|
||||
aiPrefix?: string;
|
||||
memoryOptions?: MemoryOptions | SearchOptions;
|
||||
memoryOptions?: AddMemoryOptions | SearchMemoryOptions | GetAllMemoryOptions;
|
||||
mem0Options?: ClientOptions;
|
||||
separateMessages?: boolean;
|
||||
}
|
||||
@@ -160,7 +161,7 @@ export class Mem0Memory extends BaseChatMemory implements Mem0MemoryInput {
|
||||
|
||||
mem0Client: InstanceType<typeof MemoryClient>;
|
||||
|
||||
memoryOptions: MemoryOptions | SearchOptions;
|
||||
memoryOptions: AddMemoryOptions | SearchMemoryOptions | GetAllMemoryOptions;
|
||||
|
||||
mem0Options: ClientOptions;
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ export class ConfigManager {
|
||||
userConfig.historyDbPath ||
|
||||
userConfig.historyStore?.config?.historyDbPath ||
|
||||
DEFAULT_MEMORY_CONFIG.historyStore?.config?.historyDbPath,
|
||||
customPrompt: userConfig.customPrompt,
|
||||
customInstructions: userConfig.customInstructions,
|
||||
graphStore: {
|
||||
...DEFAULT_MEMORY_CONFIG.graphStore,
|
||||
...userConfig.graphStore,
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface GraphStoreConfig {
|
||||
provider: string;
|
||||
config: Neo4jConfig;
|
||||
llm?: LLMConfig;
|
||||
customPrompt?: string;
|
||||
customInstructions?: string;
|
||||
}
|
||||
|
||||
export function validateNeo4jConfig(config: Neo4jConfig): void {
|
||||
|
||||
@@ -136,7 +136,7 @@ export class MemoryGraph {
|
||||
};
|
||||
}
|
||||
|
||||
async search(query: string, filters: Record<string, any>, limit = 100) {
|
||||
async search(query: string, filters: Record<string, any>, topK = 100) {
|
||||
const entityTypeMap = await this._retrieveNodesFromData(query, filters);
|
||||
const searchOutput = await this._searchGraphDb(
|
||||
Object.keys(entityTypeMap),
|
||||
@@ -178,7 +178,7 @@ export class MemoryGraph {
|
||||
}
|
||||
}
|
||||
|
||||
async getAll(filters: Record<string, any>, limit = 100) {
|
||||
async getAll(filters: Record<string, any>, topK = 100) {
|
||||
const session = this.graph.session();
|
||||
try {
|
||||
const result = await session.run(
|
||||
@@ -187,7 +187,7 @@ export class MemoryGraph {
|
||||
RETURN n.name AS source, type(r) AS relationship, m.name AS target
|
||||
LIMIT toInteger($limit)
|
||||
`,
|
||||
{ user_id: filters["userId"], limit: Math.floor(Number(limit)) },
|
||||
{ user_id: filters["userId"], limit: Math.floor(Number(topK)) },
|
||||
);
|
||||
|
||||
const finalResults = result.records.map((record) => ({
|
||||
@@ -253,7 +253,7 @@ export class MemoryGraph {
|
||||
entityTypeMap: Record<string, string>,
|
||||
) {
|
||||
let messages;
|
||||
if (this.config.graphStore?.customPrompt) {
|
||||
if (this.config.graphStore?.customInstructions) {
|
||||
messages = [
|
||||
{
|
||||
role: "system",
|
||||
@@ -263,7 +263,7 @@ export class MemoryGraph {
|
||||
filters["userId"],
|
||||
).replace(
|
||||
"CUSTOM_PROMPT",
|
||||
`4. ${this.config.graphStore.customPrompt}`,
|
||||
`4. ${this.config.graphStore.customInstructions}`,
|
||||
) + "\nPlease provide your response in JSON format.",
|
||||
},
|
||||
{ role: "user", content: data },
|
||||
@@ -307,7 +307,7 @@ export class MemoryGraph {
|
||||
private async _searchGraphDb(
|
||||
nodeList: string[],
|
||||
filters: Record<string, any>,
|
||||
limit = 100,
|
||||
topK = 100,
|
||||
): Promise<SearchOutput[]> {
|
||||
const resultRelations: SearchOutput[] = [];
|
||||
const session = this.graph.session();
|
||||
@@ -344,7 +344,7 @@ export class MemoryGraph {
|
||||
n_embedding: nEmbedding,
|
||||
threshold: this.threshold,
|
||||
user_id: filters["userId"],
|
||||
limit: Math.floor(Number(limit)),
|
||||
limit: Math.floor(Number(topK)),
|
||||
});
|
||||
|
||||
resultRelations.push(
|
||||
|
||||
@@ -39,7 +39,7 @@ import { captureClientEvent } from "../utils/telemetry";
|
||||
|
||||
export class Memory {
|
||||
private config: MemoryConfig;
|
||||
private customPrompt: string | undefined;
|
||||
private customInstructions: string | undefined;
|
||||
private embedder: Embedder;
|
||||
private vectorStore!: VectorStore;
|
||||
private llm: LLM;
|
||||
@@ -56,7 +56,7 @@ export class Memory {
|
||||
// Merge and validate config
|
||||
this.config = ConfigManager.mergeConfig(config);
|
||||
|
||||
this.customPrompt = this.config.customPrompt;
|
||||
this.customInstructions = this.config.customInstructions;
|
||||
this.embedder = EmbedderFactory.create(
|
||||
this.config.embedder.provider,
|
||||
this.config.embedder.config,
|
||||
@@ -293,11 +293,11 @@ export class Memory {
|
||||
}
|
||||
const parsedMessages = messages.map((m) => m.content).join("\n");
|
||||
|
||||
const [systemPrompt, userPrompt] = this.customPrompt
|
||||
const [systemPrompt, userPrompt] = this.customInstructions
|
||||
? [
|
||||
this.customPrompt.toLowerCase().includes("json")
|
||||
? this.customPrompt
|
||||
: `${this.customPrompt}\n\nYou MUST return a valid JSON object with a 'facts' key containing an array of strings.`,
|
||||
this.customInstructions.toLowerCase().includes("json")
|
||||
? this.customInstructions
|
||||
: `${this.customInstructions}\n\nYou MUST return a valid JSON object with a 'facts' key containing an array of strings.`,
|
||||
`Input:\n${parsedMessages}`,
|
||||
]
|
||||
: getFactRetrievalMessages(parsedMessages);
|
||||
@@ -478,10 +478,10 @@ export class Memory {
|
||||
await this._ensureInitialized();
|
||||
await this._captureEvent("search", {
|
||||
query_length: query.length,
|
||||
limit: config.limit,
|
||||
topK: config.topK,
|
||||
has_filters: !!config.filters,
|
||||
});
|
||||
const { userId, agentId, runId, limit = 100, filters = {} } = config;
|
||||
const { userId, agentId, runId, topK = 100, filters = {} } = config;
|
||||
|
||||
if (userId) filters.userId = userId;
|
||||
if (agentId) filters.agentId = agentId;
|
||||
@@ -497,7 +497,7 @@ export class Memory {
|
||||
const queryEmbedding = await this.embedder.embed(query);
|
||||
const memories = await this.vectorStore.search(
|
||||
queryEmbedding,
|
||||
limit,
|
||||
topK,
|
||||
filters,
|
||||
);
|
||||
|
||||
@@ -642,19 +642,19 @@ export class Memory {
|
||||
async getAll(config: GetAllMemoryOptions): Promise<SearchResult> {
|
||||
await this._ensureInitialized();
|
||||
await this._captureEvent("get_all", {
|
||||
limit: config.limit,
|
||||
topK: config.topK,
|
||||
has_user_id: !!config.userId,
|
||||
has_agent_id: !!config.agentId,
|
||||
has_run_id: !!config.runId,
|
||||
});
|
||||
const { userId, agentId, runId, limit = 100 } = config;
|
||||
const { userId, agentId, runId, topK = 100 } = config;
|
||||
|
||||
const filters: SearchFilters = {};
|
||||
if (userId) filters.userId = userId;
|
||||
if (agentId) filters.agentId = agentId;
|
||||
if (runId) filters.runId = runId;
|
||||
|
||||
const [memories] = await this.vectorStore.list(filters, limit);
|
||||
const [memories] = await this.vectorStore.list(filters, topK);
|
||||
|
||||
const excludedKeys = new Set([
|
||||
"userId",
|
||||
|
||||
@@ -14,12 +14,12 @@ export interface AddMemoryOptions extends Entity {
|
||||
}
|
||||
|
||||
export interface SearchMemoryOptions extends Entity {
|
||||
limit?: number;
|
||||
topK?: number;
|
||||
filters?: SearchFilters;
|
||||
}
|
||||
|
||||
export interface GetAllMemoryOptions extends Entity {
|
||||
limit?: number;
|
||||
topK?: number;
|
||||
}
|
||||
|
||||
export interface DeleteAllMemoryOptions extends Entity {}
|
||||
|
||||
@@ -120,11 +120,11 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
|
||||
expect(cfg.graphStore!.config.url).toBe("neo4j://custom:7687");
|
||||
});
|
||||
|
||||
it("customPrompt passes through unchanged", () => {
|
||||
it("customInstructions passes through unchanged", () => {
|
||||
const cfg = ConfigManager.mergeConfig({
|
||||
customPrompt: "You are a helpful assistant",
|
||||
customInstructions: "You are a helpful assistant",
|
||||
});
|
||||
expect(cfg.customPrompt).toBe("You are a helpful assistant");
|
||||
expect(cfg.customInstructions).toBe("You are a helpful assistant");
|
||||
});
|
||||
|
||||
it("version override passes through unchanged", () => {
|
||||
|
||||
@@ -60,7 +60,7 @@ export interface GraphStoreConfig {
|
||||
provider: string;
|
||||
config: Neo4jConfig;
|
||||
llm?: LLMConfig;
|
||||
customPrompt?: string;
|
||||
customInstructions?: string;
|
||||
}
|
||||
|
||||
export interface MemoryConfig {
|
||||
@@ -80,7 +80,7 @@ export interface MemoryConfig {
|
||||
historyStore?: HistoryStoreConfig;
|
||||
disableHistory?: boolean;
|
||||
historyDbPath?: string;
|
||||
customPrompt?: string;
|
||||
customInstructions?: string;
|
||||
graphStore?: GraphStoreConfig;
|
||||
enableGraph?: boolean;
|
||||
}
|
||||
@@ -148,7 +148,7 @@ export const MemoryConfigSchema = z.object({
|
||||
}),
|
||||
}),
|
||||
historyDbPath: z.string().optional(),
|
||||
customPrompt: z.string().optional(),
|
||||
customInstructions: z.string().optional(),
|
||||
enableGraph: z.boolean().optional(),
|
||||
graphStore: z
|
||||
.object({
|
||||
@@ -164,7 +164,7 @@ export const MemoryConfigSchema = z.object({
|
||||
config: z.record(z.string(), z.any()),
|
||||
})
|
||||
.optional(),
|
||||
customPrompt: z.string().optional(),
|
||||
customInstructions: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
historyStore: z
|
||||
|
||||
@@ -331,7 +331,7 @@ export class AzureAISearch implements VectorStore {
|
||||
*/
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
const filterExpression = filters
|
||||
@@ -341,7 +341,7 @@ export class AzureAISearch implements VectorStore {
|
||||
const vectorQuery: VectorizedQuery<any> = {
|
||||
kind: "vector",
|
||||
vector: query,
|
||||
kNearestNeighborsCount: limit,
|
||||
kNearestNeighborsCount: topK,
|
||||
fields: ["vector"],
|
||||
};
|
||||
|
||||
@@ -355,7 +355,7 @@ export class AzureAISearch implements VectorStore {
|
||||
filterMode: this.vectorFilterMode as any,
|
||||
},
|
||||
filter: filterExpression,
|
||||
top: limit,
|
||||
top: topK,
|
||||
searchFields: ["payload"],
|
||||
});
|
||||
} else {
|
||||
@@ -366,7 +366,7 @@ export class AzureAISearch implements VectorStore {
|
||||
filterMode: this.vectorFilterMode as any,
|
||||
},
|
||||
filter: filterExpression,
|
||||
top: limit,
|
||||
top: topK,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -501,7 +501,7 @@ export class AzureAISearch implements VectorStore {
|
||||
*/
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
const filterExpression = filters
|
||||
? this.buildFilterExpression(filters)
|
||||
@@ -509,7 +509,7 @@ export class AzureAISearch implements VectorStore {
|
||||
|
||||
const searchResults = await this.searchClient.search("*", {
|
||||
filter: filterExpression,
|
||||
top: limit,
|
||||
top: topK,
|
||||
});
|
||||
|
||||
const results: VectorStoreResult[] = [];
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface VectorStore {
|
||||
): Promise<void>;
|
||||
search(
|
||||
query: number[],
|
||||
limit?: number,
|
||||
topK?: number,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]>;
|
||||
get(vectorId: string): Promise<VectorStoreResult | null>;
|
||||
@@ -21,7 +21,7 @@ export interface VectorStore {
|
||||
deleteCol(): Promise<void>;
|
||||
list(
|
||||
filters?: SearchFilters,
|
||||
limit?: number,
|
||||
topK?: number,
|
||||
): Promise<[VectorStoreResult[], number]>;
|
||||
getUserId(): Promise<string>;
|
||||
setUserId(userId: string): Promise<void>;
|
||||
|
||||
@@ -101,7 +101,7 @@ export class LangchainVectorStore implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters, // filters parameter is received but will be ignored
|
||||
): Promise<VectorStoreResult[]> {
|
||||
if (this.dimension && query.length !== this.dimension) {
|
||||
@@ -119,7 +119,7 @@ export class LangchainVectorStore implements VectorStore {
|
||||
// Call similaritySearchVectorWithScore WITHOUT the filter argument
|
||||
const results = await this.lcStore.similaritySearchVectorWithScore(
|
||||
query,
|
||||
limit,
|
||||
topK,
|
||||
// Do not pass lcFilter here
|
||||
);
|
||||
|
||||
@@ -192,7 +192,7 @@ export class LangchainVectorStore implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
// No standard list method in Langchain core interface.
|
||||
console.error(
|
||||
|
||||
@@ -100,7 +100,7 @@ export class MemoryVectorStore implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 10,
|
||||
topK: number = 10,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
if (query.length !== this.dimension) {
|
||||
@@ -136,7 +136,7 @@ export class MemoryVectorStore implements VectorStore {
|
||||
}
|
||||
|
||||
results.sort((a, b) => (b.score || 0) - (a.score || 0));
|
||||
return results.slice(0, limit);
|
||||
return results.slice(0, topK);
|
||||
}
|
||||
|
||||
async get(vectorId: string): Promise<VectorStoreResult | null> {
|
||||
@@ -179,7 +179,7 @@ export class MemoryVectorStore implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
const rows = this.db.prepare(`SELECT * FROM vectors`).all() as any[];
|
||||
const results: VectorStoreResult[] = [];
|
||||
@@ -206,7 +206,7 @@ export class MemoryVectorStore implements VectorStore {
|
||||
}
|
||||
}
|
||||
|
||||
return [results.slice(0, limit), results.length];
|
||||
return [results.slice(0, topK), results.length];
|
||||
}
|
||||
|
||||
async getUserId(): Promise<string> {
|
||||
|
||||
@@ -164,12 +164,12 @@ export class PGVector implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
const filterConditions: string[] = [];
|
||||
const queryVector = `[${query.join(",")}]`; // Format query vector as string with square brackets
|
||||
const filterValues: any[] = [queryVector, limit];
|
||||
const filterValues: any[] = [queryVector, topK];
|
||||
let filterIndex = 3;
|
||||
|
||||
if (filters) {
|
||||
@@ -254,7 +254,7 @@ export class PGVector implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
const filterConditions: string[] = [];
|
||||
const filterValues: any[] = [];
|
||||
@@ -286,7 +286,7 @@ export class PGVector implements VectorStore {
|
||||
${filterClause}
|
||||
`;
|
||||
|
||||
filterValues.push(limit); // Add limit as the last parameter
|
||||
filterValues.push(topK); // Add limit as the last parameter
|
||||
|
||||
const [listResult, countResult] = await Promise.all([
|
||||
this.client.query(listQuery, filterValues),
|
||||
|
||||
@@ -139,14 +139,14 @@ export class Qdrant implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
const queryFilter = this.createFilter(filters);
|
||||
const results = await this.client.search(this.collectionName, {
|
||||
vector: query,
|
||||
filter: queryFilter,
|
||||
limit,
|
||||
limit: topK,
|
||||
});
|
||||
|
||||
return results.map((hit) => ({
|
||||
@@ -198,10 +198,10 @@ export class Qdrant implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
const scrollRequest = {
|
||||
limit,
|
||||
limit: topK,
|
||||
filter: this.createFilter(filters),
|
||||
with_payload: true,
|
||||
with_vectors: false,
|
||||
|
||||
@@ -359,7 +359,7 @@ export class RedisDB implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
const snakeFilters = filters ? toSnakeCase(filters) : undefined;
|
||||
@@ -391,14 +391,14 @@ export class RedisDB implements VectorStore {
|
||||
DIALECT: 2,
|
||||
LIMIT: {
|
||||
from: 0,
|
||||
size: limit,
|
||||
size: topK,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const results = (await this.client.ft.search(
|
||||
this.indexName,
|
||||
`${filterExpr} =>[KNN ${limit} @embedding $vec AS __vector_score]`,
|
||||
`${filterExpr} =>[KNN ${topK} @embedding $vec AS __vector_score]`,
|
||||
searchOptions,
|
||||
)) as unknown as RedisSearchResult;
|
||||
|
||||
@@ -598,7 +598,7 @@ export class RedisDB implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
const snakeFilters = filters ? toSnakeCase(filters) : undefined;
|
||||
const filterExpr = snakeFilters
|
||||
@@ -613,7 +613,7 @@ export class RedisDB implements VectorStore {
|
||||
SORTDIR: "DESC",
|
||||
LIMIT: {
|
||||
from: 0,
|
||||
size: limit,
|
||||
size: topK,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -231,13 +231,13 @@ See the SQL migration instructions in the code comments.`,
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
try {
|
||||
const rpcQuery: VectorQueryParams = {
|
||||
query_embedding: query,
|
||||
match_count: limit,
|
||||
match_count: topK,
|
||||
};
|
||||
|
||||
if (filters) {
|
||||
@@ -336,13 +336,13 @@ See the SQL migration instructions in the code comments.`,
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
try {
|
||||
let query = this.client
|
||||
.from(this.tableName)
|
||||
.select("*", { count: "exact" })
|
||||
.limit(limit);
|
||||
.limit(topK);
|
||||
|
||||
if (filters) {
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
|
||||
@@ -76,7 +76,7 @@ export class VectorizeDB implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
try {
|
||||
@@ -87,7 +87,7 @@ export class VectorizeDB implements VectorStore {
|
||||
vector: query,
|
||||
filter: filters,
|
||||
returnMetadata: "all",
|
||||
topK: limit,
|
||||
topK: topK,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -197,7 +197,7 @@ export class VectorizeDB implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 20,
|
||||
topK: number = 20,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
try {
|
||||
const result = await this.client?.vectorize.indexes.query(
|
||||
@@ -206,7 +206,7 @@ export class VectorizeDB implements VectorStore {
|
||||
account_id: this.accountId,
|
||||
vector: Array(this.dimensions).fill(0), // Dummy vector for listing
|
||||
filter: filters,
|
||||
topK: limit,
|
||||
topK: topK,
|
||||
returnMetadata: "all",
|
||||
},
|
||||
);
|
||||
|
||||
@@ -305,13 +305,15 @@ describe("_establishNodesRelationsFromData", () => {
|
||||
expect(systemContent).toContain("test-user");
|
||||
expect(systemContent).not.toContain("USER_ID");
|
||||
// CUSTOM_PROMPT placeholder stays when no custom prompt is configured
|
||||
// (only replaced when config.graphStore.customPrompt is set)
|
||||
// (only replaced when config.graphStore.customInstructions is set)
|
||||
});
|
||||
|
||||
it("appends JSON format suffix and custom prompt when configured", async () => {
|
||||
mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] });
|
||||
|
||||
const mg = graph({ customPrompt: "Focus on food relationships only." });
|
||||
const mg = graph({
|
||||
customInstructions: "Focus on food relationships only.",
|
||||
});
|
||||
await mg._establishNodesRelationsFromData("data", FILTERS, {});
|
||||
|
||||
const [messages] = mockGenerateResponse.mock.calls[0];
|
||||
|
||||
Reference in New Issue
Block a user