Commit bd9c8ebc by Archer Committed by GitHub

fix(dataset): rebuild vectors when embedding model changes (#7359)

* fix(dataset): rebuild vectors when embedding model changes

* docs: update v4.15.4 vector rebuild notes
parent fe755000
......@@ -11,3 +11,4 @@ description: 'FastGPT V4.15.4 Release Notes'
1. Fixed an issue where Chatbox displayed system tool errors during streaming responses.
2. Fixed an issue where plain-text tool responses in full run details could be incorrectly parsed as Markdown, causing formatting issues.
3. Fixed an issue where switching the embedding model triggered training but did not rebuild vectors for existing data.
......@@ -11,3 +11,4 @@ description: 'FastGPT V4.15.4 更新说明'
1. chatbox 流输出时候,不应该展示系统工具的错误。
2. 完整运行详情,纯文本的工具响应 UI 可能会被 Markdown 错误解析,格式错乱。
3. 切换向量模型后,训练任务会触发但已有数据的向量未重建。
......@@ -324,6 +324,8 @@
"content/self-host/upgrading/4-15/4152.mdx": "2026-07-18T12:51:59+08:00",
"content/self-host/upgrading/4-15/4153.en.mdx": "2026-07-18T12:51:59+08:00",
"content/self-host/upgrading/4-15/4153.mdx": "2026-07-18T12:51:59+08:00",
"content/self-host/upgrading/4-15/4154.en.mdx": "2026-07-21T13:14:50+08:00",
"content/self-host/upgrading/4-15/4154.mdx": "2026-07-21T13:14:50+08:00",
"content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00",
......@@ -464,6 +466,6 @@
"content/self-host/upgrading/outdated/499.mdx": "2026-05-07T15:06:40+08:00",
"content/self-host/upgrading/upgrade-intruction.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/upgrade-intruction.mdx": "2026-04-26T21:08:47+08:00",
"content/toc.en.mdx": "2026-07-18T12:51:59+08:00",
"content/toc.mdx": "2026-07-18T12:51:59+08:00"
"content/toc.en.mdx": "2026-07-21T13:14:50+08:00",
"content/toc.mdx": "2026-07-21T13:14:50+08:00"
}
\ No newline at end of file
......@@ -26,11 +26,13 @@ type UpdateDatasetDataByIndexesProps = Omit<UpdateDatasetDataPropsType, 'indexes
model: string;
indexSize?: number;
imageIndex?: boolean;
/** 重建索引时忽略文本相同判断,确保切换 embedding model 后重新生成向量。 */
forceRebuild?: boolean;
};
type UpdateDatasetDataSystemIndexesProps = Omit<
UpdateDatasetDataByIndexesProps,
'indexes' | 'q'
'indexes' | 'q' | 'forceRebuild'
> & {
q?: string;
imageIndex?: boolean;
......@@ -218,7 +220,8 @@ export class DatasetDataOperation {
model,
indexSize = 512,
indexPrefix,
imageIndex
imageIndex,
forceRebuild = false
}: UpdateDatasetDataByIndexesProps) {
const embModel = getEmbeddingModel(model);
......@@ -256,8 +259,11 @@ export class DatasetDataOperation {
// patchResult 先保留旧 dataId;insertVectorForPatch 会为 create/update 项写入新向量并回填新 dataId。
const patchResult = this.indexOperation.buildPatch({
currentIndexes: mongoData.indexes,
nextIndexes: indexesWithExistingSystemIds
nextIndexes: indexesWithExistingSystemIds,
isSameIndex: forceRebuild ? () => false : undefined
});
// 先保存旧向量 id;insertVectorForPatch 会原地把 update 项替换成新 dataId。
const deleteVectorIdList = this.indexOperation.getDeleteVectorIdList(patchResult);
// 提前刷新 updateTime,方便 job 扫到该 data 进行处理。
const updateTime = mongoData.updateTime;
......@@ -272,7 +278,6 @@ export class DatasetDataOperation {
});
const newIndexes = this.indexOperation.getWritablePatchIndexes(patchResult);
const deleteVectorIdList = this.indexOperation.getDeleteVectorIdList(patchResult);
await mongoSessionRun(async (session) => {
// 仅在 Q/A 变化时记录历史,最多保留最近 10 条旧内容。
......
......@@ -325,7 +325,8 @@ const rebuildData = async ({ trainingData }: { trainingData: TrainingDataType })
indexSize: trainingData.indexSize || getMaxIndexSize(embModel),
indexPrefix: trainingData.collection.indexPrefixTitle
? `# ${trainingData.collection.name}`
: undefined
: undefined,
forceRebuild: true
});
await mongoSessionRun(async (session) => {
......
......@@ -360,6 +360,74 @@ describe('Dataset data service', () => {
);
});
it('should rebuild vectors when forceRebuild is enabled even if index text is unchanged', async () => {
const { data } = await createMongoData({
q: 'same question',
a: 'same answer',
indexes: [
{
type: DatasetDataIndexTypeEnum.custom,
text: 'same custom index',
dataId: 'custom_old'
},
{
type: DatasetDataIndexTypeEnum.default,
text: 'same question',
dataId: 'question_old'
},
{
type: DatasetDataIndexTypeEnum.default,
text: 'same answer',
dataId: 'answer_old'
}
]
});
const result = await updateDatasetDataByIndexes({
dataId: String(data._id),
q: 'same question',
a: 'same answer',
indexes: [
{
type: DatasetDataIndexTypeEnum.custom,
text: 'same custom index',
dataId: 'custom_old'
}
],
model: 'text-embedding-3-small',
indexSize: 50,
forceRebuild: true
});
const updatedData = await MongoDatasetData.findById(data._id).lean();
const deleteCall = mockVectorDelete.mock.calls[0]?.[0];
expect(result.tokens).toBeGreaterThan(0);
expect(mockVectorInsert).toHaveBeenCalledTimes(1);
expect(updatedData?.indexes).toEqual(
expect.arrayContaining([
expect.objectContaining({
type: DatasetDataIndexTypeEnum.custom,
text: 'same custom index',
dataId: 'id_1'
}),
expect.objectContaining({
type: DatasetDataIndexTypeEnum.default,
text: 'same question',
dataId: 'id_2'
}),
expect.objectContaining({
type: DatasetDataIndexTypeEnum.default,
text: 'same answer',
dataId: 'id_3'
})
])
);
expect(deleteCall?.idList).toEqual(
expect.arrayContaining(['custom_old', 'question_old', 'answer_old'])
);
});
it('should reject invalid update-by-indexes requests', async () => {
const { data } = await createMongoData();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment