V4.6.5-alpha (#609)

This commit is contained in:
Archer
2023-12-15 15:57:39 +08:00
committed by GitHub
parent dd7b4b98ae
commit 05bf1b2265
127 changed files with 4283 additions and 2315 deletions

View File

@@ -40,17 +40,19 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
const cqModel = getCQModel(model);
const chatHistories = getHistories(history, histories);
const { arg, tokens } = await (async () => {
if (cqModel.functionCall) {
return functionCall({
...props,
histories: getHistories(history, histories),
histories: chatHistories,
cqModel
});
}
return completions({
...props,
histories: getHistories(history, histories),
histories: chatHistories,
cqModel
});
})();
@@ -65,7 +67,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
query: userChatInput,
tokens,
cqList: agents,
cqResult: result.value
cqResult: result.value,
contextTotalLen: chatHistories.length + 2
}
};
};
@@ -115,7 +118,7 @@ ${systemPrompt}
required: ['type']
}
};
const ai = getAIApi(user.openaiAccount, 48000);
const ai = getAIApi(user.openaiAccount, 480000);
const response = await ai.chat.completions.create({
model: cqModel.model,

View File

@@ -38,18 +38,19 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
}
const extractModel = global.extractModels[0];
const chatHistories = getHistories(history, histories);
const { arg, tokens } = await (async () => {
if (extractModel.functionCall) {
return functionCall({
...props,
histories: getHistories(history, histories),
histories: chatHistories,
extractModel
});
}
return completions({
...props,
histories: getHistories(history, histories),
histories: chatHistories,
extractModel
});
})();
@@ -84,7 +85,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
query: content,
tokens,
extractDescription: description,
extractResult: arg
extractResult: arg,
contextTotalLen: chatHistories.length + 2
}
};
}
@@ -100,11 +102,11 @@ async function functionCall({
{
obj: ChatRoleEnum.Human,
value: `<任务描述>
${description || '根据用户要求取适当的 JSON 字符串。'}
${description || '根据用户要求取适当的 JSON 字符串。'}
- 如果字段为空,你返回空字符串。
- 不要换行。
- 结合历史记录和文本进行取。
- 结合历史记录和文本进行取。
</任务描述>
<文本>
@@ -128,7 +130,8 @@ ${content}
extractKeys.forEach((item) => {
properties[item.key] = {
type: 'string',
description: item.desc
description: item.desc,
...(item.enum ? { enum: item.enum.split('\n') } : {})
};
});
@@ -192,7 +195,9 @@ async function completions({
json: extractKeys
.map(
(item) =>
`{"key":"${item.key}", "description":"${item.required}", "required":${item.required}}}`
`{"key":"${item.key}", "description":"${item.required}", "required":${item.required}${
item.enum ? `, "enum":"[${item.enum.split('\n')}]"` : ''
}}`
)
.join('\n'),
text: `${histories.map((item) => `${item.obj}:${item.value}`).join('\n')}

View File

@@ -202,7 +202,8 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
query: userChatInput,
maxToken: max_tokens,
quoteList: filterQuoteQA,
historyPreview: getHistoryPreview(completeMessages)
historyPreview: getHistoryPreview(completeMessages),
contextTotalLen: completeMessages.length
},
history: completeMessages
};

View File

@@ -26,6 +26,21 @@ import { dispatchRunPlugin } from './plugin/run';
import { dispatchPluginInput } from './plugin/runInput';
import { dispatchPluginOutput } from './plugin/runOutput';
const callbackMap: Record<string, Function> = {
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
[FlowNodeTypeEnum.questionInput]: dispatchChatInput,
[FlowNodeTypeEnum.answerNode]: dispatchAnswer,
[FlowNodeTypeEnum.chatNode]: dispatchChatCompletion,
[FlowNodeTypeEnum.datasetSearchNode]: dispatchDatasetSearch,
[FlowNodeTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
[FlowNodeTypeEnum.contentExtract]: dispatchContentExtract,
[FlowNodeTypeEnum.httpRequest]: dispatchHttpRequest,
[FlowNodeTypeEnum.runApp]: dispatchAppRequest,
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
[FlowNodeTypeEnum.pluginInput]: dispatchPluginInput,
[FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput
};
/* running */
export async function dispatchModules({
res,
@@ -111,6 +126,7 @@ export async function dispatchModules({
Object.entries(data).map(([key, val]: any) => {
updateInputValue(key, val);
});
return;
}
function moduleOutput(
@@ -119,6 +135,7 @@ export async function dispatchModules({
): Promise<any> {
pushStore(module, result);
//
const nextRunModules: RunningModuleItemType[] = [];
// Assign the output value to the next module
@@ -141,18 +158,19 @@ export async function dispatchModules({
});
});
return checkModulesCanRun(nextRunModules);
}
function checkModulesCanRun(modules: RunningModuleItemType[] = []) {
// Ensure the uniqueness of running modules
const set = new Set<string>();
const filterModules = modules.filter((module) => {
const filterModules = nextRunModules.filter((module) => {
if (set.has(module.moduleId)) return false;
set.add(module.moduleId);
return true;
});
return checkModulesCanRun(filterModules);
}
function checkModulesCanRun(modules: RunningModuleItemType[] = []) {
return Promise.all(
filterModules.map((module) => {
modules.map((module) => {
if (!module.inputs.find((item: any) => item.value === undefined)) {
moduleInput(module, { [ModuleInputKeyEnum.switch]: undefined });
return moduleRun(module);
@@ -192,20 +210,6 @@ export async function dispatchModules({
};
const dispatchRes: Record<string, any> = await (async () => {
const callbackMap: Record<string, Function> = {
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
[FlowNodeTypeEnum.questionInput]: dispatchChatInput,
[FlowNodeTypeEnum.answerNode]: dispatchAnswer,
[FlowNodeTypeEnum.chatNode]: dispatchChatCompletion,
[FlowNodeTypeEnum.datasetSearchNode]: dispatchDatasetSearch,
[FlowNodeTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
[FlowNodeTypeEnum.contentExtract]: dispatchContentExtract,
[FlowNodeTypeEnum.httpRequest]: dispatchHttpRequest,
[FlowNodeTypeEnum.runApp]: dispatchAppRequest,
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
[FlowNodeTypeEnum.pluginInput]: dispatchPluginInput,
[FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput
};
if (callbackMap[module.flowType]) {
return callbackMap[module.flowType](props);
}
@@ -232,6 +236,11 @@ export async function dispatchModules({
// start process width initInput
const initModules = runningModules.filter((item) => initRunningModuleType[item.flowType]);
// runningModules.forEach((item) => {
// console.log(item);
// });
initModules.map((module) =>
moduleInput(module, {
...startParams,

View File

@@ -1,7 +1,11 @@
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import { dispatchModules } from '../index';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import {
DYNAMIC_INPUT_KEY,
ModuleInputKeyEnum,
ModuleOutputKeyEnum
} from '@fastgpt/global/core/module/constants';
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import { getPluginRuntimeById } from '@fastgpt/service/core/plugin/controller';
import { authPluginCanUse } from '@fastgpt/service/support/permission/auth/plugin';
@@ -29,13 +33,37 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
await authPluginCanUse({ id: pluginId, teamId, tmbId });
const plugin = await getPluginRuntimeById(pluginId);
// concat dynamic inputs
const inputModule = plugin.modules.find((item) => item.flowType === FlowNodeTypeEnum.pluginInput);
if (!inputModule) return Promise.reject('Plugin error, It has no set input.');
const hasDynamicInput = inputModule.inputs.find((input) => input.key === DYNAMIC_INPUT_KEY);
const startParams: Record<string, any> = (() => {
if (!hasDynamicInput) return data;
const params: Record<string, any> = {
[DYNAMIC_INPUT_KEY]: {}
};
for (const key in data) {
const input = inputModule.inputs.find((input) => input.key === key);
if (input) {
params[key] = data[key];
} else {
params[DYNAMIC_INPUT_KEY][key] = data[key];
}
}
return params;
})();
const { responseData, answerText } = await dispatchModules({
...props,
modules: plugin.modules.map((module) => ({
...module,
showStatus: false
})),
startParams: data
startParams
});
const output = responseData.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput);
@@ -46,6 +74,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
return {
answerText,
// responseData, // debug
responseData: {
moduleLogo: plugin.avatar,
price: responseData.reduce((sum, item) => sum + (item.price || 0), 0),

View File

@@ -1,9 +1,14 @@
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import axios from 'axios';
import { flatDynamicParams } from '../utils';
export type HttpRequestProps = ModuleDispatchProps<{
[ModuleInputKeyEnum.httpUrl]: string;
[ModuleInputKeyEnum.abandon_httpUrl]: string;
[ModuleInputKeyEnum.httpMethod]: string;
[ModuleInputKeyEnum.httpReqUrl]: string;
[ModuleInputKeyEnum.httpHeader]: string;
[key: string]: any;
}>;
export type HttpResponse = {
@@ -12,39 +17,98 @@ export type HttpResponse = {
[key: string]: any;
};
export const dispatchHttpRequest = async (props: Record<string, any>): Promise<HttpResponse> => {
const {
export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<HttpResponse> => {
let {
appId,
chatId,
variables,
inputs: { url, ...body }
} = props as HttpRequestProps;
inputs: {
system_httpMethod: httpMethod,
url: abandonUrl,
system_httpReqUrl: httpReqUrl,
system_httpHeader: httpHeader,
...body
}
} = props;
const requestBody = {
...body,
chatId,
variables
};
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,
variables,
data: body
},
requestQuery: {
appId,
chatId,
...variables,
...body
}
};
}
return Promise.reject('url is empty');
})();
const formatBody = transformFlatJson({ ...requestBody });
// parse header
const headers = await (() => {
try {
if (!requestHeader) return {};
return JSON.parse(requestHeader);
} catch (error) {
return Promise.reject('Header 为非法 JSON 格式');
}
})();
try {
const response = await fetchData({
url,
body: requestBody
method: requestMethod,
url: requestUrl,
headers,
body: formatBody,
query: requestQuery
});
return {
responseData: {
price: 0,
body: requestBody,
body: formatBody,
httpResult: response
},
...response
};
} catch (error) {
console.log(error);
return {
[ModuleOutputKeyEnum.failed]: true,
responseData: {
price: 0,
body: requestBody,
body: formatBody,
httpResult: { error }
}
};
@@ -52,19 +116,97 @@ export const dispatchHttpRequest = async (props: Record<string, any>): Promise<H
};
async function fetchData({
method,
url,
body
headers,
body,
query
}: {
method: string;
url: string;
headers: Record<string, any>;
body: Record<string, any>;
query: Record<string, any>;
}): Promise<Record<string, any>> {
const response = await fetch(url, {
method: 'POST',
const { data: response } = await axios<Record<string, any>>({
method,
baseURL: `http://${process.env.HOSTNAME || 'localhost'}:${process.env.PORT || 3000}`,
url,
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
...headers
},
body: JSON.stringify(body)
}).then((res) => res.json());
params: method === 'GET' ? query : {},
data: method === 'POST' ? body : {}
});
return response;
/*
parse the json:
{
user: {
name: 'xxx',
age: 12
}
psw: 'xxx'
}
result: {
'user': {
name: 'xxx',
age: 12
},
'user.name': 'xxx',
'user.age': 12,
'psw': 'xxx'
}
*/
const parseJson = (obj: Record<string, any>, prefix = '') => {
let result: Record<string, any> = {};
for (const key in obj) {
if (typeof obj[key] === 'object') {
result[key] = obj[key];
result = {
...result,
...parseJson(obj[key], `${prefix}${key}.`)
};
} else {
result[`${prefix}${key}`] = obj[key];
}
}
return result;
};
return parseJson(response);
}
function transformFlatJson(obj: Record<string, any>) {
for (let key in obj) {
if (typeof obj[key] === 'object') {
transformFlatJson(obj[key]);
}
if (key.includes('.')) {
let parts = key.split('.');
if (parts.length <= 1) continue;
const firstKey = parts.shift();
if (!firstKey) continue;
const lastKey = parts.join('.');
if (obj[firstKey]) {
obj[firstKey] = {
...obj[firstKey],
[lastKey]: obj[key]
};
} else {
obj[firstKey] = { [lastKey]: obj[key] };
}
transformFlatJson(obj[firstKey]);
delete obj[key];
}
}
return obj;
}

View File

@@ -8,6 +8,7 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { textAdaptGptResponse } from '@/utils/adapt';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { getHistories } from '../utils';
type Props = ModuleDispatchProps<{
userChatInput: string;
@@ -26,6 +27,7 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
user,
stream,
detail,
histories,
inputs: { userChatInput, history = [], app }
} = props;
@@ -52,17 +54,19 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
});
}
const chatHistories = getHistories(history, histories);
const { responseData, answerText } = await dispatchModules({
...props,
appId: app.id,
modules: appData.modules,
histories: history,
histories: chatHistories,
startParams: {
userChatInput
}
});
const completeMessages = history.concat([
const completeMessages = chatHistories.concat([
{
obj: ChatRoleEnum.Human,
value: userChatInput

View File

@@ -1,4 +1,5 @@
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { DYNAMIC_INPUT_KEY } from '@fastgpt/global/core/module/constants';
export const getHistories = (history?: ChatItemType[] | number, histories: ChatItemType[] = []) => {
if (!history) return [];
@@ -7,3 +8,13 @@ export const getHistories = (history?: ChatItemType[] | number, histories: ChatI
return [];
};
export const flatDynamicParams = (params: Record<string, any>) => {
const dynamicParams = params[DYNAMIC_INPUT_KEY];
if (!dynamicParams) return params;
return {
...params,
...dynamicParams,
[DYNAMIC_INPUT_KEY]: undefined
};
};

View File

@@ -6,6 +6,7 @@ import { addLog } from '@fastgpt/service/common/system/log';
import type { ConcatBillProps, CreateBillProps } from '@fastgpt/global/support/wallet/bill/api.d';
import { defaultQGModels } from '@fastgpt/global/core/ai/model';
import { POST } from '@fastgpt/service/common/api/plusRequest';
import { PostReRankProps } from '@fastgpt/global/core/ai/api';
export function createBill(data: CreateBillProps) {
if (!global.systemEnv?.pluginBaseUrl) return;
@@ -247,16 +248,21 @@ export function pushWhisperBill({
export function pushReRankBill({
teamId,
tmbId,
source
source,
inputs
}: {
teamId: string;
tmbId: string;
source: `${BillSourceEnum}`;
inputs: PostReRankProps['inputs'];
}) {
const model = global.reRankModels[0];
if (!model) return { total: 0 };
const total = model.price * PRICE_SCALE;
const textLength = inputs.reduce((sum, item) => sum + item.text.length, 0);
const ratio = Math.ceil(textLength / 1000);
const total = model.price * PRICE_SCALE * ratio;
const name = 'wallet.bill.ReRank';
createBill({