Commit 8239c584 by archer

perf: 知识库优化

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