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
e7fa0c9f
authored
Jun 18, 2026
by
YeYuheng
Committed by
GitHub
Jun 18, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: route helper bot generation through pro (#7137)
parent
9d5a8777
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
76 additions
and
1262 deletions
+76
-1262
packages/global/core/chat/helperBot/topAgent/type.ts
+11
-0
packages/global/openapi/core/chat/helperBot/api.ts
+22
-23
packages/global/openapi/core/chat/helperBot/index.ts
+27
-27
packages/service/core/chat/HelperBot/dispatch/index.ts
+0
-6
packages/service/core/chat/HelperBot/dispatch/topAgent/index.ts
+0
-295
packages/service/core/chat/HelperBot/dispatch/topAgent/prompt.ts
+0
-0
packages/service/core/chat/HelperBot/dispatch/topAgent/type.ts
+0
-72
packages/service/core/chat/HelperBot/dispatch/topAgent/utils.ts
+0
-223
packages/service/core/chat/HelperBot/dispatch/type.ts
+0
-59
packages/service/core/chat/HelperBot/dispatch/utils.ts
+0
-49
packages/service/test/core/chat/topAgentDispatch.test.ts
+0
-300
packages/service/test/core/chat/topAgentUtils.test.ts
+0
-61
pro
+1
-1
projects/app/src/components/core/chat/ChatContainer/type.ts
+1
-1
projects/app/src/components/core/chat/HelperBot/components/AIItem.tsx
+7
-4
projects/app/src/components/core/chat/HelperBot/context.tsx
+4
-2
projects/app/src/components/core/chat/HelperBot/index.tsx
+1
-1
projects/app/src/pages/api/core/chat/helperBot/completions.ts
+0
-136
projects/app/src/web/common/api/fetch.ts
+2
-2
No files found.
packages/global/core/chat/helperBot/topAgent/type.ts
View file @
e7fa0c9f
import
z
from
'zod'
;
import
z
from
'zod'
;
import
{
SelectedDatasetSchema
}
from
'../../../workflow/type/io'
;
// TopAgent 参数配置
// TopAgent 参数配置
export
const
topAgentParamsSchema
=
z
.
object
({
export
const
topAgentParamsSchema
=
z
.
object
({
...
@@ -11,3 +12,13 @@ export const topAgentParamsSchema = z.object({
...
@@ -11,3 +12,13 @@ export const topAgentParamsSchema = z.object({
enableSandbox
:
z
.
boolean
().
nullish
()
enableSandbox
:
z
.
boolean
().
nullish
()
});
});
export
type
TopAgentParamsType
=
z
.
infer
<
typeof
topAgentParamsSchema
>
;
export
type
TopAgentParamsType
=
z
.
infer
<
typeof
topAgentParamsSchema
>
;
export
const
TopAgentFormDataSchema
=
z
.
object
({
systemPrompt
:
z
.
string
().
optional
(),
tools
:
z
.
array
(
z
.
string
()).
optional
().
default
([]),
datasets
:
z
.
array
(
SelectedDatasetSchema
).
optional
().
default
([]),
fileUploadEnabled
:
z
.
boolean
().
optional
().
default
(
false
),
enableSandboxEnabled
:
z
.
boolean
().
optional
().
default
(
false
),
executionPlan
:
z
.
any
().
optional
()
});
export
type
TopAgentFormDataType
=
z
.
infer
<
typeof
TopAgentFormDataSchema
>
;
packages/global/openapi/core/chat/helperBot/api.ts
View file @
e7fa0c9f
import
{
PaginationResponseSchema
}
from
'../../../api'
;
import
{
z
}
from
'zod'
;
import
{
PaginationSchema
}
from
'../../../api'
;
import
{
PaginationResponseSchema
,
PaginationSchema
}
from
'../../../api'
;
import
{
ChatFileTypeEnum
}
from
'../../../../core/chat/constants'
;
import
{
import
{
HelperBotChatItemSiteSchema
,
HelperBotChatItemSiteSchema
,
HelperBotTypeEnum
,
HelperBotTypeEnum
,
HelperBotTypeEnumSchema
HelperBotTypeEnumSchema
}
from
'../../../../core/chat/helperBot/type'
;
}
from
'../../../../core/chat/helperBot/type'
;
import
{
topAgentParamsSchema
}
from
'../../../../core/chat/helperBot/topAgent/type'
;
import
{
topAgentParamsSchema
}
from
'../../../../core/chat/helperBot/topAgent/type'
;
import
{
z
}
from
'zod'
;
import
{
ChatFileTypeEnum
}
from
'../../../../core/chat/constants'
;
export
const
HelperBotCompletionsParamsSchema
=
z
.
object
({
chatId
:
z
.
string
(),
chatItemId
:
z
.
string
(),
query
:
z
.
string
(),
files
:
z
.
array
(
z
.
object
({
type
:
z
.
enum
(
ChatFileTypeEnum
),
key
:
z
.
string
(),
url
:
z
.
string
().
optional
(),
name
:
z
.
string
()
})
),
metadata
:
z
.
object
({
type
:
z
.
literal
(
HelperBotTypeEnum
.
topAgent
),
data
:
topAgentParamsSchema
})
});
export
type
HelperBotCompletionsParamsType
=
z
.
infer
<
typeof
HelperBotCompletionsParamsSchema
>
;
// 分页获取记录
// 分页获取记录
export
const
GetHelperBotChatRecordsParamsSchema
=
PaginationSchema
.
extend
({
export
const
GetHelperBotChatRecordsParamsSchema
=
PaginationSchema
.
extend
({
...
@@ -45,22 +63,3 @@ export const GetHelperBotFilePreviewParamsSchema = z.object({
...
@@ -45,22 +63,3 @@ export const GetHelperBotFilePreviewParamsSchema = z.object({
});
});
export
type
GetHelperBotFilePreviewParamsType
=
z
.
infer
<
typeof
GetHelperBotFilePreviewParamsSchema
>
;
export
type
GetHelperBotFilePreviewParamsType
=
z
.
infer
<
typeof
GetHelperBotFilePreviewParamsSchema
>
;
export
const
GetHelperBotFilePreviewResponseSchema
=
z
.
string
();
export
const
GetHelperBotFilePreviewResponseSchema
=
z
.
string
();
export
const
HelperBotCompletionsParamsSchema
=
z
.
object
({
chatId
:
z
.
string
(),
chatItemId
:
z
.
string
(),
query
:
z
.
string
(),
files
:
z
.
array
(
z
.
object
({
type
:
z
.
enum
(
ChatFileTypeEnum
),
key
:
z
.
string
(),
url
:
z
.
string
().
optional
(),
name
:
z
.
string
()
})
),
metadata
:
z
.
object
({
type
:
z
.
literal
(
HelperBotTypeEnum
.
topAgent
),
data
:
topAgentParamsSchema
})
});
export
type
HelperBotCompletionsParamsType
=
z
.
infer
<
typeof
HelperBotCompletionsParamsSchema
>
;
packages/global/openapi/core/chat/helperBot/index.ts
View file @
e7fa0c9f
import
z
from
'zod'
;
import
z
from
'zod'
;
import
type
{
OpenAPIPath
}
from
'../../../type'
;
import
type
{
OpenAPIPath
}
from
'../../../type'
;
import
{
TagsMap
}
from
'../../../tag'
;
import
{
import
{
HelperBotCompletionsParamsSchema
,
DeleteHelperBotChatParamsSchema
,
DeleteHelperBotChatParamsSchema
,
GetHelperBotChatRecordsParamsSchema
,
GetHelperBotChatRecordsParamsSchema
,
GetHelperBotChatRecordsResponseSchema
,
GetHelperBotChatRecordsResponseSchema
HelperBotCompletionsParamsSchema
}
from
'./api'
;
}
from
'./api'
;
import
{
TagsMap
}
from
'../../../tag'
;
export
const
HelperBotPath
:
OpenAPIPath
=
{
export
const
HelperBotPath
:
OpenAPIPath
=
{
'/proApi/core/chat/helperBot/completions'
:
{
post
:
{
summary
:
'辅助生成统一对话接口'
,
description
:
'辅助生成统一对话接口'
,
tags
:
[
TagsMap
.
helperBot
],
requestBody
:
{
content
:
{
'application/json'
:
{
schema
:
HelperBotCompletionsParamsSchema
}
}
},
responses
:
{
200
:
{
description
:
'成功返回流式处理结果'
,
content
:
{
'application/stream+json'
:
{
schema
:
z
.
any
()
}
}
}
}
}
},
'/core/chat/helperBot/getRecords'
:
{
'/core/chat/helperBot/getRecords'
:
{
get
:
{
get
:
{
summary
:
'分页获取记录'
,
summary
:
'分页获取记录'
,
...
@@ -52,29 +76,5 @@ export const HelperBotPath: OpenAPIPath = {
...
@@ -52,29 +76,5 @@ export const HelperBotPath: OpenAPIPath = {
}
}
}
}
}
}
},
'/core/chat/helperBot/completions'
:
{
post
:
{
summary
:
'辅助助手对话接口'
,
description
:
'辅助助手对话接口'
,
tags
:
[
TagsMap
.
helperBot
],
requestBody
:
{
content
:
{
'application/json'
:
{
schema
:
HelperBotCompletionsParamsSchema
}
}
},
responses
:
{
200
:
{
description
:
'成功返回处理结果'
,
content
:
{
'application/stream+json'
:
{
schema
:
z
.
any
()
}
}
}
}
}
}
}
};
};
packages/service/core/chat/HelperBot/dispatch/index.ts
deleted
100644 → 0
View file @
9d5a8777
import
{
HelperBotTypeEnum
}
from
'@fastgpt/global/core/chat/helperBot/type'
;
import
{
dispatchTopAgent
}
from
'./topAgent'
;
export
const
dispatchMap
=
{
[
HelperBotTypeEnum
.
topAgent
]:
dispatchTopAgent
};
packages/service/core/chat/HelperBot/dispatch/topAgent/index.ts
deleted
100644 → 0
View file @
9d5a8777
import
{
type
HelperBotDispatchParamsType
,
type
HelperBotDispatchResponseType
}
from
'../type'
;
import
{
helperChats2GPTMessages
}
from
'@fastgpt/global/core/chat/helperBot/adaptor'
;
import
{
getPrompt
}
from
'./prompt'
;
import
{
createLLMResponse
}
from
'../../../../ai/llm/request'
;
import
{
getDefaultHelperBotModel
}
from
'../../../../ai/model'
;
import
{
SseResponseEventEnum
}
from
'@fastgpt/global/core/workflow/runtime/constants'
;
import
{
textAdaptGptResponse
}
from
'@fastgpt/global/core/workflow/runtime/utils'
;
import
{
generateResourceList
,
extractResourcesFromPlan
,
buildSystemPrompt
,
buildDisplayText
}
from
'./utils'
;
import
{
TopAgentAnswerSchema
,
TopAgentFormDataSchema
}
from
'./type'
;
import
{
formatAIResponse
}
from
'../utils'
;
import
type
{
TopAgentParamsType
}
from
'@fastgpt/global/core/chat/helperBot/topAgent/type'
;
import
type
{
UserInputInteractive
}
from
'@fastgpt/global/core/workflow/template/system/interactive/type'
;
import
{
getNanoid
}
from
'@fastgpt/global/common/string/tools'
;
import
{
WorkflowIOValueTypeEnum
}
from
'@fastgpt/global/core/workflow/constants'
;
import
{
FlowNodeInputTypeEnum
}
from
'@fastgpt/global/core/workflow/node/constant'
;
import
{
parseJsonArgs
}
from
'../../../../ai/utils'
;
import
{
MongoDataset
}
from
'../../../../dataset/schema'
;
import
{
ObjectIdSchema
}
from
'@fastgpt/global/common/type/mongo'
;
import
type
{
SelectedDatasetType
}
from
'@fastgpt/global/core/workflow/type/io'
;
import
{
getLogger
,
LogCategories
}
from
'../../../../../common/logger'
;
import
{
AGENT_SANDBOX_TOOLSET_ID
}
from
'@fastgpt/global/core/ai/sandbox/tools'
;
export
const
dispatchTopAgent
=
async
(
props
:
HelperBotDispatchParamsType
<
TopAgentParamsType
>
):
Promise
<
HelperBotDispatchResponseType
>
=>
{
const
{
query
,
files
,
data
,
histories
,
workflowResponseWrite
,
user
}
=
props
;
const
modelData
=
getDefaultHelperBotModel
();
if
(
!
modelData
)
{
return
Promise
.
reject
(
'Can not get model data'
);
}
const
usage
=
{
model
:
modelData
.
model
,
inputTokens
:
0
,
outputTokens
:
0
};
const
{
resourceList
}
=
await
generateResourceList
({
teamId
:
user
.
teamId
,
tmbId
:
user
.
tmbId
,
isRoot
:
user
.
isRoot
,
lang
:
user
.
lang
});
const
systemPrompt
=
getPrompt
({
resourceList
,
metadata
:
data
});
const
historyMessages
=
helperChats2GPTMessages
({
messages
:
histories
});
const
conversationMessages
=
[
{
role
:
'system'
as
const
,
content
:
systemPrompt
},
...
historyMessages
,
{
role
:
'user'
as
const
,
content
:
query
}
];
const
llmResponse
=
await
createLLMResponse
({
body
:
{
messages
:
conversationMessages
,
model
:
modelData
,
stream
:
true
},
onReasoning
:
({
text
})
=>
{
workflowResponseWrite
?.({
event
:
SseResponseEventEnum
.
answer
,
data
:
textAdaptGptResponse
({
reasoning_content
:
text
})
});
}
// onStreaming: ({ text }) => {
// workflowResponseWrite?.({
// event: SseResponseEventEnum.answer,
// data: textAdaptGptResponse({ text })
// });
// }
});
usage
.
inputTokens
=
llmResponse
.
usage
.
inputTokens
;
usage
.
outputTokens
=
llmResponse
.
usage
.
outputTokens
;
const
answerText
=
llmResponse
.
answerText
;
const
reasoningText
=
llmResponse
.
reasoningText
;
// console.log('Top agent response:', answerText);
try
{
const
parseAnswer
=
(
text
:
string
)
=>
{
return
TopAgentAnswerSchema
.
safeParseAsync
(
parseJsonArgs
(
text
));
};
let
result
=
await
parseAnswer
(
answerText
);
// console.dir({ label: 'Top agent parsed result', result }, {
// depth: null,
// maxArrayLength: null
// });
if
(
!
result
.
success
)
{
getLogger
(
LogCategories
.
MODULE
.
AI
.
HELPERBOT
).
warn
(
'[Top agent] JSON parse failed, try repair'
,
{
text
:
answerText
}
);
const
repairPrompt
=
`当前查询的用户问题:
${
query
}
\n辅助助手上一次的输出:\n
${
answerText
}
,\nJSON 解析的报错信息:\n
${
result
.
error
}
\n
查看JSON 的报错信息来修正 JSON 格式错误,并仅返回正确的 JSON,确保 JSON 格式正确无误且可以被解析。不要包含任何多余的信息。`
;
const
repairResponse
=
await
createLLMResponse
({
body
:
{
messages
:
[
{
role
:
'system'
as
const
,
content
:
systemPrompt
},
...
historyMessages
,
{
role
:
'user'
as
const
,
content
:
repairPrompt
}
],
model
:
modelData
,
stream
:
true
}
});
usage
.
inputTokens
+=
repairResponse
.
usage
.
inputTokens
;
usage
.
outputTokens
+=
repairResponse
.
usage
.
outputTokens
;
result
=
await
parseAnswer
(
repairResponse
.
answerText
);
console
.
dir
(
{
label
:
'Top agent parsed result'
,
result
},
{
depth
:
null
,
maxArrayLength
:
null
}
);
if
(
!
result
.
success
)
{
getLogger
(
LogCategories
.
MODULE
.
AI
.
HELPERBOT
).
warn
(
'[Top agent] JSON repair failed'
,
{
text
:
repairResponse
.
answerText
});
return
{
aiResponse
:
formatAIResponse
({
text
:
answerText
,
reasoning
:
reasoningText
}),
usage
};
}
}
const
responseJson
=
result
.
data
;
if
(
responseJson
.
phase
===
'generation'
)
{
getLogger
(
LogCategories
.
MODULE
.
AI
.
HELPERBOT
).
debug
(
'🔄 TopAgent: Configuration generation phase'
);
const
{
tools
,
knowledges
}
=
extractResourcesFromPlan
(
responseJson
.
execution_plan
);
const
filterDatasets
=
await
filterValidDatasets
({
teamId
:
user
.
teamId
,
datasetIds
:
knowledges
});
const
enableSandboxEnabled
=
responseJson
.
resources
?.
system_features
?.
sandbox
?.
enabled
||
tools
.
includes
(
AGENT_SANDBOX_TOOLSET_ID
);
const
formData
=
TopAgentFormDataSchema
.
parse
({
systemPrompt
:
buildSystemPrompt
(
responseJson
),
// 构建 system prompt
tools
,
// 从 execution_plan 提取
datasets
:
filterDatasets
,
fileUploadEnabled
:
responseJson
.
resources
?.
system_features
?.
file_upload
?.
enabled
||
false
,
enableSandboxEnabled
,
executionPlan
:
responseJson
.
execution_plan
// 保存原始 execution_plan
});
if
(
formData
)
{
workflowResponseWrite
?.({
event
:
SseResponseEventEnum
.
topAgentConfig
,
data
:
formData
});
}
workflowResponseWrite
?.({
event
:
SseResponseEventEnum
.
plan
,
data
:
{
type
:
'generation'
}
});
return
{
aiResponse
:
formatAIResponse
({
text
:
buildDisplayText
(
responseJson
),
// 构建显示文本
reasoning
:
reasoningText
,
planHint
:
{
type
:
'generation'
}
}),
usage
};
}
else
{
getLogger
(
LogCategories
.
MODULE
.
AI
.
HELPERBOT
).
debug
(
'📝 TopAgent: Information collection phase'
);
const
formDeata
=
responseJson
.
form
;
if
(
formDeata
)
{
const
inputForm
:
UserInputInteractive
=
{
type
:
'userInput'
,
params
:
{
inputForm
:
formDeata
.
map
((
item
)
=>
{
return
{
type
:
item
.
type
as
FlowNodeInputTypeEnum
,
key
:
getNanoid
(
6
),
label
:
item
.
label
,
value
:
''
,
required
:
false
,
valueType
:
item
.
type
===
FlowNodeInputTypeEnum
.
numberInput
?
WorkflowIOValueTypeEnum
.
number
:
WorkflowIOValueTypeEnum
.
string
,
list
:
'options'
in
item
?
item
.
options
?.
map
((
option
)
=>
({
label
:
option
,
value
:
option
}))
:
undefined
};
}),
description
:
responseJson
.
question
}
};
workflowResponseWrite
?.({
event
:
SseResponseEventEnum
.
collectionForm
,
data
:
inputForm
});
return
{
aiResponse
:
formatAIResponse
({
text
:
responseJson
.
question
,
reasoning
:
reasoningText
,
collectionForm
:
inputForm
}),
usage
};
}
workflowResponseWrite
?.({
event
:
SseResponseEventEnum
.
answer
,
data
:
textAdaptGptResponse
({
text
:
responseJson
.
question
})
});
return
{
aiResponse
:
formatAIResponse
({
text
:
responseJson
.
question
,
reasoning
:
reasoningText
}),
usage
};
}
}
catch
(
e
)
{
getLogger
(
LogCategories
.
MODULE
.
AI
.
HELPERBOT
).
warn
(
`[Top agent] Failed to parse JSON response`
,
{
text
:
answerText
});
return
{
aiResponse
:
formatAIResponse
({
text
:
answerText
,
reasoning
:
reasoningText
}),
usage
};
}
};
const
filterValidDatasets
=
async
({
teamId
,
datasetIds
}:
{
teamId
:
string
;
datasetIds
:
string
[];
}):
Promise
<
SelectedDatasetType
[]
>
=>
{
// Check datasetIds is
const
result
=
await
MongoDataset
.
find
(
{
teamId
,
_id
:
{
$in
:
datasetIds
.
filter
((
id
)
=>
{
const
parse
=
ObjectIdSchema
.
safeParse
(
id
);
return
parse
.
success
;
})
}
},
'_id avatar name vectorModel'
).
lean
();
return
result
.
map
((
item
)
=>
({
datasetId
:
String
(
item
.
_id
),
avatar
:
item
.
avatar
,
name
:
item
.
name
,
vectorModel
:
{
model
:
item
.
vectorModel
}
}));
};
packages/service/core/chat/HelperBot/dispatch/topAgent/prompt.ts
deleted
100644 → 0
View file @
9d5a8777
This diff is collapsed.
Click to expand it.
packages/service/core/chat/HelperBot/dispatch/topAgent/type.ts
deleted
100644 → 0
View file @
9d5a8777
import
z
from
'zod'
;
import
{
AICollectionAnswerSchema
}
from
'../type'
;
import
{
SelectedDatasetSchema
}
from
'@fastgpt/global/core/workflow/type/io'
;
// 执行计划步骤中的资源引用类型
export
const
StepResourceRefSchema
=
z
.
object
({
id
:
z
.
string
(),
type
:
z
.
enum
([
'tool'
,
'knowledge'
])
});
// 执行计划步骤类型
export
const
ExecutionStepSchema
=
z
.
object
({
id
:
z
.
string
(),
title
:
z
.
string
(),
description
:
z
.
string
(),
expectedTools
:
z
.
array
(
StepResourceRefSchema
).
optional
()
});
export
const
ExecutionPlanSchema
=
z
.
object
({
total_steps
:
z
.
number
(),
steps
:
z
.
array
(
ExecutionStepSchema
)
});
export
type
ExecutionPlanType
=
z
.
infer
<
typeof
ExecutionPlanSchema
>
;
export
const
TopAgentFormDataSchema
=
z
.
object
({
systemPrompt
:
z
.
string
().
optional
(),
tools
:
z
.
array
(
z
.
string
()).
optional
().
default
([]),
datasets
:
z
.
array
(
SelectedDatasetSchema
).
optional
().
default
([]),
fileUploadEnabled
:
z
.
boolean
().
optional
().
default
(
false
),
enableSandboxEnabled
:
z
.
boolean
().
optional
().
default
(
false
),
executionPlan
:
z
.
any
().
optional
()
});
export
type
TopAgentFormDataType
=
z
.
infer
<
typeof
TopAgentFormDataSchema
>
;
// 表单收集
export
const
TopAgentCollectionAnswerSchema
=
AICollectionAnswerSchema
.
extend
({
phase
:
z
.
literal
(
'collection'
),
reasoning
:
z
.
string
().
nullish
()
});
export
const
TopAgentGenerationAnswerSchema
=
z
.
object
({
phase
:
z
.
literal
(
'generation'
),
reasoning
:
z
.
string
().
nullish
(),
task_analysis
:
z
.
object
({
goal
:
z
.
string
(),
role
:
z
.
string
(),
key_features
:
z
.
string
()
}),
execution_plan
:
ExecutionPlanSchema
.
optional
(),
resources
:
z
.
object
({
system_features
:
z
.
object
({
file_upload
:
z
.
object
({
enabled
:
z
.
boolean
(),
purpose
:
z
.
string
().
optional
()
})
.
optional
()
.
default
({
enabled
:
false
}),
sandbox
:
z
.
object
({
enabled
:
z
.
boolean
()
})
.
optional
()
.
default
({
enabled
:
false
})
})
})
});
export
const
TopAgentAnswerSchema
=
z
.
discriminatedUnion
(
'phase'
,
[
TopAgentCollectionAnswerSchema
,
TopAgentGenerationAnswerSchema
]);
export
type
TopAgentAnswerType
=
z
.
infer
<
typeof
TopAgentAnswerSchema
>
;
export
type
TopAgentGenerationAnswerType
=
z
.
infer
<
typeof
TopAgentGenerationAnswerSchema
>
;
packages/service/core/chat/HelperBot/dispatch/topAgent/utils.ts
deleted
100644 → 0
View file @
9d5a8777
import
type
{
localeType
}
from
'@fastgpt/global/common/i18n/type'
;
import
{
parseI18nString
}
from
'@fastgpt/global/common/i18n/utils'
;
import
type
{
ExecutionPlanType
,
TopAgentGenerationAnswerType
}
from
'./type'
;
import
{
SubAppIds
,
systemSubInfo
}
from
'@fastgpt/global/core/workflow/node/agent/constants'
;
import
{
MongoDataset
}
from
'../../../../dataset/schema'
;
import
{
MongoResourcePermission
}
from
'../../../../../support/permission/schema'
;
import
{
PerResourceTypeEnum
}
from
'@fastgpt/global/support/permission/constant'
;
import
{
getGroupsByTmbId
}
from
'../../../../../support/permission/memberGroup/controllers'
;
import
{
getOrgIdSetWithParentByTmbId
}
from
'../../../../../support/permission/org/controllers'
;
import
{
getUserAvaliableWorkflowTools
}
from
'../../../../app/tool/workflowTool'
;
import
{
SystemToolRepo
}
from
'../../../../app/tool/systemTool/systemTool.repo'
;
import
{
AGENT_SANDBOX_TOOLSET_ID
}
from
'@fastgpt/global/core/ai/sandbox/tools'
;
const
getAccessibleDatasets
=
async
({
teamId
,
tmbId
}:
{
teamId
:
string
;
tmbId
:
string
})
=>
{
const
[
roleList
,
myGroupMap
,
myOrgSet
]
=
await
Promise
.
all
([
MongoResourcePermission
.
find
({
resourceType
:
PerResourceTypeEnum
.
dataset
,
teamId
,
resourceId
:
{
$exists
:
true
}
}).
lean
(),
getGroupsByTmbId
({
tmbId
,
teamId
}),
getOrgIdSetWithParentByTmbId
({
teamId
,
tmbId
})
]);
const
groupIdSet
=
new
Set
(
myGroupMap
.
map
((
item
)
=>
String
(
item
.
_id
)));
const
datasetIds
=
roleList
.
filter
(
(
item
)
=>
String
(
item
.
tmbId
)
===
String
(
tmbId
)
||
(
item
.
groupId
&&
groupIdSet
.
has
(
String
(
item
.
groupId
)))
||
(
item
.
orgId
&&
myOrgSet
.
has
(
String
(
item
.
orgId
)))
)
.
map
((
item
)
=>
String
(
item
.
resourceId
));
if
(
datasetIds
.
length
===
0
)
return
[];
return
MongoDataset
.
find
({
_id
:
{
$in
:
Array
.
from
(
new
Set
(
datasetIds
))
},
teamId
,
deleteTime
:
null
})
.
select
(
'_id name intro avatar vectorModel'
)
.
sort
({
updateTime
:
-
1
})
.
lean
();
};
export
const
generateResourceList
=
async
({
teamId
,
tmbId
,
isRoot
,
lang
=
'zh-CN'
}:
{
teamId
:
string
;
tmbId
:
string
;
isRoot
:
boolean
;
lang
?:
localeType
;
}):
Promise
<
{
resourceList
:
string
;
}
>
=>
{
const
getPrompt
=
({
tool
,
dataset
}:
{
tool
:
string
;
dataset
:
string
})
=>
{
return
`## 可用工具与知识库
### 工具
${
tool
}
### 知识库
${
dataset
}
## 可配置前端开关(不是工具,不能 @ 引用)
- **file_upload**: 文件上传开关,允许用户在对话中上传文件
- **sandbox**: 虚拟机开关,允许 Agent 使用虚拟机执行环境
`
;
};
const
systemToolRepo
=
SystemToolRepo
.
getInstance
();
const
[
systemTools
,
myTools
,
myDatasets
]
=
await
Promise
.
all
([
systemToolRepo
.
getSystemToolList
({
sources
:
[
'system'
// teamId
],
lang
})
.
then
((
res
)
=>
res
.
map
((
tool
)
=>
{
const
toolId
=
tool
.
id
;
const
name
=
tool
.
name
;
const
intro
=
tool
.
intro
;
const
description
=
tool
.
toolDescription
||
intro
||
'暂无描述'
;
return
`- **
${
toolId
}
** [工具]:
${
name
}
-
${
description
}
`
;
})
),
getUserAvaliableWorkflowTools
({
teamId
,
tmbId
}).
then
((
res
)
=>
res
.
map
((
tool
)
=>
{
const
toolId
=
tool
.
_id
;
return
`- **
${
toolId
}
** [工具]:
${
tool
.
name
}
-
${
tool
.
intro
}
`
;
})
),
getAccessibleDatasets
({
teamId
,
tmbId
}).
then
((
res
)
=>
{
return
res
.
map
((
dataset
)
=>
{
const
id
=
String
(
dataset
.
_id
);
const
name
=
dataset
.
name
||
'未命名知识库'
;
const
intro
=
dataset
.
intro
||
'暂无描述'
;
return
`- **
${
id
}
** [知识库]:
${
name
}
-
${
intro
}
`
;
});
})
]);
const
builtinTools
=
[
SubAppIds
.
readFiles
,
AGENT_SANDBOX_TOOLSET_ID
].
map
((
id
)
=>
{
const
info
=
systemSubInfo
[
id
];
return
`- **
${
id
}
** [工具]:
${
parseI18nString
(
info
.
name
,
lang
)}
-
${
info
.
toolDescription
}
`
;
});
const
allTools
=
[...
systemTools
,
...
myTools
,
...
builtinTools
];
return
{
resourceList
:
getPrompt
({
tool
:
allTools
.
length
>
0
?
allTools
.
join
(
'\n'
)
:
'暂无已安装的工具'
,
dataset
:
myDatasets
.
length
>
0
?
myDatasets
.
join
(
'\n'
)
:
'暂未配置知识库'
})
};
};
/**
* 从 execution_plan 中提取并去重所有使用的资源
*/
export
const
extractResourcesFromPlan
=
(
executionPlan
?:
ExecutionPlanType
)
=>
{
if
(
!
executionPlan
)
{
return
{
tools
:
[],
knowledges
:
[]
};
}
const
toolSet
=
new
Set
<
string
>
();
const
knowledgeSet
=
new
Set
<
string
>
();
executionPlan
.
steps
.
forEach
((
step
)
=>
{
step
.
expectedTools
?.
forEach
((
resourceRef
)
=>
{
if
(
resourceRef
.
type
===
'tool'
)
{
toolSet
.
add
(
resourceRef
.
id
);
}
else
if
(
resourceRef
.
type
===
'knowledge'
)
{
knowledgeSet
.
add
(
resourceRef
.
id
);
}
});
});
return
{
tools
:
Array
.
from
(
toolSet
),
knowledges
:
Array
.
from
(
knowledgeSet
)
};
};
/**
* 构建包含所有信息的 system prompt 文本
* 使用 {{@toolId@}} 格式引用工具,可被 parseSystemPrompt 解析
*/
export
const
buildSystemPrompt
=
(
data
:
TopAgentGenerationAnswerType
):
string
=>
{
const
parts
:
string
[]
=
[];
// 1. 任务分析
if
(
data
.
task_analysis
)
{
const
{
goal
,
role
,
key_features
}
=
data
.
task_analysis
;
parts
.
push
(
`---\n**任务目标**\n
${
goal
}
\n`
);
parts
.
push
(
`**角色定位**\n
${
role
}
\n`
);
if
(
key_features
)
{
parts
.
push
(
`**核心特征**\n
${
key_features
}
\n`
);
}
}
// 2. 执行计划
if
(
data
.
execution_plan
)
{
parts
.
push
(
`---\n**参考计划**`
);
data
.
execution_plan
.
steps
.
forEach
((
step
,
index
)
=>
{
let
description
=
step
.
description
;
// 替换 description 中的资源引用:
// - 工具: @工具ID / @工具ID@ / @[工具ID] -> {{@工具ID@}}
// - 知识库: @知识库ID / @知识库ID@ / @[知识库ID] -> {{@dataset_search@}}
if
(
step
.
expectedTools
&&
step
.
expectedTools
.
length
>
0
)
{
step
.
expectedTools
.
forEach
((
resourceRef
)
=>
{
const
replaceId
=
resourceRef
.
type
===
'knowledge'
?
SubAppIds
.
datasetSearch
:
resourceRef
.
id
;
const
escapedId
=
resourceRef
.
id
.
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
'\\$&'
);
const
regex
=
new
RegExp
(
`@(?:\\[
${
escapedId
}
\\]|
${
escapedId
}
@?)`
,
'g'
);
description
=
description
.
replace
(
regex
,
`{{@
${
replaceId
}
@}}`
);
});
}
description
=
description
.
replace
(
/
(?<
!
\{\{)
@
(?:\[(
file_upload|sandbox
)\]
|
(
file_upload|sandbox
)
@
?)(?!\}\})
/g
,
'$1$2'
);
parts
.
push
(
`\n步骤
${
index
+
1
}
.
${
step
.
title
}
\n
${
description
}
`
);
// if (step.expectedTools && step.expectedTools.length > 0) {
// const toolList = step.expectedTools
// .map((t) => {
// const ref = `{{@${t.id}@}}`;
// return `${ref}`;
// })
// .join('、');
// parts.push(`预期资源: ${toolList}`);
// }
});
parts
.
push
(
''
);
}
// 3. 系统功能
// if (data.resources?.system_features?.file_upload?.enabled) {
// parts.push(`---\n**系统功能**\n`);
// parts.push(
// `**文件上传**: 已启用\n${data.resources.system_features.file_upload.purpose}`
// );
// }
return
parts
.
join
(
'\n'
);
};
/**
* 构建用于显示的文本(与 system prompt 格式一致)
*/
export
const
buildDisplayText
=
(
data
:
TopAgentGenerationAnswerType
):
string
=>
{
return
buildSystemPrompt
(
data
);
};
packages/service/core/chat/HelperBot/dispatch/type.ts
deleted
100644 → 0
View file @
9d5a8777
import
z
from
'zod'
;
import
{
HelperBotCompletionsParamsSchema
}
from
'../../../../../global/openapi/core/chat/helperBot/api'
;
import
{
AIChatItemValueItemSchema
,
HelperBotChatItemSchema
}
from
'@fastgpt/global/core/chat/helperBot/type'
;
import
{
WorkflowResponseFnSchema
}
from
'../../../workflow/dispatch/type'
;
import
{
LocaleList
}
from
'@fastgpt/global/common/i18n/type'
;
import
{
FlowNodeInputTypeEnum
}
from
'@fastgpt/global/core/workflow/node/constant'
;
export
const
HelperBotDispatchParamsSchema
=
z
.
object
({
query
:
z
.
string
(),
files
:
HelperBotCompletionsParamsSchema
.
shape
.
files
,
data
:
z
.
unknown
(),
// Allow any type, will be constrained by generic type parameter
histories
:
z
.
array
(
HelperBotChatItemSchema
),
workflowResponseWrite
:
WorkflowResponseFnSchema
,
user
:
z
.
object
({
teamId
:
z
.
string
(),
tmbId
:
z
.
string
(),
userId
:
z
.
string
(),
isRoot
:
z
.
boolean
(),
lang
:
z
.
enum
(
LocaleList
)
})
});
type
BaseHelperBotDispatchParamsType
=
z
.
infer
<
typeof
HelperBotDispatchParamsSchema
>
;
export
type
HelperBotDispatchParamsType
<
T
=
unknown
>
=
Omit
<
BaseHelperBotDispatchParamsType
,
'data'
>
&
{
data
:
T
;
};
export
const
HelperBotDispatchResponseSchema
=
z
.
object
({
aiResponse
:
z
.
array
(
AIChatItemValueItemSchema
),
usage
:
z
.
object
({
model
:
z
.
string
(),
inputTokens
:
z
.
number
(),
outputTokens
:
z
.
number
()
})
});
export
type
HelperBotDispatchResponseType
=
z
.
infer
<
typeof
HelperBotDispatchResponseSchema
>
;
/* AI 表单输出 schema */
const
InputSchema
=
z
.
object
({
type
:
z
.
enum
([
FlowNodeInputTypeEnum
.
input
,
FlowNodeInputTypeEnum
.
numberInput
]),
label
:
z
.
string
()
});
const
SelectSchema
=
z
.
object
({
type
:
z
.
enum
([
FlowNodeInputTypeEnum
.
select
,
FlowNodeInputTypeEnum
.
multipleSelect
]),
label
:
z
.
string
(),
options
:
z
.
array
(
z
.
string
())
});
export
const
AICollectionAnswerSchema
=
z
.
object
({
question
:
z
.
string
(),
// 可能只有一个问题,可能
form
:
z
.
array
(
z
.
union
([
InputSchema
,
SelectSchema
])).
optional
()
});
export
type
AICollectionAnswerType
=
z
.
infer
<
typeof
AICollectionAnswerSchema
>
;
packages/service/core/chat/HelperBot/dispatch/utils.ts
deleted
100644 → 0
View file @
9d5a8777
import
type
{
AIChatItemValueItemType
}
from
'@fastgpt/global/core/chat/helperBot/type'
;
import
type
{
UserInputInteractive
}
from
'@fastgpt/global/core/workflow/template/system/interactive/type'
;
type
PlanHintType
=
{
planHint
?:
{
type
:
'generation'
;
};
};
export
const
formatAIResponse
=
({
text
,
reasoning
,
collectionForm
,
planHint
}:
{
text
:
string
;
reasoning
?:
string
;
collectionForm
?:
UserInputInteractive
;
planHint
?:
PlanHintType
[
'planHint'
];
}):
AIChatItemValueItemType
[]
=>
{
const
result
:
AIChatItemValueItemType
[]
=
[];
result
.
push
({
...(
reasoning
?
{
reasoning
:
{
content
:
reasoning
}
}
:
{}),
text
:
{
content
:
text
}
});
if
(
collectionForm
)
{
result
.
push
({
collectionForm
});
}
if
(
planHint
)
{
result
.
push
({
planHint
});
}
return
result
;
};
packages/service/test/core/chat/topAgentDispatch.test.ts
deleted
100644 → 0
View file @
9d5a8777
import
{
beforeEach
,
describe
,
expect
,
it
,
vi
}
from
'vitest'
;
import
{
AGENT_SANDBOX_TOOLSET_ID
}
from
'@fastgpt/global/core/ai/sandbox/tools'
;
import
{
SubAppIds
}
from
'@fastgpt/global/core/workflow/node/agent/constants'
;
import
{
SseResponseEventEnum
}
from
'@fastgpt/global/core/workflow/runtime/constants'
;
const
{
createLLMResponseMock
}
=
vi
.
hoisted
(()
=>
({
createLLMResponseMock
:
vi
.
fn
()
}));
vi
.
mock
(
'@fastgpt/service/core/ai/llm/request'
,
()
=>
({
createLLMResponse
:
createLLMResponseMock
}));
vi
.
mock
(
'@fastgpt/service/core/ai/model'
,
()
=>
({
getDefaultHelperBotModel
:
vi
.
fn
(()
=>
({
model
:
'helper-model'
}))
}));
vi
.
mock
(
'@fastgpt/service/core/app/tool/workflowTool'
,
()
=>
({
getUserAvaliableWorkflowTools
:
vi
.
fn
(
async
()
=>
[])
}));
vi
.
mock
(
'@fastgpt/service/core/app/tool/systemTool/systemTool.repo'
,
()
=>
({
SystemToolRepo
:
{
getInstance
:
vi
.
fn
(()
=>
({
getSystemToolList
:
vi
.
fn
(
async
()
=>
[])
}))
}
}));
vi
.
mock
(
'@fastgpt/service/core/dataset/schema'
,
()
=>
({
MongoDataset
:
{
find
:
vi
.
fn
(()
=>
({
select
:
vi
.
fn
(()
=>
({
sort
:
vi
.
fn
(()
=>
({
lean
:
vi
.
fn
(
async
()
=>
[])
}))
})),
lean
:
vi
.
fn
(
async
()
=>
[])
}))
}
}));
vi
.
mock
(
'@fastgpt/service/support/permission/schema'
,
()
=>
({
MongoResourcePermission
:
{
find
:
vi
.
fn
(()
=>
({
lean
:
vi
.
fn
(
async
()
=>
[])
}))
}
}));
vi
.
mock
(
'@fastgpt/service/support/permission/memberGroup/controllers'
,
()
=>
({
getGroupsByTmbId
:
vi
.
fn
(
async
()
=>
[])
}));
vi
.
mock
(
'@fastgpt/service/support/permission/org/controllers'
,
()
=>
({
getOrgIdSetWithParentByTmbId
:
vi
.
fn
(
async
()
=>
new
Set
())
}));
import
{
dispatchTopAgent
}
from
'@fastgpt/service/core/chat/HelperBot/dispatch/topAgent'
;
describe
(
'dispatchTopAgent'
,
()
=>
{
beforeEach
(()
=>
{
vi
.
clearAllMocks
();
});
const
mockGenerationResponse
=
({
description
,
expectedTools
}:
{
description
:
string
;
expectedTools
:
Array
<
{
id
:
string
;
type
:
'tool'
|
'knowledge'
}
>
;
})
=>
{
createLLMResponseMock
.
mockResolvedValue
({
answerText
:
JSON
.
stringify
({
phase
:
'generation'
,
reasoning
:
'generate agent config'
,
task_analysis
:
{
goal
:
'build helper agent'
,
role
:
'assistant'
,
key_features
:
'use selected resources'
},
execution_plan
:
{
total_steps
:
1
,
steps
:
[
{
id
:
'step_1'
,
title
:
'Use resource'
,
description
,
expectedTools
}
]
},
resources
:
{
system_features
:
{
file_upload
:
{
enabled
:
false
},
sandbox
:
{
enabled
:
false
}
}
}
}),
reasoningText
:
''
,
usage
:
{
inputTokens
:
10
,
outputTokens
:
5
}
});
};
const
dispatchAndGetTopAgentConfig
=
async
()
=>
{
const
workflowResponseWrite
=
vi
.
fn
();
await
dispatchTopAgent
({
query
:
'build an agent with selected resources'
,
files
:
[],
data
:
{},
histories
:
[],
workflowResponseWrite
,
user
:
{
teamId
:
'team_1'
,
tmbId
:
'tmb_1'
,
userId
:
'user_1'
,
isRoot
:
false
,
lang
:
'zh-CN'
}
});
const
configEvent
=
workflowResponseWrite
.
mock
.
calls
.
find
(
([
payload
])
=>
payload
.
event
===
SseResponseEventEnum
.
topAgentConfig
);
expect
(
configEvent
).
toBeDefined
();
return
configEvent
!
[
0
].
data
;
};
it
(
'enables sandbox when generated plan selects the agent sandbox toolset'
,
async
()
=>
{
createLLMResponseMock
.
mockResolvedValue
({
answerText
:
JSON
.
stringify
({
phase
:
'generation'
,
reasoning
:
'need sandbox'
,
task_analysis
:
{
goal
:
'run code'
,
role
:
'coding assistant'
,
key_features
:
'execute shell commands'
},
execution_plan
:
{
total_steps
:
1
,
steps
:
[
{
id
:
'step_1'
,
title
:
'Execute command'
,
description
:
`Use @
${
AGENT_SANDBOX_TOOLSET_ID
}
to inspect files`
,
expectedTools
:
[
{
id
:
AGENT_SANDBOX_TOOLSET_ID
,
type
:
'tool'
}
]
}
]
},
resources
:
{
system_features
:
{
file_upload
:
{
enabled
:
false
},
sandbox
:
{
enabled
:
false
}
}
}
}),
reasoningText
:
''
,
usage
:
{
inputTokens
:
10
,
outputTokens
:
5
}
});
const
workflowResponseWrite
=
vi
.
fn
();
await
dispatchTopAgent
({
query
:
'build an agent that can run commands'
,
files
:
[],
data
:
{},
histories
:
[],
workflowResponseWrite
,
user
:
{
teamId
:
'team_1'
,
tmbId
:
'tmb_1'
,
userId
:
'user_1'
,
isRoot
:
false
,
lang
:
'zh-CN'
}
});
expect
(
workflowResponseWrite
).
toHaveBeenCalledWith
({
event
:
SseResponseEventEnum
.
topAgentConfig
,
data
:
expect
.
objectContaining
({
tools
:
[
AGENT_SANDBOX_TOOLSET_ID
],
systemPrompt
:
expect
.
stringContaining
(
`{{@
${
AGENT_SANDBOX_TOOLSET_ID
}
@}}`
),
enableSandboxEnabled
:
true
})
});
});
it
(
'renders bracketed tool references in generated step descriptions'
,
async
()
=>
{
const
toolId
=
'custom/search_tool'
;
mockGenerationResponse
({
description
:
`使用 @[
${
toolId
}
] 搜索信息`
,
expectedTools
:
[
{
id
:
toolId
,
type
:
'tool'
}
]
});
const
config
=
await
dispatchAndGetTopAgentConfig
();
expect
(
config
).
toEqual
(
expect
.
objectContaining
({
tools
:
[
toolId
],
systemPrompt
:
expect
.
stringContaining
(
`{{@
${
toolId
}
@}}`
)
})
);
});
it
(
'renders plain tool references in generated step descriptions'
,
async
()
=>
{
const
toolId
=
'custom/search_tool'
;
mockGenerationResponse
({
description
:
`使用 @
${
toolId
}
搜索信息`
,
expectedTools
:
[
{
id
:
toolId
,
type
:
'tool'
}
]
});
const
config
=
await
dispatchAndGetTopAgentConfig
();
expect
(
config
).
toEqual
(
expect
.
objectContaining
({
tools
:
[
toolId
],
systemPrompt
:
expect
.
stringContaining
(
`{{@
${
toolId
}
@}}`
)
})
);
});
it
(
'renders knowledge references as dataset search skill labels'
,
async
()
=>
{
const
datasetId
=
'507f1f77bcf86cd799439011'
;
mockGenerationResponse
({
description
:
`使用 @[
${
datasetId
}
] 查询知识库`
,
expectedTools
:
[
{
id
:
datasetId
,
type
:
'knowledge'
}
]
});
const
config
=
await
dispatchAndGetTopAgentConfig
();
expect
(
config
).
toEqual
(
expect
.
objectContaining
({
systemPrompt
:
expect
.
stringContaining
(
`{{@
${
SubAppIds
.
datasetSearch
}
@}}`
)
})
);
});
it
(
'does not render system features as skill labels in generated step descriptions'
,
async
()
=>
{
mockGenerationResponse
({
description
:
`通过 @file_upload 接收文件,并使用 @
${
SubAppIds
.
readFiles
}
读取内容,不要使用 @sandbox`
,
expectedTools
:
[
{
id
:
SubAppIds
.
readFiles
,
type
:
'tool'
}
]
});
const
config
=
await
dispatchAndGetTopAgentConfig
();
expect
(
config
).
toEqual
(
expect
.
objectContaining
({
tools
:
[
SubAppIds
.
readFiles
],
systemPrompt
:
expect
.
stringContaining
(
`{{@
${
SubAppIds
.
readFiles
}
@}}`
)
})
);
expect
(
config
.
systemPrompt
).
toContain
(
'通过 file_upload 接收文件'
);
expect
(
config
.
systemPrompt
).
toContain
(
'不要使用 sandbox'
);
expect
(
config
.
systemPrompt
).
not
.
toContain
(
'{{@file_upload@}}'
);
expect
(
config
.
systemPrompt
).
not
.
toContain
(
'{{@sandbox@}}'
);
});
});
packages/service/test/core/chat/topAgentUtils.test.ts
deleted
100644 → 0
View file @
9d5a8777
import
{
describe
,
expect
,
it
,
vi
}
from
'vitest'
;
import
{
AGENT_SANDBOX_TOOLSET_ID
,
SANDBOX_SHELL_TOOL_NAME
}
from
'@fastgpt/global/core/ai/sandbox/tools'
;
vi
.
mock
(
'@fastgpt/service/core/app/tool/workflowTool'
,
()
=>
({
getUserAvaliableWorkflowTools
:
vi
.
fn
(
async
()
=>
[])
}));
vi
.
mock
(
'@fastgpt/service/core/app/tool/systemTool/systemTool.repo'
,
()
=>
({
SystemToolRepo
:
{
getInstance
:
vi
.
fn
(()
=>
({
getSystemToolList
:
vi
.
fn
(
async
()
=>
[])
}))
}
}));
vi
.
mock
(
'@fastgpt/service/core/dataset/schema'
,
()
=>
({
MongoDataset
:
{
find
:
vi
.
fn
(()
=>
({
select
:
vi
.
fn
(()
=>
({
sort
:
vi
.
fn
(()
=>
({
lean
:
vi
.
fn
(
async
()
=>
[])
}))
}))
}))
}
}));
vi
.
mock
(
'@fastgpt/service/support/permission/schema'
,
()
=>
({
MongoResourcePermission
:
{
find
:
vi
.
fn
(()
=>
({
lean
:
vi
.
fn
(
async
()
=>
[])
}))
}
}));
vi
.
mock
(
'@fastgpt/service/support/permission/memberGroup/controllers'
,
()
=>
({
getGroupsByTmbId
:
vi
.
fn
(
async
()
=>
[])
}));
vi
.
mock
(
'@fastgpt/service/support/permission/org/controllers'
,
()
=>
({
getOrgIdSetWithParentByTmbId
:
vi
.
fn
(
async
()
=>
new
Set
())
}));
import
{
generateResourceList
}
from
'@fastgpt/service/core/chat/HelperBot/dispatch/topAgent/utils'
;
describe
(
'topAgent utils'
,
()
=>
{
it
(
'lists sandbox as an agent sandbox capability group instead of the shell tool'
,
async
()
=>
{
const
{
resourceList
}
=
await
generateResourceList
({
teamId
:
'team_1'
,
tmbId
:
'tmb_1'
,
isRoot
:
false
,
lang
:
'zh-CN'
});
expect
(
resourceList
).
toContain
(
`**
${
AGENT_SANDBOX_TOOLSET_ID
}
**`
);
expect
(
resourceList
).
not
.
toContain
(
`**
${
SANDBOX_SHELL_TOOL_NAME
}
**`
);
});
});
pro
@
860cc780
Subproject commit
336c7cc6988acea74d38f123b44680cff5064c27
Subproject commit
860cc780847d747e7fdd24433e95887cdb341812
projects/app/src/components/core/chat/ChatContainer/type.ts
View file @
e7fa0c9f
...
@@ -11,7 +11,7 @@ import type {
...
@@ -11,7 +11,7 @@ import type {
UserInputInteractive
,
UserInputInteractive
,
WorkflowInteractiveResponseType
WorkflowInteractiveResponseType
}
from
'@fastgpt/global/core/workflow/template/system/interactive/type'
;
}
from
'@fastgpt/global/core/workflow/template/system/interactive/type'
;
import
type
{
TopAgentFormDataType
}
from
'@fastgpt/
service/core/chat/HelperBot/dispatch
/topAgent/type'
;
import
type
{
TopAgentFormDataType
}
from
'@fastgpt/
global/core/chat/helperBot
/topAgent/type'
;
import
type
{
AgentPlanStatusType
,
AgentPlanType
}
from
'@fastgpt/global/core/ai/agent/type'
;
import
type
{
AgentPlanStatusType
,
AgentPlanType
}
from
'@fastgpt/global/core/ai/agent/type'
;
export
type
generatingMessageProps
=
{
export
type
generatingMessageProps
=
{
...
...
projects/app/src/components/core/chat/HelperBot/components/AIItem.tsx
View file @
e7fa0c9f
...
@@ -91,12 +91,12 @@ const RenderText = React.memo(function RenderText({
...
@@ -91,12 +91,12 @@ const RenderText = React.memo(function RenderText({
return
<
Markdown
source=
{
source
}
showAnimation=
{
showAnimation
}
/>;
return
<
Markdown
source=
{
source
}
showAnimation=
{
showAnimation
}
/>;
});
});
const
RenderCollectionForm
=
React
.
memo
(
function
RenderCollectionForm
({
const
RenderCollectionForm
=
React
.
memo
(
function
RenderCollectionForm
({
isLastValue
,
canSubmit
,
collectionForm
,
collectionForm
,
onSubmit
,
onSubmit
,
showDescription
=
true
showDescription
=
true
}:
{
}:
{
isLastValue
:
boolean
;
canSubmit
:
boolean
;
collectionForm
:
UserInputInteractive
;
collectionForm
:
UserInputInteractive
;
onSubmit
:
(
formData
:
string
)
=>
void
;
onSubmit
:
(
formData
:
string
)
=>
void
;
showDescription
?:
boolean
;
showDescription
?:
boolean
;
...
@@ -141,7 +141,7 @@ const RenderCollectionForm = React.memo(function RenderCollectionForm({
...
@@ -141,7 +141,7 @@ const RenderCollectionForm = React.memo(function RenderCollectionForm({
})
}
})
}
</
Flex
>
</
Flex
>
{
!
submitted
&&
isLastValue
&&
(
{
!
submitted
&&
canSubmit
&&
(
<
Flex
justifyContent=
{
'flex-end'
}
mt=
{
4
}
>
<
Flex
justifyContent=
{
'flex-end'
}
mt=
{
4
}
>
<
Button
<
Button
size=
{
'sm'
}
size=
{
'sm'
}
...
@@ -180,6 +180,9 @@ const AIItem = ({
...
@@ -180,6 +180,9 @@ const AIItem = ({
(
'text'
in
firstValue
&&
firstValue
.
text
?.
content
)
||
(
'text'
in
firstValue
&&
firstValue
.
text
?.
content
)
||
(
'reasoning'
in
firstValue
&&
firstValue
.
reasoning
&&
!
firstValue
.
hideReason
)
(
'reasoning'
in
firstValue
&&
firstValue
.
reasoning
&&
!
firstValue
.
hideReason
)
);
);
const
lastCollectionFormIndex
=
chat
.
value
.
findLastIndex
(
(
value
)
=>
'collectionForm'
in
value
&&
value
.
collectionForm
);
return
(
return
(
<
Box
<
Box
...
@@ -214,7 +217,7 @@ const AIItem = ({
...
@@ -214,7 +217,7 @@ const AIItem = ({
return
(
return
(
<
RenderCollectionForm
<
RenderCollectionForm
key=
{
i
}
key=
{
i
}
isLastValue=
{
isLastChild
&&
i
===
chat
.
value
.
length
-
1
}
canSubmit=
{
isLastChild
&&
i
===
lastCollectionFormIndex
}
collectionForm=
{
value
.
collectionForm
}
collectionForm=
{
value
.
collectionForm
}
onSubmit=
{
onSubmitCollectionForm
}
onSubmit=
{
onSubmitCollectionForm
}
/>
/>
...
...
projects/app/src/components/core/chat/HelperBot/context.tsx
View file @
e7fa0c9f
...
@@ -2,9 +2,11 @@ import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance';
...
@@ -2,9 +2,11 @@ import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance';
import
React
,
{
type
ReactNode
}
from
'react'
;
import
React
,
{
type
ReactNode
}
from
'react'
;
import
{
createContext
}
from
'use-context-selector'
;
import
{
createContext
}
from
'use-context-selector'
;
import
{
HelperBotTypeEnum
}
from
'@fastgpt/global/core/chat/helperBot/type'
;
import
{
HelperBotTypeEnum
}
from
'@fastgpt/global/core/chat/helperBot/type'
;
import
type
{
TopAgentParamsType
}
from
'@fastgpt/global/core/chat/helperBot/topAgent/type'
;
import
type
{
TopAgentFormDataType
,
TopAgentParamsType
}
from
'@fastgpt/global/core/chat/helperBot/topAgent/type'
;
import
{
type
AppFileSelectConfigType
}
from
'@fastgpt/global/core/app/type/config.schema'
;
import
{
type
AppFileSelectConfigType
}
from
'@fastgpt/global/core/app/type/config.schema'
;
import
type
{
TopAgentFormDataType
}
from
'@fastgpt/service/core/chat/HelperBot/dispatch/topAgent/type'
;
export
type
HelperBotRefType
=
{
export
type
HelperBotRefType
=
{
restartChat
:
()
=>
void
;
restartChat
:
()
=>
void
;
...
...
projects/app/src/components/core/chat/HelperBot/index.tsx
View file @
e7fa0c9f
...
@@ -293,7 +293,7 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
...
@@ -293,7 +293,7 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
chatController
.
current
=
abortSignal
;
chatController
.
current
=
abortSignal
;
const
response
=
await
streamFetch
({
const
response
=
await
streamFetch
({
url
:
'/api/core/chat/helperBot/completions'
,
url
:
'/api/
proApi/
core/chat/helperBot/completions'
,
data
:
{
data
:
{
chatId
,
chatId
,
chatItemId
:
chatItemDataId
,
chatItemId
:
chatItemDataId
,
...
...
projects/app/src/pages/api/core/chat/helperBot/completions.ts
deleted
100644 → 0
View file @
9d5a8777
import
type
{
ApiRequestProps
,
ApiResponseType
}
from
'@fastgpt/service/type/next'
;
import
{
NextAPI
}
from
'@/service/middleware/entry'
;
import
{
HelperBotCompletionsParamsSchema
,
type
HelperBotCompletionsParamsType
}
from
'@fastgpt/global/openapi/core/chat/helperBot/api'
;
import
{
authCert
}
from
'@fastgpt/service/support/permission/auth/common'
;
import
{
MongoHelperBotChatItem
}
from
'@fastgpt/service/core/chat/HelperBot/chatItemSchema'
;
import
{
getWorkflowResponseWrite
}
from
'@fastgpt/service/core/workflow/dispatch/utils'
;
import
{
dispatchMap
}
from
'@fastgpt/service/core/chat/HelperBot/dispatch/index'
;
import
{
pushChatRecords
}
from
'@fastgpt/service/core/chat/HelperBot/utils'
;
import
{
getLocale
}
from
'@fastgpt/service/common/middle/i18n'
;
import
{
authFrequencyLimit
}
from
'@fastgpt/service/common/system/frequencyLimit/utils'
;
import
{
addSeconds
}
from
'date-fns'
;
import
{
sseErrRes
}
from
'@fastgpt/service/common/response'
;
import
{
getLogger
,
LogCategories
}
from
'@fastgpt/service/common/logger'
;
export
type
completionsBody
=
HelperBotCompletionsParamsType
;
async
function
handler
(
req
:
ApiRequestProps
<
completionsBody
>
,
res
:
ApiResponseType
<
any
>
)
{
const
logger
=
getLogger
(
LogCategories
.
MODULE
.
AI
.
HELPERBOT
);
const
setSSEHeaders
=
()
=>
{
const
headers
:
Record
<
string
,
string
>
=
{
Connection
:
'keep-alive'
,
'Content-Type'
:
'text/event-stream;charset=utf-8'
,
'Access-Control-Allow-Origin'
:
'*'
,
'X-Accel-Buffering'
:
'no'
,
'Cache-Control'
:
'no-cache, no-transform'
};
Object
.
entries
(
headers
).
forEach
(([
key
,
value
])
=>
{
res
.
setHeader
(
key
,
value
);
});
};
// keep consistent with SSE APIs, otherwise stream consumer may treat response as non-SSE
setSSEHeaders
();
const
parseResult
=
HelperBotCompletionsParamsSchema
.
safeParse
(
req
.
body
);
if
(
!
parseResult
.
success
)
{
sseErrRes
(
res
,
parseResult
.
error
);
return
res
.
end
();
}
const
{
chatId
,
chatItemId
,
query
,
files
,
metadata
}
=
parseResult
.
data
;
try
{
const
{
teamId
,
tmbId
,
userId
,
isRoot
}
=
await
authCert
({
req
,
authToken
:
true
});
// Limit
await
authFrequencyLimit
({
eventId
:
`
${
tmbId
}
-helperBot-completions`
,
maxAmount
:
10
,
expiredTime
:
addSeconds
(
new
Date
(),
60
)
}).
catch
(()
=>
{
return
Promise
.
reject
(
'Frequency limit exceeded'
);
});
const
histories
=
await
MongoHelperBotChatItem
.
find
({
userId
,
chatId
})
.
sort
({
_id
:
-
1
})
.
limit
(
40
)
.
lean
();
histories
.
reverse
();
const
workflowResponseWrite
=
getWorkflowResponseWrite
({
res
,
detail
:
true
,
streamResponse
:
true
,
id
:
chatId
,
showNodeStatus
:
true
});
// 执行不同逻辑
const
fn
=
dispatchMap
[
metadata
.
type
];
if
(
!
fn
)
{
return
Promise
.
reject
(
'Invalid helper bot type'
);
}
const
result
=
await
fn
({
query
,
files
,
data
:
metadata
.
data
,
histories
,
workflowResponseWrite
,
user
:
{
teamId
,
tmbId
,
userId
,
isRoot
,
lang
:
getLocale
(
req
)
}
});
// Save chat
await
pushChatRecords
({
type
:
metadata
.
type
,
userId
,
chatId
,
chatItemId
,
query
,
files
,
aiResponse
:
result
.
aiResponse
});
// Push usage
// pushHelperBotUsage({
// teamId,
// tmbId,
// model: result.usage.model,
// inputTokens: result.usage.inputTokens,
// outputTokens: result.usage.outputTokens
// });
}
catch
(
error
)
{
logger
.
error
(
'HelperBot completions failed'
,
{
error
,
chatId
,
chatItemId
,
metadata
});
sseErrRes
(
res
,
error
);
}
res
.
end
();
}
export
default
NextAPI
(
handler
);
export
const
config
=
{
api
:
{
bodyParser
:
{
sizeLimit
:
'20mb'
},
responseLimit
:
'20mb'
}
};
projects/app/src/web/common/api/fetch.ts
View file @
e7fa0c9f
...
@@ -25,7 +25,7 @@ import type {
...
@@ -25,7 +25,7 @@ import type {
ToolModuleResponseItemType
,
ToolModuleResponseItemType
,
SkillModuleResponseItemType
SkillModuleResponseItemType
}
from
'@fastgpt/global/core/chat/type'
;
}
from
'@fastgpt/global/core/chat/type'
;
import
type
{
TopAgentFormDataType
}
from
'@fastgpt/
service/core/chat/HelperBot/dispatch
/topAgent/type'
;
import
type
{
TopAgentFormDataType
}
from
'@fastgpt/
global/core/chat/helperBot
/topAgent/type'
;
import
type
{
UserInputInteractive
}
from
'@fastgpt/global/core/workflow/template/system/interactive/type'
;
import
type
{
UserInputInteractive
}
from
'@fastgpt/global/core/workflow/template/system/interactive/type'
;
import
type
{
AgentPlanStatusType
,
AgentPlanType
}
from
'@fastgpt/global/core/ai/agent/type'
;
import
type
{
AgentPlanStatusType
,
AgentPlanType
}
from
'@fastgpt/global/core/ai/agent/type'
;
import
type
{
StreamNoNeedToBeResumeType
}
from
'@fastgpt/global/openapi/core/ai/api'
;
import
type
{
StreamNoNeedToBeResumeType
}
from
'@fastgpt/global/openapi/core/ai/api'
;
...
@@ -204,7 +204,7 @@ function handleEventSourceData(params: HandleEventSourceDataParams) {
...
@@ -204,7 +204,7 @@ function handleEventSourceData(params: HandleEventSourceDataParams) {
}
}
case
SseResponseEventEnum
.
collectionForm
:
{
case
SseResponseEventEnum
.
collectionForm
:
{
onmessage
({
event
,
collectionForm
:
obj
});
enqueue
({
responseValueId
,
event
,
collectionForm
:
obj
});
break
;
break
;
}
}
...
...
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