Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4.8.10 perf #2630

Merged
merged 6 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions packages/global/core/workflow/runtime/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,17 @@ export const getLastInteractiveValue = (histories: ChatItemType[]) => {
const lastValue = lastAIMessage.value[lastAIMessage.value.length - 1];

if (
lastValue &&
lastValue.type === ChatItemValueTypeEnum.interactive &&
!!lastValue.interactive
!lastValue ||
lastValue.type !== ChatItemValueTypeEnum.interactive ||
!lastValue.interactive
) {
return null;
}

// Check is user select
if (
lastValue.interactive.type === 'userSelect' &&
!lastValue.interactive.params.userSelectedVal
) {
return lastValue.interactive;
}
Expand Down
115 changes: 59 additions & 56 deletions packages/service/core/workflow/dispatch/agent/runTool/toolChoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { AIChatItemType } from '@fastgpt/global/core/chat/type';
import { updateToolInputValue } from './utils';
import { computedMaxToken, computedTemperature } from '../../../../ai/utils';
import { sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
import { addLog } from '../../../../../common/system/log';

type ToolRunResponseType = {
Expand Down Expand Up @@ -367,6 +367,7 @@ async function streamResponse({
});

let textAnswer = '';
let callingTool: { name: string; arguments: string } | null = null;
let toolCalls: ChatCompletionMessageToolCall[] = [];

for await (const part of stream) {
Expand All @@ -390,69 +391,71 @@ async function streamResponse({
});
} else if (responseChoice?.tool_calls?.[0]) {
const toolCall: ChatCompletionMessageToolCall = responseChoice.tool_calls[0];

// In a stream response, only one tool is returned at a time. If have id, description is executing a tool
if (toolCall.id) {
const toolNode = toolNodes.find((item) => item.nodeId === toolCall.function?.name);
if (toolCall.id || callingTool) {
// Start call tool
if (toolCall.id) {
callingTool = {
name: toolCall.function.name || '',
arguments: toolCall.function.arguments || ''
};
} else if (callingTool) {
// Continue call
callingTool.name += toolCall.function.name || '';
callingTool.arguments += toolCall.function.arguments || '';
}

const toolFunction = callingTool!;

const toolNode = toolNodes.find((item) => item.nodeId === toolFunction.name);

if (toolNode) {
if (toolCall.function?.arguments === undefined) {
toolCall.function.arguments = '';
}
// New tool, add to list.
const toolId = getNanoid();
toolCalls.push({
...toolCall,
id: toolId,
function: toolFunction,
toolName: toolNode.name,
toolAvatar: toolNode.avatar
});

// Get last tool call
const lastToolCall = toolCalls[toolCalls.length - 1];

// new tool
if (lastToolCall?.id !== toolCall.id) {
toolCalls.push({
...toolCall,
toolName: toolNode.name,
toolAvatar: toolNode.avatar
});

workflowStreamResponse?.({
event: SseResponseEventEnum.toolCall,
data: {
tool: {
id: toolCall.id,
toolName: toolNode.name,
toolAvatar: toolNode.avatar,
functionName: toolCall.function.name,
params: toolCall.function.arguments,
response: ''
}
workflowStreamResponse?.({
event: SseResponseEventEnum.toolCall,
data: {
tool: {
id: toolId,
toolName: toolNode.name,
toolAvatar: toolNode.avatar,
functionName: toolFunction.name,
params: toolFunction?.arguments ?? '',
response: ''
}
});

continue;
}
// last tool, update params
} else {
continue;
}
});
callingTool = null;
}
}

/* arg 插入最后一个工具的参数里 */
const arg: string = toolCall?.function?.arguments ?? '';
const currentTool = toolCalls[toolCalls.length - 1];

if (currentTool) {
currentTool.function.arguments += arg;
} else {
/* arg 插入最后一个工具的参数里 */
const arg: string = toolCall?.function?.arguments ?? '';
const currentTool = toolCalls[toolCalls.length - 1];
if (currentTool && arg) {
currentTool.function.arguments += arg;

workflowStreamResponse?.({
write,
event: SseResponseEventEnum.toolParams,
data: {
tool: {
id: currentTool.id,
toolName: '',
toolAvatar: '',
params: arg,
response: ''
workflowStreamResponse?.({
write,
event: SseResponseEventEnum.toolParams,
data: {
tool: {
id: currentTool.id,
toolName: '',
toolAvatar: '',
params: arg,
response: ''
}
}
}
});
});
}
}
}
}
Expand Down
38 changes: 26 additions & 12 deletions packages/service/core/workflow/dispatch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
let chatNodeUsages: ChatNodeUsageType[] = [];
let toolRunResponse: ToolRunResponseItemType;
let debugNextStepRunNodes: RuntimeNodeItemType[] = [];
// 记录交互节点,交互节点需要在工作流完全结束后再进行计算
let workflowInteractiveResponse:
| {
entryNodeIds: string[];
interactiveResponse: UserSelectInteractive;
}
| undefined;

/* Store special response field */
function pushStore(
Expand Down Expand Up @@ -338,6 +345,16 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons

if (!nodeRunResult) return [];

// In the current version, only one interactive node is allowed at the same time
const interactiveResponse = nodeRunResult.result?.[DispatchNodeResponseKeyEnum.interactive];
if (interactiveResponse) {
workflowInteractiveResponse = {
entryNodeIds: [nodeRunResult.node.nodeId],
interactiveResponse
};
return [];
}

// Update the node output at the end of the run and get the next nodes
let { nextStepActiveNodes, nextStepSkipNodes } = nodeOutput(
nodeRunResult.node,
Expand All @@ -351,18 +368,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
(node, index, self) => self.findIndex((t) => t.nodeId === node.nodeId) === index
);

// In the current version, only one interactive node is allowed at the same time
const interactiveResponse = nodeRunResult.result?.[DispatchNodeResponseKeyEnum.interactive];
if (interactiveResponse) {
chatAssistantResponse.push(
handleInteractiveResult({
entryNodeIds: [nodeRunResult.node.nodeId],
interactiveResponse
})
);
return [];
}

// Run next nodes(先运行 run 的,再运行 skip 的)
const nextStepActiveNodesResults = (
await Promise.all(nextStepActiveNodes.map((node) => checkNodeCanRun(node)))
Expand Down Expand Up @@ -543,6 +548,15 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
await nodeRunWithActive(pluginOutputModule);
}

// Interactive node
if (workflowInteractiveResponse) {
const interactiveResult = handleInteractiveResult({
entryNodeIds: workflowInteractiveResponse.entryNodeIds,
interactiveResponse: workflowInteractiveResponse.interactiveResponse
});
chatAssistantResponse.push(interactiveResult);
}

return {
flowResponses: chatResponses,
flowUsages: chatNodeUsages,
Expand Down
12 changes: 7 additions & 5 deletions packages/web/styles/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,11 +715,13 @@ export const theme = extendTheme({
lg: '1px solid #D0E0E2'
},
radii: {
xs: '4px',
sm: '6px',
md: '8px',
lg: '12px',
xl: '16px'
none: '0',
xs: '0.25rem',
sm: '0.375rem',
md: '0.5rem',
semilg: '0.625rem',
lg: '0.75rem',
xl: '1rem'
},
shadows: {
1: '0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)',
Expand Down
12 changes: 6 additions & 6 deletions projects/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# --------- install dependence -----------
FROM node:20.14.0-alpine AS mainDeps
FROM node:20.14.0-alpine AS maindeps
WORKDIR /app

ARG proxy
Expand All @@ -26,10 +26,10 @@ ARG proxy

# copy common node_modules and one project node_modules
COPY package.json pnpm-workspace.yaml .npmrc tsconfig.json ./
COPY --from=mainDeps /app/node_modules ./node_modules
COPY --from=mainDeps /app/packages ./packages
COPY --from=maindeps /app/node_modules ./node_modules
COPY --from=maindeps /app/packages ./packages
COPY ./projects/app ./projects/app
COPY --from=mainDeps /app/projects/app/node_modules ./projects/app/node_modules
COPY --from=maindeps /app/projects/app/node_modules ./projects/app/node_modules

RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

Expand Down Expand Up @@ -63,9 +63,9 @@ COPY --from=builder --chown=nextjs:nodejs /app/projects/app/.next/server/chunks
COPY --from=builder --chown=nextjs:nodejs /app/projects/app/.next/server/worker /app/projects/app/.next/server/worker

# copy standload packages
COPY --from=mainDeps /app/node_modules/tiktoken ./node_modules/tiktoken
COPY --from=maindeps /app/node_modules/tiktoken ./node_modules/tiktoken
RUN rm -rf ./node_modules/tiktoken/encoders
COPY --from=mainDeps /app/node_modules/@zilliz/milvus2-sdk-node ./node_modules/@zilliz/milvus2-sdk-node
COPY --from=maindeps /app/node_modules/@zilliz/milvus2-sdk-node ./node_modules/@zilliz/milvus2-sdk-node


# copy package.json to version file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ import ChatBoxDivider from '../../Divider';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { checkIsInteractiveByHistories, formatChatValue2InputType } from './utils';
import {
checkIsInteractiveByHistories,
formatChatValue2InputType,
setUserSelectResultToHistories
} from './utils';
import { textareaMinH } from './constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import ChatProvider, { ChatBoxContext, ChatProviderProps } from './Provider';
Expand Down Expand Up @@ -93,14 +97,6 @@ type Props = OutLinkChatAuthProps &
onDelMessage?: (e: { contentId: string }) => void;
};

/*
The input is divided into sections
1. text
2. img
3. file
4. ....
*/

const ChatBox = (
{
feedbackType = FeedbackTypeEnum.hidden,
Expand Down Expand Up @@ -377,7 +373,13 @@ const ChatBox = (
* user confirm send prompt
*/
const sendPrompt: SendPromptFnType = useCallback(
({ text = '', files = [], history = chatHistories, autoTTSResponse = false }) => {
({
text = '',
files = [],
history = chatHistories,
autoTTSResponse = false,
isInteractivePrompt = false
}) => {
variablesForm.handleSubmit(
async (variables) => {
if (!onStartChat) return;
Expand Down Expand Up @@ -444,6 +446,7 @@ const ChatBox = (
] as UserChatItemValueItemType[],
status: 'finish'
},
// 普通 chat 模式,需要增加一个 AI 来接收响应消息
{
dataId: responseChatId,
obj: ChatRoleEnum.AI,
Expand All @@ -459,28 +462,34 @@ const ChatBox = (
}
];

const isInteractive = checkIsInteractiveByHistories(history);
// Update histories(Interactive input does not require new session rounds)
setChatHistories(isInteractive ? newChatList.slice(0, -2) : newChatList);
setChatHistories(
isInteractivePrompt
? // 把交互的结果存储到对话记录中,交互模式下,不需要新的会话轮次
setUserSelectResultToHistories(newChatList.slice(0, -2), text)
: newChatList
);

// 清空输入内容
resetInputVal({});
setQuestionGuide([]);
scrollToBottom('smooth', 100);

try {
// create abort obj
const abortSignal = new AbortController();
chatController.current = abortSignal;

// Last empty ai message will be removed
// 最后一条 AI 消息是空的,会被过滤掉,这里得到的 messages,不会包含最后一条 AI 消息,所以不需要 slice 了。
// 这里,无论是否为交互模式,最后都是 Human 的消息。
const messages = chats2GPTMessages({ messages: newChatList, reserveId: true });

const {
responseData,
responseText,
isNewChat = false
} = await onStartChat({
messages: messages,
messages, // 保证最后一条是 Human 的消息
responseChatItemId: responseChatId,
controller: abortSignal,
generatingMessage: (e) => generatingMessage({ ...e, autoTTSResponse }),
Expand Down Expand Up @@ -847,12 +856,6 @@ const ChatBox = (
abortRequest();
setValue('chatStarted', false);
scrollToBottom('smooth', 500);
},
scrollToBottom,
sendPrompt: (question: string) => {
sendPrompt({
text: question
});
}
}));

Expand Down
Loading
Loading