
这个项目完结的时候其实比我预想的要平静不少。回想接手那会儿全栈AI修图Agent这几个词叠在一起确实让很多人觉得是个花架子——AI修图就修图Agent就Agent全栈就全栈串起来是不是噱头大于实际但真正做完之后我最深的感受是这个组合并不花哨反而是当前做工具类产品非常务实的一套打法。整个项目覆盖了Vue3 Golang Uniapp三条技术栈把大模型能力、图像处理流程和跨端交互捏合成一个完整的可落地产品。这篇就来复盘一下我从项目立项到完结是怎么拆解需求、选型技术、设计Agent调度机制以及最终踩坑填坑的全过程。想从头复刻这个项目的朋友不管你是前端想转全栈还是后端想补AI应用层或者纯粹对Agent这个词的实际落地方式感兴趣这篇内容都值得认真看。我会尽量把思路、代码结构、关键实现的取舍逻辑都讲透不整虚的。1. 项目做完了先聊聊这到底是个什么东西1.1 核心需求解析用户要的其实不是修图立项之初产品侧给的需求很简单做一个能听懂人话的修图工具。但一句话需求往往是坑最深的因为听懂人话和修图两个词背后藏着完全不同的技术栈。真拆开之后需求变成了三个层次第一层用户希望用自然语言描述修图诉求比如把天空调蓝一点去掉照片里的路人把这张图变成动漫风格。这本质上是意图识别和指令解析问题属于大模型应用层。第二层系统需要真正执行图片处理操作。这里面既有传统的像素级处理调色、裁剪、抠图也有生成式AI的重绘能力动漫化、扩图、去水印。这层是整个项目里最重的部分直接决定了服务质量。第三层用户不在电脑前也能用。需求评审时明确要求支持手机端而且要跟PC端体验对齐。这就引出了多端适配和技术栈选型的问题。一句话总结这不是一个AI修图工具而是一个用自然语言驱动修图能力的Agent系统。用户感知到的是聊天界面图片前后对比但背后需要大模型做语义理解、工具调度引擎做任务分发、图像服务做实际处理三层各司其职。厘清这一点后面所有的架构设计和技术选型才有的放矢。1.2 为什么选Agent架构而不是传统对话框最早的产品雏形其实特别简单粗暴用户在输入框敲一句话后端直接把这整句话丢给大模型让模型生成一张图。试过就知道这条路根本走不通。大模型生成图片的随机性太强用户说把天空调蓝一点模型可能直接给你画一张完全不同的图。而且传统聊天方式对修图这种高精度操作毫无控制力中间没办法介入、调整、回退。最后落地的是Agent架构核心思路是把修图动作拆成一个个可调用的工具Tool大模型只负责理解用户意图然后把意图翻译成对特定工具的调用参数具体的图像处理交给专门的工具去完成。也就是说大模型从亲手干活的人变成了听懂需求的调度员。这种架构最大的好处是可控性强每个修图操作都有确定性的执行逻辑大模型只做决策不碰像素。同时扩展性也高新增一种修图能力只需写一个工具函数登记进去不需要改动主流程。后面项目进入维护期后我们陆陆续续加了七八个新工具基本做到了不改主体代码只加插件这就是Agent模式对比大模型一把梭的核心红利。1.3 项目实际产出的功能范围整个项目完结时功能范围比最初PRD多了不少但也砍了不少最终落地的核心模块有这些智能修图对话支持自然语言描述修图诉求Agent自动解析并执行支持连续对话和多轮修正。基础图像工具集调色亮度、对比度、饱和度、色温、裁剪/旋转/镜像、尺寸缩放与压缩、图片格式转换。AI增强工具集一键抠图/去背景、图片分辨率提升超分、老照片修复上色、动漫风格化、背景替换、去水印/修复。修图任务管理历史任务记录、处理进度查询、结果对比预览支持任务取消和失败重试。多端适配Web端、H5端、微信小程序端、App端统一交互逻辑和视觉风格。还有一批被砍掉的内容比如视频一键风格化、批量处理工作台。砍的原因很现实视频处理对算力要求高、单次任务时间长在现有并发模型下容易拖垮整个服务。这个取舍后面细说。2. 技术选型的完整思路为什么是Vue3 Golang Uniapp2.1 前端为什么押注Vue3而不是React技术选型往往不是选最好的而是选团队上手成本最低、生态最成熟的。我们团队前端主力栈一直是Vue所以Vue3是天然选择但仔细想Vue3也确实匹配这个项目。项目的Web端是一个中后台偏工具属性的界面左侧对话列表、中间图片编辑区、右侧参数面板这种信息密度偏高的交互场景Vue3的组合式API写起来非常顺手。Composition API对逻辑复用的设计比Vue2时代好了太多我们封装了一个useAgentChat的hook把对话状态、工具调用回执、任务轮询三块逻辑全部复用到了Web端和Uniapp端的核心页面里。组合式函数大概是这样的思路// composables/useAgentChat.ts export function useAgentChat() { const messages refChatMessage[]([]) const isProcessing ref(false) async function sendMessage(content: string, imageBase64?: string) { isProcessing.value true try { const sessionId getCurrentSession() const res await api.chat.send({ content, imageBase64, sessionId }) // 这里拿到的是Agent执行后的最终回复消息 messages.value.push(normalizeMessage(res.data)) } finally { isProcessing.value false } } return { messages, sendMessage, isProcessing } }这个设计让核心业务逻辑与平台完全解耦后面Uniapp端直接复用同一套组合式函数只是API调用的实现方式略有不同。这类跨端复用能力Vue3的生态支持是比较成熟的。2.2 后端为什么选Golang并发只是表面原因后端技术栈在Golang和Node.js之间犹豫过一段时间。Node.js的优势是前端团队无缝切换而且生态里图像处理库也很丰富。但最终选Go关键是考虑到后面要接的Agent任务调度机制。修图Agent有一个非常典型的特征大量任务需要并行处理。用户可能同一秒内发起多个不同类型的修图请求有的走GPU服务有的走CPU算法有的只是简单的像素操作它们的耗时差异巨大。Go的goroutine模型可以很优雅地解决这种高并发场景下的任务编排问题每个请求一个goroutine官方库加上channel做通信写起来非常自然性能和代码复杂度都能接受。另外一个加分项是Go单二进制部署的特点。项目交付时需要在多台服务器上部署推理服务、后端服务、前端静态资源Go编译出来的二进制文件扔上去就能跑省掉了一堆环境配置脚本。对中小型项目来说这省掉的时间成本相当可观。2.3 Uniapp的多端适配策略多端这块我们选了Uniapp而不是React Native或者纯原生。理由很务实项目需要覆盖H5、微信小程序和App三个端Uniapp是当前国内生态里对这三端覆盖最均衡的方案而且团队已经有大半年的Uniapp使用经验踩坑成本低。真用下来最需要注意的坑在于Uniapp的编译是按端裁剪的条件编译一定要规划好。我们代码里大量使用了条件编译指令来区分不同端的API调用比如图片上传在小程序端要走wx.uploadFile在App端要用uni.uploadFileH5端直接走fetch。这些逻辑如果混在一起后面排查问题会非常痛苦。App端的图像预览组件我们也换成了原生插件方案因为Uniapp自带的图片预览在处理大尺寸、高分辨率的修图结果图时存在明显的内存占用问题在低端安卓机上直接白屏。原生插件虽然增加了一部分包体积但体验稳定很多这个取舍后面细说。2.4 AI能力接入与模型服务选型AI修图的核心能力来自几个方向意图识别和工具调用决策用的是LLM API具体说是大模型对话接口配合function calling能力。图像生成类能力动漫化、背景替换、扩图使用了图像生成模型API。图像理解类能力判断图片内容、识别主体用了多模态理解模型的视觉接口。传统图像处理能力调色、裁剪、压缩使用自研的Go图像处理库完成不依赖外部API。这里有一个核心选型逻辑能用确定性算法解决的绝不用AIAI只用于理解和生成处理交给算法。一方面是因为传统算法处理速度极快几毫秒就能完成调色任务而AI生成动辄好几秒另一方面是成本考量AI API调用按次数计费如果用大模型来完成把图片亮度提升20%这种操作性价比极低。模型服务的具体品牌不展开说了国内外的都有试过最终同时接了不止一家的API做容灾切换。实测下来各家的function calling能力差异较大有的对工具参数的格式要求比较死板有的则会把对话历史里的噪声带进工具调用这里花了不少时间做Prompt调优和寄出校验。3. Agent机制是怎么把一句话变成一个修图动作的3.1 意图识别与工具调用的核心链路整个Agent系统的核心链路可以用这样一条线来概括用户输入文本可能附图片 → 调用LLM做意图理解 → LLM返回工具调用指令 → 系统执行指令调用具体修图工具 → 拿到处理结果 → 调用LLM生成用户可读的回复 → 前端展示前后对比图。这个过程里最关键的环节是LLM与工具之间的协议设计。我们采用的做法是为每个修图工具定义一个JSON Schema格式的函数描述包含工具名、功能说明、参数列表、参数类型和约束条件。在每次调用LLM时把这些函数描述拼接到Prompt上下文中让模型根据用户输入决定调用哪个函数、传什么参数。大致长这样{ name: adjust_brightness, description: 调整图片亮度, parameters: { type: object, properties: { brightness: { type: integer, description: 亮度调整值范围-100到100正数变亮负数变暗, minimum: -100, maximum: 100 }, image_id: { type: string, description: 要处理的图片ID } }, required: [brightness, image_id] } }模型返回的tool_call里会包含函数名和参数后端收到后做两层校验第一层是JSON结构校验确保参数类型和范围合法第二层是业务语义校验比如变暗和变亮的方向是否正确、目标图片是否存在。校验通过后才真正进入图像处理阶段。3.2 工具注册表与动态插件机制为了让Agent系统真正具备扩展性我们没有把工具调用逻辑写死在业务代码里而是设计了一个工具注册表。所有可被Agent调用的工具都在系统启动时注册到一个全局Map里key是工具名value是工具实现和Schema描述。核心结构大致如下type ToolHandler func(ctx context.Context, params map[string]interface{}, imageData []byte) ([]byte, error) type Tool struct { Name string Description string Schema map[string]interface{} Handler ToolHandler } var toolRegistry map[string]Tool{} func RegisterTool(t Tool) { toolRegistry[t.Name] t } func GetToolsForPrompt() []map[string]interface{} { schemas : make([]map[string]interface{}, 0, len(toolRegistry)) for name, t : range toolRegistry { schemas append(schemas, map[string]interface{}{ type: function, function: map[string]interface{}{ name: name, description: t.Description, parameters: t.Schema, }, }) } return schemas }这种设计带来的直接好处是新增一个修图功能不需要改动Agent调度主流程只需要写工具实现、定义参数Schema、调用RegisterTool注册。项目后期我们新增了天空替换人像美颜等工具都是二十分钟搞定注册主流程一行没动。但这里也有一个必须强调的坑工具多了之后LLM的工具选择准确率会下降。当工具数量超过15个时模型偶尔会拿错参数或者误选功能相似的工具。我们的解决思路是给相似工具加更清晰的description区分并且在Prompt里增加如果用户没有明确指定优先推荐xx工具这类引导语。后来实测工具选择准确率从92%提升到了98%左右效果很显著。3.3 图像处理任务的并行调度与队列设计修图任务有一个显著特点耗时差异极大。调色任务可能几十毫秒就完成了但动漫化或者超分任务可能要10秒甚至更久。如果所有任务都排在同一个队列里快速任务会被慢任务阻塞用户体验会非常差。最终的调度方案是分组队列加并发控制。具体来说把工具按预计耗时分成两组快路径调色、裁剪、旋转、压缩、格式转换等CPU轻量操作同步处理直接在当前HTTP请求中返回。慢路径动漫化、超分、老照片修复、抠图等AI生成类操作异步处理通过任务队列分发前端轮询任务状态。Go语言的goroutine channel在这里派上了大用场。每个异步任务提交时会被封装为一个Task结构包含任务ID、图片ID、工具名、参数、创建时间投递到带缓冲的channel中。后端启动固定数量的worker协程从channel中消费任务每个worker处理完再通过回调更新任务状态。一个关键的设计决策是给慢路径任务设置了并发上限。当初上线后不久就遇到AI服务被瞬时打满的情况——用户短时间提交大量动漫化请求外部图像服务API直接限流报错。后来加了一个基于令牌桶的限流器规定AI生成类任务每秒钟最多提交N个超过的排队等待。这个限流值是根据外部服务商给的并发配额和平均单任务耗时计算出来的。伪代码大概是type TaskQueue struct { ch chan *Task limiter *rate.Limiter workerNum int } func (q *TaskQueue) Submit(t *Task) error { if !q.limiter.Allow() { return ErrTaskRateLimited } q.ch - t return nil } func (q *TaskQueue) StartWorkers(ctx context.Context) { for i : 0; i q.workerNum; i { go func() { for { select { case task : -q.ch: processTask(ctx, task) case -ctx.Done(): return } } }() } }3.4 前端交互与后端状态同步前端最核心的交互是对话即操作。用户发消息、Agent回复但这个回复不是简单的文本而是一个包含工具调用过程、任务状态、处理结果、图片对比等多类型消息的复合对象。前端需要有能力渲染不同类型的消息块。消息数据结构大概是这样interface ChatMessage { id: string role: user | assistant type: text | image_result | tool_call content: string image?: { originalUrl: string processedUrl: string status: processing | success | failed } toolInfo?: { name: string status: running | success | failed duration?: number } createdAt: number }前端会根据消息类型渲染不同的UI组件tool_call类型展示工具执行状态比如正在调用动漫化工具...加一个进度动画image_result类型展示前后对比图。为了让任务轮询更高效前端统一使用一个自研的useTaskPoller组合式函数所有异步任务都在这个函数里统一管理轮询间隔、超时处理和错误重试。轮询间隔会动态调整任务刚提交时2秒轮询一次超过30秒后变成5秒一次减少无意义的HTTP请求。4. 实操过程与核心环节实现细节4.1 Web端实机操作的完整流程Web端是开发效率最高的端也是我们最先完成功能验证的端完整的用户操作流程是这样的用户打开首页会看到三个区域左侧是历史会话记录中间是对话区右侧是预览/上传区。首次进入没有历史会话界面上会有一个默认的上传引导——用户可以直接拖拽图片到中间区域或者点击上传按钮选择本地图片。上传图片后图片会显示在右侧预览区对话区自动插入一条用户消息附带图片缩略图。这时候用户直接打字说把这张图调亮一点前端将文本图片ID封装为消息体发送到后端。Agent调度后返回任务接受回执前端根据回执类型渲染工具调用状态然后开启轮询。任务完成后后端推动消息更新前端收到处理后图片的URL在右侧对比区展示原图和效果图中间有一个可拖动的分割条可以直观看到修改前后的差异。实机操作时有一个体验细节非常影响观感慢任务的状态反馈。如果用户提交了动漫化请求前端在长达几秒甚至十几秒的时间里必须给用户明确的正在处理反馈否则用户会以为系统挂了。我们的做法是设计了一套任务动画并配合语音级别的文案提示AI正在理解你的需求正在调用图像生成模型大约需要8秒。后来看数据用户在这个阶段的跳出率确实明显下降了。4.2 Uniapp端条件编译与跨端适配实战Uniapp端是整个项目里工作量比较容易被低估的部分。看似H5能跑的业务逻辑直接复用即可但实际上从Web端迁到小程序和App端时踩了一堆平台差异的坑。最典型的是图片上传。Web端用input typefile和FormData小程序用wx.uploadFileApp端又有自己的uni.uploadFile三者API不通用。最终我们封装了一个统一的uploadImage方法内部用条件编译分支async function uploadImage(filePath: string): PromiseUploadResult { // #ifdef H5 const formData new FormData() formData.append(file, filePath) const res await fetch(/api/upload, { method: POST, body: formData }) return await res.json() // #endif // #ifdef MP-WEIXIN return new Promise((resolve, reject) { wx.uploadFile({ url: /api/upload, filePath, name: file, success: (res) resolve(JSON.parse(res.data)), fail: reject, }) }) // #endif // #ifdef APP-PLUS // 使用uni.uploadFile实现App端上传 // #endif }另一个大坑是图片预览。修图结果图通常是大分辨率原图和结果图对比Web端用原生img标签加载完全没问题但小程序端的image组件渲染超大图时会明显卡顿App端也出现过多张图片同时展示导致的内存暴涨。我们最终的方案是在后端生成结果图时额外生成一张宽边不超过1200px的压缩预览图。前端对比展示永远用的是预览图点击查看原图时才加载完整分辨率图片。这个看似简单的策略节省了大量前端内存和网络带宽。4.3 后端关键服务的代码实现思路后端服务按功能模块划分为四个主要部分API网关层负责HTTP路由、鉴权、请求参数校验、统一错误处理。Agent调度层负责LLM调用、工具注册、工具调用决策、Prompt管理。任务执行层负责图像工具的执行、任务队列管理、异步任务调度、状态更新。数据存储层负责会话记录、图片元信息、任务记录、用户数据的持久化。Agent调度层的核心逻辑是processAgentLoop它实现了标准的Agent运行循环func processAgentLoop(ctx context.Context, sessionID string, userMessage string, imageData []byte) (*AgentResult, error) { // 1. 组装消息历史 history : loadSessionHistory(sessionID) history append(history, NewUserMessage(userMessage, imageData)) // 2. 调用LLM做意图理解和工具选择 resp, err : callLLMWithTools(ctx, history, GetToolsForPrompt()) if err ! nil { return nil, err } // 3. 如果模型决定调用工具 if resp.ToolCall ! nil { toolName : resp.ToolCall.Name toolParams : resp.ToolCall.Arguments // 4. 执行工具 resultData, err : executeTool(ctx, toolName, toolParams, imageData) if err ! nil { return nil, err } // 5. 将工具执行结果作为新消息返回给LLM让LLM生成用户可读的回复 history append(history, NewToolResultMessage(toolName, resultData)) finalResp, err : callLLM(ctx, history) if err ! nil { return nil, err } return NewAgentResult(finalResp.Content, resultData), nil } // 6. 模型直接返回文本简单对话场景 return NewAgentResult(resp.Content, nil), nil }这里比较核心的决策是要不要让LLM二次生成回复。最初的方案是工具执行完成直接返回处理结果不给用户任何文本解释。后来发现体验很差用户不知道这个结果是怎么来的、图片发生了什么变化。于是加了第二步把工具执行结果喂回LLM让它生成一句人话说明比如已完成图片柔焦处理背景变得更加简洁主体突出度提升明显。这种体验在用户感知上好了不止一个量级。4.4 存储模型与文件处理方案修图项目涉及大量图片文件的存取。一开始我们用本地磁盘存储但服务器磁盘很快就满了而且图片处理中间过程会产生多个版本占用空间成倍增长。后来迁移到OSS存储后端只保存文件路径和元信息上传和下载都走对象存储的直传链接。数据库选型上用了MySQL加Redis的组合。MySQL存储会话、消息、图片元信息、任务记录Redis存储实时任务状态和频控计数。核心表结构里messages表是最重要的它记录了每个会话的完整消息链包括用户消息、AI回复、工具调用记录、图片处理前后的URL。后续做历史任务重新处理功能时靠的就是这些完整的记录。图片处理的中间文件我们也做了定期清理机制超过7天的中间版本自动删除只保留最终结果图。这样既不浪费存储空间也不影响用户查看历史记录。4.5 关键性能指标的实测数据项目完结前我们对服务做了压测和体验调优。核心指标记录如下快路径任务调色/裁剪/压缩P95耗时约180ms基本无感。慢路径任务动漫化/超粉P50耗时约4.6秒P95约12.3秒。服务并发能力单个后端实例可同时处理约200个HTTP请求和15个AI生成类异步任务。工具选择准确率使用调优后的Prompt和函数描述内部测试集上约为98.2%。端到端成功率从用户提交任务到最终结果展示的成功率约为99.1%剩余失败主要集中在外部AI服务超时。这套指标为后续优化提供了基线。比如慢路径任务占整体任务比例不算高但耗时占比极高目前正在考虑对这类任务增加极速/普通两级通道用户可以选择排队更短但费用更高的处理方式。5. 常见问题与排查技巧实录5.1 大模型工具选择错乱问题这是开发过程中遇到的最诡异的一个问题。当工具数量增加到10个以上后模型偶尔会出现明明用户说的是调色模型却调用了动漫化工具的情况而且广播电视没有固定规律。排查过程挺曲折。先怀疑是函数描述不够清晰于是把每个工具的description改得更细加了更多区分度然后怀疑是Prompt上下文太长工具描述被稀释了于是把工具描述从一次性全量注入改成先让模型分类再根据分类加载对应工具描述。后者效果最明显工具选择准确率大幅上升。最终方案是做了一个简易的工具分组机制把工具分为基础编辑组AI增强组人像美化组三个分组模型先判断用户意图属于哪个分组然后只加载这个分组内的工具描述。这个策略有效隔离了相似工具之间的干扰也减少了Prompt长度间接降低了调用成本。5.2 慢任务阻塞快任务的处理方案刚开始设计任务队列时图省事所有任务进入同一个队列worker同一时间只能处理一个任务。结果上线没几天就收到用户投诉调个亮度竟然等了8秒钟。排查发现队列前面排了好几个动漫化任务每个要跑10秒快任务活活被堵在队列里。修复方案就是我前面提到的分组队列快任务同步执行直接在当前请求里跑完返回慢任务走异步队列并发数单独限制。这样调色、裁剪基本不会超过200ms用户体验恢复到了秒开级别。这个改动看起来简单但对产品留存的影响是决定性的。5.3 大图上传导致的超时与内存问题测试阶段发现高清相机原图部分高达几十MB在上传接口时会出现超时后端处理时偶尔也会内存溢出。问题出在两个地方一是网络上传速度慢一是图像解码到内存后占用巨大。解决思路是前端在上传前先做本地压缩。Uniapp端用uni.compressImageWeb端用Canvas重绘统一把上传图片压缩到宽边不大于2048px、质量80%的JPG。后端在接收到图片后先按需缩放成多种尺寸小尺寸用于快速预览原始尺寸留作处理。这样上传体积下降了约70%后端内存压力大幅减少。5.4 对话历史无限膨胀问题Agent系统每轮对话都要带上历史消息一开始存储时不做任何裁剪结果越往后Prompt越长单次调用费用越高响应速度越来越慢。后来加了两个裁剪策略按tokens裁剪系统记录每条消息的token数总token超过阈值时抛弃最久远的非关键消息保留最后N条。按对话轮数裁剪最多保留最近10轮对话内容超过后自动摘要压缩用摘要替换早期完整对话。这两个策略结合后LLM调用的token消耗下降了大约40%响应速度有明显提升而用户基本感知不到历史内容被裁剪。5.5 前端任务状态丢失的问题Web端开发时发现一个情况用户提交任务后如果刷新了页面正在处理的任务状态就找不到了用户以为任务失败重新提交造成重复请求。排查发现是前端只把任务轮询状态存在内存里刷新即丢失。修复方案是让后端任务状态与前端消息绑定前端初始化时根据消息ID恢复所有未完成任务的状态。这个改动涉及前后端联动工作量不小但完成后整个刷新不丢任务的体验确实是专业级的提升。6. 工具选型对比与配置参考6.1 图像处理中端技术实现对比做AI修图Agent最核心的选型纠结集中在图像处理部分。我们前期对比过几种方案直接调用商业图像处理API效果最好接入最简单但成本高单次调用费用在几毛到几块钱不等而且受外部服务稳定性限制。开源图像算法模型自部署可控性强批量处理成本低但需要GPU服务器部署和运维成本高且效果未必跟得上商业API。自研传统算法适合调色、裁剪等基础功能速度快且免费但无法完成AI生成类需求。最终采用混合方案基础功能自研AI功能走商业化API同时预留了自部署模型接口等业务量达到一定程度再考虑切换到自部署以降本。这套方案在初期成本可控、快速上线后期又有优化空间。6.2 多端框架Uniapp与传统原生开发选型复盘选Uniapp的利弊项目完结后其实有更清醒的认知。用Uniapp的好处是明显的一码多端但码不是零成本复用的多端也不是零配置的。每个平台都有自己的规则和API差异条件编译代码写多了以后代码的可读性其实是在下降的。如果让我重新选一次我的决策逻辑会是这样如果目标端以微信小程序为主App只是附带Uniapp依然是首选如果目标是两个以上原生体验要求较高的App可能要考虑Flutter或RN如果只做Web和H5直接Vue3单项目更清爽。但说实话作为全栈项目Uniapp带来的跨端实践经验是非常宝贵的。对新人来说用Uniapp练手多端开发成本很低能让学生在有限时间内接触到同一业务逻辑在不同端上的差异化适配这个所有大型项目都会遇到的核心问题。7. 从项目完结后得到的经验与反思这个项目做完后我最大的感触是AI应用项目的难点往往不在AI本身。大模型API的开箱即用程度已经很高调一个对话接口根本不是问题真正磨人的是工程化。比如工具注册表的设计怎么才能做到扩展性最优、异步任务队列怎么调度才能在成本和体验之间取平衡、函数描述的写法怎么才能提高工具选择准确率、多端条件编译怎么组织才不至于变成维护噩梦——这些都是即时满足之外的笨功夫。另外我特别想强调的是控制幻觉的问题。大模型的输出天然带有不确定性但在工具调用场景里这种不确定性是致命的。如果LLM把brightness: -50解析成了brightness: 50用户会把好好的图调成大白天。所以在整个Agent调度链路上我们坚持了两条铁律第一模型的决策必须是可校验的。每个工具参数在后端都要过一遍强校验逻辑类型不对、范围超限、方向存疑的调用直接拒绝宁可让用户重新确认也不能把错误操作落到图片上。第二模型的输出必须是可解释的。每次工具调用都会生成一条带有完整状态记录的消息用户可以清楚看到Agent做了什么、为什么这么做、效果如何。这种透明度不仅让产品显专业也为后续调试和优化提供了宝贵的日志数据。现在再看又一个新项目完结这句话其实背后不只是代码层面的完结更是对AI应用开发理念的一次系统整理。AI修图Agent是个很好的练手对象它体量适中既包含传统全栈开发的所有要素又牵扯到大模型应用层的意图理解、工具调用、异步任务调度这些新问题。如果你们团队正在考虑做什么全栈项目练手这个方向值得一试。但记住别被AI两个字带偏了沉下心来把工程问题一个个解决果实自然会有。