From cd3acb44ab7dcde75114b0aa311d02620d126531 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Wed, 15 Nov 2023 21:35:50 +0800 Subject: [PATCH] v4.6-4 (#473) --- docSite/assets/imgs/datasetSetting1.png | Bin 0 -> 55584 bytes .../content/docs/installation/upgrading/46.md | 5 +- docSite/content/docs/pricing.md | 8 +- .../content/docs/use-cases/datasetEngine.md | 20 ++-- packages/global/common/string/textSplitter.ts | 4 +- packages/global/core/dataset/type.d.ts | 6 +- packages/global/support/wallet/bill/api.d.ts | 2 + packages/service/core/app/schema.ts | 1 - .../service/core/dataset/collection/schema.ts | 1 - packages/service/core/dataset/schema.ts | 5 + .../service/core/dataset/training/schema.ts | 2 +- projects/app/public/locales/en/common.json | 2 + projects/app/public/locales/zh/common.json | 2 + projects/app/src/constants/dataset.ts | 13 +-- .../app/src/global/core/api/datasetReq.d.ts | 2 + projects/app/src/global/core/dataset/api.d.ts | 1 + projects/app/src/global/core/prompt/agent.ts | 6 +- projects/app/src/pages/api/admin/initv46-2.ts | 16 ++- projects/app/src/pages/api/admin/initv46-3.ts | 101 ++++++++++++++++++ .../src/pages/api/core/dataset/allDataset.ts | 3 +- .../api/core/dataset/checkExportLimit.ts | 73 +++++++++++++ .../app/src/pages/api/core/dataset/create.ts | 4 +- .../pages/api/core/dataset/data/pushData.ts | 71 ++++++++---- .../app/src/pages/api/core/dataset/detail.ts | 3 +- .../src/pages/api/core/dataset/exportAll.ts | 78 ++++---------- .../app/src/pages/api/core/dataset/list.ts | 3 +- .../app/src/pages/api/core/dataset/update.ts | 6 +- .../support/wallet/bill/createTrainingBill.ts | 10 +- .../dataset/detail/components/DataCard.tsx | 2 +- .../detail/components/Import/ImportModal.tsx | 20 ++-- .../detail/components/Import/Provider.tsx | 8 +- .../dataset/detail/components/Import/QA.tsx | 4 +- .../pages/dataset/detail/components/Info.tsx | 32 +++++- .../detail/components/InputDataModal.tsx | 6 +- .../dataset/list/component/CreateModal.tsx | 28 ++++- projects/app/src/pages/dataset/list/index.tsx | 28 +++-- projects/app/src/service/events/generateQA.ts | 16 ++- .../src/service/support/wallet/bill/push.ts | 14 ++- projects/app/src/web/core/dataset/api.ts | 3 + 39 files changed, 453 insertions(+), 156 deletions(-) create mode 100644 docSite/assets/imgs/datasetSetting1.png create mode 100644 projects/app/src/pages/api/admin/initv46-3.ts create mode 100644 projects/app/src/pages/api/core/dataset/checkExportLimit.ts diff --git a/docSite/assets/imgs/datasetSetting1.png b/docSite/assets/imgs/datasetSetting1.png new file mode 100644 index 0000000000000000000000000000000000000000..81c2d8b49ab133f4d75086b20dd420d77e901ddc GIT binary patch literal 55584 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 WBz~pGBln5`{+(6PRxVVu3I1Pmq!}Cl literal 0 HcmV?d00001 diff --git a/docSite/content/docs/installation/upgrading/46.md b/docSite/content/docs/installation/upgrading/46.md index 4ebc6fceebb..406c5e5c817 100644 --- a/docSite/content/docs/installation/upgrading/46.md +++ b/docSite/content/docs/installation/upgrading/46.md @@ -50,5 +50,6 @@ curl --location --request POST 'https://{{host}}/api/admin/initv46-2' \ 1. 新增 - 团队空间 2. 新增 - 多路向量(多个向量映射一组数据) 3. 新增 - tts语音 -4. 线上环境新增 - ReRank向量召回,提高召回精度 -5. 优化 - 知识库导出,可直接触发流下载,无需等待转圈圈 +4. 新增 - 支持知识库配置文本预处理模型 +5. 线上环境新增 - ReRank向量召回,提高召回精度 +6. 优化 - 知识库导出,可直接触发流下载,无需等待转圈圈 diff --git a/docSite/content/docs/pricing.md b/docSite/content/docs/pricing.md index 444db949e65..75fe5eb29cb 100644 --- a/docSite/content/docs/pricing.md +++ b/docSite/content/docs/pricing.md @@ -1,10 +1,10 @@ --- -title: '定价' -description: 'FastGPT 的定价' +title: '线上版定价' +description: 'FastGPT 线上版定价' icon: 'currency_yen' draft: false toc: true -weight: 10 +weight: 11 --- ## Tokens 说明 @@ -15,7 +15,7 @@ weight: 10 ## FastGPT 线上计费 -目前,FastGPT 线上计费也仅按 Tokens 使用数量为准。以下是详细的计费表(最新定价以线上表格为准,可在点击充值后实时获取): +使用: [https://fastgpt.run](https://fastgpt.run) 或 [https://ai.fastgpt.in](https://ai.fastgpt.in) 只需仅按 Tokens 使用数量扣费即可。可在 账号-使用记录 中查看具体使用情况,以下是详细的计费表(最新定价以线上表格为准,可在点击充值后实时获取): {{< table "table-hover table-striped-columns" >}} | 计费项 | 价格: 元/ 1K tokens(包含上下文) | diff --git a/docSite/content/docs/use-cases/datasetEngine.md b/docSite/content/docs/use-cases/datasetEngine.md index 5e643a42b41..48e9d6c5e89 100644 --- a/docSite/content/docs/use-cases/datasetEngine.md +++ b/docSite/content/docs/use-cases/datasetEngine.md @@ -1,6 +1,6 @@ --- title: "知识库结构讲解" -description: "本节会介绍 FastGPT 知识库结构设计,理解其 QA 的存储格式和检索格式,以便更好的构建知识库。这篇介绍主要以使用为主,详细原理不多介绍。" +description: "本节会详细介绍 FastGPT 知识库结构设计,理解其 QA 的存储格式和多向量映射,以便更好的构建知识库。这篇介绍主要以使用为主,详细原理不多介绍。" icon: "dataset" draft: false toc: true @@ -25,13 +25,21 @@ FastGPT 采用了 RAG 中的 Embedding 方案构建知识库,要使用好 Fast FastGPT 采用了 `PostgresSQL` 的 `PG Vector` 插件作为向量检索器,索引为`HNSW`。且`PostgresSQL`仅用于向量检索,`MongoDB`用于其他数据的存取。 -在`PostgresSQL`的表中,设置一个 `index` 字段用于存储向量、一个 `q` 字段用于存储向量对应的内容,以及一个 `a` 字段用于检索映射。之所以取字段为 `qa` 是由于一些历史缘故,无需完全解为 “问答对” 的格式。在实际使用过程中,可以利用`q`和`a`的组合,对检索后的内容做进一步的声明,提高大模型的理解力(注意,这里不直接提高搜索精度)。 +在`PostgresSQL`的表中,设置一个 `index` 字段用于存储向量,以及一个`data_id`用于在`MongoDB`中寻找对应的映射值。多个`index`可以对应一组`data_id`,也就是说,一组向量可以对应多组数据。在进行检索时,相同数据会进行合并。 -目前,提高向量搜索的精度,主要可以通过几种途径: +![](/imgs/datasetSetting1.png) -1. 精简`q`的内容,减少向量内容的长度:当`q`的内容更少,更准确时,检索精度自然会提高。但与此同时,会牺牲一定的检索范围,适合答案较为严格的场景。 -2. 更好分词分段:当一段话的结构和语义是完整的,并且是单一的,精度也会提高。因此,许多系统都会优化分词器,尽可能的保障每组数据的完整性。 -3. 多样性文本:为一段内容增加关键词、摘要、相似问题等描述性信息,可以使得该内容的向量具有更大的检索覆盖范围。 +## 多向量的目的和使用方式 + +在一组数据中,如果我们希望它尽可能长,但语义又要在向量中尽可能提现,则没有办法通过一组向量来表示。因此,我们采用了多向量映射的方式,将一组数据映射到多组向量中,从而保障数据的完整性和语义的提现。 + +你可以为一组较长的文本,添加多组向量,从而在检索时,只要其中一组向量被检索到,该数据也将被召回。 + +## 提高向量搜索精度的方法 + +1. 更好分词分段:当一段话的结构和语义是完整的,并且是单一的,精度也会提高。因此,许多系统都会优化分词器,尽可能的保障每组数据的完整性。 +2. 精简`index`的内容,减少向量内容的长度:当`index`的内容更少,更准确时,检索精度自然会提高。但与此同时,会牺牲一定的检索范围,适合答案较为严格的场景。 +3. 丰富`index`的数量,可以为同一个`chunk`内容增加多组`index`。 4. 优化检索词:在实际使用过程中,用户的问题通常是模糊的或是缺失的,并不一定是完整清晰的问题。因此优化用户的问题(检索词)很大程度上也可以提高精度。 5. 微调向量模型:由于市面上直接使用的向量模型都是通用型模型,在特定领域的检索精度并不高,因此微调向量模型可以很大程度上提高专业领域的检索效果。 diff --git a/packages/global/common/string/textSplitter.ts b/packages/global/common/string/textSplitter.ts index 5737347f281..ea9e6de51ef 100644 --- a/packages/global/common/string/textSplitter.ts +++ b/packages/global/common/string/textSplitter.ts @@ -63,8 +63,8 @@ export const splitText2Chunks = (props: { text: string; maxLen: number; overlapL let chunks: string[] = []; for (let i = 0; i < splitTexts.length; i++) { let text = splitTexts[i]; - let chunkToken = countPromptTokens(lastChunk, ''); - const textToken = countPromptTokens(text, ''); + let chunkToken = lastChunk.length; + const textToken = text.length; // next chunk is too large / new chunk is too large(The current chunk must be smaller than maxLen) if (textToken >= maxLen || chunkToken + textToken > maxLen * 1.4) { diff --git a/packages/global/core/dataset/type.d.ts b/packages/global/core/dataset/type.d.ts index a1a7700da7e..851e1e32616 100644 --- a/packages/global/core/dataset/type.d.ts +++ b/packages/global/core/dataset/type.d.ts @@ -1,4 +1,4 @@ -import type { VectorModelItemType } from '../../core/ai/model.d'; +import type { LLMModelItemType, VectorModelItemType } from '../../core/ai/model.d'; import { PermissionTypeEnum } from '../../support/permission/constant'; import { PushDatasetDataChunkProps } from './api'; import { @@ -19,6 +19,7 @@ export type DatasetSchemaType = { avatar: string; name: string; vectorModel: string; + agentModel: string; tags: string[]; type: `${DatasetTypeEnum}`; permission: `${PermissionTypeEnum}`; @@ -84,8 +85,9 @@ export type CollectionWithDatasetType = Omit & { +export type DatasetItemType = Omit & { vectorModel: VectorModelItemType; + agentModel: LLMModelItemType; isOwner: boolean; canWrite: boolean; }; diff --git a/packages/global/support/wallet/bill/api.d.ts b/packages/global/support/wallet/bill/api.d.ts index f24c6d9c653..fbd20025d3b 100644 --- a/packages/global/support/wallet/bill/api.d.ts +++ b/packages/global/support/wallet/bill/api.d.ts @@ -3,6 +3,8 @@ import { BillListItemType } from './type'; export type CreateTrainingBillProps = { name: string; + vectorModel?: string; + agentModel?: string; }; export type ConcatBillProps = { diff --git a/packages/service/core/app/schema.ts b/packages/service/core/app/schema.ts index dd32113fc26..356f1412cac 100644 --- a/packages/service/core/app/schema.ts +++ b/packages/service/core/app/schema.ts @@ -61,7 +61,6 @@ const AppSchema = new Schema({ try { AppSchema.index({ updateTime: -1 }); - AppSchema.index({ 'share.collection': -1 }); } catch (error) { console.log(error); } diff --git a/packages/service/core/dataset/collection/schema.ts b/packages/service/core/dataset/collection/schema.ts index 575cf851eb1..352276f2413 100644 --- a/packages/service/core/dataset/collection/schema.ts +++ b/packages/service/core/dataset/collection/schema.ts @@ -69,7 +69,6 @@ const DatasetCollectionSchema = new Schema({ try { DatasetCollectionSchema.index({ datasetId: 1 }); - DatasetCollectionSchema.index({ userId: 1 }); DatasetCollectionSchema.index({ updateTime: -1 }); } catch (error) { console.log(error); diff --git a/packages/service/core/dataset/schema.ts b/packages/service/core/dataset/schema.ts index 93789682938..6f61eb90a63 100644 --- a/packages/service/core/dataset/schema.ts +++ b/packages/service/core/dataset/schema.ts @@ -48,6 +48,11 @@ const DatasetSchema = new Schema({ required: true, default: 'text-embedding-ada-002' }, + agentModel: { + type: String, + required: true, + default: 'gpt-3.5-turbo-16k' + }, type: { type: String, enum: Object.keys(DatasetTypeMap), diff --git a/packages/service/core/dataset/training/schema.ts b/packages/service/core/dataset/training/schema.ts index d521f1dc88d..b7ce90969f7 100644 --- a/packages/service/core/dataset/training/schema.ts +++ b/packages/service/core/dataset/training/schema.ts @@ -95,7 +95,7 @@ const TrainingDataSchema = new Schema({ try { TrainingDataSchema.index({ lockTime: 1 }); - TrainingDataSchema.index({ userId: 1 }); + TrainingDataSchema.index({ datasetId: 1 }); TrainingDataSchema.index({ collectionId: 1 }); TrainingDataSchema.index({ expireAt: 1 }, { expireAfterSeconds: 7 * 24 * 60 }); } catch (error) { diff --git a/projects/app/public/locales/en/common.json b/projects/app/public/locales/en/common.json index 4808b7cbbf3..bdb957cf009 100644 --- a/projects/app/public/locales/en/common.json +++ b/projects/app/public/locales/en/common.json @@ -250,6 +250,7 @@ } }, "dataset": { + "Agent Model": "Learning Model", "Chunk Length": "Chunk Length", "Confirm move the folder": "Confirm Move", "Confirm to delete the data": "Confirm to delete the data?", @@ -259,6 +260,7 @@ "Delete Dataset Error": "Delete dataset failed", "Edit Folder": "Edit Folder", "Export": "Export", + "Export Dataset Limit Error": "Export Data Error", "File Input": "Import File", "File Size": "File Size", "Filename": "Filename", diff --git a/projects/app/public/locales/zh/common.json b/projects/app/public/locales/zh/common.json index 8053b1cc8ce..cd6ceaa425c 100644 --- a/projects/app/public/locales/zh/common.json +++ b/projects/app/public/locales/zh/common.json @@ -250,6 +250,7 @@ } }, "dataset": { + "Agent Model": "文件处理模型", "Chunk Length": "数据总量", "Confirm move the folder": "确认移动到该目录", "Confirm to delete the data": "确认删除该数据?", @@ -259,6 +260,7 @@ "Delete Dataset Error": "删除知识库异常", "Edit Folder": "编辑文件夹", "Export": "导出", + "Export Dataset Limit Error": "导出数据失败", "File Input": "文件导入", "File Size": "文件大小", "Filename": "文件名", diff --git a/projects/app/src/constants/dataset.ts b/projects/app/src/constants/dataset.ts index aff02993521..1303e4c425a 100644 --- a/projects/app/src/constants/dataset.ts +++ b/projects/app/src/constants/dataset.ts @@ -1,3 +1,4 @@ +import { defaultQAModels, defaultVectorModels } from '@fastgpt/global/core/ai/model'; import type { DatasetCollectionItemType, DatasetItemType @@ -17,13 +18,8 @@ export const defaultDatasetDetail: DatasetItemType = { permission: 'private', isOwner: false, canWrite: false, - vectorModel: { - model: 'text-embedding-ada-002', - name: 'Embedding-2', - price: 0.2, - defaultToken: 500, - maxToken: 3000 - } + vectorModel: defaultVectorModels[0], + agentModel: defaultQAModels[0] }; export const defaultCollectionDetail: DatasetCollectionItemType = { @@ -43,7 +39,8 @@ export const defaultCollectionDetail: DatasetCollectionItemType = { name: '', tags: [], permission: 'private', - vectorModel: 'text-embedding-ada-002' + vectorModel: defaultVectorModels[0].model, + agentModel: defaultQAModels[0].model }, parentId: '', name: '', diff --git a/projects/app/src/global/core/api/datasetReq.d.ts b/projects/app/src/global/core/api/datasetReq.d.ts index 7a03d5f612e..9a7d0765375 100644 --- a/projects/app/src/global/core/api/datasetReq.d.ts +++ b/projects/app/src/global/core/api/datasetReq.d.ts @@ -5,6 +5,7 @@ import type { SearchTestItemType } from '@/types/core/dataset'; import { UploadChunkItemType } from '@fastgpt/global/core/dataset/type'; import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type'; import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; +import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; /* ===== dataset ===== */ export type DatasetUpdateParams = { @@ -14,6 +15,7 @@ export type DatasetUpdateParams = { name?: string; avatar?: string; permission?: `${PermissionTypeEnum}`; + agentModel?: LLMModelItemType; }; export type SearchTestProps = { diff --git a/projects/app/src/global/core/dataset/api.d.ts b/projects/app/src/global/core/dataset/api.d.ts index dae6f72f917..a888c500a9d 100644 --- a/projects/app/src/global/core/dataset/api.d.ts +++ b/projects/app/src/global/core/dataset/api.d.ts @@ -9,6 +9,7 @@ export type CreateDatasetParams = { tags: string; avatar: string; vectorModel?: string; + agentModel?: string; type: `${DatasetTypeEnum}`; }; diff --git a/projects/app/src/global/core/prompt/agent.ts b/projects/app/src/global/core/prompt/agent.ts index e6edde2f075..d21793ca488 100644 --- a/projects/app/src/global/core/prompt/agent.ts +++ b/projects/app/src/global/core/prompt/agent.ts @@ -1,8 +1,8 @@ export const Prompt_AgentQA = { prompt: `我会给你一段文本,{{theme}},学习它们,并整理学习成果,要求为: -1. 提出最多 25 个问题。 -2. 给出每个问题的答案。 -3. 答案要详细完整,答案可以包含普通文字、链接、代码、表格、公示、媒体链接等 markdown 元素。 +1. 提出问题并给出每个问题的答案。 +2. 每个答案都要详细完整,给出相关原文描述,答案可以包含普通文字、链接、代码、表格、公示、媒体链接等 markdown 元素。 +3. 最多提出 30 个问题。 4. 按格式返回多个问题和答案: Q1: 问题。 diff --git a/projects/app/src/pages/api/admin/initv46-2.ts b/projects/app/src/pages/api/admin/initv46-2.ts index c7c7a72646f..ff1ad799a37 100644 --- a/projects/app/src/pages/api/admin/initv46-2.ts +++ b/projects/app/src/pages/api/admin/initv46-2.ts @@ -11,6 +11,8 @@ import { import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { getUserDefaultTeam } from '@fastgpt/service/support/user/team/controller'; +import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; +import { defaultQAModels } from '@fastgpt/global/core/ai/model'; let success = 0; /* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */ @@ -41,6 +43,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) await initPgData(); + await MongoDataset.updateMany( + {}, + { + agentModel: defaultQAModels[0].model + } + ); + jsonRes(res, { data: await init(limit), message: @@ -76,14 +85,19 @@ async function initPgData() { for (let i = 0; i < limit; i++) { init(i); } + async function init(index: number): Promise { const userId = rows[index]?.user_id; if (!userId) return; try { const tmb = await getUserDefaultTeam({ userId }); + console.log(tmb); + // update pg await PgClient.query( - `Update ${PgDatasetTableName} set team_id = '${tmb.teamId}', tmb_id = '${tmb.tmbId}' where user_id = '${userId}' AND team_id='null';` + `Update ${PgDatasetTableName} set team_id = '${String(tmb.teamId)}', tmb_id = '${String( + tmb.tmbId + )}' where user_id = '${userId}' AND team_id='null';` ); console.log(++success); init(index + limit); diff --git a/projects/app/src/pages/api/admin/initv46-3.ts b/projects/app/src/pages/api/admin/initv46-3.ts new file mode 100644 index 00000000000..a564e069b0e --- /dev/null +++ b/projects/app/src/pages/api/admin/initv46-3.ts @@ -0,0 +1,101 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { delay } from '@/utils/tools'; +import { PgClient } from '@fastgpt/service/common/pg'; +import { + DatasetDataIndexTypeEnum, + PgDatasetTableName +} from '@fastgpt/global/core/dataset/constant'; + +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; + +let success = 0; +/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */ +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const { limit = 50 } = req.body as { limit: number }; + await authCert({ req, authRoot: true }); + await connectToDatabase(); + success = 0; + + jsonRes(res, { + data: await init(limit) + }); + } catch (error) { + console.log(error); + + jsonRes(res, { + code: 500, + error + }); + } +} + +type PgItemType = { + id: string; + q: string; + a: string; + dataset_id: string; + collection_id: string; + data_id: string; +}; + +async function init(limit: number): Promise { + const { rows: idList } = await PgClient.query<{ id: string }>( + `SELECT id FROM ${PgDatasetTableName} WHERE inited=1` + ); + + console.log('totalCount', idList.length); + + await delay(2000); + + if (idList.length === 0) return; + + for (let i = 0; i < limit; i++) { + initData(i); + } + + async function initData(index: number): Promise { + const dataId = idList[index]?.id; + if (!dataId) { + console.log('done'); + return; + } + // get limit data where data_id is null + const { rows } = await PgClient.query( + `SELECT id,q,a,dataset_id,collection_id,data_id FROM ${PgDatasetTableName} WHERE id=${dataId};` + ); + const data = rows[0]; + if (!data) { + console.log('done'); + return; + } + + try { + // update mongo data and update inited + await MongoDatasetData.findByIdAndUpdate(data.data_id, { + q: data.q, + a: data.a, + indexes: [ + { + defaultIndex: !data.a, + type: data.a ? DatasetDataIndexTypeEnum.qa : DatasetDataIndexTypeEnum.chunk, + dataId: data.id, + text: data.q + } + ] + }); + // update pg data_id + await PgClient.query(`UPDATE ${PgDatasetTableName} SET inited=0 WHERE id=${dataId};`); + + return initData(index + limit); + } catch (error) { + console.log(error); + console.log(data); + await delay(500); + return initData(index); + } + } +} diff --git a/projects/app/src/pages/api/core/dataset/allDataset.ts b/projects/app/src/pages/api/core/dataset/allDataset.ts index ab3b5ed2fc3..30e1861d191 100644 --- a/projects/app/src/pages/api/core/dataset/allDataset.ts +++ b/projects/app/src/pages/api/core/dataset/allDataset.ts @@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; -import { getVectorModel } from '@/service/core/ai/model'; +import { getQAModel, getVectorModel } from '@/service/core/ai/model'; import type { DatasetItemType } from '@fastgpt/global/core/dataset/type.d'; import { mongoRPermission } from '@fastgpt/global/support/permission/utils'; import { authUserRole } from '@fastgpt/service/support/permission/auth/user'; @@ -22,6 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const data = datasets.map((item) => ({ ...item.toJSON(), vectorModel: getVectorModel(item.vectorModel), + agentModel: getQAModel(item.agentModel), canWrite: String(item.tmbId) === tmbId, isOwner: teamOwner || String(item.tmbId) === tmbId })); diff --git a/projects/app/src/pages/api/core/dataset/checkExportLimit.ts b/projects/app/src/pages/api/core/dataset/checkExportLimit.ts new file mode 100644 index 00000000000..0ec2e4d1f3b --- /dev/null +++ b/projects/app/src/pages/api/core/dataset/checkExportLimit.ts @@ -0,0 +1,73 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoUser } from '@fastgpt/service/support/user/schema'; +import { addLog } from '@fastgpt/service/common/mongo/controller'; +import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; +import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; +import { findDatasetIdTreeByTopDatasetId } from '@fastgpt/service/core/dataset/controller'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + await connectToDatabase(); + let { datasetId } = req.query as { + datasetId: string; + }; + + if (!datasetId) { + throw new Error('缺少参数'); + } + + // 凭证校验 + const { userId } = await authDataset({ req, authToken: true, datasetId, per: 'w' }); + + await limitCheck({ + datasetId, + userId + }); + + jsonRes(res); + } catch (err) { + res.status(500); + jsonRes(res, { + code: 500, + error: err + }); + } +} + +export async function limitCheck({ datasetId, userId }: { datasetId: string; userId: string }) { + const exportIds = await findDatasetIdTreeByTopDatasetId(datasetId); + + const limitMinutesAgo = new Date( + Date.now() - (global.feConfigs?.limit?.exportLimitMinutes || 0) * 60 * 1000 + ); + + // auth export times + const authTimes = await MongoUser.findOne( + { + _id: userId, + $or: [ + { 'limit.exportKbTime': { $exists: false } }, + { 'limit.exportKbTime': { $lte: limitMinutesAgo } } + ] + }, + '_id limit' + ); + + if (!authTimes) { + const minutes = `${global.feConfigs?.limit?.exportLimitMinutes || 0} 分钟`; + return Promise.reject(`上次导出未到 ${minutes},每 ${minutes}仅可导出一次。`); + } + + // auth max data + const total = await MongoDatasetData.countDocuments({ + datasetId: { $in: exportIds } + }); + + addLog.info(`export datasets: ${datasetId}`, { total }); + + if (total > 100000) { + return Promise.reject('数据量超出 10 万,无法导出'); + } +} diff --git a/projects/app/src/pages/api/core/dataset/create.ts b/projects/app/src/pages/api/core/dataset/create.ts index 86ad28d0618..30547e1ce73 100644 --- a/projects/app/src/pages/api/core/dataset/create.ts +++ b/projects/app/src/pages/api/core/dataset/create.ts @@ -9,7 +9,8 @@ import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/use export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { name, tags, avatar, vectorModel, parentId, type } = req.body as CreateDatasetParams; + const { name, tags, avatar, vectorModel, agentModel, parentId, type } = + req.body as CreateDatasetParams; // 凭证校验 const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true }); @@ -20,6 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< tmbId, tags, vectorModel, + agentModel, avatar, parentId: parentId || null, type diff --git a/projects/app/src/pages/api/core/dataset/data/pushData.ts b/projects/app/src/pages/api/core/dataset/data/pushData.ts index 996d0c2ac3d..7ed069bff53 100644 --- a/projects/app/src/pages/api/core/dataset/data/pushData.ts +++ b/projects/app/src/pages/api/core/dataset/data/pushData.ts @@ -10,7 +10,7 @@ import { countPromptTokens } from '@fastgpt/global/common/string/tiktoken'; import type { PushDataResponse } from '@/global/core/api/datasetRes.d'; import type { PushDatasetDataProps } from '@/global/core/dataset/api.d'; import { PushDatasetDataChunkProps } from '@fastgpt/global/core/dataset/api'; -import { getVectorModel } from '@/service/core/ai/model'; +import { getQAModel, getVectorModel } from '@/service/core/ai/model'; import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset'; import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller'; @@ -63,24 +63,14 @@ export async function pushDataToDatasetCollection({ mode, prompt, billId -}: { teamId: string; tmbId: string } & PushDatasetDataProps): Promise { - // get dataset vector model - const { - datasetId: { _id: datasetId, vectorModel } - } = await getCollectionWithDataset(collectionId); - - const vectorModelData = getVectorModel(vectorModel); - - const modeMap = { - [TrainingModeEnum.chunk]: { - maxToken: vectorModelData.maxToken * 1.5, - model: vectorModelData.model - }, - [TrainingModeEnum.qa]: { - maxToken: global.qaModels[0].maxContext * 0.8, - model: global.qaModels[0].model - } - }; +}: { + teamId: string; + tmbId: string; +} & PushDatasetDataProps): Promise { + const { datasetId, model, maxToken } = await checkModelValid({ + mode, + collectionId + }); // filter repeat or equal content const set = new Set(); @@ -102,12 +92,13 @@ export async function pushDataToDatasetCollection({ // count q token const token = countPromptTokens(item.q); - if (token > modeMap[mode].maxToken) { + if (token > maxToken) { filterResult.overToken.push(item); return; } if (set.has(text)) { + console.log('repeat', item); filterResult.repeat.push(item); } else { filterResult.success.push(item); @@ -126,7 +117,7 @@ export async function pushDataToDatasetCollection({ billId, mode, prompt, - model: modeMap[mode].model, + model, q: item.q, a: item.a, indexes: item.indexes @@ -142,6 +133,44 @@ export async function pushDataToDatasetCollection({ }; } +export async function checkModelValid({ + mode, + collectionId +}: { + mode: `${TrainingModeEnum}`; + collectionId: string; +}) { + const { + datasetId: { _id: datasetId, vectorModel, agentModel } + } = await getCollectionWithDataset(collectionId); + + if (mode === TrainingModeEnum.chunk) { + if (!collectionId) return Promise.reject(`CollectionId is empty`); + const vectorModelData = getVectorModel(vectorModel); + if (!vectorModelData) { + return Promise.reject(`Model ${vectorModel} is inValid`); + } + return { + datasetId, + maxToken: vectorModelData.maxToken * 1.5, + model: vectorModelData.model + }; + } + + if (mode === TrainingModeEnum.qa) { + const qaModelData = getQAModel(agentModel); + if (!qaModelData) { + return Promise.reject(`Model ${agentModel} is inValid`); + } + return { + datasetId, + maxToken: qaModelData.maxContext * 0.8, + model: qaModelData.model + }; + } + return Promise.reject(`Mode ${mode} is inValid`); +} + export const config = { api: { bodyParser: { diff --git a/projects/app/src/pages/api/core/dataset/detail.ts b/projects/app/src/pages/api/core/dataset/detail.ts index a9a65b1431c..399a887c02d 100644 --- a/projects/app/src/pages/api/core/dataset/detail.ts +++ b/projects/app/src/pages/api/core/dataset/detail.ts @@ -1,7 +1,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { getVectorModel } from '@/service/core/ai/model'; +import { getQAModel, getVectorModel } from '@/service/core/ai/model'; import type { DatasetItemType } from '@fastgpt/global/core/dataset/type.d'; import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; @@ -28,6 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< data: { ...dataset, vectorModel: getVectorModel(dataset.vectorModel), + agentModel: getQAModel(dataset.agentModel), canWrite, isOwner } diff --git a/projects/app/src/pages/api/core/dataset/exportAll.ts b/projects/app/src/pages/api/core/dataset/exportAll.ts index ebdd2ae2c8c..e05ddc8a47e 100644 --- a/projects/app/src/pages/api/core/dataset/exportAll.ts +++ b/projects/app/src/pages/api/core/dataset/exportAll.ts @@ -1,5 +1,5 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; +import { jsonRes, responseWriteController } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoUser } from '@fastgpt/service/support/user/schema'; import { addLog } from '@fastgpt/service/common/mongo/controller'; @@ -8,6 +8,7 @@ import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { findDatasetIdTreeByTopDatasetId } from '@fastgpt/service/core/dataset/controller'; import { Readable } from 'stream'; import type { Cursor } from '@fastgpt/service/common/mongo'; +import { limitCheck } from './checkExportLimit'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -23,39 +24,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< // 凭证校验 const { userId } = await authDataset({ req, authToken: true, datasetId, per: 'w' }); - const exportIds = await findDatasetIdTreeByTopDatasetId(datasetId); - - const limitMinutesAgo = new Date( - Date.now() - (global.feConfigs?.limit?.exportLimitMinutes || 0) * 60 * 1000 - ); - - // auth export times - const authTimes = await MongoUser.findOne( - { - _id: userId, - $or: [ - { 'limit.exportKbTime': { $exists: false } }, - { 'limit.exportKbTime': { $lte: limitMinutesAgo } } - ] - }, - '_id limit' - ); - - if (!authTimes) { - const minutes = `${global.feConfigs?.limit?.exportLimitMinutes || 0} 分钟`; - throw new Error(`上次导出未到 ${minutes},每 ${minutes}仅可导出一次。`); - } - - // auth max data - const total = await MongoDatasetData.countDocuments({ - datasetId: { $in: exportIds } + await limitCheck({ + userId, + datasetId }); - addLog.info(`export datasets: ${datasetId}`, { total }); - - if (total > 100000) { - throw new Error('数据量超出 10 万,无法导出'); - } + const exportIds = await findDatasetIdTreeByTopDatasetId(datasetId); res.setHeader('Content-Type', 'text/csv; charset=utf-8;'); res.setHeader('Content-Disposition', 'attachment; filename=dataset.csv; '); @@ -72,35 +46,27 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< 'q a' ).cursor(); - function cursorToReadableStream(cursor: Cursor) { - const readable = new Readable({ - objectMode: true, - read() {} - }); + const write = responseWriteController({ + res, + readStream: cursor + }); - readable.push(`\uFEFFindex,content`); + write(`\uFEFFindex,content`); - cursor.on('data', (doc) => { - const q = doc.q.replace(/"/g, '""') || ''; - const a = doc.a.replace(/"/g, '""') || ''; + cursor.on('data', (doc) => { + const q = doc.q.replace(/"/g, '""') || ''; + const a = doc.a.replace(/"/g, '""') || ''; - readable.push(`\n"${q}","${a}"`); - }); + write(`\n"${q}","${a}"`); + }); - cursor.on('end', async () => { - readable.push(null); - cursor.close(); - await MongoUser.findByIdAndUpdate(userId, { - 'limit.exportKbTime': new Date() - }); + cursor.on('end', async () => { + cursor.close(); + res.end(); + await MongoUser.findByIdAndUpdate(userId, { + 'limit.exportKbTime': new Date() }); - - return readable; - } - - // @ts-ignore - const stream = cursorToReadableStream(cursor); - stream.pipe(res); + }); } catch (err) { res.status(500); jsonRes(res, { diff --git a/projects/app/src/pages/api/core/dataset/list.ts b/projects/app/src/pages/api/core/dataset/list.ts index f8dd47dfed0..8d0c4319f23 100644 --- a/projects/app/src/pages/api/core/dataset/list.ts +++ b/projects/app/src/pages/api/core/dataset/list.ts @@ -1,7 +1,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { getVectorModel } from '@/service/core/ai/model'; +import { getQAModel, getVectorModel } from '@/service/core/ai/model'; import type { DatasetItemType } from '@fastgpt/global/core/dataset/type.d'; import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; @@ -28,6 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< datasets.map(async (item) => ({ ...item.toJSON(), vectorModel: getVectorModel(item.vectorModel), + agentModel: getQAModel(item.agentModel), canWrite, isOwner: teamOwner || String(item.tmbId) === tmbId })) diff --git a/projects/app/src/pages/api/core/dataset/update.ts b/projects/app/src/pages/api/core/dataset/update.ts index 670f4092f0a..42bbefe7a10 100644 --- a/projects/app/src/pages/api/core/dataset/update.ts +++ b/projects/app/src/pages/api/core/dataset/update.ts @@ -8,7 +8,8 @@ import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { id, parentId, name, avatar, tags, permission } = req.body as DatasetUpdateParams; + const { id, parentId, name, avatar, tags, permission, agentModel } = + req.body as DatasetUpdateParams; if (!id) { throw new Error('缺少参数'); @@ -26,7 +27,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< ...(name && { name }), ...(avatar && { avatar }), ...(tags && { tags }), - ...(permission && { permission }) + ...(permission && { permission }), + ...(agentModel && { agentModel: agentModel.model }) } ); diff --git a/projects/app/src/pages/api/support/wallet/bill/createTrainingBill.ts b/projects/app/src/pages/api/support/wallet/bill/createTrainingBill.ts index ffe270e4006..9660a50d989 100644 --- a/projects/app/src/pages/api/support/wallet/bill/createTrainingBill.ts +++ b/projects/app/src/pages/api/support/wallet/bill/createTrainingBill.ts @@ -5,15 +5,17 @@ import { MongoBill } from '@fastgpt/service/support/wallet/bill/schema'; import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants'; import { CreateTrainingBillProps } from '@fastgpt/global/support/wallet/bill/api.d'; +import { getQAModel, getVectorModel } from '@/service/core/ai/model'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { name } = req.body as CreateTrainingBillProps; + const { name, vectorModel, agentModel } = req.body as CreateTrainingBillProps; const { teamId, tmbId } = await authCert({ req, authToken: true, authApiKey: true }); - const qaModel = global.qaModels[0]; + const vectorModelData = getVectorModel(vectorModel); + const agentModelData = getQAModel(agentModel); const { _id } = await MongoBill.create({ teamId, @@ -23,13 +25,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) list: [ { moduleName: '索引生成', - model: 'embedding', + model: vectorModelData.name, amount: 0, tokenLen: 0 }, { moduleName: 'QA 拆分', - model: qaModel?.name, + model: agentModelData.name, amount: 0, tokenLen: 0 } diff --git a/projects/app/src/pages/dataset/detail/components/DataCard.tsx b/projects/app/src/pages/dataset/detail/components/DataCard.tsx index 578d5a3ca43..ec81738fb20 100644 --- a/projects/app/src/pages/dataset/detail/components/DataCard.tsx +++ b/projects/app/src/pages/dataset/detail/components/DataCard.tsx @@ -170,7 +170,7 @@ const DataCard = () => { {datasetDataList.map((item) => ( diff --git a/projects/app/src/pages/dataset/detail/components/Import/ImportModal.tsx b/projects/app/src/pages/dataset/detail/components/Import/ImportModal.tsx index 2c7b33fb2d9..2da84e009e0 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/ImportModal.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/ImportModal.tsx @@ -34,10 +34,10 @@ const ImportData = ({ const theme = useTheme(); const { datasetDetail } = useDatasetStore(); const [importType, setImportType] = useState<`${ImportTypeEnum}`>(ImportTypeEnum.chunk); + const vectorModel = datasetDetail.vectorModel; + const agentModel = datasetDetail.agentModel; const typeMap = useMemo(() => { - const vectorModel = datasetDetail.vectorModel; - const qaModel = qaModelList[0]; const map = { [ImportTypeEnum.chunk]: { defaultChunkLen: vectorModel?.defaultToken || 500, @@ -45,8 +45,8 @@ const ImportData = ({ mode: TrainingModeEnum.chunk }, [ImportTypeEnum.qa]: { - defaultChunkLen: qaModel?.maxContext * 0.5 || 8000, - unitPrice: qaModel?.price || 3, + defaultChunkLen: agentModel?.maxContext * 0.6 || 9000, + unitPrice: agentModel?.price || 3, mode: TrainingModeEnum.qa }, [ImportTypeEnum.csv]: { @@ -56,7 +56,13 @@ const ImportData = ({ } }; return map[importType]; - }, [datasetDetail.vectorModel, importType]); + }, [ + agentModel?.maxContext, + agentModel?.price, + importType, + vectorModel?.defaultToken, + vectorModel?.price + ]); const TitleStyle: BoxProps = { fontWeight: 'bold', @@ -104,8 +110,10 @@ const ImportData = ({ diff --git a/projects/app/src/pages/dataset/detail/components/Import/Provider.tsx b/projects/app/src/pages/dataset/detail/components/Import/Provider.tsx index 0046a25ee9b..99f0719bf8d 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/Provider.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/Provider.tsx @@ -90,6 +90,8 @@ const Provider = ({ parentId, unitPrice, mode, + vectorModel, + agentModel, defaultChunkLen = 500, importType, onUploadSuccess, @@ -99,6 +101,8 @@ const Provider = ({ parentId: string; unitPrice: number; mode: `${TrainingModeEnum}`; + vectorModel: string; + agentModel: string; defaultChunkLen: number; importType: `${ImportTypeEnum}`; onUploadSuccess: () => void; @@ -132,7 +136,9 @@ const Provider = ({ const chunks = file.chunks; // create training bill const billId = await postCreateTrainingBill({ - name: t('dataset.collections.Create Training Data', { filename: file.filename }) + name: t('dataset.collections.Create Training Data', { filename: file.filename }), + vectorModel, + agentModel }); // create a file collection and training bill const collectionId = await postDatasetCollection({ diff --git a/projects/app/src/pages/dataset/detail/components/Import/QA.tsx b/projects/app/src/pages/dataset/detail/components/Import/QA.tsx index 2d0f2b06c7c..d6bc531adca 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/QA.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/QA.tsx @@ -13,8 +13,8 @@ const fileExtension = '.txt, .doc, .docx, .pdf, .md'; const QAImport = () => { const { datasetDetail } = useDatasetStore(); - const vectorModel = datasetDetail.vectorModel; - const unitPrice = vectorModel?.price || 0.2; + const agentModel = datasetDetail.agentModel; + const unitPrice = agentModel?.price || 3; const { successChunks, diff --git a/projects/app/src/pages/dataset/detail/components/Info.tsx b/projects/app/src/pages/dataset/detail/components/Info.tsx index de798df7822..9fadd323037 100644 --- a/projects/app/src/pages/dataset/detail/components/Info.tsx +++ b/projects/app/src/pages/dataset/detail/components/Info.tsx @@ -9,7 +9,7 @@ import React, { import { useRouter } from 'next/router'; import { Box, Flex, Button, FormControl, IconButton, Input } from '@chakra-ui/react'; import { QuestionOutlineIcon, DeleteIcon } from '@chakra-ui/icons'; -import { delDatasetById, putDatasetById } from '@/web/core/dataset/api'; +import { delDatasetById } from '@/web/core/dataset/api'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; import { useToast } from '@/web/common/hooks/useToast'; import { useDatasetStore } from '@/web/core/dataset/store/dataset'; @@ -22,6 +22,8 @@ import Tag from '@/components/Tag'; import MyTooltip from '@/components/MyTooltip'; import { useTranslation } from 'next-i18next'; import PermissionRadio from '@/components/support/permission/Radio'; +import MySelect from '@/components/Select'; +import { qaModelList } from '@/web/common/system/staticData'; export interface ComponentRef { initInput: (tags: string) => void; @@ -50,7 +52,7 @@ const Info = ( multiple: false }); - const { datasetDetail, loadDatasetDetail, loadDatasets } = useDatasetStore(); + const { datasetDetail, loadDatasetDetail, loadDatasets, updateDataset } = useDatasetStore(); /* 点击删除 */ const onclickDelKb = useCallback(async () => { @@ -76,11 +78,10 @@ const Info = ( async (data: DatasetItemType) => { setBtnLoading(true); try { - await putDatasetById({ + await updateDataset({ id: datasetId, ...data }); - await loadDatasetDetail(datasetId, true); toast({ title: '更新成功', status: 'success' @@ -94,7 +95,7 @@ const Info = ( } setBtnLoading(false); }, - [loadDatasetDetail, datasetId, loadDatasets, toast] + [updateDataset, datasetId, loadDatasetDetail, toast, loadDatasets] ); const saveSubmitError = useCallback(() => { // deep search message @@ -194,6 +195,27 @@ const Info = ( })} /> + + + {t('dataset.Agent Model')} + + + ({ + label: item.name, + value: item.model + }))} + onchange={(e) => { + const agentModel = qaModelList.find((item) => item.model === e); + if (!agentModel) return; + setValue('agentModel', agentModel); + setRefresh((state) => !state); + }} + /> + + 标签 diff --git a/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx b/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx index 5dbb6bed1d0..7bb9f8c53e1 100644 --- a/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx +++ b/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx @@ -196,7 +196,7 @@ const InputDataModal = ({ const loading = useMemo(() => isImporting || isUpdating, [isImporting, isUpdating]); return ( - + diff --git a/projects/app/src/pages/dataset/list/component/CreateModal.tsx b/projects/app/src/pages/dataset/list/component/CreateModal.tsx index 8ddcab58b2c..12da57e55cb 100644 --- a/projects/app/src/pages/dataset/list/component/CreateModal.tsx +++ b/projects/app/src/pages/dataset/list/component/CreateModal.tsx @@ -15,10 +15,12 @@ import { postCreateDataset } from '@/web/core/dataset/api'; import type { CreateDatasetParams } from '@/global/core/dataset/api.d'; import MySelect from '@/components/Select'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; -import { vectorModelList } from '@/web/common/system/staticData'; +import { vectorModelList, qaModelList } from '@/web/common/system/staticData'; import Tag from '@/components/Tag'; +import { useTranslation } from 'next-i18next'; const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: string }) => { + const { t } = useTranslation(); const [refresh, setRefresh] = useState(false); const { toast } = useToast(); const router = useRouter(); @@ -29,6 +31,7 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st name: '', tags: '', vectorModel: vectorModelList[0].model, + agentModel: qaModelList[0].model, type: 'dataset', parentId } @@ -76,7 +79,7 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st }); return ( - + 创建一个知识库 @@ -106,7 +109,7 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st /> - 索引模型 + 索引模型 void; parentId?: st /> + + {t('dataset.Agent Model')} + + ({ + label: item.name, + value: item.model + }))} + onchange={(e) => { + setValue('agentModel', e); + setRefresh((state) => !state); + }} + /> + + - + 标签 diff --git a/projects/app/src/pages/dataset/list/index.tsx b/projects/app/src/pages/dataset/list/index.tsx index 5366c18aa6d..a8c27b16f5a 100644 --- a/projects/app/src/pages/dataset/list/index.tsx +++ b/projects/app/src/pages/dataset/list/index.tsx @@ -20,7 +20,8 @@ import { delDatasetById, getDatasetPaths, putDatasetById, - postCreateDataset + postCreateDataset, + getCheckExportLimit } from '@/web/core/dataset/api'; import { useTranslation } from 'next-i18next'; import Avatar from '@/components/Avatar'; @@ -38,6 +39,7 @@ import { useDrag } from '@/web/common/hooks/useDrag'; import { useUserStore } from '@/web/support/user/useUserStore'; import PermissionIconText from '@/components/support/permission/IconText'; import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { DatasetItemType } from '@fastgpt/global/core/dataset/type'; const CreateModal = dynamic(() => import('./component/CreateModal'), { ssr: false }); const MoveModal = dynamic(() => import('./component/MoveModal'), { ssr: false }); @@ -89,6 +91,23 @@ const Kb = () => { successToast: t('common.Delete Success'), errorToast: t('dataset.Delete Dataset Error') }); + // check export limit + const { mutate: exportDataset } = useRequest({ + mutationFn: async (dataset: DatasetItemType) => { + setLoading(true); + await getCheckExportLimit(dataset._id); + const a = document.createElement('a'); + a.href = `/api/core/dataset/exportAll?datasetId=${dataset._id}`; + a.download = `${dataset.name}.csv`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + }, + onSettled() { + setLoading(false); + }, + errorToast: t('dataset.Export Dataset Limit Error') + }); const { data, refetch } = useQuery(['loadDataset', parentId], () => { return Promise.all([loadDatasets(parentId), getDatasetPaths(parentId)]); @@ -371,12 +390,7 @@ const Kb = () => { ), onClick: () => { - const a = document.createElement('a'); - a.href = `/api/core/dataset/exportAll?datasetId=${dataset._id}`; - a.download = `${dataset.name}.csv`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); + exportDataset(dataset); } }, { diff --git a/projects/app/src/service/events/generateQA.ts b/projects/app/src/service/events/generateQA.ts index b03b7bc800f..fc28cf29825 100644 --- a/projects/app/src/service/events/generateQA.ts +++ b/projects/app/src/service/events/generateQA.ts @@ -109,6 +109,7 @@ export async function generateQA(): Promise { try { const startTime = Date.now(); + const model = data.model ?? global.qaModels[0].model; // request LLM to get QA const messages: ChatMessageItemType[] = [ @@ -122,9 +123,10 @@ export async function generateQA(): Promise { }) } ]; - const ai = getAIApi(undefined, 480000); + + const ai = getAIApi(undefined, 600000); const chatResponse = await ai.chat.completions.create({ - model: global.qaModels[0].model, + model, temperature: 0.01, messages, stream: false @@ -147,8 +149,11 @@ export async function generateQA(): Promise { // delete data from training await MongoDatasetTraining.findByIdAndDelete(data._id); - console.log(`split result length: `, qaArr.length); - console.log('生成QA成功,time:', `${(Date.now() - startTime) / 1000}s`); + addLog.info(`QA Training Finish`, { + time: `${(Date.now() - startTime) / 1000}s`, + splitLength: qaArr.length, + usage: chatResponse.usage + }); // add bill if (qaArr.length > 0) { @@ -156,7 +161,8 @@ export async function generateQA(): Promise { teamId: data.teamId, tmbId: data.tmbId, totalTokens, - billId: data.billId + billId: data.billId, + model }); } else { addLog.info(`QA result 0:`, { answer }); diff --git a/projects/app/src/service/support/wallet/bill/push.ts b/projects/app/src/service/support/wallet/bill/push.ts index 28a77e41063..4efa32f9519 100644 --- a/projects/app/src/service/support/wallet/bill/push.ts +++ b/projects/app/src/service/support/wallet/bill/push.ts @@ -1,5 +1,5 @@ import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants'; -import { getAudioSpeechModel } from '@/service/core/ai/model'; +import { getAudioSpeechModel, getQAModel } from '@/service/core/ai/model'; import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d'; import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import { addLog } from '@fastgpt/service/common/mongo/controller'; @@ -9,10 +9,16 @@ import { POST } from '@fastgpt/service/common/api/plusRequest'; export function createBill(data: CreateBillProps) { if (!global.systemEnv.pluginBaseUrl) return; + if (data.total === 0) { + addLog.info('0 Bill', data); + } POST('/support/wallet/bill/createBill', data); } export function concatBill(data: ConcatBillProps) { if (!global.systemEnv.pluginBaseUrl) return; + if (data.total === 0) { + addLog.info('0 Bill', data); + } POST('/support/wallet/bill/concatBill', data); } @@ -59,18 +65,18 @@ export const pushChatBill = ({ export const pushQABill = async ({ teamId, tmbId, + model, totalTokens, billId }: { teamId: string; tmbId: string; + model: string; totalTokens: number; billId: string; }) => { - addLog.info('splitData generate success', { totalTokens }); - // 获取模型单价格 - const unitPrice = global.qaModels?.[0]?.price || 3; + const unitPrice = getQAModel(model).price; // 计算价格 const total = unitPrice * totalTokens; diff --git a/projects/app/src/web/core/dataset/api.ts b/projects/app/src/web/core/dataset/api.ts index 5ee65ebaab7..d5a1635b881 100644 --- a/projects/app/src/web/core/dataset/api.ts +++ b/projects/app/src/web/core/dataset/api.ts @@ -48,6 +48,9 @@ export const putDatasetById = (data: DatasetUpdateParams) => PUT(`/core/dataset/ export const delDatasetById = (id: string) => DELETE(`/core/dataset/delete?id=${id}`); +export const getCheckExportLimit = (datasetId: string) => + GET(`/core/dataset/checkExportLimit`, { datasetId }); + /* =========== search test ============ */ export const postSearchText = (data: SearchTestProps) => POST(`/core/dataset/searchTest`, data);