Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions ui/src/workflow-canvas/WORKFLOW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -513,8 +513,9 @@ MkFormList 的排序、增删均以 `cloneDeep` 回写;MkTable 保留普通行
| `validate` | 并行调用节点校验,返回 Promise |
| `onmousedown` | 启动节点拖拽或执行节点回调 |

`getGraphData()` 在实例未初始化时返回 `undefined`;`validate()` 此时返回已完成的空数组 Promise,
有实例时汇总节点各自的校验,不额外执行顶层图结构校验。
`getGraphData()` 在实例未初始化时返回 `undefined`;`validate()` 此时返回已完成的 Promise,
有实例时调用 `core/validate.ts` 的 `validateWorkflow()` 汇总节点各自的校验,不额外执行顶层图结构校验;
AI 生成的 validate 工具复用同一入口(`collectWorkflowValidationErrors()` 取全部失败原因)。

当前 `renderGraphData()` 不主动销毁上一实例;普通数据加载使用 `render()`,不要将前者当作
已封装完整清理的重置方法。画布卸载时销毁当前实例并清理 Teleport。
Expand Down Expand Up @@ -565,9 +566,16 @@ MkFormList 的排序、增删均以 `cloneDeep` 回写;MkTable 保留普通行
| panel/ | 将用户需求、文本、思考、工具及委派事件转换为对话内容展示 |

可新增的节点以节点菜单(`node-menu/menu.ts` 的 `getMenuNodes`)为准:节点目录与 add_node 都按画布模式取菜单节点,
另含工具库节点(tool-lib-node);菜单新增节点后 AI 侧自动可用。数据源节点(`kind` 为 DataSource)是知识库工作流入口,
另含工具(tool-lib-node)与智能体(application-node,仅应用工作流)两个资源类节点,对应菜单里的资源页签;
菜单新增节点后 AI 侧自动可用。数据源节点(`kind` 为 DataSource)是知识库工作流入口,
新增时不接上游,其余节点必须建即接。停止后续聊会重建父 agent,并沿用裁剪后的历史与首条需求。

资源类节点(tool-lib-node、application-node)的签名取决于新增时绑定的资源,不是静态的:get_tools / get_applications
列出可选资源(自定义工具带 inputs;工作流工具与智能体的签名取决于它们自己声明的参数,成本较高不在列表里带),
add_node 时按 tool_id / application_id 查出真实详情、连同输入输出字段一起写入新节点的 node_data,返回结果里的
`inputs`/`outputs` 就是这份真实签名,不需要再查一次。tool-lib-node 同时覆盖自定义工具与工作流工具(AI 侧只传
这一种 type,按工具类型选择实际的内部节点),application-node 的入参只能引用上游数据,不支持常量。

节点配置 agent 位于节点目录的 agent.ts,通过 index.ts 默认导出的 agent 字段注册;
未注册的节点仍进入 AI 可选目录(说明取菜单文案,输出取模板 config.fields),可新增和连线,但不能 AI 配置。NodeAgentModule 使用 JSON Schema 定义 params、config、returns:
params 描述运行时上游引用,config 描述建图时配置,returns 描述固定和动态输出。
Expand Down
9 changes: 6 additions & 3 deletions ui/src/workflow-canvas/ai-generate/impl/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getCanvasDetailTool,
getNodeDetailTool,
getNodeAnchorsTool,
getApplicationsTool,
getToolsTool,
layoutTool,
validateTool,
Expand All @@ -28,11 +29,12 @@ const GUIDE = `你是 MaxKB「智能体工作流」的搭建助手,在 LogicFl
- 画布用 lf_id 标识:主画布 "main"(或省略);循环体 = 该循环节点 id。每个循环体固定有 loop-start-node(输出 index、item)。
- 循环变量:要跨轮次累积 / 传递数据,或循环结束后还要用循环内算出的值时,configure_node 循环节点时在 requirement 写明要声明哪些循环变量、returns 列出变量名。循环体内用 ["loop", 变量名] 引用;循环变量初始为空,修改它只能在循环体内加变量赋值节点(variable-assign-node,target=["loop", 变量名]),其他节点的输出不会自动写入,所以每个要更新的循环变量都要在产出数据的节点之后接一个变量赋值节点;循环结束后下游用 [循环节点ID, 变量名] 引用最终值。
- 目录里标注「暂不支持 AI 配置」的节点可以新增和连线,但不能 configure_node:有可配置的节点能满足需求时优先用后者;确实要用时建好接好,并在最后总结里提示用户手动配置。
- 智能体节点(application-node)相当于把当前对话的一部分交给另一个已发布的智能体处理:add_node 时传 application_id(get_applications 查询),configure_node 时绑定用户问题与它需要的每个参数(只能引用上游数据,不支持常量)。

【从零搭建】
1. 先调 plan:规划器据需求组合节点函数,写出文字方案 { plan }(用户已在「规划流程」中看到全文)。拿到方案后先不要建图:不复述全文,用两三句概括主链路与关键假设,请用户确认或提出修改,然后直接结束本轮回复(不调用任何工具)。
用户确认后再照方案从第 2 步开始建;用户提出修改时,小改直接说明调整点后再次请用户确认,大改重新调 plan,同样确认后再建。
2. 用 add_node(type, source_anchor_id) 建节点并接上游:第一个节点接 "start-node";普通链式连接传上游节点 id;判断器 / 意图识别要先 configure_node 配好分支(requirement 写清分几路、各路条件或分类),再 get_node_anchors 取各分支出口接下游;多分支汇合用 add_edge 补入边。
2. 用 add_node(type, source_anchor_id) 建节点并接上游:第一个节点接 "start-node";普通链式连接传上游节点 id;新增工具节点(tool-lib-node)同时传 tool_id(get_tools 查询),新增智能体节点(application-node)同时传 application_id(get_applications 查询);判断器 / 意图识别要先 configure_node 配好分支(requirement 写清分几路、各路条件或分类),再 get_node_anchors 取各分支出口接下游;多分支汇合用 add_edge 补入边。
3. 循环:主画布 add_node 建 loop-node 并 configure_node 配循环方式;再带 lf_id=该循环节点 id 往循环体建(首节点 source_anchor_id="loop-start-node"),循环体内节点用 lf_id=该循环 id 配置;无限循环必须放 loop-break-node。
4. 节点建好接好后 configure_node(nodeId, args, requirement, returns?) 配置,相当于调用这个函数:
- args 传实参:形参名 → 上游引用 [节点ID, 字段](多引用形参传列表),按方案中各节点的输入来源给,框架会校验并绑定;
Expand All @@ -54,7 +56,7 @@ const PLAN_GUIDE = `你是 MaxKB「智能体工作流」的规划器。下方【
- 按数据依赖排顺序:每个节点的形参都要有上游返回值(或开始节点的 question、全局变量)来提供,类型要对得上(如循环的 array 需要上游返回 array)。
- 需要按条件 / 分类走不同处理时用分支(condition-node / intent-node),需要逐项处理一组数据时用循环(loop-node)。
- 只放需求实际需要的节点;末端必须是能输出给用户的节点,最终回答 is_result=true、中间步骤 false。
- 需要外部工具时先 get_tools 看有哪些可用工具。需求含糊时按最常见理解规划,并写明假设。
- 需要外部工具时先 get_tools 看有哪些可用工具;需要把一部分任务交给另一个智能体处理时先 get_applications 看有哪些已发布的智能体可用。需求含糊时按最常见理解规划,并写明假设。

方案格式(Markdown,面向用户可读,同时足够搭建 agent 照着建):
- 按执行顺序逐个列节点:节点名称(类型)——做什么与配置要求(AI 对话的角色 / 目标 / 输出、检索什么、判断条件、表单收集哪些字段);输入:形参 ← 上游节点.返回字段(如 问题 ← 开始.question);有 [字段名] 动态返回时写出要产出的字段与类型。
Expand All @@ -70,11 +72,12 @@ export function useApplicationAgent(bindings: AgentBindings): RunnableAgent {
{
prompt: buildParentPrompt(GUIDE, bindings),
tools: [
createPlanTool(bindings, { guide: `${PLAN_GUIDE}\n\n======== 可用节点 ========\n${renderCatalog(bindings)}`, task: PLAN_TASK, tools: [getToolsTool(bindings)] }),
createPlanTool(bindings, { guide: `${PLAN_GUIDE}\n\n======== 可用节点 ========\n${renderCatalog(bindings)}`, task: PLAN_TASK, tools: [getToolsTool(bindings), getApplicationsTool(bindings)] }),
getCanvasDetailTool(bindings),
getNodeDetailTool(bindings),
getNodeAnchorsTool(bindings),
getToolsTool(bindings),
getApplicationsTool(bindings),
addNodeTool(bindings),
addEdgeTool(bindings),
deleteNodeTool(bindings),
Expand Down
9 changes: 5 additions & 4 deletions ui/src/workflow-canvas/ai-generate/impl/knowledge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const GUIDE = `你是 MaxKB「知识库工作流」的搭建助手,在 LogicFl
7. 全部完成后调 validate,problems 非空就按提示修复,直到 qualified=true,再调 layout 整理布局。

【写入前加工:标签 / 关联问题 / 元数据】
需求要给文档打标签、补关联问题、改文档名或补元数据时,在文档分段与知识库写入之间加一段加工链路,由自定义代码(tool-custom-node)把分段结果改写成带这些字段的文档列表,知识库写入改为引用代码节点:documents ← [代码节点ID, "result"]。
需求要给文档打标签、补关联问题、改文档名或补元数据时,在文档分段与知识库写入之间加一段加工链路。规划阶段应该已经 get_tools 查过现成工具;方案里写了用现成工具就照方案 add_node(type="tool-lib-node", tool_id) 接上,configure_node 时 documents 引用文档分段的 paragraph_list,其余入参按工具自己的签名绑定,输出字段必须是整个文档列表才能直连知识库写入。方案里没有现成工具,或续聊时还没查过,才用自定义代码(tool-custom-node)把分段结果改写成带这些字段的文档列表,知识库写入改为引用代码节点:documents ← [代码节点ID, "result"]。
- 用 AI 生成的(如按内容打标签):文档分段 → AI 对话(ai-chat-node)→ 自定义代码 → 知识库写入。
· AI 对话:materials 传文档分段的 paragraph_list,提示词要求只输出 JSON,不要多余文字,格式固定为 { "文档名": [{ "key": "标签名", "value": "标签值" }] },并写清标签的维度和取值范围。
· 自定义代码:inputs 两个引用参数 documents(array)← 文档分段.paragraph_list、ai_output(string)← AI 对话.answer。代码解析 ai_output(先去掉 Markdown 代码块标记,解析失败时保留原文档不加标签),遍历 documents,按 name 把标签写进对应文档的 tags,原字段一律保留,返回整个 documents 列表。AI 输出的字典只是查找表,不是返回值。
Expand All @@ -81,7 +81,8 @@ const GUIDE = `你是 MaxKB「知识库工作流」的搭建助手,在 LogicFl
const PLAN_GUIDE = `你是 MaxKB「知识库工作流」的规划器。下方【可用节点】把每个节点写成一个函数(说明、形参、返回字段)。知识库工作流把外部数据源经文档处理写入知识库;它没有固定开始节点,入口是数据源节点。根据用户需求,像写程序一样组合这些函数,写出一份文字方案:先给用户确认,再供搭建 agent 照着建。你只规划、不改画布。

思路:
- 先拆解需求:数据从哪来(本地文件 / Web 站点等数据源)、如何清洗(内容提取、分段)、写到哪(当前知识库)。
- 规划前必须先调一次 get_tools,不要凭经验判断「大概没有」就跳过:看工具库里有没有现成的工具能直接完成需求里的加工步骤(打标签、提取信息等)。有合适的就在方案里直接用它(tool-lib-node),不要重新搭一条 AI 对话 + 自定义代码的链路;确实没有才按下文的生成式做法写。
- 拆解需求:数据从哪来(本地文件 / Web 站点等数据源)、如何清洗(内容提取、分段)、写到哪(当前知识库)。
- 入口数据源优先用内置的本地文件(data-source-local-node,画布通常已自带)或 Web 站点(data-source-web-node);只有需求指向其他来源时才 list_datasource 查数据源工具(tool-lib-node)。
- 按数据依赖排顺序:本地文件.file_list → 文档内容提取 → 文档分段 → 知识库写入;Web 站点.document_list 已含内容,直接 → 文档分段 → 知识库写入。每个节点的形参都要有上游返回值来提供,类型要对得上。
- 需要按条件 / 分类走不同处理时用分支(condition-node / intent-node),需要逐项处理一组数据时用循环(loop-node)。
Expand All @@ -91,7 +92,7 @@ const PLAN_GUIDE = `你是 MaxKB「知识库工作流」的规划器。下方【
方案格式(Markdown,面向用户可读,同时足够搭建 agent 照着建):
- 按执行顺序逐个列节点:节点名称(类型)——做什么与配置要求(数据源抓取 / 上传方式、分段策略、写入哪);输入:形参 ← 上游节点.返回字段;有 [字段名] 动态返回时写出要产出的字段与类型。
- 分支节点(判断器 / 意图识别)逐路写:条件或分类 → 该路的后续节点,最后一路为 else / 其他;循环节点写清循环方式与循环体内的节点。
- 需求要给文档打标签、补关联问题或元数据时,在文档分段与知识库写入之间加加工链路:AI 生成的用 文档分段 → AI 对话(只输出 JSON)→ 自定义代码(合并进文档的 tags 等字段)→ 知识库写入;规则能确定的用 文档分段 → 自定义代码 → 知识库写入。方案里写清标签的维度和取值。
- 需求要给文档打标签、补关联问题或元数据时,在文档分段与知识库写入之间加加工链路:get_tools 查到合适的现成工具就用 文档分段 → 该工具(tool-lib-node)→ 知识库写入,方案里写清这个工具叫什么、它的输出字段对应知识库写入的哪个引用;没有合适工具,AI 生成的用 文档分段 → AI 对话(只输出 JSON)→ 自定义代码(合并进文档的 tags 等字段)→ 知识库写入,规则能确定的用 文档分段 → 自定义代码 → 知识库写入,方案里写清标签的维度和取值。
- 最后写「关键假设」:需要用户事先准备或之后补充的内容(如数据源、知识库),以及对含糊需求的理解。
方案写完即结束,之后不再调用任何工具。`

Expand All @@ -106,7 +107,7 @@ export function useKnowledgeAgent(bindings: AgentBindings): RunnableAgent {
createPlanTool(bindings, {
guide: `${PLAN_GUIDE}\n\n======== 可用节点 ========\n${renderCatalog(bindings)}`,
task: PLAN_TASK,
tools: [listDataSourceTool(bindings)],
tools: [listDataSourceTool(bindings), getToolsTool(bindings)],
}),
getCanvasDetailTool(bindings),
getNodeDetailTool(bindings),
Expand Down
8 changes: 6 additions & 2 deletions ui/src/workflow-canvas/ai-generate/impl/shared.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getMenuNodes } from '@/workflow-canvas/node-menu/menu'
import { WorkflowNodeType, type ShapeItem } from '@/workflow-canvas/types'
import { WorkflowMode, WorkflowNodeType, type ShapeItem } from '@/workflow-canvas/types'
import { loopModeOf } from '@/workflow-canvas/ai-generate/tools/canvas'
import { inheritMessages, useAgentConfig } from '@/workflow-canvas/ai-generate/common'
import { createAgent } from '@/workflow-canvas/ai-generate/create-agent'
Expand All @@ -25,7 +25,8 @@ export function nodeLine(node: CatalogNode): string {

/**
* 可用节点函数目录:与节点菜单(node-menu)一致,按画布模式列出全部菜单节点,循环体专属节点单列;
* 末尾追加「工具」(tool-lib-node)函数,对应菜单的工具 / 数据源页签。
* 末尾追加「工具」(tool-lib-node,涵盖自定义工具与工作流工具)和「智能体」(application-node,仅应用工作流)函数,
* 对应菜单里的资源页签(工具 / 数据源 / 智能体),这些节点不在 menu.ts 的静态列表里。
*/
export function renderCatalog(bindings: AgentBindings): string {
const mainGroups = getMenuNodes(bindings.workflowMode)
Expand All @@ -36,6 +37,9 @@ export function renderCatalog(bindings: AgentBindings): string {
const sections = mainGroups.filter((group) => group.list.length).map((group) => `【${group.label}】\n${group.list.map(nodeLine).join('\n\n')}`)
const toolLib = nodeAgentFor(WorkflowNodeType.ToolLib)
if (toolLib) sections.push(`【工具】\n${renderNodeFunction(WorkflowNodeType.ToolLib, '工具', toolLib)}`)
const isApplicationMode = bindings.workflowMode === WorkflowMode.Application || bindings.workflowMode === WorkflowMode.ApplicationLoop
const application = isApplicationMode ? nodeAgentFor(WorkflowNodeType.Application) : undefined
if (application) sections.push(`【智能体】\n${renderNodeFunction(WorkflowNodeType.Application, '智能体节点', application)}`)
if (loopOnly.length) sections.push(`【仅循环体内可用(lf_id=所在循环节点 id)】\n${loopOnly.map(nodeLine).join('\n\n')}`)
return sections.join('\n\n')
}
Expand Down
Loading
Loading