用 Go 从零实现多 Agent 协作框架:两个让我踩坑的设计问题

最近用 Go 写了一个多 Agent 协作框架:Planner 拆解任务 → 多专家 Worker 并行执行 → Critic 审查 → Reviewer 评分重试 → 汇总交付。听起来很顺,但第一次真实运行就踩了两个结构性 bug,每个都让结果完全不可信。

架构设计

整体流程:

复制代码
用户任务
  ↓
Planner ------ LLM 拆解为 N 个子任务,每个指定专家角色
  ↓
Worker × N ------ goroutine 并行执行
  ↓
Critic ------ 审查其他 Worker 的产出
  ↓
Reviewer ------ 对每个结果打分(1-10),未通过自动重试
  ↓
汇总输出

角色池有四个:general(通用)、writer(写作)、coder(代码)、critic(审查)。LLM 用的是 DeepSeek,所有 Worker 共享同一个 API key,只是 system prompt 不同。

Bug 1:角色映射键名不匹配

Planner 拆解任务后,给审查任务分配的角色名是 "critic"。但 Worker 初始化时,角色池 map 的键名写的是 "reviewer2":

go 复制代码
// 错误:planner 分配 "critic",但 pool 里没有这个键
pool := map[string]Worker{
    "general":  &GeneralWorker{},
    "writer":   &WriterWorker{},
    "coder":    &CoderWorker{},
    "reviewer2": &CriticWorker{},  // ← 键名不匹配
}

Go 的 map 查找不到键时不会报错,直接走 fallback 到 pool["general"]。运行时日志显示 [4] General-Worker (完成)------critic 任务用通用工人的身份去审查,system prompt 完全不对,质量可想而知。

修复: 把键名改成 "critic",和 Planner 分配的角色名对齐。教训是角色名是 Planner 和 Worker 之间的契约,应该用常量而不是手写字符串。

Bug 2:审查者看不到被审正文

这是更隐蔽的问题。所有 Worker 在 goroutine 里并行执行,每个 Worker 的 prompt 只包含自己的 task.Title 和 task.Description。

Critic 的任务描述是"审校文章技术准确性与表达",但它的 prompt 里没有 writer 写的正文。拿到的输入就是:

复制代码
任务:审校文章技术准确性与表达

没有正文。Critic 只能输出一段审查报告框架,开头就声明"由于未提供文章内容,无法进行具体审查"。Reviewer 给它打了 3 分。

问题根源:并行执行导致 Worker 之间信息隔离。Critic 必须在 Writer 完成后才能审查,但如果全部并行,它执行时 Writer 还没写完。

修复:两阶段执行

把 Worker 执行改为两阶段:

markdown 复制代码
第一阶段:所有非审查任务并行执行(goroutine + WaitGroup)
          ↓ 等待全部完成
第二阶段:审查任务串行执行,prompt 注入其他 Worker 的实际输出

核心改动是给审查任务传额外上下文:

go 复制代码
func (w *CriticWorker) RunWithContext(ctx context.Context, task Task,
    tasks []Task, otherResults []Result) error {

    prompt := task.Description + "\n\n以下是其他子任务的实际产出," +
        "请基于这些内容逐条审查:\n"
    for _, r := range otherResults {
        prompt += fmt.Sprintf("\n## 子任务 %s 产出\n%s\n", r.TaskID, r.Content)
    }
    return w.callLLM(ctx, task, prompt)
}

同时加了一个判断函数,自动识别哪些任务是审查类:

go 复制代码
func isReviewTask(t Task) bool {
    if t.AssignedTo == "critic" || t.AssignedTo == "review" {
        return true
    }
    keywords := []string{"审查", "审核", "校对", "评估", "review", "critique"}
    for _, kw := range keywords {
        if strings.Contains(t.Title, kw) || strings.Contains(t.Description, kw) {
            return true
        }
    }
    return false
}

RunWorkers() 改为:先用 goroutine + WaitGroup 跑所有非审查任务,等全部完成后,再用 RunWithContext 串行跑审查任务,把其他任务的实际输出拼进 prompt。

修复后的运行效果

ini 复制代码
📋 拆解为 4 个子任务:
   [1] 梳理核心概念 → general
   [2] 编写示例代码 → coder
   [3] 撰写文章正文 → writer
   [4] 审校技术准确性 → critic

⚡ [Worker] 并行执行中...
   ✅ [1] General-Worker (完成)
   ✅ [2] Coder (完成)
   ✅ [3] Writer (完成)
   ✅ [4] Critic (完成)

🔍 [Reviewer] 审查结果...
🔄 有 1 个任务未通过,开始重试...
🔄 [2] 重试第 1 次 (上次得分 6)
   ✅ [1] score=9
   ✅ [2] score=7  ← 重试后通过
   ✅ [3] score=8
   ✅ [4] score=9  ← critic 现在能看到正文了

统计:4 通过 / 0 未通过 / 4 总任务

对比修复前:critic 从 score=3(空转说"看不到原文")变成 score=9(逐条定位语法错误、反模式、API 过时问题),coder 首次 6 分重试到 7 分通过。

小结

两个 bug 的教训:

  1. 角色名是契约。Planner 和 Worker 之间的角色映射必须严格对齐,Go map 查不到键不会报错只会 fallback,这种静默失败最危险。
  2. 并行不等于最优。有依赖关系的任务(审查者依赖被审查者的产出)不能盲目并行。两阶段执行牺牲了一点并行度,但保证了信息完整。

项目已开源,包含完整代码、单元测试和 GitHub Actions CI: github.com/IYABAO/mult...

相关推荐
god_cvz2 小时前
第三章:1、Tool Calling(工具调用)
后端·ai编程
DO_Community4 小时前
AI Agent 运行环境难搭、空闲还收费?MicroVMs 正式公测
人工智能·agent·ai编程·agi
OpsEye5 小时前
企业 AI 管理新需求:不仅要接入模型,还要看懂每一次调用
javascript·ai编程
hoLzwEge5 小时前
AI签到「一劳永逸」
人工智能·程序员·ai编程
Rain的Java大神实战圈5 小时前
被问麻了!Function Calling、MCP、Skill 到底啥关系?一张图讲透三层架构,附 Java 生产落地全流程+30个坑
ai编程
文谦南宁AI普工5 小时前
写了个 PAP/PACT 授权面审计器:把 `poppy.json` 和 PACT 委托链的检查项跑成一条命令
aigc·ai编程
楚楚2515 小时前
2026最新5款AI编程助手基础版免费深度对比
ai编程
fthux5 小时前
编程语言:从机器指令到人类思维的演进史诗
ai编程
Rain的Java大神之路5 小时前
被问麻了!Function Calling、MCP、Skill 到底啥关系?一张图讲透三层架构,附 Java 生产落地全流程+30个坑
java·后端·ai编程