项目正式跑完最后一轮回归测试前端、后端、小程序端三个仓库全部打上 release 标签的时候我长出一口气。这个全栈 AI 修图 Agent从二月份立项到现在前后五个月中间还推翻过一次架构设计总算以比较稳定的状态收尾了。借着热乎劲把整个项目的技术决策、核心链路和踩坑过程完整复盘一遍分享给正在做或者打算做 AI Agent 方向的朋友。先说清楚这个项目到底做了什么。它不是又一个美图类工具也不是给照片加滤镜的套壳应用。这个项目的核心形态是一个能听指令干活的修图 Agent用户用自然语言描述修改目标例如背景换成一个晴天海边色调调暖一点顺便把垃圾桶抹掉Agent 自己理解意图、拆解为多个图像处理步骤、依次调用工具执行、最后返回修好的图。整个系统由 Vue3 管理端、uniapp 多端客户端、Go 后端服务、Agent 编排引擎和图像处理引擎五部分组成属于比较典型的全栈 AI 应用架构。我在这个项目里负责整体架构和 Agent 编排层的核心开发从技术选型到最终落地过程中积累了不少一手经验下面按模块拆开讲。如果你正准备做类似的多端 AI 应用这篇文章能帮你少走不少弯路。1. 这个项目到底解决什么问题从滤镜堆叠到指令式修图1.1 为什么没做成又一个一键美化工具最早立项讨论时团队内部有过一次方向之争。一部分人倾向于做传统的修图软件接入几个现成的美颜 SDK做一套滤镜模板前端套壳上线这类产品市面上太多了同质化严重用户早已审美疲劳。另一部分人想做 AI 修图但一开始的思路也只是用大模型做几个特效入口比如老照片修复、动漫化、超分增强说白了还是工具列表模式。最终我们把方向定成 Agent核心原因是看到了一个没有被满足的真实需求普通用户拿到一张照片通常不是想套某个固定模板而是有一连串具体的修改想法。帮我把背景换掉、把人物提亮、去一下皮肤瑕疵这三件事在传统工具里分别对应抠图、调色、美颜三个完全不同功能模块新手光找到这些入口就要花不少时间。如果存在一个能听懂人话的助手把这一串操作一次搞定体验上的提升是碾压性的。1.2 核心使用场景一句话驱动多步编辑我们给 Agent 定义的目标用户是有修图需求但没有修图技能的人群。这一类用户占比非常大他们不需要图层、蒙版、曲线这种专业概念只想要一个直观的结果。下面是产品上线后几个高频使用的真实场景电商卖家处理商品图上传一张白底产品图输入把背景换成浅灰色产品颜色稍微加深加一点阴影Agent 三步连续处理得到可上架的商品图。个人用户处理生活照把照片背景换成一个模糊的书架再把整体色调调成暖黄色工具链内部会依次触发人像分割、背景生成、色彩调整三个工具。设计师快速出提案这张图帮我把它变成扁平插画风格然后把主体放大一点居中Agent 执行风格迁移和裁切重定位。这三个场景有个共同特征用户描述的是目标而不是操作步骤。这正是 Agent 和传统自动化脚本的本质区别。普通修图工具的批处理需要用户手动编排滤镜顺序而 Agent 负责把自然语言翻译成一套可执行的工具调用序列。我们在后端日志里统计过平均一次完整修图对话会触发 2.8 个工具调用高的时候能到 6 个以上单工具直接解决问题的占比不足三成。说明用户确实是在靠它完成组合操作。1.3 整体架构俯瞰整个项目从外到内分五层我这样划分层级技术选型职责客户端层Vue3 Web端 uniapp多端图片上传、对话交互、结果展示接入层Go Gin统一网关、鉴权、请求路由Agent编排层自研调度器 大模型API意图解析、任务拆解、工具路由、上下文管理图像处理层开源模型 云图像API混合抠图、调色、修复、超分、风格迁移等具体操作基础服务层PostgreSQL、Redis、OSS数据持久化、队列调度、图片对象存储Agent 编排层是整个项目的大脑图像处理层是手脚。手和脑分离是这个项目最重要的架构决策。很多团队做 AI 应用时习惯让模型直接输出图像实际操作中你会发现让模型直接生成一张图和生成控制指令然后让图像引擎执行是两条完全不同的技术路线前者受限于生成模型的能力上限后者可以无限扩展工具边界后者的工程可控性明显更好。后面第五章我会专门展开讲这个决策背后的踩坑过程。2. 技术选型的真实考量Vue3 Go uniapp 这套组合是权衡出来的2.1 前端技术栈为什么保留 Web 端和客户端两套项目最开始讨论技术栈时有三个候选方案纯 Web 应用、纯 App、Web 多端小程序。最终选了 Web uniapp 多端并行主要考虑是产品形态需要快速验证。Web 端可以做到当天改代码当天上线适合跑通核心修图流程验证需求而修图这种高频操作最终一定发生在手机上所以 uniapp 这套必须同步做。前端框架最终选了 Vue3 TypeScript原因很朴素团队对 Vue3 组合式 API 的熟悉度最高生态成熟而且和 uniapp 的语法体系天然同源。这样写一套业务逻辑Web 和 App 两端都能复用大部分代码。图片展示和编辑界面用 Canvas 实现实时预览配合 OSS 直传做图片上传交互层没有用太重的前端渲染方案。需要提醒的是uniapp 在 Canvas 部分的 API 和 Web 端有细节差异这部分坑我放在第五章详细讲。2.2 后端选型Go 的并发模型和部署友好性后端选择 Go 而不是 Node.js 也不是 Java核心原因有三个。一是 Go 的 goroutine 并发模型非常适合处理图像类任务每次修图调用涉及多个图像工具的执行天然适合并发调度二是单一二进制文件部署极简对接内网模型服务、对象存储、各种图像处理引擎都非常清爽不需要像 Java 那样背一个重型运行环境三是团队长期维护 Go 服务的经验最丰富出问题能快速定位。这个项目里 Go 后端承担的工作包括RESTful 接口提供对话和图片存取能力Gin 框架、Redis 队列承接异步修图任务asynq、PostgreSQL 持久化对话记录和图片版本关系、以及作为 WebSocket 通道向客户端推送任务执行进度。实测下来 Go 在承载高并发图片流上传和处理任务分发时表现非常稳定内存占用比同量级的 Node 服务低不少。说到具体库图片上传用 OSS 直传方案后端只负责生成签名 URL不走服务端中转避免了大图传输打满网关带宽。这一点非常重要因为修图场景的单张图片体积普遍在 5MB 以上如果全部经过后端代理带宽成本会直线上升。2.3 Agent 编排层为什么没有直接用 LangChain这是项目里争议最大、也是我最有心得的一个决策点。立项时市面上的 Agent 框架已经不少尤其是 LangChain 这类工具链非常成熟网上教程一堆看起来拿来即用。但我们最终选择了自研一个轻量级调度器核心考量有三个第一我们的业务是图像工具链不是文本工具链。LangChain 的生态核心在文本、知识库、API 调用这类场景图像理解、图像处理工具的接入反而要写大量胶水代码。第二修图的交互链路高度定制需要维护图片版本与上下文绑定的特殊状态。比如用户说再调亮一点这里的再指的是上一轮处理后的那张新图而不是用户最初上传的原图。这种状态管理在 LangChain 的记忆模块里表达得很别扭自己写一个状态机可以完全按需求设计。第三自研调度器可以精确控制 Token 消耗和模型调用流程。框架越薄可控制的细节越多这对于成本敏感的照片业务来说是刚需。最终我们采用了 ReAct 模式Reasoning Acting的自研实现整个调度核心不到一千行 Go 代码却完全够用。后面第三章详细展示这套调度的核心逻辑。当然这不是说 LangChain 不好如果你的场景是知识库问答、文档处理管线这类偏文本任务LangChain 确实是更快的选择。但对于强图像、强状态、强定制化的修图 Agent自己写编排层反而性价比更高。3. Agent 核心链路意图理解、任务拆解、工具调用与结果校验3.1 状态管理每一轮对话都绑定一张图片版本有一个概念做 AI Agent 的人必须首先搞清楚在图片编辑场景里对话状态就是当前图片版本。用户每说一句话图片就变一个版本而这个新版本会成为下一轮对话的输入。这和聊天机器人完全不同聊天机器人的状态是一段文本历史修图 Agent 的状态是一棵图片编辑历史树。我们的实现方式是在对话上下文里维护一个 image_id 字段后端的 ImageVersion 表记录每一次工具调用产生的图片版本。核心表结构大致如下CREATE TABLE image_versions ( id BIGSERIAL PRIMARY KEY, conversation_id BIGINT NOT NULL REFERENCES conversations(id), parent_version_id BIGINT REFERENCES image_versions(id), tool_name VARCHAR(100), input_params JSONB, output_url TEXT, version_index INT NOT NULL, created_at TIMESTAMPTZ DEFAULT now() );每执行一次工具调用就往这张表插入一条记录同时更新对话上下文的当前 image_id。这样做的直接好处有三个用户能随时回退到之前的版本Agent 能基于任意历史版本继续编辑前端能用 parent_version_id 画出一棵图片修改树方便用户对比前后效果。这个数据结构在设计时并不起眼但实际使用中帮了大忙用户回退和基于某一步重新修改的需求非常高频。3.2 工具注册与参数约束让 AI 能上手改图Agent 的所有图像操作都抽象为工具Tool每个工具有名称、描述、参数 Schema 和执行函数。这样设计之后扩展新功能只是往注册表里加一个条目的事。我们定义了这样一个基础结构type Tool struct { Name string Description string Parameters map[string]interface{} // JSON Schema Execute func(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) }初始版本我们内置了 14 个图像工具覆盖修图产品的主流需求remove_background人像/商品主体抠图replace_background背景替换支持纯色、图片、提示词生成三种模式color_temperature色温调整暖/冷brightness_contrast亮度/对比度调整skin_smooth皮肤磨皮object_remove物体消除去杂物/去水印image_restore老照片修复super_resolution超分辨率放大style_transfer风格迁移crop_resize裁剪与尺寸调整face_enhance五官增强text_add添加文字水印color_grading色彩统一filter_apply滤镜应用每个工具的 Description 必须写清楚适用场景和不适用场景这对大模型的工具选择准确率影响极大。比如 object_remove 的描述我们会写成从图片中移除指定物体/文字/瑕疵返回移除后的图片。此工具适用于去除杂物、水印、路人、垃圾桶等不需要的元素。不适用于主体替换或风格变化如需更换整体风格请调用 style_transfer。系统提示词的角色是专业修图助理工具选择逻辑由模型根据 Description 自主决策。3.3 ReAct 循环与停止条件怎么避免 AI 无限改图Agent 的推理循环本质是一个不断观察-思考-行动的循环。我们实现的调度循环简化后大概是这样的1. 获取用户最新指令 当前图片URL 历史操作摘要 2. 调用大模型让其输出下一条操作工具调用或终止 3. 如果输出是工具调用 a. 校验参数合法性 b. 执行图像工具 c. 获取新图片URL d. 更新上下文状态 e. 回到步骤2 4. 如果输出是终止信号 a. 汇总最终结果 b. 返回给客户端一开始我们天真地认为大模型会在合适的时候自然停止当时函数调用返回了终止标记就结束。结果测试时出了问题多次出现AI 一直在执行工具调用但从不主动停止的死循环。后来分析日志发现模型没有信号判断任务是否已经达到用户预期它倾向于持续进行微小的优化性调整。解决方案是给循环加了四个停止条件优先级从高到低最大循环上限单轮用户指令最多执行 6 次工具调用超出即强制终止并返回当前状态。这个数字经过实测覆盖了大多数场景而不会让用户等待过久。模型输出终止标记模型自身判断任务完成时输出 STOP 指令。重复操作检测如果连续两次选中同一个工具且参数差异小于阈值判定为无效循环并终止。用户中断客户端 WebSocket 推送取消信号后端立即终止任务回滚到上一个稳定版本。其中重复操作检测这个条件是一次真实事故换来的。内测时用户对着一张图说让这个颜色更鲜艳一点结果模型连续调用了四次 color_grading每张图变化越来越微小用户等了一分多钟没有明显变化。加了重复检测之后遇到这种情况会主动询问是否停止体验好很多。这个大模型在重复操作时是感知不到自己正在空转的只能靠工程手段兜底。3.4 结果校验图像边缘、比例、色彩的实效检查工具执行完返回的图不能直接信任必须经过一道自动校验否则容易出现黑图、花图、尺寸不符等低级问题。图像处理引擎偶发输出坏图是有一定概率的所以我们在每个工具执行结果上加了一个统一的校验器检查以下项目图片完整性文件头是否合法、能否正常解码防止输出损坏的 JPEG/PNG。尺寸一致性除 crop_resize 和 super_resolution 之外其他工具的输入输出分辨率应保持一致允许误差 5%否则判定异常。颜色有效性检测图片的平均色彩是否发生极端变化比如全黑、全白、平均色偏红偏绿捕捉到过一次风格迁移工具输出偏绿异常图。是否有内容检查图片的像素方差如果方差过低比如完全纯色判定为一次失败调用。校验失败的图片不会进入工具链下一环而是直接标记该次工具调用失败并让模型重新选择工具或者告知用户结果不可用。这个失败重试机制是很多 Agent 项目容易忽略的没有这层保护坏图会在多轮对话里被不断放大最终用户看到的是一张完全崩坏的图。4. 性能、成本与体验的平衡全栈修图 Agent 的工程化落地4.1 图片传输Base64 和直传 URL 的取舍AI Agent 的上下文传输是一个容易翻车的地方。一开始我用 Base64 把图片直接塞进大模型的 messages 里模型确实能看到图但问题很快暴露一张 5MB 的图片转 Base64 后约 6.7MB 字符这部分显式 Token 消耗巨大而且多次工具调用之间每次都要重传整张图一轮对话下来光图片 Token 就够呛。不同模型对图片输入的要求也不一样。图片理解模型通常能接受 URL 形式的图片输入但要求 URL 能公开访问或带有鉴权签名。我们的做法是初始对话传一张压缩后的预览图后续每一轮工具调用只传上一轮图版本的高清缩略图 URL让模型感知变化但不需要加载完整大图。等到真正要执行精细调色或其他精度敏感的操作时才把高清图传给后端执行引擎。实测下来这个策略把单轮多步对话的图像 Token 开销降了接近 60%同时模型理解精度基本不受影响。大多数人不知道的一个经验是Agent 在处理图像编辑时理解任务所需的信息量远低于输出细节所需的信息量与其高频传输整张大图不如批量只传低分辨率版本让模型看清大致结构即可精确执行交给后端算法。4.2 Token 成本控制不是每一轮都要重发整个历史很多 Agent 项目做到一半才发现 Token 成本扛不住修图 Agent 比文本 Agent 更严重因为一次图片调用的 Token 开销远超普通文本对话。我们的成本控制策略有三个核心点第一对话历史压缩。因为修图 Agent 维护的是图片状态 操作列表我们不需要把所有历史对话全文传给模型只需要传一个结构化摘要当前图片版本 URL、已执行的操作列表按时间顺序最多 8 条、用户最新指令。这个摘要用固定格式文本组装Token 消耗远小于完整历史。第二操作级缓存。我们把图片处理结果做 MD5 校验值同一张图同样的参数不重复调用图像引擎直接返回缓存结果。这在电商场景非常常见用户会反复上传同一张产品图测试不同的背景色。第三大模型分级调用。意图理解、工具选择这些核心推理用能力较强的模型而操作摘要生成这类简单归纳任务用轻量模型。实测一个完整多步修图的 Tool Call 链通过分级调用成本下降约 35%效果基本无感差异。这三个策略跑了一个月后单用户单日平均 API 成本稳定在几块钱人民币以内对一款 C 端应用来说属于健康范围。做 Agent 项目的人一开始就要想着成本优化不要等用户量起来了再改架构那时候改动成本太高。4.3 异步任务与进度反馈长耗时任务的兜底方案修图任务不是一蹴而就的单步骤工具平均耗时在 2-6 秒之间一个多步任务可能要 15-30 秒。如果走同步 HTTP 请求客户端一个接口等半分钟体验非常差而且容易触发网关注销超时。我们把任务改成异步模式客户端发起任务后通过 WebSocket 接收进度推送。后端流程是HTTP 接口接收用户请求创建一个 ContextRun 记录。任务进入 Redis 队列asynq立即返回 task_id。Agent 编排器从队列中消费任务逐步执行每个工具。每个工具执行完成时向 Redis Pub/Sub 广播一个进度事件。Go 后端通过 WebSocket 通道把进度事件推给客户端对应连接。全部工具执行完成结果写入数据库并推送最终图片 URL。这套方案配合前端的步骤进度条组件让用户清楚看到当前正在做什么等待焦虑大幅降低。实测下来用户对 15-30 秒的异步处理接受度很高愿意继续等待完成的比例超过八成。真正影响耐心的是不知道还要等多久而不是等待本身。4.4 一组真实性能数据项目验收时我们做了一组压测和效果实验这里放一组有代表性的数据均为真实环境服务器 4 核 8G工具服务独立部署场景图片大小工具链长度总耗时s电商白底图换背景3.2MB3步8.7生活照换背景调色5.6MB4步14.2老照片修复超分8.1MB2步23.5复杂场景去杂物调色磨皮6.4MB5步18.9多轮对话第3轮增量调整6.4MB2步6.3图片处理引擎的耗时是主要瓶颈Agent 编排层的推理决策开销加在一起不到 15%。这也是我们能把编排层做得比较轻的原因之一真正的重活在图像工具服务上。如果将来单套处理引擎扛不住并发优先扩容图像处理服务而不是 Agent 层。5. 迭代过程中踩过的坑从第一版 Demo 到能上线的距离5.1 最大的一次返工让 AI 直接生成图片是个伪命题项目跑了两个月时做了一版关键 Demo当时的设计是让模型直接根据用户指令生成处理后的图片使用多模态大模型的图像编辑能力说是能让 AI 改图。实测下来问题非常明显模型对图片的修改是生成式的它基于理解重绘一张图而不是在原有像素上进行精确编辑。用户说把亮度调高 20%模型画出来的图明暗关系是对的但人脸的细节、远处的文字可能全变了而且无法精确控制调整幅度。这是整个项目最大的一次返工。我们把架构从模型直接输出图改成模型输出工具调用指令 后端图像工程精确执行保留大模型在意图理解和任务规划上的能力把像素操作权完全交回给算法引擎。这相当于把大模型从一个画家降级为项目监理——你说需求它拆任务具体施工由专业团队干。这个转变让修图效果从看起来还行提升到商用可交付。这也是我建议所有做 AI 修图应用的人首先要想的架构问题在生成模型真正达到 Photoshop 级别的像素操控能力之前混合架构是最靠谱的路线。大模型负责认知专用算法负责执行两者的分工必须明确。5.2 上下文爆炸修到第五步 AI 就失忆了第二版上线后内测反馈里出现一个共性现象很多用户在多轮对话后AI 会忘掉早期步骤。典型的对话是第一步先去掉背景第二步把亮度提高第三步再加一行文字等执行到第三步时AI 似乎已经不记得第一步和第二步做过什么了。排查后定位到根因是我们在组装上下文时把历史对话的文本和图片历史全塞给了模型看起来信息完整但模型的注意力在长上下文里会衰减尤其经过几次工具调用的图片版本更新后模型对当前图像状态的更新反而产生了混乱。解决方法就是我们前面提到的结构化摘要方案不传历史对话全文只传当前图片 URL 最近 N 步操作摘要 最新指令这个摘要由一个轻量模型在每一步重新生成保证信息密度高、噪声少。改了之后内测里失忆的频率大幅下降。这个经验不仅是修图 Agent 可以用凡是涉及多轮状态变更的 Agent 都应该考虑上下文不是越长越好而是要精确表达状态。5.3 uniapp 多端的图片处理兼容性uniapp 所谓的多端复用在普通业务页面确实省事一旦碰到底层图片处理 API 就到处都是坑。我们在做客户端图片预览时遇到了 Canvas 兼容性问题Web 端的 canvas.toBlob 在部分安卓 WebView 里表现不一致生成的图片可能丢失 EXIF 信息导致上传后图片方向被旋转小程序端的 Canvas 2D API 和浏览器标准写法有差异同一段绘制代码在两端行为不一致。最终的处理方案是客户端不做任何真正意义上的图像处理所有像素级操作全部交给服务端客户端只负责上传原图、展示结果图。在用户上传前客户端做一次基础压缩和方向校正压缩统一走一个兼容性良好的 JS 库不做花活。数据传到服务端后由后端图像处理引擎统一处理。这个客户端最小化处理的架构决策是兼容性问题的最终解如果你做多端 AI 应用建议一开始就按这个原则设计。5.4 多轮对话的回归测试方法修图 Agent 的测试比普通后端接口难得多核心原因在于输出不确定。同一段用户输入大模型可能每次选的工具组合都不一样这就导致自动化测试根本没法直接断言输出等于预期值。我们实践的回归测试方法是基于效果属性的验证而不是基于输出值的验证。每个测试用例定义一个预期属性和验证函数比如测试背景替换用例不验证输出图片的像素是否和基准一模一样而是验证输出图片尺寸正确、人物主体还在、图片未被损坏、背景区域的主色调接近目标色值。这些属性都可以用图像处理脚本自动检查。再比如色彩调整用例验证输出图的平均亮度相对于输入图是否有明显提升数值范围可以放宽但方向必须正确。基于属性的回归测试让我们的自动化测试覆盖率达到了单轮指令约 70%多轮组合场景手工测试为主。对于 Agent 项目来说这已经是一个比较务实的测试模型。6. 完结之后的反思哪些设计值得保留哪些应该推倒重来6.1 用户真实使用频次最高的能力项目上线稳定运行一个月后我们对行为数据做了一次完整的分析有几个数据出乎意料。使用频次最高的不是我们宣传主打的老照片修复而是object_remove物体消除和replace_background背景替换这两个加起来占了总调用量的 47%。用户说把路人甲去掉、把背景换成纯色这类需求极其高频远超我们预期。还有一个意外发现是文本编辑功能。我们当时立项时没有规划后来加上的 text_add 工具使用量排在第四。很多用户拿它做封面图、海报文案效果远超预期。做产品时多关注那些原本没规划但用户自发使用的能力它们往往藏着真正的需求。6.2 如果再做一个 Agent我会改掉的三个决定复盘下来有三个决策如果再走一遍我想换个方向第一工具数量一开始就做 14 个偏多。小团队维护十几个工具的提示词撰写、参数调优、测试用例非常吃力很多工具的调用率极低但测试成本一点不少。更合理的节奏是先做 5 个核心工具跑通闭环上线后根据用户实际需求再逐渐增加。第二没有从一开始就做多语言支持。虽然产品主要面向中文用户但出海用的英文界面和英文系统提示词应该在架构阶段就预留配置后补的改造总比原生支持更别扭。第三对话记录存的太碎。每个操作都存了一条独立记录查询整棵编辑树的时候要多次 join性能反而不如直接存一个紧凑的 JSON 操作序列。对于多轮状态型应用有时候冗余存储比高度范式化更好用。6.3 给想入局全栈 AI Agent 的同学的几点建议如果你正在考虑做一个全栈 AI Agent 项目我个人的经验是不要一上来就追求复杂不要被框架生态迷惑也不要低估工程化落地的工作量。Agent 最核心的两个能力是状态管理和工具编排两者都需要专门的架构设计不是接入一个模型 API 就能解决的。修图只是其中一个方向同样的架构可以复用到视频剪辑、文档处理、数据分析等多个场景。技术选型层面我的建议是用你团队最熟悉的语言写编排层把模型调用做成可替换的适配层。大模型更新迭代速度快今天用的最优模型三个月后可能就是性能平平适配层可以让你的 Agent 随时切换底层模型而不用动业务逻辑。这个设计在项目后期帮我们省下了很多重构成本。最后再分享一个小技巧。给 Agent 类的项目写系统提示词时不要只写你是修图助手而是写清楚你定义的工具边界、调用策略、失败处理方式甚至有需要时给它示例对话。我自己的体会是系统提示词里写清楚什么时候不要用工具比写清楚什么时候用工具更关键它能让 Agent 少犯很多自作聪明的错误。项目还有很多可以优化和扩展的地方比如接入更丰富的图像生成模型、支持视频编辑、增加协作功能等但核心骨架已经稳定下一轮迭代可以放心地在这套底座上继续加东西。
