feat(oss): port v3 pipeline with hybrid search, entity extraction, and additive scoring (#4805)

Co-authored-by: Soumil Rathi <soumilrathi@gmail.com>
Co-authored-by: Saket Aryan <saketaryan2002@gmail.com>
Co-authored-by: chaithanyak42 <chaithanya.kumar42a@gmail.com>
Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
soumil-rathi
2026-04-14 18:00:58 +05:30
committed by GitHub
co-authored by Soumil Rathi Saket Aryan chaithanyak42 kartik-mem0
parent 57f944e18a
commit a488e19044
120 changed files with 10107 additions and 17135 deletions
+56 -10
View File
@@ -23,6 +23,37 @@ import { captureClientEvent, generateHash } from "./telemetry";
import { camelToSnake, camelToSnakeKeys, snakeToCamelKeys } from "./utils";
import { createExceptionFromResponse, MemoryError } from "../common/exceptions";
// Entity params that must be passed via filters - check both snake_case and camelCase
const ENTITY_PARAMS = [
"user_id",
"agent_id",
"app_id",
"run_id",
"userId",
"agentId",
"appId",
"runId",
];
/**
* Validates that no top-level entity parameters are passed.
* @throws Error if entity params are found at top level
*/
function rejectTopLevelEntityParams(
options: Record<string, any> | undefined,
methodName: string,
): void {
const invalidKeys = Object.keys(options ?? {}).filter((k) =>
ENTITY_PARAMS.includes(k),
);
if (invalidKeys.length > 0) {
throw new Error(
`Top-level entity parameters [${invalidKeys.join(", ")}] are not supported in ${methodName}(). ` +
`Use filters: { user_id: "..." } instead.`,
);
}
}
class APIError extends Error {
constructor(message: string) {
super(message);
@@ -190,7 +221,7 @@ export default class MemoryClient {
this._captureEvent("add", [payloadKeys]);
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/memories/`,
`${this.host}/v3/memories/`,
{
method: "POST",
headers: this.headers,
@@ -254,13 +285,17 @@ export default class MemoryClient {
}
async getAll(options?: GetAllMemoryOptions): Promise<Array<Memory>> {
// Reject top-level entity params - must use filters instead
rejectTopLevelEntityParams(options as Record<string, any>, "getAll");
if (this.telemetryId === "") await this.ping();
const payloadKeys = Object.keys(options || {});
this._captureEvent("get_all", [payloadKeys]);
const { page, pageSize, ...rest } = options ?? {};
const { page, pageSize, filters, ...rest } = options ?? {};
const body: Record<string, any> = {
output_format: "v1.1",
...camelToSnakeKeys(rest),
...(filters && { filters }),
};
let url = `${this.host}/v2/memories/`;
@@ -273,33 +308,36 @@ export default class MemoryClient {
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?: SearchMemoryOptions,
): Promise<Array<Memory>> {
): Promise<{ results: Array<Memory> }> {
// Reject top-level entity params - must use filters instead
rejectTopLevelEntityParams(options as Record<string, any>, "search");
if (this.telemetryId === "") await this.ping();
const payloadKeys = Object.keys(options || {});
this._captureEvent("search", [payloadKeys]);
const { filters, ...rest } = options ?? {};
const payload: Record<string, any> = {
query,
output_format: "v1.1",
...camelToSnakeKeys(options ?? {}),
...camelToSnakeKeys(rest),
...(filters && { filters }),
};
const response = await this._fetchWithErrorHandling(
`${this.host}/v2/memories/search/`,
`${this.host}/v3/memories/search/`,
{
method: "POST",
headers: this.headers,
body: JSON.stringify(payload),
},
);
// Unwrap v1.1 format: { results: [...] } → [...]
return Array.isArray(response) ? response : (response?.results ?? response);
return response;
}
async delete(memoryId: string): Promise<{ message: string }> {
@@ -614,12 +652,16 @@ export default class MemoryClient {
throw new Error("Missing filters or schema");
}
const { filters, ...rest } = data;
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/exports/`,
{
method: "POST",
headers: this.headers,
body: JSON.stringify(camelToSnakeKeys(data)),
body: JSON.stringify({
...camelToSnakeKeys(rest),
filters,
}),
},
);
@@ -636,12 +678,16 @@ export default class MemoryClient {
throw new Error("Missing memoryExportId or filters");
}
const { filters, ...rest } = data;
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/exports/get/`,
{
method: "POST",
headers: this.headers,
body: JSON.stringify(camelToSnakeKeys(data)),
body: JSON.stringify({
...camelToSnakeKeys(rest),
...(filters && { filters }),
}),
},
);
return response;