先说明一个背景:我在做 AI Agent 绘图时,最常遇到的痛点不是“模型不懂 SVG”,而是“模型画出来的图完全不可控”。自动布局看似聪明,但一旦节点一多,图形就开始乱跑;想微调某个框的位置,只能整个重画。后来我接触到SVG-diagram这个思路——它不是用自动布局引擎,而是让 Agent 像人一样“手工摆放”每个节点和连线,坐标自己算、位置自己定。这种方式在工控组态、架构图、流程图中非常实用。
本文就围绕SVG-diagram 这个 agent skill展开,讲清楚它是什么、和普通 Agent 指令有什么区别、底层怎么实现坐标定位,并给出一个完整的可落地示例。无论你是想自己做 Agent 技能包,还是只想让 AI 帮你画出满意的架构图,这篇文章都会很有用。
1. SVG-diagram 是干什么的
1.1 它能解决什么问题
SVG-diagram 是一个agent skill,作用是让 AI Agent 以“手工摆放坐标”的方式绘制 SVG 图表。传统画图工具一般依赖自动布局算法,比如 Graphviz、Mermaid,而 SVG-diagram 的核心逻辑是:
- 由 Agent 拿到节点关系后,自己计算出每个节点在画布上的 x、y 坐标;
- 再根据坐标生成对应的
<rect>、<circle>、<path>、<text>等 SVG 元素; - 通过坐标数值的精确控制,让每个图形元素都被放在预期位置。
这种方式的优点很明显:
| 对比项 | 自动布局(Mermaid 等) | 手放坐标(SVG-diagram) |
|---|---|---|
| 布局可预测性 | 一般 | 高 |
| 对复杂图表的支持 | 一般 | 强 |
| 修改单个元素位置 | 困难 | 容易 |
| 对长文本的适配 | 一般 | 可控 |
| 落地到实际 SVG 文件 | 需转换 | 直接输出 |
1.2 它适合用在哪些场景
我在实际使用中总结了几个典型场景:
- 架构图。系统模块多、依赖关系复杂,自动布局往往把图拉得很长,手放坐标能把同类模块归拢到一起。
- 流程图。需要精确控制分支走向,或者希望流程图贴在文档里的固定位置。
- 工控组态图。工控领域经常要绘制“设备—管道—阀门”关系图,这类图对点位位置要求极高,必须手放坐标。
- 教学示意图。比如在学习 SVG 时,手放坐标能直观看到每个元素的布局逻辑。
1.3 它和普通 SVG 生成有什么不同
普通生成 SVG 的 Prompt 往往是:
帮我画一个系统架构图,包含用户端、网关、服务端。模型产出的结果随机性很大,位置全靠“感觉”。但 SVG-diagram 提供了一套约束:它给 Agent 定义了一个“坐标画布”规则,要求每个元素都显式声明x、y、width、height,并且由 Agent 自己计算节点坐标和连线路径。这样生成结果稳定、可微调、可复用。
2. Agent Skill 是什么,和 Agent 有什么区别
2.1 Skill 的概念
要理解 SVG-diagram,必须先理解agent skill。
Agent Skill 可以翻译为“智能体技能包”,本质是一套结构化的能力模块。它通常包含三部分:
- 能力描述:告诉 Agent 这个技能是做什么的、在什么场景下调用。
- 使用流程:定义调用该技能时的步骤和约束。
- 工具或模板代码:提供可直接使用的函数、模板、示例文件。
以 SVG-diagram 为例,这个 skill 可能是这样定义的:
技能名称:SVG-diagram 功能:根据给定的节点和关系,生成手放坐标的SVG图表 调用条件:用户请求绘制架构图、流程图、拓扑图、组态图 输出格式:标准SVG代码,坐标必须由技能规则自行计算2.2 Skill 与 Agent 的区别
这是很多初学者容易混淆的地方,我做一个简洁区分:
| 维度 | Agent | Skill |
|---|---|---|
| 概念层级 | 完整智能体,能自主决策 | 智能体内部的一类能力模块 |
| 是否独立运行 | 是 | 否,需要被 Agent 调用 |
| 职责范围 | 感知、规划、执行、记忆 | 执行某一类具体子任务 |
| 类比 | 一个员工 | 员工的某项工作技能 |
可以这样理解:Agent 是一个整体,它负责“判断现在该干什么”;Skill 是一个功能包,它负责“这件事具体怎么干”。SVG-diagram 本身不运行成一个 Agent,而是作为技能,嵌入到 Agent 中,当 Agent 需要画图时被激活。
2.3 为什么把 SVG 画图能力设计成 Skill 而不是独立 Agent
设计成 Skill 的主要原因有三个:
- 复用性。多个 Agent 可以共享同一个 SVG-diagram 技能,不需要重复开发。
- 职责单一。画图只负责画图,Agent 的规划和记忆能力不需要参与绘图细节。
- 可维护性。当需要调整画图规则时,只改 Skill 里的配置文件即可,不需要改整个 Agent 逻辑。
3. 核心原理:什么是手放坐标式 SVG
3.1 先理解 SVG 坐标系
SVG 的坐标系和我们初中数学里的坐标系不一样。它的原点(0, 0)位于画布左上角,x 轴向右为正,y 轴向下为正。
比如一个矩形:
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg"> <rect x="50" y="60" width="120" height="80" fill="#e0f7fa" stroke="#00796b" /> </svg>这个矩形的左上角位于距离画布左边界 50 像素、上边界 60 像素的位置。
(0,0) ----------------------------------> x 轴正向 | (50,60) | +--------------+ | | | | | | | +--------------+ ↓ y 轴正向所以“手放坐标”中的“坐标”,指的就是这种基于像素或用户单位的 x、y 数值。
3.2 自动布局与手放坐标的底层差异
Mermaid 之类的工具使用的是自动布局引擎,比如dagre、elk,它们会根据节点大小和边关系计算出布局位置,好处是省事,坏处是:
- 节点多时布局不可控;
- 想微调某个节点的位置很难;
- 输出是 Mermaid 语法,还需要额外渲染成图。
而 SVG-diagram 让 Agent 自己承担“计算坐标”的工作。这样 Agent 可以按照用户描述的布局意图,把节点一个一个摆上去。例如用户说“把用户端放左上角,网关放中间,服务端放右侧”,Agent 就会按这个方位去计算坐标。
3.3 一个简单的手放坐标示例
比如我们想让 Agent 画一个两节点的简单图:节点 A 在左边,节点 B 在右边,中间一条连线。
手放坐标的生成逻辑是:
- 画布宽度 600,高度 200。
- 节点 A 的矩形:左上角
(30, 60),宽 120,高 80。 - 节点 B 的矩形:左上角
(400, 60),宽 120,高 80。 - 连线从 A 的右边缘中点
(150, 100)连到 B 的左边缘中点(400, 100)。
生成 SVG:
<svg width="600" height="200" xmlns="http://www.w3.org/2000/svg"> <rect x="30" y="60" width="120" height="80" fill="#bbdefb" stroke="#1565c0" stroke-width="2" /> <text x="90" y="105" text-anchor="middle" font-size="16">Node A</text> <rect x="400" y="60" width="120" height="80" fill="#c8e6c9" stroke="#2e7d32" stroke-width="2" /> <text x="460" y="105" text-anchor="middle" font-size="16">Node B</text> <line x1="150" y1="100" x2="400" y2="100" stroke="#546e7f" stroke-width="2" /> </svg>可以看到,每个元素都可以精确控制位置。这就是 SVG-diagram 的核心思想。
4. 环境准备与项目结构
4.1 环境说明
编写 SVG-diagram 这个 skill 不需要复杂的开发环境,我建议按以下方式准备:
- Agent 运行时:支持 Skill 机制的 Agent 框架,例如 Claude Agent SDK、自研 LLM Agent 等。本文以通用 Skill 结构为例。
- 语言:Python 3.9+,用于编写坐标计算与 SVG 生成辅助函数。
- 浏览器:用于预览生成的 SVG 文件,Chrome、Edge 均可。
- 编辑器:VS Code 或任意文本编辑器。
版本不是固定要求,核心是掌握 Skill 的组织方式。
4.2 Skill 项目目录结构
我建议这样组织svg-diagram技能包:
svg-diagram/ ├── SKILL.md # 技能描述,给 Agent 看的说明书 ├── templates/ # SVG 模板 │ ├── basic_rect.svg # 基础矩形模板 │ ├── flow_node.svg # 流程节点模板 │ └── curved_path.svg # 曲线路径模板 ├── scripts/ │ ├── generate_svg.py # 根据节点数据生成 SVG │ ├── coordinate_utils.py # 坐标计算工具 │ └── validate_svg.py # SVG 合法性检查 ├── examples/ │ ├── architecture.json # 架构图输入示例 │ └── architecture.svg # 架构图输出示例 └── README.md # 使用说明这样的结构有几点好处:
SKILL.md是 Agent 优先读取的入口文件;templates存放常用图形模板,方便 Agent 直接复用;scripts存放计算和生成逻辑,让 Agent 不用凭空手算;examples提供输入输出样例,帮助 Agent 理解任务。
4.3 安装依赖
如果只用 Python 标准库生成 SVG,不需要额外依赖。如果希望后续做更复杂的布局,可以安装lxml用于 XML 解析:
pip install lxml不过下面的示例代码只使用标准库xml.etree.ElementTree,所以不装也能运行。
5. 编写 SKILL.md 技能说明书
5.1 SKILL.md 的作用
SKILL.md 是整个技能包的核心。它的作用不是给程序员看,而是给 Agent 的 LLM 上下文看。所以写法要有别于普通开发文档,要强调“触发条件、执行步骤、行为约束、输出格式”。
5.2 完整示例
# SVG-diagram ## 功能描述 根据用户提供的节点关系描述,生成坐标精确、排版可控的 SVG 图表。 本技能适用于:架构图、流程图、网络拓扑图、工控组态示意图。 不适用于:照片处理、位图编辑、复杂 3D 图形。 ## 触发条件 当用户提出以下请求时,必须调用本技能: - “画一张架构图” - “生成流程图” - “用 SVG 画拓扑图” - “画一个组态图” ## 执行步骤 1. 解析用户输入,提取节点列表和连接关系。 2. 根据节点数量确定画布尺寸,默认宽度 800,高度按节点行数自适应。 3. 使用 coordinate_utils.py 中的函数计算节点坐标。 4. 从 templates/ 中选择合适的模板。 5. 生成 SVG 字符串,并确保每个元素带有 x、y 属性。 6. 返回完整 SVG 代码,并附上画布尺寸说明。 ## 行为约束 - 必须手放坐标,禁止使用自动布局占位。 - 每个矩形节点必须声明 x、y、width、height。 - 连线必须基于节点边缘坐标计算,不要使用随机坐标。 - 文字如果超出矩形范围,需要重新调整矩形宽度。 - 输出必须是纯 SVG,不能附带 Mermaid 或 HTML 包装。这里的关键是“行为约束”部分。如果没有这段,Agent 很可能又按惯性输出普通 SVG,失去 hand-placed 的意义。
6. 坐标计算工具函数
6.1 为什么要写工具函数
Agent 直接计算坐标时,经常出现“算错了”的情况。把坐标计算抽成 Python 函数,有两个好处:
- Agent 可以调用函数获得准确坐标,不用心算;
- 坐标逻辑统一,生成的图样式更稳定。
6.2 坐标计算工具代码
# 文件路径:svg-diagram/scripts/coordinate_utils.py def calc_node_rect(index, node_width=140, node_height=70, gap_x=60, gap_y=50, offset_x=40, offset_y=40): """ 根据节点序号计算矩形左上角坐标。 采用两列布局:序号 0、1 在第一行,2、3 在第二行,以此类推。 """ col = index % 2 row = index // 2 x = offset_x + col * (node_width + gap_x) y = offset_y + row * (node_height + gap_y) return { "x": x, "y": y, "width": node_width, "height": node_height, "cx": x + node_width / 2, "cy": y + node_height / 2 } def calc_edge(start_rect, end_rect, direction="right"): """ 计算两个节点之间的连线坐标。 direction 控制连线从哪个方向连接到终点。 """ if direction == "right": x1 = start_rect["x"] + start_rect["width"] y1 = start_rect["cy"] x2 = end_rect["x"] y2 = end_rect["cy"] elif direction == "down": x1 = start_rect["cx"] y1 = start_rect["y"] + start_rect["height"] x2 = end_rect["cx"] y2 = end_rect["y"] else: raise ValueError("暂不支持该方向: %s" % direction) return {"x1": x1, "y1": y1, "x2": x2, "y2": y2}在这个工具里,我们使用了“两列布局”。这是最基础的手放布局策略,适合大多数图表场景。
6.3 生成 SVG 的脚本
接下来通过 Python 生成完整 SVG:
# 文件路径:svg-diagram/scripts/generate_svg.py import json import sys from xml.etree.ElementTree import Element, SubElement, tostring from xml.dom import minidom from coordinate_utils import calc_node_rect, calc_edge def prettify(elem): rough_string = tostring(elem, encoding="unicode") reparsed = minidom.parseString(rough_string) return reparsed.toprettyxml(indent=" ") def generate_svg(data: dict) -> str: nodes = data["nodes"] edges = data["edges"] svg = Element("svg", { "xmlns": "http://www.w3.org/2000/svg", "width": str(data.get("width", 800)), "height": str(data.get("height", 320)), "viewBox": "0 0 %s %s" % (data.get("width", 800), data.get("height", 320)) }) node_rect_map = {} for idx, node in enumerate(nodes): rect_info = calc_node_rect(idx) node_rect_map[node["id"]] = rect_info # 绘制矩形 rect = SubElement(svg, "rect", { "x": str(rect_info["x"]), "y": str(rect_info["y"]), "width": str(rect_info["width"]), "height": str(rect_info["height"]), "rx": "8", "ry": "8", "fill": node.get("fill", "#e3f2fd"), "stroke": node.get("stroke", "#1565c0"), "stroke-width": "2" }) rect.text = " " # 绘制文字 text = SubElement(svg, "text", { "x": str(rect_info["cx"]), "y": str(rect_info["cy"]), "text-anchor": "middle", "dominant-baseline": "middle", "font-size": "14", "font-family": "Arial, sans-serif", "fill": "#212121" }) text.text = node["label"] # 绘制连线 for edge in edges: start_rect = node_rect_map[edge["from"]] end_rect = node_rect_map[edge["to"]] line_info = calc_edge(start_rect, end_rect, direction=edge.get("direction", "right")) line = SubElement(svg, "line", { "x1": str(line_info["x1"]), "y1": str(line_info["y1"]), "x2": str(line_info["x2"]), "y2": str(line_info["y2"]), "stroke": "#455a64", "stroke-width": "2", "marker-end": "url(#arrow)" }) return prettify(svg) if __name__ == "__main__": input_file = sys.argv[1] if len(sys.argv) > 1 else "../examples/architecture.json" output_file = sys.argv[2] if len(sys.argv) > 2 else "../examples/architecture.svg" with open(input_file, "r", encoding="utf-8") as f: data = json.load(f) svg_str = generate_svg(data) with open(output_file, "w", encoding="utf-8") as f: f.write(svg_str) print("已生成 SVG 文件:", output_file)注意这里generate_svg函数把节点循环和连线圈分开了,结构清晰,便于 Agent 在出错时定位问题。
7. 完整实战:让 Agent 调用 SVG-diagram 画架构图
7.1 准备输入数据
我准备了一个简单的架构图输入文件:
// 文件路径:svg-diagram/examples/architecture.json { "width": 800, "height": 320, "nodes": [ { "id": "client", "label": "客户端", "fill": "#bbdefb", "stroke": "#1565c0" }, { "id": "gateway", "label": "API 网关", "fill": "#fff9c4", "stroke": "#f9a825" }, { "id": "service", "label": "订单服务", "fill": "#c8e6c9", "stroke": "#2e7d32" }, { "id": "database", "label": "数据库", "fill": "#ffccbc", "stroke": "#d84315" } ], "edges": [ { "from": "client", "to": "gateway", "direction": "right" }, { "from": "gateway", "to": "service", "direction": "right" }, { "from": "service", "to": "database", "direction": "right" } ] }这组数据表达的意思是:客户端 → 网关 → 订单服务 → 数据库,典型的线性架构。
7.2 运行生成脚本
在命令行执行:
cd svg-diagram/scripts python generate_svg.py ../examples/architecture.json ../examples/architecture.svg预期输出:
已生成 SVG 文件: ../examples/architecture.svg7.3 查看生成的 SVG
用浏览器打开architecture.svg,显示的图形类似:
[客户端] ────> [API网关] ────> [订单服务] ────> [数据库]虽然是一个线性图,但每个节点的坐标都是通过“两列布局 + 右向连线”精确生成的。你可以直接在文本编辑器里修改任意节点的 x、y 值,不会影响其他节点。
7.4 在 Agent 中调用 Skill
当 Agent 框架支持 Skill 调用时,用户的请求流程如下:
- 用户输入:“画一个订单系统架构图,包含客户端、网关、订单服务、数据库。”
- Agent 识别到绘图意图,加载
SKILL.md。 - Agent 调用
coordinate_utils.py的calc_node_rect计算坐标。 - Agent 调用
generate_svg.py生成 SVG。 - Agent 返回 SVG 代码并展示给用户。
这里的关键是:Agent 并不“自由发挥”画图,而是通过 Skill 定义好的工具函数完成计算,这样得到的结果稳定可控。
8. 进阶:如何让 Agent 支持自定义布局
8.1 允许用户指定行列规则
默认情况下,我们使用两列布局。但实际业务中,用户往往有自己的布局偏好。比如用户说“客户端放左上角,其他服务放右边一列”,这时候 Agent 需要在生成 SVG 前调整坐标计算参数。
可以在 SKILL.md 中增加一条规则:
## 布局规则 - 如果用户指定了位置(左上、右上、中间等),优先按用户指定的方位调整坐标。 - 如果没有指定,默认使用两列布局。8.2 支持任意节点坐标覆盖
更灵活的方式是允许输入数据直接包含坐标:
{ "id": "client", "label": "客户端", "x": 40, "y": 120 }在generate_svg.py中增加判断:
if "x" in node and "y" in node: rect_info = { "x": node["x"], "y": node["y"], "width": node.get("width", 140), "height": node.get("height", 70), "cx": node["x"] + node.get("width", 140) / 2, "cy": node["y"] + node.get("height", 70) / 2 } else: rect_info = calc_node_rect(idx)这样一个 Skill 就同时支持“自动手放坐标”和“用户指定坐标”两种模式,灵活性大幅提升。
9. 常见问题与排查思路
9.1 常见问题表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| Agent 没有调用 Skill,直接输出普通 SVG | SKILL.md 中触发条件不明确 | 检查SKILL.md中的触发条件和功能描述 |
| 生成的矩形重叠 | 节点太多但画布高度不足 | 根据节点数量动态计算画布高度 |
| 文字超出矩形框 | 文字宽度大于矩形宽度 | 按文字长度计算矩形最小宽度 |
| 连线方向不对 | 节点排列方向与连线方向不一致 | 统一使用calc_edge的方向参数 |
| SVG 在浏览器中不显示 | 缺少 xmlns 或 XML 格式错误 | 用浏览器开发者工具检查 SVG 根属性 |
| Agent 坐标计算错误 | 让 Agent 心算而非调用函数 | 强制要求 Agent 调用coordinate_utils.py中的函数 |
9.2 文字溢出问题详解
文字溢出是手放坐标 SVG 最常见的问题。解决方案有两种:
- 根据字符数估算文字宽度,动态调整矩形宽度。
- 在 SVG 中加入
<foreignObject>,让 HTML 自动换行。
第一种方式更通用,我通常会在工具函数里加一个估算函数:
def calc_text_width(text, font_size=14): # 中文字符按 1.2 倍字号宽度估算 zh_count = sum(1 for ch in text if '\u4e00' <= ch <= '\u9fff') en_count = len(text) - zh_count return int(zh_count * font_size * 1.2 + en_count * font_size * 0.6)使用这个函数,Agent 在计算矩形宽度时就能预留足够空间。
9.3 如何验证 SVG 合法性
在validate_svg.py中,可以加入基础检查:
# 文件路径:svg-diagram/scripts/validate_svg.py import sys from xml.etree.ElementTree import parse def validate_svg(path): try: tree = parse(path) root = tree.getroot() if "svg" not in root.tag: return False, "不是 SVG 文件" if root.get("xmlns") != "http://www.w3.org/2000/svg": return False, "缺少 xmlns 命名空间" return True, "SVG 校验通过" except Exception as e: return False, f"解析失败: {e}" if __name__ == "__main__": ok, message = validate_svg(sys.argv[1]) print(message)在 Agent 生成 SVG 后,强制调用这个脚本,能第一时间发现 XML 结构错误。
10. 最佳实践与工程建议
10.1 坐标计算的工程化
手放坐标的核心原则是:不要让 Agent 心算坐标。
在实际项目中,我强烈建议把所有坐标计算逻辑下沉为函数,让 Agent 直接调用。这样做至少有三个好处:
- 减少 LLM 的算术错误;
- 方便统一修改布局策略;
- 代码可以被单测覆盖。
10.2 图层与分组管理
当图表比较复杂时,可以按语义对 SVG 元素分组:
<g id="layer-background"> <rect x="0" y="0" width="800" height="320" fill="#fafafa" /> </g> <g id="layer-nodes"> <rect x="40" y="40" width="140" height="70" fill="#bbdefb" /> <text x="110" y="75" text-anchor="middle">客户端</text> </g> <g id="layer-edges"> <line x1="180" y1="75" x2="320" y2="75" stroke="#455a64" /> </g>这样做的优势是:背景、节点、连线三个层可以独立调整,生成代码时也更容易排查问题。
10.3 响应式 SVG
生产环境中,SVG 常常需要放到网页或文档里。建议在 SVG 根元素中同时设置width、height和viewBox:
<svg width="800" height="320" viewBox="0 0 800 320" xmlns="http://www.w3.org/2000/svg">这样,SVG 在不同屏幕下可以等比缩放,不会因为父容器变化而变形。
10.4 Skill 的 Prompt 工程
SVG-diagram 作为 Agent Skill,它的表现很大程度上取决于SKILL.md的质量。我建议遵循以下规范:
- 用“触发条件”明确定义适用场景,避免 Agent 在无关场景中误用。
- 用“行为约束”限制 Agent 的随意性,列明必须遵守的生成规则。
- 用“执行步骤”给出可操作流程,减少 Agent 的推理负担。
- 提供“输入输出示例”时,尽量给出正反例对。
10.5 安全与授权边界
SVG 本质上是 XML 文件,在 Web 环境中解析时要注意:
- 不要直接执行从不可信来源获取的 SVG 内容中的脚本(虽然 SVG 中的 script 在现代浏览器中被限制,仍需警惕)。
- 如果需要用户上传 SVG,建议在服务端校验 XML 结构,并移除
<script>、<foreignObject>等高风险元素。 - 在包含敏感业务架构图时,注意权限控制,不要把所有图都暴露给所有用户。
11. 总结与下一步学习建议
到这里,我们已经完整介绍了 SVG-diagram 这个 agent skill 的核心思路与落地方式。它的本质并不是什么高深算法,而是把“人类手工排版”的习惯转化成 Agent 能理解和执行的坐标计算规则。通过这种方式,AI 画出的 SVG 图不再是“开盲盒”,每次输出的节点位置、连线走向、尺寸比例,都是可预期、可修改的。
接下来,如果你想继续深入,可以考虑从以下几个方向扩展:
- 增加更多布局策略,比如树形布局、环形布局、分层布局。
- 把生成的 SVG 转为 PNG、PDF,方便文档输出。
- 扩展 Skill 的输入方式,支持从 JSON、YAML、Markdown 描述中自动提取节点关系。
- 将 SVG-diagram 嵌入到你的 Agent 工作流中,让画图成为整个自动化流程的一环。
下图是本文核心内容的关系回顾:
用户描述 ——> Agent 识别意图 ——> 调用 SVG-diagram Skill | v coordinate_utils.py 计算坐标 | v 生成手放坐标 SVG 文件 | v 浏览器预览 / 网页嵌入如果你正在做 Agent 技能开发,或者一直觉得 AI 画图不可控,可以照着本文的思路实现一个自己的 SVG-diagram。动手试一次,你会对手放坐标的优势有更直观的体会。本文提到的示例代码可以直接复制运行,建议在自己的机器上完整演练一遍。