mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-24 22:03:54 +00:00
extract modules
This commit is contained in:
@@ -18,6 +18,13 @@
|
|||||||
"New Chat": "New Chat",
|
"New Chat": "New Chat",
|
||||||
"You need to a chat app": "You need to a chat app"
|
"You need to a chat app": "You need to a chat app"
|
||||||
},
|
},
|
||||||
|
"common": {
|
||||||
|
"Add": "Add",
|
||||||
|
"Filed is repeat": "Filed is repeated",
|
||||||
|
"Filed is repeated": "",
|
||||||
|
"Input": "Input",
|
||||||
|
"Output": "Output"
|
||||||
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"Quickly build AI question and answer library": "Quickly build AI question and answer library",
|
"Quickly build AI question and answer library": "Quickly build AI question and answer library",
|
||||||
"Start Now": "Start Now",
|
"Start Now": "Start Now",
|
||||||
|
@@ -18,6 +18,13 @@
|
|||||||
"New Chat": "新对话",
|
"New Chat": "新对话",
|
||||||
"You need to a chat app": "你需要创建一个应用"
|
"You need to a chat app": "你需要创建一个应用"
|
||||||
},
|
},
|
||||||
|
"common": {
|
||||||
|
"Add": "添加",
|
||||||
|
"Filed is repeat": "",
|
||||||
|
"Filed is repeated": "字段重复了",
|
||||||
|
"Input": "输入",
|
||||||
|
"Output": "输出"
|
||||||
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"Quickly build AI question and answer library": "快速搭建 AI 问答系统",
|
"Quickly build AI question and answer library": "快速搭建 AI 问答系统",
|
||||||
"Start Now": "立即开始",
|
"Start Now": "立即开始",
|
||||||
|
@@ -54,7 +54,8 @@ export const ChatSourceMap = {
|
|||||||
export enum ChatModuleEnum {
|
export enum ChatModuleEnum {
|
||||||
'AIChat' = 'AI Chat',
|
'AIChat' = 'AI Chat',
|
||||||
'KBSearch' = 'KB Search',
|
'KBSearch' = 'KB Search',
|
||||||
'CQ' = 'Classify Question'
|
'CQ' = 'Classify Question',
|
||||||
|
'Extract' = 'Content Extract'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OutLinkTypeEnum {
|
export enum OutLinkTypeEnum {
|
||||||
|
@@ -15,6 +15,7 @@ import {
|
|||||||
Input_Template_TFSwitch,
|
Input_Template_TFSwitch,
|
||||||
Input_Template_UserChatInput
|
Input_Template_UserChatInput
|
||||||
} from './inputTemplate';
|
} from './inputTemplate';
|
||||||
|
import { ContextExtractEnum } from './flowField';
|
||||||
|
|
||||||
export const ChatModelSystemTip =
|
export const ChatModelSystemTip =
|
||||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}';
|
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}';
|
||||||
@@ -333,7 +334,7 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
|||||||
Input_Template_History,
|
Input_Template_History,
|
||||||
Input_Template_UserChatInput,
|
Input_Template_UserChatInput,
|
||||||
{
|
{
|
||||||
key: 'agents',
|
key: SpecialInputKeyEnum.agents,
|
||||||
type: FlowInputItemTypeEnum.custom,
|
type: FlowInputItemTypeEnum.custom,
|
||||||
label: '',
|
label: '',
|
||||||
value: [
|
value: [
|
||||||
@@ -375,59 +376,70 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
|||||||
};
|
};
|
||||||
export const ContextExtractModule: FlowModuleTemplateType = {
|
export const ContextExtractModule: FlowModuleTemplateType = {
|
||||||
logo: '/imgs/module/extract.png',
|
logo: '/imgs/module/extract.png',
|
||||||
name: '内容提取',
|
name: '文本内容提取',
|
||||||
intro: '从文本中提取出指定格式的数据',
|
intro: '从文本中提取出指定格式的数据',
|
||||||
description: '可从文本中提取指定的数据,例如:sql语句、搜索关键词、代码等',
|
description: '可从文本中提取指定的数据,例如:sql语句、搜索关键词、代码等',
|
||||||
flowType: FlowModuleTypeEnum.contentExtract,
|
flowType: FlowModuleTypeEnum.contentExtract,
|
||||||
inputs: [
|
inputs: [
|
||||||
|
Input_Template_TFSwitch,
|
||||||
{
|
{
|
||||||
key: 'systemPrompt',
|
key: ContextExtractEnum.description,
|
||||||
type: FlowInputItemTypeEnum.textarea,
|
type: FlowInputItemTypeEnum.textarea,
|
||||||
valueType: FlowValueTypeEnum.string,
|
valueType: FlowValueTypeEnum.string,
|
||||||
label: '提取内容描述',
|
label: '提取要求描述',
|
||||||
description: '写一段提取要求,告诉 AI 需要提取哪些内容',
|
description: '写一段提取要求,告诉 AI 需要提取哪些内容',
|
||||||
placeholder: '例如: \n1. 根据用户的\n2. Sealos 是一个集群操作系统',
|
placeholder:
|
||||||
|
'例如: \n1. 你是一个实验室预约助手。根据用户问题,提取出姓名、实验室号和预约时间',
|
||||||
value: ''
|
value: ''
|
||||||
},
|
},
|
||||||
Input_Template_History,
|
Input_Template_History,
|
||||||
Input_Template_UserChatInput,
|
|
||||||
{
|
{
|
||||||
key: 'agents',
|
key: ContextExtractEnum.content,
|
||||||
|
type: FlowInputItemTypeEnum.target,
|
||||||
|
label: '需要提取的文本',
|
||||||
|
required: true,
|
||||||
|
valueType: FlowValueTypeEnum.string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: ContextExtractEnum.extractKeys,
|
||||||
type: FlowInputItemTypeEnum.custom,
|
type: FlowInputItemTypeEnum.custom,
|
||||||
label: '',
|
label: '目标字段',
|
||||||
|
description: "由 '描述' 和 'key' 组成一个目标字段,可提取多个目标字段",
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
value: '打招呼',
|
key: 'a',
|
||||||
key: 'fasw'
|
desc: '描述',
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: '关于 xxx 的问题',
|
key: 'n',
|
||||||
key: 'fqsw'
|
desc: '描述',
|
||||||
},
|
required: true
|
||||||
{
|
|
||||||
value: '其他问题',
|
|
||||||
key: 'fesw'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
{
|
{
|
||||||
key: 'fasw',
|
key: ContextExtractEnum.success,
|
||||||
label: '',
|
label: '字段完全提取',
|
||||||
type: FlowOutputItemTypeEnum.hidden,
|
valueType: FlowValueTypeEnum.boolean,
|
||||||
|
type: FlowOutputItemTypeEnum.source,
|
||||||
targets: []
|
targets: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'fqsw',
|
key: ContextExtractEnum.failed,
|
||||||
label: '',
|
label: '提取字段缺失',
|
||||||
type: FlowOutputItemTypeEnum.hidden,
|
valueType: FlowValueTypeEnum.boolean,
|
||||||
|
type: FlowOutputItemTypeEnum.source,
|
||||||
targets: []
|
targets: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'fesw',
|
key: ContextExtractEnum.fields,
|
||||||
label: '',
|
label: '提取结果',
|
||||||
type: FlowOutputItemTypeEnum.hidden,
|
description: '一个 JSON 对象,例如 {"name:":"YY","Time":"2023/7/2 18:00"}',
|
||||||
|
valueType: FlowValueTypeEnum.other,
|
||||||
|
type: FlowOutputItemTypeEnum.source,
|
||||||
targets: []
|
targets: []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -461,7 +473,7 @@ export const ModuleTemplates = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Agent',
|
label: 'Agent',
|
||||||
list: [ClassifyQuestionModule]
|
list: [ClassifyQuestionModule, ContextExtractModule]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
export const ModuleTemplatesFlat = ModuleTemplates.map((templates) => templates.list)?.flat();
|
export const ModuleTemplatesFlat = ModuleTemplates.map((templates) => templates.list)?.flat();
|
||||||
@@ -1070,7 +1082,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
|||||||
connected: true
|
connected: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'agents',
|
key: SpecialInputKeyEnum.agents,
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
value: '打招呼、问候等问题',
|
value: '打招呼、问候等问题',
|
||||||
|
8
client/src/constants/flow/flowField.ts
Normal file
8
client/src/constants/flow/flowField.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export enum ContextExtractEnum {
|
||||||
|
extractKeys = 'extractKeys',
|
||||||
|
content = 'content',
|
||||||
|
description = 'description',
|
||||||
|
success = 'success',
|
||||||
|
failed = 'failed',
|
||||||
|
fields = 'fields'
|
||||||
|
}
|
@@ -35,7 +35,8 @@ export enum FlowModuleTypeEnum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum SpecialInputKeyEnum {
|
export enum SpecialInputKeyEnum {
|
||||||
'answerText' = 'text'
|
'answerText' = 'text',
|
||||||
|
'agents' = 'agents' // cq agent key
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FlowValueTypeEnum {
|
export enum FlowValueTypeEnum {
|
||||||
|
@@ -25,6 +25,7 @@ import { pushTaskBill } from '@/service/events/pushBill';
|
|||||||
import { BillSourceEnum } from '@/constants/user';
|
import { BillSourceEnum } from '@/constants/user';
|
||||||
import { ChatHistoryItemResType } from '@/types/chat';
|
import { ChatHistoryItemResType } from '@/types/chat';
|
||||||
import { UserModelSchema } from '@/types/mongoSchema';
|
import { UserModelSchema } from '@/types/mongoSchema';
|
||||||
|
import { dispatchContentExtract } from '@/service/moduleDispatch/agent/extract';
|
||||||
|
|
||||||
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
|
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
|
||||||
type FastGptWebChatProps = {
|
type FastGptWebChatProps = {
|
||||||
@@ -337,7 +338,8 @@ export async function dispatchModules({
|
|||||||
[FlowModuleTypeEnum.answerNode]: dispatchAnswer,
|
[FlowModuleTypeEnum.answerNode]: dispatchAnswer,
|
||||||
[FlowModuleTypeEnum.chatNode]: dispatchChatCompletion,
|
[FlowModuleTypeEnum.chatNode]: dispatchChatCompletion,
|
||||||
[FlowModuleTypeEnum.kbSearchNode]: dispatchKBSearch,
|
[FlowModuleTypeEnum.kbSearchNode]: dispatchKBSearch,
|
||||||
[FlowModuleTypeEnum.classifyQuestion]: dispatchClassifyQuestion
|
[FlowModuleTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
|
||||||
|
[FlowModuleTypeEnum.contentExtract]: dispatchContentExtract
|
||||||
};
|
};
|
||||||
if (callbackMap[module.flowType]) {
|
if (callbackMap[module.flowType]) {
|
||||||
return callbackMap[module.flowType](props);
|
return callbackMap[module.flowType](props);
|
||||||
|
@@ -10,7 +10,7 @@ import type { ClassifyQuestionAgentItemType } from '@/types/app';
|
|||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
|
||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
import { FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
|
import { FlowOutputItemTypeEnum, FlowValueTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
|
||||||
import SourceHandle from '../render/SourceHandle';
|
import SourceHandle from '../render/SourceHandle';
|
||||||
|
|
||||||
const NodeCQNode = ({
|
const NodeCQNode = ({
|
||||||
@@ -25,7 +25,7 @@ const NodeCQNode = ({
|
|||||||
onChangeNode={onChangeNode}
|
onChangeNode={onChangeNode}
|
||||||
flowInputList={inputs}
|
flowInputList={inputs}
|
||||||
CustomComponent={{
|
CustomComponent={{
|
||||||
agents: ({
|
[SpecialInputKeyEnum.agents]: ({
|
||||||
key: agentKey,
|
key: agentKey,
|
||||||
value: agents = []
|
value: agents = []
|
||||||
}: {
|
}: {
|
||||||
|
@@ -0,0 +1,146 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
||||||
|
import { NodeProps } from 'reactflow';
|
||||||
|
import { FlowModuleItemType } from '@/types/flow';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import NodeCard from '../modules/NodeCard';
|
||||||
|
import Container from '../modules/Container';
|
||||||
|
import { AddIcon } from '@chakra-ui/icons';
|
||||||
|
import RenderInput from '../render/RenderInput';
|
||||||
|
import Divider from '../modules/Divider';
|
||||||
|
import { ContextExtractAgentItemType } from '@/types/app';
|
||||||
|
import RenderOutput from '../render/RenderOutput';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
import ExtractFieldModal from '../modules/ExtractFieldModal';
|
||||||
|
import { ContextExtractEnum } from '@/constants/flow/flowField';
|
||||||
|
|
||||||
|
const NodeExtract = ({
|
||||||
|
data: { inputs, outputs, moduleId, onChangeNode, ...props }
|
||||||
|
}: NodeProps<FlowModuleItemType>) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NodeCard minW={'380px'} moduleId={moduleId} {...props}>
|
||||||
|
<Divider text="Input" />
|
||||||
|
<Container>
|
||||||
|
<RenderInput
|
||||||
|
moduleId={moduleId}
|
||||||
|
onChangeNode={onChangeNode}
|
||||||
|
flowInputList={inputs}
|
||||||
|
CustomComponent={{
|
||||||
|
[ContextExtractEnum.extractKeys]: ({
|
||||||
|
key,
|
||||||
|
value: extractKeys = []
|
||||||
|
}: {
|
||||||
|
key: string;
|
||||||
|
value?: ContextExtractAgentItemType[];
|
||||||
|
}) => (
|
||||||
|
<Box>
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>字段 key</Th>
|
||||||
|
<Th>字段描述</Th>
|
||||||
|
<Th>必填</Th>
|
||||||
|
<Th></Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{extractKeys.map((item, index) => (
|
||||||
|
<Tr key={index}>
|
||||||
|
<Td>{item.key}</Td>
|
||||||
|
<Td whiteSpace={'pre-line'} wordBreak={'break-all'}>
|
||||||
|
{item.desc}{' '}
|
||||||
|
</Td>
|
||||||
|
<Td>{item.required ? '✔' : ''}</Td>
|
||||||
|
<Td whiteSpace={'nowrap'}>
|
||||||
|
<MyIcon
|
||||||
|
mr={3}
|
||||||
|
name={'settingLight'}
|
||||||
|
w={'16px'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
onClick={() => {
|
||||||
|
setEditExtractField(item);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<MyIcon
|
||||||
|
name={'delete'}
|
||||||
|
w={'16px'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
onClick={() => {
|
||||||
|
onChangeNode({
|
||||||
|
moduleId,
|
||||||
|
type: 'inputs',
|
||||||
|
key: ContextExtractEnum.extractKeys,
|
||||||
|
value: extractKeys.filter((extract) => item.key !== extract.key)
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
<Box mt={2} textAlign={'right'}>
|
||||||
|
<Button
|
||||||
|
variant={'base'}
|
||||||
|
leftIcon={<AddIcon fontSize={'10px'} />}
|
||||||
|
onClick={() =>
|
||||||
|
setEditExtractField({
|
||||||
|
desc: '',
|
||||||
|
key: '',
|
||||||
|
required: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
新增字段
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
<Divider text="Output" />
|
||||||
|
<Container>
|
||||||
|
<RenderOutput flowOutputList={outputs} />
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
{!!editExtractFiled && (
|
||||||
|
<ExtractFieldModal
|
||||||
|
defaultField={editExtractFiled}
|
||||||
|
onClose={() => setEditExtractField(undefined)}
|
||||||
|
onSubmit={(data) => {
|
||||||
|
const extracts: ContextExtractAgentItemType[] =
|
||||||
|
inputs.find((item) => item.key === ContextExtractEnum.extractKeys)?.value || [];
|
||||||
|
|
||||||
|
const exists = extracts.find((item) => item.key === editExtractFiled.key);
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
onChangeNode({
|
||||||
|
moduleId,
|
||||||
|
type: 'inputs',
|
||||||
|
key: ContextExtractEnum.extractKeys,
|
||||||
|
value: extracts.map((item) => (item.key === editExtractFiled.key ? data : item))
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onChangeNode({
|
||||||
|
moduleId,
|
||||||
|
type: 'inputs',
|
||||||
|
key: ContextExtractEnum.extractKeys,
|
||||||
|
value: extracts.concat(data)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditExtractField(undefined);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</NodeCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NodeExtract;
|
@@ -1,8 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, useTheme } from '@chakra-ui/react';
|
import { Box, useTheme } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
const Divider = ({ text }: { text: 'Body' | 'Input' | 'Output' | string }) => {
|
const Divider = ({ text }: { text: 'Input' | 'Output' | string }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
textAlign={'center'}
|
textAlign={'center'}
|
||||||
@@ -12,7 +14,7 @@ const Divider = ({ text }: { text: 'Body' | 'Input' | 'Output' | string }) => {
|
|||||||
borderBottom={theme.borders.base}
|
borderBottom={theme.borders.base}
|
||||||
fontSize={'lg'}
|
fontSize={'lg'}
|
||||||
>
|
>
|
||||||
{text}
|
{t(`common.${text}`)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
ModalHeader,
|
||||||
|
ModalFooter,
|
||||||
|
ModalBody,
|
||||||
|
Flex,
|
||||||
|
Switch,
|
||||||
|
Input,
|
||||||
|
FormControl
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import type { ContextExtractAgentItemType } from '@/types/app';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||||
|
import MyModal from '@/components/MyModal';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
|
||||||
|
const ExtractFieldModal = ({
|
||||||
|
defaultField = {
|
||||||
|
desc: '',
|
||||||
|
key: '',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
onClose,
|
||||||
|
onSubmit
|
||||||
|
}: {
|
||||||
|
defaultField?: ContextExtractAgentItemType;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: ContextExtractAgentItemType) => void;
|
||||||
|
}) => {
|
||||||
|
const { register, handleSubmit } = useForm<ContextExtractAgentItemType>({
|
||||||
|
defaultValues: defaultField
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyModal isOpen={true} onClose={onClose}>
|
||||||
|
<ModalHeader display={'flex'} alignItems={'center'}>
|
||||||
|
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
|
||||||
|
提取字段配置
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Box w={'70px'}>必填</Box>
|
||||||
|
<Switch {...register('required')} />
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={5} alignItems={'center'}>
|
||||||
|
<Box w={'80px'}>字段描述</Box>
|
||||||
|
<Input
|
||||||
|
placeholder="姓名/年龄/sql语句……"
|
||||||
|
{...register('desc', { required: '字段描述不能为空' })}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={5} alignItems={'center'}>
|
||||||
|
<Box w={'80px'}>字段 key</Box>
|
||||||
|
<Input
|
||||||
|
placeholder="name/age/sql"
|
||||||
|
{...register('key', { required: '字段 key 不能为空' })}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</MyModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(ExtractFieldModal);
|
@@ -29,7 +29,14 @@ const NodeCard = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box minW={minW} bg={'white'} border={theme.borders.md} borderRadius={'md'} boxShadow={'sm'}>
|
<Box
|
||||||
|
minW={minW}
|
||||||
|
maxW={'430px'}
|
||||||
|
bg={'white'}
|
||||||
|
border={theme.borders.md}
|
||||||
|
borderRadius={'md'}
|
||||||
|
boxShadow={'sm'}
|
||||||
|
>
|
||||||
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
|
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
|
||||||
<Avatar src={logo} borderRadius={'md'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
<Avatar src={logo} borderRadius={'md'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
||||||
<Box ml={3} fontSize={'lg'} color={'myGray.600'}>
|
<Box ml={3} fontSize={'lg'} color={'myGray.600'}>
|
||||||
@@ -39,7 +46,7 @@ const NodeCard = ({
|
|||||||
<MyTooltip label={description} forceShow>
|
<MyTooltip label={description} forceShow>
|
||||||
<QuestionOutlineIcon
|
<QuestionOutlineIcon
|
||||||
display={['none', 'inline']}
|
display={['none', 'inline']}
|
||||||
transform={'translateY(-1px)'}
|
transform={'translateY(1px)'}
|
||||||
ml={1}
|
ml={1}
|
||||||
/>
|
/>
|
||||||
</MyTooltip>
|
</MyTooltip>
|
||||||
|
@@ -68,6 +68,9 @@ const NodeVariable = dynamic(() => import('./components/Nodes/NodeVariable'), {
|
|||||||
const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'), {
|
const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'), {
|
||||||
ssr: false
|
ssr: false
|
||||||
});
|
});
|
||||||
|
const NodeExtract = dynamic(() => import('./components/Nodes/NodeExtract'), {
|
||||||
|
ssr: false
|
||||||
|
});
|
||||||
|
|
||||||
import 'reactflow/dist/style.css';
|
import 'reactflow/dist/style.css';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
@@ -83,7 +86,8 @@ const nodeTypes = {
|
|||||||
[FlowModuleTypeEnum.kbSearchNode]: NodeKbSearch,
|
[FlowModuleTypeEnum.kbSearchNode]: NodeKbSearch,
|
||||||
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
|
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
|
||||||
[FlowModuleTypeEnum.answerNode]: NodeAnswer,
|
[FlowModuleTypeEnum.answerNode]: NodeAnswer,
|
||||||
[FlowModuleTypeEnum.classifyQuestion]: NodeCQNode
|
[FlowModuleTypeEnum.classifyQuestion]: NodeCQNode,
|
||||||
|
[FlowModuleTypeEnum.contentExtract]: NodeExtract
|
||||||
// [FlowModuleTypeEnum.empty]: EmptyModule
|
// [FlowModuleTypeEnum.empty]: EmptyModule
|
||||||
};
|
};
|
||||||
const edgeTypes = {
|
const edgeTypes = {
|
||||||
|
@@ -8,13 +8,14 @@ import { countModelPrice } from '@/service/events/pushBill';
|
|||||||
import { UserModelSchema } from '@/types/mongoSchema';
|
import { UserModelSchema } from '@/types/mongoSchema';
|
||||||
import { getModel } from '@/service/utils/data';
|
import { getModel } from '@/service/utils/data';
|
||||||
import { SystemInputEnum } from '@/constants/app';
|
import { SystemInputEnum } from '@/constants/app';
|
||||||
|
import { SpecialInputKeyEnum } from '@/constants/flow';
|
||||||
|
|
||||||
export type CQProps = {
|
export type CQProps = {
|
||||||
systemPrompt?: string;
|
systemPrompt?: string;
|
||||||
history?: ChatItemType[];
|
history?: ChatItemType[];
|
||||||
[SystemInputEnum.userChatInput]: string;
|
[SystemInputEnum.userChatInput]: string;
|
||||||
userOpenaiAccount: UserModelSchema['openaiAccount'];
|
userOpenaiAccount: UserModelSchema['openaiAccount'];
|
||||||
agents: ClassifyQuestionAgentItemType[];
|
[SpecialInputKeyEnum.agents]: ClassifyQuestionAgentItemType[];
|
||||||
};
|
};
|
||||||
export type CQResponse = {
|
export type CQResponse = {
|
||||||
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
|
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
|
||||||
|
@@ -1,50 +1,44 @@
|
|||||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
||||||
import { jsonRes } from '@/service/response';
|
|
||||||
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
|
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
|
||||||
import { ChatContextFilter } from '@/service/utils/chat/index';
|
import { ChatContextFilter } from '@/service/utils/chat/index';
|
||||||
import type { ChatItemType } from '@/types/chat';
|
import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
|
||||||
import { ChatRoleEnum } from '@/constants/chat';
|
import { ChatModuleEnum, ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat';
|
||||||
import { getAIChatApi, axiosConfig } from '@/service/ai/openai';
|
import { getAIChatApi, axiosConfig } from '@/service/ai/openai';
|
||||||
import type { ClassifyQuestionAgentItemType } from '@/types/app';
|
import type { ContextExtractAgentItemType } from '@/types/app';
|
||||||
import { SystemInputEnum } from '@/constants/app';
|
import { ContextExtractEnum } from '@/constants/flow/flowField';
|
||||||
|
import { countModelPrice } from '@/service/events/pushBill';
|
||||||
|
import { UserModelSchema } from '@/types/mongoSchema';
|
||||||
|
import { getModel } from '@/service/utils/data';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
systemPrompt?: string;
|
userOpenaiAccount: UserModelSchema['openaiAccount'];
|
||||||
history?: ChatItemType[];
|
history?: ChatItemType[];
|
||||||
[SystemInputEnum.userChatInput]: string;
|
[ContextExtractEnum.content]: string;
|
||||||
description: string;
|
[ContextExtractEnum.extractKeys]: ContextExtractAgentItemType[];
|
||||||
agents: ClassifyQuestionAgentItemType[];
|
[ContextExtractEnum.description]: string;
|
||||||
};
|
};
|
||||||
export type Response = {
|
export type Response = {
|
||||||
arguments: Record<string, any>;
|
[ContextExtractEnum.success]?: boolean;
|
||||||
deficiency: boolean;
|
[ContextExtractEnum.failed]?: boolean;
|
||||||
|
[ContextExtractEnum.fields]: Record<string, any>;
|
||||||
|
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const agentModel = 'gpt-3.5-turbo';
|
const agentModel = 'gpt-3.5-turbo';
|
||||||
const agentFunName = 'agent_extract_data';
|
const agentFunName = 'agent_extract_data';
|
||||||
const maxTokens = 3000;
|
const maxTokens = 3000;
|
||||||
|
|
||||||
export async function extract({
|
export async function dispatchContentExtract({
|
||||||
systemPrompt,
|
userOpenaiAccount,
|
||||||
agents,
|
content,
|
||||||
|
extractKeys,
|
||||||
history = [],
|
history = [],
|
||||||
userChatInput,
|
|
||||||
description
|
description
|
||||||
}: Props): Promise<Response> {
|
}: Props): Promise<Response> {
|
||||||
const messages: ChatItemType[] = [
|
const messages: ChatItemType[] = [
|
||||||
...(systemPrompt
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
obj: ChatRoleEnum.System,
|
|
||||||
value: systemPrompt
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...history,
|
...history,
|
||||||
{
|
{
|
||||||
obj: ChatRoleEnum.Human,
|
obj: ChatRoleEnum.Human,
|
||||||
value: userChatInput
|
value: content
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const filterMessages = ChatContextFilter({
|
const filterMessages = ChatContextFilter({
|
||||||
@@ -62,25 +56,25 @@ export async function extract({
|
|||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
> = {};
|
> = {};
|
||||||
agents.forEach((item) => {
|
extractKeys.forEach((item) => {
|
||||||
properties[item.key] = {
|
properties[item.key] = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: item.value
|
description: item.desc
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// function body
|
// function body
|
||||||
const agentFunction = {
|
const agentFunction = {
|
||||||
name: agentFunName,
|
name: agentFunName,
|
||||||
description,
|
description: `${description}\n如果内容不存在,返回空字符串。当前时间是2023/7/31 18:00`,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties,
|
properties,
|
||||||
required: agents.map((item) => item.key)
|
required: extractKeys.map((item) => item.key)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const chatAPI = getAIChatApi();
|
const chatAPI = getAIChatApi(userOpenaiAccount);
|
||||||
|
|
||||||
const response = await chatAPI.createChatCompletion(
|
const response = await chatAPI.createChatCompletion(
|
||||||
{
|
{
|
||||||
@@ -91,21 +85,44 @@ export async function extract({
|
|||||||
functions: [agentFunction]
|
functions: [agentFunction]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...axiosConfig()
|
...axiosConfig(userOpenaiAccount)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const arg = JSON.parse(response.data.choices?.[0]?.message?.function_call?.arguments || '{}');
|
const arg: Record<string, any> = (() => {
|
||||||
let deficiency = false;
|
try {
|
||||||
for (const key in arg) {
|
return JSON.parse(response.data.choices?.[0]?.message?.function_call?.arguments || '{}');
|
||||||
if (arg[key] === '') {
|
} catch (error) {
|
||||||
deficiency = true;
|
return {};
|
||||||
break;
|
}
|
||||||
|
})();
|
||||||
|
console.log(adaptMessages, arg);
|
||||||
|
|
||||||
|
// auth fields
|
||||||
|
let success = !extractKeys.find((item) => !arg[item.key]);
|
||||||
|
// auth empty value
|
||||||
|
if (success) {
|
||||||
|
for (const key in arg) {
|
||||||
|
if (arg[key] === '') {
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tokens = response.data.usage?.total_tokens || 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
arguments: arg,
|
[ContextExtractEnum.success]: success ? true : undefined,
|
||||||
deficiency
|
[ContextExtractEnum.failed]: success ? undefined : true,
|
||||||
|
[ContextExtractEnum.fields]: arg,
|
||||||
|
[TaskResponseKeyEnum.responseData]: {
|
||||||
|
moduleName: ChatModuleEnum.Extract,
|
||||||
|
price: userOpenaiAccount?.key ? 0 : countModelPrice({ model: agentModel, tokens }),
|
||||||
|
model: getModel(agentModel)?.name || agentModel,
|
||||||
|
tokens,
|
||||||
|
extractDescription: description,
|
||||||
|
extractResult: arg
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
6
client/src/types/app.d.ts
vendored
6
client/src/types/app.d.ts
vendored
@@ -8,6 +8,7 @@ import {
|
|||||||
import type { FlowInputItemType, FlowOutputItemType, FlowOutputTargetItemType } from './flow';
|
import type { FlowInputItemType, FlowOutputItemType, FlowOutputTargetItemType } from './flow';
|
||||||
import type { AppSchema, kbSchema } from './mongoSchema';
|
import type { AppSchema, kbSchema } from './mongoSchema';
|
||||||
import { ChatModelType } from '@/constants/model';
|
import { ChatModelType } from '@/constants/model';
|
||||||
|
import { FlowValueTypeEnum } from '@/constants/flow';
|
||||||
|
|
||||||
export type AppListItemType = {
|
export type AppListItemType = {
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -45,6 +46,11 @@ export type ClassifyQuestionAgentItemType = {
|
|||||||
value: string;
|
value: string;
|
||||||
key: string;
|
key: string;
|
||||||
};
|
};
|
||||||
|
export type ContextExtractAgentItemType = {
|
||||||
|
desc: string;
|
||||||
|
key: string;
|
||||||
|
required: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type VariableItemType = {
|
export type VariableItemType = {
|
||||||
id: string;
|
id: string;
|
||||||
|
4
client/src/types/chat.d.ts
vendored
4
client/src/types/chat.d.ts
vendored
@@ -66,4 +66,8 @@ export type ChatHistoryItemResType = {
|
|||||||
// cq
|
// cq
|
||||||
cqList?: ClassifyQuestionAgentItemType[];
|
cqList?: ClassifyQuestionAgentItemType[];
|
||||||
cqResult?: string;
|
cqResult?: string;
|
||||||
|
|
||||||
|
// content extract
|
||||||
|
extractDescription?: string;
|
||||||
|
extractResult?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user