别急着引入 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)。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐