mirror of
https://github.com/labring/FastGPT.git
synced 2025-12-25 20:02:47 +00:00
Complete agent parent (#6049)
* add role and tools filling * add: file-upload --------- Co-authored-by: xxyyh <2289112474@qq>
This commit is contained in:
parent
7bd1a8b654
commit
3fd837c221
|
|
@ -1,4 +1,4 @@
|
|||
import { ChatCompletionRequestMessageRoleEnum } from 'core/ai/constants';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '../../ai/constants';
|
||||
import type {
|
||||
ChatCompletionContentPart,
|
||||
ChatCompletionFunctionMessageParam,
|
||||
|
|
@ -96,7 +96,7 @@ export const helperChats2GPTMessages = ({
|
|||
tool_calls
|
||||
});
|
||||
aiResults.push(...toolResponse);
|
||||
} else if ('text' in value && value.text?.content === 'string') {
|
||||
} else if ('text' in value && typeof value.text?.content === 'string') {
|
||||
if (!value.text.content && item.value.length > 1) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,11 +78,22 @@ export type HelperBotChatItemSiteType = z.infer<typeof HelperBotChatItemSiteSche
|
|||
|
||||
/* 具体的 bot 的特有参数 */
|
||||
|
||||
// AI 模型配置
|
||||
export const AIModelConfigSchema = z.object({
|
||||
model: z.string(),
|
||||
temperature: z.number().nullish(),
|
||||
maxToken: z.number().nullish(),
|
||||
stream: z.boolean().nullish()
|
||||
});
|
||||
export type AIModelConfigType = z.infer<typeof AIModelConfigSchema>;
|
||||
|
||||
export const topAgentParamsSchema = z.object({
|
||||
role: z.string().nullish(),
|
||||
taskObject: z.string().nullish(),
|
||||
selectedTools: z.array(z.string()).nullish(),
|
||||
selectedDatasets: z.array(z.string()).nullish(),
|
||||
fileUpload: z.boolean().nullish()
|
||||
fileUpload: z.boolean().nullish(),
|
||||
// AI 模型配置
|
||||
modelConfig: AIModelConfigSchema.nullish()
|
||||
});
|
||||
export type TopAgentParamsType = z.infer<typeof topAgentParamsSchema>;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ export enum SseResponseEventEnum {
|
|||
|
||||
interactive = 'interactive',
|
||||
|
||||
agentPlan = 'agentPlan' // agent plan
|
||||
agentPlan = 'agentPlan', // agent plan
|
||||
|
||||
formData = 'formData' // form data for TopAgent
|
||||
}
|
||||
|
||||
export enum DispatchNodeResponseKeyEnum {
|
||||
|
|
|
|||
|
|
@ -1,35 +1,207 @@
|
|||
import type { HelperBotDispatchParamsType, HelperBotDispatchResponseType } from '../type';
|
||||
import { helperChats2GPTMessages } from '@fastgpt/global/core/chat/helperBot/adaptor';
|
||||
import { getPrompt } from './prompt';
|
||||
import { createLLMResponse } from '../../../../ai/llm/request';
|
||||
import { getLLMModel } from '../../../../ai/model';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import type { AIChatItemValueItemType } from '@fastgpt/global/core/chat/helperBot/type';
|
||||
import { getSystemToolsWithInstalled } from '../../../../app/tool/controller';
|
||||
|
||||
export const dispatchTopAgent = async (
|
||||
props: HelperBotDispatchParamsType
|
||||
): Promise<HelperBotDispatchResponseType> => {
|
||||
const { query, files, metadata, histories } = props;
|
||||
const messages = helperChats2GPTMessages({
|
||||
const { query, files, metadata, histories, workflowResponseWrite, teamId, userId } = props;
|
||||
|
||||
const modelConfig = metadata.data?.modelConfig;
|
||||
|
||||
const modelName = modelConfig?.model || global.systemDefaultModel?.llm?.model;
|
||||
if (!modelName) {
|
||||
throw new Error('未配置 LLM 模型,请在前端选择模型或在系统中配置默认模型');
|
||||
}
|
||||
const modelData = getLLMModel(modelName);
|
||||
if (!modelData) {
|
||||
throw new Error(`模型 ${modelName} 未找到`);
|
||||
}
|
||||
|
||||
const temperature = modelConfig?.temperature ?? 0.7;
|
||||
const maxToken = modelConfig?.maxToken ?? 4000;
|
||||
const stream = modelConfig?.stream ?? true;
|
||||
|
||||
const resourceList = await generateResourceList({
|
||||
teamId,
|
||||
userId
|
||||
});
|
||||
|
||||
const historyMessages = helperChats2GPTMessages({
|
||||
messages: histories,
|
||||
reserveTool: false
|
||||
});
|
||||
// 拿工具资源参考 FastGPT/projects/app/src/pages/api/core/app/tool/getSystemToolTemplates.ts
|
||||
|
||||
/*
|
||||
流输出
|
||||
onReasoning({ text }) {
|
||||
if (!aiChatReasoning) return;
|
||||
workflowStreamResponse?.({
|
||||
const systemPrompt = getPrompt({ resourceList });
|
||||
const conversationMessages = [
|
||||
{ role: 'system' as const, content: systemPrompt },
|
||||
...historyMessages,
|
||||
{ role: 'user' as const, content: query }
|
||||
];
|
||||
|
||||
// console.log('📝 TopAgent 阶段 1: 信息收集');
|
||||
// console.log('conversationMessages:', conversationMessages);
|
||||
|
||||
const llmResponse = await createLLMResponse({
|
||||
body: {
|
||||
messages: conversationMessages,
|
||||
model: modelName,
|
||||
temperature,
|
||||
stream,
|
||||
max_tokens: maxToken
|
||||
},
|
||||
onStreaming: ({ text }) => {
|
||||
workflowResponseWrite?.({
|
||||
event: SseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({ text })
|
||||
});
|
||||
},
|
||||
onReasoning: ({ text }) => {
|
||||
workflowResponseWrite?.({
|
||||
event: SseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({ reasoning_content: text })
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const firstPhaseAnswer = llmResponse.answerText;
|
||||
const firstPhaseReasoning = llmResponse.reasoningText;
|
||||
|
||||
// 尝试解析信息收集阶段的 JSON 响应
|
||||
let parsedResponse: { reasoning?: string; question?: string } | null = null;
|
||||
try {
|
||||
parsedResponse = JSON.parse(firstPhaseAnswer);
|
||||
} catch (e) {
|
||||
// 如果解析失败,说明不是 JSON 格式,可能是普通文本
|
||||
parsedResponse = null;
|
||||
}
|
||||
|
||||
if (firstPhaseAnswer.includes('「信息收集已完成」')) {
|
||||
console.log('🔄 TopAgent: 检测到信息收集完成信号,切换到计划生成阶段');
|
||||
|
||||
const newMessages = [
|
||||
...conversationMessages,
|
||||
{ role: 'assistant' as const, content: firstPhaseAnswer },
|
||||
{ role: 'user' as const, content: '请你直接生成规划方案' }
|
||||
];
|
||||
|
||||
// console.log('📋 TopAgent 阶段 2: 计划生成');
|
||||
|
||||
const planResponse = await createLLMResponse({
|
||||
body: {
|
||||
messages: newMessages,
|
||||
model: modelName,
|
||||
temperature,
|
||||
stream,
|
||||
max_tokens: maxToken
|
||||
},
|
||||
onStreaming: ({ text }) => {
|
||||
workflowResponseWrite?.({
|
||||
event: SseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
reasoning_content: text
|
||||
})
|
||||
data: textAdaptGptResponse({ text })
|
||||
});
|
||||
},
|
||||
onStreaming({ text }) {
|
||||
if (!isResponseAnswerText) return;
|
||||
workflowStreamResponse?.({
|
||||
onReasoning: ({ text }) => {
|
||||
workflowResponseWrite?.({
|
||||
event: SseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
text
|
||||
})
|
||||
data: textAdaptGptResponse({ reasoning_content: text })
|
||||
});
|
||||
}
|
||||
*/
|
||||
});
|
||||
|
||||
let formData;
|
||||
try {
|
||||
const planJson = JSON.parse(planResponse.answerText);
|
||||
// console.log('解析的计划 JSON:', planJson);
|
||||
|
||||
formData = {
|
||||
role: planJson.task_analysis?.role || '',
|
||||
taskObject: planJson.task_analysis?.goal || '',
|
||||
tools: planJson.resources?.tools?.map((tool: any) => tool.id) || [],
|
||||
fileUploadEnabled: planJson.resources?.system_features?.file_upload?.enabled || false
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('解析计划 JSON 失败:', e);
|
||||
}
|
||||
|
||||
return {
|
||||
aiResponse: formatAIResponse(planResponse.answerText, planResponse.reasoningText),
|
||||
formData
|
||||
};
|
||||
}
|
||||
|
||||
const displayText = parsedResponse?.question || firstPhaseAnswer;
|
||||
return {
|
||||
aiResponse: formatAIResponse(displayText, firstPhaseReasoning)
|
||||
};
|
||||
};
|
||||
|
||||
const generateResourceList = async ({
|
||||
teamId,
|
||||
userId
|
||||
}: {
|
||||
teamId: string;
|
||||
userId: string;
|
||||
}): Promise<string> => {
|
||||
let result = '\n## 可用资源列表\n';
|
||||
|
||||
const tools = await getSystemToolsWithInstalled({
|
||||
teamId,
|
||||
isRoot: true // TODO: 需要传入实际的 isRoot 值
|
||||
});
|
||||
|
||||
const installedTools = tools.filter((tool) => {
|
||||
return tool.installed && !tool.isFolder;
|
||||
});
|
||||
|
||||
if (installedTools.length > 0) {
|
||||
result += '### 工具\n';
|
||||
installedTools.forEach((tool) => {
|
||||
const toolId = tool.id;
|
||||
const name =
|
||||
typeof tool.name === 'string'
|
||||
? tool.name
|
||||
: tool.name?.en || tool.name?.['zh-CN'] || '未命名';
|
||||
const intro =
|
||||
typeof tool.intro === 'string' ? tool.intro : tool.intro?.en || tool.intro?.['zh-CN'] || '';
|
||||
const description = tool.toolDescription || intro || '暂无描述';
|
||||
result += `- **${toolId}** [工具]: ${name} - ${description}\n`;
|
||||
});
|
||||
} else {
|
||||
result += '### 工具\n暂无已安装的工具\n';
|
||||
}
|
||||
|
||||
// TODO: 知识库
|
||||
result += '\n### 知识库\n暂未配置知识库\n';
|
||||
|
||||
result += '\n### 系统功能\n';
|
||||
result += '- **file_upload**: 文件上传功能 (enabled, purpose, file_types)\n';
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const formatAIResponse = (text: string, reasoning?: string): AIChatItemValueItemType[] => {
|
||||
const result: AIChatItemValueItemType[] = [];
|
||||
|
||||
if (reasoning) {
|
||||
result.push({
|
||||
reasoning: {
|
||||
content: reasoning
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
result.push({
|
||||
text: {
|
||||
content: text
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -253,9 +253,11 @@ ${resourceList}
|
|||
- 形成完整的 resources 配置
|
||||
|
||||
**输出要求**:
|
||||
**重要:只输出JSON,不要添加任何解释文字、代码块标记或其他内容!**
|
||||
**重要**
|
||||
1. 只输出JSON规定的字段,不要添加任何解释文字、代码块标记或其他内容!
|
||||
2. 千万不能添加不属于以下模板中的字段到最终的结果中
|
||||
|
||||
直接输出以下格式的JSON:
|
||||
直接输出以下格式的JSON(千万不要添加其他字段进来):
|
||||
{
|
||||
"task_analysis": {
|
||||
"goal": "任务的核心目标描述",
|
||||
|
|
@ -353,7 +355,7 @@ ${resourceList}
|
|||
- ❌ 不要使用旧格式的 tools 字段,必须使用 resources 结构
|
||||
- ❌ 不要添加任何解释性文字或前言后语
|
||||
- ✅ 必须使用 resources 对象,包含 tools、knowledges、system_features
|
||||
- ✅ file_upload.enabled=true 时必须提供 purpose 字段
|
||||
- ✅ file_upload.enabled=true 时必须提供 purpose 字段,
|
||||
- ✅ knowledges 或 tools 可以为空数组(如果不需要)
|
||||
- ✅ 直接、纯净地输出JSON内容
|
||||
|
||||
|
|
|
|||
|
|
@ -11,11 +11,21 @@ export const HelperBotDispatchParamsSchema = z.object({
|
|||
files: HelperBotCompletionsParamsSchema.shape.files,
|
||||
metadata: HelperBotCompletionsParamsSchema.shape.metadata,
|
||||
histories: z.array(HelperBotChatItemSchema),
|
||||
workflowResponseWrite: WorkflowResponseFnSchema
|
||||
workflowResponseWrite: WorkflowResponseFnSchema,
|
||||
teamId: z.string(),
|
||||
userId: z.string()
|
||||
});
|
||||
export type HelperBotDispatchParamsType = z.infer<typeof HelperBotDispatchParamsSchema>;
|
||||
|
||||
export const HelperBotDispatchResponseSchema = z.object({
|
||||
aiResponse: z.array(AIChatItemValueItemSchema)
|
||||
aiResponse: z.array(AIChatItemValueItemSchema),
|
||||
formData: z
|
||||
.object({
|
||||
role: z.string().optional(),
|
||||
taskObject: z.string().optional(),
|
||||
tools: z.array(z.string()).optional(),
|
||||
fileUploadEnabled: z.boolean().optional()
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
export type HelperBotDispatchResponseType = z.infer<typeof HelperBotDispatchResponseSchema>;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import type {
|
|||
} from '@fastgpt/global/core/chat/type';
|
||||
import { MongoHelperBotChatItem } from './chatItemSchema';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { mongoSessionRun } from 'common/mongo/sessionRun';
|
||||
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
|
||||
|
||||
export const pushChatRecords = async ({
|
||||
type,
|
||||
|
|
|
|||
|
|
@ -453,6 +453,8 @@
|
|||
"toolkit_uninstalled": "未安装",
|
||||
"toolkit_update_failed": "更新失败",
|
||||
"toolkit_user_guide": "使用说明",
|
||||
"tool_load_failed": "部分工具加载失败",
|
||||
"failed_tools": "失败的工具",
|
||||
"tools": "工具",
|
||||
"tools_no_description": "这个工具没有介绍~",
|
||||
"tools_tip": "声明模型可用的工具,可以实现与外部系统交互等扩展能力",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import { streamFetch } from '@/web/common/api/fetch';
|
|||
import type { generatingMessageProps } from '../ChatContainer/type';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
|
||||
const ChatBox = ({ type, metadata, ...props }: HelperBotProps) => {
|
||||
const ChatBox = ({ type, metadata, onApply, ...props }: HelperBotProps) => {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -144,7 +144,19 @@ const ChatBox = ({ type, metadata, ...props }: HelperBotProps) => {
|
|||
}
|
||||
);
|
||||
const generatingMessage = useMemoizedFn(
|
||||
({ event, text = '', reasoningText, tool }: generatingMessageProps) => {
|
||||
({ event, text = '', reasoningText, tool, data }: generatingMessageProps & { data?: any }) => {
|
||||
if (event === SseResponseEventEnum.formData && data) {
|
||||
const formData = {
|
||||
role: data.role || '',
|
||||
taskObject: data.taskObject || '',
|
||||
selectedTools: data.tools || [],
|
||||
selectedDatasets: [],
|
||||
fileUpload: data.fileUploadEnabled || false
|
||||
};
|
||||
onApply?.(formData);
|
||||
return;
|
||||
}
|
||||
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
|
|
@ -276,6 +288,7 @@ const ChatBox = ({ type, metadata, ...props }: HelperBotProps) => {
|
|||
const chatItemDataId = getNanoid(24);
|
||||
const newChatList: HelperBotChatItemSiteType[] = [
|
||||
...chatRecords,
|
||||
// 用户消息
|
||||
{
|
||||
_id: getNanoid(24),
|
||||
createTime: new Date(),
|
||||
|
|
@ -289,6 +302,7 @@ const ChatBox = ({ type, metadata, ...props }: HelperBotProps) => {
|
|||
}
|
||||
]
|
||||
},
|
||||
// AI 消息 - 空白,用于接收流式输出
|
||||
{
|
||||
_id: getNanoid(24),
|
||||
createTime: new Date(),
|
||||
|
|
@ -297,20 +311,7 @@ const ChatBox = ({ type, metadata, ...props }: HelperBotProps) => {
|
|||
value: [
|
||||
{
|
||||
text: {
|
||||
content: `我无法直接通过“读取静态网页工具”获取 GitHub(动态站点)上的实时信息,因此不能自动抓取 fastgpt 的 star 数量。
|
||||
|
||||
不过你可以告诉我:
|
||||
|
||||
- 你指的是 **FastGPT(fastgpt-dev/FastGPT)** 吗?
|
||||
GitHub 链接通常是:https://github.com/fastgpt-dev/FastGPT
|
||||
|
||||
如果是这个项目,我可以根据我最新的训练数据给你一个**大致的历史 star 数范围**,或者你也可以让我协助编写脚本来实时查询它的 star。
|
||||
|
||||
你希望我:
|
||||
|
||||
1. 提供现阶段近似 star 数(基于 2025 的大致趋势)?
|
||||
2. 帮你写一个脚本实时查 GitHub API?
|
||||
3. 还是提供该项目的介绍?`
|
||||
content: ''
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -340,7 +341,7 @@ const ChatBox = ({ type, metadata, ...props }: HelperBotProps) => {
|
|||
})),
|
||||
metadata: {
|
||||
type: 'topAgent',
|
||||
data: {}
|
||||
data: metadata
|
||||
}
|
||||
},
|
||||
onMessage: generatingMessage,
|
||||
|
|
|
|||
|
|
@ -21,14 +21,19 @@ import type { Form2WorkflowFnType } from '../FormComponent/type';
|
|||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import HelperBot from '@/components/core/chat/HelperBot';
|
||||
import { HelperBotTypeEnum } from '@fastgpt/global/core/chat/helperBot/type';
|
||||
import { getToolPreviewNode } from '@/web/core/app/api/tool';
|
||||
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
type Props = {
|
||||
appForm: AppFormEditFormType;
|
||||
setAppForm?: React.Dispatch<React.SetStateAction<AppFormEditFormType>>;
|
||||
setRenderEdit: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
form2WorkflowFn: Form2WorkflowFnType;
|
||||
};
|
||||
const ChatTest = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => {
|
||||
const ChatTest = ({ appForm, setAppForm, setRenderEdit, form2WorkflowFn }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [activeTab, setActiveTab] = useSafeState<'helper' | 'chat_debug'>('helper');
|
||||
|
||||
|
|
@ -58,6 +63,24 @@ const ChatTest = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => {
|
|||
isReady: true
|
||||
});
|
||||
|
||||
// 构建 TopAgent metadata,从 appForm 中提取配置
|
||||
const topAgentMetadata = useMemo(
|
||||
() => ({
|
||||
role: appForm.aiSettings.aiRole,
|
||||
taskObject: appForm.aiSettings.aiTaskObject,
|
||||
selectedTools: appForm.selectedTools.map((tool) => tool.id),
|
||||
selectedDatasets: appForm.dataset.datasets.map((dataset) => dataset.datasetId),
|
||||
fileUpload: false, // TODO: 从配置中获取文件上传设置
|
||||
modelConfig: {
|
||||
model: appForm.aiSettings.model,
|
||||
temperature: appForm.aiSettings.temperature,
|
||||
maxToken: appForm.aiSettings.maxToken,
|
||||
stream: true
|
||||
}
|
||||
}),
|
||||
[appForm]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex h={'full'} gap={2}>
|
||||
<MyBox
|
||||
|
|
@ -110,7 +133,97 @@ const ChatTest = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => {
|
|||
</Flex>
|
||||
<Box flex={1}>
|
||||
{activeTab === 'helper' && (
|
||||
<HelperBot type={HelperBotTypeEnum.topAgent} metadata={{}} onApply={() => {}} />
|
||||
<HelperBot
|
||||
type={HelperBotTypeEnum.topAgent}
|
||||
metadata={topAgentMetadata}
|
||||
onApply={async (formData) => {
|
||||
if (!setAppForm) {
|
||||
console.warn('⚠️ setAppForm 未传入,无法更新表单');
|
||||
return;
|
||||
}
|
||||
|
||||
const existingToolIds = new Set(
|
||||
appForm.selectedTools.map((tool) => tool.pluginId).filter(Boolean)
|
||||
);
|
||||
|
||||
// console.log('📋 当前已存在的工具 pluginId:', Array.from(existingToolIds));
|
||||
// console.log('📋 formData.selectedTools:', formData.selectedTools);
|
||||
|
||||
const newToolIds = (formData.selectedTools || []).filter(
|
||||
(toolId: string) => !existingToolIds.has(toolId)
|
||||
);
|
||||
|
||||
if (newToolIds.length === 0) {
|
||||
// 没有新工具需要添加,仍然更新 role、taskObject 和文件上传配置
|
||||
setAppForm((prev) => ({
|
||||
...prev,
|
||||
aiSettings: {
|
||||
...prev.aiSettings,
|
||||
aiRole: formData.role || '',
|
||||
aiTaskObject: formData.taskObject || ''
|
||||
},
|
||||
chatConfig: {
|
||||
...prev.chatConfig,
|
||||
fileSelectConfig: {
|
||||
...prev.chatConfig.fileSelectConfig,
|
||||
canSelectFile: formData.fileUpload || false
|
||||
}
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
let newTools: FlowNodeTemplateType[] = [];
|
||||
const failedToolIds: string[] = [];
|
||||
|
||||
// 使用 Promise.allSettled 并行请求所有工具
|
||||
const toolPromises = newToolIds.map((toolId: string) =>
|
||||
getToolPreviewNode({ appId: toolId })
|
||||
.then((tool) => ({ status: 'fulfilled' as const, toolId, tool }))
|
||||
.catch((error) => ({ status: 'rejected' as const, toolId, error }))
|
||||
);
|
||||
|
||||
const results = await Promise.allSettled(toolPromises);
|
||||
|
||||
results.forEach((result: any) => {
|
||||
if (result.status === 'fulfilled' && result.value.status === 'fulfilled') {
|
||||
newTools.push(result.value.tool);
|
||||
} else if (result.status === 'fulfilled' && result.value.status === 'rejected') {
|
||||
failedToolIds.push(result.value.toolId);
|
||||
console.error(`❌ 工具 ${result.value.toolId} 获取失败:`, result.value.error);
|
||||
}
|
||||
});
|
||||
|
||||
if (failedToolIds.length > 0) {
|
||||
toast({
|
||||
title: t('app:tool_load_failed'),
|
||||
description: `${t('app:failed_tools')}: ${failedToolIds.join(', ')}`,
|
||||
status: 'warning',
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
|
||||
setAppForm((prev) => {
|
||||
const newForm: AppFormEditFormType = {
|
||||
...prev,
|
||||
aiSettings: {
|
||||
...prev.aiSettings,
|
||||
aiRole: formData.role || '',
|
||||
aiTaskObject: formData.taskObject || ''
|
||||
},
|
||||
selectedTools: [...prev.selectedTools, ...newTools],
|
||||
chatConfig: {
|
||||
...prev.chatConfig,
|
||||
fileSelectConfig: {
|
||||
...prev.chatConfig.fileSelectConfig,
|
||||
canSelectFile: formData.fileUpload || false
|
||||
}
|
||||
}
|
||||
};
|
||||
return newForm;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'chat_debug' && <ChatContainer />}
|
||||
</Box>
|
||||
|
|
@ -128,7 +241,7 @@ const ChatTest = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => {
|
|||
);
|
||||
};
|
||||
|
||||
const Render = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => {
|
||||
const Render = ({ appForm, setAppForm, setRenderEdit, form2WorkflowFn }: Props) => {
|
||||
const { chatId } = useChatStore();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
|
|
@ -150,6 +263,7 @@ const Render = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => {
|
|||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest
|
||||
appForm={appForm}
|
||||
setAppForm={setAppForm}
|
||||
setRenderEdit={setRenderEdit}
|
||||
form2WorkflowFn={form2WorkflowFn}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ const Edit = ({
|
|||
<Box flex={'2 0 0'} w={0} mb={3}>
|
||||
<ChatTest
|
||||
appForm={appForm}
|
||||
setAppForm={setAppForm}
|
||||
setRenderEdit={setRenderEdit}
|
||||
form2WorkflowFn={agentForm2AppWorkflow}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -293,17 +293,18 @@ export const checkNeedsUserConfiguration = (toolTemplate: FlowNodeTemplateType):
|
|||
[FlowNodeInputTypeEnum.timeRangeSelect]: true
|
||||
};
|
||||
return (
|
||||
toolTemplate.inputs.length > 0 &&
|
||||
toolTemplate.inputs.some((input) => {
|
||||
// 有工具描述的不需要配置
|
||||
if (input.toolDescription) return false;
|
||||
// 禁用流的不需要配置
|
||||
if (input.key === NodeInputKeyEnum.forbidStream) return false;
|
||||
// 系统输入配置需要配置
|
||||
if (input.key === NodeInputKeyEnum.systemInputConfig) return true;
|
||||
((toolTemplate.inputs?.length ?? 0) > 0 &&
|
||||
toolTemplate.inputs?.some((input) => {
|
||||
// 有工具描述的不需要配置
|
||||
if (input.toolDescription) return false;
|
||||
// 禁用流的不需要配置
|
||||
if (input.key === NodeInputKeyEnum.forbidStream) return false;
|
||||
// 系统输入配置需要配置
|
||||
if (input.key === NodeInputKeyEnum.systemInputConfig) return true;
|
||||
|
||||
// 检查是否包含表单类型的输入
|
||||
return input.renderTypeList.some((type) => formRenderTypesMap[type]);
|
||||
})
|
||||
// 检查是否包含表单类型的输入
|
||||
return input.renderTypeList.some((type) => formRenderTypesMap[type]);
|
||||
})) ||
|
||||
false
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { MongoHelperBotChatItem } from '@fastgpt/service/core/chat/HelperBot/cha
|
|||
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 { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
|
||||
export type completionsBody = HelperBotCompletionsParamsType;
|
||||
|
||||
|
|
@ -43,9 +44,19 @@ async function handler(req: ApiRequestProps<completionsBody>, res: ApiResponseTy
|
|||
files,
|
||||
metadata,
|
||||
histories,
|
||||
workflowResponseWrite
|
||||
workflowResponseWrite,
|
||||
teamId,
|
||||
userId
|
||||
});
|
||||
|
||||
// Send formData if exists
|
||||
if (result.formData) {
|
||||
workflowResponseWrite?.({
|
||||
event: SseResponseEventEnum.formData,
|
||||
data: result.formData
|
||||
});
|
||||
}
|
||||
|
||||
// Save chat
|
||||
await pushChatRecords({
|
||||
type: metadata.type,
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ type ResponseQueueItemType = CommonResponseType &
|
|||
| SseResponseEventEnum.toolResponse;
|
||||
tools: any;
|
||||
}
|
||||
| {
|
||||
event: SseResponseEventEnum.formData;
|
||||
}
|
||||
);
|
||||
|
||||
class FatalError extends Error {}
|
||||
|
|
@ -269,6 +272,12 @@ export const streamFetch = ({
|
|||
event,
|
||||
agentPlan: rest.agentPlan
|
||||
});
|
||||
} else if (event === SseResponseEventEnum.formData) {
|
||||
// Directly call onMessage for formData, no need to queue
|
||||
onMessage({
|
||||
event,
|
||||
data: rest
|
||||
});
|
||||
} else if (event === SseResponseEventEnum.error) {
|
||||
if (rest.statusText === TeamErrEnum.aiPointsNotEnough) {
|
||||
useSystemStore.getState().setNotSufficientModalType(TeamErrEnum.aiPointsNotEnough);
|
||||
|
|
|
|||
Loading…
Reference in New Issue