mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-23 20:18:42 +08:00
fix(server): fulfill empty embedding for trashed docs (#13461)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - None - Bug Fixes - Ensures a placeholder embedding is always created when content is empty or after deletion, reducing errors and improving Copilot stability. - Refactor - Centralized empty-embedding handling for consistent behavior across workflows. - Standardized embedding dimension configuration to a single source for reliability. - Chores - Simplified internal embedding module surface and imports for maintainability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -19,6 +19,8 @@ import {
|
|||||||
|
|
||||||
type UpdateCopilotContextInput = Pick<CopilotContext, 'config'>;
|
type UpdateCopilotContextInput = Pick<CopilotContext, 'config'>;
|
||||||
|
|
||||||
|
export const EMBEDDING_DIMENSIONS = 1024;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copilot Job Model
|
* Copilot Job Model
|
||||||
*/
|
*/
|
||||||
@@ -290,10 +292,24 @@ export class CopilotContextModel extends BaseModel {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fulfillEmptyEmbedding(workspaceId: string, docId: string) {
|
||||||
|
const emptyEmbedding = {
|
||||||
|
index: 0,
|
||||||
|
content: '',
|
||||||
|
embedding: Array.from({ length: EMBEDDING_DIMENSIONS }, () => 0),
|
||||||
|
};
|
||||||
|
await this.models.copilotContext.insertWorkspaceEmbedding(
|
||||||
|
workspaceId,
|
||||||
|
docId,
|
||||||
|
[emptyEmbedding]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async deleteWorkspaceEmbedding(workspaceId: string, docId: string) {
|
async deleteWorkspaceEmbedding(workspaceId: string, docId: string) {
|
||||||
await this.db.aiWorkspaceEmbedding.deleteMany({
|
await this.db.aiWorkspaceEmbedding.deleteMany({
|
||||||
where: { workspaceId, docId },
|
where: { workspaceId, docId },
|
||||||
});
|
});
|
||||||
|
await this.fulfillEmptyEmbedding(workspaceId, docId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async matchWorkspaceEmbedding(
|
async matchWorkspaceEmbedding(
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ import {
|
|||||||
CopilotProviderNotSupported,
|
CopilotProviderNotSupported,
|
||||||
} from '../../../base';
|
} from '../../../base';
|
||||||
import { CopilotFailedToGenerateEmbedding } from '../../../base/error/errors.gen';
|
import { CopilotFailedToGenerateEmbedding } from '../../../base/error/errors.gen';
|
||||||
import { ChunkSimilarity, Embedding } from '../../../models';
|
import {
|
||||||
|
ChunkSimilarity,
|
||||||
|
Embedding,
|
||||||
|
EMBEDDING_DIMENSIONS,
|
||||||
|
} from '../../../models';
|
||||||
import { PromptService } from '../prompt';
|
import { PromptService } from '../prompt';
|
||||||
import {
|
import {
|
||||||
type CopilotProvider,
|
type CopilotProvider,
|
||||||
@@ -16,11 +20,7 @@ import {
|
|||||||
ModelInputType,
|
ModelInputType,
|
||||||
ModelOutputType,
|
ModelOutputType,
|
||||||
} from '../providers';
|
} from '../providers';
|
||||||
import {
|
import { EmbeddingClient, type ReRankResult } from './types';
|
||||||
EMBEDDING_DIMENSIONS,
|
|
||||||
EmbeddingClient,
|
|
||||||
type ReRankResult,
|
|
||||||
} from './types';
|
|
||||||
|
|
||||||
const EMBEDDING_MODEL = 'gemini-embedding-001';
|
const EMBEDDING_MODEL = 'gemini-embedding-001';
|
||||||
const RERANK_PROMPT = 'Rerank results';
|
const RERANK_PROMPT = 'Rerank results';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export { getEmbeddingClient, MockEmbeddingClient } from './client';
|
export { getEmbeddingClient, MockEmbeddingClient } from './client';
|
||||||
export { CopilotEmbeddingJob } from './job';
|
export { CopilotEmbeddingJob } from './job';
|
||||||
export type { Chunk, DocFragment } from './types';
|
export type { Chunk, DocFragment } from './types';
|
||||||
export { EMBEDDING_DIMENSIONS, EmbeddingClient } from './types';
|
export { EmbeddingClient } from './types';
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { CopilotStorage } from '../storage';
|
|||||||
import { readStream } from '../utils';
|
import { readStream } from '../utils';
|
||||||
import { getEmbeddingClient } from './client';
|
import { getEmbeddingClient } from './client';
|
||||||
import type { Chunk, DocFragment } from './types';
|
import type { Chunk, DocFragment } from './types';
|
||||||
import { EMBEDDING_DIMENSIONS, EmbeddingClient } from './types';
|
import { EmbeddingClient } from './types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CopilotEmbeddingJob {
|
export class CopilotEmbeddingJob {
|
||||||
@@ -392,19 +392,6 @@ export class CopilotEmbeddingJob {
|
|||||||
return controller.signal;
|
return controller.signal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fulfillEmptyEmbedding(workspaceId: string, docId: string) {
|
|
||||||
const emptyEmbedding = {
|
|
||||||
index: 0,
|
|
||||||
content: '',
|
|
||||||
embedding: Array.from({ length: EMBEDDING_DIMENSIONS }, () => 0),
|
|
||||||
};
|
|
||||||
await this.models.copilotContext.insertWorkspaceEmbedding(
|
|
||||||
workspaceId,
|
|
||||||
docId,
|
|
||||||
[emptyEmbedding]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnJob('copilot.embedding.docs')
|
@OnJob('copilot.embedding.docs')
|
||||||
async embedPendingDocs({
|
async embedPendingDocs({
|
||||||
contextId,
|
contextId,
|
||||||
@@ -466,13 +453,19 @@ export class CopilotEmbeddingJob {
|
|||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Doc ${docId} in workspace ${workspaceId} has no summary, fulfilling empty embedding.`
|
`Doc ${docId} in workspace ${workspaceId} has no summary, fulfilling empty embedding.`
|
||||||
);
|
);
|
||||||
await this.fulfillEmptyEmbedding(workspaceId, docId);
|
await this.models.copilotContext.fulfillEmptyEmbedding(
|
||||||
|
workspaceId,
|
||||||
|
docId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Doc ${docId} in workspace ${workspaceId} has no fragment, fulfilling empty embedding.`
|
`Doc ${docId} in workspace ${workspaceId} has no fragment, fulfilling empty embedding.`
|
||||||
);
|
);
|
||||||
await this.fulfillEmptyEmbedding(workspaceId, docId);
|
await this.models.copilotContext.fulfillEmptyEmbedding(
|
||||||
|
workspaceId,
|
||||||
|
docId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -490,7 +483,10 @@ export class CopilotEmbeddingJob {
|
|||||||
`Doc ${docId} in workspace ${workspaceId} has no content, fulfilling empty embedding.`
|
`Doc ${docId} in workspace ${workspaceId} has no content, fulfilling empty embedding.`
|
||||||
);
|
);
|
||||||
// if the doc is empty, we still need to fulfill the embedding
|
// if the doc is empty, we still need to fulfill the embedding
|
||||||
await this.fulfillEmptyEmbedding(workspaceId, docId);
|
await this.models.copilotContext.fulfillEmptyEmbedding(
|
||||||
|
workspaceId,
|
||||||
|
docId
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,8 +98,6 @@ export type Chunk = {
|
|||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EMBEDDING_DIMENSIONS = 1024;
|
|
||||||
|
|
||||||
export abstract class EmbeddingClient {
|
export abstract class EmbeddingClient {
|
||||||
async configured() {
|
async configured() {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user