目录

StructSVG: Tree-Graph Guided Multi-Agent Text-to-SVG Generation


作者Yubo Dong et al.
机构论文未公开,机构暂缺
会议SIGGRAPH Asia 2026
链接https://github.com/sealost/StructSVG

让 LLM 写 SVG 之前,先输出一份 JSON 场景图(部件层级树 + 每个叶子的归一化 bbox + 遮挡/包含边),用拓扑排序定出绘制顺序;再采样 3 份 SVG,用 CLIP 挑最好的一份,规则检查 + LLM 打分不过就带着反馈重写,最多 3 次。本质是 plan-then-code + best-of-N + self-refine 套在 SVG 上;真正的新东西是 SVG 元素 id 与规划节点一一绑定,所以产物天然可以按部件编辑和做动画。

StructSVG overview poster
作者演示视频首帧:左为三阶段方法图与主表,右为应用(风格化、部件编辑、SVG 动画)。

资料来源说明

截至 2026-11-07,论文 PDF / arXiv / ACM 页面都还没公开(会议 12 月 1–4 日在吉隆坡)。本页内容来自三处:GitHub README、开源代码 structsvg.py(3120 行)、仓库里的 2.5 分钟演示视频(方法图与结果表从视频帧截取,数字逐个核对过)。数据集、指标定义、实验细节等论文正文内容无法确认的地方,都标了“推测”。

另一点要注意:作者的 release PR 写明,开源代码是“code audit 中找到的最完整作者快照”(2026 年 4 月的版本),不一定就是跑出论文数字的那一版。后文 论文图 vs 开源代码 列出了两者的出入。

术语速查

术语在本工作中具体是什么
Semantic TreeTreeBuilder 写出的 semantic_tree.json:一棵部件层级树。中间节点只做分组(如 bee),叶子节点带 bbox_norm=[x,y,w,h](0–1 坐标)、shape_type(rect/circle/ellipse/path/polygon)、material_ref、重要度。
Topology Graph / Primitive Graphprimitive_graph.json:节点 = 树的全部叶子;边 = OCCLUDES / CONTAINS / INSIDE_BOUNDS_OF / ATTACHES_TO / ALIGNS_WITH;外加由边拓扑排序得到的 draw_order。
material_library颜色表:{"mat_wing": {"fill":"#hex","stroke":"#hex"}},叶子按 id 引用;渐变用 gradient_ref 字段描述。
Agent同一个 LLM(默认 Gemini 3 Flash)配不同 system prompt 和工具白名单;靠往 inbox/<agent>.md 追加文字来传消息,每回合只能调一个工具。
Candidate一次 svg_gen_batch 采样出的 k=3 份完整 SVG(温度 0.2 / 0.35 / 0.5)。
Preflight确定性检查:XML 能否解析、viewBox 是否 0 0 256 256、Playwright 渲染后是否全空白、近白像素占比是否 >45%、元素 bbox 是否违反图里的边。
Tier-1 / Tier-2失败分级。Tier-1 = SVG 代码问题,打回 SVG_Engineer 做“最小补丁”重写;Tier-2 = 结构问题(遮挡/包含违规、CLIP≤0.20、id 漂移≥3),打回 TreeBuilder 改树。
Structural drift两版 SVG 的 id 集合对称差的大小(增加的 id + 删除的 id 个数)。
TextGrad / TGD失败后让 LLM 根据评分和问题清单,改写 TreeBuilder / SVG_Engineer 的 role prompt(“文本梯度下降”);下一轮指标变差就回滚。
ID Cov.主表里的指标,定义不公开;从名字和直接 LLM 生成几乎为 0 推测,是“规划节点 id 在 SVG 中出现的比例”。

研究动机

核心方法

StructSVG three-stage method
论文方法图(取自演示视频)。Stage 1:Semantic Decomposer 建语义树,Topology Planner 加遮挡 / 附着边。Stage 2:Component Drafter 按 id 生成多个候选并选出结构最对齐的一份。Stage 3:Structure-aware Refiner 分部件细化(component refinement → global alignment → detail enhancement),Quality GateKeeper 把关。
flowchart LR
  P["Text prompt"] --> TB["TreeBuilder"]
  TB -->|"semantic_tree.json"| GB["Graph builder"]
  GB -->|"edges + draw_order"| SE["SVG Engineer"]
  SE -->|"k=3 candidates"| PF["Preflight"]
  PF --> CR["Critic"]
  CR -->|"PASS"| OUT["Editable SVG"]
  CR -->|"Tier 1"| SE
  CR -->|"Tier 2"| TB
  CR -.->|"TextGrad"| RP["Role prompts"]
  RP -.-> TB
  RP -.-> SE
          
drag to pan · scroll to zoom
开源代码的实际控制流。Graph builder 不是 agent,是 SVG_Engineer 调用的工具(Python 规则 + 一次 LLM 调用)。Critic 失败时按 Tier 路由回不同 agent;虚线是 TextGrad 改写 prompt 的旁路。另有一个 Coordinator 只当看门狗,图中省略。

1. Tree-Graph Planning — 文本 → 部件树 → 拓扑图

TreeBuilder 只做一件事:读 prompt,写出 semantic_tree.json(schema v2.0)。规则:叶子才有 geometry,对称物体要列出全部实例(左右眼各一个节点),fill 只能写 hex(禁止 CSS gradient 语法),同色相邻部件要加 notes 提醒描边。prompt 里还写死了三条“能力约束”:不准设计“一个形状包住另一个”(C 形身体抱住球)、不准要求两个 path 在精确坐标对接、同一区域遮挡不超过 3 层。也就是说,系统通过在规划阶段回避 LLM 画不好的几何来降低失败率。

示意:一棵 semantic tree 长什么样(按代码 schema 手写,id 取自 gallery 里的 bee.svg,非真实输出)
{ "version": "2.0", "canvas": {"viewBox": "0 0 256 256"}, "material_library": { "mat_leaf": {"fill": "#4ade80", "stroke": "#16a34a"}, "mat_body": {"fill": "#facc15", "stroke": "#1f2937"}, "mat_wing": {"fill": "#e0f2fe", "stroke": "#1f2937"} }, "tree": {"id": "root", "role": "scene", "children": [ {"id": "flower", "role": "plant", "children": [ {"id": "stem", "is_leaf": true, "geometry": {"bbox_norm": [0.48, 0.55, 0.04, 0.45], "shape_type": "path", "material_ref": "mat_leaf", "importance": "secondary"}}, {"id": "leaf_left", "is_leaf": true, "geometry": {"bbox_norm": [0.32, 0.66, 0.18, 0.16], "shape_type": "path", "material_ref": "mat_leaf"}} ]}, {"id": "bee", "role": "subject", "children": [ {"id": "bee_wing_back", "is_leaf": true, "geometry": {...}}, {"id": "bee_abdomen", "is_leaf": true, "geometry": {...}}, {"id": "bee_wing_front","is_leaf": true, "geometry": {...}} ]} ]} }

Graph builder 是三步的混合流程:(A) 确定性地抽出所有叶子;同一父节点下相邻兄弟,若 bbox 重叠,就加一条“后者 OCCLUDES 前者”的边(兄弟顺序即图层顺序;不重叠的左右眼不加边)。(B) 调一次 LLM(temperature 0.1),只补跨父节点的边,并且只接受 5 种合法类型、只接受已存在的 id。(C) 用 Kahn 算法拓扑排序出 draw_order;如果 LLM 补的边造成环,就把跨父边整体丢掉,只保留兄弟边。

边类型对绘制顺序的含义生成后怎么检查
A OCCLUDES BB 先画,A 后画(盖在上面)SVG 文档顺序中 A 必须在 B 之后
A INSIDE_BOUNDS_OF B容器 B 先画bbox 包含关系
A CONTAINS B容器 A 先画bbox 包含关系;提示用 clipPath 实现
A ATTACHES_TO B不影响顺序代码里不检查
A ALIGNS_WITH B不影响顺序中心点在 x / y 轴上差值 < 画布 5%

直观理解:树回答“有哪些零件、怎么分组”,图回答“零件之间谁压谁、谁装着谁”。LLM 自己不擅长维护的全局一致性(图层顺序无环)交给了 30 行 Python。

2. Part-Aligned SVG Drafting — 按 id 落实成 SVG 候选

SVG_Engineer 把整张图 JSON + draw_order + prompt + 一份 8 条的 cheat sheet 发给 LLM。cheat sheet 的关键规则:每个图节点对应的 SVG 元素必须使用节点的原 id;严格按 draw_order 写;CONTAINS 用 clipPath;对称部件定义在 <defs> 里再用 <use> 复用;有机形状用“粗描边底层 + 无描边填充上层”的双层技巧消除接缝。一次生成 k=3 个候选,温度依次为 0.2 / 0.35 / 0.5。

另一种 autoregressive 模式:沿 draw_order 每次只生成一个节点的 SVG 片段,把已画部分作为上下文,最后拼起来。代码注释承认这种模式“误差会累积,也没法做整体补丁”,所以一进入重试就强制切回 one-shot。

每个候选生成后要过 preflight:XML 合法、viewBox 固定 256、用 Playwright/Chromium 渲染成 PNG 后不能全透明、可见像素中近白占比不能 >45%(除非有深色描边)、clipPath 里不能放只有描边的几何。重试时还会算 structural drift:新旧 SVG 的 id 增删数 >5 直接判废,强制“最小补丁”。

3. Structure-aware Refinement & Selection — Critic 的四路信号

  1. 选候选:调用本地 scorer 服务算 CLIP(ViT-B/32,logit/100)和 HPSv2.1。按 (round(CLIP×100), HPS) 做字典序取最大:CLIP 差 0.01 以内才看 HPS。
  2. 确定性检查:对选中的那份再跑 preflight 和图约束检查。包含 / 遮挡违规单条只算警告,≥2 条才升级为 blocking(单条可能是 path bbox 近似带来的测量误差)。
  3. LLM 视觉打分:把渲染 PNG、图 JSON、当前 SVG 树、历史最佳 SVG 树、指标和警告一起交给“严格的艺术总监”,返回 Structure / Occlusion / Color / Fidelity / Polish 五项分、总分 1–10、pass_ready 和修改建议。有 blocking 时总分必须 ≤4。
  4. 硬门槛:CLIP ≥ 0.20 且 HPS ≥ 0.18。最终 PASS 条件为:LLM 判 pass_ready、没有任何 blocking、两个下限都过。

FAIL 时由 diagnose_failure_tier 决定打回给谁:出现遮挡 / 包含的结构违规、CLIP ≤ 0.20、或 drift ≥ 3 → Tier-2,回 TreeBuilder 改树(只改出问题的节点);其余 → Tier-1,回 SVG_Engineer,带着 Critic 建议以低温(0.05–0.15)重写。两类重试共享一个计数器,最多 3 次。同时 TextGrad 会根据本轮失败信息改写 TreeBuilder 和 SVG_Engineer 的 role prompt(不改 Critic),下一轮指标下降就回滚到上一版 prompt。任务 PASS 后,LLM 从这次的树和 SVG 中总结“设计经验”,写入跨任务记忆 global_memory.json(只保留最近 8 条),后续任务的所有 agent 都能读到。

4. 多 agent 编排 — 其实是一条固定流水线

4 个角色:Coordinator(看门狗,只在报错、死循环、回合数用到 50% / 75% 时介入)、TreeBuilder、SVG_Engineer、Critic。每个 agent 有工具白名单和文件写权限白名单(例如只有 TreeBuilder 能写 semantic_tree.json),每一步只允许调用一个工具,用 send_message 交出控制权;单任务上限 30 个回合。正常路径是 TreeBuilder → SVG_Engineer → Critic 的固定顺序,“多 agent”实际上就是用对话来驱动一个带分支的状态机。

5. 最关键的一句话

先让 LLM 用 JSON 写出“零件清单 + 谁压谁”,用 Python 排好图层顺序,再要求 SVG 每个元素沿用零件 id;这样同一份结构既能指导生成、又能自动挑错、还能当编辑句柄。

论文图 vs 开源代码

方法图和 README 描述的系统,比开源代码实际做的事更多。读代码时要知道以下差别:

方面论文方法图 / README开源 structsvg.py
Agent 数Semantic Decomposer、Topology Planner、Component Drafter、Structure-aware Refiner、Quality GateKeeper4 个角色;Topology Planner 退化为工具里的一次 LLM 调用;Refiner 与 GateKeeper 合并为 Critic
Refinement按部件并行细化 → 全局对齐 → 细节增强,共三步没有逐部件细化;重试 = 整份 SVG 带反馈重写(“最小补丁”只是 prompt 里的要求,drift 检查只看 id 增删)
候选选择选“结构最对齐”的草稿按 CLIP 优先、HPS 次之选择;结构违规只在选完之后才检查
TextGradprompt-gradient refinement只改写 role prompt。直接优化 SVG 的 textgrad_tier1_patch 和直接优化图的 textgrad_tier2_rebuild 都有定义,但从未被调用;tgd_graph_patched 恒为 False
最终输出“best selection”FAIL 时也会用本轮结果覆盖 current_best_icon.svg,所以重试用尽后输出的是最后一轮,不一定是分数最高的一轮

主要实验结果

MethodSuccessCLIP↑HPS↑Aesthetic↑ImageReward↑VQAScore↑ID Cov.↑Edit↑
专门训练的 SVG 模型
OmniSVG98.7%0.2330.1424.525-1.8800.2520.00028.8%
InternSVG100.0%0.2420.1454.605-1.8660.2510.0001.7%
IntroSVG74.7%0.2450.1504.542-1.7990.3270.00026.9%
通用 LLM 直接生成 vs StructSVG(同一底座)
Gemini 3 Flash99.3%0.3190.2214.9680.4510.8020.00280.0%
StructSVG†-Gemini 3 Flash100.0%0.3230.2375.0770.8160.8580.38283.3%
GPT-4o98.7%0.2840.1764.663-1.1920.5060.00066.1%
StructSVG†-GPT-4o100.0%0.3130.2074.808-0.1410.7360.88373.3%
Claude Haiku 4.599.3%0.3140.2104.789-0.0660.7590.00570.0%
StructSVG†-Claude Haiku 4.5100.0%0.3160.2134.8730.0270.7690.55471.7%
DeepSeek-V399.3%0.2970.1934.708-0.6440.6550.00071.7%
StructSVG†-DeepSeek-V3100.0%0.3100.2044.832-0.2410.7210.73373.3%

主表(取自演示视频,粗体为全表最佳)。每个底座上 StructSVG 的各项指标都高于直接生成。

VQAScore:直接生成 vs StructSVG。越弱的底座提升越大(GPT-4o +0.230,DeepSeek-V3 +0.066),已经很强的 Claude Haiku 4.5 只多 0.010。CLIP 的规律相同:Gemini +0.004,Haiku +0.002。

消融(底座 GPT-4o)

VariantCLIP↑HPS↑Aesthetic↑ImageReward↑VQAScore↑
Direct GPT-4o0.2840.1764.663-1.1920.506
w/o Tree & Graph0.2820.1854.735-1.1510.470
w/o Tree0.2930.1884.705-0.9230.579
w/o Graph0.3120.2084.795-0.2030.742
w/o Refine0.3090.2014.754-0.4750.693
Full (GPT-4o)0.3130.2074.808-0.1410.736
消融的 VQAScore。贡献几乎都来自 Tree:去掉 Tree 降到 0.579;去掉 Graph 反而略高(0.742 vs 0.736);只保留 Refine 循环、不做规划时(w/o Tree & Graph)为 0.470,比直接生成的 0.506 还低。
Ablation qualitative examples
消融定性对比(取自视频,分辨率较低)。“兔子从魔术帽里探出头”:在 Full 和 w/o Refine 中,兔子都从帽口探出;w/o Graph 只剩一顶帽子,兔子看不到了,这正是遮挡关系出错的典型表现。可见 Graph 的作用更多体现在个例上,而不是平均指标上。

应用:编辑 / 风格 / 动画

仓库里的生成结果(原始 SVG,可以直接打开看源码)。例如 bee.svg 的元素 id 有 stem / leaf_left / petals / bee_abdomen / bee_wing_back / bee_wing_front / bee_stinger,就是规划树的叶子。

Interactive component editor
SVG-Agent Editability Studio(演示视频,未开源):点选画布上的部件 → 修改 bbox、改图里的边(cat ATTACHES_TO windowsill)、或输入局部 prompt(“make the cat ear …”),右侧实时重新渲染。
Animation authoring
动画编辑:把各部件按图层顺序摆成透视的“分层堆叠”,用文字描述动画后生成 CSS keyframes(parallax / exploded layers)。前提是每个部件有稳定 id 和明确的前后关系。

演示视频中还有风格化(同一 prompt 加 Cartoon / B&W / Watercolor / Geometric / Pixel Art / Minimalist 后缀)和长镜头 SVG 动画(夜空云飘、海底鱼游、第一人称公路)。这些都是 demo 级展示,没有定量评测;编辑器和动画工具也都不在开源仓库中。

局限与展望