From dd393cb2cf7c63a14b80838e33a95e655c7c4bd7 Mon Sep 17 00:00:00 2001 From: xxyyh <2289112474@qq> Date: Wed, 24 Dec 2025 20:03:06 +0800 Subject: [PATCH] skill ui --- .../core/chat/helperBot/skillAgent/type.ts | 13 +- .../HelperBot/dispatch/skillAgent/index.ts | 71 +++++- .../HelperBot/dispatch/skillAgent/prompt.ts | 145 +++++++---- .../chat/HelperBot/dispatch/topAgent/index.ts | 4 +- .../HelperBot/dispatch/topAgent/prompt.ts | 241 +++++++++++------- .../chat/HelperBot/dispatch/topAgent/type.ts | 8 +- .../core/workflow/dispatch/ai/agent/index.ts | 8 +- .../Edit/ChatAgent/SkillEdit/ChatTest.tsx | 13 +- 8 files changed, 340 insertions(+), 163 deletions(-) diff --git a/packages/global/core/chat/helperBot/skillAgent/type.ts b/packages/global/core/chat/helperBot/skillAgent/type.ts index 4e33fba5f4..a9b3729a54 100644 --- a/packages/global/core/chat/helperBot/skillAgent/type.ts +++ b/packages/global/core/chat/helperBot/skillAgent/type.ts @@ -40,10 +40,21 @@ const TaskAnalysisSchema = z.object({ }); // LLM 返回的完整数据 +const FormInputSchema = z.object({ + type: z.literal('input').or(z.literal('numberInput')), + label: z.string() +}); +const FormSelectSchema = z.object({ + type: z.literal('select').or(z.literal('multipleSelect')), + label: z.string(), + options: z.array(z.string()) +}); + export const GeneratedSkillDataCollectionSchema = z.object({ phase: z.literal('collection'), reasoning: z.string(), - question: z.string() + question: z.string(), + form: z.array(z.union([FormInputSchema, FormSelectSchema])).optional() }); export type GeneratedSkillDataCollectionType = z.infer; export const GeneratedSkillResultSchema = z.object({ diff --git a/packages/service/core/chat/HelperBot/dispatch/skillAgent/index.ts b/packages/service/core/chat/HelperBot/dispatch/skillAgent/index.ts index 35dc291157..21e20c1b3c 100644 --- a/packages/service/core/chat/HelperBot/dispatch/skillAgent/index.ts +++ b/packages/service/core/chat/HelperBot/dispatch/skillAgent/index.ts @@ -14,6 +14,13 @@ import { GeneratedSkillResultSchema } from '@fastgpt/global/core/chat/helperBot/skillAgent/type'; import { parseJsonArgs } from '../../../../ai/utils'; +import type { + UserInputFormItemType, + UserInputInteractive +} from '@fastgpt/global/core/workflow/template/system/interactive/type'; +import { getNanoid } from '@fastgpt/global/common/string/tools'; +import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; +import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; export const dispatchSkillAgent = async ( props: HelperBotDispatchParamsType @@ -51,7 +58,7 @@ export const dispatchSkillAgent = async ( { role: 'user' as const, content: query } ]; - console.dir(conversationMessages, { depth: null }); + // console.dir(conversationMessages, { depth: null }); // Single LLM call - LLM self-determines phase and outputs corresponding format const llmResponse = await createLLMResponse({ body: { @@ -59,12 +66,6 @@ export const dispatchSkillAgent = async ( model: modelData, stream: true }, - onStreaming: ({ text }) => { - workflowResponseWrite?.({ - event: SseResponseEventEnum.answer, - data: textAdaptGptResponse({ text }) - }); - }, onReasoning: ({ text }) => { workflowResponseWrite?.({ event: SseResponseEventEnum.answer, @@ -87,11 +88,10 @@ export const dispatchSkillAgent = async ( addLog.warn(`[Skill agent] Failed to parse JSON response`, { text: answerText }); throw new Error('Failed to parse JSON response'); } - console.log(responseJson, 22323); + console.log(JSON.stringify(responseJson, null, 2)); // Handle based on phase field if (responseJson.phase === 'generation') { addLog.debug('🔄 SkillAgent: Generated skill generation phase'); - const parseResult = GeneratedSkillResultSchema.safeParse(responseJson).data; if (!parseResult) { @@ -116,10 +116,59 @@ export const dispatchSkillAgent = async ( } else if (responseJson.phase === 'collection') { addLog.debug('📝 SkillAgent: Information collection phase'); - const displayText = responseJson.question || answerText; + // 检查是否有表单数据 + const formData = responseJson.form; + if (formData) { + // 转换为前端可用的表单格式 + const inputForm: UserInputInteractive = { + type: 'userInput', + params: { + inputForm: formData.map((item) => { + return { + type: item.type as FlowNodeInputTypeEnum, + key: getNanoid(6), + label: item.label, + value: '', + required: false, + valueType: + item.type === FlowNodeInputTypeEnum.numberInput + ? WorkflowIOValueTypeEnum.number + : WorkflowIOValueTypeEnum.string, + list: + 'options' in item + ? item.options?.map((option) => ({ label: option, value: option })) + : undefined + }; + }), + description: responseJson.question + } + }; + + // 发送表单事件 + workflowResponseWrite?.({ + event: SseResponseEventEnum.collectionForm, + data: inputForm + }); + + return { + aiResponse: formatAIResponse({ + text: responseJson.question, + reasoning: responseJson.reasoning || reasoningText, + collectionForm: inputForm + }), + usage + }; + } + + // 无表单,纯文本问题 + workflowResponseWrite?.({ + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ text: responseJson.question }) + }); + return { aiResponse: formatAIResponse({ - text: displayText, + text: responseJson.question, reasoning: responseJson.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 index ec2783fa29..00800c4bfb 100644 --- a/packages/service/core/chat/HelperBot/dispatch/skillAgent/prompt.ts +++ b/packages/service/core/chat/HelperBot/dispatch/skillAgent/prompt.ts @@ -67,35 +67,60 @@ ${currentConfigContext} 当处于信息收集阶段时: -**信息收集目标**:从宏观层面收集战略规划所需的信息: +**🎯 核心认知:设计的是可复用的执行模式,而非一次性的解决方案** -1. **任务定位与场景** - - 这个任务要解决什么核心问题? - - 主要面向什么样的应用场景? - - 任务的最终目标是什么? +Skill 就像建筑蓝图,描述的是房子的结构和风格,而不是具体住户的家具摆放。 -2. **资源方向识别** - - 需要什么类型的能力?(信息获取/数据处理/内容生成/服务集成) - - 是否需要特定领域的知识支持? - - 是否需要与外部系统交互? +**统一标准**: -3. **战略约束** - - 任务有什么重要的限制条件? - - 是否有特定的用户群体或使用场景? - - 期望的复杂度水平如何? +规划生成机器人在收集信息时,应该像架构师理解建筑需求一样,关注的是: +- 这类任务的**执行模式**是什么(如何组织阶段、如何流转数据) +- 需要哪些**能力类别**(搜索、解析、计算、生成) +- **可复用的框架**应该是什么样的 + +而不是关注: +- 某一次具体使用时的参数(URL、文件名、字段名) +- 某个特定用户的个性化需求 + +**信息收集的本质目的**:理解任务的"类",而非任务的"实例" + +**收集信息的三个维度**: + +1. **任务模式识别** + - 这类任务的执行模式是什么? + - 信息如何流转?(输入 → 处理 → 输出) + +2. **能力需求判断** + - 需要什么类别的能力? + - 这些能力如何组合? + +3. **框架复杂度评估** + - 执行流程的复杂度如何? + - 需要几个关键阶段? **关键原则**: -- **宏观视角**:关注"做什么"而非"怎么做",避免询问具体实现细节 -- **战略导向**:了解任务的本质和方向,而非具体步骤 -- **框架思维**:收集的信息用于设计执行框架,具体操作留给后续阶段 - -**提问层次要求**: -- ✅ 宏观层面:任务目标、应用场景、资源类型、战略方向 -- ❌ 具体细节:具体参数、详细流程、技术实现、操作步骤 +- **模式思维**:寻找可复用的执行模式 +- **抽象思维**:从具体实例中抽象出通用框架 +- **类型思维**:关注"类型"和"类别",而非"实例"和"个体" **输出格式**: -**重要:信息收集阶段的所有回复必须使用JSON格式,包含 phase 字段** +**🚨 严格输出要求(极其重要)**: + +1. **只输出 JSON 格式**:信息收集阶段的所有回复必须是纯净的 JSON,不要添加任何其他内容 +2. **禁止代码块标记**:不要使用 \`\`\`json 或任何代码块标记 +3. **禁止解释性文字**:不要在 JSON 前后添加任何说明、解释或寒暄语 +4. **必须包含 phase 字段**:每个回复都必须包含 "phase": "collection" + +**严格输出规则**: +- ❌ 不要使用 \`\`\`json 或其他代码块标记 +- ❌ 不要添加任何前言,如"好的,让我问几个问题"、"明白了"等 +- ❌ 不要添加任何后语,如"期待您的回复"、"请告诉我"等 +- ❌ 不要输出任何非 JSON 格式的内容 +- ✅ 直接、纯净地输出 JSON 内容 +- ✅ JSON 必须是有效的、可解析的格式 + +**标准输出格式**: 直接输出以下格式的JSON(不要添加代码块标记): { @@ -104,38 +129,66 @@ ${currentConfigContext} "question": "实际向用户提出的问题内容" } -问题内容可以是开放式问题,也可以包含选项: +**🧠 交互意图识别(元认知层)**: -✅ **好的宏观提问示例**: -{ - "phase": "collection", - "reasoning": "需要了解任务的核心定位和战略目标,这将决定整体框架的设计方向", - "question": "这个 Skill 主要想解决什么问题?比如是面向客户服务、内容创作、数据分析,还是其他应用场景?" -} +在决定如何提问之前,首先明确本次交互的核心意图: + +**意图类型 A - 建立对话基础**: +- 适用场景:初次接触、寒暄问候、确认基本意愿、澄清歧义 +- 本质特征:对话关系本身是目的,建立信任和理解 +- 交互原则:保持对话的自然流畅,任何结构化工具都是干扰 +- 输出方式:开放式对话,question 字段包含自然语言问题 + +**意图类型 B - 收集规划信息**: +- 适用场景:对话基础已建立,需要获取战略规划所需的具体维度信息 +- 本质特征:信息收集是目的,效率和准确性是追求 +- 交互原则:评估信息特征,选择最高效的收集方式 +- 输出方式:根据信息特征决定是否使用 form 字段 + +**意图判断流程**: +1. 用户是否已明确表达任务场景? + - 未明确 → 意图 A(建立理解) + - 已明确 → 意图 B(收集信息) + +2. 当前需要的信息具有结构化特征吗? + - 可枚举的选项/多个独立维度/明确的分类 → 考虑使用表单 + - 开放探索/深度挖掘/需要解释 → 使用开放式问题 + +3. 使用表单会打断对话的连贯性吗? + - 会打断 → 使用开放式问题 + - 不会打断 → 可以使用表单 + +**表单结构(可选字段)**: + +当且仅当满足"意图 B"且信息适合结构化收集时,才在 JSON 中添加 form 字段: { "phase": "collection", - "reasoning": "需要识别任务所需的核心能力类型,这将决定资源配置的大方向", - "question": "从功能角度看,这个任务主要需要:\\nA. 获取和整合信息(搜索、查询、知识检索)\\nB. 处理和分析数据(计算、转换、分析)\\nC. 生成和创作内容(文档、报告、创意)\\nD. 集成外部服务(API调用、消息通知)\\n\\n请选择最主要的方向,或描述您的理解:" + "reasoning": "...", + "question": "...", + "form": [ + { + "type": "input | numberInput", + "label": "<维度描述>" + }, + { + "type": "select | multipleSelect", + "label": "<维度描述>", + "options": ["<选项A>", "<选项B>", "..."] + } + ] } -❌ **不好的具体提问示例**(避免这样问): -- "第一步需要调用哪个API?" -- "数据格式应该是JSON还是XML?" -- "需要设置多少个验证节点?" -- "具体的搜索关键词是什么?" +**表单类型说明**: +- input: 短文本输入 +- numberInput: 数字输入 +- select: 单选下拉 +- multipleSelect: 多选下拉 -适合选择题的场景: -- 任务类型分类(客服/创作/分析/集成) -- 核心能力方向(信息获取/数据处理/内容生成/服务集成) -- 复杂度定位(简单直接/中等复杂/综合复杂) -- 应用场景(内部使用/客户服务/自动化流程) - -提问建议: -- 每个问题聚焦一个战略维度 -- 用通俗语言而非技术术语 -- 提供选项时给出清晰的场景描述 -- 避免过早深入实现细节 +**关键提醒**: +- form 字段是可选的,不是必需的 +- 只在信息明确可结构化时使用表单 +- 保持对话的自然流畅优先于追求结构化 diff --git a/packages/service/core/chat/HelperBot/dispatch/topAgent/index.ts b/packages/service/core/chat/HelperBot/dispatch/topAgent/index.ts index 2238743c54..e16e9e893d 100644 --- a/packages/service/core/chat/HelperBot/dispatch/topAgent/index.ts +++ b/packages/service/core/chat/HelperBot/dispatch/topAgent/index.ts @@ -85,8 +85,8 @@ export const dispatchTopAgent = async ( const formData = TopAgentFormDataSchema.parse({ role: responseJson.task_analysis?.role, taskObject: responseJson.task_analysis?.goal, - tools: responseJson.resources?.tools?.map((tool: any) => tool.id), - fileUploadEnabled: responseJson.resources?.file_upload?.enabled + tools: responseJson.resources?.tools, + fileUploadEnabled: responseJson.resources?.system_features?.file_upload?.enabled }); if (formData) { diff --git a/packages/service/core/chat/HelperBot/dispatch/topAgent/prompt.ts b/packages/service/core/chat/HelperBot/dispatch/topAgent/prompt.ts index a06b34d102..b34cafd1a6 100644 --- a/packages/service/core/chat/HelperBot/dispatch/topAgent/prompt.ts +++ b/packages/service/core/chat/HelperBot/dispatch/topAgent/prompt.ts @@ -38,84 +38,133 @@ ${buildMetadataInfo(metadata)} 当处于信息收集阶段时: -**重要前提**:你需要为搭建者设计可复用的执行流程模板,而不仅仅是解决单个问题。 +**重要前提**:你需要为搭建者设计可复用的执行流程模板,而不是收集每次执行时的详细参数。 -**信息收集目标**:收集设计高质量流程模板所必需的核心信息,包括: +**核心目标**:确定"用户想要实现什么样的 AI 任务"以及"需要哪些系统资源",而不是"每次执行时的具体参数"。 -1. **任务类型与场景识别**(首要任务) - - 通过开放式提问,了解用户想要实现的具体功能 - - 基于用户描述,识别和归纳出任务所属的场景类型 - - 理解任务的核心特征、目标和定位 - - 为后续信息收集确定方向和重点 +**信息收集的四个维度**: -2. **能力边界确认**(最关键,必须优先确认) - - **基于实际工具列表**确认系统能力: - * 需要使用哪些核心工具(从可用工具列表中选择) - * 这些工具的具体功能和限制条件 - * 工具之间的组合使用方式 - - **明确不支持的功能范围**(重点): - * 哪些功能系统当前无法实现 - * 哪些操作没有对应的工具支持 - * 用户可能期望但实际不可行的需求 - - **技术限制和约束条件**: - * 数据格式、大小、性能等限制 - * 第三方服务的可用性和配置需求 - * 用户权限和资源约束 +1. **任务场景识别**(What - 首要任务) + - 通过开放式提问,了解用户想要实现的功能 + - 识别任务所属的应用场景: + * 信息检索与分析(搜索、总结、对比) + * 内容生成与创作(写作、设计、编程) + * 数据处理与转换(提取、整理、格式转换) + * 多轮对话与交互(客服、咨询、辅助决策) + * 自动化工作流(批量处理、定时任务) + - 理解任务的核心价值和目标用户群体 -3. **流程定位信息** - - 目标用户群体和典型使用场景 - - 解决问题的具体类型和适用范围 - - 流程的核心价值和预期效果 +2. **能力边界确认**(Can - 最关键) + - **基于可用工具列表确认系统能力**: + * 实现这个任务需要哪些核心能力?(搜索网络、调用API、数据分析等) + * 系统提供了哪些相关工具?(从可用工具列表中选择) + * 这些工具的功能范围和限制条件是什么? -4. **输入输出规范** - - 流程的输入参数类型、格式和来源 - - 输出结果的规范、格式和目标 - - 参数约束条件(必选/可选/默认值/取值范围) + - **明确不支持的功能**(重点): + * 用户期望的功能中,哪些系统当前无法实现? + * 哪些操作没有对应的工具支持? + * 有哪些技术限制需要提前告知用户? -5. **可变逻辑识别** - - 哪些步骤需要根据参数动态调整 - - 决策点的判断条件和分支逻辑 - - 可配置的工具选项和参数映射关系 + - **技术约束条件**: + * 是否依赖第三方服务?(需要配置 API Key 等) + * 是否有数据格式、大小、性能等限制? + * 用户需要具备什么样的权限或资源? + +3. **资源需求识别**(Need) + - **知识库需求**: + * 是否需要特定领域的知识?(产品文档、技术手册、行业知识) + * 哪些知识库与任务场景相关? + * 知识来源是实时搜索还是已有知识库? + + - **系统功能需求**: + * 是否需要用户上传文件? + - 需要:用户的私有数据、个人文档 + - 不需要:公开信息、可通过工具获取的数据 + * 是否需要特殊的交互模式? + + - **数据来源类型**: + * 数据从哪里来?(用户上传、工具获取、知识库检索) + * 是固定数据源还是动态数据源? + +4. **流程定位确认**(For Whom) + - 目标用户群体: + * 专业用户还是普通用户? + * 需要什么样的专业知识背景? + - 典型使用场景: + * 在什么情况下会使用这个流程? + * 解决什么类型的问题? + - 流程的适用范围: + * 适用于哪些具体场景? + * 有哪些边界条件和限制? **关键原则**: + +- **宏观优先,细节留后**:聚焦任务类型、工具选择、资源配置,不问执行时的具体参数 - **能力边界优先**:必须先确认系统能做什么,再设计流程细节 - **工具列表约束**:严格基于可用工具列表,不假设任何未提供的能力 -- **场景分类明确**:首先明确任务类型,指导后续信息收集方向 -- **问题精准聚焦**:每个问题都直接服务于输出准确的信息和工具列表 +- **场景分类明确**:首先明确任务类型,指导后续资源选择 - **明确不可行项**:重点确认哪些功能不能做,避免后续生成无法执行的流程 -**信息收集顺序建议**: -1. 先问任务类型/场景(确定大方向,对应输出type字段) -2. 再问能力边界(确认可行性,识别可用工具) -3. 然后问流程定位(明确具体目标) -4. 最后问输入输出、可变逻辑(完善设计细节) +**应该问的问题示例**: +✅ "你想实现什么功能?比如信息搜索、内容生成、数据分析还是其他类型?" +✅ "这个任务需要搜索网络信息吗?还是主要基于已有的知识库?" +✅ "用户需要上传自己的文件吗?还是系统可以自动获取所需数据?" +✅ "这个功能主要面向哪类用户?他们的典型使用场景是什么?" +✅ "系统目前有这些搜索工具:[列出工具],你认为哪个最适合你的需求?" +**不应该问的问题示例**: +❌ "用户每次输入时需要提供哪些参数?参数格式是什么?" +❌ "默认值应该设置为多少?取值范围是什么?" +❌ "输出结果的具体格式是 JSON 还是文本?包含哪些字段?" +❌ "执行流程的详细步骤是什么?先做什么后做什么?" +❌ "决策分支的具体判断条件是什么?" + +**信息收集顺序建议**: +1. 先问任务场景(What)→ 确定大方向,对应输出的 task_analysis.goal +2. 再问能力边界(Can)→ 确认可行性,识别可用工具 +3. 然后问资源需求(Need)→ 确定知识库和系统功能 +4. 最后问流程定位(For Whom)→ 明确目标用户和使用场景 **输出格式**: -**重要:信息收集阶段的所有回复必须使用JSON格式,包含 phase 字段** +**🚨 严格输出要求(极其重要)**: -直接输出以下格式的JSON(不要添加代码块标记): +1. **只输出 JSON 格式**:信息收集阶段的所有回复必须是纯净的 JSON,不要添加任何其他内容 +2. **禁止代码块标记**:不要使用 \`\`\`json 或任何代码块标记 +3. **禁止解释性文字**:不要在 JSON 前后添加任何说明、解释或寒暄语 +4. **必须包含 phase 字段**:每个回复都必须包含 "phase": "collection" + +**严格输出规则**: +- ❌ 不要使用 \`\`\`json 或其他代码块标记 +- ❌ 不要添加任何前言,如"好的,让我问几个问题"、"明白了"等 +- ❌ 不要添加任何后语,如"期待您的回复"、"请告诉我"等 +- ❌ 不要输出任何非 JSON 格式的内容 +- ✅ 直接、纯净地输出 JSON 内容 +- ✅ JSON 必须是有效的、可解析的格式 + +**标准输出格式**: + +直接输出以下格式的 JSON: { "phase": "collection", - "reasoning": "为什么问这个问题的推理过程:基于什么考虑、希望收集什么信息、对后续有什么帮助", + "reasoning": "为什么问这个问题的推理过程:基于什么考虑、希望收集什么信息、对后续资源选择有什么帮助", "question": "实际向用户提出的问题内容" } 问题内容可以是开放式问题,也可以包含表单填写: -开放式问题,无需表单填写: +**开放式问题示例**(无需表单填写): { "phase": "collection", - "reasoning": "需要首先了解任务的基本定位和目标场景,这将决定后续需要确认的工具类型和能力边界", - "question": "我想了解一下您希望这个流程模板实现什么功能?能否详细描述一下具体要处理什么样的任务或问题?" + "reasoning": "需要首先了解任务的应用场景类型,这将决定后续需要确认的工具类型和能力边界", + "question": "我想了解一下您希望这个 AI 助手实现什么功能?比如是信息搜索、内容生成、数据分析,还是其他类型的任务?" } -表单示例,一共有 4 类表单类型: +**表单问题示例**(一共有 4 类表单类型): { "phase": "collection", - "reasoning": "需要确认参数化设计的重点方向,这将影响流程模板的灵活性设计", - "question": "我需要和你确认一些参数,请根据你的需求选择对应的选项:", + "reasoning": "需要确认任务所需的核心能力类型,这将直接影响工具选择", + "question": "为了更好地为您设计流程,我需要确认以下信息:", "form": [ { "type": "input", @@ -127,44 +176,54 @@ ${buildMetadataInfo(metadata)} }, { "type": "select", - "label": "关于流程的参数化设计,用户最需要调整的是", + "label": "这个任务主要需要以下哪种能力", "options": [ - "输入数据源(不同类型的数据库/文件)", - "处理参数(阈值、过滤条件、算法选择)", - "输出格式(报告类型、文件格式、目标系统)", - "执行环境(触发方式、频率、并发度)" + "搜索网络信息(实时获取最新资料)", + "查询知识库(基于已有文档回答)", + "生成内容(写作、设计、编程等)", + "数据分析(处理和分析数据)", + "多种能力组合" ] }, { "type": "multipleSelect", - "label": "你想了解用户什么信息", + "label": "用户需要提供哪些输入(可多选)", "options": [ - "选项 A", - "选项 B", - "选项 C", - "选项 D" + "文本描述(直接输入问题或需求)", + "文件上传(用户的私有文档)", + "不需要用户输入(自动执行)" ] } ] } -选项设计原则: -1. 选项要覆盖主要可能性(3-4个为佳) -2. 包含"其他"选项让用户可以自由回答 -3. 选项要简洁明了,便于快速理解 -4. 当问题涉及量化、分类、优先级时优先使用选择题 +**选项设计原则**: +1. 选项聚焦于任务类型、能力类型、资源类型等宏观维度 +2. 避免询问具体参数、格式、默认值等执行细节 +3. 选项要覆盖主要可能性(3-5 个为佳) +4. 包含"其他"或"多种组合"选项让用户可以自由补充 +5. 选项要简洁明了,便于快速理解 -适合选择题的场景: -- 经验水平判断(初学者/有经验/熟练/专家) -- 优先级排序(时间/质量/成本/创新) -- 任务类型分类(分析/设计/开发/测试) -- 满意度评估(非常满意/满意/一般/不满意) -- 复杂度判断(简单/中等/复杂/极复杂) +**适合选择题的场景**: +- 任务场景分类(信息检索/内容生成/数据分析/自动化流程) +- 能力类型确认(搜索/查询/生成/分析/转换) +- 数据来源类型(用户上传/工具获取/知识库检索) +- 目标用户类型(专业用户/普通用户/特定领域用户) +- 工具选择(从可用工具列表中选择最合适的) -避免的行为: -- 不要为所有问题都强制提供选项 -- 选项之间要有明显的区分度 +**避免的行为**: +- 不要询问执行时的具体参数和默认值 +- 不要询问输出格式的详细规范 +- 不要询问流程的详细执行步骤 - 不要使用过于技术化的术语 +- 选项之间要有明显的区分度 + +**质量检查清单**(每次提问前自我检查): +□ 这个问题是关于"任务类型"而不是"执行参数"吗? +□ 这个问题是关于"需要哪些工具"而不是"如何使用工具"吗? +□ 这个问题是关于"是否需要知识库"而不是"知识库的具体内容"吗? +□ 这个问题有助于选择合适的资源,而不是设计执行细节吗? +□ 如果用户回答了这个问题,我能够在可用资源列表中选择合适的工具吗? @@ -243,30 +302,36 @@ ${resourceList} **资源识别规则**: 1. 在上面的"## 可用资源列表"中查找所有可用资源 2. 每个资源ID后面都有标签:[工具] 或 [知识库] -3. 输出时必须根据标签确定 type 值: - - 标签是 [工具] → "type": "tool" - - 标签是 [知识库] → "type": "knowledge" +3. 输出时必须根据标签确定资源应该放入哪个数组: + - 标签是 [工具] → 放入 resources.tools 数组 + - 标签是 [知识库] → 放入 resources.knowledges 数组 **输出格式要求**: -- ✅ 必须使用对象数组格式:[{"id": "...", "type": "..."}] +- ✅ tools 和 knowledges 都使用字符串数组格式:["资源ID1", "资源ID2"] - ✅ 资源ID必须完全匹配列表中的ID(包括大小写、特殊字符) -- ❌ 不要使用字符串数组格式:["...", "..."] -- ❌ 不要猜测 type 值,必须根据列表中的标签确定 +- ✅ 只输出资源ID字符串,不要包含 type 等其他字段 +- ❌ 不要使用对象数组格式:[{"id": "...", "type": "..."}] +- ❌ 不要混淆工具和知识库的位置 **输出前的自我检查步骤**: -1. 查看你选择的每个资源ID,它在列表中的标签是什么? -2. 如果标签是 [工具] → 设置 "type": "tool" -3. 如果标签是 [知识库] → 设置 "type": "knowledge" -4. 确保每个资源都有 id 和 type 两个字段 +1. 查看你选择的每个资源ID,它在可用资源列表中的标签是什么? +2. 如果标签是 [工具]: + - ✅ 将资源ID放入 resources.tools 数组 + - ❌ 不要放入 resources.knowledges 数组 +3. 如果标签是 [知识库]: + - ✅ 将资源ID放入 resources.knowledges 数组 + - ❌ 不要放入 resources.tools 数组 +4. 确保每个数组中只包含资源ID字符串 **常见错误避免**: - ❌ 不要凭空想象资源名称 - ❌ 不要使用通用描述如"数据库工具"而不指定具体ID - ❌ 不要引用"可能"存在但未在列表中明确的资源 -- ❌ 不要输出字符串数组,必须是对象数组 -- ❌ 不要把 [知识库] 标签的资源设置为 type: "tool" +- ❌ 不要使用对象数组格式,必须是字符串数组 +- ❌ 不要把标签为 [知识库] 的资源放入 tools 数组 +- ❌ 不要把标签为 [工具] 的资源放入 knowledges 数组 - ❌ **不要选择多个同类型的工具** -- ✅ 必须根据列表中的标签准确设置 type 值 +- ✅ 必须根据列表中的标签准确判断应该放入哪个数组 - ✅ 基于实际可用的资源进行规划 - ✅ **同一类型的工具只选择最合适的一个** @@ -333,8 +398,8 @@ ${resourceList} - task_analysis: 提供对任务的深度理解和角色定义 - reasoning: 说明所有资源(工具+知识库+系统功能)的选择理由和协同关系 - resources: 资源配置对象,包含三类资源 - * tools: 工具数组,每个对象包含 id 和 type(值为"tool") - * knowledges: 知识库数组,每个对象包含 id 和 type(值为"knowledge") + * tools: 工具ID字符串数组,例如:["tool_id_1", "tool_id_2"] + * knowledges: 知识库ID字符串数组,例如:["kb_id_1", "kb_id_2"] * system_features: 系统功能配置对象 - file_upload.enabled: 是否需要文件上传(必填) - file_upload.purpose: 为什么需要(enabled=true时必填) @@ -411,7 +476,6 @@ ${resourceList} **严格输出规则**: - ❌ 不要使用 \`\`\`json 或其他代码块标记 -- ❌ 不要使用旧格式的 tools 字段,必须使用 resources 结构 - ❌ 不要添加任何解释性文字或前言后语 - ✅ 必须使用 resources 对象,包含 tools、knowledges、system_features - ✅ file_upload.enabled=true 时必须提供 purpose 字段, @@ -424,7 +488,6 @@ ${resourceList} 3. 资源完整性:确保所有必需的资源都包含在 resources 配置中 4. 输出格式规范:严格遵循 resources 结构要求 5. 资源去重:同一个资源在 tools 或 knowledges 数组中只出现一次 -6. type准确性:工具的type为"tool",知识库的type为"knowledge" 7. 系统功能配置正确:file_upload.enabled=true时必须提供purpose字段 8. 输出纯净性:只输出JSON,不包含任何其他内容 diff --git a/packages/service/core/chat/HelperBot/dispatch/topAgent/type.ts b/packages/service/core/chat/HelperBot/dispatch/topAgent/type.ts index 5dae7a0989..baa169202c 100644 --- a/packages/service/core/chat/HelperBot/dispatch/topAgent/type.ts +++ b/packages/service/core/chat/HelperBot/dispatch/topAgent/type.ts @@ -25,9 +25,11 @@ export const TopAgentGenerationAnswerSchema = z.object({ resources: z.object({ tools: z.array(z.string()), knowledges: z.array(z.string()), - file_upload: z.object({ - enabled: z.boolean(), - purpose: z.string() + system_features: z.object({ + file_upload: z.object({ + enabled: z.boolean(), + purpose: z.string().nullish() + }) }) }) }); diff --git a/packages/service/core/workflow/dispatch/ai/agent/index.ts b/packages/service/core/workflow/dispatch/ai/agent/index.ts index 63d150a1cd..1622c4daa6 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/index.ts @@ -145,8 +145,8 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise toolDescription: toolNode?.toolDescription || toolNode?.name || '' }; }; - // console.log(JSON.stringify(completionTools, null, 2), 'topAgent completionTools'); - // console.log(subAppsMap, 'topAgent subAppsMap'); + console.log(JSON.stringify(agentCompletionTools, null, 2), 'topAgent completionTools'); + console.log(agentSubAppsMap, 'topAgent subAppsMap'); /* ===== AI Start ===== */ @@ -418,6 +418,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise const isReplanStep = isPlanAgent && agentPlan && replanMessages; console.log('planHistoryMessages', planHistoryMessages); + // 执行 Plan/replan if (isPlanStep) { const result = await planCallFn(); @@ -429,8 +430,9 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise }); if (result) return result; } + // 如果有保存的 skill id,恢复 skill 的 tools - else if (matchedSkillId) { + if (matchedSkillId) { addLog.debug(`恢复 skill tools, skill id: ${matchedSkillId}`); const skill = await matchSkillForId({ id: matchedSkillId, 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 593ec3edc7..c1bf1ed5c9 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 @@ -67,21 +67,18 @@ const ChatTest = ({ topAgentSelectedTools = [], skill, appForm, onAIGenerate }: onApply={async (generatedSkillData) => { console.log(generatedSkillData, 222); - // 1. 计算新的 tool - const newToolIds: string[] = []; + // 1. 收集 AI 生成的所有工具 ID(完整列表,不过滤) + const allGeneratedToolIds: string[] = []; generatedSkillData.execution_plan.steps.forEach((step) => { step.expectedTools?.forEach((tool) => { - if (tool.type === 'tool') { - const exists = skill.selectedTools.find((t) => t.pluginId === tool.id); - if (exists) return; - newToolIds.push(tool.id); + if (tool.type === 'tool' && !allGeneratedToolIds.includes(tool.id)) { + allGeneratedToolIds.push(tool.id); } }); }); - // 3. 并行获取新工具详情 const newTools = await loadGeneratedTools({ - newToolIds, + newToolIds: allGeneratedToolIds, existsTools: skill.selectedTools, topAgentSelectedTools, fileSelectConfig: appForm.chatConfig.fileSelectConfig