mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-24 05:23:57 +00:00
v4.4.3 (#316)
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
"UnKnow": "UnKnow",
|
||||
"Warning": "Warning",
|
||||
"app": {
|
||||
"Quote Prompt Settings": "Quote Prompt Settings",
|
||||
"Advance App TestTip": "The current application is advanced editing mode \n. If you need to switch to [simple mode], please click the save button on the left",
|
||||
"App Detail": "App Detail",
|
||||
"Chat Logs Tips": "Logs record the app's online, shared, and API(chatId is existing) conversations",
|
||||
@@ -219,6 +220,10 @@
|
||||
"system": {
|
||||
"Help Document": "Document"
|
||||
},
|
||||
"template": {
|
||||
"Quote Content Tip": "This configuration takes effect only when reference content is passed in (knowledge base search). You can customize the structure of the reference content to better fit different scenarios. You can use {{q}}, {{a}}, {{source}} as \"search content\", \"expected content\", and \"source\", they are all optional, and here are the default values: \n{instruction:\"{{q}}\",output:\"{{a}}\"}",
|
||||
"Quote Prompt Tip": "This configuration takes effect only when reference content is passed in (knowledge base search). \n You can insert references with {{quote}}, here are the default values: \n\"\"\"{{quote}}\"\"\" The three quotes are the knowledge base I gave you, they have the highest priority. instruction is a relevant introduction and output is an expected answer or supplement."
|
||||
},
|
||||
"user": {
|
||||
"Account": "Account",
|
||||
"Amount of earnings": "Earnings",
|
||||
|
@@ -14,6 +14,7 @@
|
||||
"UnKnow": "未知",
|
||||
"Warning": "提示",
|
||||
"app": {
|
||||
"Quote Prompt Settings": "引用提示词配置",
|
||||
"Advance App TestTip": "当前应用为高级编排模式\n如需切换为【简易模式】请点击左侧保存按键",
|
||||
"App Detail": "应用详情",
|
||||
"Chat Logs Tips": "日志会记录该应用的在线、分享和 API(需填写 chatId) 对话记录",
|
||||
@@ -219,6 +220,10 @@
|
||||
"system": {
|
||||
"Help Document": "帮助文档"
|
||||
},
|
||||
"template": {
|
||||
"Quote Content Tip": "该配置只有传入引用内容(知识库搜索)时生效。\n可以自定义引用内容的结构,以更好的适配不同场景。可以使用 {{q}}, {{a}}, {{source}} 来作为 “检索内容”、“预期内容”和“来源”,他们都是可选的,下面是默认值:\n{{default}}",
|
||||
"Quote Prompt Tip": "该配置只有传入引用内容(知识库搜索)时生效。\n可以用 {{quote}} 来插入引用内容,使用 {{question}} 来插入问题。下面是默认值:\n{{default}}"
|
||||
},
|
||||
"user": {
|
||||
"Account": "账号",
|
||||
"Amount of earnings": "收益(¥)",
|
||||
|
@@ -613,11 +613,6 @@ const ChatBox = (
|
||||
flexDirection={'column'}
|
||||
alignItems={item.obj === 'Human' ? 'flex-end' : 'flex-start'}
|
||||
py={5}
|
||||
_hover={{
|
||||
'& .control': {
|
||||
display: item.status === 'finish' ? 'flex' : 'none'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.obj === 'Human' && (
|
||||
<>
|
||||
|
@@ -10,11 +10,10 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
interface Props extends ModalContentProps {
|
||||
showCloseBtn?: boolean;
|
||||
title?: any;
|
||||
isCentered?: boolean;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
const MyModal = ({
|
||||
@@ -22,14 +21,18 @@ const MyModal = ({
|
||||
onClose,
|
||||
title,
|
||||
children,
|
||||
showCloseBtn = true,
|
||||
isCentered,
|
||||
w = 'auto',
|
||||
maxW = ['90vw', '600px'],
|
||||
...props
|
||||
}: Props) => {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} autoFocus={false} isCentered={isCentered}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={() => onClose && onClose()}
|
||||
autoFocus={false}
|
||||
isCentered={isCentered}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent
|
||||
display={'flex'}
|
||||
@@ -43,7 +46,7 @@ const MyModal = ({
|
||||
>
|
||||
{!!title && <ModalHeader>{title}</ModalHeader>}
|
||||
<Box overflow={'overlay'} h={'100%'}>
|
||||
{showCloseBtn && <ModalCloseButton />}
|
||||
{onClose && <ModalCloseButton />}
|
||||
{children}
|
||||
</Box>
|
||||
</ModalContent>
|
||||
|
@@ -163,19 +163,23 @@ export const ChatModule: FlowModuleTemplateType = {
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
key: 'limitPrompt',
|
||||
type: FlowInputItemTypeEnum.textarea,
|
||||
key: 'quoteTemplate',
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
label: '引用内容模板',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
label: '引用内容提示词',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
label: '限定词',
|
||||
max: 500,
|
||||
description: ChatModelLimitTip,
|
||||
placeholder: ChatModelLimitTip,
|
||||
value: ''
|
||||
},
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: 'quoteQA',
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
type: FlowInputItemTypeEnum.custom,
|
||||
label: '引用内容',
|
||||
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
|
||||
valueType: FlowValueTypeEnum.kbQuote
|
||||
@@ -664,19 +668,6 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
||||
value: '',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'limitPrompt',
|
||||
type: 'textarea',
|
||||
valueType: 'string',
|
||||
label: '限定词',
|
||||
max: 500,
|
||||
description:
|
||||
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
||||
placeholder:
|
||||
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
||||
value: '',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
type: 'target',
|
||||
@@ -1013,18 +1004,6 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
||||
value: '',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'limitPrompt',
|
||||
type: 'textarea',
|
||||
valueType: 'string',
|
||||
label: '限定词',
|
||||
description:
|
||||
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
||||
placeholder:
|
||||
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
||||
value: '',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
type: 'target',
|
||||
@@ -1319,18 +1298,6 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
||||
value: '',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'limitPrompt',
|
||||
type: 'textarea',
|
||||
valueType: 'string',
|
||||
label: '限定词',
|
||||
description:
|
||||
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
||||
placeholder:
|
||||
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
||||
value: '将我的问题直接翻译成英语{{language}}',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
type: 'target',
|
||||
@@ -1703,19 +1670,6 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
||||
value: '知识库是关于 laf 的内容。',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'limitPrompt',
|
||||
type: 'textarea',
|
||||
valueType: 'string',
|
||||
label: '限定词',
|
||||
description:
|
||||
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
||||
placeholder:
|
||||
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
||||
value:
|
||||
'我的问题都是关于 laf 的。根据知识库回答我的问题,与 laf 无关问题,直接回复:“我不清楚,我仅能回答 laf 相关的问题。”。',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
type: 'target',
|
||||
|
@@ -64,16 +64,7 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={() => {
|
||||
if (payId) return;
|
||||
onClose();
|
||||
}}
|
||||
title={t('user.Pay')}
|
||||
isCentered
|
||||
showCloseBtn={!payId}
|
||||
>
|
||||
<MyModal isOpen={true} onClose={payId ? onClose : undefined} title={t('user.Pay')} isCentered>
|
||||
<ModalBody py={0}>
|
||||
{!payId && (
|
||||
<>
|
||||
|
@@ -1,446 +0,0 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { authUser } from '@/service/utils/auth';
|
||||
import { connectToDatabase, App } from '@/service/mongo';
|
||||
import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
|
||||
import { TaskResponseKeyEnum } from '@/constants/chat';
|
||||
import { FlowInputItemType } from '@/types/flow';
|
||||
|
||||
const chatModelInput = ({
|
||||
model,
|
||||
temperature,
|
||||
maxToken,
|
||||
systemPrompt,
|
||||
limitPrompt,
|
||||
kbList
|
||||
}: {
|
||||
model: string;
|
||||
temperature: number;
|
||||
maxToken: number;
|
||||
systemPrompt: string;
|
||||
limitPrompt: string;
|
||||
kbList: { kbId: string }[];
|
||||
}): FlowInputItemType[] => [
|
||||
{
|
||||
key: 'model',
|
||||
value: model,
|
||||
type: 'custom',
|
||||
label: '对话模型',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
value: temperature,
|
||||
label: '温度',
|
||||
type: 'slider',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'maxToken',
|
||||
value: maxToken,
|
||||
type: 'custom',
|
||||
label: '回复上限',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
value: systemPrompt,
|
||||
type: 'textarea',
|
||||
label: '系统提示词',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'limitPrompt',
|
||||
label: '限定词',
|
||||
type: 'textarea',
|
||||
value: limitPrompt,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
type: 'target',
|
||||
label: '触发器',
|
||||
connected: kbList.length > 0
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
type: 'target',
|
||||
label: '引用内容',
|
||||
connected: kbList.length > 0
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: 'target',
|
||||
label: '用户问题',
|
||||
connected: true
|
||||
}
|
||||
];
|
||||
const chatTemplate = ({
|
||||
model,
|
||||
temperature,
|
||||
maxToken,
|
||||
systemPrompt,
|
||||
limitPrompt
|
||||
}: {
|
||||
model: string;
|
||||
temperature: number;
|
||||
maxToken: number;
|
||||
systemPrompt: string;
|
||||
limitPrompt: string;
|
||||
}) => {
|
||||
return [
|
||||
{
|
||||
flowType: FlowModuleTypeEnum.questionInput,
|
||||
inputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 464.32198615344566,
|
||||
y: 1602.2698463081606
|
||||
},
|
||||
moduleId: 'userChatInput'
|
||||
},
|
||||
{
|
||||
flowType: FlowModuleTypeEnum.historyNode,
|
||||
inputs: [
|
||||
{
|
||||
key: 'maxContext',
|
||||
value: 10,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'history',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'history'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 452.5466249541586,
|
||||
y: 1276.3930310334215
|
||||
},
|
||||
moduleId: 'history'
|
||||
},
|
||||
{
|
||||
flowType: FlowModuleTypeEnum.chatNode,
|
||||
inputs: chatModelInput({
|
||||
model,
|
||||
temperature,
|
||||
maxToken,
|
||||
systemPrompt,
|
||||
limitPrompt,
|
||||
kbList: []
|
||||
}),
|
||||
outputs: [
|
||||
{
|
||||
key: TaskResponseKeyEnum.answerText,
|
||||
targets: []
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 981.9682828103937,
|
||||
y: 890.014595014464
|
||||
},
|
||||
moduleId: 'chatModule'
|
||||
}
|
||||
];
|
||||
};
|
||||
const kbTemplate = ({
|
||||
model,
|
||||
temperature,
|
||||
maxToken,
|
||||
systemPrompt,
|
||||
limitPrompt,
|
||||
kbList = [],
|
||||
searchSimilarity,
|
||||
searchLimit,
|
||||
searchEmptyText
|
||||
}: {
|
||||
model: string;
|
||||
temperature: number;
|
||||
maxToken: number;
|
||||
systemPrompt: string;
|
||||
limitPrompt: string;
|
||||
kbList: { kbId: string }[];
|
||||
searchSimilarity: number;
|
||||
searchLimit: number;
|
||||
searchEmptyText: string;
|
||||
}) => {
|
||||
return [
|
||||
{
|
||||
flowType: FlowModuleTypeEnum.questionInput,
|
||||
inputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'userChatInput'
|
||||
},
|
||||
{
|
||||
moduleId: 'kbSearch',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 464.32198615344566,
|
||||
y: 1602.2698463081606
|
||||
},
|
||||
moduleId: 'userChatInput'
|
||||
},
|
||||
{
|
||||
flowType: FlowModuleTypeEnum.historyNode,
|
||||
inputs: [
|
||||
{
|
||||
key: 'maxContext',
|
||||
value: 10,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'history',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'history'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 452.5466249541586,
|
||||
y: 1276.3930310334215
|
||||
},
|
||||
moduleId: 'history'
|
||||
},
|
||||
{
|
||||
flowType: FlowModuleTypeEnum.kbSearchNode,
|
||||
inputs: [
|
||||
{
|
||||
key: 'kbList',
|
||||
value: kbList,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'similarity',
|
||||
value: searchSimilarity,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'limit',
|
||||
value: searchLimit,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'isEmpty',
|
||||
targets: searchEmptyText
|
||||
? [
|
||||
{
|
||||
moduleId: 'emptyText',
|
||||
key: 'switch'
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'switch'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'unEmpty',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'switch'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'quoteQA'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 956.0838440206068,
|
||||
y: 887.462827870246
|
||||
},
|
||||
moduleId: 'kbSearch'
|
||||
},
|
||||
...(searchEmptyText
|
||||
? [
|
||||
{
|
||||
flowType: FlowModuleTypeEnum.answerNode,
|
||||
inputs: [
|
||||
{
|
||||
key: 'switch',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: SpecialInputKeyEnum.answerText,
|
||||
value: searchEmptyText,
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [],
|
||||
position: {
|
||||
x: 1553.5815811529146,
|
||||
y: 637.8753731306779
|
||||
},
|
||||
moduleId: 'emptyText'
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
flowType: FlowModuleTypeEnum.chatNode,
|
||||
inputs: chatModelInput({ model, temperature, maxToken, systemPrompt, limitPrompt, kbList }),
|
||||
outputs: [
|
||||
{
|
||||
key: TaskResponseKeyEnum.answerText,
|
||||
targets: []
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 1551.71405495818,
|
||||
y: 977.4911578918461
|
||||
},
|
||||
moduleId: 'chatModule'
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await authUser({ req, authRoot: true });
|
||||
await connectToDatabase();
|
||||
|
||||
const { limit = 1000 } = req.body as { limit: number };
|
||||
let skip = 0;
|
||||
const total = await App.countDocuments();
|
||||
let promise = Promise.resolve();
|
||||
console.log(total);
|
||||
|
||||
for (let i = 0; i < total; i += limit) {
|
||||
const skipVal = skip;
|
||||
skip += limit;
|
||||
promise = promise
|
||||
.then(() => init(limit, skipVal))
|
||||
.then(() => {
|
||||
console.log(skipVal);
|
||||
});
|
||||
}
|
||||
|
||||
await promise;
|
||||
|
||||
jsonRes(res, {});
|
||||
} catch (error) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function init(limit: number, skip: number) {
|
||||
// 遍历 app
|
||||
const apps = await App.find(
|
||||
{
|
||||
chat: { $ne: null },
|
||||
modules: { $exists: false }
|
||||
// userId: '63f9a14228d2a688d8dc9e1b'
|
||||
},
|
||||
'_id chat'
|
||||
).limit(limit);
|
||||
|
||||
return Promise.all(
|
||||
apps.map(async (app) => {
|
||||
if (!app.chat) return app;
|
||||
const modules = (() => {
|
||||
if (app.chat.relatedKbs.length === 0) {
|
||||
return chatTemplate({
|
||||
model: app.chat.chatModel,
|
||||
temperature: app.chat.temperature,
|
||||
maxToken: app.chat.maxToken,
|
||||
systemPrompt: app.chat.systemPrompt,
|
||||
limitPrompt: app.chat.limitPrompt
|
||||
});
|
||||
} else {
|
||||
return kbTemplate({
|
||||
model: app.chat.chatModel,
|
||||
temperature: app.chat.temperature,
|
||||
maxToken: app.chat.maxToken,
|
||||
systemPrompt: app.chat.systemPrompt,
|
||||
limitPrompt: app.chat.limitPrompt,
|
||||
kbList: app.chat.relatedKbs.map((id) => ({ kbId: id })),
|
||||
searchEmptyText: app.chat.searchEmptyText,
|
||||
searchLimit: app.chat.searchLimit,
|
||||
searchSimilarity: app.chat.searchSimilarity
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
await App.findByIdAndUpdate(app.id, {
|
||||
modules
|
||||
});
|
||||
return modules;
|
||||
})
|
||||
);
|
||||
}
|
@@ -31,6 +31,7 @@ import { SystemInputEnum } from '@/constants/app';
|
||||
import { getSystemTime } from '@/utils/user';
|
||||
import { authOutLinkChat } from '@/service/support/outLink/auth';
|
||||
import requestIp from 'request-ip';
|
||||
import { replaceVariable } from '@/utils/common/tools/text';
|
||||
|
||||
export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string };
|
||||
type FastGptWebChatProps = {
|
||||
@@ -424,10 +425,7 @@ function loadModules(
|
||||
}
|
||||
|
||||
// variables replace
|
||||
const replacedVal = item.value.replace(
|
||||
/{{(.*?)}}/g,
|
||||
(match, key) => variables[key.trim()] || match
|
||||
);
|
||||
const replacedVal = replaceVariable(item.value, variables);
|
||||
|
||||
return {
|
||||
key: item.key,
|
||||
|
112
client/src/pages/app/detail/components/AIChatSettingsModal.tsx
Normal file
112
client/src/pages/app/detail/components/AIChatSettingsModal.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { EditFormType } from '@/utils/app';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import {
|
||||
Box,
|
||||
BoxProps,
|
||||
Button,
|
||||
Flex,
|
||||
Link,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { defaultQuotePrompt, defaultQuoteTemplate } from '@/prompts/core/AIChat';
|
||||
import { feConfigs } from '@/store/static';
|
||||
|
||||
const AIChatSettingsModal = ({
|
||||
onClose,
|
||||
onSuccess,
|
||||
defaultData
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onSuccess: (e: EditFormType['chatModel']) => void;
|
||||
defaultData: EditFormType['chatModel'];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { register, handleSubmit } = useForm({
|
||||
defaultValues: defaultData
|
||||
});
|
||||
|
||||
const LabelStyles: BoxProps = {
|
||||
fontWeight: 'bold',
|
||||
mb: 1,
|
||||
fontSize: ['sm', 'md']
|
||||
};
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
title={
|
||||
<Flex alignItems={'flex-end'}>
|
||||
{t('app.Quote Prompt Settings')}
|
||||
{feConfigs?.show_doc && (
|
||||
<Link
|
||||
href={'https://doc.fastgpt.run/docs/use-cases/prompt/'}
|
||||
target={'_blank'}
|
||||
ml={1}
|
||||
textDecoration={'underline'}
|
||||
fontWeight={'normal'}
|
||||
fontSize={'md'}
|
||||
>
|
||||
查看说明
|
||||
</Link>
|
||||
)}
|
||||
</Flex>
|
||||
}
|
||||
w={'700px'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box>
|
||||
<Box {...LabelStyles}>
|
||||
引用内容模板
|
||||
<MyTooltip
|
||||
label={t('template.Quote Content Tip', { default: defaultQuoteTemplate })}
|
||||
forceShow
|
||||
>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<Textarea
|
||||
rows={4}
|
||||
placeholder={t('template.Quote Content Tip', { default: defaultQuoteTemplate }) || ''}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('quoteTemplate')}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={4}>
|
||||
<Box {...LabelStyles}>
|
||||
引用内容提示词
|
||||
<MyTooltip
|
||||
label={t('template.Quote Prompt Tip', { default: defaultQuotePrompt })}
|
||||
forceShow
|
||||
>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<Textarea
|
||||
rows={6}
|
||||
placeholder={t('template.Quote Prompt Tip', { default: defaultQuotePrompt }) || ''}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('quotePrompt')}
|
||||
/>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} onClick={onClose}>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
<Button ml={4} onClick={handleSubmit(onSuccess)}>
|
||||
{t('Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AIChatSettingsModal;
|
@@ -4,21 +4,36 @@ import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/flow';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderInput, { Label } from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import { FlowOutputItemTypeEnum } from '@/constants/flow';
|
||||
import MySelect from '@/components/Select';
|
||||
import { chatModelList } from '@/store/static';
|
||||
import MySlider from '@/components/Slider';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react';
|
||||
import { formatPrice } from '@/utils/user';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { AIChatProps } from '@/types/core/aiChat';
|
||||
|
||||
const AIChatSettingsModal = dynamic(() => import('../../../AIChatSettingsModal'));
|
||||
|
||||
const NodeChat = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs, onChangeNode } = data;
|
||||
const outputsLen = useMemo(
|
||||
() => outputs.filter((item) => item.type !== FlowOutputItemTypeEnum.hidden).length,
|
||||
[outputs]
|
||||
);
|
||||
|
||||
const chatModulesData = useMemo(() => {
|
||||
const obj: Record<string, any> = {};
|
||||
inputs.forEach((item) => {
|
||||
obj[item.key] = item.value;
|
||||
});
|
||||
return obj as AIChatProps;
|
||||
}, [inputs]);
|
||||
|
||||
const {
|
||||
isOpen: isOpenAIChatSetting,
|
||||
onOpen: onOpenAIChatSetting,
|
||||
onClose: onCloseAIChatSetting
|
||||
} = useDisclosure();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
@@ -109,21 +124,48 @@ const NodeChat = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
quoteQA: (inputItem) => {
|
||||
return (
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<MyIcon name={'settingLight'} w={'14px'} />}
|
||||
onClick={onOpenAIChatSetting}
|
||||
>
|
||||
引用提示词设置
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
{outputsLen > 0 && (
|
||||
<>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput
|
||||
onChangeNode={onChangeNode}
|
||||
moduleId={moduleId}
|
||||
flowOutputList={outputs}
|
||||
/>
|
||||
</Container>
|
||||
</>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
|
||||
{isOpenAIChatSetting && (
|
||||
<AIChatSettingsModal
|
||||
onClose={onCloseAIChatSetting}
|
||||
onSuccess={(e) => {
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
// @ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
onCloseAIChatSetting();
|
||||
}}
|
||||
defaultData={chatModulesData}
|
||||
/>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
|
@@ -63,6 +63,7 @@ import { useDatasetStore } from '@/store/dataset';
|
||||
const VariableEditModal = dynamic(() => import('../VariableEditModal'));
|
||||
const InfoModal = dynamic(() => import('../InfoModal'));
|
||||
const KBSelectModal = dynamic(() => import('../KBSelectModal'));
|
||||
const AIChatSettingsModal = dynamic(() => import('../AIChatSettingsModal'));
|
||||
|
||||
const Settings = ({ appId }: { appId: string }) => {
|
||||
const theme = useTheme();
|
||||
@@ -101,6 +102,11 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
name: 'kb.list'
|
||||
});
|
||||
|
||||
const {
|
||||
isOpen: isOpenAIChatSetting,
|
||||
onOpen: onOpenAIChatSetting,
|
||||
onClose: onCloseAIChatSetting
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenKbSelect,
|
||||
onOpen: onOpenKbSelect,
|
||||
@@ -335,51 +341,61 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
+ 新增
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box mt={2} borderRadius={'lg'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>变量名</Th>
|
||||
<Th>变量 key</Th>
|
||||
<Th>必填</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item, index) => (
|
||||
<Tr key={item.id}>
|
||||
<Td>{item.label} </Td>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => setEditVariable(item)}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => removeVariable(index)}
|
||||
/>
|
||||
</Td>
|
||||
{variables.length > 0 && (
|
||||
<Box
|
||||
mt={2}
|
||||
borderRadius={'lg'}
|
||||
overflow={'hidden'}
|
||||
borderWidth={'1px'}
|
||||
borderBottom="none"
|
||||
>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>变量名</Th>
|
||||
<Th>变量 key</Th>
|
||||
<Th>必填</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item, index) => (
|
||||
<Tr key={item.id}>
|
||||
<Td>{item.label} </Td>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => setEditVariable(item)}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => removeVariable(index)}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* model */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/AI.png'} w={'18px'} />
|
||||
<Box ml={2}>AI 配置</Box>
|
||||
<Box ml={2} flex={1}>
|
||||
AI 配置
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
@@ -452,20 +468,6 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
{...register('chatModel.systemPrompt')}
|
||||
></Textarea>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'flex-start'}>
|
||||
<Box {...LabelStyles}>
|
||||
限定词
|
||||
<MyTooltip label={ChatModelLimitTip} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<Textarea
|
||||
rows={5}
|
||||
placeholder={ChatModelLimitTip}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('chatModel.limitPrompt')}
|
||||
></Textarea>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
{/* kb */}
|
||||
@@ -483,6 +485,10 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
<MyIcon name={'edit'} w={'14px'} mr={1} />
|
||||
参数
|
||||
</Flex>
|
||||
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
|
||||
<MyIcon mr={1} name={'settingLight'} w={'14px'} />
|
||||
提示词
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex mt={1} color={'myGray.600'} fontSize={['sm', 'md']}>
|
||||
相似度: {getValues('kb.searchSimilarity')}, 单次搜索数量: {getValues('kb.searchLimit')},
|
||||
@@ -548,6 +554,16 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isOpenAIChatSetting && (
|
||||
<AIChatSettingsModal
|
||||
onClose={onCloseAIChatSetting}
|
||||
onSuccess={(e) => {
|
||||
setValue('chatModel', e);
|
||||
onCloseAIChatSetting();
|
||||
}}
|
||||
defaultData={getValues('chatModel')}
|
||||
/>
|
||||
)}
|
||||
{isOpenKbSelect && (
|
||||
<KBSelectModal
|
||||
activeKbs={selectedKbList.map((item) => ({
|
||||
|
@@ -55,7 +55,6 @@ const InfoModal = ({
|
||||
name: data.name,
|
||||
avatar: data.avatar,
|
||||
intro: data.intro,
|
||||
chat: data.chat,
|
||||
share: data.share
|
||||
});
|
||||
},
|
||||
|
@@ -282,8 +282,6 @@ export function EditLinkModal({
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
showCloseBtn={false}
|
||||
onClose={() => {}}
|
||||
title={isEdit ? titleMap.current.edit[type] : titleMap.current.create[type]}
|
||||
>
|
||||
<ModalBody>
|
||||
|
@@ -168,7 +168,7 @@ const InputDataModal = ({
|
||||
</Box>
|
||||
<Box flex={1} h={['50%', '100%']}>
|
||||
<Flex>
|
||||
<Box h={'30px'}>{'预期答案'}</Box>
|
||||
<Box h={'30px'}>{'补充内容'}</Box>
|
||||
<MyTooltip
|
||||
label={'匹配的知识点被命中后,这部分内容会随匹配知识点一起注入模型,引导模型回答'}
|
||||
>
|
||||
@@ -177,9 +177,8 @@ const InputDataModal = ({
|
||||
</Flex>
|
||||
<Textarea
|
||||
placeholder={
|
||||
'预期答案。这部分内容不会被搜索,但会作为"匹配的知识点"的内容补充,通常是问题的答案。总和最多 3000 字。'
|
||||
'这部分内容不会被搜索,但会作为"匹配的知识点"的内容补充,通常是问题的答案。'
|
||||
}
|
||||
maxLength={3000}
|
||||
resize={'none'}
|
||||
h={'calc(100% - 30px)'}
|
||||
{...register('a')}
|
||||
|
10
client/src/prompts/core/AIChat.ts
Normal file
10
client/src/prompts/core/AIChat.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const defaultQuoteTemplate = `{instruction:"{{q}}",output:"{{a}}"}`;
|
||||
export const defaultQuotePrompt = `你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充。
|
||||
2. 使用背景知识回答问题。
|
||||
3. 背景知识无法满足问题时,你需严谨的回答问题。
|
||||
我的问题是:"{{question}}"`;
|
@@ -17,12 +17,12 @@ import { ChatCompletionRequestMessageRoleEnum } from 'openai';
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
import { countMessagesTokens, sliceMessagesTB } from '@/utils/common/tiktoken';
|
||||
import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
|
||||
import { defaultQuotePrompt, defaultQuoteTemplate } from '@/prompts/core/AIChat';
|
||||
import type { AIChatProps } from '@/types/core/aiChat';
|
||||
import { replaceVariable } from '@/utils/common/tools/text';
|
||||
|
||||
export type ChatProps = {
|
||||
export type ChatProps = AIChatProps & {
|
||||
res: NextApiResponse;
|
||||
model: string;
|
||||
temperature?: number;
|
||||
maxToken?: number;
|
||||
history?: ChatItemType[];
|
||||
userChatInput: string;
|
||||
stream?: boolean;
|
||||
@@ -52,7 +52,9 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
|
||||
quoteQA = [],
|
||||
userChatInput,
|
||||
systemPrompt = '',
|
||||
limitPrompt = '',
|
||||
limitPrompt,
|
||||
quoteTemplate,
|
||||
quotePrompt,
|
||||
userOpenaiAccount,
|
||||
outputs
|
||||
} = props as ChatProps;
|
||||
@@ -67,16 +69,16 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
|
||||
return Promise.reject('The chat model is undefined, you need to select a chat model.');
|
||||
}
|
||||
|
||||
const { filterQuoteQA, quotePrompt, hasQuoteOutput } = filterQuote({
|
||||
const { filterQuoteQA, quoteText, hasQuoteOutput } = filterQuote({
|
||||
quoteQA,
|
||||
model: modelConstantsData
|
||||
model: modelConstantsData,
|
||||
quoteTemplate
|
||||
});
|
||||
|
||||
if (modelConstantsData.censor) {
|
||||
await textCensor({
|
||||
text: `${systemPrompt}
|
||||
${quotePrompt}
|
||||
${limitPrompt}
|
||||
${quoteText}
|
||||
${userChatInput}
|
||||
`
|
||||
});
|
||||
@@ -85,6 +87,7 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
|
||||
const { messages, filterMessages } = getChatMessages({
|
||||
model: modelConstantsData,
|
||||
history,
|
||||
quoteText,
|
||||
quotePrompt,
|
||||
userChatInput,
|
||||
systemPrompt,
|
||||
@@ -189,39 +192,40 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
|
||||
|
||||
function filterQuote({
|
||||
quoteQA = [],
|
||||
model
|
||||
model,
|
||||
quoteTemplate
|
||||
}: {
|
||||
quoteQA: ChatProps['quoteQA'];
|
||||
model: ChatModelItemType;
|
||||
quoteTemplate?: string;
|
||||
}) {
|
||||
const sliceResult = sliceMessagesTB({
|
||||
maxTokens: model.quoteMaxToken,
|
||||
messages: quoteQA.map((item) => ({
|
||||
obj: ChatRoleEnum.System,
|
||||
value: item.a ? `${item.q}\n${item.a}` : item.q
|
||||
value: replaceVariable(quoteTemplate || defaultQuoteTemplate, item)
|
||||
}))
|
||||
});
|
||||
|
||||
// slice filterSearch
|
||||
const filterQuoteQA = quoteQA.slice(0, sliceResult.length);
|
||||
|
||||
const quotePrompt =
|
||||
const quoteText =
|
||||
filterQuoteQA.length > 0
|
||||
? `"""${filterQuoteQA
|
||||
.map((item) =>
|
||||
item.a ? `{instruction:"${item.q}",output:"${item.a}"}` : `{instruction:"${item.q}"}`
|
||||
)
|
||||
.join('\n')}"""`
|
||||
? `${filterQuoteQA
|
||||
.map((item) => replaceVariable(quoteTemplate || defaultQuoteTemplate, item))
|
||||
.join('\n')}`
|
||||
: '';
|
||||
|
||||
return {
|
||||
filterQuoteQA,
|
||||
quotePrompt,
|
||||
quoteText,
|
||||
hasQuoteOutput: !!filterQuoteQA.find((item) => item.a)
|
||||
};
|
||||
}
|
||||
function getChatMessages({
|
||||
quotePrompt,
|
||||
quoteText,
|
||||
history = [],
|
||||
systemPrompt,
|
||||
limitPrompt,
|
||||
@@ -229,32 +233,28 @@ function getChatMessages({
|
||||
model,
|
||||
hasQuoteOutput
|
||||
}: {
|
||||
quotePrompt: string;
|
||||
quotePrompt?: string;
|
||||
quoteText: string;
|
||||
history: ChatProps['history'];
|
||||
systemPrompt: string;
|
||||
limitPrompt: string;
|
||||
limitPrompt?: string;
|
||||
userChatInput: string;
|
||||
model: ChatModelItemType;
|
||||
hasQuoteOutput: boolean;
|
||||
}) {
|
||||
const { quoteGuidePrompt } = getDefaultPrompt({ hasQuoteOutput });
|
||||
|
||||
const systemText = `${quotePrompt ? `${quoteGuidePrompt}\n\n` : ''}${systemPrompt}`;
|
||||
const question = hasQuoteOutput
|
||||
? replaceVariable(quotePrompt || defaultQuotePrompt, {
|
||||
quote: quoteText,
|
||||
question: userChatInput
|
||||
})
|
||||
: userChatInput;
|
||||
|
||||
const messages: ChatItemType[] = [
|
||||
...(systemText
|
||||
...(systemPrompt
|
||||
? [
|
||||
{
|
||||
obj: ChatRoleEnum.System,
|
||||
value: systemText
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(quotePrompt
|
||||
? [
|
||||
{
|
||||
obj: ChatRoleEnum.System,
|
||||
value: quotePrompt
|
||||
value: systemPrompt
|
||||
}
|
||||
]
|
||||
: []),
|
||||
@@ -269,7 +269,7 @@ function getChatMessages({
|
||||
: []),
|
||||
{
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: userChatInput
|
||||
value: question
|
||||
}
|
||||
];
|
||||
|
||||
@@ -375,11 +375,3 @@ async function streamResponse({
|
||||
answer
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultPrompt({ hasQuoteOutput }: { hasQuoteOutput?: boolean }) {
|
||||
return {
|
||||
quoteGuidePrompt: `三引号引用的内容是我提供给你的知识库,它们拥有最高优先级。instruction 是相关介绍${
|
||||
hasQuoteOutput ? ',output 是预期回答或补充。' : '。'
|
||||
}`
|
||||
};
|
||||
}
|
||||
|
10
client/src/types/core/aiChat.d.ts
vendored
Normal file
10
client/src/types/core/aiChat.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
export type AIChatProps = {
|
||||
model: string;
|
||||
systemPrompt: string;
|
||||
temperature: number;
|
||||
maxToken: number;
|
||||
quoteTemplate?: string;
|
||||
quotePrompt?: string;
|
||||
frequency: number;
|
||||
presence: number;
|
||||
};
|
11
client/src/types/mongoSchema.d.ts
vendored
11
client/src/types/mongoSchema.d.ts
vendored
@@ -50,17 +50,6 @@ export interface AppSchema {
|
||||
collection: number;
|
||||
};
|
||||
modules: AppModuleItemType[];
|
||||
chat?: {
|
||||
relatedKbs: string[];
|
||||
searchSimilarity: number;
|
||||
searchLimit: number;
|
||||
searchEmptyText: string;
|
||||
systemPrompt: string;
|
||||
limitPrompt: string;
|
||||
temperature: number;
|
||||
maxToken: number;
|
||||
chatModel: ChatModelType; // 聊天时用的模型,训练后就是训练的模型
|
||||
};
|
||||
}
|
||||
|
||||
export interface CollectionSchema {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import type { AppModuleItemType, VariableItemType } from '@/types/app';
|
||||
import { chatModelList, vectorModelList } from '@/store/static';
|
||||
import { chatModelList } from '@/store/static';
|
||||
import {
|
||||
FlowInputItemTypeEnum,
|
||||
FlowModuleTypeEnum,
|
||||
@@ -7,20 +7,12 @@ import {
|
||||
SpecialInputKeyEnum
|
||||
} from '@/constants/flow';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { TaskResponseKeyEnum } from '@/constants/chat';
|
||||
import type { SelectedKbType } from '@/types/plugin';
|
||||
import { FlowInputItemType } from '@/types/flow';
|
||||
import type { AIChatProps } from '@/types/core/aiChat';
|
||||
|
||||
export type EditFormType = {
|
||||
chatModel: {
|
||||
model: string;
|
||||
systemPrompt: string;
|
||||
limitPrompt: string;
|
||||
temperature: number;
|
||||
maxToken: number;
|
||||
frequency: number;
|
||||
presence: number;
|
||||
};
|
||||
chatModel: AIChatProps;
|
||||
kb: {
|
||||
list: SelectedKbType;
|
||||
searchSimilarity: number;
|
||||
@@ -41,8 +33,9 @@ export const getDefaultAppForm = (): EditFormType => {
|
||||
chatModel: {
|
||||
model: defaultChatModel.model,
|
||||
systemPrompt: '',
|
||||
limitPrompt: '',
|
||||
temperature: 0,
|
||||
quotePrompt: '',
|
||||
quoteTemplate: '',
|
||||
maxToken: defaultChatModel.contextMaxToken / 2,
|
||||
frequency: 0.5,
|
||||
presence: -0.5
|
||||
@@ -109,9 +102,14 @@ export const appModules2Form = (modules: AppModuleItemType[]) => {
|
||||
key: 'systemPrompt'
|
||||
});
|
||||
updateVal({
|
||||
formKey: 'chatModel.limitPrompt',
|
||||
formKey: 'chatModel.quoteTemplate',
|
||||
inputs: module.inputs,
|
||||
key: 'limitPrompt'
|
||||
key: 'quoteTemplate'
|
||||
});
|
||||
updateVal({
|
||||
formKey: 'chatModel.quotePrompt',
|
||||
inputs: module.inputs,
|
||||
key: 'quotePrompt'
|
||||
});
|
||||
} else if (module.flowType === FlowModuleTypeEnum.kbSearchNode) {
|
||||
updateVal({
|
||||
@@ -178,16 +176,23 @@ const chatModelInput = (formData: EditFormType): FlowInputItemType[] => [
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
value: formData.chatModel.systemPrompt,
|
||||
value: formData.chatModel.systemPrompt || '',
|
||||
type: 'textarea',
|
||||
label: '系统提示词',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'limitPrompt',
|
||||
type: 'textarea',
|
||||
value: formData.chatModel.limitPrompt,
|
||||
label: '限定词',
|
||||
key: 'quoteTemplate',
|
||||
value: formData.chatModel.quoteTemplate || '',
|
||||
type: 'hidden',
|
||||
label: '引用内容模板',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
value: formData.chatModel.quotePrompt || '',
|
||||
type: 'hidden',
|
||||
label: '引用内容提示词',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
|
@@ -81,7 +81,7 @@ export function sliceMessagesTB({
|
||||
const tokens = countPromptTokens(item.content, item.role);
|
||||
reduceTokens -= tokens;
|
||||
|
||||
if (tokens > 0) {
|
||||
if (reduceTokens > 0) {
|
||||
result.push(messages[i]);
|
||||
} else {
|
||||
break;
|
||||
|
12
client/src/utils/common/tools/text.ts
Normal file
12
client/src/utils/common/tools/text.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
replace {{variable}} to value
|
||||
*/
|
||||
export function replaceVariable(text: string, obj: Record<string, string>) {
|
||||
for (const key in obj) {
|
||||
const val = obj[key];
|
||||
if (typeof val !== 'string') continue;
|
||||
|
||||
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), val);
|
||||
}
|
||||
return text || '';
|
||||
}
|
@@ -3,8 +3,8 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"../../../../../Library/Caches/hugo_cache/modules/filecache/modules/pkg/mod/github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2@v2.21100.20000/package/dist/cjs/popper.js/*",
|
||||
"../../../../../Library/Caches/hugo_cache/modules/filecache/modules/pkg/mod/github.com/twbs/bootstrap@v5.3.0+incompatible/js/*"
|
||||
"../../../../../.cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2@v2.21100.20000/package/dist/cjs/popper.js/*",
|
||||
"../../../../../.cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/twbs/bootstrap@v5.3.0+incompatible/js/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
23
docSite/content/docs/installation/upgrading/442.md
Normal file
23
docSite/content/docs/installation/upgrading/442.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: '升级到 V4.4.2'
|
||||
description: 'FastGPT 从旧版本升级到 V4.4.2 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 994
|
||||
---
|
||||
|
||||
## 执行初始化 API
|
||||
|
||||
发起 1 个 HTTP 请求(记得携带 `headers.rootkey`,这个值是环境变量里的)
|
||||
|
||||
1. https://xxxxx/api/admin/initv442
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://{{host}}/api/admin/initv442' \
|
||||
--header 'rootkey: {{rootkey}}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
会给初始化 Mongo 的 Bill 表的索引,之前过期时间有误。
|
||||
|
@@ -1,12 +1,14 @@
|
||||
---
|
||||
title: "提示词 & 限定词"
|
||||
description: "FastGPT 提示词 & 限定词说明"
|
||||
title: "提示词 & 引用提示词"
|
||||
description: "FastGPT 提示词 & 引用提示词说明"
|
||||
icon: "sign_language"
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 310
|
||||
---
|
||||
|
||||
限定词从 V4.4.3 版本后去除,被“引用提示词”和“引用模板”替代。
|
||||
|
||||
# AI 对话消息组成
|
||||
|
||||
传递给 AI 模型的消息是一个数组,FastGPT 中这个数组的组成形式为:
|
||||
@@ -14,12 +16,9 @@ weight: 310
|
||||
```json
|
||||
[
|
||||
内置提示词(config.json 配置,一般为空)
|
||||
搜索引导词(有引用内容时候会默认携带)
|
||||
提示词 (用户输入的提示词)
|
||||
引用内容(结构看第二节)
|
||||
历史记录
|
||||
限定词(由用户填写,无默认值)
|
||||
问题
|
||||
问题(会由输入的问题、引用提示词和引用模板来决定)
|
||||
]
|
||||
```
|
||||
|
||||
@@ -27,43 +26,84 @@ weight: 310
|
||||
Tips: 可以通过点击上下文按键查看完整的
|
||||
{{% /alert %}}
|
||||
|
||||
# 引用内容结构
|
||||
# 引用模板和提示词设计
|
||||
|
||||
知识库采用 QA 对的格式存储,在转义成字符串时候会对应的转成 instruction 和 output。搜索引导词中会对这两个字段做说明,不需要重复补充。
|
||||
知识库采用 QA 对的格式存储,在转义成字符串时候会根据**引用模板**来进行格式化。知识库包含 3 个变量: q,a 和 source,可以通过 {{q}} {{a}} {{source}} 按需引入。下面一个模板例子:
|
||||
|
||||
{{% alert icon="🤖" context="success" %}}
|
||||
三引号引用的内容是我提供给你的知识库,它们拥有最高优先级。instruction 是相关介绍,output 是预期回答或补充。
|
||||
{{% /alert %}}
|
||||
**引用模板**
|
||||
|
||||
```
|
||||
"""
|
||||
{instruction:"本作的故事背景是什么?",output:"本作的故事背景是发生在日本灾难时期的东北地区。"}
|
||||
{instruction:"电影《铃芽之旅》讲述了什么故事?",output:"电影《铃芽之旅》讲述了少女岩户铃芽和关门师宗像草太为了关闭灾难之门展开的冒险旅程。"}
|
||||
{instruction:"电影《铃芽之旅》的故事背景是什么?",output:"日本"}
|
||||
"""
|
||||
{instruction:"{{q}}",output:"{{a}}",source:"{{source}}"}
|
||||
```
|
||||
|
||||
搜索到的知识库,会自动将 q,a,source 替换成对应的内容。每条搜索到的内容,会通过 `\n` 隔开。例如:
|
||||
```
|
||||
{instruction:"电影《铃芽之旅》的导演是谁?",output:"电影《铃芽之旅》的导演是新海诚。",source:"手动输入"}
|
||||
{instruction:"本作的主人公是谁?",output:"本作的主人公是名叫铃芽的少女。",source:""}
|
||||
{instruction:"电影《铃芽之旅》男主角是谁?",output:"电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。",source:""}
|
||||
{instruction:"电影《铃芽之旅》的编剧是谁?22",output:"新海诚是本片的编剧。",source:"手动输入"}
|
||||
```
|
||||
|
||||
**引用提示词**
|
||||
|
||||
引用模板需要和引用提示词一起使用,提示词中可以写引用模板的格式说明以及对话的要求等。可以使用 {{quote}} 来使用 **引用模板**,使用 {{question}} 来引入问题。例如:
|
||||
|
||||
```
|
||||
你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充。
|
||||
2. 使用背景知识回答问题。
|
||||
3. 背景知识无法回答问题时,你可以礼貌的的回答用户问题。
|
||||
我的问题是:"{{question}}"
|
||||
```
|
||||
|
||||
|
||||
# 提示词案例
|
||||
|
||||
## 仅回复知识库里的内容
|
||||
|
||||
{{% alert icon="🤖" context="warning" %}}
|
||||
**限定词**里添加:
|
||||
**引用提示词**里添加:
|
||||
```
|
||||
你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 回答前,请先判断背景知识是否足够回答问题,如果无法回答,请直接回复:“对不起,我无法回答你的问题~”。
|
||||
2. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充。
|
||||
3. 使用背景知识回答问题。
|
||||
我的问题是:"{{question}}"
|
||||
```
|
||||
|
||||
回答内容限制:你目前仅能回答三引号中提及的内容,超出引用的内容,请直接回复:“我不知道”
|
||||
{{% /alert %}}
|
||||
## 说明引用来源
|
||||
|
||||
注意,限定词会一定程度上打断上下文连贯性,且并不是 100% 生效。随着上下文和引用长度越多,限定词的效果会被削弱。实在控不住,用 GPT4 吧。
|
||||
**引用模板:**
|
||||
|
||||
## 为回答添加引用序号
|
||||
```
|
||||
{instruction:"{{q}}",output:"{{a}}",source:"{{source}}"}
|
||||
```
|
||||
|
||||
如果你希望回答内容带上引用的第几条的序号,可以参考下面的提示词:
|
||||
**引用提示词:**
|
||||
|
||||
{{% alert icon="🤖" context="warning" %}}
|
||||
**提示词**里添加:
|
||||
```
|
||||
你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充,source是背景来源。
|
||||
2. 使用背景知识回答问题。
|
||||
3. 在回答问题后,你需要给出本次回答对应的背景来源,来源展示格式如下:
|
||||
|
||||
我希望你的回答会附加上引用的序号:
|
||||
1.每个 {instruction,output} 包裹的内容是一条引用
|
||||
2.从上往下,序列号从 1-n
|
||||
3.回答的内容应使用 [1][2] 这个特殊的格式来标记引用序列号
|
||||
{{% /alert %}}
|
||||
“
|
||||
这是AI作答。本次知识来源:
|
||||
1. source1
|
||||
2. source2
|
||||
......
|
||||
”
|
||||
|
||||
我的问题是:"{{question}}"
|
||||
```
|
||||
|
Reference in New Issue
Block a user