一条提示到可评审的 PR:设计 Agent 的四个工程决定
原文:LangChain Blog – 《How We Built Our Design Agent》(https://www.langchain.com/blog/design-agent)
一、先看它到底做了什么
Content Studio 是 LangChain 内部的一个设计 Agent:同事在 Slack 线程里说一句需求,它产出视频、社媒图、幻灯片、博客头图这类成品文件。官方给的累计数字是三千多件资产、八十多套幻灯片,三分之四的请求来自市场部以外的团队,75% 的用户每周还会回来。
最值得看的不是产出量,是成本曲线:中位请求的模型花费现在约 0.5 美元,而九月中旬是约 8 美元。同时,迁移到托管平台后自研代码量减少了约八成。
一个 Agent 既要质量提升、又要成本降一个数量级,靠的绝不是换个便宜模型。这篇文章里有四个可复用的工程决定,我把它们按"如果你的 Agent 也要跑几小时、产多种资产"这个前提重新组织了一遍。
二、决定一:让产出全部是代码
原文有一句话是整个架构的地基:智能体产出的每一件资产都是用代码写的。
具体形态是:一页幻灯片是一个 HTML 页面,一张图表是一份数据规格,一段视频是一条动画时间线。这不是为了炫技,是为了可迭代。原话的意思是,Agent 可以只改一个时间参数然后重新渲染,而不会动到画面或文案。
配套的是两类引擎。一是 HeyGen 开源的 HyperFrames,把视频变成一个网页,片段、录屏、字幕、文字都是页面上的元素,由 GSAP 时间线驱动;它的命令行工具负责三件事——检查合成、抽一帧看效果、渲染成片。二是自研的 canvas 引擎,用来处理网页做不到的效果:高速运动的模糊、发光、3D 转动的 logo,它把每一帧里最多 16 个瞬间混合成一张,类似相机快门的运动模糊。
这里有个细节很关键:两个引擎都按时间戳渲染每一帧,所以第 10 秒那一帧无论什么时候渲染,看起来都一样。这一条直接解锁了三件事——可以只抽单帧检查、可以并行渲染多帧、音频可以和画面挂同一条时间线对齐。
对照一下我们平时写的 Agent:如果产出是"一段不可复现的 prose",Agent 只能整段重写;如果产出是结构化、可局部渲染的代码,它就能只改一处。判断标准很简单:Agent 能不能只重做整体的一小块。
三、决定二:把主智能体从实现者改成幕僚长
这是我认为最有借鉴价值的一条。原文的做法是把子智能体改成异步,主智能体的角色从"自己动手"变成"分派与验收":把复杂项目拆成小的、隔离的任务,派给子智能体,并行推进。
为什么必须异步?原文说得很直白:同步子智能体在这里不好用,因为主智能体必须等它们跑完,中间这段时间人就没法介入。而 Slack 线程这种交互形态,人恰恰是随时要插话的。
分派时的粒度是"给每个任务写一份子智能体档案",包含四件事:它的角色、相关上下文、它拥有哪些文件、以及怎么验证工作。同时按任务复杂度选择模型和思考强度——这里是原文给出的降本手段之一。
底层实现并不复杂:每个子智能体就是 Deep Agents 自带的 task 工具,跑成一个后台任务;它结束后,代码再排一次主智能体的运行,把结果带进去。协作靠的是项目文件夹,每个子智能体只写自己拥有的文件,进度落盘。
原文给了一个时间账本,能直观感受这种结构的形态:重做一份 52 页的幻灯片时,主智能体跑了大约一小时,而它派出的子智能体加起来工作了六个多小时。人也参与了——它先生成三页样张并自检,再分批推进、分批等确认,中途还主动加了两页。
把这段翻译成可落地的检查表,大概是这样:派活时有没有写清楚"文件所有权",直接决定并行会不会互相覆盖;有没有写清楚"怎么验证",直接决定子智能体交回来的东西能不能用。
四、决定三:能写成代码的检查,就别写在提示词里
原文有一句我打算长期引用的话:必须永远成立的顺序住在代码里,判断留在提示词里。
反面案例很具体。流水线要求先生分镜、再做动态预演、最后进正式制作,作者一开始把顺序写在 skill 的说明里,结果智能体有时为了省时间跳过评审。后来改成代码强制——装配阶段必须等每一幕都通过评审才能开始。
另一个例子是大小写这种"默认值"问题,加了自动化检查后每次都抓到,不再依赖模型记性。
正面的例子是 Agent 自己造检查。做一个鹦鹉动画时,它自己写了逐帧检查,确保丝带一直连在鸟尾巴上——判定条件是丝带的根部(黄色)在丝带飞出画面前一直贴着鸟尾(品红色)。这种检查原文没说是人写的还是模型写的,只说它自己建了出来并检查自己的渲染结果。
这条决定的价格很高,因为要写代码。但它同时解决了三个问题:模型换代后规则不会失效、评审不会被绕过、问题不会重复出现。
五、决定四:上下文分四层,每层都有配额
原文的四层分工可以做成一张表:
| skills | 怎么做具体任务 | 共 48 个 skills、接近一千个文件,按"岗位"而不是"主题"切分 |
| wiki | 公司语境与什么算好 | 单一事实源,skill 里直接链过去,不复制 |
| gate | 可量化的检查 | 用代码写,确定性的判断 |
| memory | 人教给它的东西 | 分团队共享与个人两层,各自一份索引文件 |
skill 按岗位切分这件事值得单独说:它的描述读起来像一条路由规则,还写明什么时候不该用它。原文用了一个大路由器 skill 叫 media-production,主体就是一张表,每行把一种需求(campaign、改风格、缩略图、帖子、YouTube 套餐、发布短片、短动画)映射到对应路径和专家 skill,同时决定这次要不要开共享画布,并把小改动挡在完整流程之外。
另一条是渐进披露。MCP 工具一开始不全部加载,只给自家工具,其余按需通过关键词检索工具名和描述来加载,目的就是压住上下文占用。
还有一条数字很刺眼:memory 两层的索引文件 AGENTS.md 加载在每一次模型调用里,早期版本涨到了 50 万 token,后来限制成 200 行的索引。这条我建议直接抄——凡是每轮都进上下文的东西,必须有人管配额。
最后是两个学习环。快环是同事说了条规则或偏好,Agent 在当轮就存进去;慢环是任务结束后 launch 一个轻量级子智能体,抓最多五条有证据支撑的教训,挂到本次用到的 skill 上,后续任务会读取并给这些教训打分,一周一次由后台 Agent 把真正有用的并进 skill 和 wiki。
六、原文自己踩过的坑,比经验更值钱
第一个坑是"给模型加规则"会越加越贵。最早的 harness 里,团队建了一套含 115 个可复用 UI 组件的设计系统,结果 Agent 每一帧都重复同样的布局和动画。为了补救,团队不断往 skill 里加规则、写更多动画代码、建更多组件,工程投入只增不减。后来模型升级,团队把大部分硬编码规则删掉,效果反而更好。原文引的是"苦涩的教训":为了弥补模型弱项而设计的规则,会随模型变强而失效。
这条对正在写系统提示的人是个提醒:你可以限制模型,但要定期检查这些限制还在不在解决真问题。
第二个坑是"泛化而不是品牌"。单独给品牌 skill 也会失败——漏掉正确的 logo 组合、文本对齐、主色与辅色的区分。原文给的原因是:对于需求书没覆盖到的每个决定,模型会用它见得最多的做法来填。原文引用的概念是"分布收敛",还有一个词叫僵尸 UI:如果你自己没有观点,AI 会替你产生一个。
第三个坑是反馈通道的形态不匹配。Slack 线程是线性文本,而分镜是空间性的,所以反馈最后变成"描述东西在哪",而不是"指着它说"。任何一处改动,哪怕只是改一个词,都得作为新提示重新走一遍 Agent。
第四个坑是主观描述。Agent 第一版做出来的边缘又硬又圆,因为需求里写的"不要尖角"被理解成了"圆角"。人的真实意图是边缘变柔和、尾部朝运动反方向渐隐。
第五个坑是成本错位。分镜帧渲染约 50 毫秒,而成片一秒钟要 8 到 40 秒。所以流程被设计成先在分镜阶段廉价探索方向,方向确认后再进正式制作。一个错误的创意决定,代价是几小时的渲染。
七、最小可复用的骨架
把上面四条压成一个能直接照抄的结构,大概是这个样子。下面这段是我自拟的示意代码,不是官方实现,也不依赖任何 SDK。
# asset_pipeline.py —— 可局部渲染 + 代码 gate + 文件所有权,三件事的最小骨架
import json
import time
from pathlib import Path
class Project:
"""一次任务 = 一个项目文件夹,所有产物落盘,子智能体共享同一份磁盘状态。"""
def __init__(self, root: str):
self.root = Path(root)
(self.root / "frames").mkdir(parents=True, exist_ok=True)
(self.root / "gate").mkdir(exist_ok=True)
def write(self, owner: str, rel: str, text: str):
"""带所有权校验的写入:每个文件只允许一个子智能体写,避免并行覆盖。"""
meta = self.root / "owners.json"
owners = json.loads(meta.read_text("utf-8")) if meta.exists() else {}
if owners.get(rel, owner) != owner:
raise PermissionError(f"{rel} 归 {owners[rel]} 所有")
owners[rel] = owner
meta.write_text(json.dumps(owners, ensure_ascii=False), "utf-8")
(self.root / rel).write_text(text, "utf-8")
def frame(self, index: int, seconds: float) –> Path:
"""按时间戳渲染单帧:同一秒永远渲染出同一张图,所以可以抽检、可以并行。"""
out = self.root / "frames" / f"{index:04d}_{seconds:.2f}.png"
out.write_bytes(b"") # 占位:真实实现调用渲染引擎
return out
GATES = {
# 顺序型 gate:上一阶段没过,下一阶段不许开始
"assemble": lambda p: all(g.get("passed") for g in p),
}
def enforce(stage: str, results: list[dict]) –> None:
"""能写成代码的检查就写在这里;跑不过直接抛,不靠提示词自觉。"""
if stage in GATES and not GATES[stage](results):
raise RuntimeError(f"{stage} 未通过评审,禁止进入下一阶段")
if __name__ == "__main__":
proj = Project("./out/launch-film")
proj.write("scripter", "brief.md", "开场三秒钩子,尾帧留品牌主色")
proj.write("animator", "scene1.py", "timeline = [{'t': 0.0, 'clip': 'logo_in'}]")
assert proj.frame(0, 0.0).exists()
acts = [{"act": 1, "passed": True}, {"act": 2, "passed": False}]
try:
enforce("assemble", acts) # 第二幕未过评审 → 装配被拦住
except RuntimeError as e:
print("已拦截:", e)
参数与返回值先说清楚。Project 收一个根目录,构造时把 frames 和 gate 两个子目录建好。write 收三个参数——写入者标识、相对路径、内容,它读一遍 owners.json 校验所有权,不是自己的文件就直接抛异常,最后才落盘。frame 收帧序号和秒数,返回该帧的路径;它按秒数命名,是"同一时间戳渲染结果一致"这个前提的直接体现。
enforce 收阶段名和评审结果列表,没有返回值,跑不过就抛。调用链是这样的:准备项目目录 → 各子智能体按所有权写入自己的文件 → 抽帧自检 → 评审结果汇总成 acts → enforce 拦截未过评审的幕 → 全过才进装配。
这里刻意做成"抛异常"而不是"返回警告",因为原文的教训就是:写在文档里的顺序会被优化掉,写在代码里的顺序才会被执行。
收尾
回头看成本从 8 美元到 0.5 美元这件事,原文并没有把降本归因列成清单,只明确提了迁移到托管平台后代码减少约八成、按任务复杂度选模型和强度、以及失败要被真实反馈修正。所以别把它当成一个"优化技巧合集"来抄。
真正能拿走的是那四条结构性决定:产出写成可局部重建的代码、主智能体做分派与验收、必须成立的顺序写进代码、每类上下文有明确配额。这四条和模型换代关系不大,属于换哪个模型都成立的部分。
如果你手上有一个跑十几分钟的多工具 Agent,我建议的下一步不是加工具,而是先回答一个问题:它失败的时候,你能定位到"哪一帧、哪个文件、哪个 gate 没过"吗?答不上来,先补可观测性,再谈扩容。
文中数据与判断来自 LangChain 10 月 27 日发布的这篇博客,开源 harness 在 langchain-ai/design-agent;涉及托管平台能力与模型版本的部分,以官方文档为准。
网硕互联帮助中心







评论前必须登录!
注册