公众号:dify实验室
基于LLMOps平台-Dify的一站式学习平台。包含不限于:Dify工作流案例、DSL文件分享、模型接入、Dify交流讨论等各类资源分享。
前言
Dify 或其底层模型默认使用 Mermaid 语法生成的思维导图,虽然快速方便,但是生成效果嘛,就像下图这样,又乱又丑。像极了买家秀与卖家秀。
本文旨在分享一个基于 Dify 工作流的设计思路,利用大语言模型(LLM)的能力,结合 PlantUML 语法和外部渲染服务,实现输入主题或内容后,一键生成更美观的思维导图图片,实现下图的效果。
2、工作流设计
以下是构建该 Dify 工作流的核心步骤和节点设计:
工作流概览:
输入(主题/内容)-> [LLM 处理内容/拓展主题] -> [LLM 生成 PlantUML 语法] -> [HTTP 请求 PlantUML 渲染服务] -> 输出(思维导图图片)(还 可以生成流程图,如下图所示)
节点详解:
-
1、开始节点 :
-
- 配置输入变量:
topic
(文本类型): 用于定义思维导图的核心主题。例如:“人工智能的应用领域”。
content
(文本类型, 可选): 用户可以预先提供一些关于主题的初步内容、要点或结构。如果留空,则依赖 LLM 基于
topic
进行拓展。
-
2、LLM 节点 1 (内容整理与拓展):
- 作用:
根据输入判断是整理用户提供的内容,还是围绕主题进行智能拓展,并输出结构化的思维导图层级。
- 输入:
topic
,content
- Prompt 设计 :
- 作用:
-
- 输出变量:
包含层级化内容的文本。
3.LLM 节点 2 (生成 PlantUML 语法):
-
作用:
-
将上一步输出的结构化内容转换成 PlantUML 的思维导图语法。这里使用了deepseek v3模型。(没有免费token资源的,可以看文末福利)
- 输入:
上一步的结构化文本。
-
Prompt 设计 :
# 角色:PlantUML 思维导图代码生成器 ## 任务: 将用户提供的、具有层级结构的文本,转换为符合 PlantUML 思维导图语法的、准确无误的代码。 ## 输入: 一段表示层级关系的文本。层级通常通过缩进或不同的标记符号(如 `-`, `*`, 数字等)来体现。 ## 输出要求: 1. **代码封装:** 必须使用 `@startmindmap` 作为代码开头,并以 `@endmindmap` 结尾。 2. **层级表示(核心语法):** * 使用 PlantUML 的星号 (`*`) 语法来表示节点层级。 * 根节点使用一个星号 (`*`)。 * 每个下一级节点**必须**在前一级的基础上增加一个星号(例如,根是 `*`,其子级是 `**`,子级的子级是 `***`,以此类推)。**层级必须连续递增,不能跳级。** 3. **内容保真:** 必须准确地保留原始文本中的节点内容和层级结构。 4. **格式纯粹:** 输出结果**必须**是完整且纯粹的 PlantUML 代码块,不包含任何代码之外的解释、说明、标题、代码块标记(如 ```plantuml)或任何其他文字。 ## 关键语法规则与常见错误(请严格遵守): 1. **起始/结束标签:** 绝对不能遗漏 `@startmindmap` 和 `@endmindmap`。 * *错误示例:* 缺少 `@endmindmap`。 2. **星号层级:** 层级**只由**星号数量决定,与其他缩进、符号无关。 * *错误示例 1 (使用其他符号):* ``` * Root - Child 1 // 错误,应为 ** + Child 2 // 错误,应为 ** ``` * *正确示例 1:* ``` * Root ** Child 1 ** Child 2 ``` * *错误示例 2 (跳级):* ``` * Root *** Grandchild // 错误,缺少 ** 层级 ``` * *正确示例 2 (假设有中间层):* ``` * Root ** Child *** Grandchild ``` 3. **星号与文本间的空格:** 在最后一个星号 (`*`) 和节点文本之间,**必须**有且仅有一个空格。 * *正确:* `* Node Text` * *正确:* `** Child Node` * *错误:* `*Node Text` (缺少空格) * *错误:* `** Child Node` (多余的空格) 4. **行首空格:** 行首不应有多余的空格,星号应顶格开始。 * *正确:* `* Node` * *错误:* ` * Node` (星号前有空格) 5. **节点内容:** 如果原始节点内容包含 PlantUML 的特殊字符(如 `*`, `_`, `-`, `~`, `/`, `\` 等),并且需要按原样显示,理论上需要转义或使用引号,但在此基础转换任务中,优先**直接复制**原始文本内容。如果遇到渲染问题,通常是这些特殊字符导致,但生成器应首先尝试直接映射。 ## 示例: **如果输入文本是:** 项目规划 初期调研 市场分析 (V1.0) 用户_访谈 中期执行 开发阶段 / 测试阶段 后期总结 - Review **则期望的输出 PlantUML 代码是:** ```plantuml @startmindmap * 项目规划 ** 初期调研 *** 市场分析 (V1.0) *** 用户_访谈 ** 中期执行 *** 开发阶段 / 测试阶段 ** 后期总结 - Review @endmindmap
- 输出变量:
-
- 输出变量:
plantuml_code
(包含 PlantUML 语法的文本)。 -
4.代码执行节点
作用
将plantuml代码PlantUML 代码进行特殊的压缩和编码,并生成url。
代码如下:
import zlib import base64 # 虽然不直接用标准base64编码,但导入以示对比 def main(arg1: str ) -> dict : """ 接收 PlantUML 代码字符串 (arg1),将其压缩并编码为 PlantText URL。 Args: arg1: 包含 PlantUML 代码的字符串。 Returns: 一个包含 'result' (生成的 URL) 或 'error' (错误信息) 的字典。 """ # --- PlantUML 特定的编码辅助函数 (放在 main 内部以保持自包含) --- PLANTUML_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_" def encode6bit(b): """复制 JS encode6bit 函数的逻辑""" if 0 <= b < 10 : return chr ( 48 + b) b -= 10 if 0 <= b < 26 : return chr ( 65 + b) b -= 26 if 0 <= b < 26 : return chr ( 97 + b) b -= 26 if b == 0 : return '-' if b == 1 : return '_' return '?' # 理论上对于 6-bit 值不会发生 def append3bytes(b1, b2, b3): """复制 JS append3bytes 函数的逻辑""" c1 = b1 >> 2 c2 = ((b1 & 0x03 ) << 4 ) | (b2 >> 4 ) c3 = ((b2 & 0x0F ) << 2 ) | (b3 >> 6 ) c4 = b3 & 0x3F result = "" result += encode6bit(c1 & 0x3F ) result += encode6bit(c2 & 0x3F ) result += encode6bit(c3 & 0x3F ) result += encode6bit(c4 & 0x3F ) return result def encode_plantuml(plantuml_text): """压缩并使用 PlantUML 特定的 Base64 编码""" # 1. 编码为 UTF-8 字节 utf8_bytes = plantuml_text.encode( 'utf-8' ) # 2. 使用 raw deflate 压缩 (level 9, 无 zlib 头/尾) compressor = zlib.compressobj(level= 9 , method=zlib.DEFLATED, wbits= -15 ) compressed_data = compressor.compress(utf8_bytes) compressed_data += compressor.flush() # 3. 使用 PlantUML 特定的 Base64 进行编码 encoded_result = [] data_len = len (compressed_data) i = 0 while i < data_len: b1 = compressed_data[i] i += 1 b2 = compressed_data[i] if i < data_len else 0 i += 1 b3 = compressed_data[i] if i < data_len else 0 i += 1 encoded_result.append(append3bytes(b1, b2, b3)) return "" .join(encoded_result) # --- 辅助函数结束 --- # --- 主逻辑 --- if not isinstance (arg1, str ) or not arg1: return { "error" : "输入参数 arg1 必须是非空的 PlantUML 代码字符串。" } plantuml_code = arg1 try : # 编码 PlantUML 代码 encoded_string = encode_plantuml(plantuml_code) # 构建 URL (默认使用 png 格式) base_url = "https://uml.planttext.com/plantuml/" output_format = "png" # 你可以改为 "svg" 或 "txt" url = f "{base_url}{output_format}/{encoded_string}" # 返回包含结果 URL 的字典 return { "result" : url } except Exception as e: # 打印错误到 Dify 的日志(如果可能)并返回错误信息 print (f "生成 PlantUML URL 时出错: {e}" ) return { "error" : f "无法生成 PlantUML URL: {e}" }
- 输出变量:
-
5.HTTP 请求节点
作用:
-
请求URL,获取渲染后的图片。
- 输入:代码节点的输出
- 输出:
HTTP 请求的响应体。如果请求成功,响应体将是图片数据(PNG 或 SVG)。Dify 通常能识别响应头中的
Content-Type
并尝试展示图片。确保 Dify 配置能正确处理和展示图片类型的响应。 - 输出变量:
包含图片数据。
-
-
6.结束节点 :
- 配置:
将http请求节点的输出的files设置为最终输出。
- 效果:
当工作流执行完毕时,用户将在 Dify 的结果区域直接看到生成的思维导图图片。
- 配置:
3、上述流程优化余地
尽管上述流程可以实现基本功能,但仍有进一步优化的空间:
- PlantUML 语法增强:
指导 LLM 生成更复杂的 PlantUML 语法,例如添加颜色、图标、特定布局方向(左右分布等)、使用皮肤参数(skinparam)进行样式定制。这需要在 LLM 节点 2 的 Prompt 中加入更具体的指令。
- 错误处理:
-
在 LLM 节点后检查输出是否符合预期格式。
-
在 HTTP 请求节点后检查响应状态码,如果渲染失败(例如 PlantUML 语法错误或服务器问题),可以返回错误信息或默认图片。
-
- 私有化部署: 对于有隐私或性能要求的场景,可以自建 PlantUML 服务器或 Kroki 实例,并将 HTTP 请求指向内部地址。
4、其他在线网站
除了通过 Dify 工作流生成思维导图,市面上也有许多优秀的、用户可以直接在线使用的思维导图工具,它们通常提供更丰富的交互式编辑体验:
- MindMeister:
流行的在线协作思维导图工具,功能全面,界面友好。
- XMind (Web / App):
老牌思维导图软件,提供 Web 版本和强大的桌面/移动应用,模板丰富。
- Coggle:
简洁美观的在线思维导图工具,适合快速创建和分享。
- Miro / FigJam:
更偏向于在线白板,但其思维导图功能也非常强大,适合团队协作和头脑风暴。
- Draw.io (diagrams.net):
免费的在线图表绘制工具,支持包括思维导图在内的多种图表类型,功能强大但可能稍复杂。
- ProcessOn / Boardmix:
国内流行的在线协作绘图平台,也包含思维导图功能。
这些工具各有侧重,用户可以根据自己的具体需求(例如,是需要 AI 辅助快速生成,还是需要手动精细绘制与协作)来选择合适的工具。
dify实验室
基于LLMOps平台-Dify的一站式学习平台。包含不限于:Dify工作流案例、DSL文件分享、模型接入、Dify交流讨论等各类资源分享。
关注我可领DSL文件及token福利
往期工作流文章
10分钟构建基于 Dify 的智能文章仿写工作流:配置指南,效率飙升300%!
20分钟从零到一构建Dify智能客服工作流教程(附DSL文件下载)
更多工作流案例,请到公众号主页查看
dify相关资源
如果对你有帮助,欢迎点赞收藏备用。
回复 DSL 获取公众号DSL文件资源
回复 入群 获取二维码,我拉你入群
回复 tk 获取免费token资源
你又不打算赞赏,就点赞、在看吧