4.6.8-production (#822)

* Json completion (#16)

* json-completion

* fix duplicate

* fix

* fix: config json

* feat: query extension

* perf: i18n

* 468 doc

* json editor

* perf: doc

* perf: default extension model

* docker file

* doc

* perf: token count

* perf: search extension

* format

* perf: some constants data

---------

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-02-05 00:51:46 +08:00
committed by GitHub
parent ec8e2512bc
commit 51bbdf26a3
68 changed files with 4118 additions and 3787 deletions

View File

@@ -39,8 +39,8 @@ function Row({
{...(isCodeBlock
? { transform: 'translateY(-3px)' }
: value
? { px: 3, py: 1, border: theme.borders.base }
: {})}
? { px: 3, py: 1, border: theme.borders.base }
: {})}
>
{value && <Markdown source={strValue} />}
{rawDom}
@@ -129,126 +129,154 @@ const ResponseBox = React.memo(function ResponseBox({
<Tabs list={list} activeId={currentTab} onChange={setCurrentTab} />
</Box>
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
{activeModule?.price !== undefined && (
<>
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
{activeModule?.price !== undefined && (
<Row
label={t('core.chat.response.module price')}
value={`${formatStorePrice2Read(activeModule?.price)}`}
/>
)}
<Row
label={t('core.chat.response.module price')}
value={`${formatStorePrice2Read(activeModule?.price)}`}
label={t('core.chat.response.module time')}
value={`${activeModule?.runningTime || 0}s`}
/>
)}
<Row
label={t('core.chat.response.module time')}
value={`${activeModule?.runningTime || 0}s`}
/>
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
<Row label={t('wallet.bill.Chars length')} value={`${activeModule?.charsLength}`} />
<Row label={t('wallet.bill.Input Token Length')} value={`${activeModule?.inputTokens}`} />
<Row label={t('wallet.bill.Output Token Length')} value={`${activeModule?.outputTokens}`} />
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
<Row
label={t('core.chat.response.context total length')}
value={activeModule?.contextTotalLen}
/>
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
<Row label={t('wallet.bill.Chars length')} value={`${activeModule?.charsLength}`} />
<Row label={t('wallet.bill.Input Token Length')} value={`${activeModule?.inputTokens}`} />
<Row
label={t('wallet.bill.Output Token Length')}
value={`${activeModule?.outputTokens}`}
/>
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
<Row
label={t('core.chat.response.context total length')}
value={activeModule?.contextTotalLen}
/>
</>
{/* ai chat */}
<Row label={t('core.chat.response.module temperature')} value={activeModule?.temperature} />
<Row label={t('core.chat.response.module maxToken')} value={activeModule?.maxToken} />
<Row
label={t('core.chat.response.module historyPreview')}
rawDom={
activeModule.historyPreview ? (
<Box px={3} py={2} border={theme.borders.base} borderRadius={'md'}>
{activeModule.historyPreview?.map((item, i) => (
<Box
key={i}
_notLast={{
borderBottom: '1px solid',
borderBottomColor: 'myWhite.700',
mb: 2
}}
pb={2}
>
<Box fontWeight={'bold'}>{item.obj}</Box>
<Box whiteSpace={'pre-wrap'}>{item.value}</Box>
</Box>
))}
</Box>
) : (
''
)
}
/>
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
<>
<Row
label={t('core.chat.response.module quoteList')}
rawDom={<QuoteList isShare={isShare} rawSearch={activeModule.quoteList} />}
label={t('core.chat.response.module temperature')}
value={activeModule?.temperature}
/>
)}
<Row label={t('core.chat.response.module maxToken')} value={activeModule?.maxToken} />
<Row
label={t('core.chat.response.module historyPreview')}
rawDom={
activeModule.historyPreview ? (
<Box px={3} py={2} border={theme.borders.base} borderRadius={'md'}>
{activeModule.historyPreview?.map((item, i) => (
<Box
key={i}
_notLast={{
borderBottom: '1px solid',
borderBottomColor: 'myWhite.700',
mb: 2
}}
pb={2}
>
<Box fontWeight={'bold'}>{item.obj}</Box>
<Box whiteSpace={'pre-wrap'}>{item.value}</Box>
</Box>
))}
</Box>
) : (
''
)
}
/>
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
<Row
label={t('core.chat.response.module quoteList')}
rawDom={<QuoteList isShare={isShare} rawSearch={activeModule.quoteList} />}
/>
)}
</>
{/* dataset search */}
{activeModule?.searchMode && (
<>
{activeModule?.searchMode && (
<Row
label={t('core.dataset.search.search mode')}
// @ts-ignore
value={t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
/>
)}
<Row label={t('core.chat.response.module similarity')} value={activeModule?.similarity} />
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
<Row
label={t('core.dataset.search.search mode')}
// @ts-ignore
value={t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
label={t('core.chat.response.search using reRank')}
value={activeModule?.searchUsingReRank}
/>
)}
<Row label={t('core.chat.response.module similarity')} value={activeModule?.similarity} />
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
<Row
label={t('core.chat.response.search using reRank')}
value={activeModule?.searchUsingReRank}
/>
<Row
label={t('core.chat.response.Extension model')}
value={activeModule?.extensionModel}
/>
<Row
label={t('wallet.bill.Extension result')}
value={`${activeModule?.extensionResult}`}
/>
</>
{/* classify question */}
<Row
label={t('core.chat.response.module cq')}
value={(() => {
if (!activeModule?.cqList) return '';
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
})()}
/>
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
<>
<Row
label={t('core.chat.response.module cq')}
value={(() => {
if (!activeModule?.cqList) return '';
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
})()}
/>
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
</>
{/* extract */}
<Row
label={t('core.chat.response.module extract description')}
value={activeModule?.extractDescription}
/>
{activeModule?.extractResult && (
<>
<Row
label={t('core.chat.response.module extract result')}
value={`~~~json\n${JSON.stringify(activeModule?.extractResult, null, 2)}`}
label={t('core.chat.response.module extract description')}
value={activeModule?.extractDescription}
/>
)}
{activeModule?.extractResult && (
<Row
label={t('core.chat.response.module extract result')}
value={`~~~json\n${JSON.stringify(activeModule?.extractResult, null, 2)}`}
/>
)}
</>
{/* http */}
{activeModule?.body && (
<Row
label={t('core.chat.response.module http body')}
value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`}
/>
)}
{activeModule?.httpResult && (
<Row
label={t('core.chat.response.module http result')}
value={`~~~json\n${JSON.stringify(activeModule?.httpResult, null, 2)}`}
/>
)}
<>
{activeModule?.body && (
<Row
label={t('core.chat.response.module http body')}
value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`}
/>
)}
{activeModule?.httpResult && (
<Row
label={t('core.chat.response.module http result')}
value={`~~~json\n${JSON.stringify(activeModule?.httpResult, null, 2)}`}
/>
)}
</>
{/* plugin */}
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
<Row
label={t('core.chat.response.Plugin Resonse Detail')}
rawDom={<ResponseBox response={activeModule.pluginDetail} isShare={isShare} />}
/>
)}
{activeModule?.pluginOutput && (
<Row
label={t('core.chat.response.plugin output')}
value={`~~~json\n${JSON.stringify(activeModule?.pluginOutput, null, 2)}`}
/>
)}
<>
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
<Row
label={t('core.chat.response.Plugin Resonse Detail')}
rawDom={<ResponseBox response={activeModule.pluginDetail} isShare={isShare} />}
/>
)}
{activeModule?.pluginOutput && (
<Row
label={t('core.chat.response.plugin output')}
value={`~~~json\n${JSON.stringify(activeModule?.pluginOutput, null, 2)}`}
/>
)}
</>
{/* text output */}
<Row label={t('core.chat.response.text output')} value={activeModule?.textOutput} />

View File

@@ -1011,8 +1011,9 @@ export const useChatBox = () => {
const historyDom = document.getElementById('history');
if (!historyDom) return;
const dom = Array.from(historyDom.children).map((child, i) => {
const avatar = `<img src="${child.querySelector<HTMLImageElement>('.avatar')
?.src}" alt="" />`;
const avatar = `<img src="${
child.querySelector<HTMLImageElement>('.avatar')?.src
}" alt="" />`;
const chatContent = child.querySelector<HTMLDivElement>('.markdown');

View File

@@ -90,7 +90,7 @@ const MySlider = ({
borderRadius={'md'}
transform={'translate(-50%, -155%)'}
fontSize={'11px'}
display={'none'}
display={['block', 'none']}
>
<Box transform={'scale(0.9)'}>{value}</Box>
</SliderMark>

View File

@@ -1,11 +1,12 @@
import React, { useMemo } from 'react';
import { Box, Grid } from '@chakra-ui/react';
import { Box, Flex, Grid, Image } from '@chakra-ui/react';
import type { GridProps } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
// @ts-ignore
interface Props extends GridProps {
list: { id: string; label: string | React.ReactNode }[];
list: { id: string; icon?: string; label: string | React.ReactNode }[];
activeId: string;
size?: 'sm' | 'md' | 'lg';
onChange: (id: string) => void;
@@ -46,10 +47,11 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
{...props}
>
{list.map((item) => (
<Box
<Flex
key={item.id}
py={sizeMap.inlineP}
textAlign={'center'}
alignItems={'center'}
justifyContent={'center'}
borderBottom={'2px solid transparent'}
px={3}
whiteSpace={'nowrap'}
@@ -68,8 +70,17 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
onChange(item.id);
}}
>
{item.icon && (
<>
{item.icon.startsWith('/') ? (
<Image mr={1} src={item.icon} alt={''} w={'16px'} />
) : (
<MyIcon mr={1} name={item.icon as any} w={'16px'} />
)}
</>
)}
{typeof item.label === 'string' ? t(item.label) : item.label}
</Box>
</Flex>
))}
</Grid>
);

View File

@@ -34,14 +34,12 @@ const AIChatSettingsModal = ({
onClose,
onSuccess,
defaultData,
simpleModeTemplate = SimpleModeTemplate_FastGPT_Universal,
pickerMenu = []
}: {
isAdEdit?: boolean;
onClose: () => void;
onSuccess: (e: AIChatModuleProps) => void;
defaultData: AIChatModuleProps;
simpleModeTemplate?: AppSimpleEditConfigTemplateType;
pickerMenu?: EditorVariablePickerType[];
}) => {
const { t } = useTranslation();
@@ -160,119 +158,112 @@ const AIChatSettingsModal = ({
</Box>
</Flex>
)}
{simpleModeTemplate?.systemForm?.aiSettings?.temperature && (
<Flex mb={10} mt={isAdEdit ? 8 : 6}>
<Box {...LabelStyles} mr={2} w={'80px'}>
{t('core.app.Temperature')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: t('core.app.deterministic'), value: 0 },
{ label: t('core.app.Random'), value: 10 }
]}
width={'95%'}
min={0}
max={10}
value={getValues(ModuleInputKeyEnum.aiChatTemperature)}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatTemperature, e);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
)}
{simpleModeTemplate?.systemForm?.aiSettings?.maxToken && (
<Flex mt={5} mb={5}>
<Box {...LabelStyles} mr={2} w={'80px'}>
{t('core.app.Max tokens')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: '100', value: 100 },
{ label: `${tokenLimit}`, value: tokenLimit }
]}
width={'95%'}
min={100}
max={tokenLimit}
step={50}
value={getValues(ModuleInputKeyEnum.aiChatMaxToken)}
onChange={(val) => {
setValue(ModuleInputKeyEnum.aiChatMaxToken, val);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
)}
<Flex mb={10} mt={isAdEdit ? 8 : 6}>
<Box {...LabelStyles} mr={2} w={'80px'}>
{t('core.app.Temperature')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: t('core.app.deterministic'), value: 0 },
{ label: t('core.app.Random'), value: 10 }
]}
width={'95%'}
min={0}
max={10}
value={getValues(ModuleInputKeyEnum.aiChatTemperature)}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatTemperature, e);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex mt={5} mb={5}>
<Box {...LabelStyles} mr={2} w={'80px'}>
{t('core.app.Max tokens')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: '100', value: 100 },
{ label: `${tokenLimit}`, value: tokenLimit }
]}
width={'95%'}
min={100}
max={tokenLimit}
step={50}
value={getValues(ModuleInputKeyEnum.aiChatMaxToken)}
onChange={(val) => {
setValue(ModuleInputKeyEnum.aiChatMaxToken, val);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
{simpleModeTemplate?.systemForm?.aiSettings?.quoteTemplate && (
<Box>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote templates')}
<MyTooltip
label={t('template.Quote Content Tip', {
default: Prompt_QuoteTemplateList[0].value
})}
forceShow
>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<Box
{...selectTemplateBtn}
onClick={() =>
setSelectTemplateData({
title: t('core.app.Select quote template'),
templates: Prompt_QuoteTemplateList
})
}
>
{t('common.Select template')}
</Box>
</Flex>
<PromptEditor
variables={quoteTemplateVariables}
title={t('core.app.Quote templates')}
placeholder={t('template.Quote Content Tip', {
<Box>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote templates')}
<MyTooltip
label={t('template.Quote Content Tip', {
default: Prompt_QuoteTemplateList[0].value
})}
value={aiChatQuoteTemplate}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuoteTemplate, e);
// setRefresh(!refresh);
}}
/>
</Box>
)}
{simpleModeTemplate?.systemForm?.aiSettings?.quotePrompt && (
<Box mt={4}>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote prompt')}
<MyTooltip
label={t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value })}
forceShow
>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
<PromptEditor
variables={quotePromptVariables}
title={t('core.app.Quote prompt')}
h={220}
placeholder={t('template.Quote Prompt Tip', {
default: Prompt_QuotePromptList[0].value
})}
value={aiChatQuotePrompt}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuotePrompt, e);
}}
/>
</Box>
)}
forceShow
>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<Box
{...selectTemplateBtn}
onClick={() =>
setSelectTemplateData({
title: t('core.app.Select quote template'),
templates: Prompt_QuoteTemplateList
})
}
>
{t('common.Select template')}
</Box>
</Flex>
<PromptEditor
variables={quoteTemplateVariables}
h={160}
title={t('core.app.Quote templates')}
placeholder={t('template.Quote Content Tip', {
default: Prompt_QuoteTemplateList[0].value
})}
value={aiChatQuoteTemplate}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuoteTemplate, e);
// setRefresh(!refresh);
}}
/>
</Box>
<Box mt={4}>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote prompt')}
<MyTooltip
label={t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value })}
forceShow
>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
<PromptEditor
variables={quotePromptVariables}
title={t('core.app.Quote prompt')}
h={230}
placeholder={t('template.Quote Prompt Tip', {
default: Prompt_QuotePromptList[0].value
})}
value={aiChatQuotePrompt}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuotePrompt, e);
}}
/>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} onClick={onClose}>

View File

@@ -7,6 +7,7 @@ import {
Flex,
ModalBody,
ModalFooter,
Switch,
Textarea,
useTheme
} from '@chakra-ui/react';
@@ -23,15 +24,27 @@ import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
import MyRadio from '@/components/common/MyRadio';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Tabs from '@/components/Tabs';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import SelectAiModel from '@/components/Select/SelectAiModel';
type DatasetParamsProps = {
export type DatasetParamsProps = {
searchMode: `${DatasetSearchModeEnum}`;
searchEmptyText?: string;
limit?: number;
similarity?: number;
usingReRank?: boolean;
datasetSearchUsingExtensionQuery?: boolean;
datasetSearchExtensionModel?: string;
datasetSearchExtensionBg?: string;
maxTokens?: number;
searchEmptyText?: string;
};
enum SearchSettingTabEnum {
searchMode = 'searchMode',
limit = 'limit',
queryExtension = 'queryExtension'
}
const DatasetParamsModal = ({
searchMode = DatasetSearchModeEnum.embedding,
@@ -40,22 +53,39 @@ const DatasetParamsModal = ({
similarity,
usingReRank,
maxTokens = 3000,
datasetSearchUsingExtensionQuery,
datasetSearchExtensionModel,
datasetSearchExtensionBg,
onClose,
onSuccess
}: DatasetParamsProps & { onClose: () => void; onSuccess: (e: DatasetParamsProps) => void }) => {
const { t } = useTranslation();
const theme = useTheme();
const { reRankModelList } = useSystemStore();
const { reRankModelList, llmModelList } = useSystemStore();
const [refresh, setRefresh] = useState(false);
const { register, setValue, getValues, handleSubmit } = useForm<DatasetParamsProps>({
const [currentTabType, setCurrentTabType] = useState(SearchSettingTabEnum.searchMode);
const { register, setValue, getValues, handleSubmit, watch } = useForm<DatasetParamsProps>({
defaultValues: {
searchEmptyText,
limit,
similarity,
searchMode,
usingReRank
usingReRank,
datasetSearchUsingExtensionQuery,
datasetSearchExtensionModel: datasetSearchExtensionModel ?? llmModelList[0]?.model,
datasetSearchExtensionBg
}
});
const datasetSearchUsingCfrForm = watch('datasetSearchUsingExtensionQuery');
const queryExtensionModel = watch('datasetSearchExtensionModel');
const cfbBgDesc = watch('datasetSearchExtensionBg');
const chatModelSelectList = (() =>
llmModelList.map((item) => ({
value: item.model,
label: item.name
})))();
const searchModeList = useMemo(() => {
const list = Object.values(DatasetSearchModeMap);
@@ -82,125 +112,209 @@ const DatasetParamsModal = ({
iconSrc="/imgs/modal/params.svg"
title={t('core.dataset.search.Dataset Search Params')}
w={['90vw', '550px']}
h={['90vh', 'auto']}
isCentered={searchEmptyText !== undefined}
>
<ModalBody flex={['1 0 0', 'auto']} overflow={'auto'}>
<MyRadio
gridGap={2}
gridTemplateColumns={'repeat(1,1fr)'}
list={searchModeList}
value={getValues('searchMode')}
onChange={(e) => {
setValue('searchMode', e as `${DatasetSearchModeEnum}`);
setRefresh(!refresh);
}}
<ModalBody flex={'auto'} overflow={'auto'}>
<Tabs
mb={3}
list={[
{
icon: 'modal/setting',
label: t('core.dataset.search.search mode'),
id: SearchSettingTabEnum.searchMode
},
{
icon: 'support/outlink/apikeyFill',
label: t('core.dataset.search.Filter'),
id: SearchSettingTabEnum.limit
},
{
label: t('core.module.template.Query extension'),
id: SearchSettingTabEnum.queryExtension,
icon: '/imgs/module/cfr.svg'
}
]}
activeId={currentTabType}
onChange={(e) => setCurrentTabType(e as any)}
/>
{usingReRank !== undefined && reRankModelList.length > 0 && (
{currentTabType === SearchSettingTabEnum.searchMode && (
<>
<Divider my={4} />
<Flex
alignItems={'center'}
cursor={'pointer'}
userSelect={'none'}
py={3}
pl={'14px'}
pr={'16px'}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
position={'relative'}
{...(getValues('usingReRank')
? {
borderColor: 'primary.400'
}
: {})}
onClick={(e) => {
setValue('usingReRank', !getValues('usingReRank'));
setRefresh((state) => !state);
<MyRadio
gridGap={2}
gridTemplateColumns={'repeat(1,1fr)'}
list={searchModeList}
value={getValues('searchMode')}
onChange={(e) => {
setValue('searchMode', e as `${DatasetSearchModeEnum}`);
setRefresh(!refresh);
}}
>
<MyIcon name="core/dataset/rerank" w={'18px'} mr={'14px'} />
<Box pr={2} color={'myGray.800'} flex={'1 0 0'}>
<Box>{t('core.dataset.search.ReRank')}</Box>
<Box fontSize={['xs', 'sm']} color={'myGray.500'}>
{t('core.dataset.search.ReRank desc')}
</Box>
</Box>
<Box position={'relative'} w={'18px'} h={'18px'}>
<Checkbox colorScheme="primary" isChecked={getValues('usingReRank')} size="lg" />
<Box position={'absolute'} top={0} right={0} bottom={0} left={0} zIndex={1}></Box>
</Box>
</Flex>
/>
{usingReRank !== undefined && reRankModelList.length > 0 && (
<>
<Divider my={4} />
<Flex
alignItems={'center'}
cursor={'pointer'}
userSelect={'none'}
py={3}
pl={'14px'}
pr={'16px'}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
position={'relative'}
{...(getValues('usingReRank')
? {
borderColor: 'primary.400'
}
: {})}
onClick={(e) => {
setValue('usingReRank', !getValues('usingReRank'));
setRefresh((state) => !state);
}}
>
<MyIcon name="core/dataset/rerank" w={'18px'} mr={'14px'} />
<Box pr={2} color={'myGray.800'} flex={'1 0 0'}>
<Box>{t('core.dataset.search.ReRank')}</Box>
<Box fontSize={['xs', 'sm']} color={'myGray.500'}>
{t('core.dataset.search.ReRank desc')}
</Box>
</Box>
<Box position={'relative'} w={'18px'} h={'18px'}>
<Checkbox
colorScheme="primary"
isChecked={getValues('usingReRank')}
size="lg"
/>
<Box
position={'absolute'}
top={0}
right={0}
bottom={0}
left={0}
zIndex={1}
></Box>
</Box>
</Flex>
</>
)}
</>
)}
{limit !== undefined && (
<Box display={['block', 'flex']} mt={5}>
<Box flex={'0 0 120px'} mb={[8, 0]}>
{t('core.dataset.search.Max Tokens')}
<MyTooltip label={t('core.dataset.search.Max Tokens Tips')} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Box flex={1} mx={4}>
<MySlider
markList={[
{ label: '100', value: 100 },
{ label: maxTokens, value: maxTokens }
]}
min={100}
max={maxTokens}
step={50}
value={getValues(ModuleInputKeyEnum.datasetMaxTokens) ?? 1000}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetMaxTokens, val);
setRefresh(!refresh);
}}
/>
</Box>
{currentTabType === SearchSettingTabEnum.limit && (
<Box pt={5}>
{limit !== undefined && (
<Box display={['block', 'flex']}>
<Box flex={'0 0 120px'} mb={[8, 0]}>
{t('core.dataset.search.Max Tokens')}
<MyTooltip label={t('core.dataset.search.Max Tokens Tips')} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Box flex={1} mx={4}>
<MySlider
markList={[
{ label: '100', value: 100 },
{ label: maxTokens, value: maxTokens }
]}
min={100}
max={maxTokens}
step={50}
value={getValues(ModuleInputKeyEnum.datasetMaxTokens) ?? 1000}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetMaxTokens, val);
setRefresh(!refresh);
}}
/>
</Box>
</Box>
)}
{showSimilarity && (
<Box display={['block', 'flex']} mt={10}>
<Box flex={'0 0 120px'} mb={[8, 0]}>
{t('core.dataset.search.Min Similarity')}
<MyTooltip label={t('core.dataset.search.Min Similarity Tips')} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Box flex={1} mx={4}>
<MySlider
markList={[
{ label: '0', value: 0 },
{ label: '1', value: 1 }
]}
min={0}
max={1}
step={0.01}
value={getValues(ModuleInputKeyEnum.datasetSimilarity) ?? 0.5}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetSimilarity, val);
setRefresh(!refresh);
}}
/>
</Box>
</Box>
)}
{searchEmptyText !== undefined && (
<Box display={['block', 'flex']} pt={3}>
<Box flex={'0 0 120px'} mb={[2, 0]}>
{t('core.dataset.search.Empty result response')}
</Box>
<Box flex={1}>
<Textarea
rows={5}
maxLength={500}
placeholder={t('core.dataset.search.Empty result response Tips')}
{...register('searchEmptyText')}
></Textarea>
</Box>
</Box>
)}
</Box>
)}
{showSimilarity && (
<Box display={['block', 'flex']} mt={5}>
<Box flex={'0 0 120px'} mb={[8, 0]}>
{t('core.dataset.search.Min Similarity')}
<MyTooltip label={t('core.dataset.search.Min Similarity Tips')} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Box flex={1} mx={4}>
<MySlider
markList={[
{ label: '0', value: 0 },
{ label: '1', value: 1 }
]}
min={0}
max={1}
step={0.01}
value={getValues(ModuleInputKeyEnum.datasetSimilarity) ?? 0.5}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetSimilarity, val);
setRefresh(!refresh);
}}
/>
</Box>
</Box>
)}
{searchEmptyText !== undefined && (
<Box display={['block', 'flex']} pt={3}>
<Box flex={'0 0 120px'} mb={[2, 0]}>
{t('core.dataset.search.Empty result response')}
</Box>
<Box flex={1}>
<Textarea
rows={5}
maxLength={500}
placeholder={t('core.dataset.search.Empty result response Tips')}
{...register('searchEmptyText')}
></Textarea>
{currentTabType === SearchSettingTabEnum.queryExtension && (
<Box>
<Box fontSize={'xs'} color={'myGray.500'}>
{t('core.module.template.Query extension intro')}
</Box>
<Flex mt={3} alignItems={'center'}>
<Box flex={'1 0 0'}>{t('core.dataset.search.Using query extension')}</Box>
<Switch {...register('datasetSearchUsingExtensionQuery')} />
</Flex>
{datasetSearchUsingCfrForm === true && (
<>
<Flex mt={4} alignItems={'center'}>
<Box flex={'0 0 100px'}>{t('core.ai.Model')}</Box>
<Box flex={'1 0 0'}>
<SelectAiModel
width={'100%'}
value={queryExtensionModel}
list={chatModelSelectList}
onchange={(val: any) => {
setValue('datasetSearchExtensionModel', val);
}}
/>
</Box>
</Flex>
<Box mt={3}>
<Flex alignItems={'center'}>
{t('core.app.edit.Query extension background prompt')}
<MyTooltip label={t('core.app.edit.Query extension background tip')} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
<Box mt={1}>
<PromptEditor
h={200}
showOpenModal={false}
placeholder={t('core.module.QueryExtension.placeholder')}
value={cfbBgDesc}
onChange={(e) => {
setValue('datasetSearchExtensionBg', e);
}}
/>
</Box>
</Box>
</>
)}
</Box>
)}
</ModalBody>

View File

@@ -53,8 +53,8 @@ const TTSSelect = ({
if (e === TTSTypeEnum.none || e === TTSTypeEnum.web) {
onChange({ type: e as `${TTSTypeEnum}` });
} else {
const audioModel = audioSpeechModelList.find(
(item) => item.voices?.find((voice) => voice.value === e)
const audioModel = audioSpeechModelList.find((item) =>
item.voices?.find((voice) => voice.value === e)
);
if (!audioModel) {
return;

View File

@@ -0,0 +1,26 @@
import React from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from '../render/NodeCard';
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
import Divider from '../modules/Divider';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
import RenderOutput from '../render/RenderOutput';
const NodeHttp = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs } = data;
return (
<NodeCard minW={'350px'} selected={selected} {...data}>
<Divider text="Input" />
<Container>
<RenderInput moduleId={moduleId} flowInputList={inputs} />
</Container>
<Divider text="Output" />
<Container>
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
</Container>
</NodeCard>
);
};
export default React.memo(NodeHttp);

View File

@@ -61,8 +61,9 @@ const NodeCard = (props: Props) => {
icon: 'common/refreshLight',
label: t('plugin.Synchronous version'),
onClick: () => {
const pluginId = inputs.find((item) => item.key === ModuleInputKeyEnum.pluginId)
?.value;
const pluginId = inputs.find(
(item) => item.key === ModuleInputKeyEnum.pluginId
)?.value;
if (!pluginId) return;
openConfirm(async () => {
try {

View File

@@ -1,11 +1,34 @@
import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { onChangeNode, useFlowProviderStore } from '../../../../FlowProvider';
import { useTranslation } from 'next-i18next';
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
import {
formatEditorVariablePickerIcon,
getGuideModule,
splitGuideModule
} from '@fastgpt/global/core/module/utils';
const JsonEditor = ({ item, moduleId }: RenderInputProps) => {
const JsonEditor = ({ inputs = [], item, moduleId }: RenderInputProps) => {
const { t } = useTranslation();
const { nodes } = useFlowProviderStore();
// get variable
const variables = useMemo(() => {
const globalVariables = formatEditorVariablePickerIcon(
splitGuideModule(getGuideModule(nodes.map((node) => node.data)))?.variableModules || []
);
const moduleVariables = formatEditorVariablePickerIcon(
inputs
.filter((input) => input.edit)
.map((item) => ({
key: item.key,
label: item.label
}))
);
return [...globalVariables, ...moduleVariables];
}, [inputs, nodes]);
const update = useCallback(
(value: string) => {
@@ -28,10 +51,11 @@ const JsonEditor = ({ item, moduleId }: RenderInputProps) => {
bg={'myWhite.400'}
placeholder={t(item.placeholder || '')}
resize
defaultValue={item.value}
value={item.value}
onChange={(e) => {
update(e);
}}
variables={variables}
/>
);
};

View File

@@ -7,19 +7,24 @@ import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import MyIcon from '@fastgpt/web/components/common/Icon';
import DatasetParamsModal from '@/components/core/module/DatasetParamsModal';
import DatasetParamsModal, {
DatasetParamsProps
} from '@/components/core/module/DatasetParamsModal';
import { useSystemStore } from '@/web/common/system/useSystemStore';
const SelectDatasetParam = ({ inputs = [], moduleId }: RenderInputProps) => {
const { nodes } = useFlowProviderStore();
const { t } = useTranslation();
const { llmModelList } = useSystemStore();
const [data, setData] = useState({
const [data, setData] = useState<DatasetParamsProps>({
searchMode: DatasetSearchModeEnum.embedding,
limit: 5,
similarity: 0.5,
usingReRank: false
usingReRank: false,
datasetSearchUsingExtensionQuery: true,
datasetSearchExtensionModel: llmModelList[0]?.model,
datasetSearchExtensionBg: ''
});
const tokenLimit = useMemo(() => {
@@ -69,6 +74,7 @@ const SelectDatasetParam = ({ inputs = [], moduleId }: RenderInputProps) => {
maxTokens={tokenLimit}
onClose={onClose}
onSuccess={(e) => {
setData(e);
for (let key in e) {
const item = inputs.find((input) => input.key === key);
if (!item) continue;

View File

@@ -25,7 +25,7 @@ const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
[FlowNodeTypeEnum.answerNode]: dynamic(() => import('./components/nodes/NodeAnswer')),
[FlowNodeTypeEnum.classifyQuestion]: dynamic(() => import('./components/nodes/NodeCQNode')),
[FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./components/nodes/NodeExtract')),
[FlowNodeTypeEnum.httpRequest]: NodeSimple,
[FlowNodeTypeEnum.httpRequest]: dynamic(() => import('./components/nodes/NodeHttp')),
[FlowNodeTypeEnum.runApp]: NodeSimple,
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodePluginInput')),
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),

View File

@@ -14,9 +14,6 @@ export const SimpleModeTemplate_FastGPT_Universal: AppSimpleEditConfigTemplateTy
quoteTemplate: true,
quotePrompt: true
},
cfr: {
background: true
},
dataset: {
datasets: true,
similarity: true,

View File

@@ -8,6 +8,7 @@ import {
DatasetDataIndexItemType,
SearchDataResponseItemType
} from '@fastgpt/global/core/dataset/type';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
/* ================= dataset ===================== */
export type CreateDatasetParams = {
@@ -50,10 +51,13 @@ export type GetTrainingQueueResponse = {
export type SearchTestProps = {
datasetId: string;
text: string;
limit?: number;
searchMode?: `${DatasetSearchModeEnum}`;
usingReRank: boolean;
similarity?: number;
[ModuleInputKeyEnum.datasetSimilarity]?: number;
[ModuleInputKeyEnum.datasetMaxTokens]?: number;
[ModuleInputKeyEnum.datasetSearchMode]?: `${DatasetSearchModeEnum}`;
[ModuleInputKeyEnum.datasetSearchUsingReRank]?: boolean;
[ModuleInputKeyEnum.datasetSearchUsingExtensionQuery]?: boolean;
[ModuleInputKeyEnum.datasetSearchExtensionModel]?: string;
[ModuleInputKeyEnum.datasetSearchExtensionBg]?: string;
};
export type SearchTestResponse = {
list: SearchDataResponseItemType[];

View File

@@ -58,7 +58,7 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
问题:"{{question}}"`
问题:"""{{question}}"""`
},
{
title: '问答模板',
@@ -73,7 +73,7 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
- 如果没有相关的问答对,你需要澄清。
- 避免提及你是从 QA 获取的知识,只需要回复答案。
问题:"{{question}}"`
问题:"""{{question}}"""`
},
{
title: '标准严格模板',
@@ -93,7 +93,7 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
问题:"{{question}}"`
问题:"""{{question}}"""`
},
{
title: '严格问答模板',
@@ -111,6 +111,6 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
最后,避免提及你是从 QA 获取的知识,只需要回复答案。
问题:"{{question}}"`
问题:"""{{question}}"""`
}
];

View File

@@ -66,13 +66,13 @@ export async function getInitConfig() {
await Promise.all([
initGlobal(),
initSystemConfig(),
getSimpleModeTemplates(),
// getSimpleModeTemplates(),
getSystemVersion(),
getSystemPlugin()
]);
console.log({
simpleModeTemplates: global.simpleModeTemplates,
// simpleModeTemplates: global.simpleModeTemplates,
communityPlugins: global.communityPlugins
});
} catch (error) {
@@ -165,38 +165,38 @@ export function getSystemVersion() {
}
}
async function getSimpleModeTemplates() {
if (global.simpleModeTemplates && global.simpleModeTemplates.length > 0) return;
// async function getSimpleModeTemplates() {
// if (global.simpleModeTemplates && global.simpleModeTemplates.length > 0) return;
try {
const basePath =
process.env.NODE_ENV === 'development' ? 'data/simpleTemplates' : '/app/data/simpleTemplates';
// read data/simpleTemplates directory, get all json file
const files = readdirSync(basePath);
// filter json file
const filterFiles = files.filter((item) => item.endsWith('.json'));
// try {
// const basePath =
// process.env.NODE_ENV === 'development' ? 'data/simpleTemplates' : '/app/data/simpleTemplates';
// // read data/simpleTemplates directory, get all json file
// const files = readdirSync(basePath);
// // filter json file
// const filterFiles = files.filter((item) => item.endsWith('.json'));
// read json file
const fileTemplates = filterFiles.map((item) => {
const content = readFileSync(`${basePath}/${item}`, 'utf-8');
return {
id: item.replace('.json', ''),
...JSON.parse(content)
};
});
// // read json file
// const fileTemplates = filterFiles.map((item) => {
// const content = readFileSync(`${basePath}/${item}`, 'utf-8');
// return {
// id: item.replace('.json', ''),
// ...JSON.parse(content)
// };
// });
// fetch templates from plus
const plusTemplates = await getSimpleTemplatesFromPlus();
// // fetch templates from plus
// const plusTemplates = await getSimpleTemplatesFromPlus();
global.simpleModeTemplates = [
SimpleModeTemplate_FastGPT_Universal,
...plusTemplates,
...fileTemplates
];
} catch (error) {
global.simpleModeTemplates = [SimpleModeTemplate_FastGPT_Universal];
}
}
// global.simpleModeTemplates = [
// SimpleModeTemplate_FastGPT_Universal,
// ...plusTemplates,
// ...fileTemplates
// ];
// } catch (error) {
// global.simpleModeTemplates = [SimpleModeTemplate_FastGPT_Universal];
// }
// }
function getSystemPlugin() {
if (global.communityPlugins && global.communityPlugins.length > 0) return;

View File

@@ -294,7 +294,7 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
valueType: 'string',
targets: [
{
moduleId: 'vuc92c',
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
@@ -387,15 +387,6 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
value: true,
connected: false
},
{
key: 'datasetParamsModal',
type: 'selectDatasetParamsModal',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'userChatInput',
type: 'target',
@@ -495,19 +486,6 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
label: '温度',
value: 0,
valueType: 'number',
min: 0,
max: 10,
step: 1,
markList: [
{
label: '严谨',
value: 0
},
{
label: '发散',
value: 10
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
@@ -518,19 +496,6 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
label: '回复上限',
value: maxToken,
valueType: 'number',
min: 100,
max: 4000,
step: 50,
markList: [
{
label: '100',
value: 100
},
{
label: '4000',
value: 4000
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
@@ -649,89 +614,6 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
targets: []
}
]
},
{
moduleId: 'vuc92c',
name: 'core.module.template.cfr',
avatar: '/imgs/module/cfr.svg',
flowType: 'cfr',
showStatus: true,
position: {
x: 758.2985382279098,
y: 1124.6527309337314
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectExtractModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
value: getLLMModel().model,
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.module.input.label.cfr background',
max: 300,
value: formData.cfr.background,
valueType: 'string',
description: 'core.module.input.description.cfr background',
placeholder: 'core.module.input.placeholder.cfr background',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'system_text',
label: 'core.module.output.label.cfr result',
valueType: 'string',
type: 'source',
targets: [
{
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
}
]
}
];

View File

@@ -290,7 +290,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
valueType: 'string',
targets: [
{
moduleId: 'vuc92c',
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
@@ -335,19 +335,6 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
label: '最低相关性',
value: formData.dataset.similarity,
valueType: 'number',
min: 0,
max: 1,
step: 0.01,
markList: [
{
label: '0',
value: 0
},
{
label: '1',
value: 1
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
@@ -384,12 +371,33 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
connected: false
},
{
key: 'datasetParamsModal',
type: 'selectDatasetParamsModal',
key: 'datasetSearchUsingExtensionQuery',
type: 'hidden',
label: '',
valueType: 'any',
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.datasetSearchUsingExtensionQuery,
connected: false
},
{
key: 'datasetSearchExtensionBg',
type: 'hidden',
label: '',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.datasetSearchExtensionBg,
connected: false
},
{
key: 'datasetSearchExtensionModel',
type: 'hidden',
label: '',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.datasetSearchExtensionModel,
connected: false
},
{
@@ -659,89 +667,6 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
targets: []
}
]
},
{
moduleId: 'vuc92c',
name: 'core.module.template.cfr',
avatar: '/imgs/module/cfr.svg',
flowType: 'cfr',
showStatus: true,
position: {
x: 758.2985382279098,
y: 1124.6527309337314
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectExtractModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
value: getLLMModel().model,
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.module.input.label.cfr background',
max: 300,
value: formData.cfr.background,
valueType: 'string',
description: 'core.module.input.description.cfr background',
placeholder: 'core.module.input.placeholder.cfr background',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'system_text',
label: 'core.module.output.label.cfr result',
valueType: 'string',
type: 'source',
targets: [
{
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
}
]
}
];

View File

@@ -9,7 +9,9 @@ import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
import { searchDatasetData } from '@/service/core/dataset/data/controller';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { searchQueryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
import { getLLMModel } from '@/service/core/ai/model';
import { queryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
import { datasetSearchQueryExtension } from '@fastgpt/service/core/dataset/search/utils';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -20,13 +22,16 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
limit = 1500,
similarity,
searchMode,
usingReRank
usingReRank,
datasetSearchUsingExtensionQuery = false,
datasetSearchExtensionModel,
datasetSearchExtensionBg = ''
} = req.body as SearchTestProps;
if (!datasetId || !text) {
throw new Error('缺少参数');
}
const start = Date.now();
// auth dataset role
@@ -37,20 +42,24 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
datasetId,
per: 'r'
});
// auth balance
await authTeamBalance(teamId);
// query extension
// const { queries } = await searchQueryExtension({
// query: text,
// model: global.llmModel[0].model
// });
const extensionModel =
datasetSearchUsingExtensionQuery && datasetSearchExtensionModel
? getLLMModel(datasetSearchExtensionModel)
: undefined;
const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({
query: text,
extensionModel,
extensionBg: datasetSearchExtensionBg
});
const { searchRes, charsLength, ...result } = await searchDatasetData({
teamId,
rawQuery: text,
queries: [text],
reRankQuery: rewriteQuery,
queries: concatQueries,
model: dataset.vectorModel,
limit: Math.min(limit, 20000),
similarity,
@@ -65,7 +74,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
tmbId,
charsLength,
model: dataset.vectorModel,
source: apikey ? BillSourceEnum.api : BillSourceEnum.fastgpt
source: apikey ? BillSourceEnum.api : BillSourceEnum.fastgpt,
...(aiExtensionResult &&
extensionModel && {
extensionModel: extensionModel.name,
extensionInputTokens: aiExtensionResult.inputTokens,
extensionOutputTokens: aiExtensionResult.outputTokens
})
});
if (apikey) {
updateApiKeyUsage({

View File

@@ -1,61 +0,0 @@
import React, { useCallback, useState, useTransition } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import { Box, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
const CfrEditModal = ({
defaultValue = '',
onClose,
onFinish
}: {
defaultValue?: string;
onClose: () => void;
onFinish: (value: string) => void;
}) => {
const { t } = useTranslation();
const [value, setValue] = useState(defaultValue);
return (
<MyModal
isOpen
onClose={onClose}
iconSrc="/imgs/module/cfr.svg"
w={'500px'}
title={t('core.module.template.cfr')}
>
<ModalBody>
{t('core.app.edit.cfr background prompt')}
<MyTooltip label={t('core.app.edit.cfr background tip')} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box mt={1} flex={1}>
<PromptEditor
h={200}
showOpenModal={false}
placeholder={t('core.module.input.placeholder.cfr background')}
value={value}
onChange={(e) => {
setValue(e);
}}
/>
</Box>
</ModalBody>
<ModalFooter>
<Button
onClick={() => {
onFinish(value);
onClose();
}}
>
{t('common.Done')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default React.memo(CfrEditModal);

View File

@@ -30,7 +30,6 @@ import MySelect from '@/components/Select';
import MyTooltip from '@/components/MyTooltip';
import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import VariableEdit from '@/components/core/module/Flow/components/modules/VariableEdit';
import MyTextarea from '@/components/common/Textarea/MyTextarea/index';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
@@ -45,7 +44,6 @@ const TTSSelect = dynamic(
() => import('@/components/core/module/Flow/components/modules/TTSSelect')
);
const QGSwitch = dynamic(() => import('@/components/core/module/Flow/components/modules/QGSwitch'));
const CfrEditModal = dynamic(() => import('./CfrEditModal'));
const EditForm = ({
divRef,
@@ -59,7 +57,7 @@ const EditForm = ({
const { t } = useTranslation();
const { appDetail, updateAppDetail } = useAppStore();
const { loadAllDatasets, allDatasets } = useDatasetStore();
const { isPc, llmModelList, reRankModelList, simpleModeTemplates } = useSystemStore();
const { isPc, llmModelList, reRankModelList } = useSystemStore();
const [refresh, setRefresh] = useState(false);
const [, startTst] = useTransition();
@@ -88,19 +86,16 @@ const EditForm = ({
onOpen: onOpenDatasetParams,
onClose: onCloseDatasetParams
} = useDisclosure();
const {
isOpen: isOpenCfrModal,
onOpen: onOpenCfrModal,
onClose: onCloseCfrModal
} = useDisclosure();
const { openConfirm: openConfirmSave, ConfirmModal: ConfirmSaveModal } = useConfirm({
content: t('core.app.edit.Confirm Save App Tip')
});
const aiSystemPrompt = watch('aiSettings.systemPrompt');
const selectLLMModel = watch('aiSettings.model');
const datasetSearchSetting = watch('dataset');
const variables = watch('userGuide.variables');
const formatVariables = useMemo(() => formatEditorVariablePickerIcon(variables), [variables]);
const aiSystemPrompt = watch('aiSettings.systemPrompt');
const searchMode = watch('dataset.searchMode');
const chatModelSelectList = (() =>
@@ -114,16 +109,9 @@ const EditForm = ({
[allDatasets, datasets]
);
const selectSimpleTemplate = (() =>
simpleModeTemplates?.find((item) => item.id === getValues('templateId')) ||
SimpleModeTemplate_FastGPT_Universal)();
const tokenLimit = useMemo(() => {
return (
llmModelList.find((item) => item.model === getValues('aiSettings.model'))?.quoteMaxToken ||
3000
);
}, [getValues, llmModelList]);
return llmModelList.find((item) => item.model === selectLLMModel)?.quoteMaxToken || 3000;
}, [selectLLMModel, llmModelList]);
const datasetSearchMode = useMemo(() => {
if (!searchMode) return '';
@@ -132,12 +120,11 @@ const EditForm = ({
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
mutationFn: async (data: AppSimpleEditFormType) => {
const modules = await postForm2Modules(data, data.templateId);
const modules = await postForm2Modules(data);
await updateAppDetail(appDetail._id, {
modules,
type: AppTypeEnum.simple,
simpleTemplateId: data.templateId,
permission: undefined
});
},
@@ -149,7 +136,6 @@ const EditForm = ({
['init', appDetail],
() => {
const formatVal = appModules2Form({
templateId: appDetail.simpleTemplateId,
modules: appDetail.modules
});
reset(formatVal);
@@ -228,7 +214,7 @@ const EditForm = ({
<Box px={4}>
<Box bg={'white'} borderRadius={'md'} borderWidth={'1px'} borderColor={'borderColor.base'}>
{/* simple mode select */}
<Flex {...BoxStyles}>
{/* <Flex {...BoxStyles}>
<Flex alignItems={'center'} flex={'1 0 0'}>
<MyIcon name={'core/app/simpleMode/template'} w={'20px'} />
<Box mx={2}>{t('core.app.simple.mode template select')}</Box>
@@ -248,237 +234,187 @@ const EditForm = ({
setRefresh(!refresh);
}}
/>
</Flex>
</Flex> */}
{/* ai */}
{selectSimpleTemplate?.systemForm?.aiSettings && (
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<MyIcon name={'core/app/simpleMode/ai'} w={'20px'} />
<Box ml={2} flex={1}>
{t('app.AI Settings')}
</Box>
{(selectSimpleTemplate.systemForm.aiSettings.maxToken ||
selectSimpleTemplate.systemForm.aiSettings.temperature ||
selectSimpleTemplate.systemForm.aiSettings.quoteTemplate ||
selectSimpleTemplate.systemForm.aiSettings.quotePrompt) && (
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
<MyIcon mr={1} name={'common/settingLight'} w={'14px'} />
{t('common.More settings')}
</Flex>
)}
</Flex>
{selectSimpleTemplate.systemForm.aiSettings?.model && (
<Flex alignItems={'center'} mt={5}>
<Box {...LabelStyles}>{t('core.ai.Model')}</Box>
<Box flex={'1 0 0'}>
<SelectAiModel
width={'100%'}
value={getValues(`aiSettings.model`)}
list={chatModelSelectList}
onchange={(val: any) => {
setValue('aiSettings.model', val);
const maxToken =
llmModelList.find((item) => item.model === getValues('aiSettings.model'))
?.maxResponse || 4000;
const token = maxToken / 2;
setValue('aiSettings.maxToken', token);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
)}
{selectSimpleTemplate.systemForm.aiSettings?.systemPrompt && (
<Flex mt={10} alignItems={'flex-start'}>
<Box {...LabelStyles}>
{t('core.ai.Prompt')}
<MyTooltip label={t(chatNodeSystemPromptTip)} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
{isInitd && (
<PromptEditor
value={aiSystemPrompt}
onChange={(text) => {
startTst(() => {
setValue('aiSettings.systemPrompt', text);
});
}}
variables={formatVariables}
placeholder={t('core.app.tip.chatNodeSystemPromptTip')}
title={t('core.ai.Prompt')}
/>
)}
</Flex>
)}
</Box>
)}
{/* dataset */}
{selectSimpleTemplate?.systemForm?.dataset && (
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} flex={1}>
<MyIcon name={'core/app/simpleMode/dataset'} w={'20px'} />
<Box ml={2}>{t('core.dataset.Choose Dataset')}</Box>
</Flex>
{selectSimpleTemplate.systemForm.dataset.datasets && (
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbSelect}>
<SmallAddIcon />
{t('common.Choose')}
</Flex>
)}
{(selectSimpleTemplate.systemForm.dataset.limit ||
selectSimpleTemplate.systemForm.dataset.searchMode ||
selectSimpleTemplate.systemForm.dataset.searchEmptyText ||
selectSimpleTemplate.systemForm.dataset.similarity) && (
<Flex
alignItems={'center'}
ml={3}
{...BoxBtnStyles}
onClick={onOpenDatasetParams}
>
<MyIcon name={'edit'} w={'14px'} mr={1} />
{t('common.Params')}
</Flex>
)}
</Flex>
{getValues('dataset.datasets').length > 0 && (
<Flex mt={1} color={'myGray.600'} fontSize={'sm'} mb={2}>
{t('core.dataset.search.search mode')}: {datasetSearchMode}
{', '}
{reRankModelList.length > 0 && (
<>
{t('core.dataset.search.ReRank')}:{' '}
{getValues('dataset.usingReRank') ? '✅' : '✖'}
</>
)}
{', '}
{t('core.dataset.search.Min Similarity')}: {getValues('dataset.similarity')}
{', '}
{t('core.dataset.search.Max Tokens')}: {getValues('dataset.limit')}
{getValues('dataset.searchEmptyText') === ''
? ''
: t('core.dataset.Set Empty Result Tip')}
</Flex>
)}
<Grid
gridTemplateColumns={['repeat(2, minmax(0, 1fr))', 'repeat(3, minmax(0, 1fr))']}
gridGap={[2, 4]}
>
{selectDatasets.map((item) => (
<MyTooltip key={item._id} label={t('core.dataset.Read Dataset')}>
<Flex
overflow={'hidden'}
alignItems={'center'}
p={2}
bg={'white'}
boxShadow={
'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'
}
borderRadius={'md'}
border={theme.borders.base}
cursor={'pointer'}
onClick={() =>
router.push({
pathname: '/dataset/detail',
query: {
datasetId: item._id
}
})
}
>
<Avatar src={item.avatar} w={'18px'} mr={1} />
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
{item.name}
</Box>
</Flex>
</MyTooltip>
))}
</Grid>
</Box>
)}
{/* cfr */}
{selectSimpleTemplate?.systemForm?.cfr && getValues('dataset.datasets').length > 0 && (
<Flex {...BoxStyles} alignItems={'center'}>
<Image src={'/imgs/module/cfr.svg'} alt={''} w={'18px'} />
<Box ml={2}>{t('core.module.template.cfr')}</Box>
<MyTooltip label={t('core.module.template.cfr intro')} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<Flex {...BoxBtnStyles} onClick={onOpenCfrModal}>
{getValues('cfr.background') === 'none' ? t('common.Not open') : t('common.Opened')}
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<MyIcon name={'core/app/simpleMode/ai'} w={'20px'} />
<Box ml={2} flex={1}>
{t('app.AI Settings')}
</Box>
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
<MyIcon mr={1} name={'common/settingLight'} w={'14px'} />
{t('common.More settings')}
</Flex>
</Flex>
)}
<Flex alignItems={'center'} mt={5}>
<Box {...LabelStyles}>{t('core.ai.Model')}</Box>
<Box flex={'1 0 0'}>
<SelectAiModel
width={'100%'}
value={getValues(`aiSettings.model`)}
list={chatModelSelectList}
onchange={(val: any) => {
setValue('aiSettings.model', val);
const maxToken =
llmModelList.find((item) => item.model === getValues('aiSettings.model'))
?.maxResponse || 4000;
const token = maxToken / 2;
setValue('aiSettings.maxToken', token);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex mt={10} alignItems={'flex-start'}>
<Box {...LabelStyles}>
{t('core.ai.Prompt')}
<MyTooltip label={t(chatNodeSystemPromptTip)} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
{isInitd && (
<PromptEditor
value={aiSystemPrompt}
onChange={(text) => {
startTst(() => {
setValue('aiSettings.systemPrompt', text);
});
}}
variables={formatVariables}
placeholder={t('core.app.tip.chatNodeSystemPromptTip')}
title={t('core.ai.Prompt')}
/>
)}
</Flex>
</Box>
{/* dataset */}
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} flex={1}>
<MyIcon name={'core/app/simpleMode/dataset'} w={'20px'} />
<Box ml={2}>{t('core.dataset.Choose Dataset')}</Box>
</Flex>
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbSelect}>
<SmallAddIcon />
{t('common.Choose')}
</Flex>
<Flex alignItems={'center'} ml={3} {...BoxBtnStyles} onClick={onOpenDatasetParams}>
<MyIcon name={'edit'} w={'14px'} mr={1} />
{t('common.Params')}
</Flex>
</Flex>
{getValues('dataset.datasets').length > 0 && (
<Flex mt={1} color={'myGray.600'} fontSize={'sm'} mb={2}>
{t('core.dataset.search.search mode')}: {datasetSearchMode}
{', '}
{reRankModelList.length > 0 && (
<>
{t('core.dataset.search.ReRank')}:{' '}
{getValues('dataset.usingReRank') ? '✅' : '✖'}
</>
)}
{', '}
{t('core.dataset.search.Min Similarity')}: {getValues('dataset.similarity')}
{', '}
{t('core.dataset.search.Max Tokens')}: {getValues('dataset.limit')}
{getValues('dataset.searchEmptyText') === ''
? ''
: t('core.dataset.Set Empty Result Tip')}
</Flex>
)}
<Grid
gridTemplateColumns={['repeat(2, minmax(0, 1fr))', 'repeat(3, minmax(0, 1fr))']}
gridGap={[2, 4]}
>
{selectDatasets.map((item) => (
<MyTooltip key={item._id} label={t('core.dataset.Read Dataset')}>
<Flex
overflow={'hidden'}
alignItems={'center'}
p={2}
bg={'white'}
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
borderRadius={'md'}
border={theme.borders.base}
cursor={'pointer'}
onClick={() =>
router.push({
pathname: '/dataset/detail',
query: {
datasetId: item._id
}
})
}
>
<Avatar src={item.avatar} w={'18px'} mr={1} />
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
{item.name}
</Box>
</Flex>
</MyTooltip>
))}
</Grid>
</Box>
{/* variable */}
{selectSimpleTemplate?.systemForm?.userGuide?.variables && (
<Box {...BoxStyles}>
<VariableEdit
variables={variables}
onChange={(e) => {
setValue('userGuide.variables', e);
setRefresh(!refresh);
}}
/>
</Box>
)}
<Box {...BoxStyles}>
<VariableEdit
variables={variables}
onChange={(e) => {
setValue('userGuide.variables', e);
setRefresh(!refresh);
}}
/>
</Box>
{/* welcome */}
{selectSimpleTemplate?.systemForm?.userGuide?.welcomeText && (
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<MyIcon name={'core/app/simpleMode/chat'} w={'20px'} />
<Box mx={2}>{t('core.app.Welcome Text')}</Box>
<MyTooltip label={t(welcomeTextTip)} forceShow>
<QuestionOutlineIcon />
</MyTooltip>
</Flex>
<MyTextarea
mt={2}
bg={'myWhite.400'}
rows={5}
placeholder={t(welcomeTextTip)}
defaultValue={getValues('userGuide.welcomeText')}
onBlur={(e) => {
setValue('userGuide.welcomeText', e.target.value || '');
}}
/>
</Box>
)}
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<MyIcon name={'core/app/simpleMode/chat'} w={'20px'} />
<Box mx={2}>{t('core.app.Welcome Text')}</Box>
<MyTooltip label={t(welcomeTextTip)} forceShow>
<QuestionOutlineIcon />
</MyTooltip>
</Flex>
<MyTextarea
mt={2}
bg={'myWhite.400'}
rows={5}
placeholder={t(welcomeTextTip)}
defaultValue={getValues('userGuide.welcomeText')}
onBlur={(e) => {
setValue('userGuide.welcomeText', e.target.value || '');
}}
/>
</Box>
{/* tts */}
{selectSimpleTemplate?.systemForm?.userGuide?.tts && (
<Box {...BoxStyles}>
<TTSSelect
value={getValues('userGuide.tts')}
onChange={(e) => {
setValue('userGuide.tts', e);
setRefresh((state) => !state);
}}
/>
</Box>
)}
<Box {...BoxStyles}>
<TTSSelect
value={getValues('userGuide.tts')}
onChange={(e) => {
setValue('userGuide.tts', e);
setRefresh((state) => !state);
}}
/>
</Box>
{/* question guide */}
{selectSimpleTemplate?.systemForm?.userGuide?.questionGuide && (
<Box {...BoxStyles} borderBottom={'none'}>
<QGSwitch
isChecked={getValues('userGuide.questionGuide')}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
setValue('userGuide.questionGuide', value);
setRefresh((state) => !state);
}}
/>
</Box>
)}
<Box {...BoxStyles} borderBottom={'none'}>
<QGSwitch
isChecked={getValues('userGuide.questionGuide')}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
setValue('userGuide.questionGuide', value);
setRefresh((state) => !state);
}}
/>
</Box>
</Box>
</Box>
@@ -491,7 +427,6 @@ const EditForm = ({
onCloseAIChatSetting();
}}
defaultData={getValues('aiSettings')}
simpleModeTemplate={selectSimpleTemplate}
pickerMenu={formatVariables}
/>
)}
@@ -508,28 +443,7 @@ const EditForm = ({
)}
{isOpenDatasetParams && (
<DatasetParamsModal
// {...getValues('dataset')}
searchMode={getValues('dataset.searchMode')}
searchEmptyText={
selectSimpleTemplate?.systemForm?.dataset?.searchEmptyText
? getValues('dataset.searchEmptyText')
: undefined
}
limit={
selectSimpleTemplate?.systemForm?.dataset?.limit
? getValues('dataset.limit')
: undefined
}
similarity={
selectSimpleTemplate?.systemForm?.dataset?.similarity
? getValues('dataset.similarity')
: undefined
}
usingReRank={
selectSimpleTemplate?.systemForm?.dataset?.usingReRank
? getValues('dataset.usingReRank')
: undefined
}
{...datasetSearchSetting}
maxTokens={tokenLimit}
onClose={onCloseDatasetParams}
onSuccess={(e) => {
@@ -542,15 +456,6 @@ const EditForm = ({
}}
/>
)}
{isOpenCfrModal && (
<CfrEditModal
onClose={onCloseCfrModal}
defaultValue={getValues('cfr.background')}
onFinish={(e) => {
setValue('cfr.background', e);
}}
/>
)}
</Box>
);
};

View File

@@ -39,6 +39,8 @@ import { fileDownload } from '@/web/common/file/utils';
import { readCsvContent } from '@fastgpt/web/common/file/read/csv';
import { delay } from '@fastgpt/global/common/system/utils';
import QuoteItem from '@/components/core/dataset/QuoteItem';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
@@ -48,9 +50,13 @@ type FormType = {
inputText: string;
searchParams: {
searchMode: `${DatasetSearchModeEnum}`;
usingReRank: boolean;
limit: number;
similarity: number;
similarity?: number;
limit?: number;
usingReRank?: boolean;
searchEmptyText?: string;
datasetSearchUsingExtensionQuery?: boolean;
datasetSearchExtensionModel?: string;
datasetSearchExtensionBg?: string;
};
};
@@ -58,6 +64,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
const { t } = useTranslation();
const theme = useTheme();
const { toast } = useToast();
const { llmModelList } = useSystemStore();
const { datasetDetail } = useDatasetStore();
const { pushDatasetTestItem } = useSearchTestStore();
const [inputType, setInputType] = useState<'text' | 'file'>('text');
@@ -77,12 +84,15 @@ const Test = ({ datasetId }: { datasetId: string }) => {
searchMode: DatasetSearchModeEnum.embedding,
usingReRank: false,
limit: 5000,
similarity: 0
similarity: 0,
datasetSearchUsingExtensionQuery: false,
datasetSearchExtensionModel: llmModelList[0].model,
datasetSearchExtensionBg: ''
}
}
});
const searchModeData = DatasetSearchModeMap[getValues('searchParams.searchMode')];
const searchModeData = DatasetSearchModeMap[getValues(`searchParams.searchMode`)];
const {
isOpen: isOpenSelectMode,
@@ -123,34 +133,34 @@ const Test = ({ datasetId }: { datasetId: string }) => {
});
}
});
const { mutate: onFileTest, isLoading: fileTestIsLoading } = useRequest({
mutationFn: async ({ searchParams }: FormType) => {
if (!selectFile) return Promise.reject('File is not selected');
const { data } = await readCsvContent({ file: selectFile });
const testList = data.slice(0, 100);
const results: SearchTestResponse[] = [];
// const { mutate: onFileTest, isLoading: fileTestIsLoading } = useRequest({
// mutationFn: async ({ searchParams }: FormType) => {
// if (!selectFile) return Promise.reject('File is not selected');
// const { data } = await readCsvContent({ file: selectFile });
// const testList = data.slice(0, 100);
// const results: SearchTestResponse[] = [];
for await (const item of testList) {
try {
const result = await postSearchText({ datasetId, text: item[0].trim(), ...searchParams });
results.push(result);
} catch (error) {
await delay(500);
}
}
// for await (const item of testList) {
// try {
// const result = await postSearchText({ datasetId, text: item[0].trim(), ...searchParams });
// results.push(result);
// } catch (error) {
// await delay(500);
// }
// }
return results;
},
onSuccess(res: SearchTestResponse[]) {
console.log(res);
},
onError(err) {
toast({
title: getErrText(err),
status: 'error'
});
}
});
// return results;
// },
// onSuccess(res: SearchTestResponse[]) {
// console.log(res);
// },
// onError(err) {
// toast({
// title: getErrText(err),
// status: 'error'
// });
// }
// });
const onSelectFile = async (files: File[]) => {
const file = files[0];
@@ -295,13 +305,13 @@ const Test = ({ datasetId }: { datasetId: string }) => {
<Flex justifyContent={'flex-end'}>
<Button
size={'sm'}
isLoading={textTestIsLoading || fileTestIsLoading}
isLoading={textTestIsLoading}
isDisabled={inputType === 'file' && !selectFile}
onClick={() => {
if (inputType === 'text') {
handleSubmit((data) => onTextTest(data))();
} else {
handleSubmit((data) => onFileTest(data))();
// handleSubmit((data) => onFileTest(data))();
}
}}
>

View File

@@ -35,6 +35,7 @@ import type {
} from '@fastgpt/global/core/dataset/api.d';
import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller';
import { getVectorModel } from '../../ai/model';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
export async function pushDataToTrainingQueue(
props: {
@@ -272,7 +273,7 @@ export async function updateData2Dataset({
};
}
export async function searchDatasetData(props: {
type SearchDatasetDataProps = {
teamId: string;
model: string;
similarity?: number; // min distance
@@ -280,12 +281,14 @@ export async function searchDatasetData(props: {
datasetIds: string[];
searchMode?: `${DatasetSearchModeEnum}`;
usingReRank?: boolean;
rawQuery: string;
reRankQuery: string;
queries: string[];
}) {
};
export async function searchDatasetData(props: SearchDatasetDataProps) {
let {
teamId,
rawQuery,
reRankQuery,
queries,
model,
similarity = 0,
@@ -307,27 +310,6 @@ export async function searchDatasetData(props: {
let usingSimilarityFilter = false;
/* function */
const countRecallLimit = () => {
const oneChunkToken = 50;
const estimatedLen = Math.max(20, Math.ceil(maxTokens / oneChunkToken));
if (searchMode === DatasetSearchModeEnum.embedding) {
return {
embeddingLimit: Math.min(estimatedLen, 80),
fullTextLimit: 0
};
}
if (searchMode === DatasetSearchModeEnum.fullTextRecall) {
return {
embeddingLimit: 0,
fullTextLimit: Math.min(estimatedLen, 50)
};
}
return {
embeddingLimit: Math.min(estimatedLen, 60),
fullTextLimit: Math.min(estimatedLen, 40)
};
};
const embeddingRecall = async ({ query, limit }: { query: string; limit: number }) => {
const { vectors, charsLength } = await getVectorsByText({
model: getVectorModel(model),
@@ -531,69 +513,50 @@ export async function searchDatasetData(props: {
embeddingLimit: number;
fullTextLimit: number;
}) => {
// In a group n recall, as long as one of the data appears minAmount of times, it is retained
const getIntersection = (resultList: SearchDataResponseItemType[][], minAmount = 1) => {
minAmount = Math.min(resultList.length, minAmount);
const map: Record<
string,
{
amount: number;
data: SearchDataResponseItemType;
}
> = {};
for (const list of resultList) {
for (const item of list) {
map[item.id] = map[item.id]
? {
amount: map[item.id].amount + 1,
data: item
}
: {
amount: 1,
data: item
};
}
}
return Object.values(map)
.filter((item) => item.amount >= minAmount)
.map((item) => item.data);
};
// multi query recall
const embeddingRecallResList: SearchDataResponseItemType[][] = [];
const fullTextRecallResList: SearchDataResponseItemType[][] = [];
let totalCharsLength = 0;
for await (const query of queries) {
const [{ charsLength, embeddingRecallResults }, { fullTextRecallResults }] =
await Promise.all([
embeddingRecall({
query,
limit: embeddingLimit
}),
fullTextRecall({
query,
limit: fullTextLimit
})
]);
totalCharsLength += charsLength;
embeddingRecallResList.push(embeddingRecallResults);
fullTextRecallResList.push(fullTextRecallResults);
}
await Promise.all(
queries.map(async (query) => {
const [{ charsLength, embeddingRecallResults }, { fullTextRecallResults }] =
await Promise.all([
embeddingRecall({
query,
limit: embeddingLimit
}),
fullTextRecall({
query,
limit: fullTextLimit
})
]);
totalCharsLength += charsLength;
embeddingRecallResList.push(embeddingRecallResults);
fullTextRecallResList.push(fullTextRecallResults);
})
);
// rrf concat
const rrfEmbRecall = datasetSearchResultConcat(
embeddingRecallResList.map((list) => ({ k: 60, list }))
).slice(0, embeddingLimit);
const rrfFTRecall = datasetSearchResultConcat(
fullTextRecallResList.map((list) => ({ k: 60, list }))
).slice(0, fullTextLimit);
return {
charsLength: totalCharsLength,
embeddingRecallResults: embeddingRecallResList[0],
fullTextRecallResults: fullTextRecallResList[0]
embeddingRecallResults: rrfEmbRecall,
fullTextRecallResults: rrfFTRecall
};
};
/* main step */
// count limit
const { embeddingLimit, fullTextLimit } = countRecallLimit();
const embeddingLimit = 60;
const fullTextLimit = 40;
// recall
const { embeddingRecallResults, fullTextRecallResults, charsLength } = await multiQueryRecall({
@@ -620,7 +583,7 @@ export async function searchDatasetData(props: {
return true;
});
return reRankSearchResult({
query: rawQuery,
query: reRankQuery,
data: filterSameDataResults
});
})();

View File

@@ -3,10 +3,13 @@ import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { ModelTypeEnum, getVectorModel } from '@/service/core/ai/model';
import { ModelTypeEnum, getLLMModel, getVectorModel } from '@/service/core/ai/model';
import { searchDatasetData } from '@/service/core/dataset/data/controller';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { queryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
import { getHistories } from '../utils';
import { datasetSearchQueryExtension } from '@fastgpt/service/core/dataset/search/utils';
type DatasetSearchProps = ModuleDispatchProps<{
[ModuleInputKeyEnum.datasetSelectList]: SelectedDatasetType;
@@ -15,6 +18,9 @@ type DatasetSearchProps = ModuleDispatchProps<{
[ModuleInputKeyEnum.datasetSearchMode]: `${DatasetSearchModeEnum}`;
[ModuleInputKeyEnum.userChatInput]: string;
[ModuleInputKeyEnum.datasetSearchUsingReRank]: boolean;
[ModuleInputKeyEnum.datasetSearchUsingExtensionQuery]: boolean;
[ModuleInputKeyEnum.datasetSearchExtensionModel]: string;
[ModuleInputKeyEnum.datasetSearchExtensionBg]: string;
}>;
export type DatasetSearchResponse = {
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
@@ -28,7 +34,19 @@ export async function dispatchDatasetSearch(
): Promise<DatasetSearchResponse> {
const {
teamId,
params: { datasets = [], similarity, limit = 1500, usingReRank, searchMode, userChatInput }
histories,
params: {
datasets = [],
similarity,
limit = 1500,
usingReRank,
searchMode,
userChatInput,
datasetSearchUsingExtensionQuery,
datasetSearchExtensionModel,
datasetSearchExtensionBg
}
} = props as DatasetSearchProps;
if (!Array.isArray(datasets)) {
@@ -43,15 +61,21 @@ export async function dispatchDatasetSearch(
return Promise.reject('core.chat.error.User input empty');
}
// query extension
const extensionModel =
datasetSearchUsingExtensionQuery && datasetSearchExtensionModel
? getLLMModel(datasetSearchExtensionModel)
: undefined;
const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({
query: userChatInput,
extensionModel,
extensionBg: datasetSearchExtensionBg,
histories: getHistories(6, histories)
});
// get vector
const vectorModel = getVectorModel(datasets[0]?.vectorModel?.model);
// const { queries: extensionQueries } = await searchQueryExtension({
// query: userChatInput,
// model: global.llmModels[0].model
// });
const concatQueries = [userChatInput];
// start search
const {
searchRes,
@@ -60,7 +84,7 @@ export async function dispatchDatasetSearch(
usingReRank: searchUsingReRank
} = await searchDatasetData({
teamId,
rawQuery: `${userChatInput}`,
reRankQuery: `${rewriteQuery}`,
queries: concatQueries,
model: vectorModel.model,
similarity,
@@ -70,25 +94,45 @@ export async function dispatchDatasetSearch(
usingReRank
});
// count bill results
// vector
const { total, modelName } = formatModelPrice2Store({
model: vectorModel.model,
inputLen: charsLength,
type: ModelTypeEnum.vector
});
const responseData: moduleDispatchResType & { price: number } = {
price: total,
query: concatQueries.join('\n'),
model: modelName,
charsLength,
similarity: usingSimilarityFilter ? similarity : undefined,
limit,
searchMode,
searchUsingReRank: searchUsingReRank
};
if (aiExtensionResult) {
const { total, modelName } = formatModelPrice2Store({
model: aiExtensionResult.model,
inputLen: aiExtensionResult.inputTokens,
outputLen: aiExtensionResult.outputTokens,
type: ModelTypeEnum.llm
});
responseData.price += total;
responseData.inputTokens = aiExtensionResult.inputTokens;
responseData.outputTokens = aiExtensionResult.outputTokens;
responseData.extensionModel = modelName;
responseData.extensionResult =
aiExtensionResult.extensionQueries?.join('\n') ||
JSON.stringify(aiExtensionResult.extensionQueries);
}
return {
isEmpty: searchRes.length === 0 ? true : undefined,
unEmpty: searchRes.length > 0 ? true : undefined,
quoteQA: searchRes,
responseData: {
price: total,
query: concatQueries.join('\n'),
model: modelName,
charsLength,
similarity: usingSimilarityFilter ? similarity : undefined,
limit,
searchMode,
searchUsingReRank: searchUsingReRank
}
responseData
};
}

View File

@@ -1,11 +1,10 @@
import type { ChatItemType, moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { getHistories } from '../utils';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { ModelTypeEnum, getLLMModel } from '@/service/core/ai/model';
import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
import { queryCfr } from '@fastgpt/service/core/ai/functions/cfr';
import { getHistories } from '../utils';
type Props = ModuleDispatchProps<{
[ModuleInputKeyEnum.aiModel]: string;
@@ -34,57 +33,18 @@ export const dispatchCFR = async ({
};
}
const extractModel = getLLMModel(model);
const cfrModel = getLLMModel(model);
const chatHistories = getHistories(history, histories);
const systemFewShot = systemPrompt
? `Q: 对话背景。
A: ${systemPrompt}
`
: '';
const historyFewShot = chatHistories
.map((item) => {
const role = item.obj === 'Human' ? 'Q' : 'A';
return `${role}: ${item.value}`;
})
.join('\n');
const concatFewShot = `${systemFewShot}${historyFewShot}`.trim();
const ai = getAIApi({
timeout: 480000
const { cfrQuery, inputTokens, outputTokens } = await queryCfr({
chatBg: systemPrompt,
query: userChatInput,
histories: chatHistories,
model: cfrModel.model
});
const result = await ai.chat.completions.create({
model: extractModel.model,
temperature: 0,
max_tokens: 150,
messages: [
{
role: 'user',
content: replaceVariable(defaultPrompt, {
query: `${userChatInput}`,
histories: concatFewShot
})
}
],
stream: false
});
let answer = result.choices?.[0]?.message?.content || '';
// console.log(
// replaceVariable(defaultPrompt, {
// query: userChatInput,
// histories: concatFewShot
// })
// );
// console.log(answer);
const inputTokens = result.usage?.prompt_tokens || 0;
const outputTokens = result.usage?.completion_tokens || 0;
const { total, modelName } = formatModelPrice2Store({
model: extractModel.model,
model: cfrModel.model,
inputLen: inputTokens,
outputLen: outputTokens,
type: ModelTypeEnum.llm
@@ -97,85 +57,8 @@ A: ${systemPrompt}
inputTokens,
outputTokens,
query: userChatInput,
textOutput: answer
textOutput: cfrQuery
},
[ModuleOutputKeyEnum.text]: answer
[ModuleOutputKeyEnum.text]: cfrQuery
};
};
const defaultPrompt = `请不要回答任何问题。
你的任务是结合上下文,为当前问题,实现代词替换,确保问题描述的对象清晰明确。例如:
历史记录:
"""
Q: 对话背景。
A: 关于 FatGPT 的介绍和使用等问题。
"""
当前问题: 怎么下载
输出: FastGPT 怎么下载?
----------------
历史记录:
"""
Q: 报错 "no connection"
A: FastGPT 报错"no connection"可能是因为……
"""
当前问题: 怎么解决
输出: FastGPT 报错"no connection"如何解决?
----------------
历史记录:
"""
Q: 作者是谁?
A: FastGPT 的作者是 labring。
"""
当前问题: 介绍下他
输出: 介绍下 FastGPT 的作者 labring。
----------------
历史记录:
"""
Q: 作者是谁?
A: FastGPT 的作者是 labring。
"""
当前问题: 我想购买商业版。
输出: FastGPT 商业版如何购买?
----------------
历史记录:
"""
Q: 对话背景。
A: 关于 FatGPT 的介绍和使用等问题。
"""
当前问题: nh
输出: nh
----------------
历史记录:
"""
Q: FastGPT 如何收费?
A: FastGPT 收费可以参考……
"""
当前问题: 你知道 laf 么?
输出: 你知道 laf 么?
----------------
历史记录:
"""
Q: FastGPT 的优势
A: 1. 开源
2. 简便
3. 扩展性强
"""
当前问题: 介绍下第2点。
输出: 介绍下 FastGPT 简便的优势。
----------------
历史记录:
"""
Q: 什么是 FastGPT
A: FastGPT 是一个 RAG 平台。
Q: 什么是 Sealos
A: Sealos 是一个云操作系统。
"""
当前问题: 它们有什么关系?
输出: FastGPT 和 Sealos 有什么关系?
----------------
历史记录:
"""
{{histories}}
"""
当前问题: {{query}}
输出: `;

View File

@@ -26,63 +26,40 @@ export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<Http
variables,
outputs,
params: {
system_httpMethod: httpMethod,
url: abandonUrl,
system_httpMethod: httpMethod = 'POST',
system_httpReqUrl: httpReqUrl,
system_httpHeader: httpHeader,
...body
}
} = props;
if (!httpReqUrl) {
return Promise.reject('Http url is empty');
}
body = flatDynamicParams(body);
const { requestMethod, requestUrl, requestHeader, requestBody, requestQuery } = await (() => {
// 2024-2-12 clear
if (abandonUrl) {
return {
requestMethod: 'POST',
requestUrl: abandonUrl,
requestHeader: httpHeader,
requestBody: {
...body,
appId,
chatId,
variables
},
requestQuery: {}
};
}
if (httpReqUrl) {
return {
requestMethod: httpMethod,
requestUrl: httpReqUrl,
requestHeader: httpHeader,
requestBody: {
appId,
chatId,
responseChatItemId,
variables,
data: body
},
requestQuery: {
appId,
chatId,
...variables,
...body
}
};
}
return Promise.reject('url is empty');
})();
const requestBody = {
appId,
chatId,
responseChatItemId,
variables,
data: body
};
const requestQuery = {
appId,
chatId,
...variables,
...body
};
const formatBody = transformFlatJson({ ...requestBody });
// parse header
const headers = await (() => {
try {
if (!requestHeader) return {};
return JSON.parse(requestHeader);
if (!httpHeader) return {};
return JSON.parse(httpHeader);
} catch (error) {
return Promise.reject('Header 为非法 JSON 格式');
}
@@ -90,8 +67,8 @@ export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<Http
try {
const response = await fetchData({
method: requestMethod,
url: requestUrl,
method: httpMethod,
url: httpReqUrl,
headers,
body: formatBody,
query: requestQuery

View File

@@ -87,7 +87,10 @@ export const pushGenerateVectorBill = ({
tmbId,
charsLength,
model,
source = BillSourceEnum.fastgpt
source = BillSourceEnum.fastgpt,
extensionModel,
extensionInputTokens,
extensionOutputTokens
}: {
billId?: string;
teamId: string;
@@ -95,19 +98,43 @@ export const pushGenerateVectorBill = ({
charsLength: number;
model: string;
source?: `${BillSourceEnum}`;
extensionModel?: string;
extensionInputTokens?: number;
extensionOutputTokens?: number;
}) => {
let { total, modelName } = formatModelPrice2Store({
const { total: totalVector, modelName: vectorModelName } = formatModelPrice2Store({
model,
inputLen: charsLength,
type: ModelTypeEnum.vector
});
const { extensionTotal, extensionModelName } = (() => {
if (!extensionModel || !extensionInputTokens || !extensionOutputTokens)
return {
extensionTotal: 0,
extensionModelName: ''
};
const { total, modelName } = formatModelPrice2Store({
model: extensionModel,
inputLen: extensionInputTokens,
outputLen: extensionOutputTokens,
type: ModelTypeEnum.llm
});
return {
extensionTotal: total,
extensionModelName: modelName
};
})();
const total = totalVector + extensionTotal;
// 插入 Bill 记录
if (billId) {
concatBill({
teamId,
tmbId,
total,
total: totalVector,
billId,
charsLength,
listIndex: 0
@@ -123,9 +150,20 @@ export const pushGenerateVectorBill = ({
{
moduleName: 'wallet.moduleName.index',
amount: total,
model: modelName,
model: vectorModelName,
charsLength
}
},
...(extensionModel !== undefined
? [
{
moduleName: extensionModelName,
amount: extensionTotal,
model: extensionModelName,
inputTokens: extensionInputTokens,
outputTokens: extensionOutputTokens
}
]
: [])
]
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,7 @@ import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api.d';
import { useSystemStore } from '@/web/common/system/useSystemStore';
export async function postForm2Modules(
data: AppSimpleEditFormType,
templateId = 'fastgpt-universal'
) {
export async function postForm2Modules(data: AppSimpleEditFormType) {
const llmModelList = useSystemStore.getState().llmModelList;
function userGuideTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
return [
@@ -60,7 +57,7 @@ export async function postForm2Modules(
llmModelList
};
const modules = await POST<ModuleItemType[]>(`/core/app/form2Modules/${templateId}`, props);
const modules = await POST<ModuleItemType[]>(`/core/app/form2Modules/fastgpt-universal`, props);
return [...userGuideTemplate(data), ...modules];
}

View File

@@ -128,8 +128,8 @@ const SelectCollections = ({
{title
? title
: type === 'folder'
? t('common.Root folder')
: t('dataset.collections.Select Collection')}
? t('common.Root folder')
: t('dataset.collections.Select Collection')}
</Box>
{!!tip && (
<Box fontSize={'sm'} color={'myGray.500'}>

View File

@@ -29,8 +29,7 @@ export const appSystemModuleTemplates: FlowModuleTemplateType[] = [
RunAppModule,
ClassifyQuestionModule,
ContextExtractModule,
HttpModule,
AiCFR
HttpModule
];
export const pluginSystemModuleTemplates: FlowModuleTemplateType[] = [
PluginInputModule,
@@ -42,8 +41,7 @@ export const pluginSystemModuleTemplates: FlowModuleTemplateType[] = [
RunAppModule,
ClassifyQuestionModule,
ContextExtractModule,
HttpModule,
AiCFR
HttpModule
];
export const moduleTemplatesFlat: FlowModuleTemplateType[] = [

View File

@@ -329,11 +329,16 @@ const Switch = switchMultiStyle({
baseStyle: switchPart({
track: {
bg: 'myGray.100',
borderWidth: '1px',
borderColor: 'borders.base',
_checked: {
bg: 'primary.600'
}
}
})
}),
defaultProps: {
size: 'md'
}
});
const Select = selectMultiStyle({