搞了三个月,我们终于把一批核心业务模块的生成任务压进了 Grix 多智能体接力链,跑通了从需求描述到可编译代码的自动化流水线。最直观的变化是:原来一个资深后端写一个核心模块(领域模型、仓储、服务实现)大概要两天,现在在 OpenCode 底座上并行装配,平均每个模块从投递到产出可编译代码差不多二十分钟,具体吞吐取决于同时能拉起多少个 worker。
如果你最近也在琢磨多智能体系统、AI 编程助手这类话题,或者正被“用 AI 批量生成业务模块”这件事反复折磨,这篇文章应该对你有用。它不仅是我个人折腾 OpenCode 和 Grix 多智能体系统的全过程复盘,更是一份可以抄作业的高并发装配实践指南。
1. 为什么把 OpenCode 塞进多智能体接力链
1.1 从“一个 Agent 写完整个服务”的失败说起
最开始我们确实把核心模块生成想简单了。当时给一个通用 Agent 一段业务描述,让它直接输出领域模型、仓储接口、服务实现三层代码,结果非常惨烈。
印象最深的一个案例是生成订单服务。Agent 前几百行写得还挺像回事,到后来上下文窗口接近上限,抽象接口的方法签名和实现类开始对不上,实体字段一会儿是 Long id,一会儿是 long id,仓储层查询条件夹着一个根本不存在的字段。更麻烦的是,同样一个模型,生成 A 模块用的是构造函数注入,生成 B 模块就变成了字段注入,风格完全失控,代码评审阶段几乎要全部打回重写。
我们后来复盘,得出一个关键结论:核心模块生成不是一个“写代码”问题,而是一个“工程化装配”问题。单个 Agent 不适合干这种活,不是因为模型能力不够,而是长程任务里上下文磨损、风格漂移、约束冲突几乎是必然的。这也是我后来把 OpenCode 纳入体系,并围绕它搭建 Grix 接力链的直接原因。
1.2 Grix 接力链是怎么一回事
Grix 是我们内部搭建的一个轻量级多智能体编排底座,它本身不负责生成代码,只做任务拆分、调度、产物流转和失败重试。在 Grix 上,一条完整的模块生成链路被拆成多个原子环节,每个环节由专用 Agent 处理。这些 Agent 顺序执行,前一个的输出是后一个的输入,看起来像接力赛,所以内部一直叫它“接力链”。
最初我们只拆了两个环节,后来逐步调整到五个:需求拆解、架构约束注入、核心代码生成、代码走查、单测补全。每个环节都基于同一个 OpenCode 底座,但配置了不同的 system prompt、不同的 skill 文件、不同的模型参数。最关键的一点是,整个链路里没有任何一个 Agent 见过完整业务需求,它只拿到上游环节的产物。
这样设计最大的收益是可排查、可重试、可量化。某个环节出了问题,直接看那个环节的输入输出就能定位,不用在几千行对话里翻找原因。高并发装配场景下,这种“每个环节都能独立负责人”的特性非常重要。
1.3 为什么底座选了 OpenCode 而不是商业产品
不是商业产品不好,而是开源底座在批量生成场景里有几个天然优势。
第一,可脚本化。OpenCode 是终端工具,支持非交互式调用,这和我们后面用 Python 写 worker 池的路子天然契合。第二,模型层可组合。它可以在同一套配置里接多个模型服务商,甚至可以接本地推理服务,高并发下成本控制非常灵活。第三,skills 机制可以把团队编码规范固化成文件,Agent 在生成时自动加载,产出风格能保持稳定。第四,开源意味着可审计。生成环节出现问题,能顺着源码排查到底,这在业务核心模块场景里是一颗定心丸。
当然,代价也很真实:运维、排障、并发控制、容错这些事儿都得自己扛。这些坑我会在后面的章节里一一展开。
2. 高并发装配的系统设计与选型
2.1 从“生成”到“装配”:流水线模型的取舍
如果只是生成一两个模块,根本不需要搞架构。但我们的场景是几十个核心模块要在几天内完成第一版,后续迭代还要持续产出,这才逼着我们把“生成”重新定义成“装配”。
装配的思路可以类比汽车产线。一条产线上每个工位只做固定的一件事,工位之间用标准化接口传递半成品。代码生成也一样:如果让一个 Agent 从头到尾自由发挥,生成质量的方差会很大;但你如果把“交付物标准”和“验收标准”拆到每个环节,每个 Agent 的任务范围就被压得很窄,输出质量自然稳定得多。
我们为每个环节定义了产物契约,直接用 JSON Schema 表达。比如需求拆解环节必须输出module_name、bounded_context、entities、business_rules、dependencies这些字段,缺一个就算失败。后续环节永远不会拿到一份“写得很详细但结构没法解析”的需求文档,这是装配流水线能跑起来的前提。
2.2 高并发是怎么并发起来的
单个模块的链路是串行的,但多个模块之间完全并行。任务投递进 Redis Stream 队列,每个环节有独立的 worker 池,worker 从队列里取任务、调 OpenCode、写产物、再把结果投递到下一环的队列。
为了不让某个环节变成瓶颈,调度层实现了简单的动态扩缩容:如果某个队列的积压超过阈值,就临时多拉起几个 worker,跑完再回收。所有 worker 都是无状态的,任务状态全部在队列和产物仓库里,所以不用太担心进程宕机丢进度的问题。
真正需要小心的是模型服务侧的并发限制。我维护了一个按 provider 分组的信号量池,比如 OpenAI 兼容接口的并发上限设为 4,本地推理服务的并发上限设为 8,避免大量任务涌进同一个 provider 触发限流。信号量获取不到就让 worker 原地等待,而不是简单失败重试,这样队列积压会平稳很多。
2.3 先跑通一条链路再谈规模
整个系统上线前,我们先挑了三个模块做试点,全链路跑通之后才放开并发。这个顺序非常重要。如果你一开始就把 50 个任务一次性灌进去,出问题时根本分不清是队列问题、模型问题还是 Prompt 问题。
试点阶段我重点盯了三个指标:单环节成功率、单模块平均耗时、每环节 token 消耗量。这三个指标直接决定高并发时的资源预算。比如当时测出来,一个真实业务模块平均要消耗三十到四十万 token,心里就有数了:按成本限制能同时并发几个模块,每个小时大概烧多少 token,实在不行要不要切一部分到本地模型。这笔账不提前算清楚,压测一上来很容易被账单和服务商限流同时打懵。
3. 核心环节实现与 OpenCode 实操
3.1 OpenCode 的安装和基础配置
先讲最基础的实操。我是用 npm 全局安装的 OpenCode 稳定版本,装完第一次启动会引导配置模型提供商,配置文件会生成在用户目录下的 opencode.json 里。
我在配置里同时加了几类 provider:官方的兼容接口用于生产链路,本地推理服务用于压测和高并发场景,再留一个备用模型做降级兜底。OpenCode 允许在同一套配置里定义多个 provider,运行的时候用--model参数指定走哪一个。这个能力在接力链里非常有用,我用便宜的小模型跑“需求拆解”和“代码走查”,用能力更强的模型跑“核心代码生成”,整体成本结构一下就优化了。
非交互模式是整条链路的入口。我用的是opencode run命令,后面跟 prompt 和参数,在 shell 里试通之后,再封装成 Python 子进程调用。封装时特别要注意三点:超时时间要设置,模型推理慢的时候一个小时都有可能;输出捕获要完整,不能只读 stdout,stderr 里的错误信息经常是关键线索;最后是错误分类,要把模型限流、服务超时、输出解析失败这些情况区分开,调度器才能做不同的处理策略。
3.2 Skill:把团队规范固化下来
OpenCode 的 skill 机制是我们用得最重的一个功能。简单说,每个 skill 是一个 Markdown 指令文件,里面写清楚“当 Agent 被要求做这类任务时,必须遵守以下流程和输出格式”。
我们为每个接力环节写了独立的 skill。以核心代码生成为例,skill 里规定了模块的文件结构、命名规范、依赖注入风格、日志规范、异常处理规范,还附带一个最小可编译的模板代码片段。OpenCode 生成时自动加载这个 skill,输出就不会跑偏。
这里有一条很重要的经验:skill 里不要写“请写出高质量代码”这种抽象废话,要写具体、可校验的规则。比如“所有仓储接口必须返回 Optional 而不是 null”“实体字段禁止使用基本类型”“服务层必须显式声明事务边界”。规则越具体,后续校验环节的通过率越高,重试次数越少。
3.3 接力链的上下文设计与产物契约
每个环节的 prompt 由三部分拼装而成:上游产物的摘要、当前环节的 skill 指令、本次任务的具体参数。其中摘要不是把上游完整 JSON 原样丢进去,而是由上游 Agent 在产出里附带一段“给下游的话”,下游只读这段内容。
这个设计来自一个真实教训。早期我们直接传完整产物,生成环节经常被需求文档里的业务背景描述带偏,开始自己发挥设计思路,结果代码架构不符合团队规范。后来改成摘要制后,“核心代码生成”环节只关心要建哪些表、哪些实体、哪些接口,完全不接触业务上“为什么这么做”的内容,专注度反而大幅提升。
产物契约统一用 JSON 加 Pydantic 模型定义和校验。每个环节结束后,调度器先做 schema 校验,合法才进入下一环,不合法就重试。站在工程角度,这个机制把 AI 输出的不确定性挡在了模块边界之外,每一级 worker 不需要关心上游是哪个 Agent、有没有抽风,反正拿到的数据格式一定统一。
3.4 调度核心代码参考
这里给一段简化但能跑通核心逻辑的 Python 调度示例,用 asyncio 加信号量控制并发,用 Redis Stream 做队列。实际生产环境比这个复杂,但骨架就是这样的。
import asyncio import subprocess async def generate_with_opencode(stage, product, model, semaphore): async with semaphore: prompt = build_prompt(stage, product) cmd = [ "opencode", "run", prompt, "--model", model, "--temperature", "0.2" ] proc = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) try: out, err = await asyncio.wait_for(proc.communicate(), timeout=600) except asyncio.TimeoutError: proc.kill() raise TimeoutError(f"{stage} 阶段执行超时") if proc.returncode != 0: raise RuntimeError(f"opencode 异常退出: {err.decode()[:500]}") return parse_product(out.decode())简单解释几个关键点。semaphore是全局按 provider 划分的信号量对象,保证同一时间打到某个模型服务商的请求数不超过上限。build_prompt里面会动态读取对应的 skill 文件,并拼上上游产物摘要。parse_product负责把 OpenCode 返回的文本解析成结构化 JSON,并做 schema 校验,失败就抛异常交给上层重试。
这套模式写起来不复杂,但稳定性和可扩展性都很好。我们后期增加新的环节,基本只需要写一个新的 skill 文件、定义一份新的产物契约、注册一个新的 worker,不用动调度框架。
4. 高并发装配里的典型问题与排查
4.1 免费通道的报错:free tier 限制
第一次拉高并发的时候,我们图省事,直接在 OpenCode 里用了控制台自带的免费模型通道。跑单个任务完全没问题,但并发一往上拉,就开始大面积收到同一个报错:
error from provider (console): opencode's free tier can only be used from within opencode一开始我以为是 OpenCode 自己的 bug,后来查了官方文档和源码才明白,这个免费通道是给 OpenCode 交互式终端内部使用的,外部脚本和批处理进程不被允许。说白了,就是想拿免费资源跑高并发调度,被服务端识别并拦截了。
解决方案也不复杂:在配置里接入自己团队的 API Key,或者换成自建的本地推理服务。这一步做完,这个报错彻底消失。这个坑的教训是,高并发装配场景下,模型通道的供给方式必须和调用方式匹配。试用和免费通道通常带使用边界,不适合当产线底座,产线环境宁可牺牲一点模型能力,也要保证通道稳定、可规模化、可计费。
4.2 并发限流和退避策略
即便配了自己的 Key,把并发数拉高后还是躲不开限流。429 错误在高并发场景下几乎是必然的,重点在于怎么处理得优雅。
我踩过的坑是一看到 429 就立刻重试,结果把限流窗口塞得更满,形成雪崩效应。后来改成按响应头里的Retry-After字段做精确等待,拿不到这个字段就做指数退避。同时给每个 provider 维护一个请求时间窗计数器,接近阈值时主动放慢投递速度,而不是等被拒了再被动处理。
另外我们还做了一个简单熔断机制:同一个环节连续失败超过 5 次,就把任务转到重试队列,5 分钟后再拉回来跑,同时给负责人发一条通知。原因很直接,当底层模型服务不稳定时,无脑重试只是在放大问题,不如先让系统喘口气。
4.3 上下文污染和模块漂移
这是并发量上来之后最隐蔽的问题。现象是生成结果本身能通过 schema 校验,但代码里经常出现跟当前模块完全无关的类名、注释,像是模型把上一个任务的记忆串进来了。我们内部管这个问题叫“模块漂移”。
排查了很久,根源竟然是文件系统层面的串扰。并发场景下,多个 opencode 进程如果共用同一个配置目录或临时会话目录,偶尔会发生缓存串扰。解决方法是强制每个 worker 使用独立的工作目录和独立的会话标识,任务开始前清空临时文件,任务结束后把产物拷贝到统一产物仓库再销毁工作目录。
这个改动上线之后,模块漂移现象基本绝迹。这里也想提醒大家,AI 生成流水线里的“并发安全”不只是调度层面的概念,还包括进程文件系统、临时目录、会话状态这些特别容易忽略的细节。
4.4 常见问题排查速查表
| 现象 | 可能原因 | 排查顺序 | 解决方案 |
|---|---|---|---|
| 并发拉高后大量报错 | provider 限流 | 先看响应码和响应头 | 退避重试 + 信号量限速 |
| free tier 限制报错 | 免费通道禁止外部调用 | 检查模型通道配置 | 换付费 Key 或本地推理 |
| 生成代码包含无关内容 | 共享会话目录导致上下文污染 | 查临时目录和进程参数 | 每个 worker 独立目录 |
| 输出 JSON 解析失败 | 模型输出截断或格式漂移 | 看原始输出内容 | 增加 schema 校验 + 重试 |
| 单任务持续超时 | 模型推理慢或上游服务不稳定 | 看日志耗时分布 | 设置超时 + 熔断 + 重试队列 |
5. 实测效果与几条反直觉经验
5.1 一组可以当参考的数据
试点模块从投递到产出可编译代码,单模块耗时稳定在 18 到 25 分钟之间。这里的“可编译”,是指已经过了编译、基础静态检查和单测验证的版本。相比纯手写,效率提升非常明显,但我觉得更重要的收获是生成过程变得可复制、可追踪。任何一个模块,你都能说清楚它在哪个环节花了多少时间、消耗了多少 token、中间重试了几次。
成本方面,核心代码生成环节的 token 消耗占比最高,所以我们对这个环节单独做了产物缓存。相同契约约束下的重复生成请求直接命中缓存,不再二次调用模型。这一条优化帮我们省了差不多三成 token 支出。
成功率方面,经过 schema 校验加失败重试,最终产出率稳定在百分之九十以上。剩下那百分之十的失败案例,绝大多数是需求本身的歧义导致的,比如业务规则描述前后矛盾、字段命名两种叫法混用,结果反而帮我们发现了不少需求文档里的隐藏问题。
5.2 几条反直觉的经验
第一条,模型选择不是越大越好。在高并发装配流水线里,“需求拆解”和“代码走查”这些环节用中小模型完全够用,价格低、速度快、并发上限高;只有“核心代码生成”环节需要上更强能力的模型。给每个环节差异化配置模型,成本能降一大截,这是这个项目里我最满意的优化。
第二条,写生成 prompt 不是最花时间的事,设计校验规则才是。我们的经验是“检查者 Agent”的价值远高于“写代码 Agent”。校验环节足够强,生成环节的模型差一点也能兜得住;反过来,校验弱的话,再强的模型也会输出一堆表面好看但经不起细看的东西。
第三条,任务拆得越小,成功率越高。见过很多团队想让一个 Agent 直接生成一个完整的支付服务,这种需求几乎一定会失控。我们在接力链里,连“生成实体的仓储接口”和“生成仓储实现”都是分开的两个环节。任务拆小之后,每个环节的输入输出都非常明确,成功率提升明显,排障也特别简单。
最后再分享一个细节:我们给每个环节的产物都加了一个trace_id,从最初的需求描述到最终的可编译代码,整条链路可以完整追踪。追过几次问题之后你会意识到,在 AI 生成代码的流水线里,“可追溯性”和“代码质量”同样重要。没有追踪能力,出了问题就只能对着黑盒猜,而猜是效率最低的排障方式。