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
0225519b
authored
Jun 23, 2026
by
YeYuheng
Committed by
GitHub
Jun 23, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(agent): normalize empty tool responses (#7171)
parent
e4afd59e
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
117 additions
and
12 deletions
+117
-12
packages/global/core/ai/llm/utils.ts
+7
-0
packages/global/core/ai/sandbox/constants.ts
+2
-1
packages/global/core/chat/adapt.ts
+5
-2
packages/global/test/core/chat/adapt.test.ts
+100
-0
packages/service/core/ai/llm/agentLoop/index.ts
+1
-1
packages/service/core/ai/llm/agentLoop/loop/base.ts
+1
-1
packages/service/core/ai/llm/agentLoop/loop/unified.ts
+1
-1
packages/service/core/ai/llm/agentLoop/utils.ts
+0
-6
No files found.
packages/global/core/ai/llm/utils.ts
View file @
0225519b
...
@@ -8,6 +8,13 @@ export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean)
...
@@ -8,6 +8,13 @@ export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean)
.
replace
(
/
[\[
【
]
id
[\]
】
]\(
CITE
\)
/g
,
''
);
.
replace
(
/
[\[
【
]
id
[\]
】
]\(
CITE
\)
/g
,
''
);
};
};
/**
* 规范化会写入 LLM tool message 的工具响应。
* OpenAI 兼容接口通常不接受空 tool content;undefined 和空字符串统一兜底为 none。
*/
export
const
normalizeToolResponseContent
=
(
response
?:
string
)
=>
response
===
''
||
response
===
undefined
?
'none'
:
response
;
export
const
getLLMSupportParams
=
(
llm
?:
LLMModelItemType
)
=>
{
export
const
getLLMSupportParams
=
(
llm
?:
LLMModelItemType
)
=>
{
return
{
return
{
vision
:
!!
llm
?.
vision
,
vision
:
!!
llm
?.
vision
,
...
...
packages/global/core/ai/sandbox/constants.ts
View file @
0225519b
...
@@ -35,4 +35,5 @@ export const SANDBOX_SYSTEM_PROMPT = `## 沙盒能力
...
@@ -35,4 +35,5 @@ export const SANDBOX_SYSTEM_PROMPT = `## 沙盒能力
- 使用
${
SANDBOX_EDIT_FILE_TOOL_NAME
}
对已有文件做精确查找替换
- 使用
${
SANDBOX_EDIT_FILE_TOOL_NAME
}
对已有文件做精确查找替换
- 使用
${
SANDBOX_SEARCH_TOOL_NAME
}
搜索沙盒内的文件路径
- 使用
${
SANDBOX_SEARCH_TOOL_NAME
}
搜索沙盒内的文件路径
- 生成的文件内容保存在当前工作区即可
- 生成的文件内容保存在当前工作区即可
- 若需要将生成的文件链接,可使用
${
SANDBOX_GET_FILE_URL_TOOL_NAME
}
获取临时访问链接`
;
- 若需要把沙盒中生成的文件提供给用户下载,必须先使用
${
SANDBOX_GET_FILE_URL_TOOL_NAME
}
获取临时访问链接
- 最终回复中不得直接输出 sandbox:/、/workspace/、/home/devbox/workspace/、/home/user/ 等沙盒内部路径`
;
packages/global/core/chat/adapt.ts
View file @
0225519b
...
@@ -19,6 +19,7 @@ import type {
...
@@ -19,6 +19,7 @@ import type {
ChatCompletionToolMessageParam
ChatCompletionToolMessageParam
}
from
'../ai/llm/type'
;
}
from
'../ai/llm/type'
;
import
{
ChatCompletionRequestMessageRoleEnum
}
from
'../../core/ai/constants'
;
import
{
ChatCompletionRequestMessageRoleEnum
}
from
'../../core/ai/constants'
;
import
{
normalizeToolResponseContent
}
from
'../ai/llm/utils'
;
type
FileUrlChatFileType
=
ChatFileTypeEnum
.
file
|
ChatFileTypeEnum
.
audio
|
ChatFileTypeEnum
.
video
;
type
FileUrlChatFileType
=
ChatFileTypeEnum
.
file
|
ChatFileTypeEnum
.
audio
|
ChatFileTypeEnum
.
video
;
type
FileUrlContentPart
=
Extract
<
ChatCompletionContentPart
,
{
type
:
'file_url'
}
>
;
type
FileUrlContentPart
=
Extract
<
ChatCompletionContentPart
,
{
type
:
'file_url'
}
>
;
...
@@ -315,7 +316,9 @@ export const chats2GPTMessages = ({
...
@@ -315,7 +316,9 @@ export const chats2GPTMessages = ({
toolResponse
:
{
toolResponse
:
{
tool_call_id
:
id
,
tool_call_id
:
id
,
role
:
ChatCompletionRequestMessageRoleEnum
.
Tool
,
role
:
ChatCompletionRequestMessageRoleEnum
.
Tool
,
content
:
typeof
tool
.
response
===
'string'
?
tool
.
response
:
''
content
:
normalizeToolResponseContent
(
typeof
tool
.
response
===
'string'
?
tool
.
response
:
undefined
)
}
}
};
};
};
};
...
@@ -451,7 +454,7 @@ export const chats2GPTMessages = ({
...
@@ -451,7 +454,7 @@ export const chats2GPTMessages = ({
dataId
,
dataId
,
role
:
ChatCompletionRequestMessageRoleEnum
.
Tool
,
role
:
ChatCompletionRequestMessageRoleEnum
.
Tool
,
tool_call_id
:
id
,
tool_call_id
:
id
,
content
:
response
content
:
normalizeToolResponseContent
(
response
)
});
});
};
};
...
...
packages/global/test/core/chat/adapt.test.ts
View file @
0225519b
...
@@ -1289,6 +1289,36 @@ describe('chats2GPTMessages', () => {
...
@@ -1289,6 +1289,36 @@ describe('chats2GPTMessages', () => {
expect
(
result
[
1
].
role
).
toBe
(
ChatCompletionRequestMessageRoleEnum
.
Tool
);
expect
(
result
[
1
].
role
).
toBe
(
ChatCompletionRequestMessageRoleEnum
.
Tool
);
});
});
it
(
'should normalize empty runtime tool responses to none when reserveTool is true'
,
()
=>
{
const
messages
:
ChatItemMiniType
[]
=
[
{
obj
:
ChatRoleEnum
.
AI
,
value
:
[
{
tools
:
[
{
id
:
'call_empty'
,
toolName
:
'Search'
,
toolAvatar
:
''
,
functionName
:
'search_web'
,
params
:
'{"query":"empty"}'
,
response
:
''
}
]
}
]
}
];
const
result
=
chats2GPTMessages
({
messages
,
reserveId
:
false
,
reserveTool
:
true
});
expect
(
result
[
1
]).
toMatchObject
({
role
:
ChatCompletionRequestMessageRoleEnum
.
Tool
,
tool_call_id
:
'call_empty'
,
content
:
'none'
});
});
it
(
'should handle deprecated single tool when reserveTool is true'
,
()
=>
{
it
(
'should handle deprecated single tool when reserveTool is true'
,
()
=>
{
const
messages
:
ChatItemMiniType
[]
=
[
const
messages
:
ChatItemMiniType
[]
=
[
{
{
...
@@ -1316,6 +1346,34 @@ describe('chats2GPTMessages', () => {
...
@@ -1316,6 +1346,34 @@ describe('chats2GPTMessages', () => {
expect
(
result
[
1
].
role
).
toBe
(
ChatCompletionRequestMessageRoleEnum
.
Tool
);
expect
(
result
[
1
].
role
).
toBe
(
ChatCompletionRequestMessageRoleEnum
.
Tool
);
});
});
it
(
'should normalize empty deprecated single tool responses to none when reserveTool is true'
,
()
=>
{
const
messages
:
ChatItemMiniType
[]
=
[
{
obj
:
ChatRoleEnum
.
AI
,
value
:
[
{
tool
:
{
id
:
'call_legacy_empty'
,
toolName
:
'Search'
,
toolAvatar
:
''
,
functionName
:
'search_web'
,
params
:
'{"query":"empty"}'
,
response
:
''
}
}
]
}
];
const
result
=
chats2GPTMessages
({
messages
,
reserveId
:
false
,
reserveTool
:
true
});
expect
(
result
[
1
]).
toMatchObject
({
role
:
ChatCompletionRequestMessageRoleEnum
.
Tool
,
tool_call_id
:
'call_legacy_empty'
,
content
:
'none'
});
});
it
(
'should filter invalid historical tool records when reserveTool is true'
,
()
=>
{
it
(
'should filter invalid historical tool records when reserveTool is true'
,
()
=>
{
const
messages
:
ChatItemMiniType
[]
=
[
const
messages
:
ChatItemMiniType
[]
=
[
{
{
...
@@ -2151,6 +2209,48 @@ describe('chats2GPTMessages', () => {
...
@@ -2151,6 +2209,48 @@ describe('chats2GPTMessages', () => {
]);
]);
});
});
it
(
'should normalize empty agent plan tool response to none when reserving tools'
,
()
=>
{
const
messages
:
ChatItemMiniType
[]
=
[
{
obj
:
ChatRoleEnum
.
AI
,
value
:
[
{
agentPlanUpdate
:
{
id
:
'call_plan_empty'
,
functionName
:
'update_plan'
,
params
:
'{"updates":[]}'
,
response
:
''
,
assistantText
:
'updating plan'
}
}
]
}
];
expect
(
chats2GPTMessages
({
messages
,
reserveId
:
false
,
reserveTool
:
true
})).
toEqual
([
{
role
:
ChatCompletionRequestMessageRoleEnum
.
Assistant
,
content
:
'updating plan'
,
tool_calls
:
[
{
id
:
'call_plan_empty'
,
type
:
'function'
,
function
:
{
name
:
'update_plan'
,
arguments
:
'{"updates":[]}'
}
}
]
},
{
dataId
:
undefined
,
role
:
ChatCompletionRequestMessageRoleEnum
.
Tool
,
tool_call_id
:
'call_plan_empty'
,
content
:
'none'
}
]);
});
it
(
'should restore ask_agent tool response from the matching interactive answer'
,
()
=>
{
it
(
'should restore ask_agent tool response from the matching interactive answer'
,
()
=>
{
const
messages
:
ChatItemMiniType
[]
=
[
const
messages
:
ChatItemMiniType
[]
=
[
{
{
...
...
packages/service/core/ai/llm/agentLoop/index.ts
View file @
0225519b
...
@@ -10,4 +10,4 @@ export * from './plan/updateTool';
...
@@ -10,4 +10,4 @@ export * from './plan/updateTool';
export
*
from
'./stop'
;
export
*
from
'./stop'
;
export
*
from
'./tools'
;
export
*
from
'./tools'
;
export
*
from
'./loop/unified'
;
export
*
from
'./loop/unified'
;
export
*
from
'.
/utils'
;
export
{
normalizeToolResponseContent
}
from
'@fastgpt/global/core/ai/llm
/utils'
;
packages/service/core/ai/llm/agentLoop/loop/base.ts
View file @
0225519b
...
@@ -23,7 +23,7 @@ import type {
...
@@ -23,7 +23,7 @@ import type {
import
{
AgentUsageModuleName
}
from
'../constants'
;
import
{
AgentUsageModuleName
}
from
'../constants'
;
import
{
getErrText
}
from
'@fastgpt/global/common/error/utils'
;
import
{
getErrText
}
from
'@fastgpt/global/common/error/utils'
;
import
{
batchRun
}
from
'@fastgpt/global/common/system/utils'
;
import
{
batchRun
}
from
'@fastgpt/global/common/system/utils'
;
import
{
normalizeToolResponseContent
}
from
'
..
/utils'
;
import
{
normalizeToolResponseContent
}
from
'
@fastgpt/global/core/ai/llm
/utils'
;
type
RunAgentCallProps
<
TChildrenResponse
=
unknown
>
=
{
type
RunAgentCallProps
<
TChildrenResponse
=
unknown
>
=
{
maxRunAgentTimes
:
number
;
maxRunAgentTimes
:
number
;
...
...
packages/service/core/ai/llm/agentLoop/loop/unified.ts
View file @
0225519b
...
@@ -11,7 +11,7 @@ import { parsePlanAskToolCall } from '../plan/parser';
...
@@ -11,7 +11,7 @@ import { parsePlanAskToolCall } from '../plan/parser';
import
{
applyPlanUpdate
}
from
'../plan/state'
;
import
{
applyPlanUpdate
}
from
'../plan/state'
;
import
{
runStopGate
}
from
'../stop'
;
import
{
runStopGate
}
from
'../stop'
;
import
{
getToolsForUnifiedLoop
,
normalizeToolCatalog
}
from
'../tools'
;
import
{
getToolsForUnifiedLoop
,
normalizeToolCatalog
}
from
'../tools'
;
import
{
normalizeToolResponseContent
}
from
'
..
/utils'
;
import
{
normalizeToolResponseContent
}
from
'
@fastgpt/global/core/ai/llm
/utils'
;
import
type
{
import
type
{
AgentLoopRuntime
,
AgentLoopRuntime
,
AgentLoopToolExecutionResult
,
AgentLoopToolExecutionResult
,
...
...
packages/service/core/ai/llm/agentLoop/utils.ts
deleted
100644 → 0
View file @
e4afd59e
/**
* 规范化会写入 LLM tool message 的工具响应。
* OpenAI 兼容接口通常不接受空 tool content;undefined 和空字符串统一兜底为 none。
*/
export
const
normalizeToolResponseContent
=
(
response
?:
string
)
=>
response
===
''
||
response
===
undefined
?
'none'
:
response
;
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