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
Show whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
402 additions
and
112 deletions
+402
-112
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
+18
-9
packages/global/core/chat/type.ts
+1
-0
packages/global/core/chat/utils.ts
+8
-10
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
+3
-4
packages/service/core/workflow/dispatch/ai/agent/adapter/eventMapper.ts
+16
-11
packages/service/core/workflow/dispatch/ai/agent/index.ts
+8
-13
packages/service/core/workflow/dispatch/ai/agent/piAgent/index.ts
+5
-7
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
+5
-4
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
+17
-12
projects/app/src/components/core/chat/HelperBot/components/AIItem.tsx
+24
-18
projects/app/src/components/core/chat/HelperBot/index.tsx
+35
-16
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 更新说明'
...
@@ -15,3 +15,4 @@ description: 'FastGPT V4.14.21 更新说明'
1. 工作流默认选中模型未回传表单值,导致看到的模型和实际运行模型不一致。
1. 工作流默认选中模型未回传表单值,导致看到的模型和实际运行模型不一致。
2. 工作流自动保存时,存在边丢失风险。
2. 工作流自动保存时,存在边丢失风险。
3. admin 修改系统通知弹窗时候报错。
3. admin 修改系统通知弹窗时候报错。
4. 工作流混用思考/非思考模型,可能出现独立 reason 字段上下文,导致模型调用报错。
document/data/doc-last-modified.json
View file @
05254ce0
...
@@ -276,7 +276,7 @@
...
@@ -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.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-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/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.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/40.mdx"
:
"2026-04-26T21:08:47+08:00"
,
"content/self-host/upgrading/outdated/41.en.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
{
ObjectIdSchema
}
from
'../../../common/type/mongo'
;
import
z
from
'zod'
;
import
z
from
'zod'
;
import
{
ChatRoleEnum
}
from
'../constants'
;
import
{
ChatRoleEnum
}
from
'../constants'
;
import
{
UserChatItemSchema
,
SystemChatItemSchema
,
ToolModuleResponseItemSchema
}
from
'../type'
;
import
{
UserChatItemSchema
,
SystemChatItemSchema
}
from
'../type'
;
import
{
UserInputInteractiveSchema
}
from
'../../workflow/template/system/interactive/type'
;
import
{
UserInputInteractiveSchema
}
from
'../../workflow/template/system/interactive/type'
;
export
enum
HelperBotTypeEnum
{
export
enum
HelperBotTypeEnum
{
...
@@ -21,18 +21,27 @@ export const HelperBotChatSchema = z.object({
...
@@ -21,18 +21,27 @@ export const HelperBotChatSchema = z.object({
});
});
export
type
HelperBotChatType
=
z
.
infer
<
typeof
HelperBotChatSchema
>
;
export
type
HelperBotChatType
=
z
.
infer
<
typeof
HelperBotChatSchema
>
;
// AI schema
const
AIChatContentValueSchema
=
z
export
const
AIChatItemValueItemSchema
=
z
.
union
([
.
object
({
z
.
object
({
text
:
z
text
:
z
.
object
({
.
object
({
content
:
z
.
string
()
content
:
z
.
string
()
})
})
}
),
.
optional
(
),
z
.
object
({
reasoning
:
z
reasoning
:
z
.
object
({
.
object
({
content
:
z
.
string
()
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
([
AIChatContentValueSchema
,
z
.
object
({
z
.
object
({
collectionForm
:
UserInputInteractiveSchema
collectionForm
:
UserInputInteractiveSchema
}),
}),
...
...
packages/global/core/chat/type.ts
View file @
05254ce0
...
@@ -225,6 +225,7 @@ export const AIChatItemValueSchema = z.object({
...
@@ -225,6 +225,7 @@ export const AIChatItemValueSchema = z.object({
agentStopGate
:
AgentLoopStopGateSchema
.
nullish
(),
agentStopGate
:
AgentLoopStopGateSchema
.
nullish
(),
contextCheckpoint
:
ContextCheckpointValueSchema
.
nullish
(),
contextCheckpoint
:
ContextCheckpointValueSchema
.
nullish
(),
tool
:
ToolModuleResponseItemSchema
.
nullish
().
meta
({
deprecated
:
true
}),
tool
:
ToolModuleResponseItemSchema
.
nullish
().
meta
({
deprecated
:
true
}),
hideReason
:
z
.
boolean
().
optional
(),
hideInUI
:
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
...
@@ -152,27 +152,25 @@ export const removeAIResponseCite = <T extends AIChatItemValueItemType[] | strin
return
removeDatasetCiteText
(
value
,
false
)
as
T
;
return
removeDatasetCiteText
(
value
,
false
)
as
T
;
}
}
return
value
.
map
<
AIChatItemValueItemType
>
((
item
)
=>
{
return
value
.
map
<
AIChatItemValueItemType
>
((
item
)
=>
({
if
(
item
.
text
?.
content
)
{
return
{
...
item
,
...
item
,
...(
item
.
text
?.
content
?
{
text
:
{
text
:
{
...
item
.
text
,
...
item
.
text
,
content
:
removeDatasetCiteText
(
item
.
text
.
content
,
false
)
content
:
removeDatasetCiteText
(
item
.
text
.
content
,
false
)
}
}
};
}
}
if
(
item
.
reasoning
?.
content
)
{
:
{}),
return
{
...(
item
.
reasoning
?.
content
...
item
,
?
{
reasoning
:
{
reasoning
:
{
...
item
.
reasoning
,
...
item
.
reasoning
,
content
:
removeDatasetCiteText
(
item
.
reasoning
.
content
,
false
)
content
:
removeDatasetCiteText
(
item
.
reasoning
.
content
,
false
)
}
}
};
}
}
return
item
;
:
{})
})
as
T
;
})
)
as
T
;
};
};
export
const
removeEmptyUserInput
=
(
input
?:
UserChatItemValueItemType
[])
=>
{
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', () => {
...
@@ -115,6 +115,27 @@ describe('helperChats2GPTMessages', () => {
expect
(
result
[
0
].
content
).
toBe
(
'Hello, how can I help?'
);
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'
,
()
=>
{
it
(
'should concat multiple AI text values'
,
()
=>
{
const
messages
=
[
const
messages
=
[
{
{
...
...
packages/global/test/core/chat/helperBot/type.test.ts
View file @
05254ce0
...
@@ -47,6 +47,22 @@ describe('AIChatItemValueItemSchema', () => {
...
@@ -47,6 +47,22 @@ describe('AIChatItemValueItemSchema', () => {
expect
(
result
.
success
).
toBe
(
true
);
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'
,
()
=>
{
it
(
'should validate planHint'
,
()
=>
{
const
result
=
AIChatItemValueItemSchema
.
safeParse
({
const
result
=
AIChatItemValueItemSchema
.
safeParse
({
planHint
:
{
type
:
'generation'
}
planHint
:
{
type
:
'generation'
}
...
...
packages/service/core/ai/utils.ts
View file @
05254ce0
...
@@ -40,7 +40,13 @@ export const computedTemperature = ({
...
@@ -40,7 +40,13 @@ export const computedTemperature = ({
};
};
// LLM utils
// 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
]
=>
{
export
const
parseReasoningContent
=
(
text
:
string
):
[
string
,
string
]
=>
{
const
regex
=
/<think>
([\s\S]
*
?)
<
\/
think>/
;
const
regex
=
/<think>
([\s\S]
*
?)
<
\/
think>/
;
const
match
=
text
.
match
(
regex
);
const
match
=
text
.
match
(
regex
);
...
@@ -52,7 +58,9 @@ export const parseReasoningContent = (text: string): [string, string] => {
...
@@ -52,7 +58,9 @@ export const parseReasoningContent = (text: string): [string, string] => {
const
thinkContent
=
match
[
1
].
trim
();
const
thinkContent
=
match
[
1
].
trim
();
// Add answer (remaining text after think tag)
// 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
];
return
[
thinkContent
,
answerContent
];
};
};
...
@@ -75,6 +83,29 @@ export const parseLLMStreamResponse = () => {
...
@@ -75,6 +83,29 @@ export const parseLLMStreamResponse = () => {
let
buffer_reasoningContent
=
''
;
let
buffer_reasoningContent
=
''
;
let
buffer_content
=
''
;
let
buffer_content
=
''
;
let
error
:
any
=
undefined
;
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>,如果接口已经解析了,则不再解析。
parseThinkTag - 只控制是否主动解析 <think></think>,如果接口已经解析了,则不再解析。
...
@@ -117,7 +148,7 @@ export const parseLLMStreamResponse = () => {
...
@@ -117,7 +148,7 @@ export const parseLLMStreamResponse = () => {
const
isStreamEnd
=
!!
buffer_finishReason
;
const
isStreamEnd
=
!!
buffer_finishReason
;
// Parse think
// Parse think
const
{
reasoningContent
:
parsedThinkReasoningContent
,
content
:
p
arsedThinkContent
}
=
const
{
reasoningContent
:
parsedThinkReasoningContent
,
content
:
rawP
arsedThinkContent
}
=
(()
=>
{
(()
=>
{
if
(
reasoningContent
||
!
parseThinkTag
)
{
if
(
reasoningContent
||
!
parseThinkTag
)
{
isInThinkTag
=
false
;
isInThinkTag
=
false
;
...
@@ -234,6 +265,11 @@ export const parseLLMStreamResponse = () => {
...
@@ -234,6 +265,11 @@ export const parseLLMStreamResponse = () => {
};
};
})();
})();
const
parsedThinkContent
=
normalizeContentBoundary
({
reasoningContent
:
parsedThinkReasoningContent
,
content
:
rawParsedThinkContent
});
// Parse datset cite
// Parse datset cite
if
(
retainDatasetCite
)
{
if
(
retainDatasetCite
)
{
return
{
return
{
...
...
packages/service/core/chat/HelperBot/dispatch/utils.ts
View file @
05254ce0
...
@@ -20,15 +20,14 @@ export const formatAIResponse = ({
...
@@ -20,15 +20,14 @@ export const formatAIResponse = ({
}):
AIChatItemValueItemType
[]
=>
{
}):
AIChatItemValueItemType
[]
=>
{
const
result
:
AIChatItemValueItemType
[]
=
[];
const
result
:
AIChatItemValueItemType
[]
=
[];
if
(
reasoning
)
{
result
.
push
({
result
.
push
({
...(
reasoning
?
{
reasoning
:
{
reasoning
:
{
content
:
reasoning
content
:
reasoning
}
}
});
}
}
:
{}),
result
.
push
({
text
:
{
text
:
{
content
:
text
content
:
text
}
}
...
...
packages/service/core/workflow/dispatch/ai/agent/adapter/eventMapper.ts
View file @
05254ce0
...
@@ -192,6 +192,10 @@ export const createWorkflowAgentLoopEventMapper = ({
...
@@ -192,6 +192,10 @@ export const createWorkflowAgentLoopEventMapper = ({
assistantText
?:
string
;
assistantText
?:
string
;
reasoningText
?:
string
;
reasoningText
?:
string
;
})
=>
{
})
=>
{
// 没有可见 assistantText 时,不在 request end 阶段回填 reasoning 到已有 tool。
// reasoning 的归属需要按流式顺序附着到后续 content/tool,避免误挂到上一轮工具。
if
(
!
assistantText
)
return
;
const
runtimeToolCalls
=
toolCalls
.
filter
((
call
)
=>
{
const
runtimeToolCalls
=
toolCalls
.
filter
((
call
)
=>
{
const
functionName
=
call
.
function
.
name
;
const
functionName
=
call
.
function
.
name
;
return
(
return
(
...
@@ -201,21 +205,22 @@ export const createWorkflowAgentLoopEventMapper = ({
...
@@ -201,21 +205,22 @@ export const createWorkflowAgentLoopEventMapper = ({
!
isInternalTool
(
functionName
,
internalToolNames
)
!
isInternalTool
(
functionName
,
internalToolNames
)
);
);
});
});
if
(
!
runtimeToolCalls
.
length
||
(
!
assistantText
&&
!
reasoningText
)
)
return
;
if
(
!
runtimeToolCalls
.
length
)
return
;
const
runtimeToolCallIds
=
new
Set
(
runtimeToolCalls
.
map
((
call
)
=>
call
.
id
));
const
runtimeToolCallIds
=
new
Set
(
runtimeToolCalls
.
map
((
call
)
=>
call
.
id
));
const
existingIndexes
=
assistantResponses
const
existingIndex
=
assistantResponses
.
findIndex
((
item
)
=>
.
map
((
item
,
index
)
=>
item
.
tools
?.
some
((
tool
)
=>
runtimeToolCallIds
.
has
(
tool
.
id
))
item
.
tools
?.
some
((
tool
)
=>
runtimeToolCallIds
.
has
(
tool
.
id
))
?
index
:
-
1
);
)
const
insertIndex
=
existingIndex
>=
0
?
existingIndex
:
assistantResponses
.
length
;
.
filter
((
index
)
=>
index
>=
0
);
const
insertIndex
=
existingIndexes
.
length
?
Math
.
min
(...
existingIndexes
)
:
assistantResponses
.
length
;
const
assistantValue
:
AIChatItemValueItemType
=
{
const
assistantValue
:
AIChatItemValueItemType
=
{
...(
assistantText
?
{
text
:
{
content
:
assistantText
}
}
:
{}),
text
:
{
content
:
assistantText
},
...(
reasoningText
?
{
reasoning
:
{
content
:
reasoningText
}
}
:
{})
...(
reasoningText
?
{
reasoning
:
{
content
:
reasoningText
},
...(
!
showReasoning
?
{
hideReason
:
true
}
:
{})
}
:
{})
};
};
assistantResponses
.
splice
(
insertIndex
,
0
,
assistantValue
);
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
...
@@ -332,26 +332,21 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
:
result
.
status
===
'aborted'
:
result
.
status
===
'aborted'
?
i18nT
(
'chat:completion_finish_error'
)
?
i18nT
(
'chat:completion_finish_error'
)
:
undefined
;
:
undefined
;
const
reasoningValue
=
result
.
reasoningText
if
(
result
.
reasoningText
)
{
?
{
assistantResponses
.
push
({
reasoning
:
{
reasoning
:
{
content
:
result
.
reasoningText
content
:
result
.
reasoningText
},
},
...(
aiChatReasoning
===
false
?
{
hideInUI
:
true
}
:
{})
...(
aiChatReasoning
===
false
?
{
hideReason
:
true
}
:
{})
});
}
}
:
{};
const
finalText
=
result
.
answerText
||
errorText
;
if
(
result
.
answerText
)
{
if
(
finalText
)
{
assistantResponses
.
push
({
text
:
{
content
:
result
.
answerText
}
});
}
else
if
(
errorText
)
{
assistantResponses
.
push
({
assistantResponses
.
push
({
...
reasoningValue
,
text
:
{
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<
...
@@ -80,19 +80,17 @@ export const dispatchPiAgent = async (props: DispatchAgentModuleProps): Promise<
const
appendFinalAssistantResponses
=
()
=>
{
const
appendFinalAssistantResponses
=
()
=>
{
const
reasoningText
=
piRuntime
?.
getReasoningText
()
||
''
;
const
reasoningText
=
piRuntime
?.
getReasoningText
()
||
''
;
const
answerText
=
piRuntime
?.
getAnswerText
()
||
''
;
const
answerText
=
piRuntime
?.
getAnswerText
()
||
''
;
const
showReasoning
=
aiChatReasoning
!==
false
;
if
(
reasoning
Text
)
{
if
(
answer
Text
)
{
assistantResponses
.
push
({
assistantResponses
.
push
({
...(
reasoningText
?
{
reasoning
:
{
reasoning
:
{
content
:
reasoningText
content
:
reasoningText
},
},
...(
!
showReasoning
?
{
hideInUI
:
true
}
:
{})
...(
aiChatReasoning
===
false
?
{
hideReason
:
true
}
:
{})
});
}
}
:
{}),
if
(
answerText
)
{
assistantResponses
.
push
({
text
:
{
text
:
{
content
:
answerText
content
:
answerText
}
}
...
...
packages/service/core/workflow/dispatch/ai/classifyQuestion.ts
View file @
05254ce0
...
@@ -146,7 +146,7 @@ const completions = async ({
...
@@ -146,7 +146,7 @@ const completions = async ({
body
:
{
body
:
{
model
:
cqModel
.
model
,
model
:
cqModel
.
model
,
temperature
:
0.01
,
temperature
:
0.01
,
messages
:
chats2GPTMessages
({
messages
,
reserveId
:
false
}),
messages
:
chats2GPTMessages
({
messages
,
reserveId
:
false
,
reserveReason
:
false
}),
stream
:
true
stream
:
true
},
},
userKey
:
externalProvider
.
openaiAccount
userKey
:
externalProvider
.
openaiAccount
...
...
packages/service/core/workflow/dispatch/ai/extract.ts
View file @
05254ce0
...
@@ -209,7 +209,11 @@ const toolChoice = async (props: ActionProps) => {
...
@@ -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
({
const
filterMessages
=
await
filterGPTMessageByMaxContext
({
messages
:
adaptMessages
,
messages
:
adaptMessages
,
maxContext
:
extractModel
.
maxContext
maxContext
:
extractModel
.
maxContext
...
@@ -317,7 +321,7 @@ const completions = async (props: ActionProps) => {
...
@@ -317,7 +321,7 @@ const completions = async (props: ActionProps) => {
body
:
{
body
:
{
model
:
extractModel
.
model
,
model
:
extractModel
.
model
,
temperature
:
0.01
,
temperature
:
0.01
,
messages
:
chats2GPTMessages
({
messages
,
reserveId
:
false
}),
messages
:
chats2GPTMessages
({
messages
,
reserveId
:
false
,
reserveReason
:
false
}),
stream
:
true
stream
:
true
},
},
userKey
:
externalProvider
.
openaiAccount
userKey
:
externalProvider
.
openaiAccount
...
...
packages/service/core/workflow/dispatch/index.ts
View file @
05254ce0
...
@@ -1212,15 +1212,16 @@ export class WorkflowQueue {
...
@@ -1212,15 +1212,16 @@ export class WorkflowQueue {
if
(
assistantResponses
)
{
if
(
assistantResponses
)
{
this
.
chatAssistantResponse
=
this
.
chatAssistantResponse
.
concat
(
assistantResponses
);
this
.
chatAssistantResponse
=
this
.
chatAssistantResponse
.
concat
(
assistantResponses
);
}
else
{
}
else
{
if
(
reasoningText
)
{
// reasoning 不能独立落历史;只有存在可见文本时才附着保存。
if
(
answerText
)
{
this
.
chatAssistantResponse
.
push
({
this
.
chatAssistantResponse
.
push
({
...(
reasoningText
?
{
reasoning
:
{
reasoning
:
{
content
:
reasoningText
content
:
reasoningText
}
}
});
}
}
if
(
answerText
)
{
:
{}),
this
.
chatAssistantResponse
.
push
({
text
:
{
text
:
{
content
:
answerText
content
:
answerText
}
}
...
...
packages/service/test/core/ai/utils.test.ts
View file @
05254ce0
...
@@ -85,6 +85,14 @@ describe('parseReasoningContent', () => {
...
@@ -85,6 +85,14 @@ describe('parseReasoningContent', () => {
expect
(
parseReasoningContent
(
'<think>reasoning</think>'
)).
toEqual
([
'reasoning'
,
''
]);
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'
,
()
=>
{
it
(
'should handle multiline think content'
,
()
=>
{
expect
(
parseReasoningContent
(
'<think>line1\nline2</think>answer'
)).
toEqual
([
expect
(
parseReasoningContent
(
'<think>line1\nline2</think>answer'
)).
toEqual
([
'line1\nline2'
,
'line1\nline2'
,
...
@@ -191,6 +199,22 @@ describe('parseLLMStreamResponse', () => {
...
@@ -191,6 +199,22 @@ describe('parseLLMStreamResponse', () => {
correct
:
{
answer
:
'你好1你好2你好3'
,
reasoning
:
'这是思考过程'
}
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
:
[
data
:
[
{
content
:
'<think>这是'
},
{
content
:
'<think>这是'
},
{
content
:
'思考'
},
{
content
:
'思考'
},
...
...
packages/service/test/core/workflow/dispatch/ai/agent/adapter/eventMapper.test.ts
View file @
05254ce0
...
@@ -97,6 +97,81 @@ describe('createWorkflowAgentLoopEventMapper', () => {
...
@@ -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'
,
()
=>
{
it
(
'filters internal tool calls and streams runtime tool lifecycle events'
,
()
=>
{
const
workflowStreamResponse
=
vi
.
fn
();
const
workflowStreamResponse
=
vi
.
fn
();
const
mapper
=
createWorkflowAgentLoopEventMapper
({
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', () => {
...
@@ -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', () => {
...
@@ -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 = ({
...
@@ -456,17 +456,24 @@ const ChatBox = ({
};
};
}
}
if
(
event
===
SseResponseEventEnum
.
answer
||
event
===
SseResponseEventEnum
.
fastAnswer
)
{
if
(
event
===
SseResponseEventEnum
.
answer
||
event
===
SseResponseEventEnum
.
fastAnswer
)
{
if
(
reasoningText
)
{
const
replaceUpdateValue
=
(
nextValue
:
AIChatItemValueItemType
)
=>
({
if
(
updateValue
?.
reasoning
)
{
updateValue
.
reasoning
.
content
+=
reasoningText
;
return
{
...
item
,
...
item
,
value
:
[
value
:
[
...
item
.
value
.
slice
(
0
,
updateIndex
),
...
item
.
value
.
slice
(
0
,
updateIndex
),
update
Value
,
next
Value
,
...
item
.
value
.
slice
(
updateIndex
+
1
)
...
item
.
value
.
slice
(
updateIndex
+
1
)
]
]
});
if
(
reasoningText
)
{
if
(
updateValue
?.
reasoning
)
{
updateValue
.
reasoning
.
content
+=
reasoningText
;
return
replaceUpdateValue
(
updateValue
);
}
else
if
(
updateValue
?.
text
&&
!
updateValue
.
text
.
content
)
{
updateValue
.
reasoning
=
{
content
:
reasoningText
};
};
return
replaceUpdateValue
(
updateValue
);
}
else
{
}
else
{
const
val
:
AIChatItemValueItemType
=
{
const
val
:
AIChatItemValueItemType
=
{
id
:
responseValueId
,
id
:
responseValueId
,
...
@@ -483,14 +490,12 @@ const ChatBox = ({
...
@@ -483,14 +490,12 @@ const ChatBox = ({
if
(
text
)
{
if
(
text
)
{
if
(
updateValue
?.
text
)
{
if
(
updateValue
?.
text
)
{
updateValue
.
text
.
content
+=
text
;
updateValue
.
text
.
content
+=
text
;
return
{
return
replaceUpdateValue
(
updateValue
);
...
item
,
}
else
if
(
updateValue
?.
reasoning
)
{
value
:
[
updateValue
.
text
=
{
...
item
.
value
.
slice
(
0
,
updateIndex
),
content
:
text
updateValue
,
...
item
.
value
.
slice
(
updateIndex
+
1
)
]
};
};
return
replaceUpdateValue
(
updateValue
);
}
else
{
}
else
{
const
newValue
:
AIChatItemValueItemType
=
{
const
newValue
:
AIChatItemValueItemType
=
{
id
:
responseValueId
,
id
:
responseValueId
,
...
...
projects/app/src/components/core/chat/HelperBot/components/AIItem.tsx
View file @
05254ce0
...
@@ -173,6 +173,14 @@ const AIItem = ({
...
@@ -173,6 +173,14 @@ const AIItem = ({
onSubmitCollectionForm
:
(
formData
:
string
)
=>
void
;
onSubmitCollectionForm
:
(
formData
:
string
)
=>
void
;
})
=>
{
})
=>
{
const
{
t
}
=
useTranslation
();
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
(
return
(
<
Box
<
Box
_hover=
{
{
_hover=
{
{
...
@@ -192,8 +200,7 @@ const AIItem = ({
...
@@ -192,8 +200,7 @@ const AIItem = ({
color=
{
'myGray.900'
}
color=
{
'myGray.900'
}
bg=
{
'myGray.100'
}
bg=
{
'myGray.100'
}
>
>
{
chat
.
value
.
length
===
1
&&
{
isWaitingForResponse
?
(
(
!
(
'text'
in
chat
.
value
[
0
])
||
!
chat
.
value
[
0
].
text
?.
content
)
?
(
<
RenderText
showAnimation=
{
true
}
text=
{
t
(
'chat:chat.waiting_for_response'
)
}
/>
<
RenderText
showAnimation=
{
true
}
text=
{
t
(
'chat:chat.waiting_for_response'
)
}
/>
)
:
(
)
:
(
<>
<>
...
@@ -203,35 +210,34 @@ const AIItem = ({
...
@@ -203,35 +210,34 @@ const AIItem = ({
<
RenderText
key=
{
i
}
showAnimation=
{
false
}
text=
{
t
(
'chat:plan_check_tip'
)
}
/>
<
RenderText
key=
{
i
}
showAnimation=
{
false
}
text=
{
t
(
'chat:plan_check_tip'
)
}
/>
);
);
}
}
if
(
'
text'
in
value
&&
value
.
text
)
{
if
(
'
collectionForm'
in
value
&&
value
.
collectionForm
)
{
return
(
return
(
<
Render
Text
<
Render
CollectionForm
key=
{
i
}
key=
{
i
}
showAnimation=
{
isChatting
&&
isLastChild
}
isLastValue=
{
isLastChild
&&
i
===
chat
.
value
.
length
-
1
}
text=
{
value
.
text
.
content
}
collectionForm=
{
value
.
collectionForm
}
onSubmit=
{
onSubmitCollectionForm
}
/>
/>
);
);
}
}
if
(
'reasoning'
in
value
&&
value
.
reasoning
)
{
return
(
return
(
<
React
.
Fragment
key=
{
i
}
>
{
'reasoning'
in
value
&&
value
.
reasoning
&&
!
value
.
hideReason
&&
(
<
RenderResoningContent
<
RenderResoningContent
key=
{
i
}
isChatting=
{
isChatting
}
isChatting=
{
isChatting
}
isLastResponseValue=
{
isLastChild
}
isLastResponseValue=
{
isLastChild
}
content=
{
value
.
reasoning
.
content
}
content=
{
value
.
reasoning
.
content
}
/>
/>
);
)
}
}
{
'text'
in
value
&&
value
.
text
&&
(
if
(
'collectionForm'
in
value
&&
value
.
collectionForm
)
{
<
RenderText
return
(
showAnimation=
{
isChatting
&&
isLastChild
}
<
RenderCollectionForm
text=
{
value
.
text
.
content
}
key=
{
i
}
isLastValue=
{
isLastChild
&&
i
===
chat
.
value
.
length
-
1
}
collectionForm=
{
value
.
collectionForm
}
onSubmit=
{
onSubmitCollectionForm
}
/>
/>
)
}
</
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
...
@@ -180,17 +180,31 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
}
}
if
(
event
===
SseResponseEventEnum
.
answer
||
event
===
SseResponseEventEnum
.
fastAnswer
)
{
if
(
event
===
SseResponseEventEnum
.
answer
||
event
===
SseResponseEventEnum
.
fastAnswer
)
{
if
(
reasoningText
)
{
const
replaceUpdateValue
=
(
nextValue
:
AIChatItemValueItemType
)
=>
({
if
(
'reasoning'
in
updateValue
&&
updateValue
.
reasoning
)
{
updateValue
.
reasoning
.
content
+=
reasoningText
;
return
{
...
item
,
...
item
,
value
:
[
value
:
[
...
item
.
value
.
slice
(
0
,
updateIndex
),
...
item
.
value
.
slice
(
0
,
updateIndex
),
update
Value
,
next
Value
,
...
item
.
value
.
slice
(
updateIndex
+
1
)
...
item
.
value
.
slice
(
updateIndex
+
1
)
]
]
};
});
if
(
reasoningText
)
{
if
(
'reasoning'
in
updateValue
&&
updateValue
.
reasoning
)
{
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
{
}
else
{
const
val
:
AIChatItemValueItemType
=
{
const
val
:
AIChatItemValueItemType
=
{
reasoning
:
{
reasoning
:
{
...
@@ -205,15 +219,20 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
...
@@ -205,15 +219,20 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
}
}
if
(
text
)
{
if
(
text
)
{
if
(
'text'
in
updateValue
&&
updateValue
.
text
)
{
if
(
'text'
in
updateValue
&&
updateValue
.
text
)
{
updateValue
.
text
.
content
+=
text
;
return
replaceUpdateValue
({
return
{
...
updateValue
,
...
item
,
text
:
{
value
:
[
...
updateValue
.
text
,
...
item
.
value
.
slice
(
0
,
updateIndex
),
content
:
updateValue
.
text
.
content
+
text
updateValue
,
}
...
item
.
value
.
slice
(
updateIndex
+
1
)
});
]
}
else
if
(
'reasoning'
in
updateValue
&&
updateValue
.
reasoning
)
{
};
return
replaceUpdateValue
({
...
updateValue
,
text
:
{
content
:
text
}
});
}
else
{
}
else
{
const
newValue
:
AIChatItemValueItemType
=
{
const
newValue
:
AIChatItemValueItemType
=
{
text
:
{
text
:
{
...
@@ -258,7 +277,7 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
...
@@ -258,7 +277,7 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
}
}
const
chatItemDataId
=
getNanoid
(
24
);
const
chatItemDataId
=
getNanoid
(
24
);
le
t
newChatList
:
HelperBotChatItemSiteType
[]
=
[
cons
t
newChatList
:
HelperBotChatItemSiteType
[]
=
[
...
chatRecords
,
...
chatRecords
,
// 用户消息
// 用户消息
{
{
...
...
projects/app/src/components/core/chat/components/AIResponseBox/index.tsx
View file @
05254ce0
...
@@ -43,7 +43,7 @@ const AIResponseBox = ({
...
@@ -43,7 +43,7 @@ const AIResponseBox = ({
const
responseBlocks
:
React
.
ReactNode
[]
=
[];
const
responseBlocks
:
React
.
ReactNode
[]
=
[];
if
(
'reasoning'
in
value
&&
value
.
reasoning
)
{
if
(
'reasoning'
in
value
&&
value
.
reasoning
&&
!
value
.
hideReason
)
{
responseBlocks
.
push
(
responseBlocks
.
push
(
<
RenderReasoningContent
<
RenderReasoningContent
key=
"reasoning"
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