500 行 Go 代码实现多 Agent 编排引擎:Coordinator、Worker 与 Debate 模式
·
别急着引入 LangChain / CrewAI 那几十万行依赖。本文用纯 Go 实现一个轻量多 Agent 编排引擎,支持三种协作模式:Coordinator-Worker、点对点 Debate、和流水线 Pipeline。全部代码约 500 行,零外部依赖。
一、三种编排模式
| 模式 | 拓扑 | 适用场景 | 代表用例 |
|---|---|---|---|
| Coordinator-Worker | 星型 | 任务可以拆成独立子任务 | 代码审查:Coordinator 分配"安全检查"“性能分析”"风格审查"给不同 Worker |
| Debate | 全连接 | 需要多方论证的决策 | 架构选型:两个 Agent 互相质疑对方方案,第三 Agent 做裁判 |
| Pipeline | 线性 | 有明确前后依赖的流程 | 文档生成:调研 → 大纲 → 撰写 → 润色 → 翻译 |
Coordinator-Worker Debate Pipeline
┌───────┐ ┌───┐───┐───┐ ┌───┐ ┌───┐ ┌───┐
│Coordinator│ │ A │←→│ B │ │ S1│──→│ S2│──→│ S3│
└──┬──┬──┘ └───┘───┘───┘ └───┘ └───┘ └───┘
│ │ ↕
┌──┘ └──┐ ┌───┐
▼ ▼ │Judge│
Worker1 Worker2 └───┘
二、核心抽象
2.1 Agent 接口
// agent/types.go
package agent
import "context"
// Message Agent 间通信的消息
type Message struct {
Role string `json:"role"` // "user" | "assistant" | "system"
From string `json:"from"` // 发送者 Agent 名称
Content string `json:"content"`
}
// Agent 所有 Agent 必须实现的接口
type Agent interface {
// Name 返回 Agent 名称(用于日志和消息路由)
Name() string
// Run 执行 Agent 逻辑
// ctx: 上下文,用于超时控制
// history: 当前对话历史
// 返回: Agent 的回复消息
Run(ctx context.Context, history []Message) (Message, error)
}
// LLMClient LLM 调用抽象(便于测试 Mock)
type LLMClient interface {
Chat(ctx context.Context, systemPrompt string, messages []Message) (string, error)
}
2.2 简单的 LLM Agent 实现
// agent/llm_agent.go
package agent
import (
"context"
"fmt"
"strings"
)
// LLMAgent 基于 LLM 调用的 Agent 实现
type LLMAgent struct {
name string
systemPrompt string
llm LLMClient
}
func NewLLMAgent(name, systemPrompt string, llm LLMClient) *LLMAgent {
return &LLMAgent{name: name, systemPrompt: systemPrompt, llm: llm}
}
func (a *LLMAgent) Name() string { return a.name }
func (a *LLMAgent) Run(ctx context.Context, history []Message) (Message, error) {
// 将 Messages 转换为 LLMClient 能理解的格式
llmMessages := make([]Message, 0, len(history)+1)
// 注入 system prompt
llmMessages = append(llmMessages, Message{
Role: "system",
From: a.name,
Content: a.systemPrompt,
})
llmMessages = append(llmMessages, history...)
content, err := a.llm.Chat(ctx, a.systemPrompt, history)
if err != nil {
return Message{}, fmt.Errorf("agent %s llm call: %w", a.name, err)
}
return Message{
Role: "assistant",
From: a.name,
Content: strings.TrimSpace(content),
}, nil
}
三、三种编排模式实现
3.1 Coordinator-Worker 模式
// orchestrator/coordinator_worker.go
package orchestrator
import (
"context"
"fmt"
"strings"
"sync"
"your-project/agent"
)
// CoordinatorWorker 星型编排:Coordinator 分配任务给多个 Worker
type CoordinatorWorker struct {
coordinator agent.Agent
workers []agent.Agent
}
func NewCoordinatorWorker(coordinator agent.Agent, workers []agent.Agent) *CoordinatorWorker {
return &CoordinatorWorker{coordinator: coordinator, workers: workers}
}
func (cw *CoordinatorWorker) Run(ctx context.Context, task string) (string, error) {
// Step 1: Coordinator 分析任务并分配到 Worker
planMsg := agent.Message{
Role: "user",
From: "user",
Content: fmt.Sprintf(
"You are the coordinator. Your job is to break down this task into subtasks "
"and assign each to a worker. Available workers: %s.\n\n"
"Respond in this exact format:\n"
"WORKER:<name>\nTASK:<subtask>\n---\n"
"(repeat for each worker)\n\n"
"TASK: %s",
cw.workerNames(), task,
),
}
response, err := cw.coordinator.Run(ctx, []agent.Message{planMsg})
if err != nil {
return "", fmt.Errorf("coordinator plan: %w", err)
}
// Step 2: 解析分配结果,并行执行 Worker
assignments := parseAssignments(response.Content)
if len(assignments) == 0 {
// Coordinator 决定不分配,直接返回
return response.Content, nil
}
workerResults := cw.executeWorkers(ctx, assignments)
// Step 3: Coordinator 汇总结果
summaryPrompt := agent.Message{
Role: "user",
From: "user",
Content: fmt.Sprintf(
"Synthesize the following worker results into a coherent final answer.\n\n%s",
formatWorkerResults(workerResults),
),
}
summary, err := cw.coordinator.Run(ctx, []agent.Message{summaryPrompt})
if err != nil {
return "", fmt.Errorf("coordinator summary: %w", err)
}
return summary.Content, nil
}
func (cw *CoordinatorWorker) workerNames() string {
names := make([]string, len(cw.workers))
for i, w := range cw.workers {
names[i] = w.Name()
}
return strings.Join(names, ", ")
}
func (cw *CoordinatorWorker) executeWorkers(
ctx context.Context, assignments map[string]string,
) map[string]string {
results := make(map[string]string)
var mu sync.Mutex
var wg sync.WaitGroup
workerMap := make(map[string]agent.Agent)
for _, w := range cw.workers {
workerMap[w.Name()] = w
}
for name, subtask := range assignments {
worker, ok := workerMap[name]
if !ok {
continue
}
wg.Add(1)
go func(worker agent.Agent, subtask string) {
defer wg.Done()
taskMsg := agent.Message{
Role: "user",
From: "coordinator",
Content: subtask,
}
resp, err := worker.Run(ctx, []agent.Message{taskMsg})
if err != nil {
mu.Lock()
results[worker.Name()] = fmt.Sprintf("ERROR: %v", err)
mu.Unlock()
return
}
mu.Lock()
results[worker.Name()] = resp.Content
mu.Unlock()
}(worker, subtask)
}
wg.Wait()
return results
}
// parseAssignments 解析 Coordinator 的分配输出
// 格式: WORKER:<name>\nTASK:<subtask>\n---
func parseAssignments(content string) map[string]string {
assignments := make(map[string]string)
blocks := strings.Split(content, "---")
for _, block := range blocks {
block = strings.TrimSpace(block)
lines := strings.Split(block, "\n")
var name, task string
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "WORKER:") {
name = strings.TrimSpace(strings.TrimPrefix(line, "WORKER:"))
} else if strings.HasPrefix(line, "TASK:") {
task = strings.TrimSpace(strings.TrimPrefix(line, "TASK:"))
}
}
if name != "" && task != "" {
assignments[name] = task
}
}
return assignments
}
func formatWorkerResults(results map[string]string) string {
var b strings.Builder
for name, result := range results {
fmt.Fprintf(&b, "### %s\n%s\n\n", name, result)
}
return b.String()
}
3.2 Debate 模式
# Debate 其实是 Go 实现,但核心逻辑这样展示更清晰
# 实际上是 debate_orchestrator.go
Debate 模式的精髓:两个 Agent 互相迭代辩论 N 轮,然后裁判给出最终结论。
// orchestrator/debate.go
package orchestrator
import (
"context"
"fmt"
"strings"
"your-project/agent"
)
// DebateConfig 辩论配置
type DebateConfig struct {
MaxRounds int // 最大辩论轮数
AgreementThreshold float64 // 当双方回复相似度 > 该值时提前结束(未来扩展)
}
// DebateOrchestrator 辩论编排器
// 参与者: Proposer (正方) + Opponent (反方) + Judge (裁判)
type DebateOrchestrator struct {
proposer agent.Agent
opponent agent.Agent
judge agent.Agent
config DebateConfig
}
func NewDebate(
proposer, opponent, judge agent.Agent,
config DebateConfig,
) *DebateOrchestrator {
if config.MaxRounds == 0 {
config.MaxRounds = 3
}
return &DebateOrchestrator{
proposer: proposer,
opponent: opponent,
judge: judge,
config: config,
}
}
func (d *DebateOrchestrator) Run(ctx context.Context, topic string) (string, error) {
history := []agent.Message{
{Role: "user", From: "user", Content: fmt.Sprintf(
"DEBATE TOPIC: %s\n\nProposer: make your case.\nOpponent: critique the proposal.\n"+
"Each round, respond to the other's last argument.",
topic,
)},
}
for round := 1; round <= d.config.MaxRounds; round++ {
// Proposer 发言
contextMsg := agent.Message{
Role: "user",
From: "system",
Content: fmt.Sprintf("Round %d: You are the PROPOSER. Make your argument or respond to the opponent's critique.",
round),
}
roundHistory := append(history, contextMsg)
propMsg, err := d.proposer.Run(ctx, roundHistory)
if err != nil {
return "", fmt.Errorf("proposer round %d: %w", round, err)
}
history = append(history, propMsg)
// Opponent 回应
contextMsg = agent.Message{
Role: "user",
From: "system",
Content: fmt.Sprintf("Round %d: You are the OPPONENT. Critique the proposer's latest argument.",
round),
}
roundHistory = append(history, contextMsg)
oppMsg, err := d.opponent.Run(ctx, roundHistory)
if err != nil {
return "", fmt.Errorf("opponent round %d: %w", round, err)
}
history = append(history, oppMsg)
}
// Judge 做最终裁决
judgePrompt := agent.Message{
Role: "user",
From: "system",
Content: fmt.Sprintf(
"You are the JUDGE. Review the following debate on topic: %s\n\n"+
"DEBATE TRANSCRIPT:\n%s\n\n"+
"Give your final verdict. State which side made the stronger case and why. "+
"Output format:\nVERDICT: <proposer/opponent/tie>\nREASONING: <your reasoning>",
topic, formatDebateHistory(history),
),
}
verdict, err := d.judge.Run(ctx, []agent.Message{judgePrompt})
if err != nil {
return "", fmt.Errorf("judge: %w", err)
}
return fmt.Sprintf("Debate complete (%d rounds).\n\n%s", d.config.MaxRounds, verdict.Content), nil
}
func formatDebateHistory(history []agent.Message) string {
var b strings.Builder
for _, msg := range history {
if msg.Role == "user" && (msg.From == "system" || msg.From == "user") {
continue // 跳过系统提示,只留辩论内容
}
fmt.Fprintf(&b, "[%s] %s\n\n", msg.From, msg.Content)
}
return b.String()
}
3.3 Pipeline 模式
// orchestrator/pipeline.go
package orchestrator
import (
"context"
"fmt"
"your-project/agent"
)
// Pipeline 顺序流水线编排
type Pipeline struct {
stages []PipelineStage
}
// PipelineStage 流水线的一个阶段
type PipelineStage struct {
Name string
Agent agent.Agent
// Transform 可选:对前一个阶段的输出做变换再传给当前 Agent
Transform func(prevOutput string) string
}
func NewPipeline(stages ...PipelineStage) *Pipeline {
return &Pipeline{stages: stages}
}
func (p *Pipeline) Run(ctx context.Context, input string) (string, error) {
current := input
for i, stage := range p.stages {
// 可选:变换上游输出
if stage.Transform != nil {
current = stage.Transform(current)
}
taskMsg := agent.Message{
Role: "user",
From: "user",
Content: current,
}
fmt.Printf("[PIPELINE] Stage %d/%d: %s\n", i+1, len(p.stages), stage.Name)
result, err := stage.Agent.Run(ctx, []agent.Message{taskMsg})
if err != nil {
return "", fmt.Errorf("pipeline stage %s: %w", stage.Name, err)
}
current = result.Content
}
return current, nil
}
四、使用示例
4.1 代码审查(Coordinator-Worker)
func main() {
llm := &OpenAIClient{Model: "gpt-4o-mini"} // 你实现的 LLMClient
// 定义 Worker Agents
securityReviewer := agent.NewLLMAgent(
"security",
"You are a security reviewer. Check code for SQL injection, XSS, hardcoded secrets, and unsafe deserialization.",
llm,
)
perfReviewer := agent.NewLLMAgent(
"performance",
"You are a performance engineer. Find N+1 queries, unnecessary allocations, and blocking I/O patterns.",
llm,
)
styleReviewer := agent.NewLLMAgent(
"style",
"You are a code style reviewer. Check naming conventions, function length (< 50 lines), and cyclomatic complexity.",
llm,
)
// Coordinator
coordinator := agent.NewLLMAgent(
"coordinator",
"You are a code review coordinator. Break down code review into subtasks for security, performance, and style reviewers.",
llm,
)
// 编排
orchestrator := orchestrator.NewCoordinatorWorker(coordinator,
[]agent.Agent{securityReviewer, perfReviewer, styleReviewer},
)
code := `...` // 待审查的代码
result, err := orchestrator.Run(context.Background(), code)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
4.2 架构选型辩论
debate := orchestrator.NewDebate(
agent.NewLLMAgent("proposer", "Argue for a microservices architecture.", llm),
agent.NewLLMAgent("opponent", "Argue against microservices. Prefer a modular monolith.", llm),
agent.NewLLMAgent("judge", "You are an impartial architect. Evaluate both sides and decide.", llm),
orchestrator.DebateConfig{MaxRounds: 3},
)
verdict, _ := debate.Run(context.Background(),
"Should we use microservices or a modular monolith for our new e-commerce platform with 20 engineers?")
fmt.Println(verdict)
4.3 文档生成流水线
pipeline := orchestrator.NewPipeline(
orchestrator.PipelineStage{
Name: "research",
Agent: agent.NewLLMAgent("researcher", "Research this topic thoroughly. Provide key facts and concepts.", llm),
},
orchestrator.PipelineStage{
Name: "outline",
Agent: agent.NewLLMAgent("outliner", "Create a structured outline with 3-5 sections.", llm),
},
orchestrator.PipelineStage{
Name: "write",
Agent: agent.NewLLMAgent("writer", "Write a comprehensive article following the outline. Use concrete examples.", llm),
},
orchestrator.PipelineStage{
Name: "polish",
Agent: agent.NewLLMAgent("polisher", "Polish the writing. Improve clarity, fix grammar, ensure consistent tone.", llm),
},
)
article, _ := pipeline.Run(context.Background(),
"Write a technical article about database indexing strategies for PostgreSQL.")
fmt.Println(article)
五、扩展方向
5.1 上下文窗口管理
多 Agent 最头疼的问题:每传一轮,历史消息就翻倍。三招应对:
// context_manager.go
// 策略1: 滑动窗口 — 只保留最近 N 条消息
func slidingWindow(history []agent.Message, maxTokens int) []agent.Message {
tokens := 0
for i := len(history) - 1; i >= 0; i-- {
tokens += len(history[i].Content) / 4 // 粗略估算
if tokens > maxTokens {
return history[i+1:]
}
}
return history
}
// 策略2: 摘要压缩 — 对旧消息生成摘要
func summarizeHistory(history []agent.Message, llm agent.LLMClient) agent.Message {
// 将前 N-3 轮压缩为一个 summary message
// ...
}
// 策略3: 分层 — 子 Agent 只看自己的子任务,不接触完整 context
5.2 超时与取消
// 给每个 Worker 独立的超时
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 在 executeWorkers 中使用 ctx 传递
5.3 人机协同断点
// 在 Pipeline 的关键阶段插入人工审核
type HumanReviewStage struct {
name string
reviewFunc func(output string) (approved bool, feedback string)
}
func (h *HumanReviewStage) Run(ctx context.Context, history []agent.Message) (agent.Message, error) {
content := history[len(history)-1].Content
approved, feedback := h.reviewFunc(content)
if !approved {
return agent.Message{
Role: "human",
From: h.name,
Content: fmt.Sprintf("REJECTED. Feedback: %s\n\nOriginal output:\n%s", feedback, content),
}, nil
}
return history[len(history)-1], nil
}
六、总结
| 指标 | LangChain / CrewAI | 本文实现 |
|---|---|---|
| 代码行数 | ~500,000+ | ~500 |
| 启动时间 | 2-5s | < 0.1s |
| 依赖数量 | 50+ | 0 |
| 自定义能力 | 需要读源码 | 直接改 |
| 调试难度 | 高(层层包装) | 低(透明的消息传递) |
核心洞察:多 Agent 编排的本质就是消息路由。三个模式对应三种拓扑——星型、全连接、线性,50 行一个模式就够。当你的需求超出这 500 行能承载的范围时,再考虑引入框架也不迟。
代码依赖:仅 Go 标准库 + 一个你自己实现的 LLMClient(对接 OpenAI / Anthropic / Ollama)。
更多推荐


所有评论(0)