diff --git a/packages/global/core/ai/skill/type.ts b/packages/global/core/ai/skill/type.ts new file mode 100644 index 000000000..99f4174f9 --- /dev/null +++ b/packages/global/core/ai/skill/type.ts @@ -0,0 +1,23 @@ +import { ObjectIdSchema } from '../../../common/type/mongo'; +import z from 'zod'; + +export const SkillToolSchema = z.object({ + id: z.string(), + config: z.record(z.string(), z.any()) +}); +export type SkillToolType = z.infer; + +export const AiSkillSchema = z.object({ + _id: ObjectIdSchema, + teamId: ObjectIdSchema, + tmbId: ObjectIdSchema, + appId: ObjectIdSchema, + createTime: z.date(), + updateTime: z.date(), + name: z.string(), + description: z.string().optional(), + steps: z.string().default(''), + tools: z.array(SkillToolSchema), + datasets: z.array(z.any()) +}); +export type AiSkillSchemaType = z.infer; diff --git a/packages/global/core/app/formEdit/type.ts b/packages/global/core/app/formEdit/type.ts index 524f6dfb0..6da00c5fe 100644 --- a/packages/global/core/app/formEdit/type.ts +++ b/packages/global/core/app/formEdit/type.ts @@ -23,12 +23,11 @@ export const SkillEditTypeSchema = z.object({ id: z.string(), name: z.string(), description: z.string(), - prompt: z.string(), + stepsText: z.string().optional(), // 执行步骤的文本描述 dataset: z.object({ list: z.array(SelectedDatasetSchema) }), - selectedTools: z.array(SelectedToolItemTypeSchema), - fileSelectConfig: AppFileSelectConfigTypeSchema + selectedTools: z.array(SelectedToolItemTypeSchema) }); export type SkillEditType = z.infer; diff --git a/packages/global/core/chat/helperBot/skillAgent/type.ts b/packages/global/core/chat/helperBot/skillAgent/type.ts new file mode 100644 index 000000000..4e33fba5f --- /dev/null +++ b/packages/global/core/chat/helperBot/skillAgent/type.ts @@ -0,0 +1,62 @@ +import { z } from 'zod'; +import { topAgentParamsSchema } from '../topAgent/type'; + +// SkillAgent 参数配置 (区分 skill 特有配置和 topAgent 通用配置) +export const skillAgentParamsSchema = z.object({ + // Skill 特有配置 + skillAgent: z + .object({ + name: z.string().nullish(), + description: z.string().nullish(), + stepsText: z.string().nullish() + }) + .nullish(), + // TopAgent 通用配置 + topAgent: topAgentParamsSchema.nullish() +}); +export type SkillAgentParamsType = z.infer; + +/* 模型生成结构 */ +// 工具定义 +export const GeneratedSkillToolSchema = z.object({ + id: z.string(), + type: z.enum(['tool', 'knowledge']) +}); + +// 步骤定义 +export const GeneratedSkillStepSchema = z.object({ + id: z.string(), + title: z.string(), + description: z.string(), + expectedTools: z.array(GeneratedSkillToolSchema) +}); + +// 任务分析 +const TaskAnalysisSchema = z.object({ + name: z.string(), + description: z.string(), + goal: z.string(), + type: z.string() +}); + +// LLM 返回的完整数据 +export const GeneratedSkillDataCollectionSchema = z.object({ + phase: z.literal('collection'), + reasoning: z.string(), + question: z.string() +}); +export type GeneratedSkillDataCollectionType = z.infer; +export const GeneratedSkillResultSchema = z.object({ + phase: z.literal('generation'), + plan_analysis: TaskAnalysisSchema, + execution_plan: z.object({ + total_steps: z.number(), + steps: z.array(GeneratedSkillStepSchema) + }) +}); +export type GeneratedSkillResultType = z.infer; +export const GeneratedSkillSchema = z.union([ + GeneratedSkillDataCollectionSchema, + GeneratedSkillResultSchema +]); +export type GeneratedSkillType = z.infer; diff --git a/packages/global/core/chat/helperBot/topAgent/type.ts b/packages/global/core/chat/helperBot/topAgent/type.ts new file mode 100644 index 000000000..a115217cb --- /dev/null +++ b/packages/global/core/chat/helperBot/topAgent/type.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +// TopAgent 参数配置 +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() +}); +export type TopAgentParamsType = z.infer; diff --git a/packages/global/core/chat/helperBot/type.ts b/packages/global/core/chat/helperBot/type.ts index 505fd4a23..ae9a09967 100644 --- a/packages/global/core/chat/helperBot/type.ts +++ b/packages/global/core/chat/helperBot/type.ts @@ -5,7 +5,7 @@ import { UserChatItemSchema, SystemChatItemSchema, ToolModuleResponseItemSchema export enum HelperBotTypeEnum { topAgent = 'topAgent', - skillEditor = 'skillEditor' + skillAgent = 'skillAgent' } export const HelperBotTypeEnumSchema = z.enum(Object.values(HelperBotTypeEnum)); export type HelperBotTypeEnumType = z.infer; @@ -70,19 +70,3 @@ export const HelperBotChatItemSiteSchema = z }) .and(HelperBotChatRoleSchema); export type HelperBotChatItemSiteType = z.infer; - -/* 具体的 bot 的特有参数 */ - -// Top agent -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() -}); -export type TopAgentParamsType = z.infer; - -// Skill editor -export const skillEditorParamsSchema = z.object({}); -export type SkillEditorParamsType = z.infer; diff --git a/packages/global/core/workflow/runtime/constants.ts b/packages/global/core/workflow/runtime/constants.ts index d74e44fe8..164eeeb6d 100644 --- a/packages/global/core/workflow/runtime/constants.ts +++ b/packages/global/core/workflow/runtime/constants.ts @@ -19,7 +19,8 @@ export enum SseResponseEventEnum { agentPlan = 'agentPlan', // agent plan - formData = 'formData' // form data for TopAgent + formData = 'formData', // form data for TopAgent + generatedSkill = 'generatedSkill' // generated skill for SkillAgent } export enum DispatchNodeResponseKeyEnum { diff --git a/packages/global/openapi/core/ai/index.ts b/packages/global/openapi/core/ai/index.ts new file mode 100644 index 000000000..f5cf92515 --- /dev/null +++ b/packages/global/openapi/core/ai/index.ts @@ -0,0 +1,6 @@ +import type { OpenAPIPath } from '../../type'; +import { AISkillPath } from './skill'; + +export const AIPath: OpenAPIPath = { + ...AISkillPath +}; diff --git a/packages/global/openapi/core/ai/skill/api.ts b/packages/global/openapi/core/ai/skill/api.ts new file mode 100644 index 000000000..0861e7593 --- /dev/null +++ b/packages/global/openapi/core/ai/skill/api.ts @@ -0,0 +1,54 @@ +import { z } from 'zod'; +import { AiSkillSchema, SkillToolSchema } from '../../../../core/ai/skill/type'; +import { SelectedToolItemTypeSchema } from '../../../../core/app/formEdit/type'; +import { ObjectIdSchema } from '../../../../common/type/mongo'; + +export const ListAiSkillBody = z.object({ + appId: z.string(), + searchText: z.string().optional() +}); +export type ListAiSkillBodyType = z.infer; +// Simplified list item schema - only id and name +export const ListAiSkillItemSchema = z.object({ + _id: ObjectIdSchema, + name: z.string() +}); +export const ListAiSkillResponseSchema = z.array(ListAiSkillItemSchema); +export type ListAiSkillResponse = z.infer; + +export const GetAiSkillDetailQuery = z.object({ + id: z.string() +}); +export type GetAiSkillDetailQueryType = z.infer; +// Detail response with expanded tools +export const GetAiSkillDetailResponseSchema = AiSkillSchema.omit({ + tools: true, + teamId: true, + tmbId: true, + appId: true, + createTime: true, + updateTime: true +}).extend({ + tools: z.array(SelectedToolItemTypeSchema) +}); +export type GetAiSkillDetailResponse = z.infer; + +export const UpdateAiSkillBody = z.object({ + id: z.string().optional(), + appId: z.string(), // Required for creating new skill, optional for updating existing skill + name: z.string().optional(), + description: z.string().optional(), + steps: z.string().optional(), + tools: z.array(SkillToolSchema).optional(), + datasets: z.array(z.any()).optional() +}); +export type UpdateAiSkillBodyType = z.infer; +export const UpdateAiSkillResponseSchema = z.string(); +export type UpdateAiSkillResponse = z.infer; + +export const DeleteAiSkillQuery = z.object({ + id: z.string() +}); +export type DeleteAiSkillQueryType = z.infer; +export const DeleteAiSkillResponseSchema = z.object({}); +export type DeleteAiSkillResponse = z.infer; diff --git a/packages/global/openapi/core/ai/skill/index.ts b/packages/global/openapi/core/ai/skill/index.ts new file mode 100644 index 000000000..a92c0821b --- /dev/null +++ b/packages/global/openapi/core/ai/skill/index.ts @@ -0,0 +1,109 @@ +import type { OpenAPIPath } from '../../../type'; +import { + ListAiSkillBody, + GetAiSkillDetailQuery, + UpdateAiSkillBody, + DeleteAiSkillQuery +} from './api'; +import { TagsMap } from '../../../tag'; +import { z } from 'zod'; + +export const AISkillPath: OpenAPIPath = { + '/core/ai/skill/list': { + post: { + summary: '获取AI技能列表', + description: '获取指定应用的AI技能列表,支持分页和搜索', + tags: [TagsMap.aiSkill], + requestBody: { + content: { + 'application/json': { + schema: ListAiSkillBody + } + } + }, + responses: { + 200: { + description: '成功获取技能列表', + content: { + 'application/json': { + schema: z.object({ + list: z.array(z.any()), + total: z.number() + }) + } + } + } + } + } + }, + '/core/ai/skill/detail': { + get: { + summary: '获取AI技能详情', + description: '根据技能ID获取详细信息,会自动获取对应的应用权限进行鉴权', + tags: [TagsMap.aiSkill], + requestParams: { + query: GetAiSkillDetailQuery + }, + responses: { + 200: { + description: '成功获取技能详情', + content: { + 'application/json': { + schema: z.any() + } + } + } + } + } + }, + '/core/ai/skill/update': { + put: { + summary: '更新或创建AI技能', + description: + '使用 upsert 方式更新或创建AI技能。如果提供 id 则更新现有技能(会自动获取 appId 进行鉴权),如果不提供 id 则创建新技能(需提供 appId)', + tags: [TagsMap.aiSkill], + requestBody: { + content: { + 'application/json': { + schema: UpdateAiSkillBody + } + } + }, + responses: { + 200: { + description: '成功更新或创建技能', + content: { + 'application/json': { + schema: z.object({ + success: z.boolean(), + _id: z.string() + }) + } + } + } + } + } + }, + '/core/ai/skill/delete': { + delete: { + summary: '删除AI技能', + description: '根据技能ID删除AI技能,会自动获取对应的应用权限进行鉴权', + tags: [TagsMap.aiSkill], + requestParams: { + query: DeleteAiSkillQuery + }, + responses: { + 200: { + description: '成功删除技能', + content: { + 'application/json': { + schema: z.object({ + success: z.boolean() + }) + } + } + } + } + } + } +}; diff --git a/packages/global/openapi/core/chat/helperBot/api.ts b/packages/global/openapi/core/chat/helperBot/api.ts index a6f31b717..6c19f2514 100644 --- a/packages/global/openapi/core/chat/helperBot/api.ts +++ b/packages/global/openapi/core/chat/helperBot/api.ts @@ -1,11 +1,11 @@ -import { PaginationPropsSchema, PaginationResponseSchema } from '../../../type'; +import { PaginationPropsSchema } from '../../../type'; import { type HelperBotChatItemSiteType, HelperBotTypeEnum, - HelperBotTypeEnumSchema, - skillEditorParamsSchema, - topAgentParamsSchema + HelperBotTypeEnumSchema } from '../../../../core/chat/helperBot/type'; +import { topAgentParamsSchema } from '../../../../core/chat/helperBot/topAgent/type'; +import { skillAgentParamsSchema } from '../../../../core/chat/helperBot/skillAgent/type'; import { z } from 'zod'; import type { PaginationResponse } from '../../../../../web/common/fetch/type'; import { ChatFileTypeEnum } from '../../../../core/chat/constants'; @@ -61,8 +61,8 @@ export const HelperBotCompletionsParamsSchema = z.object({ data: topAgentParamsSchema }), z.object({ - type: z.literal(HelperBotTypeEnum.skillEditor), - data: skillEditorParamsSchema + type: z.literal(HelperBotTypeEnum.skillAgent), + data: skillAgentParamsSchema }) ]) }); diff --git a/packages/global/openapi/core/chat/helperBot/generatedSkill/api.ts b/packages/global/openapi/core/chat/helperBot/generatedSkill/api.ts new file mode 100644 index 000000000..55add6820 --- /dev/null +++ b/packages/global/openapi/core/chat/helperBot/generatedSkill/api.ts @@ -0,0 +1,53 @@ +import { z } from 'zod'; +import { PaginationPropsSchema } from '../../../../type'; +import type { PaginationResponse } from '../../../../../../web/common/fetch/type'; +import { type GeneratedSkillSiteType } from '../../../../../core/chat/helperBot/skillAgent/type'; + +// Save Generated Skill +export const SaveGeneratedSkillParamsSchema = z.object({ + appId: z.string(), + chatId: z.string(), + chatItemId: z.string(), + name: z.string(), + description: z.string().optional(), + steps: z.string().default(''), + status: z.enum(['draft', 'active', 'archived']).optional() +}); +export type SaveGeneratedSkillParamsType = z.infer; +export const SaveGeneratedSkillResponseSchema = z.object({ + _id: z.string() +}); +export type SaveGeneratedSkillResponseType = z.infer; + +// Get Generated Skills List +export const GetGeneratedSkillsParamsSchema = z + .object({ + appId: z.string(), + searchText: z.string().optional(), + status: z.enum(['draft', 'active', 'archived']).optional() + }) + .and(PaginationPropsSchema); +export type GetGeneratedSkillsParamsType = z.infer; +export type GetGeneratedSkillsResponseType = PaginationResponse; + +// Get Generated Skill Detail +export const GetGeneratedSkillDetailParamsSchema = z.object({ + id: z.string() +}); +export type GetGeneratedSkillDetailParamsType = z.infer; + +// Update Generated Skill +export const UpdateGeneratedSkillParamsSchema = z.object({ + id: z.string(), + name: z.string().optional(), + description: z.string().optional(), + steps: z.string().optional(), + status: z.enum(['draft', 'active', 'archived']).optional() +}); +export type UpdateGeneratedSkillParamsType = z.infer; + +// Delete Generated Skill +export const DeleteGeneratedSkillParamsSchema = z.object({ + id: z.string() +}); +export type DeleteGeneratedSkillParamsType = z.infer; diff --git a/packages/global/openapi/index.ts b/packages/global/openapi/index.ts index b44014081..3cdb2346b 100644 --- a/packages/global/openapi/index.ts +++ b/packages/global/openapi/index.ts @@ -6,6 +6,7 @@ import { PluginPath } from './core/plugin'; import { WalletPath } from './support/wallet'; import { CustomDomainPath } from './support/customDomain'; import { AppPath } from './core/app'; +import { AIPath } from './core/ai'; export const openAPIDocument = createDocument({ openapi: '3.1.0', @@ -20,7 +21,8 @@ export const openAPIDocument = createDocument({ ...ApiKeyPath, ...PluginPath, ...WalletPath, - ...CustomDomainPath + ...CustomDomainPath, + ...AIPath }, servers: [{ url: '/api' }], 'x-tagGroups': [ @@ -28,6 +30,14 @@ export const openAPIDocument = createDocument({ name: 'Agent 应用', tags: [TagsMap.appLog] }, + { + name: 'AI 相关', + tags: [TagsMap.aiSkill] + }, + { + name: '对话', + tags: [TagsMap.chatSetting, TagsMap.chatPage] + }, { name: '对话管理', tags: [TagsMap.chatHistory, TagsMap.chatPage, TagsMap.chatFeedback, TagsMap.chatSetting] diff --git a/packages/global/openapi/tag.ts b/packages/global/openapi/tag.ts index a6b95db18..725a5dea0 100644 --- a/packages/global/openapi/tag.ts +++ b/packages/global/openapi/tag.ts @@ -1,8 +1,11 @@ export const TagsMap = { /* Core */ + // Helper helperBot: '辅助助手', // Agent - log appLog: 'Agent 日志', + // Ai -skill + aiSkill: 'AI技能管理', // Chat - home chatPage: '对话页操作', diff --git a/packages/service/core/ai/skill/schema.ts b/packages/service/core/ai/skill/schema.ts new file mode 100644 index 000000000..1b2f581e8 --- /dev/null +++ b/packages/service/core/ai/skill/schema.ts @@ -0,0 +1,57 @@ +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +const { Schema } = connectionMongo; +import { AppCollectionName } from '../../app/schema'; +import type { AiSkillSchemaType } from '@fastgpt/global/core/ai/skill/type'; + +const AppAISkillSchema = new Schema({ + teamId: { + type: Schema.Types.ObjectId, + required: true, + ref: TeamCollectionName + }, + tmbId: { + type: Schema.Types.ObjectId, + required: true, + ref: TeamMemberCollectionName + }, + appId: { + type: Schema.Types.ObjectId, + required: true, + ref: AppCollectionName + }, + createTime: { + type: Date, + default: () => new Date() + }, + updateTime: { + type: Date, + default: () => new Date() + }, + + name: { + type: String, + required: true + }, + description: String, + steps: { + type: String, + default: '' + }, + tools: { + type: [Object], + default: [] + }, + datasets: { + type: [Object], + default: [] + } +}); + +// 复合索引 +AppAISkillSchema.index({ teamId: 1, appId: 1, updateTime: -1 }); + +export const MongoAiSkill = getMongoModel('app_ai_skills', AppAISkillSchema); diff --git a/packages/service/core/chat/HelperBot/dispatch/index.ts b/packages/service/core/chat/HelperBot/dispatch/index.ts index 78fdc066e..e22f17050 100644 --- a/packages/service/core/chat/HelperBot/dispatch/index.ts +++ b/packages/service/core/chat/HelperBot/dispatch/index.ts @@ -1,8 +1,8 @@ import { HelperBotTypeEnum } from '@fastgpt/global/core/chat/helperBot/type'; import { dispatchTopAgent } from './topAgent'; -import { dispatchSkillEditor } from './skillEditor'; +import { dispatchSkillAgent } from './skillAgent'; export const dispatchMap = { [HelperBotTypeEnum.topAgent]: dispatchTopAgent, - [HelperBotTypeEnum.skillEditor]: dispatchSkillEditor + [HelperBotTypeEnum.skillAgent]: dispatchSkillAgent }; diff --git a/packages/service/core/chat/HelperBot/dispatch/skillAgent/index.ts b/packages/service/core/chat/HelperBot/dispatch/skillAgent/index.ts new file mode 100644 index 000000000..35dc29115 --- /dev/null +++ b/packages/service/core/chat/HelperBot/dispatch/skillAgent/index.ts @@ -0,0 +1,149 @@ +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 { generateResourceList } from '../topAgent/utils'; +import { addLog } from '../../../../../common/system/log'; +import { formatAIResponse } from '../utils'; +import { + type SkillAgentParamsType, + type GeneratedSkillType, + GeneratedSkillResultSchema +} from '@fastgpt/global/core/chat/helperBot/skillAgent/type'; +import { parseJsonArgs } from '../../../../ai/utils'; + +export const dispatchSkillAgent = async ( + props: HelperBotDispatchParamsType +): Promise => { + const { query, files, data, histories, workflowResponseWrite, user } = props; + + const modelData = getLLMModel(); + 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, + isRoot: user.isRoot + }); + + const systemPrompt = getPrompt({ + resourceList, + metadata: data + }); + + const historyMessages = helperChats2GPTMessages({ + messages: histories, + reserveTool: false + }); + const conversationMessages = [ + { role: 'system' as const, content: systemPrompt }, + ...historyMessages, + { role: 'user' as const, content: query } + ]; + + console.dir(conversationMessages, { depth: null }); + // Single LLM call - LLM self-determines phase and outputs corresponding format + const llmResponse = await createLLMResponse({ + body: { + messages: conversationMessages, + model: modelData, + stream: true + }, + onStreaming: ({ text }) => { + workflowResponseWrite?.({ + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ text }) + }); + }, + onReasoning: ({ text }) => { + workflowResponseWrite?.({ + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ reasoning_content: text }) + }); + } + }); + + usage.inputTokens = llmResponse.usage.inputTokens; + usage.outputTokens = llmResponse.usage.outputTokens; + + const answerText = llmResponse.answerText; + const reasoningText = llmResponse.reasoningText; + + // Parse JSON response + try { + const responseJson = parseJsonArgs(answerText); + + if (!responseJson) { + addLog.warn(`[Skill agent] Failed to parse JSON response`, { text: answerText }); + throw new Error('Failed to parse JSON response'); + } + console.log(responseJson, 22323); + // Handle based on phase field + if (responseJson.phase === 'generation') { + addLog.debug('🔄 SkillAgent: Generated skill generation phase'); + + const parseResult = GeneratedSkillResultSchema.safeParse(responseJson).data; + + if (!parseResult) { + addLog.warn(`[Skill agent] Failed to parse JSON response`, { responseJson }); + throw new Error('Failed to parse JSON response'); + } + + // Send generatedSkill event + workflowResponseWrite?.({ + event: SseResponseEventEnum.generatedSkill, + data: parseResult + }); + + // Return original format (backward compatible) + return { + aiResponse: formatAIResponse({ + text: answerText, + reasoning: reasoningText + }), + usage + }; + } else if (responseJson.phase === 'collection') { + addLog.debug('📝 SkillAgent: Information collection phase'); + + const displayText = responseJson.question || answerText; + return { + aiResponse: formatAIResponse({ + text: displayText, + reasoning: responseJson.reasoning || reasoningText + }), + usage + }; + } else { + // Unknown phase + addLog.warn(`[Skill agent] Unknown phase`, responseJson); + return { + aiResponse: formatAIResponse({ + text: answerText, + reasoning: reasoningText + }), + usage + }; + } + } catch (e) { + // JSON parse failed - return original text + addLog.warn(`[Skill agent] Failed to parse JSON response`, { text: answerText }); + return { + aiResponse: formatAIResponse({ + text: answerText, + reasoning: reasoningText + }), + usage + }; + } +}; diff --git a/packages/service/core/chat/HelperBot/dispatch/skillAgent/prompt.ts b/packages/service/core/chat/HelperBot/dispatch/skillAgent/prompt.ts new file mode 100644 index 000000000..8c108d564 --- /dev/null +++ b/packages/service/core/chat/HelperBot/dispatch/skillAgent/prompt.ts @@ -0,0 +1,425 @@ +import type { SkillAgentParamsType } from '@fastgpt/global/core/chat/helperBot/skillAgent/type'; +import { buildSkillAgentMetadataInfo } from './utils'; + +export const getPrompt = ({ + resourceList, + metadata +}: { + resourceList: string; + metadata?: SkillAgentParamsType; +}) => { + const currentConfigContext = buildSkillAgentMetadataInfo(metadata); + // console.log('------currentConfigContext------', currentConfigContext); + return ` + + +你是一个**任务执行流程设计专家**,专门将任务目标转化为清晰的顺序执行步骤。 + +**核心价值**:提供简洁、清晰、可执行的步骤列表,帮助用户完成任务。 + +**核心能力**: +- 任务分解:将任务拆解为清晰的执行步骤 +- 工具匹配:为每个步骤选择合适的工具 +- 流程组织:按照逻辑顺序组织步骤 + + + +**核心目标**:设计一个顺序执行的流程来完成任务,包含: +1. **清晰的步骤**:每个步骤说明要做什么 +2. **工具指定**:每个步骤使用哪个工具 +3. **顺序执行**:从第一步到最后一步,依次执行 + +**输出价值**: +- 用户可以按照步骤顺序完成任务 +- 每个步骤明确使用的工具 +- 流程简单直接,易于理解 + + +${currentConfigContext} + + +**如果有前置信息**(任务目标、工具列表等): +- 这些是已经确定的,不需要重复询问 +- 你的任务是将它们组织成顺序执行的步骤 +- 重点是"按什么顺序做什么" + + + +**系统功能处理**(如果预设计划中包含系统功能配置): + +如果预设计划中已包含系统功能配置(如文件上传),你需要: +1. **识别已启用的系统功能** + - 检查 resources.system_features 中哪些功能已启用 + - 理解每个系统功能的目的和使用场景 + +2. **在步骤设计中考虑系统功能** + - 如果文件上传已启用:在第一步或相关步骤中说明需要用户上传文件 + - 在步骤描述中明确说明文件的作用和处理方式 + - 考虑文件处理相关的步骤(如数据提取、格式转换等) + +3. **系统功能不在 expected_tools 中** + - 系统功能是平台级配置,不是工具调用 + - expected_tools 中只包含实际执行操作的工具和知识库 + - 系统功能已在前期确定,步骤设计时只需要考虑其影响即可 + + + +当处于信息收集阶段时: + +**信息收集目标**:收集设计执行步骤所需的信息: + +1. **步骤分解** + - 完成任务需要哪些步骤? + - 每个步骤的目的是什么? + - 步骤的执行顺序是什么? + +2. **工具使用** + - 每个步骤应该使用哪个工具? + - 是否有步骤需要使用多个工具? + +3. **流程细节** + - 任务的起点是什么? + - 任务的终点是什么? + - 是否需要中间验证步骤? + +**关键原则**: +- **简单直接**:不考虑复杂的分支和条件,就是顺序执行 +- **基于已知**:使用已确定的工具列表 +- **用户友好**:步骤描述清晰,容易理解 + +**输出格式**: + +**重要:信息收集阶段的所有回复必须使用JSON格式,包含 phase 字段** + +直接输出以下格式的JSON(不要添加代码块标记): +{ + "phase": "collection", + "reasoning": "为什么问这个问题的推理过程:基于什么考虑、希望收集什么信息、对后续有什么帮助", + "question": "实际向用户提出的问题内容" +} + +问题内容可以是开放式问题,也可以包含选项: + +开放式问题示例: +{ + "phase": "collection", + "reasoning": "需要首先了解任务的基本定位和目标场景,这将决定后续需要确认的工具类型和步骤设计", + "question": "我想了解一下您希望这个执行流程实现什么功能?能否详细描述一下具体要处理什么样的任务?" +} + +选择题示例: +{ + "phase": "collection", + "reasoning": "需要确认步骤设计的重点方向,这将影响流程的详细程度", + "question": "关于流程的设计,您更关注:\\nA. 步骤的详细程度(每个步骤都很详细)\\nB. 步骤的灵活性(可以根据情况调整)\\nC. 步骤的简洁性(尽可能少的步骤)\\nD. 其他\\n\\n请选择最符合的选项,或输入您的详细回答:" +} + +适合选择题的场景: +- 经验水平判断(初学者/有经验/熟练/专家) +- 优先级排序(时间/质量/成本/创新) +- 任务类型分类(分析/设计/开发/测试) +- 复杂度判断(简单/中等/复杂/极复杂) + +避免的行为: +- 不要为所有问题都强制提供选项 +- 选项之间要有明显的区分度 +- 不要使用过于技术化的术语 + + + +**系统能力边界确认**: + +**动态约束原则**: +1. **只规划现有能力**:只能使用系统当前提供的工具和功能 +2. **基于实际能力判断**:如果系统有编程工具,就可以规划编程任务 +3. **能力适配规划**:根据可用工具库的能力边界来设计流程 +4. **避免能力假设**:不能假设系统有未明确提供的能力 + +**规划前自我检查**: +- 这个步骤需要什么具体能力? +- 当前系统中是否有对应的工具提供这种能力? +- 用户是否具备使用该工具的条件? +- 如果没有合适的工具,能否用现有能力组合实现? + +**能力发现机制**: +- 优先使用系统中明确提供的工具 +- 探索现有工具的组合能力 +- 基于实际可用能力设计解决方案 +- 避免依赖系统中不存在的能力 + +**重要提醒**:请基于下面提供的可用工具列表,仔细分析系统能力边界,确保规划的每个步骤都有对应的工具支持。 + + + +**系统资源定义**(重要:理解三类资源的本质区别) + +**工具 (Tool)**: +- 定义:可以执行特定功能的能力模块 +- 功能:执行操作、调用API、处理数据、生成内容等 +- 特点:主动执行,产生结果或副作用 +- 示例:搜索引擎、数据库操作、邮件发送、内容生成 + +**知识库 (Knowledge)**: +- 定义:系统上已经搭建好的文件存储系统,包含特定领域的结构化信息 +- 功能:存储和检索信息,提供领域知识查询 +- 特点:被动查询,返回已存储的信息 +- 示例:产品文档库、技术手册、行业知识库 + +**系统功能 (System Features)**: +- 定义:平台级的功能开关,控制执行流程的特殊能力 +- 功能:影响任务执行方式的系统级配置 +- 特点:开关控制,改变交互模式 +- 示例:文件上传、用户交互、实时数据流 + +**关键区别**: +- 工具 = "做事情"(执行动作、调用服务、处理数据) +- 知识库 = "查信息"(检索已有知识、获取领域信息) +- 系统功能 = "改变模式"(启用特殊交互方式、系统级能力) + +**选择建议**: +- 需要执行操作(搜索、发送、计算、转换)→ 选择工具 +- 需要查询特定领域的信息(产品资料、技术文档、行业知识)→ 选择知识库 +- 需要用户提供文件/特殊交互方式 → 启用系统功能 +- 三者可以配合使用:例如用搜索工具获取实时信息,用知识库补充领域知识,启用文件上传让用户提供私有数据 + + + +当处于计划生成阶段时: + +**可用资源列表**: +""" +${resourceList} +""" + +**计划生成要求**: +1. 严格按照JSON格式输出 +2. **严格确保所有引用的工具都在可用工具列表中** - 这是硬性要求 +3. 考虑搭建者的实际约束条件(时间、资源、技能等) +4. 计划要具体、可执行、步骤清晰,适合作为流程模板 +5. **绝不要使用任何不在可用工具列表中的工具** - 违背此项将导致计划被拒绝 + +**🚨 资源使用严格限制(极其重要)**: + +**资源识别规则**: +1. 在上面的"## 可用资源列表"中查找所有可用资源 +2. 每个资源ID后面都有标签:[工具] 或 [知识库] +3. 输出时必须根据标签确定 type 值: + - 标签是 [工具] → "type": "tool" + - 标签是 [知识库] → "type": "knowledge" +4. **系统功能不在 expected_tools 中**: + - 系统功能(如file_upload)已在前期确定,不需要在步骤的 expected_tools 中列出 + - expected_tools 只包含实际执行操作的工具和知识库 + - 如果预设计划中启用了文件上传,只需在步骤描述中说明,不要在 expected_tools 中添加 + +**输出格式要求**: +- ✅ expected_tools 必须使用对象数组格式:[{"id": "...", "type": "..."}] +- ✅ 资源ID必须完全匹配列表中的ID(包括大小写、特殊字符) +- ❌ 不要使用字符串数组格式:["...", "..."] +- ❌ 不要猜测 type 值,必须根据列表中的标签确定 + +**输出前的自我检查步骤**: +1. 查看你选择的每个资源ID,它在列表中的标签是什么? +2. 如果标签是 [工具] → 设置 "type": "tool" +3. 如果标签是 [知识库] → 设置 "type": "knowledge" +4. 确保每个资源都有 id 和 type 两个字段 + +**常见错误避免**: +- ❌ 不要凭空想象资源名称 +- ❌ 不要使用通用描述如"数据库工具"而不指定具体资源ID +- ❌ 不要引用"可能"存在但未在列表中明确的资源 +- ❌ 不要输出字符串数组,必须是对象数组 +- ❌ 不要把 [知识库] 标签的资源设置为 type: "tool" +- ✅ 必须根据列表中的标签准确设置 type 值 +- ✅ 基于实际可用的资源进行规划 + +**深度分析框架**(内部思考过程,不输出): +🔍 第一层:任务本质分析 +- 识别用户的核心目标和真实意图 +- 分析任务的复杂度、范围和关键约束 +- 确定主要的功能需求和预期成果 + +📋 第二层:阶段划分 +- 将任务分解为逻辑清晰的执行阶段 +- 识别每个阶段的具体子任务和依赖关系 +- 确保阶段间的逻辑连贯性和合理性 + +🛠️ 第三层:工具类别匹配 +根据每个阶段的功能需求,建议最合适的工具类别: +- 信息获取 → 搜索工具类(搜索引擎、网页抓取、API查询) +- 数据处理 → 数据处理类(数据库操作、数据分析、格式转换) +- 内容创建 → 内容生成类(文本生成、图像创作、PPT制作) +- 服务集成 → API集成类(第三方服务、邮件发送、消息通知) +- 实用操作 → 实用工具类(编码解码、文件处理、实用操作) + +🎯 第四层:精确工具选择 +在确定的类别中,建议最合适的1-2个具体工具: +- 基于任务细节选择功能最匹配的工具 +- 考虑工具组合的协同效应 +- 确保工具调用格式的准确性 + +**输出格式要求**: +**重要**:只输出JSON,不要有任何其他文字说明、注释或markdown标记。 +直接输出以下格式的JSON: +{ + "phase": "generation", + "plan_analysis": { + "name": "简洁的计划名称(英文,适合作为函数名)", + "description": "详细的计划描述,说明这个计划的功能和适用场景,用于后续plan匹配", + "goal": "任务的核心目标描述", + "type": "任务类型分类" + }, + "execution_plan": { + "total_steps": 数字, + "steps": [ + { + "id": "step1", + "title": "简洁明确的步骤标题", + "description": "使用@[资源名称]格式的简洁任务描述,明确指出要做什么", + "expectedTools": [ + {"id": "资源ID1", "type": "tool或knowledge"}, + {"id": "资源ID2", "type": "tool或knowledge"} + ] + } + ] + } +} + +**字段说明**: +- name: 简洁的英文计划名称,使用驼峰命名或下划线,例如:"createMarketingReport", "analyzeCustomerData" +- description: 详细描述计划的功能、使用场景和输出结果,用于智能匹配用户需求 +- description应该包含:功能说明、适用场景、预期输出、关键特性等 + +资源使用格式: +- 在description中使用@[资源名称]格式引用资源 +- 在expected_tools中使用对象数组格式列出资源 +- 每个对象必须包含 id 和 type 两个字段 +- type 值根据资源列表中的标签确定([工具]→"tool",[知识库]→"knowledge") +- 确保资源引用的准确性 +- 优先选择功能最匹配的资源 + +工具选择原则: +1. 功能匹配优先:选择最能满足步骤需求的工具 +2. 组合优化:多个工具可以组合使用以获得更好效果 +3. 逻辑连贯:确保工具选择的逻辑性 +4. 简洁高效:避免不必要的工具冗余 + +**✅ 正确示例**: +{ + "phase": "generation", + "plan_analysis": { + "name": "createTravelItinerary", + "description": "创建旅游行程计划,包括景点查询、天气预报、行程文档生成。适用于需要规划旅行的场景,输出完整的markdown格式行程文档。", + "goal": "为用户规划详细的旅游行程", + "type": "内容生成" + }, + "execution_plan": { + "total_steps": 3, + "steps": [ + { + "id": "step1", + "title": "查询旅游目的地信息", + "description": "使用@[travel_destinations]知识库查询目的地的景点、美食、住宿等详细信息", + "expectedTools": [ + {"id": "travel_destinations", "type": "knowledge"} + ] + }, + { + "id": "step2", + "title": "查询实时天气信息", + "description": "使用@[mojiWeather/tool]工具获取目的地未来7天的天气预报", + "expectedTools": [ + {"id": "mojiWeather/tool", "type": "tool"} + ] + }, + { + "id": "step3", + "title": "生成行程计划文档", + "description": "使用@[markdownTransform]工具将行程信息格式化为markdown文档", + "expectedTools": [ + {"id": "markdownTransform", "type": "tool"} + ] + } + ] + } +} + +**❌ 错误示例1**(使用字符串数组而非对象数组): +{ + "expectedTools": ["travel_destinations", "mojiWeather/tool"] // ❌ 应该是对象数组 +} + +**❌ 错误示例2**(type 值错误): +{ + "expectedTools": [ + {"id": "travel_destinations", "type": "tool"} // ❌ 这是知识库,应该是 "knowledge" + ] +} + +**❌ 错误示例3**(缺少必需字段): +{ + "expectedTools": [ + {"id": "mojiWeather/tool"} // ❌ 缺少 type 字段 + ] +} + +质量要求: +1. 任务理解深度:确保每个计划都基于对用户需求的深度理解 +2. 逻辑严谨性:步骤间要有清晰的逻辑关系和依赖关系 +3. 工具匹配精度:每个工具的选择都要有明确的理由 +4. 输出格式规范:严格遵循JSON格式要求,expected_tools必须是对象数组 +5. 描述简洁性:步骤描述要简洁明了,只说明做什么 +6. 资源类型准确:type值必须根据资源列表中的标签准确设置 + + + +**🎯 关键:如何判断当前应该处于哪个阶段** + +**每次回复前,你必须自主评估以下问题**: + +1. **信息充分性评估**: + - 我是否已经明确了解要完成的任务目标? + - 我是否知道需要使用哪些工具和资源? + - 我是否了解任务的关键约束条件? + - 如果上述问题有任何不确定,应该输出 \`"phase": "collection"\` 继续提问 + +2. **配置生成时机判断**: + - 满足以下**所有条件**时,才能输出 \`"phase": "generation"\`: + * 已经明确任务的核心目标和场景 + * 已经确认可用的工具和资源 + * 已经收集到足够信息来设计执行步骤 + * 对话轮次达到 2-4 轮(避免过早生成) + +3. **阶段回退机制**: + - 如果用户在配置生成后继续发送消息 + - 评估新信息: + * 如果是小调整(修改步骤、工具选择等)→ 输出 \`"phase": "generation"\` 生成新计划 + * 如果发现核心需求变化或信息不足 → 输出 \`"phase": "collection"\` 回退继续提问 + +**重要原则**: +- ❌ 不要在第一轮对话就生成计划(除非用户提供了极其详细的需求) +- ❌ 不要在信息不足时强行生成计划 +- ✅ 宁可多问一两个问题,也不要生成不准确的计划 +- ✅ 当确信信息充分时,果断切换到计划生成阶段 +- ✅ 支持灵活的阶段切换,包括从计划生成回退到信息收集 + + + +**回复格式要求**: +- **所有回复必须是 JSON 格式**,包含 \`phase\` 字段 +- 信息收集阶段:输出 \`{"phase": "collection", "reasoning": "...", "question": "..."}\` +- 计划生成阶段:输出 \`{"phase": "generation", "plan_analysis": {...}, "execution_plan": {...}}\` +- ❌ 不要输出任何非 JSON 格式的内容 +- ❌ 不要添加代码块标记(如 \\\`\\\`\\\`json) + +**特殊场景处理**: +- 如果用户明确要求"直接生成计划",即使信息不足也应输出 \`"phase": "generation"\` +- 如果用户说"重新开始"或"从头来过",回到 \`"phase": "collection"\` 重新收集 +- 避免过度询问,通常 2-4 轮即可完成信息收集 + +**质量保证**: +- 收集的信息要具体、准确、可验证 +- 生成的计划要基于收集到的信息 +- 确保计划中的每个步骤都是可执行的 +- 严格基于系统能力边界进行规划 +`; +}; diff --git a/packages/service/core/chat/HelperBot/dispatch/skillAgent/utils.ts b/packages/service/core/chat/HelperBot/dispatch/skillAgent/utils.ts new file mode 100644 index 000000000..0b19440bf --- /dev/null +++ b/packages/service/core/chat/HelperBot/dispatch/skillAgent/utils.ts @@ -0,0 +1,55 @@ +import type { SkillAgentParamsType } from '@fastgpt/global/core/chat/helperBot/skillAgent/type'; + +export const buildSkillAgentMetadataInfo = (metadata?: SkillAgentParamsType): string => { + if (!metadata) return ''; + + const sections: string[] = []; + + if (metadata.skillAgent) { + const { name, description, stepsText } = metadata.skillAgent; + if (name || description || stepsText) { + sections.push('**Skill 配置** (当前 Skill 的专属配置):'); + if (name) sections.push(`- skill名称: ${name}`); + if (description) sections.push(`- skill描述: ${description}`); + if (stepsText) sections.push(`- 详细的步骤信息: ${stepsText}`); + } + } + + if (metadata.topAgent) { + const topAgentSections: string[] = []; + const { role, taskObject, selectedTools, selectedDatasets, fileUpload } = metadata.topAgent; + + if (role) topAgentSections.push(`- 预设角色: ${role}`); + if (taskObject) topAgentSections.push(`- 预设任务目标: ${taskObject}`); + if (selectedTools && selectedTools.length > 0) { + topAgentSections.push(`- 预设工具: ${selectedTools.join(', ')}`); + } + if (selectedDatasets && selectedDatasets.length > 0) { + topAgentSections.push(`- 预设知识库: ${selectedDatasets.join(', ')}`); + } + if (fileUpload !== undefined && fileUpload !== null) { + topAgentSections.push(`- 文件上传: ${fileUpload ? '已启用' : '已禁用'}`); + } + + if (topAgentSections.length > 0) { + sections.push('\n**Agent 的整体背景信息配置** :'); + sections.push(...topAgentSections); + } + } + + if (sections.length === 0) return ''; + + return ` + +搭建者已提供以下预设信息,这些信息具有**高优先级**,请在后续的信息收集和规划中优先参考: + +${sections.join('\n')} + +**重要提示**: +- **Skill 配置**优先级最高,如果已有 name、description、stepsText,说明这是已配置的 skill,应基于这些信息执行任务 +- **TopAgent 通用配置**提供了全局的角色、工具、知识库等配置,可以在 skill 执行中使用 +- 在信息收集阶段,如果预设信息已经提供了某个维度的答案,可以跳过相关问题 +- 在规划阶段,**优先使用**预设的工具和知识库 + +`; +}; diff --git a/packages/service/core/chat/HelperBot/dispatch/skillEditor/index.ts b/packages/service/core/chat/HelperBot/dispatch/skillEditor/index.ts deleted file mode 100644 index 937bac3e5..000000000 --- a/packages/service/core/chat/HelperBot/dispatch/skillEditor/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { HelperBotDispatchParamsType, HelperBotDispatchResponseType } from '../type'; - -export const dispatchSkillEditor = async ( - props: HelperBotDispatchParamsType -): Promise => { - console.log(props, 22222); - return { - aiResponse: [], - usage: { - model: '', - inputTokens: 0, - outputTokens: 0 - } - }; -}; diff --git a/packages/service/core/chat/HelperBot/dispatch/topAgent/index.ts b/packages/service/core/chat/HelperBot/dispatch/topAgent/index.ts index da75df3ed..ce9b4e1d9 100644 --- a/packages/service/core/chat/HelperBot/dispatch/topAgent/index.ts +++ b/packages/service/core/chat/HelperBot/dispatch/topAgent/index.ts @@ -9,7 +9,7 @@ import { generateResourceList } from './utils'; import { TopAgentFormDataSchema } from './type'; import { addLog } from '../../../../../common/system/log'; import { formatAIResponse } from '../utils'; -import type { TopAgentParamsType } from '@fastgpt/global/core/chat/helperBot/type'; +import type { TopAgentParamsType } from '@fastgpt/global/core/chat/helperBot/topAgent/type'; export const dispatchTopAgent = async ( props: HelperBotDispatchParamsType diff --git a/packages/service/core/chat/HelperBot/dispatch/topAgent/prompt.ts b/packages/service/core/chat/HelperBot/dispatch/topAgent/prompt.ts index af1c2f527..053479fdf 100644 --- a/packages/service/core/chat/HelperBot/dispatch/topAgent/prompt.ts +++ b/packages/service/core/chat/HelperBot/dispatch/topAgent/prompt.ts @@ -1,4 +1,4 @@ -import type { TopAgentParamsType } from '@fastgpt/global/core/chat/helperBot/type'; +import type { TopAgentParamsType } from '@fastgpt/global/core/chat/helperBot/topAgent/type'; import { buildMetadataInfo } from './utils'; export const getPrompt = ({ diff --git a/packages/service/core/chat/HelperBot/dispatch/topAgent/utils.ts b/packages/service/core/chat/HelperBot/dispatch/topAgent/utils.ts index 971f915fd..bfe0a3008 100644 --- a/packages/service/core/chat/HelperBot/dispatch/topAgent/utils.ts +++ b/packages/service/core/chat/HelperBot/dispatch/topAgent/utils.ts @@ -1,6 +1,6 @@ import type { localeType } from '@fastgpt/global/common/i18n/type'; import { getSystemToolsWithInstalled } from '../../../../app/tool/controller'; -import type { TopAgentParamsType } from '@fastgpt/global/core/chat/helperBot/type'; +import type { TopAgentParamsType } from '@fastgpt/global/core/chat/helperBot/topAgent/type'; export const generateResourceList = async ({ teamId, isRoot, diff --git a/packages/service/core/chat/HelperBot/utils.ts b/packages/service/core/chat/HelperBot/utils.ts index 761b71eda..16c1a23e1 100644 --- a/packages/service/core/chat/HelperBot/utils.ts +++ b/packages/service/core/chat/HelperBot/utils.ts @@ -1,10 +1,10 @@ -import type { HelperBotTypeEnum } from '@fastgpt/global/core/chat/helperBot/type'; -import type { HelperBotCompletionsParamsType } from '@fastgpt/global/openapi/core/chat/helperBot/api'; -import { MongoHelperBotChat } from './chatSchema'; import type { AIChatItemValueItemType, - UserChatItemValueItemType -} from '@fastgpt/global/core/chat/type'; + HelperBotTypeEnum +} from '@fastgpt/global/core/chat/helperBot/type'; +import type { HelperBotCompletionsParamsType } from '@fastgpt/global/openapi/core/chat/helperBot/api'; +import { MongoHelperBotChat } from './chatSchema'; +import type { UserChatItemValueItemType } from '@fastgpt/global/core/chat/type'; import { MongoHelperBotChatItem } from './chatItemSchema'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { mongoSessionRun } from '../../../common/mongo/sessionRun'; diff --git a/packages/service/core/workflow/dispatch/ai/agent/index.ts b/packages/service/core/workflow/dispatch/ai/agent/index.ts index 361f34822..94a7a3410 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/index.ts @@ -36,6 +36,7 @@ import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type import { addLog } from '../../../../../common/system/log'; import { checkTaskComplexity } from './master/taskComplexity'; import { getNanoid } from '@fastgpt/global/common/string/tools'; +import { matchSkillForPlan } from './skillMatcher'; export type DispatchAgentModuleProps = ModuleDispatchProps<{ [NodeInputKeyEnum.history]?: ChatItemType[]; @@ -163,7 +164,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise if (taskIsComplexity) { /* ===== Plan Agent ===== */ - const planCallFn = async () => { + const planCallFn = async (referencePlanSystemPrompt?: string) => { // 点了确认。此时肯定有 agentPlans if ( lastInteractive?.type === 'agentPlanCheck' && @@ -179,7 +180,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise interactive: lastInteractive, subAppList, getSubAppInfo, - systemPrompt, + systemPrompt: referencePlanSystemPrompt || systemPrompt, model, temperature, top_p: aiChatTopP, @@ -349,9 +350,36 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise // Replan step: 已有 plan,且有 replan 历史消息 const isReplanStep = isPlanAgent && agentPlan && replanMessages; + // 🆕 执行 Skill 匹配(仅在 isPlanStep 且没有 planHistoryMessages 时) + let matchedSkillSystemPrompt: string | undefined; + + console.log('planHistoryMessages', planHistoryMessages); // 执行 Plan/replan if (isPlanStep) { - const result = await planCallFn(); + // match skill + addLog.debug('尝试匹配用户的历史 skills'); + const matchResult = await matchSkillForPlan({ + teamId: runningUserInfo.teamId, + appId: runningAppInfo.id, + userInput: lastInteractive ? interactiveInput : userChatInput, + model + }); + if (matchResult.matched && matchResult.systemPrompt) { + addLog.debug(`匹配到 skill: ${matchResult.skill?.name}`); + matchedSkillSystemPrompt = matchResult.systemPrompt; + + // 可选: 推送匹配信息给前端 + workflowStreamResponse?.({ + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text: `📋 找到参考技能: ${matchResult.systemPrompt}` + }) + }); + } else { + addLog.debug(`未匹配到 skill,原因: ${matchResult.reason}`); + } + + const result = await planCallFn(matchedSkillSystemPrompt); // 有 result 代表 plan 有交互响应(check/ask) if (result) return result; } else if (isReplanStep) { diff --git a/packages/service/core/workflow/dispatch/ai/agent/skillMatcher.ts b/packages/service/core/workflow/dispatch/ai/agent/skillMatcher.ts new file mode 100644 index 000000000..42998b47e --- /dev/null +++ b/packages/service/core/workflow/dispatch/ai/agent/skillMatcher.ts @@ -0,0 +1,205 @@ +import { MongoAiSkill } from '../../../../ai/skill/schema'; +import type { AiSkillSchemaType } from '@fastgpt/global/core/ai/skill/type'; +import { createLLMResponse } from '../../../../ai/llm/request'; +import type { ChatCompletionMessageParam, ChatCompletionTool } from '@fastgpt/global/core/ai/type'; +import { getLLMModel } from '../../../../ai/model'; + +/** + * 生成唯一函数名 + * 参考 MatcherService.ts 的 _generateUniqueFunctionName + */ +const generateUniqueFunctionName = (skill: HelperBotGeneratedSkillType): string => { + let baseName = skill.name || skill._id.toString(); + + // 清理名称 + let cleanName = baseName.replace(/[^a-zA-Z0-9_]/g, '_'); + + if (cleanName && !/^[a-zA-Z_]/.test(cleanName)) { + cleanName = 'skill_' + cleanName; + } else if (!cleanName) { + cleanName = 'skill_unknown'; + } + + const timestampSuffix = Date.now().toString().slice(-6); + // return `${cleanName}_${timestampSuffix}`; + return `${cleanName}`; +}; + +/** + * 构建 Skill Tools 数组 + * 参考 MatcherService.ts 的 match 函数 + */ +export const buildSkillTools = ( + skills: HelperBotGeneratedSkillType[] +): { + tools: ChatCompletionTool[]; + skillsMap: Record; +} => { + const tools: ChatCompletionTool[] = []; + const skillsMap: Record = {}; + + for (const skill of skills) { + // 生成唯一函数名 + const functionName = generateUniqueFunctionName(skill); + skillsMap[functionName] = skill; + + // 构建 description + let description = skill.description || 'No description available'; + + tools.push({ + type: 'function', + function: { + name: functionName, + description: description, + parameters: { + type: 'object', + properties: {}, + required: [] + } + } + }); + } + + return { tools, skillsMap }; +}; + +/** + * 格式化 Skill 为 SystemPrompt + * 将匹配到的 skill 格式化为 XML 提示词 + */ +export const formatSkillAsSystemPrompt = (skill: HelperBotGeneratedSkillType): string => { + let prompt = '\n'; + prompt += `**参考技能**: ${skill.name}\n\n`; + + if (skill.description) { + prompt += `**描述**: ${skill.description}\n\n`; + } + + if (skill.steps && skill.steps.trim()) { + prompt += `**步骤信息**:\n${skill.steps}\n\n`; + } + + prompt += '**说明**:\n'; + prompt += '1. 以上是用户之前保存的类似任务的执行计划\n'; + prompt += '2. 请参考该技能的步骤流程和工具选择\n'; + prompt += '3. 根据当前用户的具体需求,调整和优化计划\n'; + prompt += '4. 保持步骤的逻辑性和完整性\n'; + prompt += '\n'; + + return prompt; +}; + +/** + * 主匹配函数 + * 参考 MatcherService.ts 的 match 方法 + */ +export const matchSkillForPlan = async ({ + teamId, + appId, + userInput, + model +}: { + teamId: string; + appId: string; + userInput: string; + model: string; +}): Promise<{ + matched: boolean; + skill?: HelperBotGeneratedSkillType; + systemPrompt?: string; + reason?: string; +}> => { + try { + // 1. 查询用户的 skills (使用 teamId 和 appId) + const skills = await MongoAiSkill.find({ + teamId, + appId, + status: { $in: ['active', 'draft'] } + }) + .sort({ createTime: -1 }) + .limit(50) // 限制数量,避免 tools 过多 + .lean(); + console.log('skill list length', skills.length); + console.log('skill', skills); + if (!skills || skills.length === 0) { + return { matched: false, reason: 'No skills available' }; + } + + // 2. 构建 tools 数组 + const { tools, skillsMap } = buildSkillTools(skills); + + console.log('tools', tools); + + // 3. 获取模型配置 + const modelData = getLLMModel(model); + + // 4. 调用 LLM Tool Calling 进行匹配 + // 构建系统提示词,指导 LLM 选择相似的任务 + const systemPrompt = `你是一个智能任务匹配助手。请根据用户的当前需求,从提供的技能工具集中选择最相似的任务。 + + **匹配原则**: + 1. **任务目标相似性**:选择与用户目标最匹配的技能 + 2. **执行步骤相似性**:考虑任务执行的流程和步骤 + 3. **工具使用相似性**:优先选择使用类似工具组合的技能 + 4. **场景适用性**:考虑应用场景和上下文的相似性 + + **选择建议**: + - 如果用户的需求与某个技能高度匹配,直接选择对应的工具 + - 如果有多个相似技能,选择最符合主要目标的那个 + - 如果没有找到完全匹配的技能,选择功能最相近的 + + 请从以下工具中选择一个最匹配的:`; + + // 构建简化的消息,只包含系统提示词和用户输入 + const allMessages = [ + { + role: 'system' as const, + content: systemPrompt + }, + { + role: 'user' as const, + content: userInput + } + ]; + console.log('match request', { userInput, skillCount: skills.length }); + + const { toolCalls } = await createLLMResponse({ + body: { + model: modelData.model, + messages: allMessages, + tools, + tool_choice: 'auto', + toolCallMode: modelData.toolChoice ? 'toolChoice' : 'prompt', + stream: false + } + }); + + // 5. 解析匹配结果 + if (toolCalls && toolCalls.length > 0) { + const toolCall = toolCalls[0]; + const functionName = toolCall.function.name; + + if (skillsMap[functionName]) { + const matchedSkill = skillsMap[functionName]; + const systemPrompt = formatSkillAsSystemPrompt(matchedSkill); + + return { + matched: true, + skill: matchedSkill, + systemPrompt + }; + } + } + + return { + matched: false, + reason: 'No matching skill found' + }; + } catch (error: any) { + console.error('Error during skill matching:', error); + return { + matched: false, + reason: error.message || 'Unknown error' + }; + } +}; diff --git a/packages/service/core/workflow/dispatch/ai/agent/sub/plan/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/index.ts index 6431893f8..9ba1c03ac 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/sub/plan/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/index.ts @@ -103,11 +103,11 @@ export const dispatchPlanAgent = async ({ }); } - console.log('Plan request messages'); - console.dir( - { requestMessages, tools: isTopPlanAgent ? [PlanAgentAskTool] : [] }, - { depth: null } - ); + // console.log('Plan request messages'); + // console.dir( + // { requestMessages, tools: isTopPlanAgent ? [PlanAgentAskTool] : [] }, + // { depth: null } + // ); let { answerText, toolCalls = [], diff --git a/packages/service/core/workflow/dispatch/ai/agent/sub/plan/prompt.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/prompt.ts index b7a81d5f4..455e56eff 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/sub/plan/prompt.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/prompt.ts @@ -250,7 +250,7 @@ export const getUserContent = ({ systemPrompt?: string; getSubAppInfo: GetSubAppInfoFnType; }) => { - let userContent = `任务描述:${userInput}`; + let userContent = `用户输入:${userInput}`; if (systemPrompt) { userContent += `\n\n背景信息:${parseSystemPrompt({ systemPrompt, getSubAppInfo })}\n请按照用户提供的背景信息来重新生成计划,优先遵循用户的步骤安排和偏好。`; } diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index 6990f5c0d..51aa4e0e7 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -77,6 +77,7 @@ "confirm_delete_chat_content": "Are you sure you want to delete this chat? This action cannot be undone!", "confirm_delete_chats": "Are you sure you want to delete {{n}} conversation records? \nThe records will be permanently deleted!", "confirm_delete_folder_tip": "When you delete this folder, all applications and corresponding chat records under it will be deleted.", + "confirm_delete_skill": "Confirm to delete this skill?", "confirm_delete_tool": "Confirm to delete this tool?", "copilot_config_message": "Current Node Configuration Information: \n Code Type: {{codeType}} \n Current Code: \\\\`\\\\`\\\\`{{codeType}} \n{{code}} \\\\`\\\\`\\\\` \n Input Parameters: {{inputs}} \n Output Parameters: {{outputs}}", "copilot_confirm_message": "The original configuration has been received to understand the current code structure and input and output parameters. \nPlease explain your optimization requirements.", @@ -139,6 +140,7 @@ "dataset_search_tool_description": "Call the \"Semantic Search\" and \"Full-text Search\" capabilities to find reference content that may be related to the problem from the \"Knowledge Base\". \nPrioritize calling this tool to assist in answering user questions.", "dataset_select": "Optional knowledge base", "day": "Day", + "delete_failed": "Delete failed", "deleted": "App deleted", "document": "document", "document_quote": "Document Reference", @@ -149,6 +151,7 @@ "empty_folder": "(empty folder)", "empty_tool_tips": "Please add tools on the left side", "execute_time": "Execution Time", + "execution_steps": "Execution Steps", "expand_tool_create": "Expand MCP/Http create", "export_config_successful": "Configuration copied, some sensitive information automatically filtered. Please check for any remaining sensitive data.", "export_configs": "Export", @@ -273,6 +276,7 @@ "move_app": "Move Application", "my_agents": "my agents", "no_mcp_tools_list": "No data yet, the MCP address needs to be parsed first", + "no_steps_yet": "No steps yet, generate via chat", "node_not_intro": "This node is not introduced", "not_json_file": "Please select a JSON file", "not_the_newest": "Not the latest", @@ -327,9 +331,11 @@ "show_templates": "Expand", "show_top_p_tip": "An alternative method of temperature sampling, called Nucleus sampling, the model considers the results of tokens with TOP_P probability mass quality. \nTherefore, 0.1 means that only tokens containing the highest probability quality are considered. \nThe default is 1.", "simple_tool_tips": "This tool contains special inputs and does not support being called by simple applications.", + "skill_delete_success": "Skill deleted successfully", "skill_description_placeholder": "Used to guide the Agent to select the skill for execution", "skill_editor": "Skill-assisted generation", "skill_empty_name": "Unnamed skill", + "skill_local_removed": "Local skill removed", "skill_name_placeholder": "Please enter the skill description, for display only", "skills": "Skills", "skills_tip": "Model behavioral knowledge", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 16a4e4589..8543abe0a 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -219,6 +219,7 @@ "confirm_choice": "Confirm Choice", "confirm_input_delete_placeholder": "Please enter: {{confirmText}}", "confirm_input_delete_tip": "Please type {{confirmText}} to confirm", + "confirm_exit_without_saving": "Confirm to leave? \nEditing results will not be retained.", "confirm_logout": "Confirm to log out?", "confirm_move": "Move Here", "confirm_update": "confirm_update", diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index 054370cca..7712fc1c1 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -79,6 +79,7 @@ "confirm_delete_chat_content": "确定要删除这个对话吗?此操作不可恢复!", "confirm_delete_chats": "确认删除 {{n}} 组对话记录?记录将会永久删除!", "confirm_delete_folder_tip": "删除该文件夹时,将会删除它下面所有应用及对应的聊天记录。", + "confirm_delete_skill": "确认删除该技能?", "confirm_delete_tool": "确认删除该工具?", "copilot_config_message": "`当前节点配置信息: \n代码类型:{{codeType}} \n当前代码: \\`\\`\\`{{codeType}} \n{{code}} \\`\\`\\` \n输入参数: {{inputs}} \n输出参数: {{outputs}}`", "copilot_confirm_message": "已接收到原始配置,了解当前代码结构和输入输出参数。请说明您的优化需求。", @@ -142,6 +143,7 @@ "dataset_search_tool_description": "调用“语义检索”和“全文检索”能力,从“知识库”中查找可能与问题相关的参考内容。优先调用该工具来辅助回答用户的问题。", "dataset_select": "可选知识库", "day": "日", + "delete_failed": "删除失败", "deleted": "应用已删除", "document": "文档", "document_quote": "文档引用", @@ -153,6 +155,7 @@ "empty_folder": "(空文件夹)", "empty_tool_tips": "请在左侧添加工具", "execute_time": "执行时间", + "execution_steps": "执行步骤", "expand_tool_create": "展开MCP、Http创建", "export_config_successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据", "export_configs": "导出配置", @@ -286,6 +289,7 @@ "move_app": "移动应用", "my_agents": "我的 agents", "no_mcp_tools_list": "暂无数据,需先解析 MCP 地址", + "no_steps_yet": "暂无步骤,请通过对话生成", "node_not_intro": "这个节点没有介绍", "not_json_file": "请选择JSON文件", "not_the_newest": "非最新版", @@ -341,9 +345,11 @@ "show_templates": "显示模板", "show_top_p_tip": "用温度采样的替代方法,称为Nucleus采样,该模型考虑了具有TOP_P概率质量质量的令牌的结果。因此,0.1表示仅考虑包含最高概率质量的令牌。默认为 1。", "simple_tool_tips": "该工具含有特殊输入,暂不支持被简易应用调用", + "skill_delete_success": "技能删除成功", "skill_description_placeholder": "用于引导 Agent 选中该技能进行执行", "skill_editor": "技能辅助生成", "skill_empty_name": "未命名的技能", + "skill_local_removed": "本地技能已移除", "skill_name_placeholder": "请输入技能明,仅用于展示", "skills": "技能", "skills_tip": "模型的行为知识", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index dc3dc3cce..56fcaa0a6 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -220,6 +220,7 @@ "confirm_choice": "确认选择", "confirm_input_delete_placeholder": "请输入: {{confirmText}}", "confirm_input_delete_tip": "请输入 {{confirmText}} 确认", + "confirm_exit_without_saving": "确认离开?编辑结果不会被保留。", "confirm_logout": "确认退出登录?", "confirm_move": "移动到这", "confirm_update": "确认更新", diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index 60af81780..99d3493fc 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -77,6 +77,7 @@ "confirm_delete_chat_content": "確定要刪除這個對話嗎?\n此操作不可恢復!", "confirm_delete_chats": "確認刪除 {{n}} 組對話記錄?\n記錄將會永久刪除!", "confirm_delete_folder_tip": "刪除該文件夾時,將會刪除它下面所有應用及對應的聊天記錄。", + "confirm_delete_skill": "確認刪除該技能?", "confirm_delete_tool": "確認刪除該工具?", "copilot_config_message": "當前節點配置信息: \n代碼類型:{{codeType}} \n當前代碼: \\\\`\\\\`\\\\`{{codeType}} \n{{code}} \\\\`\\\\`\\\\` \n輸入參數: {{inputs}} \n輸出參數: {{outputs}}", "copilot_confirm_message": "已接收到原始配置,了解當前代碼結構和輸入輸出參數。\n請說明您的優化需求。", @@ -138,6 +139,7 @@ "dataset_search_tool_description": "呼叫「語意搜尋」和「全文搜尋」功能,從「知識庫」中尋找可能與問題相關的參考內容。優先呼叫這個工具來協助回答使用者的問題。", "dataset_select": "可選知識庫", "day": "日", + "delete_failed": "刪除失敗", "deleted": "應用已刪除", "document": "文件", "document_quote": "文件引用", @@ -325,9 +327,11 @@ "show_templates": "顯示模板", "show_top_p_tip": "用溫度取樣的替代方法,稱為 Nucleus 取樣,該模型考慮了具有 TOP_P 機率質量質量的令牌的結果。\n因此,0.1 表示僅考慮包含最高機率質量的令牌。\n預設為 1。", "simple_tool_tips": "該工具含有特殊輸入,暫不支持被簡易應用調用", + "skill_delete_success": "技能刪除成功", "skill_description_placeholder": "用於引導 Agent 選中該技能進行執行", "skill_editor": "技能輔助生成", "skill_empty_name": "未命名的技能", + "skill_local_removed": "本地技能已移除", "skill_name_placeholder": "請輸入技能明,僅用於展示", "skills": "技能", "skills_tip": "模型的行為知識", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index d2b11ba2d..f78d3678a 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -219,6 +219,7 @@ "confirm_choice": "確認選擇", "confirm_input_delete_placeholder": "請輸入: {{confirmText}}", "confirm_input_delete_tip": "請輸入 {{confirmText}} 確認", + "confirm_exit_without_saving": "確認離開?\n編輯結果不會被保留。", "confirm_logout": "確認退出登錄?", "confirm_move": "移動至此", "confirm_update": "確認更新", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e47c035b..0d12b20db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,7 +140,7 @@ importers: version: 6.3.4 '@modelcontextprotocol/sdk': specifier: ^1.24.0 - version: 1.24.0(zod@4.1.12) + version: 1.24.3(zod@4.1.12) '@node-rs/jieba': specifier: 2.0.1 version: 2.0.1 @@ -529,7 +529,7 @@ importers: version: 3.0.6 '@modelcontextprotocol/sdk': specifier: ^1.24.0 - version: 1.24.0(zod@4.1.12) + version: 1.24.3(zod@4.1.12) '@node-rs/jieba': specifier: 2.0.1 version: 2.0.1 @@ -822,7 +822,7 @@ importers: version: link:../../packages/global '@modelcontextprotocol/sdk': specifier: ^1.24.0 - version: 1.24.0(zod@4.1.12) + version: 1.24.3(zod@4.1.12) chalk: specifier: ^5.3.0 version: 5.4.1 @@ -834,7 +834,7 @@ importers: version: 16.5.0 express: specifier: ^4.22.0 - version: 4.22.0 + version: 4.22.1 devDependencies: '@types/express': specifier: ^5.0.1 @@ -935,7 +935,7 @@ importers: dependencies: express: specifier: ^4.22.0 - version: 4.22.0 + version: 4.22.1 packages: @@ -2886,8 +2886,8 @@ packages: '@mixmark-io/domino@2.2.0': resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} - '@modelcontextprotocol/sdk@1.24.0': - resolution: {integrity: sha512-D8h5KXY2vHFW8zTuxn2vuZGN0HGrQ5No6LkHwlEA9trVgNdPL3TF1dSqKA7Dny6BbBYKSW/rOBDXdC8KJAjUCg==} + '@modelcontextprotocol/sdk@1.24.3': + resolution: {integrity: sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -5104,12 +5104,12 @@ packages: bluebird@3.4.7: resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} - body-parser@1.20.4: - resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@2.2.1: - resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==} + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} engines: {node: '>=18'} boolbase@1.0.0: @@ -5523,9 +5523,9 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - content-disposition@1.0.1: - resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} - engines: {node: '>=18'} + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} @@ -5537,8 +5537,8 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-signature@1.0.7: - resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} @@ -5548,8 +5548,8 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookie@1.1.1: - resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} cookiejar@2.1.4: @@ -6416,12 +6416,12 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - eventsource-parser@3.0.6: - resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + eventsource-parser@3.0.1: + resolution: {integrity: sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==} engines: {node: '>=18.0.0'} - eventsource@3.0.7: - resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + eventsource@3.0.6: + resolution: {integrity: sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==} engines: {node: '>=18.0.0'} execa@5.1.1: @@ -6455,18 +6455,18 @@ packages: exponential-backoff@3.1.2: resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} - express-rate-limit@7.5.1: - resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + express-rate-limit@7.5.0: + resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} engines: {node: '>= 16'} peerDependencies: - express: '>= 4.11' + express: ^4.11 || 5 || ^5.0.0-beta.1 - express@4.22.0: - resolution: {integrity: sha512-c2iPh3xp5vvCLgaHK03+mWLFPhox7j1LwyxcZwFVApEv5i0X+IjPpbT50SJJwwLpdBVfp45AkK/v+AFgv/XlfQ==} + express@4.22.1: + resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} engines: {node: '>= 0.10.0'} - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} engines: {node: '>= 18'} extend@3.0.2: @@ -6592,13 +6592,13 @@ packages: resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} engines: {node: '>=0.10.0'} - finalhandler@1.3.2: - resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} find-cache-dir@3.3.2: resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} @@ -7029,10 +7029,6 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -7082,10 +7078,6 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.0: - resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} - engines: {node: '>=0.10.0'} - ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -8404,9 +8396,9 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} @@ -9054,8 +9046,9 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -9182,8 +9175,8 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - pkce-challenge@5.0.1: - resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + pkce-challenge@5.0.0: + resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} engines: {node: '>=16.20.0'} pkg-dir@4.2.0: @@ -9394,6 +9387,10 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -9427,13 +9424,13 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - raw-body@2.5.3: - resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} @@ -9955,10 +9952,6 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} - send@0.19.1: - resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==} - engines: {node: '>= 0.8.0'} - send@1.2.0: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} @@ -10171,10 +10164,6 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - std-env@3.8.1: resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==} @@ -11640,7 +11629,7 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 - debug: 4.4.3 + debug: 4.4.1 lodash.debounce: 4.0.8 resolve: 1.22.10 transitivePeerDependencies: @@ -12352,7 +12341,7 @@ snapshots: '@babel/parser': 7.26.10 '@babel/template': 7.26.9 '@babel/types': 7.26.10 - debug: 4.4.3 + debug: 4.4.1 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -13164,7 +13153,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 + debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -13726,20 +13715,20 @@ snapshots: '@mixmark-io/domino@2.2.0': {} - '@modelcontextprotocol/sdk@1.24.0(zod@4.1.12)': + '@modelcontextprotocol/sdk@1.24.3(zod@4.1.12)': dependencies: ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) content-type: 1.0.5 cors: 2.8.5 cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.0.6 - express: 5.2.1 - express-rate-limit: 7.5.1(express@5.2.1) + eventsource: 3.0.6 + eventsource-parser: 3.0.1 + express: 5.1.0 + express-rate-limit: 7.5.0(express@5.1.0) jose: 6.1.3 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 + pkce-challenge: 5.0.0 + raw-body: 3.0.0 zod: 4.1.12 zod-to-json-schema: 3.25.0(zod@4.1.12) transitivePeerDependencies: @@ -16014,7 +16003,7 @@ snapshots: accepts@2.0.0: dependencies: - mime-types: 3.0.2 + mime-types: 3.0.1 negotiator: 1.0.0 acorn-import-attributes@1.9.5(acorn@8.15.0): @@ -16266,7 +16255,7 @@ snapshots: axios@1.12.1: dependencies: - follow-redirects: 1.15.9(debug@4.4.3) + follow-redirects: 1.15.9(debug@4.4.0) form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -16397,33 +16386,33 @@ snapshots: bluebird@3.4.7: {} - body-parser@1.20.4: + body-parser@1.20.3: dependencies: bytes: 3.1.2 content-type: 1.0.5 debug: 2.6.9 depd: 2.0.0 destroy: 1.2.0 - http-errors: 2.0.1 + http-errors: 2.0.0 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.14.0 - raw-body: 2.5.3 + qs: 6.13.0 + raw-body: 2.5.2 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: - supports-color - body-parser@2.2.1: + body-parser@2.2.0: dependencies: bytes: 3.1.2 content-type: 1.0.5 debug: 4.4.3 - http-errors: 2.0.1 - iconv-lite: 0.7.0 + http-errors: 2.0.0 + iconv-lite: 0.6.3 on-finished: 2.4.1 qs: 6.14.0 - raw-body: 3.0.2 + raw-body: 3.0.0 type-is: 2.0.1 transitivePeerDependencies: - supports-color @@ -16870,7 +16859,9 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-disposition@1.0.1: {} + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 content-type@1.0.5: {} @@ -16878,13 +16869,13 @@ snapshots: convert-source-map@2.0.0: {} - cookie-signature@1.0.7: {} + cookie-signature@1.0.6: {} cookie-signature@1.2.2: {} cookie@0.7.2: {} - cookie@1.1.1: {} + cookie@1.0.2: {} cookiejar@2.1.4: {} @@ -18167,11 +18158,11 @@ snapshots: events@3.3.0: {} - eventsource-parser@3.0.6: {} + eventsource-parser@3.0.1: {} - eventsource@3.0.7: + eventsource@3.0.6: dependencies: - eventsource-parser: 3.0.6 + eventsource-parser: 3.0.1 execa@5.1.1: dependencies: @@ -18225,27 +18216,27 @@ snapshots: exponential-backoff@3.1.2: {} - express-rate-limit@7.5.1(express@5.2.1): + express-rate-limit@7.5.0(express@5.1.0): dependencies: - express: 5.2.1 + express: 5.1.0 - express@4.22.0: + express@4.22.1: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.4 + body-parser: 1.20.3 content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.7.2 - cookie-signature: 1.0.7 + cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.3.2 + finalhandler: 1.3.1 fresh: 0.5.2 - http-errors: 2.0.1 + http-errors: 2.0.0 merge-descriptors: 1.0.3 methods: 1.1.2 on-finished: 2.4.1 @@ -18255,34 +18246,33 @@ snapshots: qs: 6.14.0 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.19.1 + send: 0.19.0 serve-static: 1.16.2 setprototypeof: 1.2.0 - statuses: 2.0.2 + statuses: 2.0.1 type-is: 1.6.18 utils-merge: 1.0.1 vary: 1.1.2 transitivePeerDependencies: - supports-color - express@5.2.1: + express@5.1.0: dependencies: accepts: 2.0.0 - body-parser: 2.2.1 - content-disposition: 1.0.1 + body-parser: 2.2.0 + content-disposition: 1.0.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 debug: 4.4.3 - depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1 + finalhandler: 2.1.0 fresh: 2.0.0 - http-errors: 2.0.1 + http-errors: 2.0.0 merge-descriptors: 2.0.0 - mime-types: 3.0.2 + mime-types: 3.0.1 on-finished: 2.4.1 once: 1.4.0 parseurl: 1.3.3 @@ -18292,7 +18282,7 @@ snapshots: router: 2.2.0 send: 1.2.0 serve-static: 2.2.0 - statuses: 2.0.2 + statuses: 2.0.1 type-is: 2.0.1 vary: 1.1.2 transitivePeerDependencies: @@ -18447,26 +18437,26 @@ snapshots: filter-obj@1.1.0: {} - finalhandler@1.3.2: + finalhandler@1.3.1: dependencies: debug: 2.6.9 encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 - statuses: 2.0.2 + statuses: 2.0.1 unpipe: 1.0.0 transitivePeerDependencies: - supports-color - finalhandler@2.1.1: + finalhandler@2.1.0: dependencies: debug: 4.4.3 encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 - statuses: 2.0.2 + statuses: 2.0.1 transitivePeerDependencies: - supports-color @@ -18512,9 +18502,9 @@ snapshots: dependencies: tabbable: 6.2.0 - follow-redirects@1.15.9(debug@4.4.3): + follow-redirects@1.15.9(debug@4.4.0): optionalDependencies: - debug: 4.4.3 + debug: 4.4.0 for-each@0.3.5: dependencies: @@ -19022,14 +19012,6 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 @@ -19077,10 +19059,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.0: - dependencies: - safer-buffer: 2.1.2 - ieee754@1.2.1: {} ignore@5.3.2: {} @@ -19455,7 +19433,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.4.3 + debug: 4.4.1 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -20018,7 +19996,7 @@ snapshots: light-my-request@6.3.0: dependencies: - cookie: 1.1.1 + cookie: 1.0.2 process-warning: 4.0.1 set-cookie-parser: 2.7.1 @@ -20832,7 +20810,7 @@ snapshots: micromark@3.2.0: dependencies: '@types/debug': 4.1.12 - debug: 4.4.3 + debug: 4.4.1 decode-named-character-reference: 1.2.0 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -20854,7 +20832,7 @@ snapshots: micromark@4.0.2: dependencies: '@types/debug': 4.1.12 - debug: 4.4.3 + debug: 4.4.1 decode-named-character-reference: 1.2.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -20891,7 +20869,7 @@ snapshots: dependencies: mime-db: 1.52.0 - mime-types@3.0.2: + mime-types@3.0.1: dependencies: mime-db: 1.54.0 @@ -21015,9 +20993,9 @@ snapshots: dependencies: async-mutex: 0.5.0 camelcase: 6.3.0 - debug: 4.4.3 + debug: 4.4.0 find-cache-dir: 3.3.2 - follow-redirects: 1.15.9(debug@4.4.3) + follow-redirects: 1.15.9(debug@4.4.0) https-proxy-agent: 7.0.6 mongodb: 6.14.2(socks@2.8.4) new-find-package-json: 2.0.0 @@ -21166,7 +21144,7 @@ snapshots: new-find-package-json@2.0.0: dependencies: - debug: 4.4.3 + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -21731,7 +21709,7 @@ snapshots: path-to-regexp@6.3.0: {} - path-to-regexp@8.3.0: {} + path-to-regexp@8.2.0: {} path-type@4.0.0: {} @@ -21848,7 +21826,7 @@ snapshots: pirates@4.0.6: {} - pkce-challenge@5.0.1: {} + pkce-challenge@5.0.0: {} pkg-dir@4.2.0: dependencies: @@ -22045,6 +22023,10 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 + qs@6.13.0: + dependencies: + side-channel: 1.1.0 + qs@6.14.0: dependencies: side-channel: 1.1.0 @@ -22087,18 +22069,18 @@ snapshots: range-parser@1.2.1: {} - raw-body@2.5.3: + raw-body@2.5.2: dependencies: bytes: 3.1.2 - http-errors: 2.0.1 + http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 - raw-body@3.0.2: + raw-body@3.0.0: dependencies: bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.0 + http-errors: 2.0.0 + iconv-lite: 0.6.3 unpipe: 1.0.0 rc@1.2.8: @@ -22656,7 +22638,7 @@ snapshots: depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 - path-to-regexp: 8.3.0 + path-to-regexp: 8.2.0 transitivePeerDependencies: - supports-color @@ -22780,24 +22762,6 @@ snapshots: transitivePeerDependencies: - supports-color - send@0.19.1: - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - send@1.2.0: dependencies: debug: 4.4.3 @@ -22805,12 +22769,12 @@ snapshots: escape-html: 1.0.3 etag: 1.8.1 fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 + http-errors: 2.0.0 + mime-types: 3.0.1 ms: 2.1.3 on-finished: 2.4.1 range-parser: 1.2.1 - statuses: 2.0.2 + statuses: 2.0.1 transitivePeerDependencies: - supports-color @@ -22978,7 +22942,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.3 - debug: 4.4.3 + debug: 4.4.1 socks: 2.8.4 transitivePeerDependencies: - supports-color @@ -23052,8 +23016,6 @@ snapshots: statuses@2.0.1: {} - statuses@2.0.2: {} - std-env@3.8.1: {} stdin-discarder@0.1.0: @@ -23251,7 +23213,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.3 + debug: 4.4.1 fast-safe-stringify: 2.1.1 form-data: 4.0.4 formidable: 2.1.2 @@ -23632,7 +23594,7 @@ snapshots: dependencies: content-type: 1.0.5 media-typer: 1.1.0 - mime-types: 3.0.2 + mime-types: 3.0.1 typed-array-buffer@1.0.3: dependencies: @@ -23979,7 +23941,7 @@ snapshots: vite-node@1.6.1(@types/node@24.0.13)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0): dependencies: cac: 6.7.14 - debug: 4.4.3 + debug: 4.4.1 pathe: 1.1.2 picocolors: 1.1.1 vite: 5.4.14(@types/node@24.0.13)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0) @@ -24087,7 +24049,7 @@ snapshots: '@vitest/utils': 1.6.1 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.4.3 + debug: 4.4.0 execa: 8.0.1 local-pkg: 0.5.1 magic-string: 0.30.17 diff --git a/projects/app/src/components/core/chat/ChatContainer/type.d.ts b/projects/app/src/components/core/chat/ChatContainer/type.d.ts index e85860ee2..93dd2395b 100644 --- a/projects/app/src/components/core/chat/ChatContainer/type.d.ts +++ b/projects/app/src/components/core/chat/ChatContainer/type.d.ts @@ -8,6 +8,7 @@ import type { import type { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import type { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; import type { TopAgentFormDataType } from '@fastgpt/service/core/chat/HelperBot/dispatch/topAgent/type'; +import type { GeneratedSkillDataType } from '@fastgpt/global/core/chat/helperBot/generatedSkill/type'; export type generatingMessageProps = { event: SseResponseEventEnum; @@ -27,6 +28,7 @@ export type generatingMessageProps = { // Agent formData?: TopAgentFormDataType; + generatedSkill?: GeneratedSkillDataType; }; export type StartChatFnProps = { diff --git a/projects/app/src/components/core/chat/HelperBot/components/AIItem.tsx b/projects/app/src/components/core/chat/HelperBot/components/AIItem.tsx index 286d2c0a2..7efd0556e 100644 --- a/projects/app/src/components/core/chat/HelperBot/components/AIItem.tsx +++ b/projects/app/src/components/core/chat/HelperBot/components/AIItem.tsx @@ -7,11 +7,9 @@ import { AccordionIcon, AccordionItem, AccordionPanel, - Button, Flex, HStack } from '@chakra-ui/react'; -import AIResponseBox from '../../components/AIResponseBox'; import { useTranslation } from 'next-i18next'; import MyIcon from '@fastgpt/web/components/common/Icon'; import Markdown from '@/components/Markdown'; @@ -71,6 +69,7 @@ const RenderResoningContent = React.memo(function RenderResoningContent({ ); }); + const RenderText = React.memo(function RenderText({ showAnimation, text diff --git a/projects/app/src/components/core/chat/HelperBot/context.tsx b/projects/app/src/components/core/chat/HelperBot/context.tsx index c902fbac3..4f9fb915c 100644 --- a/projects/app/src/components/core/chat/HelperBot/context.tsx +++ b/projects/app/src/components/core/chat/HelperBot/context.tsx @@ -1,22 +1,30 @@ import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance'; -import React, { useState, type ReactNode } from 'react'; +import React, { type ReactNode } from 'react'; import { createContext } from 'use-context-selector'; -import { - HelperBotTypeEnum, - type HelperBotTypeEnumType, - type TopAgentParamsType -} 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 { AppFileSelectConfigType } from '@fastgpt/global/core/app/type'; import type { TopAgentFormDataType } from '@fastgpt/service/core/chat/HelperBot/dispatch/topAgent/type'; +import type { + GeneratedSkillResultType, + SkillAgentParamsType +} from '@fastgpt/global/core/chat/helperBot/skillAgent/type'; export type HelperBotProps = { emptyDom?: ReactNode; fileSelectConfig?: AppFileSelectConfigType; -} & { - type: HelperBotTypeEnumType; - metadata: TopAgentParamsType; - onApply: (e: TopAgentFormDataType) => void; -}; +} & ( + | { + type: typeof HelperBotTypeEnum.topAgent; + metadata: TopAgentParamsType; + onApply: (e: TopAgentFormDataType) => void; + } + | { + type: typeof HelperBotTypeEnum.skillAgent; + metadata: SkillAgentParamsType; + onApply: (e: GeneratedSkillResultType) => void; + } +); type HelperBotContextType = HelperBotProps & {}; export const HelperBotContext = createContext({ diff --git a/projects/app/src/components/core/chat/HelperBot/generatedSkill/api.ts b/projects/app/src/components/core/chat/HelperBot/generatedSkill/api.ts new file mode 100644 index 000000000..9cd38c83d --- /dev/null +++ b/projects/app/src/components/core/chat/HelperBot/generatedSkill/api.ts @@ -0,0 +1,21 @@ +import { POST, GET, PUT, DELETE } from '@/web/common/api/request'; +import type { + ListAiSkillBodyType, + ListAiSkillResponse, + GetAiSkillDetailQueryType, + UpdateAiSkillBodyType, + DeleteAiSkillQueryType +} from '@fastgpt/global/openapi/core/ai/skill/api'; +import type { AiSkillSchemaType } from '@fastgpt/global/core/ai/skill/type'; + +export const updateAiSkill = (data: UpdateAiSkillBodyType) => + PUT<{ success: boolean; _id: string }>('/core/ai/skill/update', data); + +export const getAiSkillList = (data: ListAiSkillBodyType) => + POST('/core/ai/skill/list', data); + +export const getAiSkillDetail = (data: GetAiSkillDetailQueryType) => + GET('/core/ai/skill/detail', data); + +export const deleteAiSkill = (data: DeleteAiSkillQueryType) => + DELETE<{ success: boolean }>('/core/ai/skill/delete', data); diff --git a/projects/app/src/components/core/chat/HelperBot/index.tsx b/projects/app/src/components/core/chat/HelperBot/index.tsx index 76ba8a2fd..2a0c36900 100644 --- a/projects/app/src/components/core/chat/HelperBot/index.tsx +++ b/projects/app/src/components/core/chat/HelperBot/index.tsx @@ -27,7 +27,10 @@ import { useForm } from 'react-hook-form'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { useTranslation } from 'next-i18next'; import { useMemoizedFn, useThrottleFn } from 'ahooks'; -import type { HelperBotChatItemSiteType } from '@fastgpt/global/core/chat/helperBot/type'; +import { + HelperBotTypeEnum, + type HelperBotChatItemSiteType +} from '@fastgpt/global/core/chat/helperBot/type'; import type { onSendMessageParamsType } from './type'; import { textareaMinH } from '../ChatContainer/ChatBox/constants'; import { streamFetch } from '@/web/common/api/fetch'; @@ -42,6 +45,7 @@ const ChatBox = ({ type, metadata, onApply, ...props }: HelperBotProps) => { // Messages 管理 const [chatId, setChatId] = useState(getNanoid(12)); const [isChatting, setIsChatting] = useState(false); + const chatForm = useForm({ defaultValues: { input: '', @@ -144,7 +148,14 @@ const ChatBox = ({ type, metadata, onApply, ...props }: HelperBotProps) => { } ); const generatingMessage = useMemoizedFn( - ({ event, text = '', reasoningText, tool, formData }: generatingMessageProps) => { + ({ + event, + text = '', + reasoningText, + tool, + formData, + generatedSkill + }: generatingMessageProps) => { setChatRecords((state) => state.map((item, index) => { if (index !== state.length - 1) return item; @@ -155,7 +166,19 @@ const ChatBox = ({ type, metadata, onApply, ...props }: HelperBotProps) => { // Special event: form data if (event === SseResponseEventEnum.formData && formData) { - onApply?.(formData); + if (type === HelperBotTypeEnum.topAgent) { + onApply?.(formData); + } + return item; + } + + // Special event: generated skill + if (event === SseResponseEventEnum.generatedSkill && generatedSkill) { + console.log('📊 HelperBot: Received generatedSkill event', generatedSkill); + // 直接将生成的 skill 数据传递给 onApply 回调(仅在 skillAgent 类型时) + if (type === HelperBotTypeEnum.skillAgent) { + onApply?.(generatedSkill); + } return item; } @@ -320,7 +343,7 @@ const ChatBox = ({ type, metadata, onApply, ...props }: HelperBotProps) => { try { const abortSignal = new AbortController(); chatController.current = abortSignal; - + console.log('metadata-fronted', metadata); const { responseText } = await streamFetch({ url: '/api/core/chat/helperBot/completions', data: { @@ -334,7 +357,7 @@ const ChatBox = ({ type, metadata, onApply, ...props }: HelperBotProps) => { name: item.name })), metadata: { - type, + type: type, data: metadata } }, diff --git a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/Edit.tsx b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/Edit.tsx index 27e86aa5c..b0fbc784c 100644 --- a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/Edit.tsx +++ b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/Edit.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo, useCallback } from 'react'; import { Box, Flex } from '@chakra-ui/react'; import ChatTest from './ChatTest'; @@ -12,6 +12,8 @@ import { type SimpleAppSnapshotType } from '../FormComponent/useSnapshots'; import { agentForm2AppWorkflow } from './utils'; import styles from '../FormComponent/styles.module.scss'; import dynamic from 'next/dynamic'; +import { getAiSkillDetail } from '@/web/core/ai/skill/api'; +import { useToast } from '@fastgpt/web/hooks/useToast'; const SkillEditForm = dynamic(() => import('./SkillEdit/EditForm'), { ssr: false }); const SKillChatTest = dynamic(() => import('./SkillEdit/ChatTest'), { ssr: false }); @@ -27,7 +29,31 @@ const Edit = ({ }) => { const { isPc } = useSystem(); const [renderEdit, setRenderEdit] = useState(true); - const [editSkill, setEditSkill] = useState(); + // 状态:当前正在编辑的 skill(完整对象) + const [editingSkill, setEditingSkill] = useState(); + + // 处理保存 + const handleSaveSkill = useCallback( + (savedSkill: SkillEditType) => { + setAppForm((state) => { + const skillExists = state.skills.some((s) => s.id === savedSkill.id); + return { + ...state, + skills: skillExists + ? state.skills.map((s) => (s.id === savedSkill.id ? savedSkill : s)) + : [savedSkill, ...state.skills] + }; + }); + setEditingSkill(undefined); + }, + [setAppForm] + ); + const handleAIGenerate = useCallback( + (updates: Partial) => { + setEditingSkill((prev) => (prev ? { ...prev, ...updates } : prev)); + }, + [setEditingSkill] + ); return ( - setEditSkill(e)} - /> + )} @@ -75,7 +97,7 @@ const Edit = ({ )} {/* Mask */} - {editSkill && ( + {editingSkill && ( - {editSkill && ( + {editingSkill && ( <> setEditSkill(undefined)} - setAppForm={setAppForm} + skill={editingSkill} + onClose={() => setEditingSkill(undefined)} + onSave={handleSaveSkill} /> - + )} diff --git a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/EditForm.tsx b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/EditForm.tsx index ee0fce912..cfe4f85bc 100644 --- a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/EditForm.tsx +++ b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/EditForm.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState, useTransition } from 'react'; +import React, { useCallback, useEffect, useMemo, useTransition } from 'react'; import { Box, Flex, @@ -7,8 +7,7 @@ import { useTheme, useDisclosure, Button, - HStack, - Input + HStack } from '@chakra-ui/react'; import type { SkillEditType } from '@fastgpt/global/core/app/formEdit/type'; import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type'; @@ -32,6 +31,8 @@ import ToolSelect from '../FormComponent/ToolSelector/ToolSelect'; import SkillRow from './SkillEdit/Row'; import { cardStyles } from '../../constants'; import { SmallAddIcon } from '@chakra-ui/icons'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { getAiSkillDetail } from '@/web/core/ai/skill/api'; const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal')); @@ -102,6 +103,29 @@ const EditForm = ({ } }, [selectedModel, setAppForm]); + // 打开编辑器 + const handleEditSkill = useCallback( + async (skill: SkillEditType) => { + // If skill has dbId, load full details from server + if (skill.id) { + const detail = await getAiSkillDetail({ id: skill.id }); + // Merge server data with local data + onEditSkill({ + id: detail._id, + name: detail.name, + description: detail.description || '', + stepsText: detail.steps, + selectedTools: detail.tools || [], + dataset: { list: detail.datasets || [] } + }); + } else { + // New skill without dbId + onEditSkill(skill); + } + }, + [onEditSkill] + ); + return ( <> @@ -168,7 +192,6 @@ const EditForm = ({ })); }); }} - // variableLabels={formatVariables} title={t('app:ai_role')} isRichText={false} /> @@ -204,16 +227,7 @@ const EditForm = ({ - { - setAppForm((state) => ({ - ...state, - skills: state.skills.filter((item) => item.id !== id) - })); - }} - /> + {/* tool choice */} diff --git a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/SkillEdit/ChatTest.tsx b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/SkillEdit/ChatTest.tsx index 0c3199f8d..8558bfb49 100644 --- a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/SkillEdit/ChatTest.tsx +++ b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/SkillEdit/ChatTest.tsx @@ -5,24 +5,35 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import MyIcon from '@fastgpt/web/components/common/Icon'; import type { SkillEditType } from '@fastgpt/global/core/app/formEdit/type'; import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type'; -import { useContextSelector } from 'use-context-selector'; -import { AppContext } from '../../../context'; import MyBox from '@fastgpt/web/components/common/MyBox'; -import { cardStyles } from '../../../constants'; import HelperBot from '@/components/core/chat/HelperBot'; import { HelperBotTypeEnum } from '@fastgpt/global/core/chat/helperBot/type'; import { useToast } from '@fastgpt/web/hooks/useToast'; type Props = { skill: SkillEditType; - setAppForm: React.Dispatch>; + appForm: AppFormEditFormType; + onAIGenerate: (updates: Partial) => void; }; -const ChatTest = ({ skill, setAppForm }: Props) => { +const ChatTest = ({ skill, appForm, onAIGenerate }: Props) => { const { t } = useTranslation(); - const { toast } = useToast(); - // 构建 SkillAgent metadata,从 appForm 中提取配置 - const skillAgentMetadata = useMemo(() => ({}), []); + const skillAgentMetadata = useMemo(() => { + return { + skillAgent: { + name: skill.name, + description: skill.description, + stepsText: skill.stepsText + }, + topAgent: { + role: appForm.aiSettings.aiRole, + taskObject: appForm.aiSettings.aiTaskObject, + fileUpload: appForm.chatConfig.fileSelectConfig?.canSelectFile || false, + selectedTools: skill.selectedTools?.map((tool) => tool.id) || [], + selectedDatasets: skill.dataset?.list?.map((ds) => ds.datasetId) || [] + } + }; + }, [appForm.aiSettings, appForm.chatConfig.fileSelectConfig, skill]); return ( @@ -46,10 +57,28 @@ const ChatTest = ({ skill, setAppForm }: Props) => { { - console.log(e); + onApply={(generatedSkillData) => { + console.log(generatedSkillData, 222); + // const stepsText = generatedSkillData.execution_plan.steps + // .map((step, index) => { + // let stepText = `步骤 ${index + 1}: ${step.title}\n${step.description}`; + // if (step.expectedTools && step.expectedTools.length > 0) { + // const tools = step.expectedTools + // .map((tool) => `${tool.type === 'tool' ? '🔧' : '📚'} ${tool.id}`) + // .join(', '); + // stepText += `\n使用工具: ${tools}`; + // } + // return stepText; + // }) + // .join('\n\n'); + + // onAIGenerate({ + // name: generatedSkillData.plan_analysis.name || skill.name, + // description: generatedSkillData.plan_analysis.description || skill.description, + // stepsText: stepsText + // }); }} /> diff --git a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/SkillEdit/EditForm.tsx b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/SkillEdit/EditForm.tsx index 6bab50f4e..f748aaa77 100644 --- a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/SkillEdit/EditForm.tsx +++ b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/SkillEdit/EditForm.tsx @@ -1,9 +1,8 @@ -import React, { useEffect, useMemo, useTransition } from 'react'; +import React, { useEffect } from 'react'; import { Box, Flex, Grid, - type BoxProps, useTheme, useDisclosure, Button, @@ -14,79 +13,62 @@ import { } from '@chakra-ui/react'; import type { AppFileSelectConfigType } from '@fastgpt/global/core/app/type'; import type { SkillEditType } from '@fastgpt/global/core/app/formEdit/type'; -import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type'; import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; +import { useForm } from 'react-hook-form'; import dynamic from 'next/dynamic'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import Avatar from '@fastgpt/web/components/common/Avatar'; import MyIcon from '@fastgpt/web/components/common/Icon'; -import VariableEdit from '@/components/core/app/VariableEdit'; -import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor'; -import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils'; -import SearchParamsTip from '@/components/core/dataset/SearchParamsTip'; -import SettingLLMModel from '@/components/core/ai/SettingLLMModel'; -import { TTSTypeEnum } from '@/web/core/app/constants'; -import { workflowSystemVariables } from '@/web/core/app/utils'; -import { useContextSelector } from 'use-context-selector'; -import { AppContext } from '@/pageComponents/app/detail/context'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; -import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip'; import { getWebLLMModel } from '@/web/common/system/utils'; import ToolSelect from '../../FormComponent/ToolSelector/ToolSelect'; -import OptimizerPopover from '@/components/common/PromptEditor/OptimizerPopover'; -import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node'; -import { useSkillManager } from '../hooks/useSkillManager'; -import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance'; -import { cardStyles } from '../../../constants'; -import { defaultSkill as defaultEditSkill } from './Row'; -import { useForm } from 'react-hook-form'; -import type { LLMModelItemType } from '@fastgpt/global/core/ai/model'; import { SmallAddIcon } from '@chakra-ui/icons'; -import { getNanoid } from '@fastgpt/global/common/string/tools'; +import { updateAiSkill } from '@/web/core/ai/skill/api'; +import { useContextSelector } from 'use-context-selector'; +import { AppContext } from '@/pageComponents/app/detail/context'; +import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); -const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal')); -const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect')); -const QGConfig = dynamic(() => import('@/components/core/app/QGConfig')); -const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig')); -const InputGuideConfig = dynamic(() => import('@/components/core/app/InputGuideConfig')); -const WelcomeTextConfig = dynamic(() => import('@/components/core/app/WelcomeTextConfig')); -const FileSelectConfig = dynamic(() => import('@/components/core/app/FileSelect')); -const EditForm = ({ - model, - fileSelectConfig, - defaultSkill = defaultEditSkill, - onClose, - setAppForm -}: { +type EditFormProps = { model: string; fileSelectConfig?: AppFileSelectConfigType; - defaultSkill?: SkillEditType; + skill: SkillEditType; onClose: () => void; - setAppForm: React.Dispatch>; -}) => { + onSave: (skill: SkillEditType) => void; +}; + +const EditForm = ({ model, fileSelectConfig, skill, onClose, onSave }: EditFormProps) => { const theme = useTheme(); const router = useRouter(); const { t } = useTranslation(); - const [, startTst] = useTransition(); + const appId = useContextSelector(AppContext, (v) => v.appId); + + // Form state management with validation + const { + register, + handleSubmit, + watch, + setValue, + reset, + formState: { isDirty } + } = useForm({ + defaultValues: skill + }); + + // Reset form when skill prop changes + useEffect(() => { + reset(skill); + }, [skill, reset]); const selectedModel = getWebLLMModel(model); - - const { register, setValue, handleSubmit, reset, watch } = useForm({ - defaultValues: defaultSkill - }); - useEffect(() => { - reset(defaultSkill); - }, [defaultSkill, reset]); - - const name = watch('name'); - const prompt = watch('prompt'); - const selectedTools = watch('selectedTools'); - const selectDatasets = watch('dataset.list'); + const selectedTools = watch('selectedTools') || []; + const selectDatasets = watch('dataset.list') || []; + const skillName = watch('name'); const { isOpen: isOpenDatasetSelect, @@ -94,15 +76,46 @@ const EditForm = ({ onClose: onCloseKbSelect } = useDisclosure(); - const onSave = (e: SkillEditType) => { - setAppForm((state) => ({ - ...state, - skills: e.id - ? state.skills.map((item) => (item.id === e.id ? e : item)) - : [{ ...e, id: getNanoid(6) }, ...state.skills] - })); - onClose(); - }; + const { openConfirm, ConfirmModal } = useConfirm({ + content: t('common:confirm_exit_without_saving') + }); + + const { runAsync: onSubmit, loading: isSaving } = useRequest2( + async (formData: SkillEditType) => { + const result = await updateAiSkill({ + id: formData.id, + appId, + name: formData.name, + description: formData.description || '', + steps: formData.stepsText || '', + tools: (formData.selectedTools || []).map((item) => ({ + id: item.pluginId!, + // 遍历 tool 的 inputs,转成 object + config: item.inputs?.reduce( + (acc, input) => { + acc[input.key] = input.value; + return acc; + }, + {} as Record + ) + })), + datasets: (formData.dataset?.list || []).map((item) => ({ + datasetId: item.datasetId, + name: item.name, + avatar: item.avatar, + vectorModel: item.vectorModel + })) + }); + + onSave({ ...formData, id: result }); + }, + { + manual: true, + successToast: t('common:save_success') + } + ); + + const handleFormSubmit = handleSubmit(onSubmit); return ( <> @@ -116,17 +129,26 @@ const EditForm = ({ aria-label={''} w={'28px'} h={'28px'} - onClick={onClose} + onClick={() => { + if (isDirty) { + openConfirm(() => { + onClose(); + })(); + } else { + onClose(); + } + }} /> - {name || t('app:skill_empty_name')} + {skillName || t('app:skill_empty_name')} @@ -136,12 +158,16 @@ const EditForm = ({ {t('common:Name')} - + + + {/* Desc */} @@ -152,30 +178,32 @@ const EditForm = ({