Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
c4799df3
authored
Aug 27, 2025
by
Archer
Committed by
GitHub
Aug 27, 2025
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
perf: workflow code (#5548)
* perf: workflow code * add tool call limit
parent
610634e1
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
128 additions
and
113 deletions
+128
-113
packages/global/core/workflow/runtime/type.d.ts
+2
-1
packages/service/core/ai/llm/promptToolCall.ts
+6
-3
packages/service/core/ai/llm/request.ts
+5
-1
packages/service/core/workflow/dispatch/abandoned/runApp.ts
+20
-22
packages/service/core/workflow/dispatch/ai/agent/index.ts
+1
-1
packages/service/core/workflow/dispatch/ai/agent/toolCall.ts
+3
-3
packages/service/core/workflow/dispatch/child/runApp.ts
+2
-2
packages/service/core/workflow/dispatch/index.ts
+67
-61
packages/service/core/workflow/dispatch/loop/runLoop.ts
+2
-2
packages/service/core/workflow/dispatch/plugin/run.ts
+2
-2
test/cases/service/core/ai/llm/toolCall.test.ts
+18
-15
No files found.
packages/global/core/workflow/runtime/type.d.ts
View file @
c4799df3
...
...
@@ -72,9 +72,10 @@ export type ChatDispatchProps = {
maxRunTimes
:
number
;
isToolCall
?:
boolean
;
workflowStreamResponse
?:
WorkflowResponseType
;
workflowDispatchDeep
?:
number
;
version
?:
'v1'
|
'v2'
;
workflowDispatchDeep
:
number
;
responseAllData
?:
boolean
;
responseDetail
?:
boolean
;
};
...
...
packages/service/core/ai/llm/promptToolCall.ts
View file @
c4799df3
...
...
@@ -66,11 +66,12 @@ export const promptToolCallMessageRewrite = (
return
cloneMessages
;
};
const
ERROR_TEXT
=
'Tool
run
error'
;
const
ERROR_TEXT
=
'Tool
call
error'
;
export
const
parsePromptToolCall
=
(
str
:
string
):
{
answer
:
string
;
streamAnswer
?:
string
;
toolCalls
?:
ChatCompletionMessageToolCall
[];
}
=>
{
str
=
str
.
trim
();
...
...
@@ -99,11 +100,13 @@ export const parsePromptToolCall = (
}
catch
(
error
)
{
if
(
prefixReg
.
test
(
str
))
{
return
{
answer
:
ERROR_TEXT
answer
:
`
${
ERROR_TEXT
}
:
${
str
}
`
,
streamAnswer
:
`
${
ERROR_TEXT
}
:
${
str
}
`
};
}
else
{
return
{
answer
:
str
answer
:
str
,
streamAnswer
:
str
};
}
}
...
...
packages/service/core/ai/llm/request.ts
View file @
c4799df3
...
...
@@ -324,7 +324,11 @@ export const createStreamResponse = async ({
}
const
{
reasoningContent
,
content
,
finish_reason
,
usage
}
=
getResponseData
();
const
{
answer
:
llmAnswer
,
toolCalls
}
=
parsePromptToolCall
(
content
);
const
{
answer
:
llmAnswer
,
streamAnswer
,
toolCalls
}
=
parsePromptToolCall
(
content
);
if
(
streamAnswer
)
{
onStreaming
?.({
text
:
streamAnswer
});
}
toolCalls
?.
forEach
((
call
)
=>
{
onToolCall
?.({
call
});
...
...
packages/service/core/workflow/dispatch/abandoned/runApp.ts
View file @
c4799df3
...
...
@@ -2,7 +2,7 @@
import
type
{
ChatItemType
}
from
'@fastgpt/global/core/chat/type.d'
;
import
type
{
ModuleDispatchProps
}
from
'@fastgpt/global/core/workflow/runtime/type'
;
import
{
type
SelectAppItemType
}
from
'@fastgpt/global/core/workflow/template/system/abandoned/runApp/type'
;
import
{
dispatchWorkF
low
}
from
'../index'
;
import
{
runWorkf
low
}
from
'../index'
;
import
{
ChatRoleEnum
}
from
'@fastgpt/global/core/chat/constants'
;
import
{
SseResponseEventEnum
}
from
'@fastgpt/global/core/workflow/runtime/constants'
;
import
{
...
...
@@ -59,27 +59,25 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
const
chatHistories
=
getHistories
(
history
,
histories
);
const
{
files
}
=
chatValue2RuntimePrompt
(
query
);
const
{
flowResponses
,
flowUsages
,
assistantResponses
,
system_memories
}
=
await
dispatchWorkFlow
(
{
...
props
,
runningAppInfo
:
{
id
:
String
(
appData
.
_id
),
teamId
:
String
(
appData
.
teamId
),
tmbId
:
String
(
appData
.
tmbId
)
},
runtimeNodes
:
storeNodes2RuntimeNodes
(
appData
.
modules
,
getWorkflowEntryNodeIds
(
appData
.
modules
)
),
runtimeEdges
:
storeEdges2RuntimeEdges
(
appData
.
edges
),
histories
:
chatHistories
,
query
:
runtimePrompt2ChatsValue
({
files
,
text
:
userChatInput
}),
variables
:
props
.
variables
}
);
const
{
flowResponses
,
flowUsages
,
assistantResponses
,
system_memories
}
=
await
runWorkflow
({
...
props
,
runningAppInfo
:
{
id
:
String
(
appData
.
_id
),
teamId
:
String
(
appData
.
teamId
),
tmbId
:
String
(
appData
.
tmbId
)
},
runtimeNodes
:
storeNodes2RuntimeNodes
(
appData
.
modules
,
getWorkflowEntryNodeIds
(
appData
.
modules
)
),
runtimeEdges
:
storeEdges2RuntimeEdges
(
appData
.
edges
),
histories
:
chatHistories
,
query
:
runtimePrompt2ChatsValue
({
files
,
text
:
userChatInput
}),
variables
:
props
.
variables
});
const
completeMessages
=
chatHistories
.
concat
([
{
...
...
packages/service/core/workflow/dispatch/ai/agent/index.ts
View file @
c4799df3
...
...
@@ -201,7 +201,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
return
runToolCall
({
...
props
,
...
requestParams
,
maxRunToolTimes
:
3
0
maxRunToolTimes
:
10
0
});
})();
...
...
packages/service/core/workflow/dispatch/ai/agent/toolCall.ts
View file @
c4799df3
...
...
@@ -8,7 +8,7 @@ import { responseWriteController } from '../../../../../common/response';
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
{
dispatchWorkF
low
}
from
'../../index'
;
import
{
runWorkf
low
}
from
'../../index'
;
import
type
{
DispatchToolModuleProps
,
RunToolResponse
,
ToolNodeItemType
}
from
'./type'
;
import
json5
from
'json5'
;
import
type
{
DispatchFlowResponse
}
from
'../../type'
;
...
...
@@ -110,7 +110,7 @@ export const runToolCall = async (
initToolCallEdges
(
runtimeEdges
,
interactiveEntryToolParams
.
entryNodeIds
);
// Run entry tool
const
toolRunResponse
=
await
dispatchWorkF
low
({
const
toolRunResponse
=
await
runWorkf
low
({
...
workflowProps
,
isToolCall
:
true
});
...
...
@@ -383,7 +383,7 @@ export const runToolCall = async (
})();
initToolNodes
(
runtimeNodes
,
[
toolNode
.
nodeId
],
startParams
);
const
toolRunResponse
=
await
dispatchWorkF
low
({
const
toolRunResponse
=
await
runWorkf
low
({
...
workflowProps
,
isToolCall
:
true
});
...
...
packages/service/core/workflow/dispatch/child/runApp.ts
View file @
c4799df3
import
type
{
ChatItemType
}
from
'@fastgpt/global/core/chat/type.d'
;
import
type
{
ModuleDispatchProps
}
from
'@fastgpt/global/core/workflow/runtime/type'
;
import
{
dispatchWorkF
low
}
from
'../index'
;
import
{
runWorkf
low
}
from
'../index'
;
import
{
ChatRoleEnum
}
from
'@fastgpt/global/core/chat/constants'
;
import
{
SseResponseEventEnum
}
from
'@fastgpt/global/core/workflow/runtime/constants'
;
import
{
...
...
@@ -132,7 +132,7 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
runTimes
,
workflowInteractiveResponse
,
system_memories
}
=
await
dispatchWorkF
low
({
}
=
await
runWorkf
low
({
...
props
,
lastInteractive
:
childrenInteractive
,
// Rewrite stream mode
...
...
packages/service/core/workflow/dispatch/index.ts
View file @
c4799df3
...
...
@@ -47,7 +47,7 @@ import { removeSystemVariable, rewriteRuntimeWorkFlow } from './utils';
import
{
getHandleId
}
from
'@fastgpt/global/core/workflow/utils'
;
import
{
callbackMap
}
from
'./constants'
;
type
Props
=
ChatDispatchProps
&
{
type
Props
=
Omit
<
ChatDispatchProps
,
'workflowDispatchDeep'
>
&
{
runtimeNodes
:
RuntimeNodeItemType
[];
runtimeEdges
:
RuntimeEdgeItemType
[];
};
...
...
@@ -58,8 +58,62 @@ type NodeResponseCompleteType = Omit<NodeResponseType, 'responseData'> & {
[
DispatchNodeResponseKeyEnum
.
nodeResponse
]?:
ChatHistoryItemResType
;
};
/
* running */
/
/ Run workflow
export
async
function
dispatchWorkFlow
(
data
:
Props
):
Promise
<
DispatchFlowResponse
>
{
const
{
res
,
stream
,
externalProvider
}
=
data
;
let
streamCheckTimer
:
NodeJS
.
Timeout
|
null
=
null
;
// set sse response headers
if
(
res
)
{
res
.
setHeader
(
'Connection'
,
'keep-alive'
);
// Set keepalive for long connection
if
(
stream
)
{
res
.
on
(
'close'
,
()
=>
res
.
end
());
res
.
on
(
'error'
,
()
=>
{
addLog
.
error
(
'Request error'
);
res
.
end
();
});
res
.
setHeader
(
'Content-Type'
,
'text/event-stream;charset=utf-8'
);
res
.
setHeader
(
'Access-Control-Allow-Origin'
,
'*'
);
res
.
setHeader
(
'X-Accel-Buffering'
,
'no'
);
res
.
setHeader
(
'Cache-Control'
,
'no-cache, no-transform'
);
// 10s sends a message to prevent the browser from thinking that the connection is disconnected
streamCheckTimer
=
setInterval
(()
=>
{
data
?.
workflowStreamResponse
?.({
event
:
SseResponseEventEnum
.
answer
,
data
:
textAdaptGptResponse
({
text
:
''
})
});
},
10000
);
}
}
// Get default variables
const
defaultVariables
=
{
...
externalProvider
.
externalWorkflowVariables
,
...
getSystemVariables
(
data
)
};
// Init some props
return
runWorkflow
({
...
data
,
variables
:
defaultVariables
,
workflowDispatchDeep
:
0
}).
finally
(()
=>
{
if
(
streamCheckTimer
)
{
clearInterval
(
streamCheckTimer
);
}
});
}
type
RunWorkflowProps
=
ChatDispatchProps
&
{
runtimeNodes
:
RuntimeNodeItemType
[];
runtimeEdges
:
RuntimeEdgeItemType
[];
};
export
const
runWorkflow
=
async
(
data
:
RunWorkflowProps
):
Promise
<
DispatchFlowResponse
>
=>
{
let
{
res
,
runtimeNodes
=
[],
...
...
@@ -67,28 +121,15 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
histories
=
[],
variables
=
{},
externalProvider
,
stream
=
false
,
retainDatasetCite
=
true
,
version
=
'v1'
,
responseDetail
=
true
,
responseAllData
=
true
}
=
data
;
const
startTime
=
Date
.
now
();
await
rewriteRuntimeWorkFlow
({
nodes
:
runtimeNodes
,
edges
:
runtimeEdges
,
lang
:
data
.
lang
});
// 初始化深度和自动增加深度,避免无限嵌套
if
(
!
data
.
workflowDispatchDeep
)
{
data
.
workflowDispatchDeep
=
1
;
}
else
{
data
.
workflowDispatchDeep
+=
1
;
}
const
isRootRuntime
=
data
.
workflowDispatchDeep
===
1
;
// 初始化 runtimeNodesMap
const
runtimeNodesMap
=
new
Map
(
runtimeNodes
.
map
((
item
)
=>
[
item
.
nodeId
,
item
]));
// Over max depth
data
.
workflowDispatchDeep
++
;
const
isRootRuntime
=
data
.
workflowDispatchDeep
===
1
;
if
(
data
.
workflowDispatchDeep
>
20
)
{
return
{
flowResponses
:
[],
...
...
@@ -106,42 +147,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
};
}
let
workflowRunTimes
=
0
;
let
streamCheckTimer
:
NodeJS
.
Timeout
|
null
=
null
;
// Init
if
(
isRootRuntime
)
{
// set sse response headers
res
?.
setHeader
(
'Connection'
,
'keep-alive'
);
// Set keepalive for long connection
if
(
stream
&&
res
)
{
res
.
on
(
'close'
,
()
=>
res
.
end
());
res
.
on
(
'error'
,
()
=>
{
addLog
.
error
(
'Request error'
);
res
.
end
();
});
res
.
setHeader
(
'Content-Type'
,
'text/event-stream;charset=utf-8'
);
res
.
setHeader
(
'Access-Control-Allow-Origin'
,
'*'
);
res
.
setHeader
(
'X-Accel-Buffering'
,
'no'
);
res
.
setHeader
(
'Cache-Control'
,
'no-cache, no-transform'
);
// 10s sends a message to prevent the browser from thinking that the connection is disconnected
streamCheckTimer
=
setInterval
(()
=>
{
data
?.
workflowStreamResponse
?.({
event
:
SseResponseEventEnum
.
answer
,
data
:
textAdaptGptResponse
({
text
:
''
})
});
},
10000
);
}
const
startTime
=
Date
.
now
();
// Get default variables
variables
=
{
...
externalProvider
.
externalWorkflowVariables
,
...
getSystemVariables
(
data
)
};
}
await
rewriteRuntimeWorkFlow
({
nodes
:
runtimeNodes
,
edges
:
runtimeEdges
,
lang
:
data
.
lang
});
/*
工作流队列控制
...
...
@@ -161,7 +169,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
- 触发交互节点后,需要跳过所有 skip 节点,避免后续执行了 skipNode。
*/
class
WorkflowQueue
{
runtimeNodesMap
=
new
Map
(
runtimeNodes
.
map
((
item
)
=>
[
item
.
nodeId
,
item
]));
// Workflow variables
workflowRunTimes
=
0
;
chatResponses
:
ChatHistoryItemResType
[]
=
[];
// response request and save to database
chatAssistantResponse
:
AIChatItemValueItemType
[]
=
[];
// The value will be returned to the user
chatNodeUsages
:
ChatNodeUsageType
[]
=
[];
...
...
@@ -221,7 +231,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
}
const
nodeId
=
this
.
activeRunQueue
.
keys
().
next
().
value
;
const
node
=
nodeId
?
runtimeNodesMap
.
get
(
nodeId
)
:
undefined
;
const
node
=
nodeId
?
this
.
runtimeNodesMap
.
get
(
nodeId
)
:
undefined
;
if
(
nodeId
)
{
this
.
activeRunQueue
.
delete
(
nodeId
);
...
...
@@ -501,7 +511,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
system_memories
:
newMemories
}:
NodeResponseCompleteType
)
=>
{
// Add run times
workflowRunTimes
+=
runTimes
;
this
.
workflowRunTimes
+=
runTimes
;
data
.
maxRunTimes
-=
runTimes
;
if
(
newMemories
)
{
...
...
@@ -650,7 +660,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
// Get node run status by edges
const
status
=
checkNodeRunStatus
({
nodesMap
:
runtimeNodesMap
,
nodesMap
:
this
.
runtimeNodesMap
,
node
,
runtimeEdges
});
...
...
@@ -820,10 +830,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
});
}
if
(
streamCheckTimer
)
{
clearInterval
(
streamCheckTimer
);
}
return
{
flowResponses
:
workflowQueue
.
chatResponses
,
flowUsages
:
workflowQueue
.
chatNodeUsages
,
...
...
@@ -833,7 +839,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
nextStepRunNodes
:
workflowQueue
.
debugNextStepRunNodes
},
workflowInteractiveResponse
:
interactiveResult
,
[
DispatchNodeResponseKeyEnum
.
runTimes
]:
workflowRunTimes
,
[
DispatchNodeResponseKeyEnum
.
runTimes
]:
workflow
Queue
.
workflow
RunTimes
,
[
DispatchNodeResponseKeyEnum
.
assistantResponses
]:
mergeAssistantResponseAnswerText
(
workflowQueue
.
chatAssistantResponse
),
...
...
@@ -848,7 +854,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
:
undefined
,
durationSeconds
};
}
}
;
/* get system variable */
const
getSystemVariables
=
({
...
...
packages/service/core/workflow/dispatch/loop/runLoop.ts
View file @
c4799df3
...
...
@@ -4,7 +4,7 @@ import {
type
DispatchNodeResultType
,
type
ModuleDispatchProps
}
from
'@fastgpt/global/core/workflow/runtime/type'
;
import
{
dispatchWorkF
low
}
from
'..'
;
import
{
runWorkf
low
}
from
'..'
;
import
{
DispatchNodeResponseKeyEnum
}
from
'@fastgpt/global/core/workflow/runtime/constants'
;
import
{
type
AIChatItemValueItemType
,
...
...
@@ -93,7 +93,7 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
index
++
;
const
response
=
await
dispatchWorkF
low
({
const
response
=
await
runWorkf
low
({
...
props
,
lastInteractive
:
interactiveData
?.
childrenResponse
,
variables
:
newVariables
,
...
...
packages/service/core/workflow/dispatch/plugin/run.ts
View file @
c4799df3
...
...
@@ -20,7 +20,7 @@ import { filterSystemVariables, getNodeErrResponse } from '../utils';
import
{
getPluginRunUserQuery
}
from
'@fastgpt/global/core/workflow/utils'
;
import
type
{
NodeInputKeyEnum
,
NodeOutputKeyEnum
}
from
'@fastgpt/global/core/workflow/constants'
;
import
{
getChildAppRuntimeById
}
from
'../../../app/plugin/controller'
;
import
{
dispatchWorkF
low
}
from
'../index'
;
import
{
runWorkf
low
}
from
'../index'
;
import
{
getUserChatInfoAndAuthTeamPoints
}
from
'../../../../support/permission/auth/team'
;
import
{
dispatchRunTool
}
from
'../child/runTool'
;
import
type
{
PluginRuntimeType
}
from
'@fastgpt/global/core/app/plugin/type'
;
...
...
@@ -118,7 +118,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
...(
externalProvider
?
externalProvider
.
externalWorkflowVariables
:
{})
};
const
{
flowResponses
,
flowUsages
,
assistantResponses
,
runTimes
,
system_memories
}
=
await
dispatchWorkF
low
({
await
runWorkf
low
({
...
props
,
// Rewrite stream mode
...(
system_forbid_stream
...
...
test/cases/service/core/ai/llm/toolCall.test.ts
View file @
c4799df3
...
...
@@ -141,18 +141,20 @@ describe('parsePromptToolCall function tests', () => {
const
input
=
'1: {"name": "tool", "arguments": invalid json}'
;
const
result
=
parsePromptToolCall
(
input
);
expect
(
result
).
toEqual
({
answer
:
'Tool run error'
});
expect
(
result
.
answer
).
toEqual
(
'Tool call error: 1: {"name": "tool", "arguments": invalid json}'
);
expect
(
result
.
streamAnswer
).
toEqual
(
'Tool call error: 1: {"name": "tool", "arguments": invalid json}'
);
});
it
(
'should return error message for incomplete JSON with 1:'
,
()
=>
{
const
input
=
'1: {"name": "tool"'
;
const
result
=
parsePromptToolCall
(
input
);
expect
(
result
).
toEqual
({
answer
:
'Tool run error'
});
expect
(
result
.
answer
).
toEqual
(
'Tool call error: 1: {"name": "tool"'
);
expect
(
result
.
streamAnswer
).
toEqual
(
'Tool call error: 1: {"name": "tool"'
);
});
it
(
'should handle empty JSON object with 1: (creates tool call with undefined properties)'
,
()
=>
{
...
...
@@ -187,18 +189,16 @@ describe('parsePromptToolCall function tests', () => {
const
input
=
'1:'
;
const
result
=
parsePromptToolCall
(
input
);
expect
(
result
).
toEqual
({
answer
:
'Tool run error'
});
expect
(
result
.
answer
).
toEqual
(
'Tool call error: 1:'
);
expect
(
result
.
streamAnswer
).
toEqual
(
'Tool call error: 1:'
);
});
it
(
'should handle input with only prefix and whitespace'
,
()
=>
{
const
input
=
'1: '
;
const
result
=
parsePromptToolCall
(
input
);
expect
(
result
).
toEqual
({
answer
:
'Tool run error'
});
expect
(
result
.
answer
).
toEqual
(
'Tool call error: 1:'
);
expect
(
result
.
streamAnswer
).
toEqual
(
'Tool call error: 1:'
);
});
it
(
'should handle JSON5 syntax in tool call'
,
()
=>
{
...
...
@@ -244,9 +244,12 @@ describe('parsePromptToolCall function tests', () => {
const
result
=
parsePromptToolCall
(
input
);
// The sliceJsonStr function can't properly extract JSON when there's extra text after
expect
(
result
).
toEqual
({
answer
:
'Tool run error'
});
expect
(
result
.
answer
).
toEqual
(
'Tool call error: Text 1: {"name": "tool1", "arguments": {"param": "value"}} more text 1: {"name": "tool2", "arguments": {}}'
);
expect
(
result
.
streamAnswer
).
toEqual
(
'Tool call error: Text 1: {"name": "tool1", "arguments": {"param": "value"}} more text 1: {"name": "tool2", "arguments": {}}'
);
});
it
(
'should handle tool name with underscores and numbers'
,
()
=>
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment