mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 21:13:50 +00:00
perf: 知识库录入
This commit is contained in:
@@ -119,20 +119,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
.select('text q')
|
.select('text q')
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (!res) return '';
|
if (!res) return '';
|
||||||
const questions = res.q.map((item) => item.text).join(' ');
|
// const questions = res.q.map((item) => item.text).join(' ');
|
||||||
const answer = res.text;
|
const answer = res.text;
|
||||||
return `${questions} ${answer}`;
|
return `${answer}`;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
).filter((item) => item);
|
).filter((item) => item);
|
||||||
|
|
||||||
// textArr 筛选,最多 3000 tokens
|
// textArr 筛选,最多 3000 tokens
|
||||||
const systemPrompt = systemPromptFilter(textArr, 2800);
|
const systemPrompt = systemPromptFilter(textArr, 3400);
|
||||||
|
|
||||||
prompts.unshift({
|
prompts.unshift({
|
||||||
obj: 'SYSTEM',
|
obj: 'SYSTEM',
|
||||||
value: `根据下面的知识回答问题: ${systemPrompt}`
|
value: `${model.systemPrompt}。 我的知识库: "${systemPrompt}"`
|
||||||
});
|
});
|
||||||
|
|
||||||
// 控制在 tokens 数量,防止超出
|
// 控制在 tokens 数量,防止超出
|
||||||
|
@@ -190,91 +190,97 @@ const Chat = ({ chatId }: { chatId: string }) => {
|
|||||||
/**
|
/**
|
||||||
* 发送一个内容
|
* 发送一个内容
|
||||||
*/
|
*/
|
||||||
const sendPrompt = useCallback(async () => {
|
const sendPrompt = useCallback(
|
||||||
const storeInput = inputVal;
|
async (e?: React.MouseEvent<HTMLDivElement>) => {
|
||||||
// 去除空行
|
e?.stopPropagation();
|
||||||
const val = inputVal
|
e?.preventDefault();
|
||||||
.trim()
|
|
||||||
.split('\n')
|
|
||||||
.filter((val) => val)
|
|
||||||
.join('\n');
|
|
||||||
if (!chatData?.modelId || !val || !ChatBox.current || isChatting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 长度校验
|
const storeInput = inputVal;
|
||||||
const tokens = encode(val).length;
|
// 去除空行
|
||||||
const model = modelList.find((item) => item.model === chatData.modelName);
|
const val = inputVal
|
||||||
|
.trim()
|
||||||
if (model && tokens >= model.maxToken) {
|
.split('\n')
|
||||||
toast({
|
.filter((val) => val)
|
||||||
title: '单次输入超出 4000 tokens',
|
.join('\n');
|
||||||
status: 'warning'
|
if (!chatData?.modelId || !val || !ChatBox.current || isChatting) {
|
||||||
});
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newChatList: ChatSiteItemType[] = [
|
|
||||||
...chatData.history,
|
|
||||||
{
|
|
||||||
obj: 'Human',
|
|
||||||
value: val,
|
|
||||||
status: 'finish'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
obj: 'AI',
|
|
||||||
value: '',
|
|
||||||
status: 'loading'
|
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
// 插入内容
|
// 长度校验
|
||||||
setChatData((state) => ({
|
const tokens = encode(val).length;
|
||||||
...state,
|
const model = modelList.find((item) => item.model === chatData.modelName);
|
||||||
history: newChatList
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 清空输入内容
|
if (model && tokens >= model.maxToken) {
|
||||||
resetInputVal('');
|
toast({
|
||||||
scrollToBottom();
|
title: '单次输入超出 4000 tokens',
|
||||||
|
status: 'warning'
|
||||||
try {
|
|
||||||
await gptChatPrompt(newChatList[newChatList.length - 2]);
|
|
||||||
|
|
||||||
// 如果是 Human 第一次发送,插入历史记录
|
|
||||||
const humanChat = newChatList.filter((item) => item.obj === 'Human');
|
|
||||||
if (humanChat.length === 1) {
|
|
||||||
pushChatHistory({
|
|
||||||
chatId,
|
|
||||||
title: humanChat[0].value
|
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: typeof err === 'string' ? err : err?.message || '聊天出错了~',
|
|
||||||
status: 'warning',
|
|
||||||
duration: 5000,
|
|
||||||
isClosable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
resetInputVal(storeInput);
|
const newChatList: ChatSiteItemType[] = [
|
||||||
|
...chatData.history,
|
||||||
|
{
|
||||||
|
obj: 'Human',
|
||||||
|
value: val,
|
||||||
|
status: 'finish'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
obj: 'AI',
|
||||||
|
value: '',
|
||||||
|
status: 'loading'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 插入内容
|
||||||
setChatData((state) => ({
|
setChatData((state) => ({
|
||||||
...state,
|
...state,
|
||||||
history: newChatList.slice(0, newChatList.length - 2)
|
history: newChatList
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
}, [
|
// 清空输入内容
|
||||||
inputVal,
|
resetInputVal('');
|
||||||
chatData,
|
scrollToBottom();
|
||||||
isChatting,
|
|
||||||
resetInputVal,
|
try {
|
||||||
scrollToBottom,
|
await gptChatPrompt(newChatList[newChatList.length - 2]);
|
||||||
toast,
|
|
||||||
gptChatPrompt,
|
// 如果是 Human 第一次发送,插入历史记录
|
||||||
pushChatHistory,
|
const humanChat = newChatList.filter((item) => item.obj === 'Human');
|
||||||
chatId
|
if (humanChat.length === 1) {
|
||||||
]);
|
pushChatHistory({
|
||||||
|
chatId,
|
||||||
|
title: humanChat[0].value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
toast({
|
||||||
|
title: typeof err === 'string' ? err : err?.message || '聊天出错了~',
|
||||||
|
status: 'warning',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
resetInputVal(storeInput);
|
||||||
|
|
||||||
|
setChatData((state) => ({
|
||||||
|
...state,
|
||||||
|
history: newChatList.slice(0, newChatList.length - 2)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
inputVal,
|
||||||
|
chatData,
|
||||||
|
isChatting,
|
||||||
|
resetInputVal,
|
||||||
|
scrollToBottom,
|
||||||
|
toast,
|
||||||
|
gptChatPrompt,
|
||||||
|
pushChatHistory,
|
||||||
|
chatId
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
// 删除一句话
|
// 删除一句话
|
||||||
const delChatRecord = useCallback(
|
const delChatRecord = useCallback(
|
||||||
|
@@ -19,7 +19,7 @@ import { DeleteIcon } from '@chakra-ui/icons';
|
|||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||||
|
|
||||||
type FormData = { text: string; q: { val: string }[] };
|
type FormData = { text: string; q: string };
|
||||||
|
|
||||||
const InputDataModal = ({
|
const InputDataModal = ({
|
||||||
onClose,
|
onClose,
|
||||||
@@ -36,17 +36,9 @@ const InputDataModal = ({
|
|||||||
const { register, handleSubmit, control } = useForm<FormData>({
|
const { register, handleSubmit, control } = useForm<FormData>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
text: '',
|
text: '',
|
||||||
q: [{ val: '' }]
|
q: ''
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const {
|
|
||||||
fields: inputQ,
|
|
||||||
append: appendQ,
|
|
||||||
remove: removeQ
|
|
||||||
} = useFieldArray({
|
|
||||||
control,
|
|
||||||
name: 'q'
|
|
||||||
});
|
|
||||||
|
|
||||||
const sureImportData = useCallback(
|
const sureImportData = useCallback(
|
||||||
async (e: FormData) => {
|
async (e: FormData) => {
|
||||||
@@ -58,10 +50,12 @@ const InputDataModal = ({
|
|||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
text: e.text,
|
text: e.text,
|
||||||
q: e.q.map((item) => ({
|
q: [
|
||||||
id: nanoid(),
|
{
|
||||||
text: item.val
|
id: nanoid(),
|
||||||
}))
|
text: e.q
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -81,50 +75,46 @@ const InputDataModal = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={true} onClose={onClose}>
|
<Modal isOpen={true} onClose={onClose} isCentered>
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent maxW={'min(900px, 90vw)'} maxH={'80vh'} position={'relative'}>
|
<ModalContent
|
||||||
|
m={0}
|
||||||
|
display={'flex'}
|
||||||
|
flexDirection={'column'}
|
||||||
|
h={'90vh'}
|
||||||
|
maxW={'90vw'}
|
||||||
|
position={'relative'}
|
||||||
|
>
|
||||||
<ModalHeader>手动导入</ModalHeader>
|
<ModalHeader>手动导入</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<Box px={6} pb={2} overflowY={'auto'}>
|
|
||||||
<Box mb={2}>知识点:</Box>
|
<Flex flex={'1 0 0'} h={0} px={6} pb={2}>
|
||||||
<Textarea
|
<Box flex={2} mr={4} h={'100%'}>
|
||||||
mb={4}
|
<Box h={'30px'}>问题</Box>
|
||||||
placeholder="知识点"
|
<Textarea
|
||||||
rows={3}
|
placeholder="相关问题,可以回车输入多个问法, 最多500字"
|
||||||
maxH={'200px'}
|
maxLength={500}
|
||||||
{...register(`text`, {
|
resize={'none'}
|
||||||
required: '知识点'
|
h={'calc(100% - 30px)'}
|
||||||
})}
|
{...register(`q`, {
|
||||||
/>
|
required: '相关问题,可以回车输入多个问法'
|
||||||
{inputQ.map((item, index) => (
|
})}
|
||||||
<Box key={item.id} mb={5}>
|
/>
|
||||||
<Box mb={2}>问法{index + 1}:</Box>
|
</Box>
|
||||||
<Flex>
|
<Box flex={3} h={'100%'}>
|
||||||
<Input
|
<Box h={'30px'}>知识点</Box>
|
||||||
placeholder="问法"
|
<Textarea
|
||||||
{...register(`q.${index}.val`, {
|
placeholder="知识点"
|
||||||
required: '问法不能为空'
|
resize={'none'}
|
||||||
})}
|
h={'calc(100% - 30px)'}
|
||||||
></Input>
|
{...register(`text`, {
|
||||||
{inputQ.length > 1 && (
|
required: '知识点'
|
||||||
<IconButton
|
})}
|
||||||
icon={<DeleteIcon />}
|
/>
|
||||||
aria-label={'delete'}
|
</Box>
|
||||||
colorScheme={'gray'}
|
</Flex>
|
||||||
variant={'unstyled'}
|
|
||||||
onClick={() => removeQ(index)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Flex px={6} pt={2} pb={4}>
|
<Flex px={6} pt={2} pb={4}>
|
||||||
<Button alignSelf={'flex-start'} variant={'outline'} onClick={() => appendQ({ val: '' })}>
|
|
||||||
增加问法
|
|
||||||
</Button>
|
|
||||||
<Box flex={1}></Box>
|
<Box flex={1}></Box>
|
||||||
<Button variant={'outline'} mr={3} onClick={onClose}>
|
<Button variant={'outline'} mr={3} onClick={onClose}>
|
||||||
取消
|
取消
|
||||||
|
@@ -50,7 +50,7 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
|
|||||||
pageNum
|
pageNum
|
||||||
} = usePagination<ModelDataSchema>({
|
} = usePagination<ModelDataSchema>({
|
||||||
api: getModelDataList,
|
api: getModelDataList,
|
||||||
pageSize: 10,
|
pageSize: 8,
|
||||||
params: {
|
params: {
|
||||||
modelId: model._id
|
modelId: model._id
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
{data && data.length > 0 && <Box fontSize={'xs'}>{data.length}条数据正在拆分中...</Box>}
|
{data && data.length > 0 && <Box fontSize={'xs'}>{data.length}条数据正在拆分中...</Box>}
|
||||||
<Box mt={4}>
|
<Box mt={4}>
|
||||||
<TableContainer h={'600px'} overflowY={'auto'}>
|
<TableContainer>
|
||||||
<Table variant={'simple'}>
|
<Table variant={'simple'}>
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
@@ -141,10 +141,7 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
|
|||||||
whiteSpace={'pre-wrap'}
|
whiteSpace={'pre-wrap'}
|
||||||
_notLast={{ mb: 1 }}
|
_notLast={{ mb: 1 }}
|
||||||
>
|
>
|
||||||
Q{i + 1}:{' '}
|
{item.text}
|
||||||
<Box as={'span'} userSelect={'all'}>
|
|
||||||
{item.text}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Td>
|
</Td>
|
||||||
|
@@ -108,7 +108,7 @@ const ModelEditForm = ({
|
|||||||
|
|
||||||
<Slider
|
<Slider
|
||||||
aria-label="slider-ex-1"
|
aria-label="slider-ex-1"
|
||||||
min={1}
|
min={0}
|
||||||
max={10}
|
max={10}
|
||||||
step={1}
|
step={1}
|
||||||
value={getValues('temperature')}
|
value={getValues('temperature')}
|
||||||
@@ -138,24 +138,17 @@ const ModelEditForm = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Box mt={4}>
|
<Box mt={4}>
|
||||||
{canTrain ? (
|
<Box mb={1}>系统提示词</Box>
|
||||||
<Box fontWeight={'bold'}>
|
<Textarea
|
||||||
训练的模型会自动根据知识库内容回答,无法设置系统prompt。注意:
|
rows={6}
|
||||||
使用该模型,在对话时需要消耗等多的 tokens
|
maxLength={-1}
|
||||||
</Box>
|
{...register('systemPrompt')}
|
||||||
) : (
|
placeholder={
|
||||||
<>
|
canTrain
|
||||||
<Box mb={1}>系统提示词</Box>
|
? '训练的模型会根据知识库内容,生成一部分系统提示词,因此在对话时需要消耗更多的 tokens。你仍可以增加一些提示词,让其效果更精确。'
|
||||||
<Textarea
|
: '模型默认的 prompt 词,通过调整该内容,可以生成一个限定范围的模型。\n\n注意,改功能会影响对话的整体朝向!'
|
||||||
rows={6}
|
}
|
||||||
maxLength={-1}
|
/>
|
||||||
{...register('systemPrompt')}
|
|
||||||
placeholder={
|
|
||||||
'模型默认的 prompt 词,通过调整该内容,可以生成一个限定范围的模型。\n\n注意,改功能会影响对话的整体朝向!'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Card>
|
</Card>
|
||||||
{/* <Card p={4}>
|
{/* <Card p={4}>
|
||||||
|
Reference in New Issue
Block a user