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
05254ce0
authored
May 21, 2026
by
Archer
Committed by
GitHub
May 21, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
perf: adapt messages (#6959)
parent
0f55590a
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
435 additions
and
145 deletions
+435
-145
document/content/self-host/upgrading/4-14/41421.mdx
+1
-0
document/data/doc-last-modified.json
+1
-1
packages/global/core/chat/adapt.ts
+0
-0
packages/global/core/chat/helperBot/type.ts
+20
-11
packages/global/core/chat/type.ts
+1
-0
packages/global/core/chat/utils.ts
+17
-19
packages/global/test/core/chat/adapt.test.ts
+0
-0
packages/global/test/core/chat/helperBot/adaptor.test.ts
+21
-0
packages/global/test/core/chat/helperBot/type.test.ts
+16
-0
packages/service/core/ai/utils.ts
+39
-3
packages/service/core/chat/HelperBot/dispatch/utils.ts
+7
-8
packages/service/core/workflow/dispatch/ai/agent/adapter/eventMapper.ts
+16
-11
packages/service/core/workflow/dispatch/ai/agent/index.ts
+12
-17
packages/service/core/workflow/dispatch/ai/agent/piAgent/index.ts
+8
-10
packages/service/core/workflow/dispatch/ai/classifyQuestion.ts
+1
-1
packages/service/core/workflow/dispatch/ai/extract.ts
+6
-2
packages/service/core/workflow/dispatch/index.ts
+8
-7
packages/service/test/core/ai/utils.test.ts
+24
-0
packages/service/test/core/workflow/dispatch/ai/agent/adapter/eventMapper.test.ts
+75
-0
packages/service/test/core/workflow/dispatch/ai/agent/index.test.ts
+38
-0
packages/service/test/core/workflow/dispatch/ai/agent/piAgent/index.test.ts
+39
-0
projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx
+19
-14
projects/app/src/components/core/chat/HelperBot/components/AIItem.tsx
+27
-21
projects/app/src/components/core/chat/HelperBot/index.tsx
+38
-19
projects/app/src/components/core/chat/components/AIResponseBox/index.tsx
+1
-1
No files found.
document/content/self-host/upgrading/4-14/41421.mdx
View file @
05254ce0
...
...
@@ -15,3 +15,4 @@ description: 'FastGPT V4.14.21 更新说明'
1. 工作流默认选中模型未回传表单值,导致看到的模型和实际运行模型不一致。
2. 工作流自动保存时,存在边丢失风险。
3. admin 修改系统通知弹窗时候报错。
4. 工作流混用思考/非思考模型,可能出现独立 reason 字段上下文,导致模型调用报错。
document/data/doc-last-modified.json
View file @
05254ce0
...
...
@@ -276,7 +276,7 @@
"content/self-host/upgrading/4-14/4149.en.mdx"
:
"2026-04-26T21:08:47+08:00"
,
"content/self-host/upgrading/4-14/4149.mdx"
:
"2026-04-26T21:08:47+08:00"
,
"content/self-host/upgrading/4-15/4150.mdx"
:
"2026-05-20T17:52:26+08:00"
,
"content/self-host/upgrading/4-15/41502.mdx"
:
"2026-05-
18T19:47:26
+08:00"
,
"content/self-host/upgrading/4-15/41502.mdx"
:
"2026-05-
21T16:33:20
+08:00"
,
"content/self-host/upgrading/outdated/40.en.mdx"
:
"2026-04-26T21:08:47+08:00"
,
"content/self-host/upgrading/outdated/40.mdx"
:
"2026-04-26T21:08:47+08:00"
,
"content/self-host/upgrading/outdated/41.en.mdx"
:
"2026-04-26T21:08:47+08:00"
,
...
...
packages/global/core/chat/adapt.ts
View file @
05254ce0
This diff is collapsed.
Click to expand it.
packages/global/core/chat/helperBot/type.ts
View file @
05254ce0
import
{
ObjectIdSchema
}
from
'../../../common/type/mongo'
;
import
z
from
'zod'
;
import
{
ChatRoleEnum
}
from
'../constants'
;
import
{
UserChatItemSchema
,
SystemChatItemSchema
,
ToolModuleResponseItemSchema
}
from
'../type'
;
import
{
UserChatItemSchema
,
SystemChatItemSchema
}
from
'../type'
;
import
{
UserInputInteractiveSchema
}
from
'../../workflow/template/system/interactive/type'
;
export
enum
HelperBotTypeEnum
{
...
...
@@ -21,18 +21,27 @@ export const HelperBotChatSchema = z.object({
});
export
type
HelperBotChatType
=
z
.
infer
<
typeof
HelperBotChatSchema
>
;
const
AIChatContentValueSchema
=
z
.
object
({
text
:
z
.
object
({
content
:
z
.
string
()
})
.
optional
(),
reasoning
:
z
.
object
({
content
:
z
.
string
()
})
.
optional
(),
hideReason
:
z
.
boolean
().
optional
()
})
.
refine
((
value
)
=>
value
.
text
||
value
.
reasoning
,
{
message
:
'HelperBot AI content value requires text or reasoning'
});
// AI schema
export
const
AIChatItemValueItemSchema
=
z
.
union
([
z
.
object
({
text
:
z
.
object
({
content
:
z
.
string
()
})
}),
z
.
object
({
reasoning
:
z
.
object
({
content
:
z
.
string
()
})
}),
AIChatContentValueSchema
,
z
.
object
({
collectionForm
:
UserInputInteractiveSchema
}),
...
...
packages/global/core/chat/type.ts
View file @
05254ce0
...
...
@@ -225,6 +225,7 @@ export const AIChatItemValueSchema = z.object({
agentStopGate
:
AgentLoopStopGateSchema
.
nullish
(),
contextCheckpoint
:
ContextCheckpointValueSchema
.
nullish
(),
tool
:
ToolModuleResponseItemSchema
.
nullish
().
meta
({
deprecated
:
true
}),
hideReason
:
z
.
boolean
().
optional
(),
hideInUI
:
z
.
boolean
().
optional
()
});
...
...
packages/global/core/chat/utils.ts
View file @
05254ce0
...
...
@@ -152,27 +152,25 @@ export const removeAIResponseCite = <T extends AIChatItemValueItemType[] | strin
return
removeDatasetCiteText
(
value
,
false
)
as
T
;
}
return
value
.
map
<
AIChatItemValueItemType
>
((
item
)
=>
{
if
(
item
.
text
?.
content
)
{
return
{
...
item
,
text
:
{
...
item
.
text
,
content
:
removeDatasetCiteText
(
item
.
text
.
content
,
false
)
return
value
.
map
<
AIChatItemValueItemType
>
((
item
)
=>
({
...
item
,
...(
item
.
text
?.
content
?
{
text
:
{
...
item
.
text
,
content
:
removeDatasetCiteText
(
item
.
text
.
content
,
false
)
}
}
};
}
if
(
item
.
reasoning
?.
content
)
{
return
{
...
item
,
reasoning
:
{
...
item
.
reasoning
,
content
:
removeDatasetCiteText
(
item
.
reasoning
.
content
,
false
)
:
{}),
...(
item
.
reasoning
?.
content
?
{
reasoning
:
{
...
item
.
reasoning
,
content
:
removeDatasetCiteText
(
item
.
reasoning
.
content
,
false
)
}
}
};
}
return
item
;
})
as
T
;
:
{})
}))
as
T
;
};
export
const
removeEmptyUserInput
=
(
input
?:
UserChatItemValueItemType
[])
=>
{
...
...
packages/global/test/core/chat/adapt.test.ts
View file @
05254ce0
This diff is collapsed.
Click to expand it.
packages/global/test/core/chat/helperBot/adaptor.test.ts
View file @
05254ce0
...
...
@@ -115,6 +115,27 @@ describe('helperChats2GPTMessages', () => {
expect
(
result
[
0
].
content
).
toBe
(
'Hello, how can I help?'
);
});
it
(
'should convert merged AI reasoning and text value with visible text'
,
()
=>
{
const
messages
=
[
{
obj
:
ChatRoleEnum
.
AI
,
value
:
[
{
reasoning
:
{
content
:
'Hidden thinking'
},
hideReason
:
true
,
text
:
{
content
:
'Visible answer'
}
}
]
}
]
as
HelperBotChatItemType
[];
const
result
=
helperChats2GPTMessages
({
messages
});
expect
(
result
).
toHaveLength
(
1
);
expect
(
result
[
0
].
role
).
toBe
(
ChatCompletionRequestMessageRoleEnum
.
Assistant
);
expect
(
result
[
0
].
content
).
toBe
(
'Visible answer'
);
});
it
(
'should concat multiple AI text values'
,
()
=>
{
const
messages
=
[
{
...
...
packages/global/test/core/chat/helperBot/type.test.ts
View file @
05254ce0
...
...
@@ -47,6 +47,22 @@ describe('AIChatItemValueItemSchema', () => {
expect
(
result
.
success
).
toBe
(
true
);
});
it
(
'should validate merged text and hidden reasoning content'
,
()
=>
{
const
result
=
AIChatItemValueItemSchema
.
safeParse
({
reasoning
:
{
content
:
'Hidden thinking'
},
hideReason
:
true
,
text
:
{
content
:
'Visible answer'
}
});
expect
(
result
.
success
).
toBe
(
true
);
});
it
(
'should reject content value without text or reasoning'
,
()
=>
{
const
result
=
AIChatItemValueItemSchema
.
safeParse
({
hideReason
:
true
});
expect
(
result
.
success
).
toBe
(
false
);
});
it
(
'should validate planHint'
,
()
=>
{
const
result
=
AIChatItemValueItemSchema
.
safeParse
({
planHint
:
{
type
:
'generation'
}
...
...
packages/service/core/ai/utils.ts
View file @
05254ce0
...
...
@@ -40,7 +40,13 @@ export const computedTemperature = ({
};
// LLM utils
// Parse <think></think> tags to think and answer - unstream response
const
normalizeFirstAnswerAfterReasoning
=
(
answer
:
string
)
=>
answer
.
trimStart
();
/**
* 从非流式模型结果中拆分 <think></think> 思考内容和最终回答。
* </think> 后面的前导空白只用于分隔 reasoning 与 answer,不作为正文保留,
* 避免 reasoning-only 输出被解析成 answerText="\n"。
*/
export
const
parseReasoningContent
=
(
text
:
string
):
[
string
,
string
]
=>
{
const
regex
=
/<think>
([\s\S]
*
?)
<
\/
think>/
;
const
match
=
text
.
match
(
regex
);
...
...
@@ -52,7 +58,9 @@ export const parseReasoningContent = (text: string): [string, string] => {
const
thinkContent
=
match
[
1
].
trim
();
// Add answer (remaining text after think tag)
const
answerContent
=
text
.
slice
(
match
.
index
!
+
match
[
0
].
length
);
const
answerContent
=
normalizeFirstAnswerAfterReasoning
(
text
.
slice
(
match
.
index
!
+
match
[
0
].
length
)
);
return
[
thinkContent
,
answerContent
];
};
...
...
@@ -75,6 +83,29 @@ export const parseLLMStreamResponse = () => {
let
buffer_reasoningContent
=
''
;
let
buffer_content
=
''
;
let
error
:
any
=
undefined
;
let
shouldNormalizeFirstAnswer
=
false
;
const
normalizeContentBoundary
=
({
reasoningContent
,
content
}:
{
reasoningContent
:
string
;
content
:
string
;
})
=>
{
if
(
reasoningContent
)
{
shouldNormalizeFirstAnswer
=
true
;
}
if
(
!
content
||
!
shouldNormalizeFirstAnswer
)
return
content
;
const
normalizedContent
=
normalizeFirstAnswerAfterReasoning
(
content
);
if
(
normalizedContent
)
{
shouldNormalizeFirstAnswer
=
false
;
}
return
normalizedContent
;
};
/*
parseThinkTag - 只控制是否主动解析 <think></think>,如果接口已经解析了,则不再解析。
...
...
@@ -117,7 +148,7 @@ export const parseLLMStreamResponse = () => {
const
isStreamEnd
=
!!
buffer_finishReason
;
// Parse think
const
{
reasoningContent
:
parsedThinkReasoningContent
,
content
:
p
arsedThinkContent
}
=
const
{
reasoningContent
:
parsedThinkReasoningContent
,
content
:
rawP
arsedThinkContent
}
=
(()
=>
{
if
(
reasoningContent
||
!
parseThinkTag
)
{
isInThinkTag
=
false
;
...
...
@@ -234,6 +265,11 @@ export const parseLLMStreamResponse = () => {
};
})();
const
parsedThinkContent
=
normalizeContentBoundary
({
reasoningContent
:
parsedThinkReasoningContent
,
content
:
rawParsedThinkContent
});
// Parse datset cite
if
(
retainDatasetCite
)
{
return
{
...
...
packages/service/core/chat/HelperBot/dispatch/utils.ts
View file @
05254ce0
...
...
@@ -20,15 +20,14 @@ export const formatAIResponse = ({
}):
AIChatItemValueItemType
[]
=>
{
const
result
:
AIChatItemValueItemType
[]
=
[];
if
(
reasoning
)
{
result
.
push
({
reasoning
:
{
content
:
reasoning
}
});
}
result
.
push
({
...(
reasoning
?
{
reasoning
:
{
content
:
reasoning
}
}
:
{}),
text
:
{
content
:
text
}
...
...
packages/service/core/workflow/dispatch/ai/agent/adapter/eventMapper.ts
View file @
05254ce0
...
...
@@ -192,6 +192,10 @@ export const createWorkflowAgentLoopEventMapper = ({
assistantText
?:
string
;
reasoningText
?:
string
;
})
=>
{
// 没有可见 assistantText 时,不在 request end 阶段回填 reasoning 到已有 tool。
// reasoning 的归属需要按流式顺序附着到后续 content/tool,避免误挂到上一轮工具。
if
(
!
assistantText
)
return
;
const
runtimeToolCalls
=
toolCalls
.
filter
((
call
)
=>
{
const
functionName
=
call
.
function
.
name
;
return
(
...
...
@@ -201,21 +205,22 @@ export const createWorkflowAgentLoopEventMapper = ({
!
isInternalTool
(
functionName
,
internalToolNames
)
);
});
if
(
!
runtimeToolCalls
.
length
||
(
!
assistantText
&&
!
reasoningText
)
)
return
;
if
(
!
runtimeToolCalls
.
length
)
return
;
const
runtimeToolCallIds
=
new
Set
(
runtimeToolCalls
.
map
((
call
)
=>
call
.
id
));
const
existingIndexes
=
assistantResponses
.
map
((
item
,
index
)
=>
item
.
tools
?.
some
((
tool
)
=>
runtimeToolCallIds
.
has
(
tool
.
id
))
?
index
:
-
1
)
.
filter
((
index
)
=>
index
>=
0
);
const
insertIndex
=
existingIndexes
.
length
?
Math
.
min
(...
existingIndexes
)
:
assistantResponses
.
length
;
const
existingIndex
=
assistantResponses
.
findIndex
((
item
)
=>
item
.
tools
?.
some
((
tool
)
=>
runtimeToolCallIds
.
has
(
tool
.
id
))
);
const
insertIndex
=
existingIndex
>=
0
?
existingIndex
:
assistantResponses
.
length
;
const
assistantValue
:
AIChatItemValueItemType
=
{
...(
assistantText
?
{
text
:
{
content
:
assistantText
}
}
:
{}),
...(
reasoningText
?
{
reasoning
:
{
content
:
reasoningText
}
}
:
{})
text
:
{
content
:
assistantText
},
...(
reasoningText
?
{
reasoning
:
{
content
:
reasoningText
},
...(
!
showReasoning
?
{
hideReason
:
true
}
:
{})
}
:
{})
};
assistantResponses
.
splice
(
insertIndex
,
0
,
assistantValue
);
};
...
...
packages/service/core/workflow/dispatch/ai/agent/index.ts
View file @
05254ce0
...
...
@@ -332,26 +332,21 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
:
result
.
status
===
'aborted'
?
i18nT
(
'chat:completion_finish_error'
)
:
undefined
;
if
(
result
.
reasoningText
)
{
assistantResponses
.
push
({
reasoning
:
{
content
:
result
.
reasoningText
},
...(
aiChatReasoning
===
false
?
{
hideInUI
:
true
}
:
{})
});
}
if
(
result
.
answerText
)
{
assistantResponses
.
push
({
text
:
{
content
:
result
.
answerText
const
reasoningValue
=
result
.
reasoningText
?
{
reasoning
:
{
content
:
result
.
reasoningText
},
...(
aiChatReasoning
===
false
?
{
hideReason
:
true
}
:
{})
}
});
}
else
if
(
errorText
)
{
:
{};
const
finalText
=
result
.
answerText
||
errorText
;
if
(
finalText
)
{
assistantResponses
.
push
({
...
reasoningValue
,
text
:
{
content
:
error
Text
content
:
final
Text
}
});
}
...
...
packages/service/core/workflow/dispatch/ai/agent/piAgent/index.ts
View file @
05254ce0
...
...
@@ -80,19 +80,17 @@ export const dispatchPiAgent = async (props: DispatchAgentModuleProps): Promise<
const
appendFinalAssistantResponses
=
()
=>
{
const
reasoningText
=
piRuntime
?.
getReasoningText
()
||
''
;
const
answerText
=
piRuntime
?.
getAnswerText
()
||
''
;
const
showReasoning
=
aiChatReasoning
!==
false
;
if
(
reasoningText
)
{
assistantResponses
.
push
({
reasoning
:
{
content
:
reasoningText
},
...(
!
showReasoning
?
{
hideInUI
:
true
}
:
{})
});
}
if
(
answerText
)
{
assistantResponses
.
push
({
...(
reasoningText
?
{
reasoning
:
{
content
:
reasoningText
},
...(
aiChatReasoning
===
false
?
{
hideReason
:
true
}
:
{})
}
:
{}),
text
:
{
content
:
answerText
}
...
...
packages/service/core/workflow/dispatch/ai/classifyQuestion.ts
View file @
05254ce0
...
...
@@ -146,7 +146,7 @@ const completions = async ({
body
:
{
model
:
cqModel
.
model
,
temperature
:
0.01
,
messages
:
chats2GPTMessages
({
messages
,
reserveId
:
false
}),
messages
:
chats2GPTMessages
({
messages
,
reserveId
:
false
,
reserveReason
:
false
}),
stream
:
true
},
userKey
:
externalProvider
.
openaiAccount
...
...
packages/service/core/workflow/dispatch/ai/extract.ts
View file @
05254ce0
...
...
@@ -209,7 +209,11 @@ const toolChoice = async (props: ActionProps) => {
]
}
];
const
adaptMessages
=
chats2GPTMessages
({
messages
,
reserveId
:
false
});
const
adaptMessages
=
chats2GPTMessages
({
messages
,
reserveId
:
false
,
reserveReason
:
false
});
const
filterMessages
=
await
filterGPTMessageByMaxContext
({
messages
:
adaptMessages
,
maxContext
:
extractModel
.
maxContext
...
...
@@ -317,7 +321,7 @@ const completions = async (props: ActionProps) => {
body
:
{
model
:
extractModel
.
model
,
temperature
:
0.01
,
messages
:
chats2GPTMessages
({
messages
,
reserveId
:
false
}),
messages
:
chats2GPTMessages
({
messages
,
reserveId
:
false
,
reserveReason
:
false
}),
stream
:
true
},
userKey
:
externalProvider
.
openaiAccount
...
...
packages/service/core/workflow/dispatch/index.ts
View file @
05254ce0
...
...
@@ -1212,15 +1212,16 @@ export class WorkflowQueue {
if
(
assistantResponses
)
{
this
.
chatAssistantResponse
=
this
.
chatAssistantResponse
.
concat
(
assistantResponses
);
}
else
{
if
(
reasoningText
)
{
this
.
chatAssistantResponse
.
push
({
reasoning
:
{
content
:
reasoningText
}
});
}
// reasoning 不能独立落历史;只有存在可见文本时才附着保存。
if
(
answerText
)
{
this
.
chatAssistantResponse
.
push
({
...(
reasoningText
?
{
reasoning
:
{
content
:
reasoningText
}
}
:
{}),
text
:
{
content
:
answerText
}
...
...
packages/service/test/core/ai/utils.test.ts
View file @
05254ce0
...
...
@@ -85,6 +85,14 @@ describe('parseReasoningContent', () => {
expect
(
parseReasoningContent
(
'<think>reasoning</think>'
)).
toEqual
([
'reasoning'
,
''
]);
});
it
(
'should remove separator whitespace after think tag'
,
()
=>
{
expect
(
parseReasoningContent
(
'<think>reasoning</think>\n'
)).
toEqual
([
'reasoning'
,
''
]);
expect
(
parseReasoningContent
(
'<think>reasoning</think>\n\nanswer'
)).
toEqual
([
'reasoning'
,
'answer'
]);
});
it
(
'should handle multiline think content'
,
()
=>
{
expect
(
parseReasoningContent
(
'<think>line1\nline2</think>answer'
)).
toEqual
([
'line1\nline2'
,
...
...
@@ -191,6 +199,22 @@ describe('parseLLMStreamResponse', () => {
correct
:
{
answer
:
'你好1你好2你好3'
,
reasoning
:
'这是思考过程'
}
},
{
data
:
[{
content
:
'<think>这是'
},
{
content
:
'思考过程</think>\n'
}],
correct
:
{
answer
:
''
,
reasoning
:
'这是思考过程'
}
},
{
data
:
[{
content
:
'<think>这是'
},
{
content
:
'思考过程</think>\n'
},
{
content
:
'你好1'
}],
correct
:
{
answer
:
'你好1'
,
reasoning
:
'这是思考过程'
}
},
{
data
:
[{
content
:
'<think>这是'
},
{
content
:
'思考过程</think>\n\n你好1'
}],
correct
:
{
answer
:
'你好1'
,
reasoning
:
'这是思考过程'
}
},
{
data
:
[{
reasoning_content
:
'这是思考过程'
},
{
content
:
'\n'
},
{
content
:
'你好1'
}],
correct
:
{
answer
:
'你好1'
,
reasoning
:
'这是思考过程'
}
},
{
data
:
[
{
content
:
'<think>这是'
},
{
content
:
'思考'
},
...
...
packages/service/test/core/workflow/dispatch/ai/agent/adapter/eventMapper.test.ts
View file @
05254ce0
...
...
@@ -97,6 +97,81 @@ describe('createWorkflowAgentLoopEventMapper', () => {
});
});
it
(
'hides reasoning stream but persists reasoning with hideReason'
,
()
=>
{
const
workflowStreamResponse
=
vi
.
fn
();
const
mapper
=
createWorkflowAgentLoopEventMapper
({
workflowStreamResponse
,
getSubAppInfo
:
(
id
)
=>
({
name
:
id
,
avatar
:
''
,
toolDescription
:
''
}),
internalToolNames
:
new
Set
(),
showReasoning
:
false
});
mapper
.
emitEvent
({
type
:
'reasoning_delta'
,
text
:
'hidden'
});
expect
(
workflowStreamResponse
).
not
.
toHaveBeenCalled
();
mapper
.
emitEvent
({
type
:
'tool_call'
,
call
:
{
id
:
'call_search'
,
type
:
'function'
,
function
:
{
name
:
'search'
,
arguments
:
'{}'
}
}
});
mapper
.
emitEvent
({
type
:
'llm_request_end'
,
requestIndex
:
1
,
modelName
:
'GPT-4'
,
requestId
:
'req_search'
,
finishReason
:
'tool_calls'
,
answerText
:
'Need to search.'
,
reasoningText
:
'hidden thinking'
,
toolCalls
:
[
{
id
:
'call_search'
,
type
:
'function'
,
function
:
{
name
:
'search'
,
arguments
:
'{}'
}
}
]
});
expect
(
mapper
.
assistantResponses
).
toEqual
([
{
text
:
{
content
:
'Need to search.'
},
reasoning
:
{
content
:
'hidden thinking'
},
hideReason
:
true
},
{
id
:
'call_search'
,
tools
:
[
{
id
:
'call_search'
,
toolName
:
'search'
,
toolAvatar
:
''
,
functionName
:
'search'
,
params
:
'{}'
}
]
}
]);
});
it
(
'filters internal tool calls and streams runtime tool lifecycle events'
,
()
=>
{
const
workflowStreamResponse
=
vi
.
fn
();
const
mapper
=
createWorkflowAgentLoopEventMapper
({
...
...
packages/service/test/core/workflow/dispatch/ai/agent/index.test.ts
View file @
05254ce0
...
...
@@ -228,4 +228,42 @@ describe('dispatchRunAgent user context', () => {
}
]);
});
it
(
'keeps reasoning with hideReason when reasoning display is disabled'
,
async
()
=>
{
const
{
dispatchRunAgent
}
=
await
import
(
'@fastgpt/service/core/workflow/dispatch/ai/agent'
);
const
props
=
createProps
();
props
.
params
.
aiChatReasoning
=
false
;
runUnifiedAgentLoopMock
.
mockResolvedValueOnce
({
status
:
'done'
,
answerText
:
'ok'
,
reasoningText
:
'hidden thinking'
,
completeMessages
:
[],
assistantMessages
:
[],
requestIds
:
[]
});
let
resultPromise
:
Promise
<
any
>
;
runWithContext
(
{
queryUrlTypeMap
:
{},
mcpClientMemory
:
{}
},
()
=>
{
resultPromise
=
dispatchRunAgent
(
props
);
}
);
const
result
=
await
resultPromise
!
;
expect
(
result
[
DispatchNodeResponseKeyEnum
.
assistantResponses
]).
toEqual
([
{
reasoning
:
{
content
:
'hidden thinking'
},
hideReason
:
true
,
text
:
{
content
:
'ok'
}
}
]);
});
});
packages/service/test/core/workflow/dispatch/ai/agent/piAgent/index.test.ts
View file @
05254ce0
...
...
@@ -265,4 +265,43 @@ describe('dispatchPiAgent user context', () => {
}
]);
});
it
(
'keeps reasoning with hideReason when reasoning display is disabled'
,
async
()
=>
{
const
{
dispatchPiAgent
}
=
await
import
(
'@fastgpt/service/core/workflow/dispatch/ai/agent/piAgent'
);
const
props
=
createProps
();
props
.
params
.
aiChatReasoning
=
false
;
createPiAgentWorkflowRuntimeMock
.
mockReturnValueOnce
({
onPayload
:
vi
.
fn
(),
handleAgentEvent
:
vi
.
fn
(),
appendChildNodeResponse
:
vi
.
fn
(),
getReasoningText
:
vi
.
fn
(()
=>
'hidden thinking'
),
getAnswerText
:
vi
.
fn
(()
=>
'pi answer'
),
appendPendingAgentError
:
vi
.
fn
()
});
let
resultPromise
:
Promise
<
any
>
;
runWithContext
(
{
queryUrlTypeMap
:
{},
mcpClientMemory
:
{}
},
()
=>
{
resultPromise
=
dispatchPiAgent
(
props
);
}
);
const
result
=
await
resultPromise
!
;
expect
(
result
[
DispatchNodeResponseKeyEnum
.
assistantResponses
]).
toEqual
([
{
reasoning
:
{
content
:
'hidden thinking'
},
hideReason
:
true
,
text
:
{
content
:
'pi answer'
}
}
]);
});
});
projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx
View file @
05254ce0
...
...
@@ -456,17 +456,24 @@ const ChatBox = ({
};
}
if
(
event
===
SseResponseEventEnum
.
answer
||
event
===
SseResponseEventEnum
.
fastAnswer
)
{
const
replaceUpdateValue
=
(
nextValue
:
AIChatItemValueItemType
)
=>
({
...
item
,
value
:
[
...
item
.
value
.
slice
(
0
,
updateIndex
),
nextValue
,
...
item
.
value
.
slice
(
updateIndex
+
1
)
]
});
if
(
reasoningText
)
{
if
(
updateValue
?.
reasoning
)
{
updateValue
.
reasoning
.
content
+=
reasoningText
;
return
{
...
item
,
value
:
[
...
item
.
value
.
slice
(
0
,
updateIndex
),
updateValue
,
...
item
.
value
.
slice
(
updateIndex
+
1
)
]
return
replaceUpdateValue
(
updateValue
);
}
else
if
(
updateValue
?.
text
&&
!
updateValue
.
text
.
content
)
{
updateValue
.
reasoning
=
{
content
:
reasoningText
};
return
replaceUpdateValue
(
updateValue
);
}
else
{
const
val
:
AIChatItemValueItemType
=
{
id
:
responseValueId
,
...
...
@@ -483,14 +490,12 @@ const ChatBox = ({
if
(
text
)
{
if
(
updateValue
?.
text
)
{
updateValue
.
text
.
content
+=
text
;
return
{
...
item
,
value
:
[
...
item
.
value
.
slice
(
0
,
updateIndex
),
updateValue
,
...
item
.
value
.
slice
(
updateIndex
+
1
)
]
return
replaceUpdateValue
(
updateValue
);
}
else
if
(
updateValue
?.
reasoning
)
{
updateValue
.
text
=
{
content
:
text
};
return
replaceUpdateValue
(
updateValue
);
}
else
{
const
newValue
:
AIChatItemValueItemType
=
{
id
:
responseValueId
,
...
...
projects/app/src/components/core/chat/HelperBot/components/AIItem.tsx
View file @
05254ce0
...
...
@@ -173,6 +173,14 @@ const AIItem = ({
onSubmitCollectionForm
:
(
formData
:
string
)
=>
void
;
})
=>
{
const
{
t
}
=
useTranslation
();
const
firstValue
=
chat
.
value
[
0
];
const
isWaitingForResponse
=
chat
.
value
.
length
===
1
&&
!
(
(
'text'
in
firstValue
&&
firstValue
.
text
?.
content
)
||
(
'reasoning'
in
firstValue
&&
firstValue
.
reasoning
&&
!
firstValue
.
hideReason
)
);
return
(
<
Box
_hover=
{
{
...
...
@@ -192,8 +200,7 @@ const AIItem = ({
color=
{
'myGray.900'
}
bg=
{
'myGray.100'
}
>
{
chat
.
value
.
length
===
1
&&
(
!
(
'text'
in
chat
.
value
[
0
])
||
!
chat
.
value
[
0
].
text
?.
content
)
?
(
{
isWaitingForResponse
?
(
<
RenderText
showAnimation=
{
true
}
text=
{
t
(
'chat:chat.waiting_for_response'
)
}
/>
)
:
(
<>
...
...
@@ -203,25 +210,6 @@ const AIItem = ({
<
RenderText
key=
{
i
}
showAnimation=
{
false
}
text=
{
t
(
'chat:plan_check_tip'
)
}
/>
);
}
if
(
'text'
in
value
&&
value
.
text
)
{
return
(
<
RenderText
key=
{
i
}
showAnimation=
{
isChatting
&&
isLastChild
}
text=
{
value
.
text
.
content
}
/>
);
}
if
(
'reasoning'
in
value
&&
value
.
reasoning
)
{
return
(
<
RenderResoningContent
key=
{
i
}
isChatting=
{
isChatting
}
isLastResponseValue=
{
isLastChild
}
content=
{
value
.
reasoning
.
content
}
/>
);
}
if
(
'collectionForm'
in
value
&&
value
.
collectionForm
)
{
return
(
<
RenderCollectionForm
...
...
@@ -232,6 +220,24 @@ const AIItem = ({
/>
);
}
return
(
<
React
.
Fragment
key=
{
i
}
>
{
'reasoning'
in
value
&&
value
.
reasoning
&&
!
value
.
hideReason
&&
(
<
RenderResoningContent
isChatting=
{
isChatting
}
isLastResponseValue=
{
isLastChild
}
content=
{
value
.
reasoning
.
content
}
/>
)
}
{
'text'
in
value
&&
value
.
text
&&
(
<
RenderText
showAnimation=
{
isChatting
&&
isLastChild
}
text=
{
value
.
text
.
content
}
/>
)
}
</
React
.
Fragment
>
);
})
}
</>
)
}
...
...
projects/app/src/components/core/chat/HelperBot/index.tsx
View file @
05254ce0
...
...
@@ -180,17 +180,31 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
}
if
(
event
===
SseResponseEventEnum
.
answer
||
event
===
SseResponseEventEnum
.
fastAnswer
)
{
const
replaceUpdateValue
=
(
nextValue
:
AIChatItemValueItemType
)
=>
({
...
item
,
value
:
[
...
item
.
value
.
slice
(
0
,
updateIndex
),
nextValue
,
...
item
.
value
.
slice
(
updateIndex
+
1
)
]
});
if
(
reasoningText
)
{
if
(
'reasoning'
in
updateValue
&&
updateValue
.
reasoning
)
{
updateValue
.
reasoning
.
content
+=
reasoningText
;
return
{
...
item
,
value
:
[
...
item
.
value
.
slice
(
0
,
updateIndex
),
updateValue
,
...
item
.
value
.
slice
(
updateIndex
+
1
)
]
};
return
replaceUpdateValue
({
...
updateValue
,
reasoning
:
{
...
updateValue
.
reasoning
,
content
:
updateValue
.
reasoning
.
content
+
reasoningText
}
});
}
else
if
(
'text'
in
updateValue
&&
updateValue
.
text
&&
!
updateValue
.
text
.
content
)
{
return
replaceUpdateValue
({
...
updateValue
,
reasoning
:
{
content
:
reasoningText
}
});
}
else
{
const
val
:
AIChatItemValueItemType
=
{
reasoning
:
{
...
...
@@ -205,15 +219,20 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
}
if
(
text
)
{
if
(
'text'
in
updateValue
&&
updateValue
.
text
)
{
updateValue
.
text
.
content
+=
text
;
return
{
...
item
,
value
:
[
...
item
.
value
.
slice
(
0
,
updateIndex
),
updateValue
,
...
item
.
value
.
slice
(
updateIndex
+
1
)
]
};
return
replaceUpdateValue
({
...
updateValue
,
text
:
{
...
updateValue
.
text
,
content
:
updateValue
.
text
.
content
+
text
}
});
}
else
if
(
'reasoning'
in
updateValue
&&
updateValue
.
reasoning
)
{
return
replaceUpdateValue
({
...
updateValue
,
text
:
{
content
:
text
}
});
}
else
{
const
newValue
:
AIChatItemValueItemType
=
{
text
:
{
...
...
@@ -258,7 +277,7 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
}
const
chatItemDataId
=
getNanoid
(
24
);
le
t
newChatList
:
HelperBotChatItemSiteType
[]
=
[
cons
t
newChatList
:
HelperBotChatItemSiteType
[]
=
[
...
chatRecords
,
// 用户消息
{
...
...
projects/app/src/components/core/chat/components/AIResponseBox/index.tsx
View file @
05254ce0
...
...
@@ -43,7 +43,7 @@ const AIResponseBox = ({
const
responseBlocks
:
React
.
ReactNode
[]
=
[];
if
(
'reasoning'
in
value
&&
value
.
reasoning
)
{
if
(
'reasoning'
in
value
&&
value
.
reasoning
&&
!
value
.
hideReason
)
{
responseBlocks
.
push
(
<
RenderReasoningContent
key=
"reasoning"
...
...
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