
说实话刚开始接到这个需求的时候我内心是有点抗拒的运营同学手里只有一张刚拍好的商品主图却在几分钟内要一整套淘宝详情页——包含标题、核心卖点、参数表格、详情排版、SKU引导图。一个个去开聊天窗口让模型帮忙写再手动拼页面效率低到让人怀疑人生。后来我索性用 Go 搭了一条 AI Agent 流水线图片进去整套详情页出来中间让 Agent 自己去调度视觉模型、文本模型、类目规则和模板渲染器。跑通之后原来要折腾大半天的事情现在约 10 分钟就能产出一版可用的详情页。这篇文章就把我从 0 到 1 的设计思路、技术方案、核心代码片段和实操中踩过的坑完整记录一遍希望对想用 Go 做 AI 应用尤其是做内容生成类流水线的朋友有参考价值。1. 为什么用 Go 来搭 AI Agent 流水线1.1 业务场景先想清楚整个需求的起点并不复杂一张商品主图通常是白底图或者简单棚拍图要变成一套能直接上架的淘宝详情页。这里面涉及的不只是“让 AI 写段文案”而是多个环节的协同图片要能被 AI 看懂从中抽出商品品类、材质、颜色、卖点抽出的结果要落到淘宝类目体系里比如服装类目有自己的属性组3C 数码类目有另外一套参数标题、卖点、详情描述要符合电商文案习惯还得避开某些平台敏感词最终要渲染成 HTML 或图片模块排版要干净能直接丢进后台装修工具。这些环节如果全部靠人工粘合等于每个步骤都要写脚本对接模型、解析结果、再塞给下一个环节链路越长越容易乱。所以我一开始就决定把它做成一条有明确数据结构的流水线而 Go 在这一点上很搭。1.2 为什么没有选 Python做 AI 相关的东西第一反应都是 Python毕竟模型生态在那里。但在这个具体项目里我权衡下来反而觉得 Go 更顺手流水线本质上是“阶段式处理 并发调度”Go 的 goroutine 和 channel 写起来比 Python 里的线程池或者 asyncio 直观不少最终交付的是一个小型 HTTP 服务要部署在运营机器上Go 编译完是单个二进制文件不像 Python 需要装依赖环境模型 API 调用大多是 HTTP 请求Go 标准库足够胜任配合 go-openai 这类 SDK 也很成熟团队其他服务都是 Go 技术栈新链路可以直接复用日志、配置、监控那一套基建。这也不是说 Python 不行而是看场景。如果你只是做一次性脚本Python 的胶水能力确实更快但如果要把流水线沉淀成长期跑的线上服务Go 的部署和并发优势就体现出来了。1.3 技术栈全家桶我最终用的技术栈如下整体都是轻量方案组件选型用途HTTP 框架gofiber/fiber v2对外提供服务接口接收图片 URL模型 SDKsashabaranov/go-openai支持 OpenAI 协议的多模态模型接入模板渲染html/template生成详情页 HTML 骨架图片处理imaging / 自写压缩逻辑控制图片体积适配上游模型限制配置管理viper管理模型地址、密钥、类目规则日志zap记录流水线每个阶段的耗时与结果这套组合的最大好处是全部静态编译部署时丢一个二进制文件到服务器就行。2. 流水线整体拆解从一张图到一个页面2.1 输入层先把图片压缩到合理体积很多人在这一步会翻车直接把原图 Base64 塞给视觉模型结果请求体积动辄十几 MB要么超时要么被模型网关拒绝。视觉模型对图像分辨率有限制对单张图片的体积也有隐形的推荐上限所以我在输入层做了三件事判断图片格式非 JPEG 的先转换把长边压缩到 1024 像素以内保留宽高比JPEG 质量压到 80% 左右再做 Base64 编码。实测下来一张 3000px 的原始商品图经过这套处理Base64 之后一般在 200KB 到 400KB 之间既能保留细节又不会把请求拖垮。这里的关键经验是不要为了省事把原图直接传上去压缩图片省下的不光是流量还有视觉模型的 token 消耗速度。2.2 感知层让视觉模型输出结构化 JSON图片经过预处理之后进入感知层。我用的是支持视觉输入的多模态模型提示词里明确要求返回固定 JSON 结构。这一步不要用聊天式自由回答否则后面没法解析。提示词大致是你是一名电商商品信息抽取专家。请分析用户提供的商品主图输出以下 JSON 字段 category, category_hint, title_zh, main_color, material, style, scenes, features 其中 features 是一个字符串数组最多给出 5 个可以用于电商文案的卖点。 只输出 JSON不要输出其他解释文字。温度参数我会调到 0.1 左右尽量减少随机性。模型返回的 JSON 先做一次 schema 校验字段缺失或类型不对就重试一次。这是整个流水线最常见的问题来源后面第 4 章还会细讲。2.3 决策层Agent 工具调用与多轮修正这里是我认为最能体现“Agent”价值的地方。感知层拿到的是“模型觉得是什么”但淘宝详情页需要的是“符合类目规则的参数”。比如模型识别出“白色夹克”类目里也需要知道它对应的是“上装 夹克”以及该往规格表里填哪些必填项。我把这一步做成了 Agent 工具调用循环Agent 手里挂了几个工具search_category_rule根据品类关键词返回该类目下的必填属性和可选属性fill_spec_item将模型抽取的字段映射到类目属性枚举check_title_length校验标题是否超过 30 个汉字超了就自动截断或提示改写sensitive_word_check过滤平台可能判违规的词汇。流程是这样的用户消息先触发感知层抽取然后把抽取结果交给 AgentAgent 根据结果决定该调用哪个工具每调用一次工具工具返回的结果又作为上下文进入下一轮直到 Agent 认为信息完备、输出最终可用的商品结构化数据。这个设计避免了我原来那种“写一个函数里面顺序调三次模型”的生硬做法。因为底层模型遇到类目规则冲突时会自己决定是否要再核实一次处理未知情况灵活得多。给 Agent 加一个最大轮次限制我设的是 5 轮防止它陷进无意义的反复调用。2.4 生成层标题、卖点与详情文案结构化数据出来后生成层再分段生成电商文案。这个阶段我用了几组不同的提示词分别是标题生成30 个汉字以内要求包含核心品类词、风格词、使用场景词同时保证通顺核心卖点从 features 数组里取 3 到 5 个卖点每个卖点扩写成一句带场景感的文案详情描述生成 3 到 5 段详情文案每段有一个小标题段落之间逻辑递进。这里的技巧是不要让一个模型调用同时做完所有事而是拆成独立小任务。道理很简单标题和长描述的注意力侧重不同合在一个 prompt 里容易互相干扰比如标题变得跟简介一样啰嗦。拆开之后各自设定 temperature卖点文案我设 0.7标题生成设 0.5既能有点创意又不至于偏。2.5 渲染层从数据到淘宝详情页模板文案和数据最终要落到页面上。淘宝详情页本质上是图片模块、文本模块和参数表格的组合我这边生成两层东西一个 HTML 预览文件方便本地检查效果一组按模块拆分的 JSON 数据可以直接对接美工工具或店铺装修脚本。HTML 部分我用的 Go 标准库html/template模板结构固定传入的数据就是前面流水线生成的结构体。为了后续可维护我没有把模板强行塞进代码里而是放到独立的.tmpl文件这样运营同事要调整排版风格直接改模板就行不用动 Go 代码。3. 核心代码与实操要点3.1 流水线骨架用接口和切片组织阶段我在 Go 里把流水线抽象成很简单的结构一个 pipeline 由若干 stage 组成每个 stage 接收 context 和 product 引用处理完改掉 product 本身。type Product struct { ImageURL string json:image_url ImageBase64 string json:- Category string json:category CategoryID string json:category_id Attributes map[string]string json:attributes Title string json:title SellingPoint []string json:selling_points Description string json:description Specs []SpecItem json:specs } type SpecItem struct { Name string json:name Value string json:value } type Stage func(ctx context.Context, p *Product) error type Pipeline struct { stages []Stage logger *zap.Logger } func (pl *Pipeline) Add(stages ...Stage) { pl.stages append(pl.stages, stages...) } func (pl *Pipeline) Run(ctx context.Context, p *Product) error { for i, stage : range pl.stages { st : time.Now() if err : stage(ctx, p); err ! nil { return fmt.Errorf(pipeline stage[%d] failed: %w, i, err) } pl.logger.Info(stage done, zap.Int(index, i), zap.Duration(cost, time.Since(st)), zap.String(category, p.Category), ) } return nil }用来串流水线的时候只需要按顺序注册pl : Pipeline{} pl.Add( preProcessImage, extractProductInfo, agentRefineCategory, generateTitle, generateSellingPoints, generateDescription, renderHTML, )每个 stage 都是一个独立函数方便单测也方便单独替换实现。比如想把某个视觉模型换成另一个只需要改extractProductInfo内部逻辑其他环节完全无感。3.2 视觉理解多模态消息的构造方式我用 go-openai 构造多模态消息时Content 不是简单的字符串而是要传一个数组里面同时包含 text 和 image_url 两种元素b64 : base64.StdEncoding.EncodeToString(imgData) messages : []openai.ChatCompletionMessage{ { Role: openai.ChatMessageRoleUser, Content: []openai.ChatMessagePart{ { Type: openai.ChatMessagePartTypeText, Text: visionPrompt, }, { Type: openai.ChatMessagePartTypeImageURL, ImageURL: openai.ImageURL{ URL: data:image/jpeg;base64, b64, }, }, }, }, { Role: openai.ChatMessageRoleUser, Content: 请尽量输出结构化 JSON不要添加额外解释。, }, } resp, err : client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ Model: gpt-4o, Messages: messages, Temperature: 0.1, }, )这里有个很重要的小细节我把“只输出 JSON”这句话放在第二条 user 消息里而不是和图片同一条消息放一起。实际测试下来模型处理图文混合时更容易增强注意但把指令和图片拆成两条整体指令约束能力反而更稳定。这条经验不是来自文档而是多轮测试试出来的。3.3 Agent 循环工具调用如何实现Agent 工具调用说白了就是一个带上下文的多轮循环模型在每一轮可以选择直接回答也可以要求执行某个工具。实现时是一个 for 循环直到模型不再返回 ToolCallsfunc (a *Agent) Run(ctx context.Context, userPrompt string, messages []openai.ChatCompletionMessage) (string, error) { messages append(messages, openai.ChatCompletionMessage{ Role: openai.ChatMessageRoleUser, Content: userPrompt, }) for round : 0; round a.MaxRounds; round { resp, err : a.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ Model: a.Model, Messages: messages, Tools: a.Tools, Temperature: 0.2, }, ) if err ! nil { return , err } msg : resp.Choices[0].Message messages append(messages, msg) if len(msg.ToolCalls) 0 { return msg.Content, nil } for _, call : range msg.ToolCalls { toolResult : a.dispatchTool(ctx, call.Function.Name, call.Function.Arguments) messages append(messages, openai.ChatCompletionMessage{ Role: openai.ChatMessageRoleTool, ToolCallID: call.ID, Name: call.Function.Name, Content: toolResult, }) } } return , errors.New(agent exceeded max rounds) }dispatchTool是一个简单的 switch 分发根据函数名调用对应的 Go 函数。工具的定义是一个 JSON Schema 数组在初始化 Agent 时传入。为了让 Agent 理解每个工具的用途我会在描述里写清楚“这个工具接收什么参数、返回什么结果、何时该调用”描述越具体调用准确率越高。3.4 模板渲染从 Product 到 HTML模板部分我用 Go 自带模板就够示例模板核心部分大致是这样的div classdetail-page section classmain-title h1{{.Title}}/h1 /section section classselling-points {{range .SellingPoint}} div classpoint-item{{.}}/div {{end}} /section section classspec-table table {{range .Specs}} trtd{{.Name}}/tdtd{{.Value}}/td/tr {{end}} /table /section section classdescription {{.Description}} /section /div执行渲染的代码也一样简单tmpl, err : template.ParseFiles(templates/detail.tmpl) buf : bytes.Buffer{} if err : tmpl.Execute(buf, product); err ! nil { return err } product.RenderedHTML buf.String()如果你不需要生成 HTML而是直接生成详情页的图文模块可以把每个卖点单独渲染成一张卡片再交给 chromedp 截图成 PNG。我自己是先用 HTML 给运营确认内容再用脚本截图出图这样内容审核和排版分离方便后续换风格。4. 线上跑的坑排查与对策实录4.1 图片请求体积引发 400 和超时上线第一天我就接到告警说自己测试时好好的运营一传原图就报 400。查下来是运营直接拖了一张 12MB 的原图进去代码虽然做了压缩但压缩失败时走了旧逻辑直接把原图 Base64 发出去了。视觉模型网关对单次请求体积有限制超了就拒绝。对策是把图片处理做成强制的一环压缩失败就报错重试绝不把原图作为后备。另外给 HTTP 客户端设了 120 秒超时因为视觉模型大图处理慢默认超时太短很容易误杀正常请求。后来我把长边限制在 1024、质量压到 80%请求体积从 12MB 一路降到 300KB 左右成功率明显上来了。4.2 模型输出 JSON 不稳定即便提示词里写了“只输出 JSON”模型偶尔还是会夹带 Markdown 代码块或者漏掉字段。这其实不是模型不聪明而是视觉模型在图文混合输入下更容易受图片干扰注意力分配不稳定。我现在在代码里做了一个双重保障第一次返回的结果先用encoding/json做严格解析失败就带着错误信息重新请求模型解析成功后再对照 schema 做字段级校验缺失的字段单独重新生成。重试的提示词会把上一次的错误原样贴回去让模型自己看到哪里有问题。实测几次之后成功率能稳定在 98% 以上。不要相信模型“一定输出干净 JSON”把它当作概率事件来处理才是正路。4.3 并发上量后token 成本有点失控流水线里每一步都在调模型一个商品走完链路大概要消耗 1 到 2 万 token虽然单次不贵但每天跑几百个商品账单数字就比较感人了。我做了两件事来控制成本图片压缩是省 token 最大的杠杆图片越小视觉理解步骤消耗越少把抽取结果做了缓存同一个图片 URL 在 24 小时内重复请求直接命中缓存不重新调用模型。缓存结构很简单以图片 URL 的 MD5 为 key存整个 Product 结构。跑了一周之后缓存命中率大概 30%省下了不少成本。对 To B 的服务来说这一步我觉得比优化模型 prompt 更重要。4.4 类目属性枚举不一致模型返回的颜色是“米白色”但淘宝类目里的枚举值可能只有“米白/乳白”直接填进去会被系统打回。这个问题排查的时候最隐蔽因为模型和详情页都显示正常直到真正提交上架才报“属性值错误”。我最后写了一个属性归一化工具在 Agent 的fill_spec_item工具里内置了一套同义词映射表遇到没有映射的值就让 Agent 在类目枚举范围内选最接近的。比如“浅灰”映射到“灰色系”“纯棉”映射到“棉 100%”。这类业务规则不可能靠模型幻想出来最早放在提示词里效果不稳定还是放在代码里最保险。5. 扩展从单图流水线到平台化铺货现在的流水线已经不再只服务淘宝详情页了。因为 Pipeline 本身是独立 stage 的组合我把模板层拆出去之后同一套感知和生成环节可以直接输出小红书图文、京东详情、拼多多短视频脚本等不同形态的内容。每次要支持一个新平台只需要新增一个渲染器不用重新跑一遍 AI 链路。我也发现流水线的质量瓶颈其实不在模型有多强而在中间的业务规则够不够准。类目规则、敏感词库、同义词表这三样东西才是保证输出可用的护城河。模型负责发散规则负责收敛这个组合配合得好才能让流水线真正可落地。最后分享两个我个人体会很深的小技巧第一每个 stage 一定要记录耗时和中间结果日志出了问题直接按时间线回放比对着报错信息猜快得多第二尽量把所有模型的 temperature 都调到 0.2 以下尤其是信息抽取环节不要让它发挥“创意”电商场景里稳定比惊艳重要得多。这套链路跑下来我最大的感受是AI Agent 流水线不是玄学它就是把一堆已有的能力用工程手段串起来Go 在这里边完全能挑大梁。