4.8 preview (#1288)
* Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * perf: workflow ux * system config * Newflow (#89) * docs: Add doc for Xinference (#1266) Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * perf: workflow ux * system config * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * rename code * move code * update flow * input type selector * perf: workflow runtime * feat: node adapt newflow * feat: adapt plugin * feat: 360 connection * check workflow * perf: flow 性能 * change plugin input type (#81) * change plugin input type * plugin label mode * perf: nodecard * debug * perf: debug ui * connection ui * change workflow ui (#82) * feat: workflow debug * adapt openAPI for new workflow (#83) * adapt openAPI for new workflow * i18n * perf: plugin debug * plugin input ui * delete * perf: global variable select * fix rebase * perf: workflow performance * feat: input render type icon * input icon * adapt flow (#84) * adapt newflow * temp * temp * fix * feat: app schedule trigger * feat: app schedule trigger * perf: schedule ui * feat: ioslatevm run js code * perf: workflow varialbe table ui * feat: adapt simple mode * feat: adapt input params * output * feat: adapt tamplate * fix: ts * add if-else module (#86) * perf: worker * if else node * perf: tiktoken worker * fix: ts * perf: tiktoken * fix if-else node (#87) * fix if-else node * type * fix * perf: audio render * perf: Parallel worker * log * perf: if else node * adapt plugin * prompt * perf: reference ui * reference ui * handle ux * template ui and plugin tool * adapt v1 workflow * adapt v1 workflow completions * perf: time variables * feat: workflow keyboard shortcuts * adapt v1 workflow * update workflow example doc (#88) * fix: simple mode select tool --------- Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com> * doc * perf: extract node * extra node field * update plugin version * doc * variable * change doc & fix prompt editor (#90) * fold workflow code * value type label --------- Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
@@ -1,50 +1,39 @@
|
||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { filterGPTMessageByMaxTokens } from '../../../chat/utils';
|
||||
import {
|
||||
countGptMessagesTokens,
|
||||
countMessagesTokens
|
||||
} from '@fastgpt/global/common/string/tiktoken';
|
||||
import { countMessagesTokens } from '../../../../common/string/tiktoken/index';
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { getAIApi } from '../../../ai/config';
|
||||
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { Prompt_CQJson } from '@fastgpt/global/core/ai/prompt/agent';
|
||||
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { ModelTypeEnum, getLLMModel } from '../../../ai/model';
|
||||
import { getHistories } from '../utils';
|
||||
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||
import {
|
||||
ChatCompletionCreateParams,
|
||||
ChatCompletionMessageParam,
|
||||
ChatCompletionTool
|
||||
} from '@fastgpt/global/core/ai/type';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.aiModel]: string;
|
||||
[ModuleInputKeyEnum.aiSystemPrompt]?: string;
|
||||
[ModuleInputKeyEnum.history]?: ChatItemType[] | number;
|
||||
[ModuleInputKeyEnum.userChatInput]: string;
|
||||
[ModuleInputKeyEnum.agents]: ClassifyQuestionAgentItemType[];
|
||||
[NodeInputKeyEnum.aiModel]: string;
|
||||
[NodeInputKeyEnum.aiSystemPrompt]?: string;
|
||||
[NodeInputKeyEnum.history]?: ChatItemType[] | number;
|
||||
[NodeInputKeyEnum.userChatInput]: string;
|
||||
[NodeInputKeyEnum.agents]: ClassifyQuestionAgentItemType[];
|
||||
}>;
|
||||
type CQResponse = DispatchNodeResultType<{
|
||||
[key: string]: any;
|
||||
[NodeOutputKeyEnum.cqResult]: string;
|
||||
}>;
|
||||
type ActionProps = Props & { cqModel: LLMModelItemType };
|
||||
|
||||
const agentFunName = 'classify_question';
|
||||
|
||||
/* request openai chat */
|
||||
export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse> => {
|
||||
const {
|
||||
user,
|
||||
module: { name },
|
||||
node: { nodeId, name },
|
||||
histories,
|
||||
params: { model, history = 6, agents, userChatInput }
|
||||
} = props as Props;
|
||||
@@ -57,27 +46,11 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const { arg, tokens } = await (async () => {
|
||||
if (cqModel.toolChoice) {
|
||||
return toolChoice({
|
||||
...props,
|
||||
histories: chatHistories,
|
||||
cqModel
|
||||
});
|
||||
}
|
||||
if (cqModel.functionCall) {
|
||||
return functionCall({
|
||||
...props,
|
||||
histories: chatHistories,
|
||||
cqModel
|
||||
});
|
||||
}
|
||||
return completions({
|
||||
...props,
|
||||
histories: chatHistories,
|
||||
cqModel
|
||||
});
|
||||
})();
|
||||
const { arg, tokens } = await completions({
|
||||
...props,
|
||||
histories: chatHistories,
|
||||
cqModel
|
||||
});
|
||||
|
||||
const result = agents.find((item) => item.key === arg?.type) || agents[agents.length - 1];
|
||||
|
||||
@@ -88,7 +61,10 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
});
|
||||
|
||||
return {
|
||||
[result.key]: true,
|
||||
[NodeOutputKeyEnum.cqResult]: result.value,
|
||||
[DispatchNodeResponseKeyEnum.skipHandleId]: agents
|
||||
.filter((item) => item.key !== arg?.type)
|
||||
.map((item) => getHandleId(nodeId, 'source', item.key)),
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
|
||||
model: modelName,
|
||||
@@ -109,164 +85,6 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
};
|
||||
};
|
||||
|
||||
const getFunctionCallSchema = ({
|
||||
cqModel,
|
||||
histories,
|
||||
params: { agents, systemPrompt, userChatInput }
|
||||
}: ActionProps) => {
|
||||
const messages: ChatItemType[] = [
|
||||
...histories,
|
||||
{
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: systemPrompt
|
||||
? `<背景知识>
|
||||
${systemPrompt}
|
||||
</背景知识>
|
||||
|
||||
问题: "${userChatInput}"
|
||||
`
|
||||
: userChatInput
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const adaptMessages = chats2GPTMessages({ messages, reserveId: false });
|
||||
const filterMessages = filterGPTMessageByMaxTokens({
|
||||
messages: adaptMessages,
|
||||
maxTokens: cqModel.maxContext
|
||||
});
|
||||
|
||||
// function body
|
||||
const agentFunction = {
|
||||
name: agentFunName,
|
||||
description: '结合对话记录及背景知识,对问题进行分类,并返回对应的类型字段',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
description: `问题类型。下面是几种可选的问题类型: ${agents
|
||||
.map((item) => `${item.value},返回:'${item.key}'`)
|
||||
.join(';')}`,
|
||||
enum: agents.map((item) => item.key)
|
||||
}
|
||||
},
|
||||
required: ['type']
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
agentFunction,
|
||||
filterMessages
|
||||
};
|
||||
};
|
||||
|
||||
const toolChoice = async (props: ActionProps) => {
|
||||
const { user, cqModel } = props;
|
||||
|
||||
const { agentFunction, filterMessages } = getFunctionCallSchema(props);
|
||||
// function body
|
||||
const tools: ChatCompletionTool[] = [
|
||||
{
|
||||
type: 'function',
|
||||
function: agentFunction
|
||||
}
|
||||
];
|
||||
|
||||
const ai = getAIApi({
|
||||
userKey: user.openaiAccount,
|
||||
timeout: 480000
|
||||
});
|
||||
|
||||
const response = await ai.chat.completions.create({
|
||||
model: cqModel.model,
|
||||
temperature: 0,
|
||||
messages: filterMessages,
|
||||
tools,
|
||||
tool_choice: { type: 'function', function: { name: agentFunName } }
|
||||
});
|
||||
|
||||
try {
|
||||
const arg = JSON.parse(
|
||||
response?.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments || ''
|
||||
);
|
||||
const completeMessages: ChatCompletionMessageParam[] = [
|
||||
...filterMessages,
|
||||
{
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
tool_calls: response.choices?.[0]?.message?.tool_calls
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
arg,
|
||||
tokens: countGptMessagesTokens(completeMessages, tools)
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(response.choices?.[0]?.message);
|
||||
|
||||
console.log('Your model may not support toll_call', error);
|
||||
|
||||
return {
|
||||
arg: {},
|
||||
tokens: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const functionCall = async (props: ActionProps) => {
|
||||
const { user, cqModel } = props;
|
||||
|
||||
const { agentFunction, filterMessages } = getFunctionCallSchema(props);
|
||||
const functions: ChatCompletionCreateParams.Function[] = [agentFunction];
|
||||
|
||||
const ai = getAIApi({
|
||||
userKey: user.openaiAccount,
|
||||
timeout: 480000
|
||||
});
|
||||
|
||||
const response = await ai.chat.completions.create({
|
||||
model: cqModel.model,
|
||||
temperature: 0,
|
||||
messages: filterMessages,
|
||||
function_call: {
|
||||
name: agentFunName
|
||||
},
|
||||
functions
|
||||
});
|
||||
|
||||
try {
|
||||
const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || '');
|
||||
const completeMessages: ChatCompletionMessageParam[] = [
|
||||
...filterMessages,
|
||||
{
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
function_call: response.choices?.[0]?.message?.function_call
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
arg,
|
||||
tokens: countGptMessagesTokens(completeMessages, undefined, functions)
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(response.choices?.[0]?.message);
|
||||
|
||||
console.log('Your model may not support toll_call', error);
|
||||
|
||||
return {
|
||||
arg: {},
|
||||
tokens: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const completions = async ({
|
||||
cqModel,
|
||||
user,
|
||||
@@ -283,11 +101,11 @@ const completions = async ({
|
||||
content: replaceVariable(cqModel.customCQPrompt || Prompt_CQJson, {
|
||||
systemPrompt: systemPrompt || 'null',
|
||||
typeList: agents
|
||||
.map((item) => `{"questionType": "${item.value}", "typeId": "${item.key}"}`)
|
||||
.join('\n'),
|
||||
.map((item) => `{"类型ID":"${item.key}", "问题类型":"${item.value}"}`)
|
||||
.join('------'),
|
||||
history: histories
|
||||
.map((item) => `${item.obj}:${chatValue2RuntimePrompt(item.value).text}`)
|
||||
.join('\n'),
|
||||
.join('------'),
|
||||
question: userChatInput
|
||||
})
|
||||
}
|
||||
@@ -309,11 +127,14 @@ const completions = async ({
|
||||
});
|
||||
const answer = data.choices?.[0].message?.content || '';
|
||||
|
||||
console.log(JSON.stringify(chats2GPTMessages({ messages, reserveId: false }), null, 2));
|
||||
console.log(answer, '----');
|
||||
|
||||
const id =
|
||||
agents.find((item) => answer.includes(item.key) || answer.includes(item.value))?.key || '';
|
||||
|
||||
return {
|
||||
tokens: countMessagesTokens(messages),
|
||||
tokens: await countMessagesTokens(messages),
|
||||
arg: { type: id }
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,15 +2,15 @@ import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { filterGPTMessageByMaxTokens } from '../../../chat/utils';
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import {
|
||||
countGptMessagesTokens,
|
||||
countMessagesTokens
|
||||
} from '@fastgpt/global/common/string/tiktoken';
|
||||
countMessagesTokens,
|
||||
countGptMessagesTokens
|
||||
} from '../../../../common/string/tiktoken/index';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { getAIApi } from '../../../ai/config';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { Prompt_ExtractJson } from '@fastgpt/global/core/ai/prompt/agent';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
@@ -24,20 +24,20 @@ import {
|
||||
ChatCompletionTool
|
||||
} from '@fastgpt/global/core/ai/type';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.history]?: ChatItemType[];
|
||||
[ModuleInputKeyEnum.contextExtractInput]: string;
|
||||
[ModuleInputKeyEnum.extractKeys]: ContextExtractAgentItemType[];
|
||||
[ModuleInputKeyEnum.description]: string;
|
||||
[ModuleInputKeyEnum.aiModel]: string;
|
||||
[NodeInputKeyEnum.history]?: ChatItemType[];
|
||||
[NodeInputKeyEnum.contextExtractInput]: string;
|
||||
[NodeInputKeyEnum.extractKeys]: ContextExtractAgentItemType[];
|
||||
[NodeInputKeyEnum.description]: string;
|
||||
[NodeInputKeyEnum.aiModel]: string;
|
||||
}>;
|
||||
type Response = DispatchNodeResultType<{
|
||||
[ModuleOutputKeyEnum.success]?: boolean;
|
||||
[ModuleOutputKeyEnum.failed]?: boolean;
|
||||
[ModuleOutputKeyEnum.contextExtractFields]: string;
|
||||
[NodeOutputKeyEnum.success]?: boolean;
|
||||
[NodeOutputKeyEnum.failed]?: boolean;
|
||||
[NodeOutputKeyEnum.contextExtractFields]: string;
|
||||
}>;
|
||||
|
||||
type ActionProps = Props & { extractModel: LLMModelItemType };
|
||||
@@ -47,7 +47,7 @@ const agentFunName = 'request_function';
|
||||
export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
const {
|
||||
user,
|
||||
module: { name },
|
||||
node: { name },
|
||||
histories,
|
||||
params: { content, history = 6, model, description, extractKeys }
|
||||
} = props;
|
||||
@@ -119,9 +119,10 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
});
|
||||
|
||||
return {
|
||||
[ModuleOutputKeyEnum.success]: success ? true : undefined,
|
||||
[ModuleOutputKeyEnum.failed]: success ? undefined : true,
|
||||
[ModuleOutputKeyEnum.contextExtractFields]: JSON.stringify(arg),
|
||||
// [DispatchNodeResponseKeyEnum.skipHandleId]: success
|
||||
// ? [getHandleId(nodeId, 'source', NodeOutputKeyEnum.failed)]
|
||||
// : [getHandleId(nodeId, 'source', NodeOutputKeyEnum.success)],
|
||||
[NodeOutputKeyEnum.contextExtractFields]: JSON.stringify(arg),
|
||||
...arg,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
|
||||
@@ -143,7 +144,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
};
|
||||
}
|
||||
|
||||
const getFunctionCallSchema = ({
|
||||
const getFunctionCallSchema = async ({
|
||||
extractModel,
|
||||
histories,
|
||||
params: { content, extractKeys, description }
|
||||
@@ -171,7 +172,7 @@ ${description ? `- ${description}` : ''}
|
||||
}
|
||||
];
|
||||
const adaptMessages = chats2GPTMessages({ messages, reserveId: false });
|
||||
const filterMessages = filterGPTMessageByMaxTokens({
|
||||
const filterMessages = await filterGPTMessageByMaxTokens({
|
||||
messages: adaptMessages,
|
||||
maxTokens: extractModel.maxContext
|
||||
});
|
||||
@@ -196,7 +197,8 @@ ${description ? `- ${description}` : ''}
|
||||
description: '需要执行的函数',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties
|
||||
properties,
|
||||
required: []
|
||||
}
|
||||
};
|
||||
|
||||
@@ -209,7 +211,7 @@ ${description ? `- ${description}` : ''}
|
||||
const toolChoice = async (props: ActionProps) => {
|
||||
const { user, extractModel } = props;
|
||||
|
||||
const { filterMessages, agentFunction } = getFunctionCallSchema(props);
|
||||
const { filterMessages, agentFunction } = await getFunctionCallSchema(props);
|
||||
|
||||
const tools: ChatCompletionTool[] = [
|
||||
{
|
||||
@@ -252,7 +254,7 @@ const toolChoice = async (props: ActionProps) => {
|
||||
}
|
||||
];
|
||||
return {
|
||||
tokens: countGptMessagesTokens(completeMessages, tools),
|
||||
tokens: await countGptMessagesTokens(completeMessages, tools),
|
||||
arg
|
||||
};
|
||||
};
|
||||
@@ -260,7 +262,7 @@ const toolChoice = async (props: ActionProps) => {
|
||||
const functionCall = async (props: ActionProps) => {
|
||||
const { user, extractModel } = props;
|
||||
|
||||
const { agentFunction, filterMessages } = getFunctionCallSchema(props);
|
||||
const { agentFunction, filterMessages } = await getFunctionCallSchema(props);
|
||||
const functions: ChatCompletionCreateParams.Function[] = [agentFunction];
|
||||
|
||||
const ai = getAIApi({
|
||||
@@ -290,7 +292,7 @@ const functionCall = async (props: ActionProps) => {
|
||||
|
||||
return {
|
||||
arg,
|
||||
tokens: countGptMessagesTokens(completeMessages, undefined, functions)
|
||||
tokens: await countGptMessagesTokens(completeMessages, undefined, functions)
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(response.choices?.[0]?.message);
|
||||
@@ -355,7 +357,7 @@ Human: ${content}`
|
||||
if (start === -1 || end === -1) {
|
||||
return {
|
||||
rawResponse: answer,
|
||||
tokens: countMessagesTokens(messages),
|
||||
tokens: await countMessagesTokens(messages),
|
||||
arg: {}
|
||||
};
|
||||
}
|
||||
@@ -368,14 +370,14 @@ Human: ${content}`
|
||||
try {
|
||||
return {
|
||||
rawResponse: answer,
|
||||
tokens: countMessagesTokens(messages),
|
||||
tokens: await countMessagesTokens(messages),
|
||||
arg: json5.parse(jsonStr) as Record<string, any>
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return {
|
||||
rawResponse: answer,
|
||||
tokens: countMessagesTokens(messages),
|
||||
tokens: await countMessagesTokens(messages),
|
||||
arg: {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,44 +16,36 @@ import {
|
||||
responseWriteController,
|
||||
responseWriteNodeStatus
|
||||
} from '../../../../../common/response';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/module/runtime/utils';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||
import { dispatchWorkFlow } from '../../index';
|
||||
import { DispatchToolModuleProps, RunToolResponse, ToolModuleItemType } from './type.d';
|
||||
import { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type.d';
|
||||
import json5 from 'json5';
|
||||
import { DispatchFlowResponse } from '../../type';
|
||||
import { countGptMessagesTokens } from '@fastgpt/global/common/string/tiktoken';
|
||||
import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { AIChatItemType, AIChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { AIChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { updateToolInputValue } from './utils';
|
||||
|
||||
type FunctionRunResponseType = {
|
||||
moduleRunResponse: DispatchFlowResponse;
|
||||
toolRunResponse: DispatchFlowResponse;
|
||||
functionCallMsg: ChatCompletionFunctionMessageParam;
|
||||
}[];
|
||||
|
||||
export const runToolWithFunctionCall = async (
|
||||
props: DispatchToolModuleProps & {
|
||||
messages: ChatCompletionMessageParam[];
|
||||
toolModules: ToolModuleItemType[];
|
||||
toolNodes: ToolNodeItemType[];
|
||||
toolModel: LLMModelItemType;
|
||||
},
|
||||
response?: RunToolResponse
|
||||
): Promise<RunToolResponse> => {
|
||||
const {
|
||||
toolModel,
|
||||
toolModules,
|
||||
messages,
|
||||
res,
|
||||
runtimeModules,
|
||||
detail = false,
|
||||
module,
|
||||
stream
|
||||
} = props;
|
||||
const { toolModel, toolNodes, messages, res, runtimeNodes, detail = false, node, stream } = props;
|
||||
const assistantResponses = response?.assistantResponses || [];
|
||||
|
||||
const functions: ChatCompletionCreateParams.Function[] = toolModules.map((module) => {
|
||||
const functions: ChatCompletionCreateParams.Function[] = toolNodes.map((item) => {
|
||||
const properties: Record<
|
||||
string,
|
||||
{
|
||||
@@ -62,7 +54,7 @@ export const runToolWithFunctionCall = async (
|
||||
required?: boolean;
|
||||
}
|
||||
> = {};
|
||||
module.toolParams.forEach((item) => {
|
||||
item.toolParams.forEach((item) => {
|
||||
properties[item.key] = {
|
||||
type: 'string',
|
||||
description: item.toolDescription || ''
|
||||
@@ -70,17 +62,17 @@ export const runToolWithFunctionCall = async (
|
||||
});
|
||||
|
||||
return {
|
||||
name: module.moduleId,
|
||||
description: module.intro,
|
||||
name: item.nodeId,
|
||||
description: item.intro,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties,
|
||||
required: module.toolParams.filter((item) => item.required).map((item) => item.key)
|
||||
required: item.toolParams.filter((item) => item.required).map((item) => item.key)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const filterMessages = filterGPTMessageByMaxTokens({
|
||||
const filterMessages = await filterGPTMessageByMaxTokens({
|
||||
messages,
|
||||
maxTokens: toolModel.maxContext - 500 // filter token. not response maxToken
|
||||
});
|
||||
@@ -107,25 +99,25 @@ export const runToolWithFunctionCall = async (
|
||||
);
|
||||
|
||||
const { answer, functionCalls } = await (async () => {
|
||||
if (stream) {
|
||||
if (res && stream) {
|
||||
return streamResponse({
|
||||
res,
|
||||
detail,
|
||||
toolModules,
|
||||
toolNodes,
|
||||
stream: aiResponse
|
||||
});
|
||||
} else {
|
||||
const result = aiResponse as ChatCompletion;
|
||||
const function_call = result.choices?.[0]?.message?.function_call;
|
||||
const toolModule = toolModules.find((module) => module.moduleId === function_call?.name);
|
||||
const toolNode = toolNodes.find((node) => node.nodeId === function_call?.name);
|
||||
|
||||
const toolCalls = function_call
|
||||
? [
|
||||
{
|
||||
...function_call,
|
||||
id: getNanoid(),
|
||||
toolName: toolModule?.name,
|
||||
toolAvatar: toolModule?.avatar
|
||||
toolName: toolNode?.name,
|
||||
toolAvatar: toolNode?.avatar
|
||||
}
|
||||
]
|
||||
: [];
|
||||
@@ -143,9 +135,9 @@ export const runToolWithFunctionCall = async (
|
||||
functionCalls.map(async (tool) => {
|
||||
if (!tool) return;
|
||||
|
||||
const toolModule = toolModules.find((module) => module.moduleId === tool.name);
|
||||
const toolNode = toolNodes.find((node) => node.nodeId === tool.name);
|
||||
|
||||
if (!toolModule) return;
|
||||
if (!toolNode) return;
|
||||
|
||||
const startParams = (() => {
|
||||
try {
|
||||
@@ -155,21 +147,25 @@ export const runToolWithFunctionCall = async (
|
||||
}
|
||||
})();
|
||||
|
||||
const moduleRunResponse = await dispatchWorkFlow({
|
||||
const toolRunResponse = await dispatchWorkFlow({
|
||||
...props,
|
||||
runtimeModules: runtimeModules.map((module) => ({
|
||||
...module,
|
||||
isEntry: module.moduleId === toolModule.moduleId
|
||||
})),
|
||||
startParams
|
||||
runtimeNodes: runtimeNodes.map((item) =>
|
||||
item.nodeId === toolNode.nodeId
|
||||
? {
|
||||
...item,
|
||||
isEntry: true,
|
||||
inputs: updateToolInputValue({ params: startParams, inputs: item.inputs })
|
||||
}
|
||||
: item
|
||||
)
|
||||
});
|
||||
|
||||
const stringToolResponse = (() => {
|
||||
if (typeof moduleRunResponse.toolResponses === 'object') {
|
||||
return JSON.stringify(moduleRunResponse.toolResponses, null, 2);
|
||||
if (typeof toolRunResponse.toolResponses === 'object') {
|
||||
return JSON.stringify(toolRunResponse.toolResponses, null, 2);
|
||||
}
|
||||
|
||||
return moduleRunResponse.toolResponses ? String(moduleRunResponse.toolResponses) : 'none';
|
||||
return toolRunResponse.toolResponses ? String(toolRunResponse.toolResponses) : 'none';
|
||||
})();
|
||||
|
||||
const functionCallMsg: ChatCompletionFunctionMessageParam = {
|
||||
@@ -195,17 +191,17 @@ export const runToolWithFunctionCall = async (
|
||||
}
|
||||
|
||||
return {
|
||||
moduleRunResponse,
|
||||
toolRunResponse,
|
||||
functionCallMsg
|
||||
};
|
||||
})
|
||||
)
|
||||
).filter(Boolean) as FunctionRunResponseType;
|
||||
|
||||
const flatToolsResponseData = toolsRunResponse.map((item) => item.moduleRunResponse).flat();
|
||||
const flatToolsResponseData = toolsRunResponse.map((item) => item.toolRunResponse).flat();
|
||||
|
||||
const functionCall = functionCalls[0];
|
||||
if (functionCall && !res.closed) {
|
||||
if (functionCall && !res?.closed) {
|
||||
// Run the tool, combine its results, and perform another round of AI calls
|
||||
const assistantToolMsgParams: ChatCompletionAssistantMessageParam = {
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
@@ -215,7 +211,7 @@ export const runToolWithFunctionCall = async (
|
||||
...filterMessages,
|
||||
assistantToolMsgParams
|
||||
] as ChatCompletionMessageParam[];
|
||||
const tokens = countGptMessagesTokens(concatToolMessages, undefined, functions);
|
||||
const tokens = await countGptMessagesTokens(concatToolMessages, undefined, functions);
|
||||
const completeMessages = [
|
||||
...concatToolMessages,
|
||||
...toolsRunResponse.map((item) => item?.functionCallMsg)
|
||||
@@ -225,14 +221,14 @@ export const runToolWithFunctionCall = async (
|
||||
if (stream && detail) {
|
||||
responseWriteNodeStatus({
|
||||
res,
|
||||
name: module.name
|
||||
name: node.name
|
||||
});
|
||||
}
|
||||
|
||||
// tool assistant
|
||||
const toolAssistants = toolsRunResponse
|
||||
.map((item) => {
|
||||
const assistantResponses = item.moduleRunResponse.assistantResponses || [];
|
||||
const assistantResponses = item.toolRunResponse.assistantResponses || [];
|
||||
return assistantResponses;
|
||||
})
|
||||
.flat();
|
||||
@@ -282,7 +278,7 @@ export const runToolWithFunctionCall = async (
|
||||
content: answer
|
||||
};
|
||||
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
||||
const tokens = countGptMessagesTokens(completeMessages, undefined, functions);
|
||||
const tokens = await countGptMessagesTokens(completeMessages, undefined, functions);
|
||||
// console.log(tokens, 'response token');
|
||||
|
||||
// concat tool assistant
|
||||
@@ -300,12 +296,12 @@ export const runToolWithFunctionCall = async (
|
||||
async function streamResponse({
|
||||
res,
|
||||
detail,
|
||||
toolModules,
|
||||
toolNodes,
|
||||
stream
|
||||
}: {
|
||||
res: NextApiResponse;
|
||||
detail: boolean;
|
||||
toolModules: ToolModuleItemType[];
|
||||
toolNodes: ToolNodeItemType[];
|
||||
stream: StreamChatType;
|
||||
}) {
|
||||
const write = responseWriteController({
|
||||
@@ -324,6 +320,7 @@ async function streamResponse({
|
||||
}
|
||||
|
||||
const responseChoice = part.choices?.[0]?.delta;
|
||||
|
||||
if (responseChoice.content) {
|
||||
const content = responseChoice?.content || '';
|
||||
textAnswer += content;
|
||||
@@ -344,9 +341,9 @@ async function streamResponse({
|
||||
// 流响应中,每次只会返回一个函数,如果带了name,说明触发某个函数
|
||||
if (functionCall?.name) {
|
||||
functionId = getNanoid();
|
||||
const toolModule = toolModules.find((module) => module.moduleId === functionCall?.name);
|
||||
const toolNode = toolNodes.find((item) => item.nodeId === functionCall?.name);
|
||||
|
||||
if (toolModule) {
|
||||
if (toolNode) {
|
||||
if (functionCall?.arguments === undefined) {
|
||||
functionCall.arguments = '';
|
||||
}
|
||||
@@ -354,8 +351,8 @@ async function streamResponse({
|
||||
...functionCall,
|
||||
id: functionId,
|
||||
name: functionCall.name,
|
||||
toolName: toolModule.name,
|
||||
toolAvatar: toolModule.avatar
|
||||
toolName: toolNode.name,
|
||||
toolAvatar: toolNode.avatar
|
||||
});
|
||||
|
||||
if (detail) {
|
||||
@@ -365,8 +362,8 @@ async function streamResponse({
|
||||
data: JSON.stringify({
|
||||
tool: {
|
||||
id: functionId,
|
||||
toolName: toolModule.name,
|
||||
toolAvatar: toolModule.avatar,
|
||||
toolName: toolNode.name,
|
||||
toolAvatar: toolNode.avatar,
|
||||
functionName: functionCall.name,
|
||||
params: functionCall.arguments,
|
||||
response: ''
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import type {
|
||||
DispatchNodeResultType,
|
||||
RunningModuleItemType
|
||||
} from '@fastgpt/global/core/module/runtime/type';
|
||||
RuntimeNodeItemType
|
||||
} from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { ModelTypeEnum, getLLMModel } from '../../../../ai/model';
|
||||
import { getHistories } from '../../utils';
|
||||
import { filterToolNodeIdByEdges, getHistories } from '../../utils';
|
||||
import { runToolWithToolChoice } from './toolChoice';
|
||||
import { DispatchToolModuleProps, ToolModuleItemType } from './type.d';
|
||||
import { DispatchToolModuleProps, ToolNodeItemType } from './type.d';
|
||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import {
|
||||
@@ -27,8 +27,9 @@ type Response = DispatchNodeResultType<{}>;
|
||||
|
||||
export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<Response> => {
|
||||
const {
|
||||
module: { name, outputs },
|
||||
runtimeModules,
|
||||
node: { nodeId, name, outputs },
|
||||
runtimeNodes,
|
||||
runtimeEdges,
|
||||
histories,
|
||||
params: { model, systemPrompt, userChatInput, history = 6 }
|
||||
} = props;
|
||||
@@ -38,26 +39,19 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
|
||||
/* get tool params */
|
||||
|
||||
// get tool output targets
|
||||
const toolOutput = outputs.find((output) => output.key === ModuleOutputKeyEnum.selectedTools);
|
||||
|
||||
if (!toolOutput) {
|
||||
return Promise.reject('No tool output found');
|
||||
}
|
||||
|
||||
const targets = toolOutput.targets;
|
||||
const toolNodeIds = filterToolNodeIdByEdges({ nodeId, edges: runtimeEdges });
|
||||
|
||||
// Gets the module to which the tool is connected
|
||||
const toolModules = targets
|
||||
.map((item) => {
|
||||
const tool = runtimeModules.find((module) => module.moduleId === item.moduleId);
|
||||
const toolNodes = toolNodeIds
|
||||
.map((nodeId) => {
|
||||
const tool = runtimeNodes.find((item) => item.nodeId === nodeId);
|
||||
return tool;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.map<ToolModuleItemType>((tool) => {
|
||||
.map<ToolNodeItemType>((tool) => {
|
||||
const toolParams = tool?.inputs.filter((input) => !!input.toolDescription) || [];
|
||||
return {
|
||||
...(tool as RunningModuleItemType),
|
||||
...(tool as RuntimeNodeItemType),
|
||||
toolParams
|
||||
};
|
||||
});
|
||||
@@ -85,7 +79,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
if (toolModel.toolChoice) {
|
||||
return runToolWithToolChoice({
|
||||
...props,
|
||||
toolModules,
|
||||
toolNodes,
|
||||
toolModel,
|
||||
messages: adaptMessages
|
||||
});
|
||||
@@ -93,7 +87,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
if (toolModel.functionCall) {
|
||||
return runToolWithFunctionCall({
|
||||
...props,
|
||||
toolModules,
|
||||
toolNodes,
|
||||
toolModel,
|
||||
messages: adaptMessages
|
||||
});
|
||||
@@ -110,7 +104,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
|
||||
return runToolWithPromptCall({
|
||||
...props,
|
||||
toolModules,
|
||||
toolNodes,
|
||||
toolModel,
|
||||
messages: adaptMessages
|
||||
});
|
||||
|
||||
@@ -13,16 +13,17 @@ import {
|
||||
responseWriteController,
|
||||
responseWriteNodeStatus
|
||||
} from '../../../../../common/response';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/module/runtime/utils';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||
import { dispatchWorkFlow } from '../../index';
|
||||
import { DispatchToolModuleProps, RunToolResponse, ToolModuleItemType } from './type.d';
|
||||
import { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type.d';
|
||||
import json5 from 'json5';
|
||||
import { countGptMessagesTokens } from '@fastgpt/global/common/string/tiktoken';
|
||||
import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index';
|
||||
import { getNanoid, replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { AIChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { updateToolInputValue } from './utils';
|
||||
|
||||
type FunctionCallCompletion = {
|
||||
id: string;
|
||||
@@ -35,25 +36,16 @@ type FunctionCallCompletion = {
|
||||
export const runToolWithPromptCall = async (
|
||||
props: DispatchToolModuleProps & {
|
||||
messages: ChatCompletionMessageParam[];
|
||||
toolModules: ToolModuleItemType[];
|
||||
toolNodes: ToolNodeItemType[];
|
||||
toolModel: LLMModelItemType;
|
||||
},
|
||||
response?: RunToolResponse
|
||||
): Promise<RunToolResponse> => {
|
||||
const {
|
||||
toolModel,
|
||||
toolModules,
|
||||
messages,
|
||||
res,
|
||||
runtimeModules,
|
||||
detail = false,
|
||||
module,
|
||||
stream
|
||||
} = props;
|
||||
const { toolModel, toolNodes, messages, res, runtimeNodes, detail = false, node, stream } = props;
|
||||
const assistantResponses = response?.assistantResponses || [];
|
||||
|
||||
const toolsPrompt = JSON.stringify(
|
||||
toolModules.map((module) => {
|
||||
toolNodes.map((item) => {
|
||||
const properties: Record<
|
||||
string,
|
||||
{
|
||||
@@ -62,7 +54,7 @@ export const runToolWithPromptCall = async (
|
||||
required?: boolean;
|
||||
}
|
||||
> = {};
|
||||
module.toolParams.forEach((item) => {
|
||||
item.toolParams.forEach((item) => {
|
||||
properties[item.key] = {
|
||||
type: 'string',
|
||||
description: item.toolDescription || ''
|
||||
@@ -70,12 +62,12 @@ export const runToolWithPromptCall = async (
|
||||
});
|
||||
|
||||
return {
|
||||
toolId: module.moduleId,
|
||||
description: module.intro,
|
||||
toolId: item.nodeId,
|
||||
description: item.intro,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties,
|
||||
required: module.toolParams.filter((item) => item.required).map((item) => item.key)
|
||||
required: item.toolParams.filter((item) => item.required).map((item) => item.key)
|
||||
}
|
||||
};
|
||||
})
|
||||
@@ -89,7 +81,7 @@ export const runToolWithPromptCall = async (
|
||||
toolsPrompt
|
||||
});
|
||||
|
||||
const filterMessages = filterGPTMessageByMaxTokens({
|
||||
const filterMessages = await filterGPTMessageByMaxTokens({
|
||||
messages,
|
||||
maxTokens: toolModel.maxContext - 500 // filter token. not response maxToken
|
||||
});
|
||||
@@ -114,11 +106,11 @@ export const runToolWithPromptCall = async (
|
||||
);
|
||||
|
||||
const answer = await (async () => {
|
||||
if (stream) {
|
||||
if (res && stream) {
|
||||
const { answer } = await streamResponse({
|
||||
res,
|
||||
detail,
|
||||
toolModules,
|
||||
toolNodes,
|
||||
stream: aiResponse
|
||||
});
|
||||
|
||||
@@ -140,7 +132,7 @@ export const runToolWithPromptCall = async (
|
||||
content: parseAnswerResult
|
||||
};
|
||||
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
||||
const tokens = countGptMessagesTokens(completeMessages, undefined);
|
||||
const tokens = await countGptMessagesTokens(completeMessages, undefined);
|
||||
// console.log(tokens, 'response token');
|
||||
|
||||
// concat tool assistant
|
||||
@@ -158,11 +150,11 @@ export const runToolWithPromptCall = async (
|
||||
const toolsRunResponse = await (async () => {
|
||||
if (!parseAnswerResult) return Promise.reject('tool run error');
|
||||
|
||||
const toolModule = toolModules.find((module) => module.moduleId === parseAnswerResult.name);
|
||||
if (!toolModule) return Promise.reject('tool not found');
|
||||
const toolNode = toolNodes.find((item) => item.nodeId === parseAnswerResult.name);
|
||||
if (!toolNode) return Promise.reject('tool not found');
|
||||
|
||||
parseAnswerResult.toolName = toolModule.name;
|
||||
parseAnswerResult.toolAvatar = toolModule.avatar;
|
||||
parseAnswerResult.toolName = toolNode.name;
|
||||
parseAnswerResult.toolAvatar = toolNode.avatar;
|
||||
|
||||
// run tool flow
|
||||
const startParams = (() => {
|
||||
@@ -181,8 +173,8 @@ export const runToolWithPromptCall = async (
|
||||
data: JSON.stringify({
|
||||
tool: {
|
||||
id: parseAnswerResult.id,
|
||||
toolName: toolModule.name,
|
||||
toolAvatar: toolModule.avatar,
|
||||
toolName: toolNode.name,
|
||||
toolAvatar: toolNode.avatar,
|
||||
functionName: parseAnswerResult.name,
|
||||
params: parseAnswerResult.arguments,
|
||||
response: ''
|
||||
@@ -193,11 +185,15 @@ export const runToolWithPromptCall = async (
|
||||
|
||||
const moduleRunResponse = await dispatchWorkFlow({
|
||||
...props,
|
||||
runtimeModules: runtimeModules.map((module) => ({
|
||||
...module,
|
||||
isEntry: module.moduleId === toolModule.moduleId
|
||||
})),
|
||||
startParams
|
||||
runtimeNodes: runtimeNodes.map((item) =>
|
||||
item.nodeId === toolNode.nodeId
|
||||
? {
|
||||
...item,
|
||||
isEntry: true,
|
||||
inputs: updateToolInputValue({ params: startParams, inputs: item.inputs })
|
||||
}
|
||||
: item
|
||||
)
|
||||
});
|
||||
|
||||
const stringToolResponse = (() => {
|
||||
@@ -233,7 +229,7 @@ export const runToolWithPromptCall = async (
|
||||
if (stream && detail) {
|
||||
responseWriteNodeStatus({
|
||||
res,
|
||||
name: module.name
|
||||
name: node.name
|
||||
});
|
||||
}
|
||||
|
||||
@@ -246,7 +242,7 @@ export const runToolWithPromptCall = async (
|
||||
...filterMessages,
|
||||
assistantToolMsgParams
|
||||
] as ChatCompletionMessageParam[];
|
||||
const tokens = countGptMessagesTokens(concatToolMessages, undefined);
|
||||
const tokens = await countGptMessagesTokens(concatToolMessages, undefined);
|
||||
const completeMessages: ChatCompletionMessageParam[] = [
|
||||
...concatToolMessages,
|
||||
{
|
||||
@@ -308,7 +304,7 @@ async function streamResponse({
|
||||
}: {
|
||||
res: NextApiResponse;
|
||||
detail: boolean;
|
||||
toolModules: ToolModuleItemType[];
|
||||
toolNodes: ToolNodeItemType[];
|
||||
stream: StreamChatType;
|
||||
}) {
|
||||
const write = responseWriteController({
|
||||
@@ -326,6 +322,8 @@ async function streamResponse({
|
||||
}
|
||||
|
||||
const responseChoice = part.choices?.[0]?.delta;
|
||||
// console.log(responseChoice, '---===');
|
||||
|
||||
if (responseChoice.content) {
|
||||
const content = responseChoice?.content || '';
|
||||
textAnswer += content;
|
||||
@@ -360,7 +358,6 @@ async function streamResponse({
|
||||
if (!textAnswer) {
|
||||
return Promise.reject('LLM api response empty');
|
||||
}
|
||||
// console.log(textAnswer, '---===');
|
||||
return { answer: textAnswer.trim() };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
|
||||
export type AnswerProps = ModuleDispatchProps<{}>;
|
||||
export type AnswerResponse = DispatchNodeResultType<{}>;
|
||||
|
||||
@@ -17,19 +17,20 @@ import {
|
||||
responseWriteController,
|
||||
responseWriteNodeStatus
|
||||
} from '../../../../../common/response';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/module/runtime/utils';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||
import { dispatchWorkFlow } from '../../index';
|
||||
import { DispatchToolModuleProps, RunToolResponse, ToolModuleItemType } from './type.d';
|
||||
import { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type.d';
|
||||
import json5 from 'json5';
|
||||
import { DispatchFlowResponse } from '../../type';
|
||||
import { countGptMessagesTokens } from '@fastgpt/global/common/string/tiktoken';
|
||||
import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index';
|
||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { AIChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { updateToolInputValue } from './utils';
|
||||
|
||||
type ToolRunResponseType = {
|
||||
moduleRunResponse: DispatchFlowResponse;
|
||||
toolRunResponse: DispatchFlowResponse;
|
||||
toolMsgParams: ChatCompletionToolMessageParam;
|
||||
}[];
|
||||
|
||||
@@ -43,24 +44,15 @@ type ToolRunResponseType = {
|
||||
export const runToolWithToolChoice = async (
|
||||
props: DispatchToolModuleProps & {
|
||||
messages: ChatCompletionMessageParam[];
|
||||
toolModules: ToolModuleItemType[];
|
||||
toolNodes: ToolNodeItemType[];
|
||||
toolModel: LLMModelItemType;
|
||||
},
|
||||
response?: RunToolResponse
|
||||
): Promise<RunToolResponse> => {
|
||||
const {
|
||||
toolModel,
|
||||
toolModules,
|
||||
messages,
|
||||
res,
|
||||
runtimeModules,
|
||||
detail = false,
|
||||
module,
|
||||
stream
|
||||
} = props;
|
||||
const { toolModel, toolNodes, messages, res, runtimeNodes, detail = false, node, stream } = props;
|
||||
const assistantResponses = response?.assistantResponses || [];
|
||||
|
||||
const tools: ChatCompletionTool[] = toolModules.map((module) => {
|
||||
const tools: ChatCompletionTool[] = toolNodes.map((item) => {
|
||||
const properties: Record<
|
||||
string,
|
||||
{
|
||||
@@ -69,7 +61,7 @@ export const runToolWithToolChoice = async (
|
||||
required?: boolean;
|
||||
}
|
||||
> = {};
|
||||
module.toolParams.forEach((item) => {
|
||||
item.toolParams.forEach((item) => {
|
||||
properties[item.key] = {
|
||||
type: 'string',
|
||||
description: item.toolDescription || ''
|
||||
@@ -79,18 +71,18 @@ export const runToolWithToolChoice = async (
|
||||
return {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: module.moduleId,
|
||||
description: module.intro,
|
||||
name: item.nodeId,
|
||||
description: item.intro,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties,
|
||||
required: module.toolParams.filter((item) => item.required).map((item) => item.key)
|
||||
required: item.toolParams.filter((item) => item.required).map((item) => item.key)
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const filterMessages = filterGPTMessageByMaxTokens({
|
||||
const filterMessages = await filterGPTMessageByMaxTokens({
|
||||
messages,
|
||||
maxTokens: toolModel.maxContext - 300 // filter token. not response maxToken
|
||||
});
|
||||
@@ -117,11 +109,11 @@ export const runToolWithToolChoice = async (
|
||||
);
|
||||
|
||||
const { answer, toolCalls } = await (async () => {
|
||||
if (stream) {
|
||||
if (res && stream) {
|
||||
return streamResponse({
|
||||
res,
|
||||
detail,
|
||||
toolModules,
|
||||
toolNodes,
|
||||
stream: aiResponse
|
||||
});
|
||||
} else {
|
||||
@@ -130,11 +122,11 @@ export const runToolWithToolChoice = async (
|
||||
|
||||
// 加上name和avatar
|
||||
const toolCalls = calls.map((tool) => {
|
||||
const toolModule = toolModules.find((module) => module.moduleId === tool.function?.name);
|
||||
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
|
||||
return {
|
||||
...tool,
|
||||
toolName: toolModule?.name || '',
|
||||
toolAvatar: toolModule?.avatar || ''
|
||||
toolName: toolNode?.name || '',
|
||||
toolAvatar: toolNode?.avatar || ''
|
||||
};
|
||||
});
|
||||
|
||||
@@ -145,13 +137,13 @@ export const runToolWithToolChoice = async (
|
||||
}
|
||||
})();
|
||||
|
||||
// Run the selected tool.
|
||||
// Run the selected tool by LLM.
|
||||
const toolsRunResponse = (
|
||||
await Promise.all(
|
||||
toolCalls.map(async (tool) => {
|
||||
const toolModule = toolModules.find((module) => module.moduleId === tool.function?.name);
|
||||
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
|
||||
|
||||
if (!toolModule) return;
|
||||
if (!toolNode) return;
|
||||
|
||||
const startParams = (() => {
|
||||
try {
|
||||
@@ -161,21 +153,25 @@ export const runToolWithToolChoice = async (
|
||||
}
|
||||
})();
|
||||
|
||||
const moduleRunResponse = await dispatchWorkFlow({
|
||||
const toolRunResponse = await dispatchWorkFlow({
|
||||
...props,
|
||||
runtimeModules: runtimeModules.map((module) => ({
|
||||
...module,
|
||||
isEntry: module.moduleId === toolModule.moduleId
|
||||
})),
|
||||
startParams
|
||||
runtimeNodes: runtimeNodes.map((item) =>
|
||||
item.nodeId === toolNode.nodeId
|
||||
? {
|
||||
...item,
|
||||
isEntry: true,
|
||||
inputs: updateToolInputValue({ params: startParams, inputs: item.inputs })
|
||||
}
|
||||
: item
|
||||
)
|
||||
});
|
||||
|
||||
const stringToolResponse = (() => {
|
||||
if (typeof moduleRunResponse.toolResponses === 'object') {
|
||||
return JSON.stringify(moduleRunResponse.toolResponses, null, 2);
|
||||
if (typeof toolRunResponse.toolResponses === 'object') {
|
||||
return JSON.stringify(toolRunResponse.toolResponses, null, 2);
|
||||
}
|
||||
|
||||
return moduleRunResponse.toolResponses ? String(moduleRunResponse.toolResponses) : 'none';
|
||||
return toolRunResponse.toolResponses ? String(toolRunResponse.toolResponses) : 'none';
|
||||
})();
|
||||
|
||||
const toolMsgParams: ChatCompletionToolMessageParam = {
|
||||
@@ -202,15 +198,15 @@ export const runToolWithToolChoice = async (
|
||||
}
|
||||
|
||||
return {
|
||||
moduleRunResponse,
|
||||
toolRunResponse,
|
||||
toolMsgParams
|
||||
};
|
||||
})
|
||||
)
|
||||
).filter(Boolean) as ToolRunResponseType;
|
||||
|
||||
const flatToolsResponseData = toolsRunResponse.map((item) => item.moduleRunResponse).flat();
|
||||
if (toolCalls.length > 0 && !res.closed) {
|
||||
const flatToolsResponseData = toolsRunResponse.map((item) => item.toolRunResponse).flat();
|
||||
if (toolCalls.length > 0 && !res?.closed) {
|
||||
// Run the tool, combine its results, and perform another round of AI calls
|
||||
const assistantToolMsgParams: ChatCompletionAssistantToolParam = {
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
@@ -220,7 +216,7 @@ export const runToolWithToolChoice = async (
|
||||
...filterMessages,
|
||||
assistantToolMsgParams
|
||||
] as ChatCompletionMessageParam[];
|
||||
const tokens = countGptMessagesTokens(concatToolMessages, tools);
|
||||
const tokens = await countGptMessagesTokens(concatToolMessages, tools);
|
||||
const completeMessages = [
|
||||
...concatToolMessages,
|
||||
...toolsRunResponse.map((item) => item?.toolMsgParams)
|
||||
@@ -231,14 +227,14 @@ export const runToolWithToolChoice = async (
|
||||
if (stream && detail) {
|
||||
responseWriteNodeStatus({
|
||||
res,
|
||||
name: module.name
|
||||
name: node.name
|
||||
});
|
||||
}
|
||||
|
||||
// tool assistant
|
||||
const toolAssistants = toolsRunResponse
|
||||
.map((item) => {
|
||||
const assistantResponses = item.moduleRunResponse.assistantResponses || [];
|
||||
const assistantResponses = item.toolRunResponse.assistantResponses || [];
|
||||
return assistantResponses;
|
||||
})
|
||||
.flat();
|
||||
@@ -289,7 +285,7 @@ export const runToolWithToolChoice = async (
|
||||
content: answer
|
||||
};
|
||||
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
||||
const tokens = countGptMessagesTokens(completeMessages, tools);
|
||||
const tokens = await countGptMessagesTokens(completeMessages, tools);
|
||||
// console.log(tokens, 'response token');
|
||||
|
||||
// concat tool assistant
|
||||
@@ -307,12 +303,12 @@ export const runToolWithToolChoice = async (
|
||||
async function streamResponse({
|
||||
res,
|
||||
detail,
|
||||
toolModules,
|
||||
toolNodes,
|
||||
stream
|
||||
}: {
|
||||
res: NextApiResponse;
|
||||
detail: boolean;
|
||||
toolModules: ToolModuleItemType[];
|
||||
toolNodes: ToolNodeItemType[];
|
||||
stream: StreamChatType;
|
||||
}) {
|
||||
const write = responseWriteController({
|
||||
@@ -347,18 +343,16 @@ async function streamResponse({
|
||||
|
||||
// 流响应中,每次只会返回一个工具. 如果带了 id,说明是执行一个工具
|
||||
if (toolCall.id) {
|
||||
const toolModule = toolModules.find(
|
||||
(module) => module.moduleId === toolCall.function?.name
|
||||
);
|
||||
const toolNode = toolNodes.find((item) => item.nodeId === toolCall.function?.name);
|
||||
|
||||
if (toolModule) {
|
||||
if (toolNode) {
|
||||
if (toolCall.function?.arguments === undefined) {
|
||||
toolCall.function.arguments = '';
|
||||
}
|
||||
toolCalls.push({
|
||||
...toolCall,
|
||||
toolName: toolModule.name,
|
||||
toolAvatar: toolModule.avatar
|
||||
toolName: toolNode.name,
|
||||
toolAvatar: toolNode.avatar
|
||||
});
|
||||
|
||||
if (detail) {
|
||||
@@ -368,8 +362,8 @@ async function streamResponse({
|
||||
data: JSON.stringify({
|
||||
tool: {
|
||||
id: toolCall.id,
|
||||
toolName: toolModule.name,
|
||||
toolAvatar: toolModule.avatar,
|
||||
toolName: toolNode.name,
|
||||
toolAvatar: toolNode.avatar,
|
||||
functionName: toolCall.function.name,
|
||||
params: toolCall.function.arguments,
|
||||
response: ''
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import type {
|
||||
ModuleDispatchProps,
|
||||
DispatchNodeResponseType
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import type { RunningModuleItemType } from '@fastgpt/global/core/module/runtime/type';
|
||||
} from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
import type { DispatchFlowResponse } from '../../type.d';
|
||||
import { AIChatItemValueItemType, ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
|
||||
export type DispatchToolModuleProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.history]?: ChatItemType[];
|
||||
[ModuleInputKeyEnum.aiModel]: string;
|
||||
[ModuleInputKeyEnum.aiSystemPrompt]: string;
|
||||
[ModuleInputKeyEnum.userChatInput]: string;
|
||||
[NodeInputKeyEnum.history]?: ChatItemType[];
|
||||
[NodeInputKeyEnum.aiModel]: string;
|
||||
[NodeInputKeyEnum.aiSystemPrompt]: string;
|
||||
[NodeInputKeyEnum.userChatInput]: string;
|
||||
}>;
|
||||
|
||||
export type RunToolResponse = {
|
||||
@@ -23,6 +22,6 @@ export type RunToolResponse = {
|
||||
completeMessages?: ChatCompletionMessageParam[];
|
||||
assistantResponses?: AIChatItemValueItemType[];
|
||||
};
|
||||
export type ToolModuleItemType = RunningModuleItemType & {
|
||||
toolParams: RunningModuleItemType['inputs'];
|
||||
export type ToolNodeItemType = RuntimeNodeItemType & {
|
||||
toolParams: RuntimeNodeItemType['inputs'];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
|
||||
export const updateToolInputValue = ({
|
||||
params,
|
||||
inputs
|
||||
}: {
|
||||
params: Record<string, any>;
|
||||
inputs: FlowNodeInputItemType[];
|
||||
}) => {
|
||||
return inputs.map((input) => ({
|
||||
...input,
|
||||
value: params[input.key] ?? input.value
|
||||
}));
|
||||
};
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
} from '../../../chat/utils';
|
||||
import type { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/module/runtime/utils';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { getAIApi } from '../../../ai/config';
|
||||
import type {
|
||||
ChatCompletion,
|
||||
@@ -18,12 +18,11 @@ import { formatModelChars2Points } from '../../../../support/wallet/usage/utils'
|
||||
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { postTextCensor } from '../../../../common/api/requestPlusApi';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import type { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
|
||||
import type { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import {
|
||||
countGptMessagesTokens,
|
||||
countMessagesTokens
|
||||
} from '@fastgpt/global/common/string/tiktoken';
|
||||
} from '../../../../common/string/tiktoken/index';
|
||||
import {
|
||||
chats2GPTMessages,
|
||||
getSystemPrompt,
|
||||
@@ -34,28 +33,28 @@ import {
|
||||
Prompt_QuotePromptList,
|
||||
Prompt_QuoteTemplateList
|
||||
} from '@fastgpt/global/core/ai/prompt/AIChat';
|
||||
import type { AIChatModuleProps } from '@fastgpt/global/core/module/node/type.d';
|
||||
import type { AIChatNodeProps } from '@fastgpt/global/core/workflow/runtime/type.d';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { responseWrite, responseWriteController } from '../../../../common/response';
|
||||
import { getLLMModel, ModelTypeEnum } from '../../../ai/model';
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { getHistories } from '../utils';
|
||||
import { filterSearchResultsByMaxChars } from '@fastgpt/global/core/dataset/search/utils';
|
||||
import { filterSearchResultsByMaxChars } from '../../utils';
|
||||
import { getHistoryPreview } from '@fastgpt/global/core/chat/utils';
|
||||
|
||||
export type ChatProps = ModuleDispatchProps<
|
||||
AIChatModuleProps & {
|
||||
[ModuleInputKeyEnum.userChatInput]: string;
|
||||
[ModuleInputKeyEnum.history]?: ChatItemType[] | number;
|
||||
[ModuleInputKeyEnum.aiChatDatasetQuote]?: SearchDataResponseItemType[];
|
||||
AIChatNodeProps & {
|
||||
[NodeInputKeyEnum.userChatInput]: string;
|
||||
[NodeInputKeyEnum.history]?: ChatItemType[] | number;
|
||||
[NodeInputKeyEnum.aiChatDatasetQuote]?: SearchDataResponseItemType[];
|
||||
}
|
||||
>;
|
||||
export type ChatResponse = DispatchNodeResultType<{
|
||||
[ModuleOutputKeyEnum.answerText]: string;
|
||||
[ModuleOutputKeyEnum.history]: ChatItemType[];
|
||||
[NodeOutputKeyEnum.answerText]: string;
|
||||
[NodeOutputKeyEnum.history]: ChatItemType[];
|
||||
}>;
|
||||
|
||||
/* request openai chat */
|
||||
@@ -66,7 +65,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
detail = false,
|
||||
user,
|
||||
histories,
|
||||
module: { name, outputs },
|
||||
node: { name },
|
||||
inputFiles = [],
|
||||
params: {
|
||||
model,
|
||||
@@ -95,7 +94,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
return Promise.reject('The chat model is undefined, you need to select a chat model.');
|
||||
}
|
||||
|
||||
const { quoteText } = filterQuote({
|
||||
const { quoteText } = await filterQuote({
|
||||
quoteQA,
|
||||
model: modelConstantsData,
|
||||
quoteTemplate
|
||||
@@ -111,7 +110,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
});
|
||||
}
|
||||
|
||||
const { filterMessages } = getChatMessages({
|
||||
const { filterMessages } = await getChatMessages({
|
||||
model: modelConstantsData,
|
||||
histories: chatHistories,
|
||||
quoteQA,
|
||||
@@ -182,7 +181,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
);
|
||||
|
||||
const { answerText } = await (async () => {
|
||||
if (stream) {
|
||||
if (res && stream) {
|
||||
// sse response
|
||||
const { answer } = await streamResponse({
|
||||
res,
|
||||
@@ -190,8 +189,6 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
stream: response
|
||||
});
|
||||
|
||||
targetResponse({ res, detail, outputs });
|
||||
|
||||
return {
|
||||
answerText: answer
|
||||
};
|
||||
@@ -211,7 +208,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
});
|
||||
const chatCompleteMessages = GPTMessages2Chats(completeMessages);
|
||||
|
||||
const tokens = countMessagesTokens(chatCompleteMessages);
|
||||
const tokens = await countMessagesTokens(chatCompleteMessages);
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
model,
|
||||
tokens,
|
||||
@@ -242,7 +239,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
};
|
||||
};
|
||||
|
||||
function filterQuote({
|
||||
async function filterQuote({
|
||||
quoteQA = [],
|
||||
model,
|
||||
quoteTemplate
|
||||
@@ -262,7 +259,7 @@ function filterQuote({
|
||||
}
|
||||
|
||||
// slice filterSearch
|
||||
const filterQuoteQA = filterSearchResultsByMaxChars(quoteQA, model.quoteMaxToken);
|
||||
const filterQuoteQA = await filterSearchResultsByMaxChars(quoteQA, model.quoteMaxToken);
|
||||
|
||||
const quoteText =
|
||||
filterQuoteQA.length > 0
|
||||
@@ -273,7 +270,7 @@ function filterQuote({
|
||||
quoteText
|
||||
};
|
||||
}
|
||||
function getChatMessages({
|
||||
async function getChatMessages({
|
||||
quotePrompt,
|
||||
quoteText,
|
||||
quoteQA,
|
||||
@@ -313,7 +310,7 @@ function getChatMessages({
|
||||
];
|
||||
const adaptMessages = chats2GPTMessages({ messages, reserveId: false });
|
||||
|
||||
const filterMessages = filterGPTMessageByMaxTokens({
|
||||
const filterMessages = await filterGPTMessageByMaxTokens({
|
||||
messages: adaptMessages,
|
||||
maxTokens: model.maxContext - 300 // filter token. not response maxToken
|
||||
});
|
||||
@@ -322,7 +319,7 @@ function getChatMessages({
|
||||
filterMessages
|
||||
};
|
||||
}
|
||||
function getMaxTokens({
|
||||
async function getMaxTokens({
|
||||
maxToken,
|
||||
model,
|
||||
filterMessages = []
|
||||
@@ -335,7 +332,7 @@ function getMaxTokens({
|
||||
const tokensLimit = model.maxContext;
|
||||
|
||||
/* count response max token */
|
||||
const promptsToken = countGptMessagesTokens(filterMessages);
|
||||
const promptsToken = await countGptMessagesTokens(filterMessages);
|
||||
maxToken = promptsToken + maxToken > tokensLimit ? tokensLimit - promptsToken : maxToken;
|
||||
|
||||
if (maxToken <= 0) {
|
||||
@@ -346,28 +343,6 @@ function getMaxTokens({
|
||||
};
|
||||
}
|
||||
|
||||
function targetResponse({
|
||||
res,
|
||||
outputs,
|
||||
detail
|
||||
}: {
|
||||
res: NextApiResponse;
|
||||
outputs: ModuleItemType['outputs'];
|
||||
detail: boolean;
|
||||
}) {
|
||||
const targets =
|
||||
outputs.find((output) => output.key === ModuleOutputKeyEnum.answerText)?.targets || [];
|
||||
|
||||
if (targets.length === 0) return;
|
||||
responseWrite({
|
||||
res,
|
||||
event: detail ? SseResponseEventEnum.answer : undefined,
|
||||
data: textAdaptGptResponse({
|
||||
text: '\n'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async function streamResponse({
|
||||
res,
|
||||
detail,
|
||||
|
||||
38
packages/service/core/workflow/dispatch/code/isolatedvm.ts
Normal file
38
packages/service/core/workflow/dispatch/code/isolatedvm.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { addLog } from '../../../../common/system/log';
|
||||
const ivm = require('isolated-vm');
|
||||
|
||||
export const runJsCode = ({
|
||||
code,
|
||||
variables
|
||||
}: {
|
||||
code: string;
|
||||
variables: Record<string, any>;
|
||||
}) => {
|
||||
const isolate = new ivm.Isolate({ memoryLimit: 16 });
|
||||
const context = isolate.createContextSync();
|
||||
const jail = context.global;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// custom log function
|
||||
jail.setSync('responseData', function (args: any): any {
|
||||
if (typeof args === 'object') {
|
||||
resolve(args);
|
||||
} else {
|
||||
reject('Not an invalid response');
|
||||
}
|
||||
});
|
||||
|
||||
// Add global variables
|
||||
jail.setSync('variables', new ivm.ExternalCopy(variables).copyInto());
|
||||
|
||||
try {
|
||||
const scriptCode = `
|
||||
${code}
|
||||
responseData(main(variables))`;
|
||||
context.evalSync(scriptCode, { timeout: 2000 });
|
||||
} catch (err) {
|
||||
addLog.error('Error during script execution:', err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,16 +1,16 @@
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { datasetSearchResultConcat } from '@fastgpt/global/core/dataset/search/utils';
|
||||
import { filterSearchResultsByMaxChars } from '@fastgpt/global/core/dataset/search/utils';
|
||||
import { filterSearchResultsByMaxChars } from '../../utils';
|
||||
|
||||
type DatasetConcatProps = ModuleDispatchProps<
|
||||
{
|
||||
[ModuleInputKeyEnum.datasetMaxTokens]: number;
|
||||
[NodeInputKeyEnum.datasetMaxTokens]: number;
|
||||
} & { [key: string]: SearchDataResponseItemType[] }
|
||||
>;
|
||||
type DatasetConcatResponse = {
|
||||
[ModuleOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[];
|
||||
[NodeOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[];
|
||||
};
|
||||
|
||||
export async function dispatchDatasetConcat(
|
||||
@@ -30,6 +30,6 @@ export async function dispatchDatasetConcat(
|
||||
);
|
||||
|
||||
return {
|
||||
[ModuleOutputKeyEnum.datasetQuoteQA]: filterSearchResultsByMaxChars(rrfConcatResults, limit)
|
||||
[NodeOutputKeyEnum.datasetQuoteQA]: await filterSearchResultsByMaxChars(rrfConcatResults, limit)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import {
|
||||
DispatchNodeResponseType,
|
||||
DispatchNodeResultType
|
||||
} from '@fastgpt/global/core/module/runtime/type.d';
|
||||
} from '@fastgpt/global/core/workflow/runtime/type.d';
|
||||
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
|
||||
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
|
||||
import type { SelectedDatasetType } from '@fastgpt/global/core/workflow/api.d';
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { ModelTypeEnum, getLLMModel, getVectorModel } from '../../../ai/model';
|
||||
import { searchDatasetData } from '../../../dataset/search/controller';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { getHistories } from '../utils';
|
||||
import { datasetSearchQueryExtension } from '../../../dataset/search/utils';
|
||||
@@ -17,20 +17,18 @@ import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
import { checkTeamReRankPermission } from '../../../../support/permission/teamLimit';
|
||||
|
||||
type DatasetSearchProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.datasetSelectList]: SelectedDatasetType;
|
||||
[ModuleInputKeyEnum.datasetSimilarity]: number;
|
||||
[ModuleInputKeyEnum.datasetMaxTokens]: number;
|
||||
[ModuleInputKeyEnum.datasetSearchMode]: `${DatasetSearchModeEnum}`;
|
||||
[ModuleInputKeyEnum.userChatInput]: string;
|
||||
[ModuleInputKeyEnum.datasetSearchUsingReRank]: boolean;
|
||||
[ModuleInputKeyEnum.datasetSearchUsingExtensionQuery]: boolean;
|
||||
[ModuleInputKeyEnum.datasetSearchExtensionModel]: string;
|
||||
[ModuleInputKeyEnum.datasetSearchExtensionBg]: string;
|
||||
[NodeInputKeyEnum.datasetSelectList]: SelectedDatasetType;
|
||||
[NodeInputKeyEnum.datasetSimilarity]: number;
|
||||
[NodeInputKeyEnum.datasetMaxTokens]: number;
|
||||
[NodeInputKeyEnum.datasetSearchMode]: `${DatasetSearchModeEnum}`;
|
||||
[NodeInputKeyEnum.userChatInput]: string;
|
||||
[NodeInputKeyEnum.datasetSearchUsingReRank]: boolean;
|
||||
[NodeInputKeyEnum.datasetSearchUsingExtensionQuery]: boolean;
|
||||
[NodeInputKeyEnum.datasetSearchExtensionModel]: string;
|
||||
[NodeInputKeyEnum.datasetSearchExtensionBg]: string;
|
||||
}>;
|
||||
export type DatasetSearchResponse = DispatchNodeResultType<{
|
||||
[ModuleOutputKeyEnum.datasetIsEmpty]?: boolean;
|
||||
[ModuleOutputKeyEnum.datasetUnEmpty]?: boolean;
|
||||
[ModuleOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[];
|
||||
[NodeOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[];
|
||||
}>;
|
||||
|
||||
export async function dispatchDatasetSearch(
|
||||
@@ -39,7 +37,7 @@ export async function dispatchDatasetSearch(
|
||||
const {
|
||||
teamId,
|
||||
histories,
|
||||
module,
|
||||
node,
|
||||
params: {
|
||||
datasets = [],
|
||||
similarity,
|
||||
@@ -67,10 +65,10 @@ export async function dispatchDatasetSearch(
|
||||
}
|
||||
|
||||
// query extension
|
||||
const extensionModel =
|
||||
datasetSearchUsingExtensionQuery && datasetSearchExtensionModel
|
||||
? getLLMModel(datasetSearchExtensionModel)
|
||||
: undefined;
|
||||
const extensionModel = datasetSearchUsingExtensionQuery
|
||||
? getLLMModel(datasetSearchExtensionModel)
|
||||
: undefined;
|
||||
|
||||
const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({
|
||||
query: userChatInput,
|
||||
extensionModel,
|
||||
@@ -122,7 +120,7 @@ export async function dispatchDatasetSearch(
|
||||
const nodeDispatchUsages: ChatNodeUsageType[] = [
|
||||
{
|
||||
totalPoints,
|
||||
moduleName: module.name,
|
||||
moduleName: node.name,
|
||||
model: modelName,
|
||||
tokens
|
||||
}
|
||||
@@ -151,8 +149,6 @@ export async function dispatchDatasetSearch(
|
||||
}
|
||||
|
||||
return {
|
||||
isEmpty: searchRes.length === 0 ? true : undefined,
|
||||
unEmpty: searchRes.length > 0 ? true : undefined,
|
||||
quoteQA: searchRes,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: responseData,
|
||||
nodeDispatchUsages,
|
||||
|
||||
@@ -1,54 +1,57 @@
|
||||
import { NextApiResponse } from 'next';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { ChatDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import type { RunningModuleItemType } from '@fastgpt/global/core/module/runtime/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import type { ChatDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import type {
|
||||
AIChatItemValueItemType,
|
||||
ChatHistoryItemResType,
|
||||
ToolRunResponseItemType
|
||||
} from '@fastgpt/global/core/chat/type.d';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { responseWriteNodeStatus } from '../../../common/response';
|
||||
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
|
||||
|
||||
import { dispatchHistory } from './init/history';
|
||||
import { dispatchChatInput } from './init/userChatInput';
|
||||
import { dispatchWorkflowStart } from './init/workflowStart';
|
||||
import { dispatchChatCompletion } from './chat/oneapi';
|
||||
import { dispatchDatasetSearch } from './dataset/search';
|
||||
import { dispatchDatasetConcat } from './dataset/concat';
|
||||
import { dispatchAnswer } from './tools/answer';
|
||||
import { dispatchClassifyQuestion } from './agent/classifyQuestion';
|
||||
import { dispatchContentExtract } from './agent/extract';
|
||||
import { dispatchHttpRequest } from './tools/http';
|
||||
import { dispatchHttp468Request } from './tools/http468';
|
||||
import { dispatchAppRequest } from './tools/runApp';
|
||||
import { dispatchQueryExtension } from './tools/queryExternsion';
|
||||
import { dispatchRunPlugin } from './plugin/run';
|
||||
import { dispatchPluginInput } from './plugin/runInput';
|
||||
import { dispatchPluginOutput } from './plugin/runOutput';
|
||||
import { checkTheModuleConnectedByTool, valueTypeFormat } from './utils';
|
||||
import { valueTypeFormat } from './utils';
|
||||
import {
|
||||
filterWorkflowEdges,
|
||||
checkNodeRunStatus
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
import { dispatchRunTools } from './agent/runTool/index';
|
||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { DispatchFlowResponse } from './type';
|
||||
import { dispatchStopToolCall } from './agent/runTool/stopTool';
|
||||
import { dispatchLafRequest } from './tools/runLaf';
|
||||
import { dispatchIfElse } from './tools/runIfElse';
|
||||
import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { getReferenceVariableValue } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { dispatchSystemConfig } from './init/systemConfiig';
|
||||
|
||||
const callbackMap: Record<`${FlowNodeTypeEnum}`, Function> = {
|
||||
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
|
||||
[FlowNodeTypeEnum.questionInput]: dispatchChatInput,
|
||||
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
|
||||
[FlowNodeTypeEnum.answerNode]: dispatchAnswer,
|
||||
[FlowNodeTypeEnum.chatNode]: dispatchChatCompletion,
|
||||
[FlowNodeTypeEnum.datasetSearchNode]: dispatchDatasetSearch,
|
||||
[FlowNodeTypeEnum.datasetConcatNode]: dispatchDatasetConcat,
|
||||
[FlowNodeTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
|
||||
[FlowNodeTypeEnum.contentExtract]: dispatchContentExtract,
|
||||
[FlowNodeTypeEnum.httpRequest]: dispatchHttpRequest,
|
||||
[FlowNodeTypeEnum.httpRequest468]: dispatchHttp468Request,
|
||||
[FlowNodeTypeEnum.runApp]: dispatchAppRequest,
|
||||
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
|
||||
@@ -58,17 +61,19 @@ const callbackMap: Record<`${FlowNodeTypeEnum}`, Function> = {
|
||||
[FlowNodeTypeEnum.tools]: dispatchRunTools,
|
||||
[FlowNodeTypeEnum.stopTool]: dispatchStopToolCall,
|
||||
[FlowNodeTypeEnum.lafModule]: dispatchLafRequest,
|
||||
[FlowNodeTypeEnum.ifElseNode]: dispatchIfElse,
|
||||
|
||||
// none
|
||||
[FlowNodeTypeEnum.userGuide]: () => Promise.resolve()
|
||||
[FlowNodeTypeEnum.systemConfig]: dispatchSystemConfig,
|
||||
[FlowNodeTypeEnum.emptyNode]: () => Promise.resolve(),
|
||||
[FlowNodeTypeEnum.globalVariable]: () => Promise.resolve()
|
||||
};
|
||||
|
||||
/* running */
|
||||
export async function dispatchWorkFlow({
|
||||
res,
|
||||
modules = [],
|
||||
runtimeModules,
|
||||
startParams = {},
|
||||
runtimeNodes = [],
|
||||
runtimeEdges = [],
|
||||
histories = [],
|
||||
variables = {},
|
||||
user,
|
||||
@@ -76,12 +81,11 @@ export async function dispatchWorkFlow({
|
||||
detail = false,
|
||||
...props
|
||||
}: ChatDispatchProps & {
|
||||
modules?: ModuleItemType[]; // app modules
|
||||
runtimeModules?: RunningModuleItemType[];
|
||||
startParams?: Record<string, any>; // entry module params
|
||||
runtimeNodes: RuntimeNodeItemType[];
|
||||
runtimeEdges: RuntimeEdgeItemType[];
|
||||
}): Promise<DispatchFlowResponse> {
|
||||
// set sse response headers
|
||||
if (stream) {
|
||||
if (stream && res) {
|
||||
res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('X-Accel-Buffering', 'no');
|
||||
@@ -92,17 +96,17 @@ export async function dispatchWorkFlow({
|
||||
...getSystemVariable({ timezone: user.timezone }),
|
||||
...variables
|
||||
};
|
||||
const runningModules = runtimeModules ? runtimeModules : loadModules(modules, variables);
|
||||
|
||||
let chatResponses: ChatHistoryItemResType[] = []; // response request and save to database
|
||||
let chatAssistantResponse: AIChatItemValueItemType[] = []; // The value will be returned to the user
|
||||
let chatNodeUsages: ChatNodeUsageType[] = [];
|
||||
let toolRunResponse: ToolRunResponseItemType;
|
||||
let runningTime = Date.now();
|
||||
let debugNextStepRunNodes: RuntimeNodeItemType[] = [];
|
||||
|
||||
/* Store special response field */
|
||||
function pushStore(
|
||||
{ inputs = [] }: RunningModuleItemType,
|
||||
{ inputs = [] }: RuntimeNodeItemType,
|
||||
{
|
||||
answerText = '',
|
||||
responseData,
|
||||
@@ -110,7 +114,7 @@ export async function dispatchWorkFlow({
|
||||
toolResponses,
|
||||
assistantResponses
|
||||
}: {
|
||||
[ModuleOutputKeyEnum.answerText]?: string;
|
||||
[NodeOutputKeyEnum.answerText]?: string;
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]?: ChatHistoryItemResType;
|
||||
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]?: ChatNodeUsageType[];
|
||||
[DispatchNodeResponseKeyEnum.toolResponses]?: ToolRunResponseItemType;
|
||||
@@ -143,7 +147,7 @@ export async function dispatchWorkFlow({
|
||||
// save assistant text response
|
||||
if (answerText) {
|
||||
const isResponseAnswerText =
|
||||
inputs.find((item) => item.key === ModuleInputKeyEnum.aiChatIsResponseText)?.value ?? true;
|
||||
inputs.find((item) => item.key === NodeInputKeyEnum.aiChatIsResponseText)?.value ?? true;
|
||||
if (isResponseAnswerText) {
|
||||
chatAssistantResponse.push({
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
@@ -156,85 +160,112 @@ export async function dispatchWorkFlow({
|
||||
|
||||
runningTime = time;
|
||||
}
|
||||
/* Inject data into module input */
|
||||
function moduleInput(module: RunningModuleItemType, data: Record<string, any> = {}) {
|
||||
const updateInputValue = (key: string, value: any) => {
|
||||
const index = module.inputs.findIndex((item: any) => item.key === key);
|
||||
if (index === -1) return;
|
||||
module.inputs[index].value = value;
|
||||
};
|
||||
Object.entries(data).map(([key, val]: any) => {
|
||||
updateInputValue(key, val);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
/* Pass the output of the module to the next stage */
|
||||
function moduleOutput(
|
||||
module: RunningModuleItemType,
|
||||
function nodeOutput(
|
||||
node: RuntimeNodeItemType,
|
||||
result: Record<string, any> = {}
|
||||
): Promise<any> {
|
||||
pushStore(module, result);
|
||||
): RuntimeNodeItemType[] {
|
||||
pushStore(node, result);
|
||||
|
||||
const nextRunModules: RunningModuleItemType[] = [];
|
||||
|
||||
// Assign the output value to the next module
|
||||
module.outputs.map((outputItem) => {
|
||||
// Assign the output value to the next node
|
||||
node.outputs.forEach((outputItem) => {
|
||||
if (result[outputItem.key] === undefined) return;
|
||||
/* update output value */
|
||||
outputItem.value = result[outputItem.key];
|
||||
|
||||
/* update target */
|
||||
outputItem.targets.map((target: any) => {
|
||||
// find module
|
||||
const targetModule = runningModules.find((item) => item.moduleId === target.moduleId);
|
||||
if (!targetModule) return;
|
||||
|
||||
// push to running queue
|
||||
nextRunModules.push(targetModule);
|
||||
|
||||
// update input
|
||||
moduleInput(targetModule, { [target.key]: outputItem.value });
|
||||
});
|
||||
});
|
||||
|
||||
// Ensure the uniqueness of running modules
|
||||
const set = new Set<string>();
|
||||
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(
|
||||
modules.map((module) => {
|
||||
if (!module.inputs.find((item: any) => item.value === undefined)) {
|
||||
// remove switch
|
||||
moduleInput(module, { [ModuleInputKeyEnum.switch]: undefined });
|
||||
return moduleRun(module);
|
||||
}
|
||||
})
|
||||
// Get next source edges and update status
|
||||
const skipHandleId = (result[DispatchNodeResponseKeyEnum.skipHandleId] || []) as string[];
|
||||
const targetEdges = filterWorkflowEdges(runtimeEdges).filter(
|
||||
(item) => item.source === node.nodeId
|
||||
);
|
||||
}
|
||||
async function moduleRun(module: RunningModuleItemType): Promise<any> {
|
||||
if (res.closed || props.maxRunTimes <= 0) return Promise.resolve();
|
||||
|
||||
if (stream && detail && module.showStatus) {
|
||||
// update edge status
|
||||
targetEdges.forEach((edge) => {
|
||||
if (skipHandleId.includes(edge.sourceHandle)) {
|
||||
edge.status = 'skipped';
|
||||
} else {
|
||||
edge.status = 'active';
|
||||
}
|
||||
});
|
||||
|
||||
const nextStepNodes = runtimeNodes.filter((node) => {
|
||||
return targetEdges.some((item) => item.target === node.nodeId);
|
||||
});
|
||||
|
||||
if (props.mode === 'debug') {
|
||||
debugNextStepRunNodes = debugNextStepRunNodes.concat(nextStepNodes);
|
||||
return [];
|
||||
}
|
||||
|
||||
return nextStepNodes;
|
||||
}
|
||||
function checkNodeCanRun(nodes: RuntimeNodeItemType[] = []): Promise<any> {
|
||||
return Promise.all(
|
||||
nodes.map((node) => {
|
||||
const status = checkNodeRunStatus({
|
||||
node,
|
||||
runtimeEdges
|
||||
});
|
||||
|
||||
if (status === 'run') {
|
||||
return nodeRunWithActive(node);
|
||||
}
|
||||
if (status === 'skip') {
|
||||
return nodeRunWithSkip(node);
|
||||
}
|
||||
|
||||
return [];
|
||||
})
|
||||
).then((result) => {
|
||||
const flat = result.flat();
|
||||
if (flat.length === 0) return;
|
||||
// update output
|
||||
const nextNodes = flat.map((item) => nodeOutput(item.node, item.result)).flat();
|
||||
return checkNodeCanRun(nextNodes);
|
||||
});
|
||||
}
|
||||
// 运行完一轮后,清除连线的状态,避免污染进程
|
||||
function nodeRunFinish(node: RuntimeNodeItemType) {
|
||||
const edges = runtimeEdges.filter((item) => item.target === node.nodeId);
|
||||
edges.forEach((item) => {
|
||||
item.status = 'waiting';
|
||||
});
|
||||
}
|
||||
/* Inject data into module input */
|
||||
function getNodeRunParams(node: RuntimeNodeItemType) {
|
||||
const params: Record<string, any> = {};
|
||||
node.inputs.forEach((input) => {
|
||||
// replace {{}} variables
|
||||
let value = replaceVariable(input.value, variables);
|
||||
|
||||
// replace reference variables
|
||||
value = getReferenceVariableValue({
|
||||
value,
|
||||
nodes: runtimeNodes,
|
||||
variables
|
||||
});
|
||||
// console.log(JSON.stringify(input, null, 2), '=====================');
|
||||
|
||||
// format valueType
|
||||
params[input.key] = valueTypeFormat(value, input.valueType);
|
||||
});
|
||||
|
||||
return params;
|
||||
}
|
||||
async function nodeRunWithActive(node: RuntimeNodeItemType) {
|
||||
if (res?.closed || props.maxRunTimes <= 0) return [];
|
||||
// push run status messages
|
||||
if (res && stream && detail && node.showStatus) {
|
||||
responseStatus({
|
||||
res,
|
||||
name: module.name,
|
||||
name: node.name,
|
||||
status: 'running'
|
||||
});
|
||||
}
|
||||
|
||||
// get module running params
|
||||
const params: Record<string, any> = {};
|
||||
module.inputs.forEach((item) => {
|
||||
params[item.key] = valueTypeFormat(item.value, item.valueType);
|
||||
});
|
||||
// get node running params
|
||||
const params = getNodeRunParams(node);
|
||||
|
||||
const dispatchData: ModuleDispatchProps<Record<string, any>> = {
|
||||
...props,
|
||||
@@ -244,15 +275,16 @@ export async function dispatchWorkFlow({
|
||||
user,
|
||||
stream,
|
||||
detail,
|
||||
module,
|
||||
runtimeModules: runningModules,
|
||||
node,
|
||||
runtimeNodes,
|
||||
runtimeEdges,
|
||||
params
|
||||
};
|
||||
|
||||
// run module
|
||||
const dispatchRes: Record<string, any> = await (async () => {
|
||||
if (callbackMap[module.flowType]) {
|
||||
return callbackMap[module.flowType](dispatchData);
|
||||
if (callbackMap[node.flowNodeType]) {
|
||||
return callbackMap[node.flowNodeType](dispatchData);
|
||||
}
|
||||
return {};
|
||||
})();
|
||||
@@ -261,139 +293,74 @@ export async function dispatchWorkFlow({
|
||||
const formatResponseData: ChatHistoryItemResType = (() => {
|
||||
if (!dispatchRes[DispatchNodeResponseKeyEnum.nodeResponse]) return undefined;
|
||||
return {
|
||||
moduleName: module.name,
|
||||
moduleType: module.flowType,
|
||||
nodeId: node.nodeId,
|
||||
moduleName: node.name,
|
||||
moduleType: node.flowNodeType,
|
||||
...dispatchRes[DispatchNodeResponseKeyEnum.nodeResponse]
|
||||
};
|
||||
})();
|
||||
|
||||
// Add output default value
|
||||
module.outputs.forEach((item) => {
|
||||
node.outputs.forEach((item) => {
|
||||
if (!item.required) return;
|
||||
if (dispatchRes[item.key] !== undefined) return;
|
||||
dispatchRes[item.key] = valueTypeFormat(item.defaultValue, item.valueType);
|
||||
});
|
||||
|
||||
// Pass userChatInput
|
||||
const hasUserChatInputTarget = !!module.outputs.find(
|
||||
(item) => item.key === ModuleOutputKeyEnum.userChatInput
|
||||
)?.targets?.length;
|
||||
nodeRunFinish(node);
|
||||
|
||||
return moduleOutput(module, {
|
||||
[ModuleOutputKeyEnum.finish]: true,
|
||||
[ModuleOutputKeyEnum.userChatInput]: hasUserChatInputTarget
|
||||
? params[ModuleOutputKeyEnum.userChatInput]
|
||||
: undefined,
|
||||
...dispatchRes,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: formatResponseData,
|
||||
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]:
|
||||
dispatchRes[DispatchNodeResponseKeyEnum.nodeDispatchUsages]
|
||||
});
|
||||
return {
|
||||
node,
|
||||
result: {
|
||||
...dispatchRes,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: formatResponseData
|
||||
}
|
||||
};
|
||||
}
|
||||
async function nodeRunWithSkip(node: RuntimeNodeItemType) {
|
||||
// 其后所有target的节点,都设置为skip
|
||||
const targetEdges = runtimeEdges.filter((item) => item.source === node.nodeId);
|
||||
nodeRunFinish(node);
|
||||
|
||||
return {
|
||||
node,
|
||||
result: {
|
||||
[DispatchNodeResponseKeyEnum.skipHandleId]: targetEdges.map((item) => item.sourceHandle)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// start process width initInput
|
||||
const initModules = runningModules.filter((item) => item.isEntry);
|
||||
const entryNodes = runtimeNodes.filter((item) => item.isEntry);
|
||||
|
||||
// reset entry
|
||||
modules.forEach((item) => {
|
||||
runtimeNodes.forEach((item) => {
|
||||
item.isEntry = false;
|
||||
});
|
||||
|
||||
initModules.map((module) =>
|
||||
moduleInput(module, {
|
||||
...startParams,
|
||||
history: [] // abandon history field. History module will get histories from other fields.
|
||||
})
|
||||
);
|
||||
await checkModulesCanRun(initModules);
|
||||
await checkNodeCanRun(entryNodes);
|
||||
|
||||
// focus try to run pluginOutput
|
||||
const pluginOutputModule = runningModules.find(
|
||||
(item) => item.flowType === FlowNodeTypeEnum.pluginOutput
|
||||
const pluginOutputModule = runtimeNodes.find(
|
||||
(item) => item.flowNodeType === FlowNodeTypeEnum.pluginOutput
|
||||
);
|
||||
if (pluginOutputModule) {
|
||||
await moduleRun(pluginOutputModule);
|
||||
if (pluginOutputModule && props.mode !== 'debug') {
|
||||
await nodeRunWithActive(pluginOutputModule);
|
||||
}
|
||||
|
||||
return {
|
||||
flowResponses: chatResponses,
|
||||
flowUsages: chatNodeUsages,
|
||||
debugResponse: {
|
||||
finishedNodes: runtimeNodes,
|
||||
finishedEdges: runtimeEdges,
|
||||
nextStepRunNodes: debugNextStepRunNodes
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.assistantResponses]:
|
||||
concatAssistantResponseAnswerText(chatAssistantResponse),
|
||||
mergeAssistantResponseAnswerText(chatAssistantResponse),
|
||||
[DispatchNodeResponseKeyEnum.toolResponses]: toolRunResponse
|
||||
};
|
||||
}
|
||||
|
||||
/* init store modules to running modules */
|
||||
function loadModules(
|
||||
modules: ModuleItemType[],
|
||||
variables: Record<string, any>
|
||||
): RunningModuleItemType[] {
|
||||
return modules
|
||||
.filter((item) => {
|
||||
return ![FlowNodeTypeEnum.userGuide].includes(item.moduleId as any);
|
||||
})
|
||||
.map<RunningModuleItemType>((module) => {
|
||||
return {
|
||||
moduleId: module.moduleId,
|
||||
name: module.name,
|
||||
avatar: module.avatar,
|
||||
intro: module.intro,
|
||||
flowType: module.flowType,
|
||||
showStatus: module.showStatus,
|
||||
isEntry: module.isEntry,
|
||||
inputs: module.inputs
|
||||
.filter(
|
||||
/*
|
||||
1. system input must be save
|
||||
2. connected by source handle
|
||||
3. manual input value or have default value
|
||||
4. For the module connected by the tool, leave the toolDescription input
|
||||
*/
|
||||
(item) => {
|
||||
const isTool = checkTheModuleConnectedByTool(modules, module);
|
||||
|
||||
if (isTool && item.toolDescription) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
item.type === FlowNodeInputTypeEnum.systemInput ||
|
||||
item.connected ||
|
||||
item.value !== undefined
|
||||
);
|
||||
}
|
||||
) // filter unconnected target input
|
||||
.map((item) => {
|
||||
const replace = ['string'].includes(typeof item.value);
|
||||
|
||||
return {
|
||||
key: item.key,
|
||||
// variables replace
|
||||
value: replace ? replaceVariable(item.value, variables) : item.value,
|
||||
valueType: item.valueType,
|
||||
required: item.required,
|
||||
toolDescription: item.toolDescription
|
||||
};
|
||||
}),
|
||||
outputs: module.outputs
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
required: item.required,
|
||||
defaultValue: item.defaultValue,
|
||||
answer: item.key === ModuleOutputKeyEnum.answerText,
|
||||
value: undefined,
|
||||
valueType: item.valueType,
|
||||
targets: item.targets
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
// finish output always at last
|
||||
if (a.key === ModuleOutputKeyEnum.finish) return 1;
|
||||
if (b.key === ModuleOutputKeyEnum.finish) return -1;
|
||||
return 0;
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/* sse response modules staus */
|
||||
export function responseStatus({
|
||||
res,
|
||||
@@ -418,7 +385,8 @@ export function getSystemVariable({ timezone }: { timezone: string }) {
|
||||
};
|
||||
}
|
||||
|
||||
export const concatAssistantResponseAnswerText = (response: AIChatItemValueItemType[]) => {
|
||||
/* Merge consecutive text messages into one */
|
||||
export const mergeAssistantResponseAnswerText = (response: AIChatItemValueItemType[]) => {
|
||||
const result: AIChatItemValueItemType[] = [];
|
||||
// 合并连续的text
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { getHistories } from '../utils';
|
||||
export type HistoryProps = ModuleDispatchProps<{
|
||||
maxContext?: number;
|
||||
[ModuleInputKeyEnum.history]: ChatItemType[];
|
||||
}>;
|
||||
|
||||
export const dispatchHistory = (props: Record<string, any>) => {
|
||||
const {
|
||||
histories,
|
||||
params: { maxContext }
|
||||
} = props as HistoryProps;
|
||||
|
||||
return {
|
||||
history: getHistories(maxContext, histories)
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
export type UserChatInputProps = ModuleDispatchProps<{
|
||||
[NodeInputKeyEnum.userChatInput]: string;
|
||||
}>;
|
||||
|
||||
export const dispatchSystemConfig = (props: Record<string, any>) => {
|
||||
const { variables } = props as UserChatInputProps;
|
||||
return variables;
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
export type UserChatInputProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.userChatInput]: string;
|
||||
}>;
|
||||
|
||||
export const dispatchChatInput = (props: Record<string, any>) => {
|
||||
const {
|
||||
params: { userChatInput }
|
||||
} = props as UserChatInputProps;
|
||||
return {
|
||||
userChatInput
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
export type UserChatInputProps = ModuleDispatchProps<{
|
||||
[NodeInputKeyEnum.userChatInput]: string;
|
||||
}>;
|
||||
|
||||
export const dispatchWorkflowStart = (props: Record<string, any>) => {
|
||||
const {
|
||||
variables: { userChatInput },
|
||||
params: { userChatInput: query }
|
||||
} = props as UserChatInputProps;
|
||||
return {
|
||||
userChatInput: query || userChatInput
|
||||
};
|
||||
};
|
||||
@@ -1,25 +1,31 @@
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { dispatchWorkFlow } from '../index';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { DYNAMIC_INPUT_KEY, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { getPluginRuntimeById } from '../../../plugin/controller';
|
||||
import { authPluginCanUse } from '../../../../support/permission/auth/plugin';
|
||||
import { setEntryEntries } from '../utils';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
|
||||
import {
|
||||
getDefaultEntryNodeIds,
|
||||
initWorkflowEdgeStatus,
|
||||
storeNodes2RuntimeNodes
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { updateToolInputValue } from '../agent/runTool/utils';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
type RunPluginProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.pluginId]: string;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
type RunPluginResponse = DispatchNodeResultType<{}>;
|
||||
|
||||
export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPluginResponse> => {
|
||||
const {
|
||||
node: { pluginId },
|
||||
mode,
|
||||
teamId,
|
||||
tmbId,
|
||||
params: { pluginId, ...data }
|
||||
params: data
|
||||
} = props;
|
||||
|
||||
if (!pluginId) {
|
||||
@@ -30,37 +36,67 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
|
||||
const plugin = await getPluginRuntimeById(pluginId);
|
||||
|
||||
// concat dynamic inputs
|
||||
const inputModule = plugin.modules.find((item) => item.flowType === FlowNodeTypeEnum.pluginInput);
|
||||
const inputModule = plugin.nodes.find(
|
||||
(item) => item.flowNodeType === 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 hasDynamicInput = inputModule.inputs.find(
|
||||
(input) => input.key === NodeInputKeyEnum.addInputParam
|
||||
);
|
||||
|
||||
const startParams: Record<string, any> = (() => {
|
||||
if (!hasDynamicInput) return data;
|
||||
|
||||
const params: Record<string, any> = {
|
||||
[DYNAMIC_INPUT_KEY]: {}
|
||||
[NodeInputKeyEnum.addInputParam]: {}
|
||||
};
|
||||
|
||||
for (const key in data) {
|
||||
if (key === NodeInputKeyEnum.addInputParam) continue;
|
||||
|
||||
const input = inputModule.inputs.find((input) => input.key === key);
|
||||
if (input) {
|
||||
params[key] = data[key];
|
||||
} else {
|
||||
params[DYNAMIC_INPUT_KEY][key] = data[key];
|
||||
params[NodeInputKeyEnum.addInputParam][key] = data[key];
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
})();
|
||||
|
||||
// replace input by dynamic variables
|
||||
if (hasDynamicInput) {
|
||||
for (const key in startParams) {
|
||||
if (key === NodeInputKeyEnum.addInputParam) continue;
|
||||
startParams[key] = replaceVariable(
|
||||
startParams[key],
|
||||
startParams[NodeInputKeyEnum.addInputParam]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { flowResponses, flowUsages, assistantResponses } = await dispatchWorkFlow({
|
||||
...props,
|
||||
modules: setEntryEntries(plugin.modules).map((module) => ({
|
||||
...module,
|
||||
showStatus: false
|
||||
})),
|
||||
runtimeModules: undefined, // must reset
|
||||
startParams
|
||||
runtimeNodes: storeNodes2RuntimeNodes(plugin.nodes, getDefaultEntryNodeIds(plugin.nodes)).map(
|
||||
(node) => {
|
||||
if (node.flowNodeType === FlowNodeTypeEnum.pluginInput) {
|
||||
return {
|
||||
...node,
|
||||
showStatus: false,
|
||||
inputs: updateToolInputValue({
|
||||
inputs: node.inputs,
|
||||
params: startParams
|
||||
})
|
||||
};
|
||||
}
|
||||
return {
|
||||
...node,
|
||||
showStatus: false
|
||||
};
|
||||
}
|
||||
),
|
||||
runtimeEdges: initWorkflowEdgeStatus(plugin.edges)
|
||||
});
|
||||
|
||||
const output = flowResponses.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
|
||||
export type PluginInputProps = ModuleDispatchProps<{
|
||||
[key: string]: any;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type.d';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type.d';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
|
||||
export type PluginOutputProps = ModuleDispatchProps<{
|
||||
[key: string]: any;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import {
|
||||
DispatchNodeResponseKeyEnum,
|
||||
SseResponseEventEnum
|
||||
} from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { responseWrite } from '../../../../common/response';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/module/runtime/utils';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
export type AnswerProps = ModuleDispatchProps<{
|
||||
text: string;
|
||||
}>;
|
||||
export type AnswerResponse = DispatchNodeResultType<{
|
||||
[ModuleOutputKeyEnum.answerText]: string;
|
||||
[NodeOutputKeyEnum.answerText]: string;
|
||||
}>;
|
||||
|
||||
export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
|
||||
@@ -16,12 +19,13 @@ export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
|
||||
res,
|
||||
detail,
|
||||
stream,
|
||||
node: { name },
|
||||
params: { text = '' }
|
||||
} = props as AnswerProps;
|
||||
|
||||
const formatText = typeof text === 'string' ? text : JSON.stringify(text, null, 2);
|
||||
|
||||
if (stream) {
|
||||
if (res && stream) {
|
||||
responseWrite({
|
||||
res,
|
||||
event: detail ? SseResponseEventEnum.fastAnswer : undefined,
|
||||
@@ -32,6 +36,9 @@ export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
|
||||
}
|
||||
|
||||
return {
|
||||
[ModuleOutputKeyEnum.answerText]: formatText
|
||||
[NodeOutputKeyEnum.answerText]: formatText,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
textOutput: formatText
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import {
|
||||
DYNAMIC_INPUT_KEY,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum
|
||||
} from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import axios from 'axios';
|
||||
import { valueTypeFormat } from '../utils';
|
||||
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
|
||||
|
||||
type HttpRequestProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.abandon_httpUrl]: string;
|
||||
[ModuleInputKeyEnum.httpMethod]: string;
|
||||
[ModuleInputKeyEnum.httpReqUrl]: string;
|
||||
[ModuleInputKeyEnum.httpHeaders]: string;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
type HttpResponse = DispatchNodeResultType<{
|
||||
[ModuleOutputKeyEnum.failed]?: boolean;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
|
||||
const flatDynamicParams = (params: Record<string, any>) => {
|
||||
const dynamicParams = params[DYNAMIC_INPUT_KEY];
|
||||
if (!dynamicParams) return params;
|
||||
return {
|
||||
...params,
|
||||
...dynamicParams,
|
||||
[DYNAMIC_INPUT_KEY]: undefined
|
||||
};
|
||||
};
|
||||
|
||||
export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<HttpResponse> => {
|
||||
let {
|
||||
appId,
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
variables,
|
||||
module: { outputs },
|
||||
params: {
|
||||
system_httpMethod: httpMethod = 'POST',
|
||||
system_httpReqUrl: httpReqUrl,
|
||||
system_httpHeader: httpHeader,
|
||||
...body
|
||||
}
|
||||
} = props;
|
||||
|
||||
if (!httpReqUrl) {
|
||||
return Promise.reject('Http url is empty');
|
||||
}
|
||||
|
||||
body = flatDynamicParams(body);
|
||||
|
||||
const requestBody = {
|
||||
appId,
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
variables,
|
||||
data: body
|
||||
};
|
||||
const requestQuery = {
|
||||
appId,
|
||||
chatId,
|
||||
...variables,
|
||||
...body
|
||||
};
|
||||
|
||||
const formatBody = transformFlatJson({ ...requestBody });
|
||||
|
||||
// parse header
|
||||
const headers = await (() => {
|
||||
try {
|
||||
if (!httpHeader) return {};
|
||||
return JSON.parse(httpHeader);
|
||||
} catch (error) {
|
||||
return Promise.reject('Header 为非法 JSON 格式');
|
||||
}
|
||||
})();
|
||||
|
||||
try {
|
||||
const response = await fetchData({
|
||||
method: httpMethod,
|
||||
url: httpReqUrl,
|
||||
headers,
|
||||
body: formatBody,
|
||||
query: requestQuery
|
||||
});
|
||||
|
||||
// format output value type
|
||||
const results: Record<string, any> = {};
|
||||
for (const key in response) {
|
||||
const output = outputs.find((item) => item.key === key);
|
||||
if (!output) continue;
|
||||
results[key] = valueTypeFormat(response[key], output.valueType);
|
||||
}
|
||||
|
||||
return {
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
totalPoints: 0,
|
||||
body: formatBody,
|
||||
httpResult: response
|
||||
},
|
||||
...results
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
return {
|
||||
[ModuleOutputKeyEnum.failed]: true,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
totalPoints: 0,
|
||||
body: formatBody,
|
||||
httpResult: { error }
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
async function fetchData({
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
body,
|
||||
query
|
||||
}: {
|
||||
method: string;
|
||||
url: string;
|
||||
headers: Record<string, any>;
|
||||
body: Record<string, any>;
|
||||
query: Record<string, any>;
|
||||
}): Promise<Record<string, any>> {
|
||||
const { data: response } = await axios<Record<string, any>>({
|
||||
method,
|
||||
baseURL: `http://${SERVICE_LOCAL_HOST}`,
|
||||
url,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers
|
||||
},
|
||||
timeout: 360000,
|
||||
params: method === 'GET' ? query : {},
|
||||
data: method === 'POST' ? body : {}
|
||||
});
|
||||
|
||||
/*
|
||||
parse the json:
|
||||
{
|
||||
user: {
|
||||
name: 'xxx',
|
||||
age: 12
|
||||
},
|
||||
list: [
|
||||
{
|
||||
name: 'xxx',
|
||||
age: 50
|
||||
},
|
||||
[{ test: 22 }]
|
||||
],
|
||||
psw: 'xxx'
|
||||
}
|
||||
|
||||
result: {
|
||||
'user': { name: 'xxx', age: 12 },
|
||||
'user.name': 'xxx',
|
||||
'user.age': 12,
|
||||
'list': [ { name: 'xxx', age: 50 }, [ [Object] ] ],
|
||||
'list[0]': { name: 'xxx', age: 50 },
|
||||
'list[0].name': 'xxx',
|
||||
'list[0].age': 50,
|
||||
'list[1]': [ { test: 22 } ],
|
||||
'list[1][0]': { test: 22 },
|
||||
'list[1][0].test': 22,
|
||||
'psw': 'xxx'
|
||||
}
|
||||
*/
|
||||
const parseJson = (obj: Record<string, any>, prefix = '') => {
|
||||
let result: Record<string, any> = {};
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
result[`${prefix}[${i}]`] = obj[i];
|
||||
|
||||
if (Array.isArray(obj[i])) {
|
||||
result = {
|
||||
...result,
|
||||
...parseJson(obj[i], `${prefix}[${i}]`)
|
||||
};
|
||||
} else if (typeof obj[i] === 'object') {
|
||||
result = {
|
||||
...result,
|
||||
...parseJson(obj[i], `${prefix}[${i}].`)
|
||||
};
|
||||
}
|
||||
}
|
||||
} else if (typeof obj == 'object') {
|
||||
for (const key in obj) {
|
||||
result[`${prefix}${key}`] = obj[key];
|
||||
|
||||
if (Array.isArray(obj[key])) {
|
||||
result = {
|
||||
...result,
|
||||
...parseJson(obj[key], `${prefix}${key}`)
|
||||
};
|
||||
} else if (typeof obj[key] === 'object') {
|
||||
result = {
|
||||
...result,
|
||||
...parseJson(obj[key], `${prefix}${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;
|
||||
}
|
||||
@@ -1,16 +1,21 @@
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import {
|
||||
DYNAMIC_INPUT_KEY,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum
|
||||
} from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
NodeInputKeyEnum,
|
||||
NodeOutputKeyEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
DispatchNodeResponseKeyEnum,
|
||||
SseResponseEventEnum
|
||||
} from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import axios from 'axios';
|
||||
import { valueTypeFormat } from '../utils';
|
||||
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
|
||||
import { addLog } from '../../../../common/system/log';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { responseWrite } from '../../../../common/response';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
|
||||
type PropsArrType = {
|
||||
key: string;
|
||||
@@ -18,17 +23,17 @@ type PropsArrType = {
|
||||
value: string;
|
||||
};
|
||||
type HttpRequestProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.abandon_httpUrl]: string;
|
||||
[ModuleInputKeyEnum.httpMethod]: string;
|
||||
[ModuleInputKeyEnum.httpReqUrl]: string;
|
||||
[ModuleInputKeyEnum.httpHeaders]: PropsArrType[];
|
||||
[ModuleInputKeyEnum.httpParams]: PropsArrType[];
|
||||
[ModuleInputKeyEnum.httpJsonBody]: string;
|
||||
[DYNAMIC_INPUT_KEY]: Record<string, any>;
|
||||
[NodeInputKeyEnum.abandon_httpUrl]: string;
|
||||
[NodeInputKeyEnum.httpMethod]: string;
|
||||
[NodeInputKeyEnum.httpReqUrl]: string;
|
||||
[NodeInputKeyEnum.httpHeaders]: PropsArrType[];
|
||||
[NodeInputKeyEnum.httpParams]: PropsArrType[];
|
||||
[NodeInputKeyEnum.httpJsonBody]: string;
|
||||
[NodeInputKeyEnum.addInputParam]: Record<string, any>;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
type HttpResponse = DispatchNodeResultType<{
|
||||
[ModuleOutputKeyEnum.failed]?: boolean;
|
||||
[NodeOutputKeyEnum.failed]?: boolean;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
|
||||
@@ -36,11 +41,13 @@ const UNDEFINED_SIGN = 'UNDEFINED_SIGN';
|
||||
|
||||
export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<HttpResponse> => {
|
||||
let {
|
||||
res,
|
||||
detail,
|
||||
appId,
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
variables,
|
||||
module: { outputs },
|
||||
node: { outputs },
|
||||
histories,
|
||||
params: {
|
||||
system_httpMethod: httpMethod = 'POST',
|
||||
@@ -48,7 +55,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
system_httpHeader: httpHeader,
|
||||
system_httpParams: httpParams = [],
|
||||
system_httpJsonBody: httpJsonBody,
|
||||
[DYNAMIC_INPUT_KEY]: dynamicInput,
|
||||
[NodeInputKeyEnum.addInputParam]: dynamicInput,
|
||||
...body
|
||||
}
|
||||
} = props;
|
||||
@@ -63,19 +70,25 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
responseChatItemId,
|
||||
...variables,
|
||||
histories: histories.slice(-10),
|
||||
...body
|
||||
...body,
|
||||
...dynamicInput
|
||||
};
|
||||
|
||||
httpReqUrl = replaceVariable(httpReqUrl, concatVariables);
|
||||
const allVariables = {
|
||||
[NodeInputKeyEnum.addInputParam]: concatVariables,
|
||||
...concatVariables
|
||||
};
|
||||
|
||||
httpReqUrl = replaceVariable(httpReqUrl, allVariables);
|
||||
// parse header
|
||||
const headers = await (() => {
|
||||
try {
|
||||
if (!httpHeader || httpHeader.length === 0) return {};
|
||||
// array
|
||||
return httpHeader.reduce((acc: Record<string, string>, item) => {
|
||||
const key = replaceVariable(item.key, concatVariables);
|
||||
const value = replaceVariable(item.value, concatVariables);
|
||||
acc[key] = valueTypeFormat(value, 'string');
|
||||
const key = replaceVariable(item.key, allVariables);
|
||||
const value = replaceVariable(item.value, allVariables);
|
||||
acc[key] = valueTypeFormat(value, WorkflowIOValueTypeEnum.string);
|
||||
return acc;
|
||||
}, {});
|
||||
} catch (error) {
|
||||
@@ -83,18 +96,18 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
}
|
||||
})();
|
||||
const params = httpParams.reduce((acc: Record<string, string>, item) => {
|
||||
const key = replaceVariable(item.key, concatVariables);
|
||||
const value = replaceVariable(item.value, concatVariables);
|
||||
acc[key] = valueTypeFormat(value, 'string');
|
||||
const key = replaceVariable(item.key, allVariables);
|
||||
const value = replaceVariable(item.value, allVariables);
|
||||
acc[key] = valueTypeFormat(value, WorkflowIOValueTypeEnum.string);
|
||||
return acc;
|
||||
}, {});
|
||||
const requestBody = await (() => {
|
||||
if (!httpJsonBody) return { [DYNAMIC_INPUT_KEY]: dynamicInput };
|
||||
httpJsonBody = replaceVariable(httpJsonBody, concatVariables);
|
||||
if (!httpJsonBody) return {};
|
||||
try {
|
||||
httpJsonBody = replaceVariable(httpJsonBody, allVariables);
|
||||
const jsonParse = JSON.parse(httpJsonBody);
|
||||
const removeSignJson = removeUndefinedSign(jsonParse);
|
||||
return { [DYNAMIC_INPUT_KEY]: dynamicInput, ...removeSignJson };
|
||||
return removeSignJson;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return Promise.reject(`Invalid JSON body: ${httpJsonBody}`);
|
||||
@@ -118,6 +131,16 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
results[key] = valueTypeFormat(formatResponse[key], output.valueType);
|
||||
}
|
||||
|
||||
if (typeof formatResponse[NodeOutputKeyEnum.answerText] === 'string') {
|
||||
responseWrite({
|
||||
res,
|
||||
event: detail ? SseResponseEventEnum.fastAnswer : undefined,
|
||||
data: textAdaptGptResponse({
|
||||
text: formatResponse[NodeOutputKeyEnum.answerText]
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
totalPoints: 0,
|
||||
@@ -127,13 +150,13 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
httpResult: rawResponse
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.toolResponses]: results,
|
||||
[ModuleOutputKeyEnum.httpRawResponse]: rawResponse,
|
||||
[NodeOutputKeyEnum.httpRawResponse]: rawResponse,
|
||||
...results
|
||||
};
|
||||
} catch (error) {
|
||||
addLog.error('Http request error', error);
|
||||
return {
|
||||
[ModuleOutputKeyEnum.failed]: true,
|
||||
[NodeOutputKeyEnum.failed]: true,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
totalPoints: 0,
|
||||
params: Object.keys(params).length > 0 ? params : undefined,
|
||||
@@ -141,7 +164,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
||||
httpResult: { error: formatHttpError(error) }
|
||||
},
|
||||
[ModuleOutputKeyEnum.httpRawResponse]: getErrText(error)
|
||||
[NodeOutputKeyEnum.httpRawResponse]: getErrText(error)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { ModelTypeEnum, getLLMModel } from '../../../../core/ai/model';
|
||||
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
|
||||
import { queryExtension } from '../../../../core/ai/functions/queryExtension';
|
||||
import { getHistories } from '../utils';
|
||||
import { hashStr } from '@fastgpt/global/common/string/tools';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.aiModel]: string;
|
||||
[ModuleInputKeyEnum.aiSystemPrompt]?: string;
|
||||
[ModuleInputKeyEnum.history]?: ChatItemType[] | number;
|
||||
[ModuleInputKeyEnum.userChatInput]: string;
|
||||
[NodeInputKeyEnum.aiModel]: string;
|
||||
[NodeInputKeyEnum.aiSystemPrompt]?: string;
|
||||
[NodeInputKeyEnum.history]?: ChatItemType[] | number;
|
||||
[NodeInputKeyEnum.userChatInput]: string;
|
||||
}>;
|
||||
type Response = DispatchNodeResultType<{
|
||||
[ModuleOutputKeyEnum.text]: string;
|
||||
[NodeOutputKeyEnum.text]: string;
|
||||
}>;
|
||||
|
||||
export const dispatchQueryExtension = async ({
|
||||
histories,
|
||||
module,
|
||||
node,
|
||||
params: { model, systemPrompt, history, userChatInput }
|
||||
}: Props): Promise<Response> => {
|
||||
if (!userChatInput) {
|
||||
@@ -65,12 +65,12 @@ export const dispatchQueryExtension = async ({
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
||||
{
|
||||
moduleName: module.name,
|
||||
moduleName: node.name,
|
||||
totalPoints,
|
||||
model: modelName,
|
||||
tokens
|
||||
}
|
||||
],
|
||||
[ModuleOutputKeyEnum.text]: JSON.stringify(filterSameQueries)
|
||||
[NodeOutputKeyEnum.text]: JSON.stringify(filterSameQueries)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { SelectAppItemType } from '@fastgpt/global/core/module/type';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { dispatchWorkFlow } from '../index';
|
||||
import { MongoApp } from '../../../../core/app/schema';
|
||||
import { responseWrite } from '../../../../common/response';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/module/runtime/utils';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { getHistories, setEntryEntries } from '../utils';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import {
|
||||
getDefaultEntryNodeIds,
|
||||
initWorkflowEdgeStatus,
|
||||
storeNodes2RuntimeNodes,
|
||||
textAdaptGptResponse
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { getHistories } from '../utils';
|
||||
import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.userChatInput]: string;
|
||||
[ModuleInputKeyEnum.history]?: ChatItemType[] | number;
|
||||
[NodeInputKeyEnum.userChatInput]: string;
|
||||
[NodeInputKeyEnum.history]?: ChatItemType[] | number;
|
||||
app: SelectAppItemType;
|
||||
}>;
|
||||
type Response = DispatchNodeResultType<{
|
||||
[ModuleOutputKeyEnum.answerText]: string;
|
||||
[ModuleOutputKeyEnum.history]: ChatItemType[];
|
||||
[NodeOutputKeyEnum.answerText]: string;
|
||||
[NodeOutputKeyEnum.history]: ChatItemType[];
|
||||
}>;
|
||||
|
||||
export const dispatchAppRequest = async (props: Props): Promise<Response> => {
|
||||
@@ -48,7 +53,7 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
|
||||
return Promise.reject('App not found');
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
if (res && stream) {
|
||||
responseWrite({
|
||||
res,
|
||||
event: detail ? SseResponseEventEnum.answer : undefined,
|
||||
@@ -63,11 +68,12 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
|
||||
const { flowResponses, flowUsages, assistantResponses } = await dispatchWorkFlow({
|
||||
...props,
|
||||
appId: app.id,
|
||||
modules: setEntryEntries(appData.modules),
|
||||
runtimeModules: undefined, // must reset
|
||||
runtimeNodes: storeNodes2RuntimeNodes(appData.modules, getDefaultEntryNodeIds(appData.modules)),
|
||||
runtimeEdges: initWorkflowEdgeStatus(appData.edges),
|
||||
histories: chatHistories,
|
||||
inputFiles,
|
||||
startParams: {
|
||||
variables: {
|
||||
...props.variables,
|
||||
userChatInput
|
||||
}
|
||||
});
|
||||
|
||||
70
packages/service/core/workflow/dispatch/tools/runIfElse.ts
Normal file
70
packages/service/core/workflow/dispatch/tools/runIfElse.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { VariableConditionEnum } from '@fastgpt/global/core/workflow/template/system/ifElse/constant';
|
||||
import {
|
||||
IfElseConditionType,
|
||||
IfElseListItemType
|
||||
} from '@fastgpt/global/core/workflow/template/system/ifElse/type';
|
||||
import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type';
|
||||
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[NodeInputKeyEnum.condition]: IfElseConditionType;
|
||||
[NodeInputKeyEnum.ifElseList]: IfElseListItemType[];
|
||||
}>;
|
||||
|
||||
function checkCondition(condition: VariableConditionEnum, variableValue: any, value: string) {
|
||||
const operations = {
|
||||
[VariableConditionEnum.isEmpty]: () => !variableValue,
|
||||
[VariableConditionEnum.isNotEmpty]: () => !!variableValue,
|
||||
[VariableConditionEnum.equalTo]: () => variableValue === value,
|
||||
[VariableConditionEnum.notEqual]: () => variableValue !== value,
|
||||
[VariableConditionEnum.greaterThan]: () => variableValue > Number(value),
|
||||
[VariableConditionEnum.lessThan]: () => variableValue < Number(value),
|
||||
[VariableConditionEnum.greaterThanOrEqualTo]: () => variableValue >= Number(value),
|
||||
[VariableConditionEnum.lessThanOrEqualTo]: () => variableValue <= Number(value),
|
||||
[VariableConditionEnum.include]: () => variableValue.includes(value),
|
||||
[VariableConditionEnum.notInclude]: () => !variableValue.includes(value),
|
||||
[VariableConditionEnum.startWith]: () => variableValue.startsWith(value),
|
||||
[VariableConditionEnum.endWith]: () => variableValue.endsWith(value),
|
||||
[VariableConditionEnum.lengthEqualTo]: () => variableValue.length === Number(value),
|
||||
[VariableConditionEnum.lengthNotEqualTo]: () => variableValue.length !== Number(value),
|
||||
[VariableConditionEnum.lengthGreaterThan]: () => variableValue.length > Number(value),
|
||||
[VariableConditionEnum.lengthGreaterThanOrEqualTo]: () => variableValue.length >= Number(value),
|
||||
[VariableConditionEnum.lengthLessThan]: () => variableValue.length < Number(value),
|
||||
[VariableConditionEnum.lengthLessThanOrEqualTo]: () => variableValue.length <= Number(value)
|
||||
};
|
||||
|
||||
return (operations[condition] || (() => false))();
|
||||
}
|
||||
|
||||
export const dispatchIfElse = async (props: Props): Promise<DispatchNodeResultType<{}>> => {
|
||||
const {
|
||||
params,
|
||||
runtimeNodes,
|
||||
node: { nodeId }
|
||||
} = props;
|
||||
const { condition, ifElseList } = params;
|
||||
const listResult = ifElseList.map((item) => {
|
||||
const { variable, condition: variableCondition, value } = item;
|
||||
|
||||
const variableValue = runtimeNodes
|
||||
.find((node) => node.nodeId === variable[0])
|
||||
?.outputs?.find((item) => item.id === variable[1])?.value;
|
||||
|
||||
return checkCondition(variableCondition as VariableConditionEnum, variableValue, value || '');
|
||||
});
|
||||
|
||||
const result = condition === 'AND' ? listResult.every(Boolean) : listResult.some(Boolean);
|
||||
|
||||
return {
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
totalPoints: 0,
|
||||
ifElseResult: result ? 'IF' : 'ELSE'
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.skipHandleId]: result
|
||||
? [getHandleId(nodeId, 'source', 'ELSE')]
|
||||
: [getHandleId(nodeId, 'source', 'IF')]
|
||||
};
|
||||
};
|
||||
@@ -1,23 +1,19 @@
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import {
|
||||
DYNAMIC_INPUT_KEY,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum
|
||||
} from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import axios from 'axios';
|
||||
import { valueTypeFormat } from '../utils';
|
||||
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
|
||||
import { addLog } from '../../../../common/system/log';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
|
||||
type LafRequestProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.httpReqUrl]: string;
|
||||
[DYNAMIC_INPUT_KEY]: Record<string, any>;
|
||||
[NodeInputKeyEnum.httpReqUrl]: string;
|
||||
[NodeInputKeyEnum.addInputParam]: Record<string, any>;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
type LafResponse = DispatchNodeResultType<{
|
||||
[ModuleOutputKeyEnum.failed]?: boolean;
|
||||
[NodeOutputKeyEnum.failed]?: boolean;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
|
||||
@@ -29,9 +25,13 @@ export const dispatchLafRequest = async (props: LafRequestProps): Promise<LafRes
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
variables,
|
||||
module: { outputs },
|
||||
node: { outputs },
|
||||
histories,
|
||||
params: { system_httpReqUrl: httpReqUrl, [DYNAMIC_INPUT_KEY]: dynamicInput, ...body }
|
||||
params: {
|
||||
system_httpReqUrl: httpReqUrl,
|
||||
[NodeInputKeyEnum.addInputParam]: dynamicInput,
|
||||
...body
|
||||
}
|
||||
} = props;
|
||||
|
||||
if (!httpReqUrl) {
|
||||
@@ -83,13 +83,13 @@ export const dispatchLafRequest = async (props: LafRequestProps): Promise<LafRes
|
||||
httpResult: rawResponse
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.toolResponses]: rawResponse,
|
||||
[ModuleOutputKeyEnum.httpRawResponse]: rawResponse,
|
||||
[NodeOutputKeyEnum.httpRawResponse]: rawResponse,
|
||||
...results
|
||||
};
|
||||
} catch (error) {
|
||||
addLog.error('Http request error', error);
|
||||
return {
|
||||
[ModuleOutputKeyEnum.failed]: true,
|
||||
[NodeOutputKeyEnum.failed]: true,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
totalPoints: 0,
|
||||
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
|
||||
|
||||
@@ -4,12 +4,19 @@ import {
|
||||
ChatItemValueItemType,
|
||||
ToolRunResponseItemType
|
||||
} from '@fastgpt/global/core/chat/type';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
|
||||
export type DispatchFlowResponse = {
|
||||
flowResponses: ChatHistoryItemResType[];
|
||||
flowUsages: ChatNodeUsageType[];
|
||||
debugResponse: {
|
||||
finishedNodes: RuntimeNodeItemType[];
|
||||
finishedEdges: RuntimeEdgeItemType[];
|
||||
nextStepRunNodes: RuntimeNodeItemType[];
|
||||
};
|
||||
[DispatchNodeResponseKeyEnum.toolResponses]: ToolRunResponseItemType;
|
||||
[DispatchNodeResponseKeyEnum.assistantResponses]: AIChatItemValueItemType[];
|
||||
};
|
||||
|
||||
@@ -1,43 +1,49 @@
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { ModuleIOValueTypeEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import {
|
||||
WorkflowIOValueTypeEnum,
|
||||
NodeOutputKeyEnum
|
||||
} from '@fastgpt/global/core/workflow/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import {
|
||||
RuntimeEdgeItemType,
|
||||
RuntimeNodeItemType
|
||||
} from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
|
||||
export const setEntryEntries = (modules: ModuleItemType[]) => {
|
||||
const initRunningModuleType: Record<string, boolean> = {
|
||||
[FlowNodeTypeEnum.historyNode]: true,
|
||||
[FlowNodeTypeEnum.questionInput]: true,
|
||||
[FlowNodeTypeEnum.pluginInput]: true
|
||||
};
|
||||
|
||||
modules.forEach((item) => {
|
||||
if (initRunningModuleType[item.flowType]) {
|
||||
item.isEntry = true;
|
||||
}
|
||||
});
|
||||
return modules;
|
||||
export const filterToolNodeIdByEdges = ({
|
||||
nodeId,
|
||||
edges
|
||||
}: {
|
||||
nodeId: string;
|
||||
edges: RuntimeEdgeItemType[];
|
||||
}) => {
|
||||
return edges
|
||||
.filter(
|
||||
(edge) => edge.source === nodeId && edge.targetHandle === NodeOutputKeyEnum.selectedTools
|
||||
)
|
||||
.map((edge) => edge.target);
|
||||
};
|
||||
|
||||
export const checkTheModuleConnectedByTool = (
|
||||
modules: ModuleItemType[],
|
||||
module: ModuleItemType
|
||||
) => {
|
||||
let sign = false;
|
||||
const toolModules = modules.filter((item) => item.flowType === FlowNodeTypeEnum.tools);
|
||||
// export const checkTheModuleConnectedByTool = (
|
||||
// modules: StoreNodeItemType[],
|
||||
// node: StoreNodeItemType
|
||||
// ) => {
|
||||
// let sign = false;
|
||||
// const toolModules = modules.filter((item) => item.flowNodeType === FlowNodeTypeEnum.tools);
|
||||
|
||||
toolModules.forEach((item) => {
|
||||
const toolOutput = item.outputs.find(
|
||||
(output) => output.key === ModuleOutputKeyEnum.selectedTools
|
||||
);
|
||||
toolOutput?.targets.forEach((target) => {
|
||||
if (target.moduleId === module.moduleId) {
|
||||
sign = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
// toolModules.forEach((item) => {
|
||||
// const toolOutput = item.outputs.find(
|
||||
// (output) => output.key === NodeOutputKeyEnum.selectedTools
|
||||
// );
|
||||
// toolOutput?.targets.forEach((target) => {
|
||||
// if (target.moduleId === node.moduleId) {
|
||||
// sign = true;
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
return sign;
|
||||
};
|
||||
// return sign;
|
||||
// };
|
||||
|
||||
export const getHistories = (history?: ChatItemType[] | number, histories: ChatItemType[] = []) => {
|
||||
if (!history) return [];
|
||||
@@ -48,7 +54,7 @@ export const getHistories = (history?: ChatItemType[] | number, histories: ChatI
|
||||
};
|
||||
|
||||
/* value type format */
|
||||
export const valueTypeFormat = (value: any, type?: `${ModuleIOValueTypeEnum}`) => {
|
||||
export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => {
|
||||
if (value === undefined) return;
|
||||
|
||||
if (type === 'string') {
|
||||
@@ -57,6 +63,16 @@ export const valueTypeFormat = (value: any, type?: `${ModuleIOValueTypeEnum}`) =
|
||||
}
|
||||
if (type === 'number') return Number(value);
|
||||
if (type === 'boolean') return Boolean(value);
|
||||
try {
|
||||
if (type === WorkflowIOValueTypeEnum.datasetQuote && !Array.isArray(value)) {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
if (type === WorkflowIOValueTypeEnum.selectDataset && !Array.isArray(value)) {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
} catch (error) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user