1. 为什么“让 AI 硬写”这条路越来越走不通
过去一年我接触了不少 Agent 项目,从早期的纯 Prompt 编排到后来的多智能体协作,一个很明显的感受是:让大模型直接输出最终产物,在 Demo 阶段很惊艳,一上生产就露馅。你让它生成一段前端代码、一份报表、一张流程图,它确实能给你吐出来,但格式飘忽、结构不稳定、改一处崩三处,维护成本高得离谱。
这就是“硬写”的核心问题——模型在自由文本空间里做生成,没有约束、没有中间表示、没有可校验的结构。它像一个即兴发挥的画家,每次画出来的东西都不一样,你没法用工程手段去控制它。
于是行业里逐渐收敛出一个共识:Agent 的生成能力要落地,必须走“可视化生成”这条路。所谓可视化生成,不是让 Agent 画个图那么简单,而是让 Agent 的输出落到一套结构化的中间表示(IR)上,再通过SDK或DSL把这套 IR 渲染成最终形态。模型负责“决定生成什么”,IR 和渲染层负责“保证生成成什么样”。职责一分离,稳定性就上来了。
这篇文章我想聊的就是这套方案的完整思路:IR 怎么设计、DSL 和 SDK 各自扮演什么角色、Agent 怎么和它们对接、实际落地时会踩哪些坑。如果你正在做 Agent 开发,尤其是涉及代码生成、流程编排、图表产出这类场景,这套东西能帮你少走很多弯路。不管你是刚接触 Agent 的新手,还是已经踩过几轮坑的老手,下面这些内容应该都能对上你的实际需求。
2. 可视化生成方案的整体设计与思路拆解
2.1 从“文本直出”到“IR 中转”的范式转变
先把这个转变讲透。传统做法是:用户提需求 → 模型直接生成目标产物(代码/图表/配置)。可视化生成的做法是:用户提需求 → 模型生成 IR → IR 经过校验和优化 → 渲染层把 IR 转成目标产物。
多出来的这一层 IR,是整个方案的价值所在。它带来的好处有几个层面。
第一是可控性。IR 是有 Schema 的,字段类型、取值范围、层级关系都是明确的。模型输出的 IR 一旦不符合 Schema,校验层直接拦下来,可以要求模型重试或者走降级逻辑。这比让模型直接输出一段自由文本、然后你用正则去抠要靠谱得多。
第二是可编辑性。IR 是结构化的,意味着用户可以手动改。Agent 生成一版初稿,人在可视化界面上拖拖拽拽调整,改完的 IR 还能再喂回给 Agent 做二次优化。这种“人机协同”在纯文本直出的模式下几乎没法做。
第三是多端渲染。同一份 IR,可以渲染成网页、可以导出成图片、可以转成 PDF、可以生成代码。渲染层和生成层解耦之后,一套 IR 支撑多种输出形态,复用率极高。
我个人的经验是,IR 的粒度设计是最考验功力的地方。太粗,表达能力不够,复杂场景描述不了;太细,模型生成负担重,容易出错。一般建议 IR 只描述“语义结构”,不描述“视觉细节”。比如一个流程图,IR 里只定义节点、连线、层级关系,至于节点用什么颜色、圆角多大,交给渲染层的主题配置去管。
2.2 DSL、SDK、IR 三者的分工与协作
这三个词经常被混着用,我先把它们的关系理清楚。
IR(Intermediate Representation)是中间表示,是数据的结构化描述。它是一份 JSON、一份 AST、或者任何有明确 Schema 的数据结构。IR 是“内容”,是 Agent 真正要产出的东西。
DSL(Domain Specific Language)是领域特定语言,是面向人的描述方式。它可以是文本形式的(比如一段类似 Markdown 的语法),也可以是图形化的(比如可视化编辑器里的拖拽操作)。DSL 是“输入方式”,用户或者 Agent 通过 DSL 来表达意图。
SDK(Software Development Kit)是开发工具包,是面向程序员的接口。它提供 API,让代码能够程序化地操作 IR、调用渲染、扩展能力。SDK 是“集成方式”,是把这套能力嵌进你自己系统的桥梁。
它们三者的协作链路是这样的:用户用 DSL 描述需求(或者 Agent 根据自然语言生成 DSL)→ DSL 解析成 IR → IR 经过校验和优化 → SDK 调用渲染引擎把 IR 转成最终产物。
提示:很多团队一开始想省事,跳过 DSL 直接让 Agent 生成 IR。短期看是快了,但长期会发现用户没法手动介入,可视化编辑做不起来。DSL 这层看似多余,实则是人机协同的接口,建议一开始就规划好。
2.3 为什么这套方案特别适合 Agent 场景
Agent 和传统程序最大的区别在于输出的不确定性。传统程序你写死逻辑,输入确定输出就确定。Agent 是靠模型推理的,同样的输入可能给出不同的输出。这种不确定性在创意场景是优点,在工程场景是灾难。
可视化生成方案恰好能对冲这种不确定性。模型的不确定性被限制在“生成 IR”这一步,而 IR 到最终产物的转换是确定性的、可测试的、可回归的。你把测试用例写在 IR 层面,就能保证渲染结果稳定。模型换了、Prompt 改了,只要 IR 的 Schema 不变,下游渲染逻辑完全不用动。
另一个适配点是增量生成。Agent 做复杂任务时,往往是一步一步来的。有了 IR,每一步的产出都是对 IR 的局部修改,而不是重新生成一整段文本。这让 Agent 的“记忆”和“状态管理”变得清晰很多——状态就是当前这份 IR。
3. 核心细节解析与实操要点
3.1 IR Schema 设计:从需求反推结构
设计 IR Schema 最容易犯的错是“拍脑袋定字段”。我见过不少项目,Schema 定完用了两周发现不够用,又加字段,加着加着就乱了。正确的做法是从渲染需求反推。
具体怎么操作?先列出你这套系统要支持的所有输出形态,然后倒推每种形态需要哪些信息。比如你要支持流程图、时序图、架构图三种,那就把三种图共性的部分抽出来做基础 Schema,差异部分做扩展字段。
一个典型的 IR 基础结构大概长这样:
{ "version": "1.0", "type": "flowchart", "meta": { "title": "订单处理流程", "createdAt": "2024-01-01T00:00:00Z" }, "nodes": [ { "id": "n1", "kind": "start", "label": "用户下单", "position": { "x": 0, "y": 0 } } ], "edges": [ { "id": "e1", "from": "n1", "to": "n2", "label": "支付成功" } ], "extensions": {} }这里有几个设计要点值得展开。
version 字段必须有。IR 是会演进的,没有版本号,老数据遇到新渲染器就是灾难。我踩过的坑是早期没加版本号,后来 Schema 改了,历史数据全部渲染失败,只能写脚本做数据迁移,费时费力。
id 要全局唯一且稳定。Agent 增量修改 IR 时,靠 id 来定位节点。如果 id 每次生成都变,增量修改就无从谈起。建议用有意义的 id 或者 UUID,别用数组下标。
position 这类视觉信息要不要放 IR 里,是个争议点。我的建议是:可以放,但渲染层要有默认布局算法兜底。Agent 生成时通常不关心坐标,渲染层自动布局;用户手动调整后,坐标写回 IR 固化下来。这样兼顾自动化和可编辑性。
extensions 字段是扩展性的关键。不同业务场景有特殊需求,别往主 Schema 里塞,统一放 extensions,渲染层按需读取。这样主 Schema 保持稳定,扩展能力又不受限。
3.2 DSL 语法设计:让人和 Agent 都能写
DSL 的设计目标很明确:人类写起来不累,Agent 生成起来不容易错。这两个目标有时候是冲突的,需要权衡。
人类友好的 DSL 通常追求简洁,比如类似这样的语法:
flowchart 订单处理 { start 用户下单 用户下单 -> 支付校验: 提交订单 支付校验 -> 支付成功: 校验通过 支付校验 -> 支付失败: 校验失败 支付成功 -> 发货 支付失败 -> 结束 }这种语法人读起来很顺,但 Agent 生成时容易漏掉括号、缩进错乱。所以实际落地时,我一般会做双模式支持:文本 DSL 给人用,JSON 形式的 IR 给 Agent 用,两者之间做双向转换。Agent 直接生成 IR,用户想手改时转成 DSL 展示,改完再转回 IR。
DSL 解析器的实现,推荐用解析器组合子或者PEG 解析器,别手写正则。正则处理嵌套结构会要命,我见过用正则解析 DSL 的项目,遇到嵌套三层就崩了。用成熟的解析库,比如 JavaScript 生态的 PEG.js、Python 的 Lark,能省大量时间。
注意:DSL 的报错信息一定要友好。用户写错了,你得告诉他“第 3 行第 5 列,缺少右括号”,而不是抛一个“Parse Error”。Agent 生成出错时,详细的报错信息还能作为反馈喂回给模型,让它自我修正。
3.3 SDK 接口设计:把能力封装成积木
SDK 是给开发者用的,设计原则是高内聚、低耦合、易扩展。核心接口一般分四类。
解析类:把 DSL 文本解析成 IR,或者把 IR 序列化成 DSL。接口类似parse(dslText) => IR和stringify(ir) => dslText。
校验类:校验 IR 是否符合 Schema,返回详细的错误列表。接口类似validate(ir) => { valid, errors }。
渲染类:把 IR 渲染成目标产物。接口类似render(ir, options) => output,options 里指定输出格式、主题、尺寸等。
扩展类:注册自定义节点类型、自定义渲染器、自定义主题。接口类似registerNodeType(type, definition)。
SDK 的版本管理要格外注意。IR Schema 变了,SDK 大版本号要跟着变。我建议 SDK 内部做向后兼容的 IR 升级器,老版本 IR 进来,自动升级到最新 Schema 再处理。这样用户升级 SDK 时,历史数据不用动。
3.4 Agent 与 IR 的对接方式
Agent 怎么产出 IR?主流有三种方式,各有适用场景。
方式一:Function Calling。把 IR Schema 定义成工具的参数,让模型通过调用工具来生成结构化输出。这种方式约束最强,模型必须按 Schema 来,出错率最低。缺点是 Schema 复杂时,工具定义会很长,占用大量上下文。
方式二:结构化输出模式。现在很多模型 API 支持 JSON Schema 约束的输出,直接让模型按 Schema 生成 JSON。这种方式比 Function Calling 轻量,适合 Schema 中等复杂度的场景。
方式三:DSL 生成 + 解析。让模型生成 DSL 文本,SDK 解析成 IR。这种方式对模型能力要求最高,但灵活性也最好,适合 DSL 语法简洁、模型熟悉的场景。
我的实践经验是:复杂 Schema 用方式一,中等复杂度用方式二,简单场景用方式三。另外不管用哪种,都要加校验重试机制——生成结果校验不过,把错误信息拼回 Prompt 让模型重试,一般重试一两次就能过。
4. 实操过程与核心环节实现
4.1 环境搭建与依赖选型
假设我们要做一个支持流程图和架构图的可视化生成系统,技术栈选型如下。
前端渲染用React + D3.js,D3 的灵活性足够,各种图形都能画。如果追求开箱即用,AntV G6也是好选择,内置了流程图、架构图的布局算法。DSL 解析用PEG.js,语法定义清晰,生成的解析器性能不错。IR 校验用Ajv,JSON Schema 校验的事实标准,报错信息详细。
后端 Agent 编排用Python,模型调用和 Prompt 管理生态成熟。IR 的存储用PostgreSQL 的 JSONB 字段,既能存结构化数据,又能做字段级查询。
安装依赖:
# 前端 npm install d3 ajv pegjs # 后端 pip install openai jsonschema psycopg24.2 IR Schema 的完整定义与校验实现
先定义 JSON Schema,这是整个系统的契约。
{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": ["version", "type", "nodes", "edges"], "properties": { "version": { "type": "string", "pattern": "^\\d+\\.\\d+$" }, "type": { "enum": ["flowchart", "architecture", "sequence"] }, "meta": { "type": "object", "properties": { "title": { "type": "string", "maxLength": 200 } } }, "nodes": { "type": "array", "items": { "type": "object", "required": ["id", "kind", "label"], "properties": { "id": { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$" }, "kind": { "type": "string" }, "label": { "type": "string", "maxLength": 500 }, "position": { "type": "object", "properties": { "x": { "type": "number" }, "y": { "type": "number" } } } } } }, "edges": { "type": "array", "items": { "type": "object", "required": ["id", "from", "to"], "properties": { "id": { "type": "string" }, "from": { "type": "string" }, "to": { "type": "string" }, "label": { "type": "string" } } } } } }校验实现用 Ajv:
import Ajv from 'ajv'; import schema from './ir-schema.json'; const ajv = new Ajv({ allErrors: true }); const validate = ajv.compile(schema); export function validateIR(ir) { const valid = validate(ir); if (!valid) { return { valid: false, errors: validate.errors.map(e => ({ path: e.instancePath, message: e.message })) }; } // 额外做引用完整性校验:边的 from/to 必须指向存在的节点 const nodeIds = new Set(ir.nodes.map(n => n.id)); const danglingEdges = ir.edges.filter( e => !nodeIds.has(e.from) || !nodeIds.has(e.to) ); if (danglingEdges.length > 0) { return { valid: false, errors: danglingEdges.map(e => ({ path: `/edges/${e.id}`, message: `边引用了不存在的节点: ${e.from} -> ${e.to}` })) }; } return { valid: true, errors: [] }; }这里有个细节值得说:JSON Schema 校验不了引用完整性。边的 from/to 是不是指向真实存在的节点,Schema 层面管不了,得在代码里补一层校验。这个坑我踩过,Agent 生成的 IR 里出现了指向不存在节点的边,渲染时直接报错。补上这层校验后,问题在生成阶段就被拦住了。
4.3 Agent 生成 IR 的完整链路
以 Function Calling 方式为例,完整链路如下。
第一步,把 IR Schema 转成工具定义。这一步可以写脚本自动转换,别手写,容易出错。
import json def schema_to_tool(ir_schema): return { "type": "function", "function": { "name": "generate_diagram_ir", "description": "根据用户需求生成图表的结构化中间表示", "parameters": ir_schema } }第二步,构造 Prompt 并调用模型。
from openai import OpenAI client = OpenAI() SYSTEM_PROMPT = """你是一个图表生成助手。根据用户描述,调用 generate_diagram_ir 工具生成图表结构。注意: 1. 节点 id 必须唯一,使用有意义的英文标识 2. 边的 from/to 必须指向已定义的节点 3. 节点 label 用中文,简洁明了 4. 不要生成 position 字段,布局由渲染层自动处理 """ def generate_ir(user_input, max_retries=3): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input} ] for attempt in range(max_retries): response = client.chat.completions.create( model="gpt-4o", messages=messages, tools=[schema_to_tool(ir_schema)], tool_choice={"type": "function", "function": {"name": "generate_diagram_ir"}} ) tool_call = response.choices[0].message.tool_calls[0] ir = json.loads(tool_call.function.arguments) result = validate_ir(ir) if result["valid"]: return ir # 校验失败,把错误信息喂回模型重试 messages.append(response.choices[0].message) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": f"校验失败: {json.dumps(result['errors'], ensure_ascii=False)}" }) messages.append({ "role": "user", "content": "请根据上述错误修正 IR,重新生成。" }) raise Exception("多次重试后仍无法生成合法 IR")这段代码的核心是校验重试循环。实测下来,第一次生成就有 70% 左右的通过率,加上重试,最终通过率能到 98% 以上。剩下 2% 一般是需求本身有歧义,需要人工介入。
提示:重试时把错误信息结构化地喂回去,比笼统说“你错了”有效得多。模型看到“边 e3 引用了不存在的节点 n99”,能精准定位问题。
4.4 渲染层的实现与自动布局
渲染层拿到 IR 后,先做自动布局,再画图。布局算法选型看图表类型:流程图用Dagre(有向无环图布局),架构图用力导向布局,时序图用简单的垂直排列。
以 Dagre 为例:
import dagre from 'dagre'; export function autoLayout(ir) { const g = new dagre.graphlib.Graph(); g.setGraph({ rankdir: 'TB', nodesep: 50, ranksep: 80 }); g.setDefaultEdgeLabel(() => ({})); ir.nodes.forEach(node => { g.setNode(node.id, { width: 160, height: 60 }); }); ir.edges.forEach(edge => { g.setEdge(edge.from, edge.to); }); dagre.layout(g); return { ...ir, nodes: ir.nodes.map(node => { const pos = g.node(node.id); return { ...node, position: { x: pos.x - pos.width / 2, y: pos.y - pos.height / 2 } }; }) }; }布局参数nodesep和ranksep需要根据节点数量动态调整。节点多的时候,间距太小会挤成一团。我的经验是:节点数小于 10 用默认值,10 到 30 之间把 ranksep 调到 100,超过 30 建议分层次或者拆图。
渲染用 D3 画 SVG:
import * as d3 from 'd3'; export function render(svgElement, ir) { const svg = d3.select(svgElement); svg.selectAll('*').remove(); const g = svg.append('g'); // 画边 g.selectAll('.edge') .data(ir.edges) .enter() .append('path') .attr('class', 'edge') .attr('d', d => { const from = ir.nodes.find(n => n.id === d.from); const to = ir.nodes.find(n => n.id === d.to); return `M${from.position.x + 80},${from.position.y + 30} L${to.position.x + 80},${to.position.y + 30}`; }) .attr('stroke', '#999') .attr('fill', 'none'); // 画节点 g.selectAll('.node') .data(ir.nodes) .enter() .append('rect') .attr('class', 'node') .attr('x', d => d.position.x) .attr('y', d => d.position.y) .attr('width', 160) .attr('height', 60) .attr('rx', 8) .attr('fill', '#fff') .attr('stroke', '#333'); // 画标签 g.selectAll('.label') .data(ir.nodes) .enter() .append('text') .attr('x', d => d.position.x + 80) .attr('y', d => d.position.y + 35) .attr('text-anchor', 'middle') .text(d => d.label); }实际项目里还要处理边的箭头、标签位置、节点换行等细节,但核心逻辑就是上面这些。渲染层要保证幂等——同一份 IR 渲染多次,结果必须一致。这是可测试性的基础。
5. 常见问题与排查技巧实录
5.1 Agent 生成 IR 的典型错误与修复
下面这张表是我在实际项目中整理的高频错误清单,基本覆盖了 90% 以上的问题。
| 错误类型 | 典型表现 | 根因 | 修复方式 |
|---|---|---|---|
| 引用悬空 | 边的 from/to 指向不存在的节点 | 模型生成节点时漏了某个 id | 校验层拦截,错误信息喂回重试 |
| id 重复 | 两个节点用了同一个 id | 模型偷懒复用 id | Schema 加唯一性校验,或生成后去重 |
| 类型错误 | position 写成字符串 | 模型对类型不敏感 | JSON Schema 强约束,校验拦截 |
| 结构过深 | 嵌套层级超过渲染层支持 | 需求本身复杂,模型没做扁平化 | Prompt 里明确层级上限,超限时拆分 |
| 语义漂移 | 生成的图跟需求对不上 | Prompt 描述不清或模型理解偏差 | 加 few-shot 示例,或让用户确认 |
引用悬空是最常见的,我统计过,大概占所有错误的四成。修复方式前面讲过了,就是在 Schema 校验之外补一层引用完整性校验。
id 重复的问题比较隐蔽,因为 JSON Schema 的uniqueItems只能校验数组元素整体唯一,不能校验某个字段唯一。得自己写校验逻辑:
function checkUniqueIds(ir) { const ids = ir.nodes.map(n => n.id); const dupes = ids.filter((id, idx) => ids.indexOf(id) !== idx); if (dupes.length > 0) { return { valid: false, errors: [{ message: `重复的节点 id: ${dupes.join(', ')}` }] }; } return { valid: true }; }5.2 渲染性能问题的排查思路
节点数量上去之后,渲染会变卡。我遇到过节点超过 200 个时,页面直接卡死的情况。排查下来主要是两个问题。
一是 DOM 节点太多。每个图节点对应多个 SVG 元素,200 个节点就是上千个 DOM,浏览器扛不住。解决方案是虚拟化渲染——只渲染视口内的节点,视口外的用占位符。或者干脆换成 Canvas 渲染,D3 也支持 Canvas 模式,性能比 SVG 好一个数量级。
二是布局算法复杂度高。力导向布局是 O(n²) 的,节点多了计算量爆炸。解决方案是分层布局或者增量布局——先粗排,用户交互时再局部精调。Dagre 这类层次布局算法复杂度是 O(n),大图场景优先选它。
排查性能问题,Chrome DevTools 的 Performance 面板是利器。录制一段渲染过程,看火焰图里哪个函数耗时最长。我一般先看布局计算和 DOM 操作两块,八成问题出在这。
5.3 DSL 版本兼容的坑
DSL 和 IR 一样,是会演进的。老版本 DSL 文件在新版本系统里打不开,这是很常见的痛点。我踩过的坑是:DSL 语法加了个新关键字,老文件解析直接报错。
解决方案是在 DSL 里加版本声明,解析器根据版本号走不同的解析规则。
@version 1.0 flowchart 订单处理 { ... }解析器读到@version 1.0,就调用 v1 的解析规则。新版本解析器要内置所有历史版本的解析规则,做向下兼容。如果实在兼容不了,就提供迁移工具,把老 DSL 转成新 DSL。
IR 层面同理,前面提到的 version 字段就是干这个的。SDK 里内置 IR 升级器,老版本 IR 进来先升级再处理。这样用户升级 SDK 时,历史数据不用动,平滑过渡。
注意:版本兼容这事,一定要在项目早期就规划。等到用户量上来再补,迁移成本会高得吓人。我见过一个项目,DSL 改了五版都没做版本管理,最后历史数据全部作废,用户怨声载道。
5.4 人机协同编辑的冲突处理
可视化生成的一大卖点是“Agent 生成初稿,人来精修”。但这里有个冲突:用户改完 IR 后,Agent 又生成了一版,把用户的修改覆盖了怎么办?
我的处理方式是基于操作日志的合并。用户每次编辑都记录成一条操作(增删改节点/边),Agent 的生成也记录成操作。合并时按时间顺序应用操作,冲突的地方让用户选择保留哪一版。
更简单的做法是锁定机制——用户手动编辑过的部分打上标记,Agent 后续生成时跳过这些部分。这种方式实现简单,但灵活性差一些。具体选哪种,看你的用户场景。如果用户编辑频繁,建议上操作日志合并;如果只是偶尔微调,锁定机制够用。
6. 方案落地的经验与扩展方向
6.1 从单图到多图编排的演进
单张图的生成跑通之后,下一步自然是多图编排。比如一个复杂的业务流程,拆成主流程图加若干子流程图,图与图之间可以跳转。
IR 层面要支持这种编排,可以在 meta 里加subDiagrams字段,或者在节点上加ref字段指向另一张图。渲染层要支持图之间的导航。这块我还在探索,目前的做法是用一个顶层 IR 管理所有子图,渲染时按需加载。
多图编排对 Agent 的要求也更高了,它得理解图与图之间的关系。我的做法是分两步:先让 Agent 生成顶层结构,再逐个生成子图。每步都做校验,确保局部正确。
6.2 把 IR 作为 Agent 的长期记忆载体
前面提到过,IR 天然适合做 Agent 的状态管理。进一步想,它其实可以作为 Agent 的长期记忆载体。
传统 Agent 的记忆是文本形式的对话历史,检索靠向量相似度,召回的内容零散、不结构化。如果把记忆存成 IR,每个记忆单元是一个结构化的节点,节点之间有明确的关系,检索和推理都会精准很多。
我试过把项目知识图谱存成 IR,Agent 需要查信息时,直接在 IR 上做图查询,比向量检索准得多。这个方向我觉得很有潜力,尤其是需要精确推理的场景。
6.3 一些实操中的小技巧
最后分享几个实操中总结的小技巧,都是踩坑换来的。
Prompt 里给 few-shot 示例,比长篇描述有效。模型对示例的模仿能力很强,给两三个高质量的 IR 示例,生成质量立竿见影地提升。
校验错误信息要分级。致命错误(如引用悬空)直接拦截重试,警告级错误(如 label 过长)记录日志但不拦截。这样既保证正确性,又不至于因为小问题反复重试。
渲染层加缓存。同一份 IR 渲染结果缓存起来,IR 没变就直接用缓存。这在用户频繁切换视图时能明显提升体验。
IR 的 diff 算法要专门优化。Agent 增量修改 IR 时,需要计算新旧 IR 的差异。朴素的 diff 算法在节点多时很慢,建议用基于 id 的哈希比对,复杂度能降到 O(n)。
给用户提供 IR 的“源码视图”。有些高级用户想直接改 IR,给他们一个 JSON 编辑器,改完实时校验、实时渲染。这个功能看似小众,但能极大提升专业用户的粘性。
这套可视化生成方案,我从最初的想法到落地跑通,前后折腾了大半年。中间推翻过两次 IR Schema,重写过一次 DSL 解析器,踩的坑不计其数。但跑通之后回头看,这些投入都是值得的——Agent 的生成能力终于从“玩具”变成了“工具”,能真正嵌进生产流程里用了。如果你也在做类似的事情,希望这些经验能帮你少走点弯路。