Skip to content
Open
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
46 changes: 46 additions & 0 deletions src/main/libs/coworkMemoryExtractor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect,test } from 'vitest';

import { extractTurnMemoryChanges } from './coworkMemoryExtractor';

const assistantText = '好的,已了解。';

test('keeps the highest-confidence implicit memories when more than the cap qualify', () => {
// Order matters: two lower-confidence candidates appear BEFORE the highest one.
// preference (0.88) -> ownership (0.9) -> profile (0.93)
const userText = '我喜欢深色主题。我养了一只猫。我叫张三。';

const changes = extractTurnMemoryChanges({
userText,
assistantText,
guardLevel: 'relaxed',
});

const implicitAdds = changes.filter((c) => !c.isExplicit && c.action === 'add');

// Cap is 2 per turn.
expect(implicitAdds).toHaveLength(2);

// The profile fact (0.93) must survive even though it appears last, and the
// lowest-confidence preference (0.88) must be the one dropped.
const kept = implicitAdds.map((c) => c.text);
expect(kept).toContain('我叫张三');
expect(kept).toContain('我养了一只猫');
expect(kept).not.toContain('我喜欢深色主题');

// Sorted by confidence descending.
expect(implicitAdds[0].confidence).toBeGreaterThanOrEqual(implicitAdds[1].confidence);
});

test('deduplicates identical implicit candidates within a single turn', () => {
const userText = '我叫张三。我叫张三。';

const changes = extractTurnMemoryChanges({
userText,
assistantText,
guardLevel: 'relaxed',
});

const implicitAdds = changes.filter((c) => !c.isExplicit && c.action === 'add');
expect(implicitAdds).toHaveLength(1);
expect(implicitAdds[0].text).toBe('我叫张三');
});
10 changes: 7 additions & 3 deletions src/main/libs/coworkMemoryExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,15 @@ function extractImplicit(options: ExtractTurnMemoryOptions): ExtractedMemoryChan
isExplicit: false,
reason,
});

if (result.length >= maxImplicitAdds) break;
}

return result;
// Keep the highest-confidence candidates rather than the first ones seen, so a
// low-value preference appearing early cannot crowd out a high-value profile
// fact appearing later in the same turn. Array.prototype.sort is stable, so
// candidates that tie on confidence preserve their original order.
return result
.sort((a, b) => b.confidence - a.confidence)
.slice(0, maxImplicitAdds);
}

export function extractTurnMemoryChanges(options: ExtractTurnMemoryOptions): ExtractedMemoryChange[] {
Expand Down
Loading