Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/changelog/sdk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ mode: "wide"
<Tabs>
<Tab title="Python">

<Update label="2026-06-24" description="v2.0.9">

**Bug Fixes:**
- **Memory (OSS):** Improve entity extraction precision by avoiding sentence-start common noun noise, preserving useful topic phrases, and exact-deduplicating entity links before semantic matching ([#5829](https://github.com/mem0ai/mem0/pull/5829))

</Update>

<Update label="2026-06-24" description="v2.0.8">

**New Features:**
Expand Down Expand Up @@ -1063,6 +1070,13 @@ See the [OSS v1 to v2 migration guide](https://docs.mem0.ai/migration/oss-v1-to-

<Tab title="TypeScript">

<Update label="2026-06-24" description="v3.0.11">

**Bug Fixes:**
- **Memory (OSS):** Align entity extraction with Python by reducing generic entity noise, preserving useful topic phrases, and exact-deduplicating entity links before semantic matching ([#5829](https://github.com/mem0ai/mem0/pull/5829))

</Update>

<Update label="2026-06-24" description="v3.0.10">

**Bug Fixes:**
Expand Down
2 changes: 1 addition & 1 deletion mem0-ts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mem0ai",
"version": "3.0.10",
"version": "3.0.11",
"description": "The Memory Layer For Your AI Apps",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
82 changes: 72 additions & 10 deletions mem0-ts/src/oss/src/memory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,44 @@ export class Memory {
return filters;
}

private _normalizeEntityText(value: string): string {
return value.trim().toLowerCase().replace(/\s+/g, " ");
}

private async _existingEntitiesByText(
entityStore: VectorStore,
filters: Record<string, any>,
): Promise<Map<string, { id: string; payload: Record<string, any> }>> {
const rowsByText = new Map<
string,
{ id: string; payload: Record<string, any> }
>();
let rows: Array<{ id: string; payload: Record<string, any> }> = [];
try {
const listed = await entityStore.list(filters, 10000);
rows = (
Array.isArray(listed) && Array.isArray(listed[0])
? listed[0]
: (listed as any)
) as Array<{ id: string; payload: Record<string, any> }>;
} catch (e) {
console.debug(
`Exact entity lookup failed, falling back to semantic dedup: ${e}`,
);
return rowsByText;
}

for (const row of rows) {
const text = row.payload?.data;
if (typeof text !== "string") continue;
const key = this._normalizeEntityText(text);
if (key && !rowsByText.has(key)) {
rowsByText.set(key, row);
}
}
return rowsByText;
}

/**
* Remove `memoryId` from every entity record scoped to `filters`.
* If an entity's `linkedMemoryIds` becomes empty after removal, the
Expand Down Expand Up @@ -393,6 +431,10 @@ export class Memory {
if (entities.length === 0) return;

const entityStore = await this.getEntityStore();
const exactMatches = await this._existingEntitiesByText(
entityStore,
filters,
);

for (const entity of entities) {
try {
Expand All @@ -409,12 +451,21 @@ export class Memory {
score?: number;
payload: Record<string, any>;
}> = [];
try {
matches = await entityStore.search(entityVec, 1, filters);
} catch {}
const exactMatch = exactMatches.get(
this._normalizeEntityText(entity.text),
);
if (!exactMatch) {
try {
matches = await entityStore.search(entityVec, 1, filters);
} catch {}
}

if (matches.length > 0 && (matches[0].score ?? 0) >= 0.95) {
const match = matches[0];
const semanticMatch =
matches.length > 0 && (matches[0].score ?? 0) >= 0.95
? matches[0]
: undefined;
const match = exactMatch ?? semanticMatch;
if (match) {
const payload = match.payload || {};
const linked = new Set<string>(
Array.isArray(payload.linkedMemoryIds)
Expand Down Expand Up @@ -1062,6 +1113,10 @@ export class Memory {

if (valid.length > 0) {
const entityStore = await this.getEntityStore();
const exactMatches = await this._existingEntitiesByText(
entityStore,
filters,
);

// 7c: Search for existing entities one by one (no batch search)
const toInsertVectors: number[][] = [];
Expand All @@ -1077,13 +1132,20 @@ export class Memory {
score?: number;
payload: Record<string, any>;
}> = [];
try {
matches = await entityStore.search(entityVec, 1, filters);
} catch {}
const exactMatch = exactMatches.get(key);
if (!exactMatch) {
try {
matches = await entityStore.search(entityVec, 1, filters);
} catch {}
}

if (matches.length > 0 && (matches[0].score ?? 0) >= 0.95) {
const semanticMatch =
matches.length > 0 && (matches[0].score ?? 0) >= 0.95
? matches[0]
: undefined;
const match = exactMatch ?? semanticMatch;
if (match) {
// Update existing entity
const match = matches[0];
const payload = match.payload || {};
const linked = new Set<string>(payload.linkedMemoryIds ?? []);
for (const mid of memoryIds) linked.add(mid);
Expand Down
Loading
Loading