这篇迟来的复盘文章,其实已经在我草稿箱里“压”了快两周。原因很简单:项目做完的那天晚上,我盯着屏幕上跑通的演示视频,居然有种“不知道该从哪儿讲起”的恍惚感。不是因为代码量有多惊人,而是因为 这个“全栈 AI 修图 Agent”项目,真正让我第一次完整地走通了一条从 Model、Agent 到前后端全链路的研发闭环 ——以前总觉得这些概念是散的,这次它们终于在同一个项目里拧成了一股绳。

写这篇长文,不是想晒什么“新项目完结”的仪式感,而是想把我踩过的坑、反复推翻过的架构决策、还有那种“原来 Agent 工程化和写 Demo 完全是两码事”的顿悟,一次性讲清楚。如果你也正打算从纯前端或者纯后端往全栈 AI 应用方向转,或者在 Agent 开发的“工具调用”和“意图识别”里绕不出来,这篇文章应该能帮你省下不少自己摸索的时间。

1. 项目缘起:为什么“又”做了一个修图 Agent,而不是传统修图网站

先说个现象。这两年 AI 修图工具并不稀缺,从在线抠图到一键变清晰,市面上能叫得上名的产品少说也有几十款。但如果你真正以“创作者”的身份去用,很快就会发现一个尴尬的事实: 市面上的修图工具大多是“单点功能” ——这个工具只能抠图,那个工具只能调色,想完成“帮我抠出人物、换一个背景、再把整体色调调成日系清新风”这条完整链路,你得手动切换三四个网站,还要反复上传下载同一个文件。

这个痛点我感受很深。之前我做后期时,经常要在不同工具之间来回倒腾,效率低不说,最烦人的是风格参数每次都要重新调。所以这个项目立项的时候,我就给自己定了一个跟以往不太一样的目标: 我不做“又一款修图网站”,而是做一个“能听懂人话、能自己规划修图步骤、能调用不同图像工具”的 Agent 。

1.1 从“按钮式修图”到“对话式修图”的思维转变

传统修图应用的核心交互是“功能按钮”。用户看到“抠图”“增强”“滤镜”这些按钮,然后点击,再调参数。这个模式对工具类产品来说非常成熟,但它有一个隐藏门槛: 用户得知道“自己需要什么功能” 。

而对话式修图 Agent 的产品逻辑完全不同。用户只需要说“把这个背景换掉”或者“让照片更有质感”,Agent 要自己判断出:这背后对应的是语义分割、图像合成、色彩分级这些技术操作,然后编排好调用顺序去执行。换句话说, 传统 App 是“把工具放到用户面前”,Agent 是“把用户的意图翻译成工具链” 。

1.2 这个 Agent 到底能修哪些图

这里我先明确一下项目边界。由于是个人项目,我不太可能从零训练一个图像生成模型,所以我的策略是“ 管线编排 + 能力串联 ”——底层调用现成的图像处理能力(比如抠图模型、超分模型、图像描述模型),上层用 Agent 做意图解析和步骤规划。目前实际跑通的能力包括:

  • 背景替换 :准确识别主体轮廓,替换成用户描述的新场景
  • 批量调色 :一次性处理多张图片,统一色调风格
  • 智能裁剪 :根据主体位置自动构图裁剪
  • 图像增强 :模糊图片变清晰、低分辨率放大

从实际使用效果来看,单一能力跑通并不难,难点全在“ Agent 怎么知道该用哪个能力、按照什么顺序组合 ”上。这也就是整篇文章想跟你重点聊的核心。

2. 技术选型全景:全栈项目不该只有“前端 + 后端 + 模型”三层

很多全栈初学者对“全栈 AI 应用”的理解,就是 React 写页面、Python 写后端接口、再挂一个模型 API。这个理解没错,但放到 Agent 场景里是远远不够的。 真正的全栈 AI 应用,中间必须多出一个“Agent 编排层” ,而这一层才是整个项目技术架构的灵魂。

2.1 前端技术栈:为什么选了 React + TypeScript 而不是 Vue

前端选型上,我几乎没有犹豫就决定了 React + TypeScript。原因有以下几点:

第一, Agent 应用的交互状态非常复杂 。用户对话消息、Agent 执行的每个步骤、工具调用的中间产物、最终图像结果,这些状态如果不用强类型约束,光是维护 props 就能把人逼疯。TypeScript 的接口定义在这种场景下价值极高——我可以提前定义好“工具执行结果”的数据结构,后端返回什么、前端展示什么,一目了然。

第二, React 的生态更适合做 AI 应用原型 。流式输出、消息列表、画布预览,这些组件在 React 生态里能找到非常成熟的方案。特别是 react-markdown 配合 remark-gfm ,处理 Agent 返回的结构化文本非常顺手。

2.2 后端技术栈:Node.js 不只是“能跑”而已

后端我选择的是 Node.js 技术栈(NestJS 框架)。可能有人会问,AI 应用的后端不是用 Python 更合适吗?

我的答案是: 要看“AI 应用”这四个字的重心在哪 。如果你做的是模型训练、模型微调,那 Python 是唯一解;但如果你做的是“ AI 应用”,核心是业务逻辑编排、API 网关、任务队列、数据持久化,Node.js 的优势反而更突出——它和前端语言统一,类型可以共享;异步 I/O 处理并发请求非常高效;而且说实话,如果只是调 API 拼逻辑,Python 和 Node.js 在开发效率上并不存在代差。

在这类项目中,语言差异不是瓶颈, 架构和组织代码的能力才是关键 。

2.3 Agent 编排层:被很多人忽略的“全栈”新维度

如果这个项目只做前端 + 后端 + 调模型接口,那它跟我之前写过的技术 Demo 没有本质区别。真正让它升格为“Agent 项目”的,是中间新增的 Agent 编排层 。

这一层要做的事包括:

  • 接收用户输入的自然语言指令,解析背后的真实意图
  • 根据意图编排工具调用序列,决定先调什么、后调什么
  • 解析工具返回值,判断是否需要再次调用模型,还是直接产出最终结果
  • 把整个执行过程以结构化的方式推送给前端,让用户能看到“Agent 正在做什么”

这个编排层我最早是用纯 if-else 写的,后来发现可维护性太差,于是重构成了“ 策略模式 + 有限状态机 ”的结合体。关于这一块的踩坑过程,后面有专门的章节展开讲。

2.4 图像能力接入:我的工具调用设计

图像能力的接入,我采取的是“工具化”思路。每一个图像处理能力——分割、放大、调色——都被封装成一个独立的工具函数,定义好输入输出格式,然后交给 Agent 调用。这样设计的好处非常直接:Agent 不用关心某个工具内部是怎么实现的,它只需要知道“这个工具能干什么、需要什么参数、返回什么结果”。

借助本项目,我实际试过几个不同的图像处理服务商,最大的教训是: 做个本地 mock 服务做基于 OpenCV 的调色和格式转换,能帮你省下一大笔在线 API 调试时间 。我在项目初期就是在真实 API 上调工具协议,结果经常因为网络超时排查半天,后来全部改成本地 mock 服务,开发效率至少提升了一半,而且本地实现可以做很多工具的底层兜底,不会因为某个服务商的限流卡住整个链路。

3. Agent 核心机制拆解:意图解析、任务规划与工具调用

这一章是全文的“硬核区”,我尽量用最容易理解的方式讲透 Agent 工作流的三个核心环节。如果你对 Agent 的印象还停留在“聊天机器人”,那看完这一章,你应该能理解: Agent 和 Chatbot 最大的区别,是 Agent 能“动手干活”,而不仅仅是“动嘴说话” 。

3.1 第一步:意图解析——怎么让模型明白用户要“干什么”

这里我先用生活化的比喻解释一下。如果把 Agent 比作一个餐厅经理,用户的话就是顾客下的订单。订单上写的是“来点爽口的”,经理就得自己翻译成“拍黄瓜加冰镇酸梅汤”;订单上写“老样子”,经理还得结合历史订单来判断“老样子”到底是什么。

技术实现上,我使用的方案是“ LLM 函数调用(Function Calling)+ 结构化输出约束 ”。

我会在系统提示词里明确告诉模型:

  • 用户需求可能对应图片编辑、图像生成、信息查询、简单对话四类行为
  • 如果是编辑请求,必须提炼出操作类型、目标区域、风格参数
  • 如果信息不足,不能瞎猜,必须列出需要追问用户的问题清单

这个约束非常关键。没有结构化约束的模型输出,就像你让一个实习生去干活但没说清楚交付格式,他可能给你一段流畅的小作文,但你就是没法把它变成程序能识别的指令。

3.2 第二步:任务规划——把“大目标”拆解成“工具链”

意图解析之后,Agent 面临的核心挑战是: 得到一个意图不等于知道怎么执行 。比如用户说“把这个人的背景换成海边日落”,这里至少涉及两个图像操作——前景分割(识别人的轮廓)和背景合成(把新背景填进去)。

任务规划这个环节,本质上是一个决策问题: 当前请求,应该拆成哪些子任务?子任务之间是串行还是并行?

我采取的方案是“ 方案 + 子步骤 ”两段式结构:

  • 第一段:Agent 根据工具清单,描述将要采用的图像处理方案(这个方案会推送给前端展示,让用户了解 Agent 的计划)
  • 第二段:Agent 输出一个结构化的子步骤数组,每个子步骤包含:工具名称、工具参数、步骤描述

这个设计的妙处在于: 既保留了模型的“智能规划”能力,又把执行路径变得可预测、可调试 。如果某个步骤失败了,我可以精确定位到是哪个工具、什么参数导致的,而不是面对一大段模型输出一头雾水。

3.3 第三步:工具注册与调用——怎么让 Agent 能“驾驭”外部能力

在工具调用环节,我参考了业界常见的“工具注册表”模式。项目里有一个 tools/index.ts ,每个图像工具都遵循如下格式:

interface ToolDefinition {
  name: string;              // 工具唯一标识
  description: string;       // 给 LLM 看的工具能力描述
  parameters: JSONSchema;    // 参数格式约束
  executor: (params: any) => Promise<ToolResult>;
}

这里最难的部分不是写 executor,而是 给每个工具写 description 。因为 LLM 是凭借 description 来决定“什么时候用哪个工具”的,描述得太笼统,模型会乱选;描述得太长,又会浪费 token。我自己的经验是:要用“触发场景 + 能力边界 + 参数说明”三段式来写。例如抠图工具的 description:

当用户需要将图像中的人物、物体或主体与背景分离时使用。支持输入图像 URL 或 base64 字符串,返回透明背景的主体抠图结果。本工具处理的是语义分割任务,不是单纯的边缘检测;在调用前请确保已经从用户输入中提取了图像地址。

这样写完后,模型“用错工具”的概率明显下降。这也是一个通用经验: Agent 的质量,一半靠模型能力,另一半靠工具描述的工程化水平 。

3.4 状态管理:Agent 的“有限状态机”设计

Agent 的一次完整任务不是“一次请求 + 一次响应”这么简单。它更像是:用户发指令 → Agent 回方案 → 用户确认 → Agent 开始执行 → 工具返回中间结果 → Agent 判断是否继续 → 直到完成。

在这个过程中,任务状态不断变化。最开始我用一堆布尔值( isThinking 、 isDone 、 isError )来标记状态,结果代码越写越乱。后来重构为标准的 有限状态机(FSM) :

IDLE → PLANNING → CONFIRMING → EXECUTING → FINISHED
                            ↘ ERROR → RETRY → EXECUTING

这个状态的转换逻辑被统一封装在一个 TaskStateMachine 类里,前端收到状态变更就展示对应的 UI,后端收到状态事件就触发对应的动作。 状态机带来的最大好处,是任何时刻系统都处于一个合法状态,不会出现“既是执行中又是已完成”这种边界混乱。

3.5 与“无限制 AI”等热搜词的思考:为什么我没有追求“全功能”

在搜索项目相关资料的过程中,我注意到“无限制 AI”“无审核生成式 AI”这类关键词的热度很高。这里我想聊两句我的真实想法: 技术能力应该有边界感。“无限制”听起来美好,但如果你做的是面向公众的图像工具,一旦被恶意利用,后果是不可逆的。

所以这个项目里,我对图像输入做了基础的内容合规校验;对生成类工具做了输出内容过滤;用户上传的图像也会被临时存储且定时清理。我认为真正的技术能力,不只是“能不能实现”,更包括“敢不敢为后果负责”。这一点在任何内容生成型产品里都值得优先考虑。

4. 全栈链路实战:从 Agent 规划到图像处理落地的完整流程

理论说了很多,这一章我完整带你走一遍代码链路。我会以“给我这张图换个赛博朋克风格的背景”这个最经典的案例为线索,说明这条指令从用户输入到最终成图,中间到底发生了什么。

4.1 前端交互层:消息流与工具执行轨迹展示

前端的核心页面其实只有两个视图:左半部分是对话列表和状态流,右半部分是图像预览画布。

先说对话列表。用户的指令消息、Agent 的规划文案、工具执行状态卡片,全都以“流式消息”的形式平铺在时间线上。为了实现流畅的流式体验,我用的是 SSE(Server-Sent Events)——后端把 Agent 执行过程中的事件逐步推送到前端,前端按事件类型渲染不同的组件。

工具执行状态卡片是 UI 层最有“Agent 感”的地方。它会展示:

  • 当前正在执行的工具图标和名称
  • 执行的进度状态(等待中 / 执行中 / 完成 / 失败)
  • 工具返回的中间产物预览

这里有个产品细节想跟你分享: 千万不要把工具执行过程完全隐藏 。用户看到 Agent 在“先抠图再合成再调色”,会建立强烈的信任感,这种信任感是黑盒式处理给不了的。这也是 Agent 产品区别于传统 API 封装类产品的重要体验差异。

SSE 的核心订阅逻辑可以精简成这样:

const eventSource = new EventSource(`/api/tasks/${taskId}/events`);

eventSource.addEventListener('agent.status', (event) => {
  const data = JSON.parse(event.data);
  updateTaskState(data.taskState); // 更新任务状态机
});

eventSource.addEventListener('tool.executed', (event) => {
  const tool = JSON.parse(event.data);
  pushToolCard(tool);              // 新增工具执行卡片
  refreshPreview(tool.resultUrl);  // 刷新画布预览
});

4.2 后端 API 设计:任务驱动而不是请求驱动

传统 REST API 的设计是“一个请求对应一个响应”,但在 Agent 场景里,一次用户指令的执行时间可能长达几十秒甚至几分钟,这时候如果还用同步请求,会非常难受。我的设计是“ 任务驱动 ”:

  • 前端发起 POST /api/tasks ,创建图像编辑任务,拿到 taskId
  • 后端将这个任务丢进队列,开始异步执行 Agent 流水线
  • 前端通过 GET /api/tasks/{taskId}/events 建立 SSE 长连接
  • 全部工具执行完成后,后端推送 task.completed 事件,包含最终图像 URL

这个模式的本质,是把“执行过程”和“结果交付”解耦,也是全栈 AI 应用后端的通用标配。

4.3 工具层实战:以“背景替换”为例的完整调用链

背景替换这条链路在项目里算典型的三步串联,我在这里完整贴一下精简版的流程代码(去掉具体 API 密钥等敏感信息)。

第一步:调用分割工具,获取主体掩码

const segResult = await imageTools.segment({
  imageUrl: userUploadedImageUrl,
  target: 'person'
});

// segResult.maskUrl 是主体区域的透明底 PNG
// segResult.bounds 是主体在原图中的包围盒坐标

第二步:生成新背景图

const bgResult = await imageTools.generateBackground({
  prompt: 'cyberpunk city street at night, neon lights, rain reflections',
  aspectRatio: '16:9'
});

// bgResult.imageUrl 是生成的赛博朋克风格背景图

第三步:图像合成

const finalResult = await imageTools.composite({
  foregroundUrl: segResult.maskUrl,
  backgroundUrl: bgResult.imageUrl,
  position: { x: 0, y: 0 },
  scale: 1.0,
  blendMode: 'normal'
});

// finalResult.imageUrl 就是最终成图

这三个工具依次执行完毕后,后端把最终图片地址包装成任务结果推给前端。 可以看到的是:工具层完全不关心“为什么要做这三步”,它只提供原子能力;而 Agent 层完全不需要知道“合成算法是怎么实现的”,它只负责任务编排。这就是分层架构的意义——各司其职,互不干扰。

4.4 前端图像预览:从“上传原图”到“分阶段效果展示”

图像预览区的设计比看起来要复杂一些,核心难点在于“ 怎么展示中间状态 ”。

我实现了一个三栏对比组件:

  • 最左侧始终显示原始上传图
  • 中间栏展示“最近一次工具执行后的中间产物”
  • 最右侧展示“Agent 全部执行完的最终结果”

这个设计的用户体验非常好。用户能直观地看到“每一步操作带来了什么变化”,而不是从头到尾只有一个“转圈”然后突然出图。中间产物的加载我是用“对象临时存储 + 过期清理”策略实现的——每个中间图片保存 24 小时然后自动删除,防止存储空间被撑爆。

4.5 一个隐藏的难点:上下文保持与“多轮修改”

修图场景和对话场景还有一个显著区别: 用户经常会基于上一轮的编辑结果继续提要求 。比如:

  • 第一轮:“把背景换成海边日落”
  • 第二轮:“嗯…这背景有点太亮了,暗一点”

这就意味着 Agent 不能把每一次对话当成完全独立的请求。我的做法是: 每一轮任务结束时,把最终结果的图片 URL 和简短的编辑操作摘要存回对话上下文 ,下一轮任务启动时,这些信息会自动作为系统上下文的一部分传给 Agent。

这个设计让多轮修改成为可能,但也带来一个问题:上下文越来越长,Token 消耗不断增加。我的折中方案是只保留最近 5 轮的摘要,更早的历史记录只保留图片 URL 不保留操作细节。这个“摘要遗忘”策略让成本控制在一个合理范围内。

5. 避坑实录:全栈 Agent 项目里最容易踩的 7 个深坑

这个项目的开发周期大概是六周。说实话,前两周半的进度比我想象的顺利得多,但后面三周多几乎每天都在跟各种奇怪的问题搏斗。这一章我系统地复盘一下最典型的 7 个深坑,希望你能避开。

5.1 工具参数里的“图像格式”不一致,导致黑图

现象 :分割工具返回的是 PNG 透明底,但合成工具默认只接受 JPG,结果合成工具读图时出现黑色底块。

根因 :不同图像服务返回的图片格式不统一,而且很多工具对中国区访问的存储域名有自己的处理逻辑。我在系统提示词里没有把“统一输出为 PNG”作为公共约定,各个工具各自为政,格式混乱。

解法 :所有图像工具的输出在返回 Agent 之前,统一经过一层“标准格式转换中间件”,强制转成 PNG 并提供可访问的 URL。这一步看似多此一举,实际上省掉了大量后续兼容性排查的时间。

5.2 LLM 把 image_url 参数填成了辅助文本

现象 :用户上传了一张图,Agent 在规划时却把图片地址写成了“用户上传的图片在附件里”这段自然语言文本。

根因 :我对工具参数的描述不够强硬,模型以为图片可以在消息上下文里传递,就偷懒没填参数。

解法 :在系统上下文里加了一条硬性规则: 所有图像编辑类请求,必须先从用户消息中提取或确认 image_url 参数,该参数不得为空;如果未能提取到,必须追问用户提供图片链接 。同时,在工具参数的 JSON Schema 里把 imageUrl 标记为 required ,并且加上 pattern 校验必须是 http(s):// 开头。

5.3 SSE 连接频繁断开,任务执行到一半就断了

现象 :Agent 执行长任务时,SSE 连接经常在 1 分钟左右断开,前端拿到的事件不完整。

根因 :我一开始用默认的 HTTP 服务器配置,没有设置心跳保活,也没有处理代理服务器的空闲超时。很多云厂商的接入层对长连接的空闲时间有硬性限制,静默时间一长就会主动断开。

解法 :实现了两个机制:一是 SSE 服务端每隔 15 秒发送一次注释心跳包( data: ping\n\n );二是前端在断开时自动重连,并根据 Last-Event-ID 事件游标补齐缺失的事件区间。这个“断线续传”机制很关键,否则用户在弱网环境下根本没法完成多步骤修图任务。

5.4 Agent 规划出“不可能执行”的流程

现象 :用户说“把这张图放大两倍再添加文字”,Agent 规划出来的步骤居然是“先生成文字再放大”。这个顺序导致文字被放大后清晰度下降,效果很差。

根因 :模型对图像处理工具的依赖关系缺乏理解,规划时只是把工具按“提到顺序”排列,没有思考工具之间的顺序约束。

解法 :我在每个工具的 description 里增加了“前置依赖建议”字段。比如“添加文字”工具的描述里明确写: 建议在本工具之前完成所有几何变换(缩放、裁剪、旋转)类操作,否则文字会被拉伸变形 。同时,在 Agent 规划结果的后置校验环节,增加了一个简单的规则引擎,检测是否存在明显顺序冲突,如果发现就强制调整执行顺序。

5.5 前端大量图片加载导致内存溢出

现象 :任务执行到第 8、9 步时,浏览器开始明显变卡,最后直接崩溃。

根因 :每次工具执行都会返回一个新的中间结果图,前端为了对比方便,把所有图片都缓存在内存里没有释放,几百张 Base64 图片数据直接把标签页拖垮。

解法 :改用临时对象存储 URL 方式引用中间结果,前端只保留当前步骤和前一步骤两张图片的内存引用;同时加了一个纯前端的 LRU 缓存,超出限制就自动把最旧的图片从 createObjectURL 释放掉。

5.6 多轮对话中的上下文污染

现象 :用户第一轮说“换背景”,第二轮说“顺便把亮度调一下”。Agent 在第二轮居然把背景又换了一次,然后才调亮度。

根因 :对话历史里的工具结果和当前请求混在一起,模型无法区分“哪些是已经完成的历史操作”“哪些是当前请求新增的操作”。

解法 :在维护上下文时,把“历史任务摘要”和“当前请求目标”分开存放。系统提示词里明确告知模型: 当前轮只处理本轮提出的新增需求,历史已完成的编辑结果作为上下文参考,除非用户明确要求推翻重做,否则不要重复执行 。这个约束加上之后,多轮指令的准确率提升非常明显。

5.7 真实 API 的限流与故障:必须有降级方案

现象 :某图像服务商的免费额度用完,导致 Agent 流程中某一步直接 429 报错,整个任务中断。

根因 :我的编排层没有考虑“工具调用失败后的降级方案”,只要一个工具挂了,整个任务链就断了。

解法 :为关键工具增加了“降级策略”配置。比如分割服务失败,自动降级为“传统图像处理库的轮廓检测”兜底;背景生成失败,自动降级为“纯色渐变背景”。同时增加了“能力健康检测”模块,启动时探活所有工具端点,不健康的服务会自动从工具清单里摘除,这样 Agent 就不会规划出无法执行的流程。

6. 项目可观测性:Agent 应用调试,不能只靠 console.log

传统全栈项目调试,靠 console.log 和断点基本能应付。但 Agent 应用不一样—— 它的很多决策是模型自主做出的,日志里不打出来,你根本不知道模型为什么选了这个工具而不是另一个 。

这个项目做到中期,我开始认真给 Agent 执行链路加可观测性。具体做了三件事:

6.1 记录“模型原始输出”和“结构化输出”的对照

每次调用 LLM,我都会同时保存两份日志:一份是模型返回的原始 JSON/文本,另一份是经过 schema 校验和纠错后的结构化指令。很多诡异的行为,一对比这两份日志就能看出来是“模型输出本身就错了”还是“我的解析层把正确的输出搞坏了”。

6.2 任务执行轨迹的可视化

我把一次完整任务的所有事件(意图解析、任务规划、工具开始、工具结束、中间产物、失败重试)按时间线串起来,渲染成一个“任务执行轨迹图”。排查问题的时候,直接看轨迹图就能快速定位到“哪一步耗时异常”“哪一步输入输出不匹配”。这个功能也是我自己最满意的一个部分, 做 Agent 产品,给自己的 Agent 做一套“行车记录仪”,绝对是值得的投资 。

6.3 工具调用的成本审计

每次 LLM 调用和图像 API 调用都会记录 token 数、耗时和费用。项目后期优化成本时,这张表帮了大忙——我能一眼看出哪些环节过度消耗 token,然后针对性地压缩上下文和优化提示词。做 AI 应用, 成本意识要前置 ,不然上线后用户一多,账单会让你怀疑人生。

7. 项目复盘与进阶路线:从“修图 Agent”到“通用任务型 Agent”

项目完结后,我花了几天时间做了一次比较彻底的复盘。这里想把最后沉淀下来的思考和下一步计划也一并写出来。

7.1 这个项目验证了什么

首先,它验证了“ LLM + 工具调用 + 状态机 ”这个组合在图像处理领域是实际可行的。Agent 完全能够通过策略性调用不同的图像工具,完成复杂的修图任务,而且用户体验比传统“按钮式修图”要自然得多。其次,它也验证了一个全栈 AI 工程师的核心能力不是“会调 API”,而是“能把模型能力和工程架构丝滑地结合起来”——这包括工具描述工程、状态流转设计、降级容灾策略,这些能力在传统全栈开发里是学不到的。

7.2 这个项目暴露了什么短板

目前的 Agent 其实还比较“脆”。第一个短板是 对边缘案例的处理能力不足 :如果用户上传的图片质量极低、主体不清晰,分割工具和后续步骤的质量都会明显下降,Agent 自己也很难察觉并主动提示用户。第二个短板是 不支持自定义工具扩展 :目前工具清单是写死的,后续如果想让第三方开发者也能上传自己的图像处理能力,还需要改造工具注册表并增加更多安全校验。第三个短板是 缺少更细粒度的用户反馈收集 :Agent 执行完成后,只能靠最终成图效果来判断结果好坏,缺少对中间步骤的满意度反馈采集,这会影响后续优化方向的判断。

7.3 下一步的可行性计划

基于复盘的结论,我规划了几个明确的优化方向:

  1. 引入“质量评估器” :增加一个独立的评价模型,在最终结果交付给用户之前,对成图质量做一个自动打分和基础检查,低分结果自动触发重新规划或提示用户补充描述。
  2. 开放工具注册机制 :把工具定义从硬编码改为可配置的 JSON 描述 + 可插拔的执行函数,这样不修改核心代码也能新增工具能力。
  3. 更精细的 Agent 记忆管理 :引入向量数据库保存历史编辑偏好,例如用户长期偏好高饱和度风格、暖色调等,新任务启动时可以自动把这些偏好加入上下文。
  4. 移动端适配 :目前前端布局以桌面为主,但实际使用场景里“手机上传照片”的需求占比很高,移动端的交互重构会是下一个短期目标。

8. 最后分享一个我反复用到的“Agent 系统提示词”模板

文章的最后,我直接把我在项目里反复打磨过的一份“Agent 图像编辑系统提示词模板”分享出来。它没有绑定某个具体模型,直接把规则抽象成了四层。几乎任何修图类 Agent 场景都能套用,你在设计自己的 Agent 系统时也可以直接修改使用。

8.1 第一层:角色设定与能力边界

你是一名专业且经验丰富的图像编辑 Agent。你的目标是理解用户的图像处理需求,
通过规划和调用图像编辑工具来完成用户的指令。

你有能力执行以下类型的图像操作:主体分割、背景生成、图像合成、色彩调整、
智能裁剪、图像增强、文字添加、尺寸调整。

你不需要向用户解释工具内部的工作原理。你只需要根据用户指令规划并执行正确的工具链。
当用户的需求在你的能力范围之外,或者信息不足以执行时,请明确告知用户。

8.2 第二层:工作流程约束

你处理每一次图像编辑请求,都将严格按照以下流程进行:
1. 分析用户的请求,提取关键信息:输入图像地址、编辑目标、风格参数
2. 检查信息完整性,如果缺少必要信息,列出需要追问的问题,不要猜测
3. 规划工具调用链,输出执行方案,等待用户确认后执行
4. 执行过程中,实时汇报当前正在执行的工具与进度
5. 全部工具执行完成后,汇总最终结果,简要说明完成情况

如果用户是基于历史结果的二次修改,只处理本轮新提出的需求,
除非用户明确要求推翻重做,否则不要重复执行历史步骤。

8.3 第三层:工具使用选择策略

当你有多个工具可选择时,按以下原则决策:
- 如果用户指令是单一操作,直接使用对应工具
- 如果用户指令包含多个操作,先做几何变换(缩放、裁剪),再做像素级变换(调色、增强),最后做合成类操作
- 当“达到相同效果有多条路径”时,选择步骤最少、成本最低的路径
- 如果某一步失败,优先考虑降级方案,是否需要调整后续步骤或者重新规划

8.4 第四层:输出格式要求

你的所有结构化输出必须遵循 JSON 格式,包含字段:
- action: 本次操作的类型标识
- plan: 自然语言描述的步骤计划,需向用户展示
- steps: 结构化工具调用链数组,每个元素包含工具名、参数、说明
- needMoreInfo: 如果需要向用户提问,此处必须是 true,否则 false
- questions: needMoreInfo 为 true 时,此处列出需要追问的问题列表

JSON 之外的任何额外文字,仅用于对话说明,不作为程序执行指令。

这份模板的设计意图很清晰: 它既约束了模型的行为边界,又给了足够的自由度去处理多种多样的用户指令 。我在不同的模型上试过,效果都比我预想的要好,也大大减少了“模型乱来”的概率。我给这个模板命名为“四层约束法”,如果你正在设计自己的 Agent 系统,不妨按这个思路试一下。


其实写到这里,这个项目的意义已经超出了“完成了多少个功能”的范畴。它真正让我体会到了全栈 AI 应用开发的独特节奏: 一边要理解模型能力的边界,一边要像传统工程一样保证系统的稳定性和可维护性,中间还要兼顾用户体验的细腻感 。这三条线交织在一起,就是 Agent 应用和传统应用最大的不同,也是最迷人的地方。希望这篇长文能给你带来一些实际的启发。如果在做的过程中有任何问题,欢迎在评论区聊聊你的方案和踩坑经历。

更多推荐