fix(server): frequent embedding (#13475)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Smarter embedding pipeline skips re-embedding when content hasn’t
changed; added content sanitization for embeddings and workspace content
retrieval.
- Bug Fixes
- Re-embedding now requires both a document update and the last
embedding being older than 10 minutes, reducing unnecessary work.
- Refactor
- Consolidated embedding preprocessing and moved sanitization utilities
into shared models; upserts now refresh stored content.
- Tests
- Expanded snapshot-based tests covering multiple time/age scenarios for
embedding decision logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2025-08-12 09:45:41 +08:00
committed by GitHub
parent 125564b7d2
commit 65f679c4f0
10 changed files with 177 additions and 43 deletions
@@ -101,6 +101,28 @@ Generated by [AVA](https://avajs.dev).
0
## should check need to be embedded
> document with no embedding should need embedding
true
> document with recent embedding should not need embedding
false
> document updated after embedding and older-than-10m should need embedding
true
> should not need embedding when only 10-minute window passed without updates
false
> should need embedding when doc updated and last embedding older than 10 minutes
true
## should filter outdated doc id style in embedding status
> should include modern doc format
@@ -293,7 +293,10 @@ test('should check need to be embedded', async t => {
workspace.id,
docId
);
t.true(needsEmbedding, 'document with no embedding should need embedding');
t.snapshot(
needsEmbedding,
'document with no embedding should need embedding'
);
}
{
@@ -313,7 +316,7 @@ test('should check need to be embedded', async t => {
workspace.id,
docId
);
t.false(
t.snapshot(
needsEmbedding,
'document with recent embedding should not need embedding'
);
@@ -328,15 +331,83 @@ test('should check need to be embedded', async t => {
editorId: user.id,
});
// simulate an old embedding
const oldEmbeddingTime = new Date(Date.now() - 25 * 60 * 1000);
await t.context.db.aiWorkspaceEmbedding.updateMany({
where: { workspaceId: workspace.id, docId },
data: { updatedAt: oldEmbeddingTime },
});
let needsEmbedding = await t.context.copilotWorkspace.checkDocNeedEmbedded(
workspace.id,
docId
);
t.true(
t.snapshot(
needsEmbedding,
'document updated after embedding should need embedding'
'document updated after embedding and older-than-10m should need embedding'
);
}
{
// only time passed (>10m since last embedding) but no doc updates => should NOT re-embed
const baseNow = Date.now();
const docId2 = randomUUID();
const t0 = baseNow - 30 * 60 * 1000; // snapshot updated 30 minutes ago
const t1 = baseNow - 25 * 60 * 1000; // embedding updated 25 minutes ago
await t.context.doc.upsert({
spaceId: workspace.id,
docId: docId2,
blob: Uint8Array.from([1, 2, 3]),
timestamp: t0,
editorId: user.id,
});
await t.context.copilotContext.insertWorkspaceEmbedding(
workspace.id,
docId2,
[
{
index: 0,
content: 'content2',
embedding: Array.from({ length: 1024 }, () => 1),
},
]
);
await t.context.db.aiWorkspaceEmbedding.updateMany({
where: { workspaceId: workspace.id, docId: docId2 },
data: { updatedAt: new Date(t1) },
});
let needsEmbedding = await t.context.copilotWorkspace.checkDocNeedEmbedded(
workspace.id,
docId2
);
t.snapshot(
needsEmbedding,
'should not need embedding when only 10-minute window passed without updates'
);
const t2 = baseNow - 5 * 60 * 1000; // doc updated 5 minutes ago
await t.context.doc.upsert({
spaceId: workspace.id,
docId: docId2,
blob: Uint8Array.from([7, 8, 9]),
timestamp: t2,
editorId: user.id,
});
needsEmbedding = await t.context.copilotWorkspace.checkDocNeedEmbedded(
workspace.id,
docId2
);
t.snapshot(
needsEmbedding,
'should need embedding when doc updated and last embedding older than 10 minutes'
);
}
// --- new cases end ---
});
test('should check embedding table', async t => {