☰
Agent可视化生成方案:用IR中间表示替代AI硬写,提升输出稳定性
2026/10/27 8:33:34 网站建设 项目流程

1. 为什么“让 AI 硬写”正在成为过去式

过去一年我接触了不少做 Agent 项目的团队,从两三个人的小作坊到几十人的中台组都有,一个特别普遍的现象是:大家一开始都倾向于让大模型直接生成最终产物。你要一个表单,就让模型吐一段 JSON;你要一个流程,就让模型写一段代码;你要一个页面,就让模型拼一段 HTML。刚开始 demo 跑得挺爽,一旦进入真实业务,问题就全冒出来了。

最典型的三个坑:第一,输出不稳定。同一个 prompt,今天生成的 JSON 字段叫userName,明天变成user_name,后天干脆少一个字段。第二,无法校验。模型生成的东西语法上可能没错,但语义上完全不符合业务约束,你只能靠人肉 review。第三,改不动。业务方说“把这个按钮挪到右边”,你没法在生成结果上做增量修改,只能重新生成一遍,前面调好的东西全丢了。

这就是“Agent 可视化生成方案”要解决的核心问题。它的思路其实很朴素:别让 AI 直接写最终产物,让 AI 写中间表示(IR),再由确定性的渲染器/编译器把 IR 变成最终产物。这个中间表示可以是 DSL,可以是结构化的 JSON Schema,也可以是一套 SDK 调用序列。AI 只负责它擅长的部分——理解意图、做决策、填参数;而结构、约束、渲染这些确定性的事情,交给代码去做。

我个人的判断是,这套思路在未来一两年会成为 Agent 生成类项目的主流架构。原因很简单:大模型的能力边界是概率性的,而工程系统需要的是确定性。可视化生成方案本质上是在概率和确定之间架了一座桥。这篇文章我会把这套方案的来龙去脉、核心设计、实操细节和踩坑经验完整拆一遍,适合正在做 Agent 项目、低代码平台、AI 生成工具的同学参考,也适合刚接触 Agent 开发想少走弯路的朋友。

2. 可视化生成方案的整体设计与思路拆解

2.1 从“端到端生成”到“IR 中转”的范式转变

先把这个范式转变讲清楚。传统的端到端生成,链路是用户意图 -> LLM -> 最终产物。可视化生成方案的链路是用户意图 -> LLM -> IR -> 渲染器/编译器 -> 最终产物。多了一层,但这一层是整个方案的灵魂。

为什么多这一层就稳了?打个比方。你让一个装修师傅直接给你砌一面墙,他可能砌歪,可能砖缝不齐。但如果你让他先画一张施工图(IR),图纸上有明确的尺寸、材料、位置,然后让施工队按图施工(渲染器),那结果的可控性就完全不一样了。LLM 就是那个设计师,它擅长的是“这面墙应该在哪、多高、什么风格”,而不是“每一块砖怎么摆”。把后者交给确定性的渲染器,才是正确的分工。

IR 这层带来的直接好处有三个。可校验:IR 是有 schema 的,字段类型、取值范围、必填项都能定义,生成完立刻能跑校验。可修改:用户说“把标题改大一点”,你只需要改 IR 里的一个字段,不用重新生成整个产物。可复用:同一份 IR 可以渲染成 Web、移动端、甚至导出成图片,一套逻辑多端复用。

2.2 IR、DSL、SDK 三者的定位与取舍

热词里同时出现了 IR、DSL、SDK,很多人会搞混。我用一句话区分:IR 是数据,DSL 是语法,SDK 是接口。

IR(Intermediate Representation)是中间表示,本质是一棵结构化的数据树,通常用 JSON 或类似结构描述。它回答的是“这个产物由哪些元素组成、元素之间什么关系”。DSL(Domain Specific Language)是领域特定语言,是 IR 的一种文本化表达形式,比如你设计一套button { text: "提交", action: submit }这样的语法。SDK 则是给开发者或 Agent 调用的编程接口,Agent 通过调用 SDK 的方法来构建 IR。

那到底该选哪个?我的经验是这样:

方案适合场景优势劣势
纯 JSON IR结构简单、字段固定的场景实现快、易校验表达力有限、嵌套深了难读
自定义 DSL需要人写、需要版本管理的场景可读性好、diff 友好要写 parser、维护成本高
SDK 调用Agent 生成、程序化构建类型安全、IDE 友好需要设计 API、跨语言麻烦

实际项目里我通常推荐以 JSON IR 为核心,DSL 作为可选的人机交互层,SDK 作为 Agent 的调用入口。三者不是互斥的,而是同一套 IR 的不同“视图”。Agent 通过 SDK 构建 IR,人可以通过 DSL 编辑 IR,最终都落到同一份 JSON 上,由同一个渲染器处理。这样既保证了 Agent 生成的稳定性,又给了人介入修改的空间。

2.3 为什么这套方案特别适合 Agent 场景

Agent 和普通程序最大的区别是:Agent 的决策是动态的、多步的、带不确定性的。如果让 Agent 直接生成最终产物,每一步的不确定性都会累积到结果上,误差会放大。而引入 IR 之后,Agent 的每一步只需要产出一个局部正确的 IR 片段,最后再组装。

举个例子。一个生成报表页面的 Agent,如果直接写 HTML,它要同时考虑布局、样式、数据绑定、交互逻辑,任何一处出错整个页面就废了。但如果它先产出 IR:{ type: "table", columns: [...], dataSource: "..." },那它只需要保证这个片段结构对,渲染器负责把它变成正确的 HTML。Agent 的认知负担大大降低,出错概率也随之下降。

这也是为什么我一直在强调:Agent 可视化生成的核心不是“让 AI 更聪明”,而是“让 AI 只做它该做的事”。把确定性的部分从 AI 手里拿走,交给代码,这才是工程化的思路。

3. 核心细节解析与实操要点

3.1 IR Schema 设计:约束要刚刚好

IR 的 schema 设计是整个方案的地基,设计得好后面一路顺,设计得差后面全是补丁。我踩过的最大一个坑是:schema 设计得太自由。一开始我让 IR 支持任意嵌套、任意字段,结果 Agent 生成出来的结构千奇百怪,渲染器根本处理不过来。

后来我总结出一个原则:schema 的约束要“刚刚好”——足够表达业务,又足够限制 Agent。具体怎么做?三个要点。

第一,用枚举代替自由字符串。比如按钮类型,不要用type: string,要用type: "primary" | "secondary" | "danger"。这样 Agent 只能在有限选项里选,不会瞎编。

第二,限制嵌套深度。我一般把 IR 的嵌套控制在 3 到 4 层以内。太深了 Agent 容易迷路,渲染器也难写。如果业务确实需要深层结构,用引用(ref)来扁平化。

第三,必填项要明确。每个节点哪些字段必须有,哪些可选,schema 里写死。Agent 生成后立刻用 JSON Schema 校验,不合格的直接打回重生成,不要让它带着错误往下走。

{ "type": "object", "required": ["type", "id"], "properties": { "type": { "enum": ["container", "text", "button", "table"] }, "id": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" }, "props": { "type": "object" }, "children": { "type": "array", "maxItems": 20 } } }

上面这段 schema 就是我在多个项目里验证过的“最小可用 IR”模板。type用枚举锁死,id用正则约束命名,children限制数量防止 Agent 无限展开。看着简单,但能挡掉 80% 的脏数据。

3.2 Agent 侧:如何让模型稳定产出合规 IR

Schema 定好了,接下来是让 Agent 稳定地产出符合 schema 的 IR。这里有几个实操要点,都是血泪教训。

第一,用 function calling 或 structured output,别用纯文本解析。早期我用 prompt 让模型输出 JSON,然后正则提取,结果模型经常在 JSON 前后加解释文字,或者用 markdown 代码块包起来,解析起来极其痛苦。现在主流模型都支持结构化输出,直接约束输出格式,省心太多。

第二,给 few-shot 示例,但别给太多。示例能显著提升生成质量,但给多了会占满上下文,还会让模型过度模仿示例。我的经验是给 2 到 3 个覆盖不同场景的示例就够了,重点是覆盖边界情况,比如空列表、可选字段缺失。

第三,把校验结果反馈给 Agent。Agent 生成 IR 后,先跑校验,如果失败,把具体的错误信息(哪个字段、什么错误)作为下一轮输入喂回去,让它修正。这个“生成-校验-修正”的循环通常 1 到 2 轮就能收敛。我实测下来,加了校验反馈之后,一次通过率从 60% 左右提升到了 90% 以上。

注意:校验反馈的 prompt 要具体,不要只说“格式错误”,要说“字段children[2].type的值buton不在允许的枚举中,请从button、text、container中选择”。错误信息越具体,Agent 修正得越快。

3.3 渲染器:确定性是唯一要求

渲染器是 IR 到最终产物的翻译层,它的核心要求只有一个:确定性。同样的 IR 输入,必须产出同样的结果,不能有任何随机性。

渲染器的实现方式取决于你的目标产物。如果目标是 Web 页面,渲染器就是一个把 IR 转成 React/Vue 组件的函数;如果目标是图片,渲染器就是一套绘制逻辑;如果目标是配置文件,渲染器就是一个序列化器。

我建议渲染器用注册表模式实现:每种 IR 节点类型注册一个处理函数,渲染时根据type字段查表调用。这样新增节点类型只需要注册新函数,不用改核心逻辑。

const renderers = { container: (node, ctx) => `<div>${node.children.map(c => render(c, ctx)).join('')}</div>`, text: (node, ctx) => `<span>${escape(node.props.content)}</span>`, button: (node, ctx) => `<button>{ "type": "dashboard", "title": "销售数据看板", "children": [ { "type": "chart", "id": "sales_trend", "props": { "chartType": "line", "dataSource": "sales_daily", "xField": "date", "yField": "amount" } }, { "type": "table", "id": "top_products", "props": { "dataSource": "product_rank", "columns": ["name", "sales", "growth"] } } ] }

第二步,写 Agent 的 system prompt。核心是告诉模型:你的任务是产出符合 schema 的 IR,不要输出任何解释文字,遇到不确定的字段用默认值。

第三步,接结构化输出。以 OpenAI 的接口为例,用response_format约束成 JSON,并把 schema 传进去。这样模型输出的就是纯 JSON,不用解析。

第四步,写校验函数。用 JSON Schema 校验库(比如 ajv)跑一遍,失败就把错误喂回给 Agent 重试。

第五步,写渲染器。把 IR 转成实际的 HTML 或组件。图表部分可以接 ECharts 或 Chart.js,表格部分自己写个简单的渲染函数。

第六步,加一层缓存。同样的用户意图,IR 大概率是一样的,缓存起来能省不少 token。我用的是意图文本的 hash 作为 key,命中率在重复场景下能到 40% 以上。

4.2 参数计算:IR 节点数量与 token 消耗的关系

这里分享一个我实测出来的经验公式,帮你预估成本。假设每个 IR 节点的平均 token 消耗是T_node(包含 type、id、props 等字段),节点数量是N,那么一次生成的输出 token 大约是N * T_node。我实测下来,一个结构简单的节点(比如 text)大约 30 到 50 token,一个复杂节点(比如带数据绑定的 chart)大约 100 到 150 token。

如果你的看板有 10 个节点,平均 80 token,那输出就是 800 token 左右。加上 system prompt 和 few-shot 示例(通常 500 到 1000 token),一次生成的总消耗在 1500 到 2000 token。这个量级用主流模型成本很低,但如果你的场景节点数上百,就要考虑分批生成或者用更小的模型了。

提示:节点数超过 50 的时候,建议把 IR 拆成多个子 IR 分别生成,最后合并。一次性生成太多节点,模型容易在后半段“偷懒”,字段质量明显下降。

4.3 实操现场:一次完整的生成与修正过程

我拿一个真实案例走一遍。用户输入:“给我做一个展示最近一周订单量的折线图,标题叫订单趋势”。

第一轮生成,Agent 输出:

{ "type": "dashboard", "title": "订单趋势", "children": [ { "type": "chart", "id": "order_trend", "props": { "chartType": "line", "dataSource": "orders_weekly", "xField": "date", "yField": "count" } } ] }

校验:通过。schema 里chartType是枚举,line合法;dataSource是字符串,合法。

渲染:渲染器把chart节点转成 ECharts 配置,dataSource映射到实际的数据接口,页面正常显示。

用户反馈:“把折线改成柱状图”。这时候不需要重新生成整个 IR,只需要改一个字段:chartType从line改成bar。改完重新渲染,秒级响应。这就是 IR 方案相比端到端生成的最大优势——增量修改。

如果用户说“再加一个表格展示 Top 10 商品”,那就在children里追加一个table节点,同样不用动已有的部分。整个过程 Agent 只需要处理“追加”这个局部操作,认知负担极小。

5. 常见问题与排查技巧实录

5.1 Agent 生成的 IR 总是缺字段怎么办

这是最高频的问题。原因通常有两个:一是 schema 里没把字段标成 required,模型觉得可省就省了;二是 prompt 里没强调“所有必填字段必须出现”。

解决办法:在 schema 里明确 required,并且在 system prompt 里加一句“输出必须包含所有 required 字段,缺失字段会导致校验失败”。另外,可以在 few-shot 示例里故意展示一个“完整字段”的例子,让模型有样学样。我加了这两条之后,缺字段的问题基本消失了。

5.2 渲染出来的结果和预期不符怎么排查

渲染问题排查有个固定套路:先看 IR,再看渲染器,最后看数据。

第一步,把 Agent 生成的 IR 打印出来,人肉看一眼结构对不对。很多时候问题出在 IR 本身就错了,比如dataSource写错了名字。

第二步,如果 IR 没问题,用一份手写的、确定正确的 IR 喂给渲染器,看渲染结果对不对。如果手写 IR 渲染正常,说明渲染器没问题,问题在 Agent 生成的 IR 上。

第三步,如果 IR 和渲染器都没问题,那就是数据源的问题。检查数据接口返回的字段名和 IR 里声明的xField、yField是否一致。

这个三步排查法能覆盖 95% 的渲染问题,比盲目 debug 高效得多。

5.3 常见问题速查表

问题现象可能原因排查方向解决方式
IR 校验失败字段缺失或类型错误看校验错误信息反馈给 Agent 重生成
渲染报 unknown typeIR 里有未注册的节点类型检查 type 字段注册新渲染函数或修正 IR
页面空白数据源为空或字段不匹配看网络请求和数据修正 dataSource 或字段映射
生成结果不稳定prompt 约束不够对比多次生成结果加 few-shot、用结构化输出
token 消耗过高节点数太多或示例太长统计输入输出 token分批生成、精简示例

5.4 几个我踩过的坑

坑一:schema 太宽松导致 Agent 乱发挥。早期我用了additionalProperties: true,结果 Agent 加了一堆自定义字段,渲染器全不认识。后来改成additionalProperties: false,世界清净了。

坑二:渲染器静默失败。有一次渲染器遇到未知节点类型直接返回空字符串,导致页面缺了一块但没报错,排查了半天。后来改成遇到未知类型直接抛错,问题立刻暴露。

坑三:IR 缓存没考虑用户权限。缓存 key 只用了意图文本,结果 A 用户生成的看板被 B 用户命中了,数据泄露。后来缓存 key 加上了用户 ID 和数据权限范围,才解决。

注意:如果你的 IR 里包含数据源引用,缓存一定要带上权限维度,否则很容易出安全问题。这个坑我在两个项目里都见过,代价都不小。

6. 方案扩展与个人经验

6.1 从单端渲染到多端复用

IR 方案最大的想象空间在于多端复用。同一份 IR,你可以写多个渲染器:一个渲染成 Web 页面,一个渲染成移动端原生组件,一个渲染成 PDF 报告,一个渲染成图片。Agent 只需要生成一次 IR,多端自动适配。

我在一个项目里就做过这事:同一份看板 IR,Web 端用 React 渲染,导出报告用服务端渲染成 PDF,分享到社交平台用 canvas 渲染成图片。三个渲染器加起来不到 800 行代码,但省掉了三套独立的生成逻辑。这个投入产出比非常高。

6.2 让 IR 支持组合与继承

当你的 IR 节点类型越来越多,会发现很多节点有共同的属性。这时候可以引入“组件”概念:把常用的节点组合封装成一个可复用的组件,IR 里用ref引用它。这样 Agent 生成时可以用更少的节点表达更复杂的结构,token 消耗也降下来了。

比如一个“带标题的图表卡片”,可以封装成一个组件,Agent 只需要写{ type: "ref", name: "titled_chart", props: {...} },渲染器展开成完整的节点树。这个思路和前端组件化是一模一样的,本质都是抽象和复用。

6.3 我个人在实际操作中的体会

做了这么多 Agent 生成项目,我最大的体会是:不要试图让 AI 做所有事,也不要试图让代码做所有事。AI 擅长理解模糊意图、做开放决策;代码擅长执行确定性逻辑、保证一致性。可视化生成方案的本质,就是在这两者之间划一条清晰的边界。

这条边界划在哪里,取决于你的业务。约束越强、结构越固定的场景,边界越往 AI 那边推,让 AI 多做决策;约束越弱、越需要灵活性的场景,边界越往代码那边推,让 AI 只做最核心的判断。找到这条边界,比堆砌任何 prompt 技巧都重要。

最后分享一个小技巧:在 IR 里加一个meta字段,记录生成来源、时间戳、Agent 版本。这个字段渲染器不处理,但排查问题时特别有用。你能一眼看出这个 IR 是哪次生成的、用的哪个版本的 prompt,回溯起来方便很多。这个习惯我坚持了两年,救过我好几次。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询