ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Go构建AI Agent流水线:批量生成淘宝详情页的工程实践

Go构建AI Agent流水线:批量生成淘宝详情页的工程实践 上个月接了个活儿把一批商品图变成淘宝详情页。不巧的是这不是一两张而是 30 个 SKU每个都要做 6-8 屏图文详情美工排期排到两周后。等是不可能等的于是我就想能不能让 AI 先把草稿全部做出来我再在草稿上改这个念头最后变成了一条用 Go 写的 AI Agent 流水线输入一张商品图输出一整套可以直接发布的淘宝详情页。从第一版跑通到稳定运行前后大概花了两周。这篇文章把整条流水线的设计思路、关键实现和踩坑记录完整梳理一遍。适合两类人看一类是电商或供应链侧的技术同学想用 AI 批量生产商品素材另一类是正在用 Go 做 AI 应用的开发者想看看在 Go 生态里编排 LLM 任务、控制并发、做数据校验的工程套路。文章不假设你之前写过 Agent但如果你了解 Go 基本语法和 HTTP 调用会看得更顺。为什么没直接去用现成的 Agent 工作流平台我在写之前也试过 Dify 这类可视化流水线工具搭一条收到图片→输出文案的演示链路确实快但很快就碰到两个问题一是平台里的节点在做文字拼接、循环、条件分支时不够灵活二是我想把这套能力嵌进已有的 Go 服务里让它变成一个内部 API而不是一个在网页上拖拽的工作流。所以最后决定自己写这才有了这篇 Go 版的流水线拆解。1. 从1 张图到整套详情页这个需求到底拆出了什么1.1 一张详情页背后其实是 5 类内容先说需求拆解。一张商品图丢给 AIAI 要产出什么如果我一开始就把任务定义成生成详情页模型大概率会给你一段不像样的话。我把详情页拆成了 5 类内容主图文案3-5 条短文案配合首屏图使用卖点模块3-6 个卖点每个都要有标题和一两句解释规格参数表从图中提取型号、材质、尺寸、颜色等结构化数据场景化文案适合什么场景、解决什么问题引导转化文案优惠、搭配、售后提示这个拆分非常关键。LLM 擅长的是理解一段输入然后按指令输出如果你把输出格式拆成一个个明确的模块质量会稳定很多。反过来如果你让 LLM自由发挥一整个页面它写出来的东西会非常散。我在第一版就吃过这个亏生成的详情页文案东一句西一句像是从几篇不同文章里拼出来的。拆分之后每个模块的职责清晰模型反而更容易写出有重点的内容。1.2 为什么是Agent而不是固定脚本这里要先明确一下流水线里 Agent 的含义。我所说的 AI Agent不是那种能自己规划、上网搜索的通用助手而是一个在关键环节用 LLM 做决策、在固定环节用代码做处理的混合架构。固定环节包括图片下载、OCR、模板渲染这些用代码写死决策环节包括从图里提取哪些字段卖点怎么表达这类没有标准答案的问题交给 LLM。用固定脚本也能跑通但会非常脆弱。比如不同商家发的商品图有的带价格水印、有的只有白底图、有的连标签都是英文固定规则的鲁棒性很差。而 LLM 决策点可以适应这些变化。同一段 Prompt 处理十张风格迥异的图只要给足上下文基本都能输出结构一致的 JSON。这就是我把这套东西叫 Agent 流水线的根本原因它不是一条没有脑子的传送带而是在关键节点上有会看、会想、会写的环节。1.3 为什么选 Go 而不是 Python这个很容易被追问。现在做 AI 应用大家默认用 Python因为生态丰富。我确实先用 Python 快速验证了一版但到了要嵌入正式服务、被多个团队调用的时候Go 的优势就体现出来了。部署简单单二进制丢到服务器就能跑不用配 Python 环境和依赖并发原语成熟goroutine 加 channel 天然适合流水线这种任务和后端服务的 HTTP、定时任务、消息队列集成更顺畅内存占用比 Python 低不少长期跑不会因为进程吃太多内存被运维盯上。如果你的技术栈本来就是 Python那用 Python 完全没问题。这条流水线选 Go纯粹是因为它是一条要放到正式服务里持续跑的流水线。一个真实感受是Python 对我来说是写起来快但 Go 是跑起来稳。2. 四道工序与两次 Agent 决策流水线的骨架2.1 从一张图到最终详情页数据是这么走的整条流水线我分成了四道工序每一道都有明确的输入输出。数据在工序之间以结构化的 JSON 流转不是传字符串。这一点很重要LLM 输出的东西如果不立刻结构化后面写模板时每次都要重新解析维护成本会非常高。工序输入输出核心实现图片预处理原始商品图压缩图 OCR 文本Go 开源 OCR商品信息提取压缩图 OCR 文本结构化商品 JSON视觉 LLM JSON Schema 校验文案生成商品 JSON分模块文案LLM 并行调用页面渲染分模块文案HTML 长图/分组图html/template headless 浏览器每一道工序都是一个独立的函数或服务接口是固定的。好处是任何一道工序都可以单独替换以后 OCR 想换云服务只动第一道文案模型想升级只动第三道。流水线跑起来之后你会发现这种各自独立、接口固定的架构比把所有逻辑揉在一起好调试太多了。2.2 为什么把理解图和写文案拆成两个 Agent有些方案会用一次调用搞定把图发给 LLM让模型直接输出详情页文案。我一开始也这么干效果不稳定。原因有两个。一是 token 浪费。理解图需要仔细看细节写文案则需要看很多文字上下文。两个任务混在一次调用里模型会偷懒跳过细致看图的部分直接凭经验编卖点。二是职责不同。图片理解需要的是准确、客观尽可能完整复述图中信息文案生成需要的是有吸引力、有转化感适当的润色和发挥是允许的。这两种目标差得很远放在同一个 Prompt 里模型会搞不清楚该偏向哪边。拆开后每个 Agent 的 Prompt 可以各自调优比如图片理解的 Prompt 强调不要编造文案生成的 Prompt 强调卖点要突出。2.3 数据协议用 JSON Schema 锁死每一道工序的输出LLM 输出是自由文本但流水线内部需要强类型数据。我在每个 Agent 节点都套了一层 JSON Schema 校验大概做法是在 Prompt 里要求 LLM 只输出 JSON用 Go 库比如github.com/santhosh-tekuri/jsonschema/v5校验返回的 JSON 是否满足 Schema不通过就带着校验错误信息重试一次最多重试 2 次两次都失败就走人工介入队列这个设计看起来有点繁琐但真正跑起来以后帮了大忙。LLM 非常喜欢在 JSON 里加多余字段、把数字写成字符串、漏掉必填字段。没有 Schema 校验后面模板渲染必然崩。我举个例子信息提取的 Schema 大概是这样的{ type: object, required: [name, category, specs], properties: { name: { type: string }, category: { type: string }, brand: { type: [string, null] }, specs: { type: array, items: { type: object, required: [key, value], properties: { key: { type: string }, value: { type: string } } } } } }这套 Schema 也是 Prompt 的一部分模型会看着这段结构去生成。它起到了双重作用既约束了模型输出的格式也给下游代码提供了校验标准。3. 商品信息提取先让模型学会看懂图再谈写文案3.1 先 OCR再视觉理解不要一上来就把图丢给大模型我的第一版是直接把图传给多模态 LLM让它输出商品信息。结果发现几个问题分辨率被压缩后小字看不清价格被水印遮住时它自己瞎猜高宽比过长的图模型只在中间画幅里找信息。后来改成先 OCR、再视觉理解的两段式先用开源中文 OCR 方案我用的是 PaddleOCR把图上的文字抠出来。OCR 这一步得到的是坐标加文本质量稳定、价格便宜。把 OCR 文本和压缩后的图片一起交给视觉 LLM让模型文字重点看 OCR 结果图上重点看颜色、材质、形状。这个组合明显提升了字段提取的准确率尤其是型号、尺寸、成分这些硬信息。OCR 引擎也不一定非得自建云服务 OCR 也可以看成本和延迟的取舍。我选择自建 OCR 是因为流水线是常驻服务不想每次都把图片上传到第三方。3.2 信息提取 Prompt 的设计与输出约束图片理解节点的 Prompt 有四个固定部分角色设定、输入说明、输出要求、反例。输出要求用 JSON Schema 描述而不是让模型自由发挥。Prompt 的核心内容是你是电商商品信息提取助手。请根据图片和 OCR 文本提取以下字段 - name: 商品名称String必填 - category: 商品类目String必填 - brand: 品牌String可空 - specs: 规格列表Array每个元素包含 key/value - attributes: 属性列表Array每个元素包含 name/value - tags: 卖点标签Array of String最多8个 要求 1. 只输出 JSON不要输出任何解释性文字 2. specs 只保留图中明确出现的信息不确定的字段不要编造 3. 如果图中没有品牌信息brand 输出 null 4. 忽略促销水印、验证码和角标 logo 上的文字specs 只保留图中明确出现的信息这个限制非常重要防止模型编造参数。忽略促销水印这条则是从踩坑里学来的最初模型会把图上限时 5 折的角标当成商品折扣参数写进 specs后来在 Prompt 里明确排除才解决。3.3 校验规则哪些字段可以空哪些不能空Schema 校验之外我还加了一层业务校验name不能为空且必须包含商品核心词比如连衣裙蓝牙耳机否则说明模型没真正理解这张图直接进人工队列specs里每个 key 不能重复重复就合并数字类字段重量、尺寸如果解析出来不是数字转成字符串给模板但打上 warning 标记brand为空时后续文案不能出现品牌相关表述校验的意义不只是防止崩溃更是控制质量。AI 生成的详情页如果有一个参数是错的发到买家手里就是售后事故。所以在流水线里宁可把有疑问的样本踢到人工队列也不能让它在无人看守的情况下直接进入发布链路。4. 文案生成把电商感写进 Prompt而不是指望模型自由发挥4.1 Prompt 里的电商语境人设、例子、禁止项文案生成节点比信息提取复杂很多。给模型的 Prompt 里要放入三类信息从上一道工序来的结构化商品 JSON、卖点标签的展开说明让模型知道轻薄显瘦具体指什么、文案模块要求主图文案 / 卖点模块 / 场景文案 / 转化引导。语气控制同样重要。电商文案不是写说明文而要带转化感。我在 Prompt 里给了几个正面例子和反面例子比如正确示范3 秒速干面料健身完直接穿出街错误示范该面料具有速干功能适合健身穿着模型对例子的学习能力很强给几个正反例子比在 Prompt 里写要生动、要活泼有效得多。另一个很有用的做法是列禁止项不允许出现这是一个该产品这类说明书腔调的词不允许出现与规格参数冲突的数值表述不允许编造用户评价。4.2 并行生成模块文案goroutine 加信号量文案生成是整条流水线里最耗时的部分如果串行生成 5 个模块可能要 40 秒。我改成并行每个文案模块是一个独立的 goroutine各自调用 LLM用golang.org/x/sync/errgroup管理并发其中一个失败不会阻塞其他模块用信号量控制全局并发上限避免把 API 的 QPS 打爆。一个关键细节不要在 goroutine 里直接共享 map 写结果。我在第一版犯过这个错并发一高就 panic后来改成每模块独立结果 统一合并非常稳定。核心代码大概是这样的g, ctx : errgroup.WithContext(ctx) g.SetLimit(5) results : make(map[string]string) var mu sync.Mutex for _, mod : range modules { mod : mod g.Go(func() error { content, err : generateCopy(ctx, mod) if err ! nil { return err } mu.Lock() results[mod.Name] content mu.Unlock() return nil }) } if err : g.Wait(); err ! nil { return nil, err }4.3 文案质量的弱约束与兜底文案不像信息提取那样能硬校验但也有一些弱约束可以自动检查模块数量必须等于 Prompt 里要求的数量每个卖点标题不能超过 12 个字正文控制在 30-60 字不允许出现说明书腔调的词出现则触发一次改写重试不允许出现与规格表矛盾的硬伤比如规格表里是 500g文案里写成 1kg这一步用正则把数字和单位提取出来对比弱约束不追求完美只求把明显低质量的输出拦下来。实测有大约 5% 的文案样本会被打回重写重写后的合格率能到 95% 以上。还有一个小技巧把打回重写时模型改了什么记录下来回头分析这些记录能帮你优化 Prompt减少重复打回。5. Go 编排层并发、重试、状态恢复这些工程细节5.1 LLM 客户端封装超时、重试与模型分级Go 调用 LLM我先封装了一个统一的 client对外暴露的方法很简单type LLMClient interface { Complete(ctx context.Context, req CompleteRequest) (string, error) CompleteJSON(ctx context.Context, req CompleteRequest, schema *jsonschema.Schema) (map[string]any, error) }CompleteJSON内部做了三件事请求模型、解析 JSON、校验 Schema不通过自动重试。超时设置上我一开始用了 20 秒发现大模型经常要 20-30 秒才能吐完一份长文案后来改成 60 秒并配合 context 的超时控制。另一个节省成本的关键是模型分级。便宜的模型做信息提取和简单分类贵的模型只做文案生成。我画了一条线凡是输入输出控制在 1000 token 内的任务用快速廉价模型凡是需要长文案输出的任务用高质量模型。整条流水线的单套成本因此下降了不少后面我会列具体的 token 消耗。5.2 全局并发控制多条流水线同时跑怎么办并发控制我用了两层。第一层是errgroup管理一条流水线内一批文案模块是否全部完成第二层是全局信号量控制所有流水线任务同时最多发起多少个 LLM 请求。信号量这个设计很关键。LLM API 是按 QPS 限流的如果你一个详情页同时发起 10 个请求20 个详情页同时跑就是 200 个并发必然触发 429 限流。我在上游设置一个全局信号量比如最多 10 个并发请求剩余排队等待。实测下来吞吐量并不会因为并发收敛而下降太多反而因为少了重试整体更稳定。sem : semaphore.NewWeighted(10) for _, task : range tasks { if err : sem.Acquire(ctx, 1); err ! nil { return err } go func() { defer sem.Release(1) doLLMCall(ctx, task) }() }需要提醒的是goroutine 的数量要控制否则大量 goroutine 阻塞在信号量上也很浪费。我一般把信号量的值设置成 API 限流值的 80% 左右留一点余量给运维告警和突发。5.3 状态记录与失败恢复断点续跑而不是从头再来流水线跑长任务时一定会遇到 API 抖动、偶发超时。如果整个流水线因为一个文案模块失败就从头再来浪费非常严重。我实现了一个简单的状态记录器每道工序完成后把产物持久化到本地文件或 Redis如果某一步失败记录错误并跳过最后统一生成失败报告重跑时可以选择只跑失败的部分。这个设计在数据量大的时候特别有用。有一回跑了 200 个商品到第 170 个时网络抖动导致一批请求失败我直接以失败清单重跑只花了原来 1/6 的时间就补齐了。状态记录本身不复杂但赚回的效率很可观。6. 渲染与平台适配HTML 模板、长图切块和发布前的最后一步6.1 HTML 模板渲染用 html/template 而不是字符串拼接文案生成完就是渲染。我用 Go 标准库的html/template定义了一套详情页模板包括顶部主图、卖点区、参数表、场景区、引导区。模板里只接收结构化数据不接受 LLM 原文这样能最大程度避免模型输出里的特殊字符破坏页面结构。div classselling-point h3{{.Title}}/h3 p{{.Desc}}/p /div模板渲染的好处是样式统一、修改布局只动模板、数据与展示分离。换皮肤或者适配不同平台时只改模板不用改流水线代码。我后来给不同品类分别做了模板服装类和数码类的详情页布局差异很大模板化之后这个差异被隔离得很好。6.2 HTML 转长图记住分屏截图这个原则淘宝详情页可以上传 HTML 代码也可以用图片。考虑到有些平台对富文本支持不友好我做了一个长图模式把 HTML 截图成一张长图按平台宽度切成若干屏输出成一组图片。长图渲染这里有个坑。服务端没有浏览器截图要用 headless 浏览器。我用了一个 puppeteer 的 Go 移植方案在容器里跑 headless Chromium。启动参数里的沙箱要关掉否则在容器里会启动失败。这个坑在部署文档里写了一长段如果你也遇到类似问题大概率是沙箱权限导致的。切图时还有一个容易忽略的点图片内存占用。一张 750 宽、几万像素高的长图在内存里可能到几百 MB。解决方案是分屏截图而不是整页截图再裁剪。每屏单独进入截图视口内存占用可控很多速度也更快。6.3 平台适配与输出规范不同平台对详情页的宽度、图片数量、文字内容有各自的限制。我在流水线里做了一层平台配置默认按主流电商平台的 750px 宽度来设计同时保留其他 Profile 的扩展能力。参数默认值说明详情页宽度750px按平台要求单屏最大高度视平台而定超出自动拆屏输出格式长图 / 分组图可切换图片文字策略尽量少仅主图 Slogan 和卖点标题烧录在图上我个人的原则是文字尽量少直接烧录在图片里。一是避免平台审核时因图片文字难以修改而被动二是方便后期调整文案。图中的文字只有主图 Slogan 和卖点标题详细内容都放在 HTML 的文本层。7. 实测数据、成本预算和踩坑记录7.1 效果对比流水线到底省了多少时间跑通之后我做了一轮对比同一批商品分别用人工和流水线制作维度人工制作流水线生成含人工微调单套耗时1.5-2 小时8-12 分钟30 套总耗时约 1.5-2 天半天含复核文案一致性依赖个人发挥稳定统一风格参数准确率手工录入偶尔出错OCRLLM 提取人工抽检注意表中的含人工微调。我的定位不是全自动发布而是AI 起草、人拍板。实测下来文案部分大概有 20% 需要人工修改主要是卖点的表达和品牌调性的匹配。这些修改会沉淀成一个风格反馈库后续重跑同类型商品时会把修正后的表达风格注入 Prompt反复几轮之后需要人工修改的比例会明显下降。7.2 token 成本算一笔账这套流水线的 token 消耗大概每套详情页在 1.6 万到 2.5 万 token 之间。具体分布如下图片理解约 6000-10000 token含图片多模态输入文案生成约 8000-15000 token分模块并行产出重试损耗平均在 15%-20% 左右按当前主流 API 价格折算单套成本大概在几毛到一两块人民币具体看用的模型和厂商。相比美工成本和排期这个价格算是非常便宜。不过要注意如果文案反复打回重写成本会翻倍所以先校验再重试的策略不只是质量手段也是成本手段。我后来给每套详情页设了一个 token 预算超了就自动停止重试转人工写。7.3 三个最典型的坑逐个说透第一个坑是 LLM 截断导致 JSON 不完整。长文案生成时输出到一半就断了JSON 直接非法。最初我以为是网络问题后来发现是模型自身的输出长度限制。解决办法是让模型用流出式生成把长文案拆成多个短片段分别输出同时开启 JSON 模式这个模式下模型会更注意输出结构的完整性。第二个坑是视觉模型对带水印图片的误判。原图如果有促销水印模型可能把水印里的限时 5 折当成商品参数写入 specs。我在 Prompt 里明确标注忽略促销水印、验证码、logo情况好转很多。但如果水印位置正好盖住了关键参数这种情况下光学识别很难处理最终只能进人工队列。第三个坑是长图内存爆炸。这个我在切图部分已经提过最终用分屏截图解决同时给渲染服务单独限制内存上限超了就自动排队。三个坑都属于不看实测根本想不到的类型写在这里希望你能少走一圈弯路。8. 保留人工复核的取舍与后续扩展方向8.1 为什么最后没有做成全自动很多人做 AI 流水线喜欢追求全自动但我觉得电商场景最后一公里必须有人。原因不是模型不够好而是商品信息一旦出错影响的是真实交易和售后体验。我把流水线设计成批量生成草稿 → 人工抽检修正 → 确认发布的形态人工环节被压缩到原来的十分之一以下但仍然保留。这个取舍值得展开说一下。我见过一些团队把自动化率当作 KPI强行去掉人工环节结果出了几单售后得不偿失。流水线的价值不是替代人而是把人的时间从重复劳动里解放出来聚焦在判断和创意上。对我来说AI 管起草、人管拍板是当前最合适的平衡点。8.2 后续可以往三个方向扩展这套流水线跑通后可以往三个方向走。一是接入商品库。目前输入是一张图扩展后可以接商品数据库批量读取 SKU、价格、库存自动生成对应版本的详情页连图都不用一张张上传。二是接入知识库。我准备把历史评论、售后反馈、品牌手册接入知识库让文案生成时能引用真实用户反馈而不是让模型编好评如潮。这个方向 Dify 这类平台做起来很方便自己写则需要额外搭向量检索工程量大一些但掌控力更强。三是服务化。现在这条 Go 流水线已经在内部 API 里运行下一步是给运营同学提供一个简单的页面上传商品图几分钟后收到详情页草稿。这才是流水线最终的价值——让不懂 AI 的人也能用起来。最后再分享一个实际经验如果你也准备搭一条类似的流水线先把手上的一个品类跑通别一上来就做多品类通用。单品类跑通之后再去抽象通用部分不然你会死在 Prompt 的反复调优上。这条流水线本身不复杂复杂的是让 AI 稳定输出你想要的东西那需要你在每一道工序上都跟模型较真。
返回列表