Commit 8239c584 by archer

perf: 知识库优化

parent 456686f3
import { GET, POST, DELETE, PUT } from './request';
import type { ModelSchema, ModelDataSchema } from '@/types/mongoSchema';
import type { ModelSchema, ModelDataSchema, ModelSplitDataSchema } from '@/types/mongoSchema';
import { ModelUpdateParams } from '@/types/model';
import { TrainingItemType } from '../types/training';
import { PagingData } from '@/types';
import { RequestPaging } from '../types/index';
import { Obj2Query } from '@/utils/tools';
......@@ -39,6 +38,9 @@ type GetModelDataListProps = RequestPaging & {
export const getModelDataList = (props: GetModelDataListProps) =>
GET(`/model/data/getModelData?${Obj2Query(props)}`);
export const getModelSplitDataList = (modelId: string) =>
GET<ModelSplitDataSchema[]>(`/model/data/getSplitData?modelId=${modelId}`);
export const postModelDataInput = (data: {
modelId: string;
data: { text: ModelDataSchema['text']; q: ModelDataSchema['q'] }[];
......
......@@ -17,30 +17,24 @@ export const introPage = `
4. 进入模型页,创建一个模型,建议直接用 ChatGPT。
5. 在模型列表点击【对话】,即可使用 API 进行聊天。
### 模型配置
### 定制 prompt
1. **提示语**:会在每个对话框的第一句自动加入,用于限定该模型的对话内容。
1. 进入模型编辑页
2. 调整温度和提示词
3. 使用该模型对话。每次对话时,提示词和温度都会自动注入,方便管理个人的模型。建议把自己日常经常需要使用的 5~10 个方向预设好。
### 知识库
2. **单句最大长度**:每个聊天,单次输入内容的最大长度。
1. 创建模型时选择【知识库】
2. 进入模型编辑页
3. 导入数据,可以选择手动导入,或者选择文件导入。文件导入会自动调用 chatGPT 理解文件内容,并生成知识库。
4. 使用该模型对话。
3. **上下文最大长度**:每个聊天,最多的轮数除以2,建议设置为偶数。可以持续聊天,但是旧的聊天内容会被截断,AI 就不会知道被截取的内容。
例如:上下文最大长度为6。在第 4 轮对话时,第一轮对话的内容不会被计入。
4. **过期时间**:生成对话框后,这个对话框多久过期。
5. **聊天最大加载次数**:单个对话框最多被加载几次,设置为-1代表不限制,正数代表只能加载 n 次,防止被盗刷。
### 对话框介绍
1. 每个对话框以 chatId 作为标识。
2. 每次点击【对话】,都会生成新的对话框,无法回到旧的对话框。对话框内刷新,会恢复对话内容。
3. 直接分享对话框(网页)的链接给朋友,会共享同一个对话内容。但是!!!千万不要两个人同时用一个链接,会串味,还没解决这个问题。
4. 如果想分享一个纯的对话框,请点击侧边栏的分享按键。例如:
注意:使用知识库模型对话时,tokens 消耗会加快。
### 其他问题
还有其他问题,可以加我 wx: YNyiqi,拉个交流群大家一起聊聊。
`;
export const chatProblem = `
......@@ -58,10 +52,9 @@ export const chatProblem = `
`;
export const versionIntro = `
## Fast GPT V2.0
## Fast GPT V2.2
* 定制知识库:创建模型时可以选择【知识库】模型, 可以手动导入知识点或者直接导入一个文件自动学习。
* 删除和复制功能:点击对话头像,可以选择复制或删除该条内容。
* 优化记账模式: 不再根据文本长度进行记账,而是根据实际消耗 tokens 数量进行记账。
* 文本 QA 拆分: 可以在[数据]模块,使用 QA 拆分功能,粘贴文字或者选择文件均可以实现自动生成 QA。可以一键导出,用于微调模型。
`;
export const shareHint = `
......
import { useState, useCallback, useMemo } from 'react';
import type { PagingData } from '../types/index';
import { IconButton, Flex, Box } from '@chakra-ui/react';
import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons';
import { useQuery, useMutation } from '@tanstack/react-query';
import { useToast } from './useToast';
export const usePagination = <T = any,>({
api,
pageSize = 10,
params = {}
}: {
api: (data: any) => any;
pageSize?: number;
params?: Record<string, any>;
}) => {
const { toast } = useToast();
const [pageNum, setPageNum] = useState(1);
const [total, setTotal] = useState(0);
const maxPage = useMemo(() => Math.ceil(total / pageSize), [pageSize, total]);
const {
mutate,
data = [],
isLoading
} = useMutation({
mutationFn: async (num: number = pageNum) => {
try {
const res: PagingData<T> = await api({
pageNum: num,
pageSize,
...params
});
setPageNum(num);
setTotal(res.total);
return res.data;
} catch (error: any) {
toast({
title: error?.message || '获取数据异常',
status: 'error'
});
console.log(error);
}
}
});
useQuery(['init'], () => {
mutate(1);
return null;
});
const Pagination = useCallback(() => {
return (
<Flex alignItems={'center'} justifyContent={'end'}>
<IconButton
isDisabled={pageNum === 1}
icon={<ArrowBackIcon />}
aria-label={'left'}
size={'sm'}
onClick={() => mutate(pageNum - 1)}
/>
<Box mx={2}>
{pageNum}/{maxPage}
</Box>
<IconButton
isDisabled={pageNum === maxPage}
icon={<ArrowForwardIcon />}
aria-label={'left'}
size={'sm'}
onClick={() => mutate(pageNum + 1)}
/>
</Flex>
);
}, [maxPage, mutate, pageNum]);
return {
pageNum,
pageSize,
total,
data,
isLoading,
Pagination,
getData: mutate
};
};
......@@ -40,6 +40,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
await connectToDatabase();
let startTime = Date.now();
const { chat, userApiKey, systemKey, userId } = await authChat(chatId, authorization);
......@@ -81,7 +82,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// 获取 chatAPI
const chatAPI = getOpenAIApi(userApiKey || systemKey);
let startTime = Date.now();
// 发出请求
const chatResponse = await chatAPI.createChatCompletion(
{
......
......@@ -48,6 +48,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase();
const redis = await connectRedis();
let startTime = Date.now();
const { chat, userApiKey, systemKey, userId } = await authChat(chatId, authorization);
......@@ -83,17 +84,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const redisData: any[] = await redis.sendCommand([
'FT.SEARCH',
`idx:${VecModelDataIndex}`,
`@modelId:{${String(chat.modelId._id)}} @vector:[VECTOR_RANGE 0.2 $blob]`,
`@modelId:{${String(
chat.modelId._id
)}} @vector:[VECTOR_RANGE 0.15 $blob]=>{$YIELD_DISTANCE_AS: score}`,
// `@modelId:{${String(chat.modelId._id)}}=>[KNN 10 @vector $blob AS score]`,
'RETURN',
'1',
'dataId',
// 'SORTBY',
// 'score',
'SORTBY',
'score',
'PARAMS',
'2',
'blob',
binary,
'LIMIT',
'0',
'20',
'DIALECT',
'2'
]);
......@@ -117,8 +123,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20].map((i) => {
if (!redisData[i] || !redisData[i][1]) return '';
return ModelData.findById(redisData[i][1])
.select('text')
.then((res) => res?.text || '');
.select('text q')
.then((res) => {
if (!res) return '';
const questions = res.q.map((item) => item.text).join(' ');
const answer = res.text;
return `${questions} ${answer}`;
});
})
)
).filter((item) => item);
......@@ -128,7 +139,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
prompts.unshift({
obj: 'SYSTEM',
value: `请根据下面的知识回答问题: ${systemPrompt}`
value: `根据下面的知识回答问题: ${systemPrompt}`
});
// 控制在 tokens 数量,防止超出
......@@ -150,7 +161,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// 计算温度
const temperature = modelConstantsData.maxTemperature * (model.temperature / 10);
let startTime = Date.now();
// 发出请求
const chatResponse = await chatAPI.createChatCompletion(
{
......
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, SplitData, Model } from '@/service/mongo';
import { authToken } from '@/service/utils/tools';
/* 拆分数据成QA */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { modelId } = req.query as { modelId: string };
if (!modelId) {
throw new Error('参数错误');
}
await connectToDatabase();
const { authorization } = req.headers;
const userId = await authToken(authorization);
// 找到长度大于0的数据
const data = await SplitData.find({
userId,
modelId,
textList: { $exists: true, $not: { $size: 0 } }
});
jsonRes(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
......@@ -53,7 +53,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
textList
});
// generateQA();
generateQA();
jsonRes(res, {
data: { chunks, replaceText }
......
......@@ -25,13 +25,13 @@ const Empty = ({ intro }: { intro: string }) => {
<Box whiteSpace={'pre-line'}>{intro}</Box>
</Card>
)}
{/* version intro */}
<Card p={4} mb={10}>
<Header>常见问题</Header>
<Markdown source={chatProblem} />
<Markdown source={versionIntro} />
</Card>
{/* version intro */}
<Card p={4}>
<Markdown source={versionIntro} />
<Header>常见问题</Header>
<Markdown source={chatProblem} />
</Card>
</Box>
);
......
......@@ -21,13 +21,18 @@ import {
import type { ModelSchema } from '@/types/mongoSchema';
import { ModelDataSchema } from '@/types/mongoSchema';
import { ModelDataStatusMap } from '@/constants/model';
import { usePaging } from '@/hooks/usePaging';
import ScrollData from '@/components/ScrollData';
import { getModelDataList, delOneModelData, putModelDataById } from '@/api/model';
import { usePagination } from '@/hooks/usePagination';
import {
getModelDataList,
delOneModelData,
putModelDataById,
getModelSplitDataList
} from '@/api/model';
import { DeleteIcon, RepeatIcon } from '@chakra-ui/icons';
import { useToast } from '@/hooks/useToast';
import { useLoading } from '@/hooks/useLoading';
import dynamic from 'next/dynamic';
import { useQuery } from '@tanstack/react-query';
const InputModel = dynamic(() => import('./InputDataModal'));
const SelectModel = dynamic(() => import('./SelectFileModal'));
......@@ -37,16 +42,15 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
const { Loading } = useLoading();
const {
nextPage,
isLoadAll,
requesting,
data: modelDataList,
isLoading,
Pagination,
total,
setData,
getData
} = usePaging<ModelDataSchema>({
getData,
pageNum
} = usePagination<ModelDataSchema>({
api: getModelDataList,
pageSize: 20,
pageSize: 10,
params: {
modelId: model._id
}
......@@ -77,6 +81,18 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
onClose: onCloseSelectModal
} = useDisclosure();
const { data, refetch } = useQuery(['getModelSplitDataList'], () =>
getModelSplitDataList(model._id)
);
const refetchData = useCallback(
(num = 1) => {
getData(num);
refetch();
},
[getData, refetch]
);
return (
<>
<Flex>
......@@ -91,7 +107,7 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
aria-label={'refresh'}
variant={'outline'}
mr={4}
onClick={() => getData(1, true)}
onClick={() => refetchData(pageNum)}
/>
<Menu>
<MenuButton as={Button}>导入</MenuButton>
......@@ -101,16 +117,9 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
</MenuList>
</Menu>
</Flex>
<ScrollData
h={'100%'}
px={6}
mt={3}
isLoadAll={isLoadAll}
requesting={requesting}
nextPage={nextPage}
position={'relative'}
>
<TableContainer mt={4}>
{data && data.length > 0 && <Box fontSize={'xs'}>{data.length}条数据正在拆分中...</Box>}
<Box mt={4}>
<TableContainer h={'600px'} overflowY={'auto'}>
<Table variant={'simple'}>
<Thead>
<Tr>
......@@ -150,12 +159,6 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
const oldVal = modelDataList.find((data) => item._id === data._id)?.text;
if (oldVal !== e.target.value) {
updateAnswer(item._id, e.target.value);
setData((state) =>
state.map((data) => ({
...data,
text: data._id === item._id ? e.target.value : data.text
}))
);
}
}}
></Textarea>
......@@ -170,7 +173,7 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
size={'sm'}
onClick={async () => {
delOneModelData(item._id);
setData((state) => state.filter((data) => data._id !== item._id));
refetchData(pageNum);
}}
/>
</Td>
......@@ -179,21 +182,17 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
</Tbody>
</Table>
</TableContainer>
<Loading loading={requesting} fixed={false} />
</ScrollData>
<Box mt={2} textAlign={'end'}>
<Pagination />
</Box>
</Box>
<Loading loading={isLoading} fixed={false} />
{isOpenInputModal && (
<InputModel
modelId={model._id}
onClose={onCloseInputModal}
onSuccess={() => getData(1, true)}
/>
<InputModel modelId={model._id} onClose={onCloseInputModal} onSuccess={refetchData} />
)}
{isOpenSelectModal && (
<SelectModel
modelId={model._id}
onClose={onCloseSelectModal}
onSuccess={() => getData(1, true)}
/>
<SelectModel modelId={model._id} onClose={onCloseSelectModal} onSuccess={refetchData} />
)}
</>
);
......
......@@ -37,7 +37,7 @@ const SelectFileModal = ({
const { File, onOpen } = useSelectFile({ fileType: fileExtension, multiple: true });
const [fileText, setFileText] = useState('');
const { openConfirm, ConfirmChild } = useConfirm({
content: '确认导入该文件,需要一定时间进行拆解,该任务无法终止!'
content: '确认导入该文件,需要一定时间进行拆解,该任务无法终止!如果余额不足,任务讲被终止。'
});
const onSelectFile = useCallback(
......
......@@ -11,8 +11,7 @@ import { useGlobalStore } from '@/store/global';
import { useScreen } from '@/hooks/useScreen';
import ModelEditForm from './components/ModelEditForm';
import { useQuery } from '@tanstack/react-query';
// import dynamic from 'next/dynamic';
import ModelDataCard from './components/ModelDataCard';
import dynamic from 'next/dynamic';
const ModelDataCard = dynamic(() => import('./components/ModelDataCard'));
......@@ -251,7 +250,6 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
{canTrain && model._id && (
<Card
p={4}
height={'700px'}
{...media(
{
gridColumnStart: 1,
......
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