一条流水线从一张商品图开始跑完之后吐出一整套淘宝详情页——标题、卖点、详情文案、场景图、参数表格全齐了。这就是我最近用 Go 搭的一条 AI Agent 流水线。作为常年写后端服务的人我这次刻意没有跟风用 Python而是选了 Go 来做整套编排。这篇文章把整个方案的思路、核心代码拆解、跑通的过程和踩过的坑一次说透给同样想用 Go 碰 AI Agent 的朋友一个真实参考。先说结论Go 做 AI Agent 流水线完全可行尤其在编排、并发、部署这三块体验比 Python 顺手得多。但如果你指望拿 Go 去写模型训练或搞科研实验那还是趁早用 Python。这个项目我实际跑了两个月中间重构了两版最后沉淀出来的这套方案在稳定性、单机吞吐和交付效率上都达到了能用的水平。下面按我的实际经验从头讲。1. 为什么用 Go 来搭 AI Agent 流水线1.1 Python 很香但 Go 更适合流水线做 AI 相关的东西默认选 Python 是刻板印象。Go 也有自己的牌可以打。我说说我为什么坚持用 Go主要是四个点。第一是并发模型。流水线这种场景本质就是上游产出、下游消费每个环节之间天然存在并行空间。比如第一张图在生成场景图的时候文案模块完全可以同时跑。Go 的 goroutine 和 channel 是语言原生的编排工具写起来就是几行的事。Python 的 GIL 决定了它在多线程上先天吃亏真要并行还得上 asyncio 或者 multiprocessing心智负担完全不一样。第二是部署体验。Go 编译出来就是单个二进制丢到服务器上就能跑没有 Python 那一堆依赖环境的问题。我们这个流水线最终要部署成常驻服务接收队列里的商品图请求。Go 在这一点上可以说是拿来即用我连 Docker 镜像都不需要额外折腾 Python 运行时。第三是资源占用。一条流水线跑起来如果同时处理 20 个商品Python 那套进程模型内存轻松破几个 G。Go 这边 goroutine 是微线程跑几十上百个并发任务内存控制在一个合理的范围内。作为常驻服务内存就是钱这点账要算清楚。第四是生态的补位。Go 确实没有 Python 那么庞大的 AI 库生态但注意——我们调 LLM 和图像模型走的是 HTTP API不是本地推理。这就不需要什么 numpy、torch 全家桶。流水线里真正的重活是请求编排、数据转换、模板渲染、并发控制这些恰恰是 Go 的强项。1.2 这个项目到底干了什么我从头说一下我在做的这件事。输入的是一张商品图比如一个白底的电热水壶照片。输出是一个完整的淘宝详情页包括商品标题30 字以内的淘宝风格标题5 条核心卖点文案详情页分模块文案产品故事、功能解析、适用场景、规格参数、售后说明配套的场景图把白底商品图合成到生活场景里最终的 HTML 详情页文件很多团队做这种事是人工分工运营写文案设计师做场景图美工拼页面一个人一天可能只能做两三款商品。我的目标是扔一张图进去5 分钟内出一整套初稿人只需要做最后的审核和微调。这里有个关键认知这不是调一个 LLM 生成点文字那么简单。一个完整的详情页涉及多轮信息提取、多种内容生成、图片处理还要做格式封装。每一步之间还有依赖关系标题要根据商品特征生成卖点要根据标题和特征深化场景图要根据商品种类选场景和风格。这一串动作连在一起才叫流水线。2. 流水线整体设计拆解2.1 从 1 张图到整套详情页的环节拆解我把整套流程拆成了 6 个 Stage串成一条有向流水线。每一段都只负责一件具体的事输入输出都有明确的类型定义。Stage职责输入输出S1 图像解析调用多模态模型识别商品信息原始商品图结构化商品特征类目/颜色/材质/风格S2 标题生成基于特征生成淘宝风格标题商品特征标题候选3 条S3 卖点生成生成 5 条核心卖点特征 标题卖点列表S4 场景图生成把商品图合成进场景图原图 场景风格参数场景图3 张S5 详情文案生成生成各详情模块文案特征 卖点结构化文案 JSONS6 页面渲染合成 HTML 详情页以上全部输出完整 HTML 文件这个结构在代码里的体现就是一个 Stage 接口和一张注册表。每一段可以是不同的模型、不同的 prompt 策略、甚至不同的供应商互不干扰。中间某一环要换模型只改那一个节点其他都不动。2.2 Agent 与普通程序调 LLM 的本质区别做这个项目之前我先把AI Agent和普通调 LLM之间的界限想透了。很多人把代码里调了一次 ChatGPT API就称为 Agent这是误解。普通调 LLM 是单向的我给你 prompt你给我回答结束。Agent 的核心是能够根据中间结果做决策并导向下一步动作。在我们的流水线里S1 拿到商品图识别出的结果可能不完整比如颜色没识别出来。这时候 Agent 行为体现在程序会根据缺失字段自动组合一轮追问让模型重新输出完整信息再进入下一步。这个根据当前状态调整下一步动作的过程就是 Agent 的雏形。我们的流水线其实是编排式 Agent。它没有让 LLM 自己决定下一步干什么而是由 Go 代码把动作序列定死LLM 负责在每个节点上完成推理和生成。这样做的好处是可控、可观测、可调试。你不需要担心模型突然想跑偏做了一件你没想到的事。我在生产环境里最怕的就是不可控所以选择了这种程序编排 模型推理的混合模式。2.3 为什么选 Pipeline 而不是单一 Agent我也考虑过另一种方案把所有需求一股脑丢给一个 Agent让它自己规划步骤、循环调用工具直到产出详情页。这种方式看起来更AI但实际用下来有几个致命问题。第一是成本不可控。自由式 Agent 经常在无关紧要的步骤上反复调用模型token 消耗是编排式的 3 倍以上。第二是质量不稳定。模型自己规划容易遗漏环节比如忘了生成场景图或者标题和卖点风格脱节。第三是排障困难。一个自由 Agent 跑完一个流程中途发生了什么基本靠日志猜很难定位是哪一步出了问题。Pipeline 的好处是每一段都有明确的输入输出契约我可以单独测试每一环也方便在任意环节插入人工审核。比如 S1 识别出的商品信息运营同事可以先看一眼确认再决定继续还是重新识别。这种事在自由 Agent 方案里很难做到。所以我的判断是在业务流程足够清晰的情况下Pipeline 永远优于自由 Agent。3. 核心实现Go 代码层面的关键环节3.1 把 LLM 封装成一个好用的 Go Client在 Go 里调 LLM 的 API 不算难难的是把请求-重试-解析-降级这套流程做得干净。我直接说我的封装方式。首先定义统一的消息结构type Message struct { Role string json:role Content string json:content } type ChatRequest struct { Model string json:model Messages []Message json:messages MaxTokens int json:max_tokens,omitempty JSONMode bool json:response_format,omitempty } type ChatResponse struct { Choices []struct { Message Message json:message } json:choices }然后是客户端的核心方法我做了三件重要的事func (c *LLMClient) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) { var lastErr error for attempt : 1; attempt 3; attempt { resp, err : c.doRequest(ctx, req) if err nil { return resp, nil } lastErr err // 指数退避重试 backoff : time.Duration(attempt*attempt) * time.Second select { case -ctx.Done(): return nil, ctx.Err() case -time.After(backoff): } } return nil, lastErr }第一件重要的事是重试。LLM API 偶尔会出现 5xx 或超时这是常态。我加了最多 3 次重试用指数退避而不是固定间隔避免同时打到上游造成雪崩。第二件是把上下文塞到请求里支持后续的并发编排。第三件是支持 JSON 输出模式要求模型返回结构化数据这是流水线能否自动跑下去的关键。提示调 LLM 时如果模型直接返回文本你还要自己解析非常脆弱。务必在请求里开启 JSON 模式并在 prompt 里给一个严格的输出样例这样后续代码才能稳定消费。这是我在踩了大量坑之后总结出来的第一原则。3.2 Stage 编排与并发控制Stage 编排是整个项目的地基。我定义了一个接口所有环节都必须实现它type Stage interface { Name() string Run(ctx context.Context, input map[string]any) (map[string]any, error) }每个 Stage 接收一个 map 作为输入输出一个新的 map。这个设计可能看起来不够类型安全但它的好处是极大的灵活性新加一个 Stage 不需要改上下游的任何代码只需要在串联时把需要的 key 从上游取出来。用 Go 的泛型强行定义类型反而会让流水线变得僵硬。具体编排用 errgroup 管理并发这是一个能救命的标准库g, ctx : errgroup.WithContext(ctx) // 并行执行卖点生成和场景图生成互不依赖 g.Go(func() error { result, err : sellPointStage.Run(ctx, stageInput) if err ! nil { return fmt.Errorf(卖点生成失败: %w, err) } output[sell_points] result return nil }) g.Go(func() error { result, err : sceneImageStage.Run(ctx, stageInput) if err ! nil { return fmt.Errorf(场景图生成失败: %w, err) } output[scene_images] result return nil }) if err : g.Wait(); err ! nil { return nil, err }errgroup 的好处是任何一个 goroutine 返回错误整个流水线立刻取消不会出现一个环节失败但其他环节还在傻跑浪费钱的情况。context 一取消所有下游的 HTTP 请求都会中断。这里我要特别强调一个实战经验流水线的每个 Stage 必须设置独立的超时时间。比如 S1 图像识别我给 30 秒S4 场景图生成我给 90 秒S6 模板渲染我只要 2 秒。用 context.WithTimeout 在 Stage 入口处包一层防止某个模型 API 卡死把整个流水线拖垮。3.3 图像处理环节图像这块是整个流水线里最容易翻车的地方因为你面对的不是文本而是一堆像素和模型输出质量的不确定性。我先说我用的图像生成方案调用图像生成模型的 API输入原图 场景描述让模型产出场景图。具体 prompt 的策略是这样的将输入的商品图融合到以下场景中现代简约风格的厨房台面。 要求 1. 保持商品主体外观/颜色/细节不变 2. 光影方向与场景自然融合 3. 输出竖构图 1024x1536 4. 画面干净无文字这里有个坑很多图像模型会把商品主体的颜色或形状改掉。为了降低这个概率我会在调图像模型之前先用多模态模型把商品的精确外观特征提取出来写进 prompt 里做约束。比如这是一个淡蓝色的不锈钢电热水壶壶身有磨砂质感手柄是深灰色塑料这样模型生成时就有了锚点。如果模型 API 不支持直接传参考图还有一个备选方案先用抠图服务把商品从原图中分离拿到透明背景的商品 PNG然后用图像编辑模型把这个 PNG 贴到场景背景上。这个方案更稳但多一步操作耗时也更久。我的建议是优先试直出场景图质量不行再走抠图合成的路线。3.4 详情页模板输出最后一环是把前面生成的所有内容合成一个淘宝详情页。我用的 Go 的 html/template 直接渲染没引入任何前端框架。模板结构大概长这样type DetailPageData struct { Title string json:title SellPoints []string json:sell_points DescModules []Module json:desc_modules Scenes []string json:scenes Specs []SpecItem json:specs } type Module struct { Heading string json:heading Content string json:content } type SpecItem struct { Name string json:name Value string json:value }渲染代码tmpl, err : template.ParseFiles(templates/detail_page.html) if err ! nil { return err } var buf bytes.Buffer if err : tmpl.Execute(buf, data); err ! nil { return err }这里有一个细节值得说详情页里的图片场景图是通过 URL 引用的而不是把图片二进制直接塞进 HTML。这是因为详情页最终要上传到淘宝后台编辑器里插入图片用的是 URL。所以我在 S4 结束后会把场景图上传到对象存储拿到公开访问的 URL再喂给 S5 的文案生成和 S6 的模板渲染。这样整个链路里传递的都是字符串不会因为图片过大导致内存问题。4. 实操过程跑通一条完整流水线4.1 环境准备我先把 Go 的版本环境说清楚。这个项目用的是 Go 1.22长期稳定版。如果你刚接触 Go直接从官网下载安装包即可装完确认版本go version然后初始化项目mkdir agent-pipeline cd agent-pipeline go mod init pipeline依赖方面我没有用太多第三方库核心就一个 errgroup标准库自带和 go-resty 用来发 HTTP 请求图像处理部分全走 API不需要本地图像库。这会让你后面的开发专注在流程逻辑上而不是被库的 API 折腾得分心。4.2 一次典型运行记录我把整个流水线的入口封装成一个 RunPipeline 函数参数就是要处理的商品图路径。运行时输出大概是这样的[1/6] 图像解析: 识别到[电热水壶, 不锈钢, 淡蓝色, 磨砂质感, 家用, 北欧风格] 耗时 8.2s [2/6] 标题生成: 3 条候选已生成 耗时 3.5s [3/6] 卖点生成: 5 条卖点已生成 耗时 4.1s [4/6] 场景图生成: 3 张场景图已生成 耗时 42.7s [5/6] 详情文案生成: 6 个模块已生成 耗时 6.8s [6/6] 页面渲染: detail_page_20250412_1030.html 耗时 0.3s 总耗时: 65.6s 总token消耗: 约 45000 tokens看到这个输出你就会明白为什么我说 S4 是瓶颈。场景图生成在最开始的版本里是串行的3 张图要 2 分多钟。后来我把 S4 内部改成 3 个 goroutine 并行调用时间从 2 分钟压缩到 42 秒。这就是流水线设计里找出瓶颈用并发破局的实操案例。4.3 参数调优调优主要集中在两个维度模型选型和 prompt 细节。模型选型方面S2 和 S3 是纯文本生成我用的是通用大模型的中档配置温度调到 0.7确保有点创意但不会跑偏。S1 必须用多模态模型这个没有替代方案。S5 详情文案生成我反而把温度调到 0.4因为详情页的文案要贴合实际功能不能太飘。prompt 细节方面最经典的一个坑是让模型生成标题时它经常写出超过 30 个字的标题。淘宝标题一旦超长发布时会被系统截断。我的解决办法是在 prompt 里显式加入标题必须少于 30 个汉字的约束并且在代码里做了一个字符串截断兜底。这个兜底逻辑很重要——不要完全相信模型能遵守约束。5. 常见问题与排查技巧实录5.1 LLM 返回 JSON 反复横跳这是所有做 AI 应用的同行都会遇到的经典问题。哪怕你开了 JSON 模式模型偶尔也会给你输出一段夹杂着 markdown 代码块的半 JSON。比如它可能在 JSON 外面包了 json 标记或者在里面多了一个逗号导致解析失败。我的排查方案分三层第一层解析前做预处理去掉代码块标记去掉首尾的空白和不可见字符。第二层如果标准 json.Unmarshal 失败就尝试用正则把疑似 key-value 结构抠出来重新组装。第三层如果还是失败直接丢弃这一轮输出重试一次。重试时机提示换一个更严格的 prompt。一个更治本的办法是把输出结构定义成数组对象让模型返回一个 JSON 数组数组里每个元素是明确字段的对象。结构越简单模型出错率越低。用连环嵌套的复杂结构出错率会指数级上升。5.2 并发数量失控刚开始用 goroutine 时我把每个商品的所有 Stage 都并行跑结果有 20 个商品同时进来S1 同步发出 20 个多模态请求直接把 API 限流打爆了返回一堆 429。排查下来发现是我对并发量的认知有问题。goroutine 本身很轻但每个 goroutine 发起的 HTTP 请求对上游 API 来说是重负载。解决方法是加一个信号量var sem make(chan struct{}, 5) // 最多 5 个并发请求 func limitedRun(ctx context.Context, fn func() error) error { select { case sem - struct{}{}: defer func() { -sem }() return fn() case -ctx.Done(): return ctx.Err() } }所有 LLM 请求都走 limitedRun 之后上游限流问题再没出现过。如果你用的 API 文档给出了 RPM 限制直接把信号量大小设为限制值的 60%留出余量。5.3 图片处理质量这个问题的坑很深。图像生成模型返回的图有时主体商品会变形。最开始我完全依赖模型后来发现只要 prompt 里明确说保持商品外观不变输出高质量真实感商品图同时把商品特征写得足够详细变形率能降到 10% 以下。如果画面里出现文字乱码尤其是中文乱码目前没有完全可靠的解决方式。我的建议是生成完之后加一个简单的图像质量检测环节用多模态模型问一句图片中是否有乱码文字或商品变形如果有就重新生成最多重试 2 次。虽然多了几次调用但能保证最终交付质量值这个成本。5.4 成本控制说完质量说成本。整个流水线跑一次场景图生成的费用占了大头。因为我用的是按张计费的图像生成 API3 张场景图比所有文本生成加起来的成本还高。控制成本有三个策略。第一在 prompt 里要求输出中等分辨率而不是最高分辨率除非客户明确要高清。第二对商品进行分类低价值商品只生成 1 张场景图只有高价值商品才生成 3 张。第三把同批次的商品合并到一条流水线处理有些公共信息可以复用减少重复的上下文输入。实测这三个策略能把单次流水线的成本压缩 40% 左右。6. 一些后续可以扩展的方向流水线跑通之后我再分享一个我现在正在做的扩展方向给你做个参考。目前这套系统是单图进、整页出但实际运营中经常遇到一个商品有 5 到 10 张原始图的场景。我现在在改的是把 S1 改成多图输入也就是支持对多张商品图分别提取特征再合并成一份完整的商品信息。这个改动对下游的文案生成质量提升极大因为模型能看到更多角度的商品细节。另外流水线输出的 HTML 详情页还不是最终可以直接上传淘宝后台的格式。淘宝后台有自己的富文本编辑器需要按它的要求粘贴或导入。我目前的做法是把 HTML 里的模块内容再转成纯文本和图片块的组合方便运营直接复制进去。如果你做的是其他电商平台要提前研究目标平台的编辑格式别等到最后一步才发现输出格式不对。注意做这种批量生成详情页的工具有一点一定要把握好——生成的内容只是初稿务必安排人工审核后再发布。商品描述涉及消费者权益错漏信息不能直接流向市场。自动化工具的价值是帮你把 80% 的重复劳动做掉剩下 20% 的审核校对才是你真正要盯住的地方。这个把关环节千万别省。我跑完这套流水线最大的体会是AI Agent 落地难点从来不是某个模型有多强而是你怎么把强弱不一的模型、图像服务、模板系统拧在一条像样的流水线上。Go 在这里的角色非常明确——它不是最耀眼的那个但它是把整条线真正串起来的那根线。如果你也想做类似的自动化内容生产工具希望这篇能帮你少走一段弯路。
