☰
用Go搭建AI Agent流水线:一张商品图10分钟生成淘宝详情页
2026/11/4 9:19:35 网站建设 项目流程

说实话,刚开始接到这个需求的时候,我内心是有点抗拒的:运营同学手里只有一张刚拍好的商品主图,却在几分钟内要一整套淘宝详情页——包含标题、核心卖点、参数表格、详情排版、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:过滤平台可能判违规的词汇。

流程是这样的:用户消息先触发感知层抽取,然后把抽取结果交给 Agent,Agent 根据结果决定该调用哪个工具,每调用一次工具,工具返回的结果又作为上下文进入下一轮,直到 Agent 认为信息完备、输出最终可用的商品结构化数据。

这个设计避免了我原来那种“写一个函数,里面顺序调三次模型”的生硬做法。因为底层模型遇到类目规则冲突时,会自己决定是否要再核实一次,处理未知情况灵活得多。给 Agent 加一个最大轮次限制(我设的是 5 轮),防止它陷进无意义的反复调用。

2.4 生成层:标题、卖点与详情文案

结构化数据出来后,生成层再分段生成电商文案。这个阶段我用了几组不同的提示词,分别是:

  • 标题生成:30 个汉字以内,要求包含核心品类词、风格词、使用场景词,同时保证通顺;
  • 核心卖点:从 features 数组里取 3 到 5 个卖点,每个卖点扩写成一句带场景感的文案;
  • 详情描述:生成 3 到 5 段详情文案,每段有一个小标题,段落之间逻辑递进。

这里的技巧是不要让一个模型调用同时做完所有事,而是拆成独立小任务。道理很简单:标题和长描述的注意力侧重不同,合在一个 prompt 里,容易互相干扰,比如标题变得跟简介一样啰嗦。拆开之后各自设定 temperature,卖点文案我设 0.7,标题生成设 0.5,既能有点创意又不至于偏。

2.5 渲染层:从数据到淘宝详情页模板

文案和数据最终要落到页面上。淘宝详情页本质上是图片模块、文本模块和参数表格的组合,我这边生成两层东西:

  1. 一个 HTML 预览文件,方便本地检查效果;
  2. 一组按模块拆分的 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 循环,直到模型不再返回 ToolCalls:

func (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 class="detail-page"> <section class="main-title"> <h1>{{.Title}}</h1> </section> <section class="selling-points"> {{range .SellingPoint}} <div class="point-item">{{.}}</div> {{end}} </section> <section class="spec-table"> <table> {{range .Specs}} <tr><td>{{.Name}}</td><td>{{.Value}}</td></tr> {{end}} </table> </section> <section class="description"> {{.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 在这里边完全能挑大梁。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询