主Agent与多个协同子Agent的方案设计

0 阅读7分钟

前言

如今的大模型应用架构设计基本都是一个主Agent携带多个子Agent。

主Agent负责调度其他垂类Agent,子Agent负责单一领域的角色,属于垂直域专家。

架构上比较类似这样:

┌─────────────────────────────────────────────────────────┐
│                    主 Agent(Orchestrator)              │
│  职责:理解用户意图、分解任务、协调子 Agent、聚合结果   │
└──────────────────────┬──────────────────────────────────┘
                       │
        ┌──────────────┼──────────────┬──────────────┐
        │              │              │              │
        ▼              ▼              ▼              ▼
   ┌────────┐    ┌────────┐    ┌────────┐    ┌────────┐
   │差旅Agent│   │日程Agent│   │支付Agent│   │通知Agent│
   │(Travel)│   │(Calendar)│  │(Payment)│  │(Alert) │
   └────────┘    └────────┘    └────────┘    └────────┘
        │              │              │              │
        └──────────────┴──────────────┴──────────────┘
                       │
        ┌──────────────┼──────────────┐
        │              │              │
        ▼              ▼              ▼
   数据库           API 服务        外部服务
   (DB)          (Flights,        (Payment,
                  Hotels,          Email,
                 Trains)          SMS)

那一个基本的LLM应用框架一般怎么设计?本文基于Midwayjs来解读分析。

Agent&提示词设计

基类Agent

所有Agent都集成于该类,核心触发如下能力。

  1. 上下文管理;
  2. 大模型调用;
  3. 提示词注入;
// src/agent/base-agent.ts
import { Logger } from "@midwayjs/core"
import { LLMService } from "@/service/llm.service"

interface Message {
  role: "system" | "user" | "assistant"
  content: string
}

interface ToolCall {
  name: string
  arguments: Record<string, any>
  id?: string
}

/**
 * Agent 基类
 * 所有 Agent 都继承这个基类
 */
export abstract class BaseAgent {
  @Logger()
  logger: any

  protected llmService: LLMService
  protected conversationHistory: Message[] = []

  constructor(llmService: LLMService) {
    this.llmService = llmService
  }

  /**
   * 初始化 Agent
   * 1. 设置系统提示词
   * 2. 注入工具定义
   * 3. 初始化对话历史
   */
  protected initializeAgent(
    systemPrompt: string,
    tools: any[]
  ): void {
    this.logger.info(`[${this.getAgentName()}] 初始化 Agent`)

    // Step 1: 清空历史对话
    this.conversationHistory = []

    // Step 2: 添加系统提示词
    const enrichedSystemPrompt = this.enrichSystemPrompt(
      systemPrompt,
      tools
    )

    this.conversationHistory.push({
      role: "system",
      content: enrichedSystemPrompt,
    })

    this.logger.info(
      `[${this.getAgentName()}] Agent 初始化完成,已注入 ${tools.length} 个工具`
    )
  }

  /**
   * 增强系统提示词(注入工具定义)
   */
  private enrichSystemPrompt(systemPrompt: string, tools: any[]): string {
    const toolDescriptions = tools
      .map(
        (tool) => `
### 工具:${tool.name}
描述:${tool.description}
参数:${JSON.stringify(tool.parameters, null, 2)}
`
      )
      .join("\n")

    return `
${systemPrompt}

## 可用的工具

${toolDescriptions}

## 工具调用格式

当你需要使用工具时,请返回以下 JSON 格式:
\`\`\`json
{
  "type": "tool_call",
  "tool_name": "工具名称",
  "arguments": {
    "参数1": "值1",
    "参数2": "值2"
  }
}
\`\`\`

重要:
1. 每次只调用一个工具
2. 工具会返回结果,你会收到 "tool_result" 角色的消息
3. 根据工具结果继续推理和决策
4. 最终向用户返回友好的文字回复
`
  }

  /**
   * 与大模型交互(核心方法)
   */
  async callLLM(userMessage: string): Promise<string> {
    this.logger.info(
      `[${this.getAgentName()}] 用户消息: ${userMessage}`
    )

    // 1. 添加用户消息到历史
    this.conversationHistory.push({
      role: "user",
      content: userMessage,
    })

    // 2. 调用大模型
    let response = await this.llmService.call({
      model: "gpt-4",
      messages: this.conversationHistory,
      temperature: 0.7,
      maxTokens: 2000,
    })

    this.logger.info(
      `[${this.getAgentName()}] 模型响应: ${response.content.substring(0, 100)}...`
    )

    // 3. 检查是否是工具调用
    let finalResponse = response.content
    let toolCalls = this.extractToolCalls(response.content)

    // 4. 如果有工具调用,递归执行直到没有工具调用
    while (toolCalls.length > 0) {
      this.logger.info(
        `[${this.getAgentName()}] 检测到工具调用: ${toolCalls.map((t) => t.name).join(", ")}`
      )

      // 添加助手的响应到历史
      this.conversationHistory.push({
        role: "assistant",
        content: response.content,
      })

      // 执行所有工具调用
      const toolResults = await Promise.all(
        toolCalls.map((call) =>
          this.executeTool(call.name, call.arguments)
        )
      )

      // 5. 将工具结果添加到历史
      const toolResultMessage = toolResults
        .map(
          (result, index) => `
[工具结果 ${index + 1}]
工具:${toolCalls[index].name}
参数:${JSON.stringify(toolCalls[index].arguments)}
结果:${JSON.stringify(result, null, 2)}
`
        )
        .join("\n")

      this.conversationHistory.push({
        role: "user",
        content: `工具执行结果:\n${toolResultMessage}`,
      })

      this.logger.info(
        `[${this.getAgentName()}] 工具执行完成,继续推理...`
      )

      // 6. 再次调用大模型,让它基于工具结果继续推理
      response = await this.llmService.call({
        model: "gpt-4",
        messages: this.conversationHistory,
        temperature: 0.7,
        maxTokens: 2000,
      })

      this.logger.info(
        `[${this.getAgentName()}] 后续模型响应: ${response.content.substring(0, 100)}...`
      )

      // 7. 再次检查是否有工具调用
      toolCalls = this.extractToolCalls(response.content)
      finalResponse = response.content
    }

    // 8. 添加最终回复到历史
    this.conversationHistory.push({
      role: "assistant",
      content: finalResponse,
    })

    return finalResponse
  }

  /**
   * 提取工具调用(从模型响应中)
   */
  private extractToolCalls(content: string): ToolCall[] {
    const toolCalls: ToolCall[] = []

    // 匹配 JSON 格式的工具调用
    const jsonMatches = content.match(/```json\n([\s\S]*?)\n```/g)

    if (jsonMatches) {
      jsonMatches.forEach((match) => {
        try {
          const json = match.replace(/```json\n/g, "").replace(/\n```/g, "")
          const parsed = JSON.parse(json)

          if (parsed.type === "tool_call") {
            toolCalls.push({
              name: parsed.tool_name,
              arguments: parsed.arguments,
            })
          }
        } catch (error) {
          this.logger.warn(`[${this.getAgentName()}] 无法解析 JSON: ${match}`)
        }
      })
    }

    return toolCalls
  }

  /**
   * 执行工具(由子类实现)
   */
  protected abstract executeTool(
    toolName: string,
    arguments: Record<string, any>
  ): Promise<any>

  /**
   * 获取 Agent 名称
   */
  protected abstract getAgentName(): string
}

工具定义&设计

工具定义核心是基于约定式的配置体,来提供给大模型。

这些工具可以是mcp,可以是function call,在工具中增加type即可扩展。

// src/tools/travel-tools.ts

/**
 * 差旅工具定义
 * 这些工具会被注入到 Agent 的提示词中
 */
export const TRAVEL_TOOLS = [
  {
    name: "search_flights",
    description: "搜索机票,返回可用的航班列表",
    parameters: {
      type: "object",
      properties: {
        from: {
          type: "string",
          description: "出发城市(如:北京、上海)",
        },
        to: {
          type: "string",
          description: "目的城市",
        },
        date: {
          type: "string",
          description: "出发日期(格式:YYYY-MM-DD)",
        },
        return_date: {
          type: "string",
          description: "返回日期(可选,格式:YYYY-MM-DD)",
        },
      },
      required: ["from", "to", "date"],
    },
  },
  {
    name: "search_hotels",
    description: "搜索酒店,返回可用的酒店列表",
    parameters: {
      type: "object",
      properties: {
        city: {
          type: "string",
          description: "目的城市",
        },
        check_in: {
          type: "string",
          description: "入住日期(格式:YYYY-MM-DD)",
        },
        check_out: {
          type: "string",
          description: "退房日期(格式:YYYY-MM-DD)",
        },
        max_price: {
          type: "number",
          description: "最高价格(可选,单位:元)",
        },
      },
      required: ["city", "check_in", "check_out"],
    },
  },
  {
    name: "book_trip",
    description: "预订机票和酒店,返回订单号",
    parameters: {
      type: "object",
      properties: {
        flight_id: {
          type: "string",
          description: "航班 ID",
        },
        hotel_id: {
          type: "string",
          description: "酒店 ID",
        },
        passengers: {
          type: "number",
          description: "乘客人数",
        },
      },
      required: ["flight_id", "hotel_id"],
    },
  },
  {
    name: "get_trip_details",
    description: "获取已预订差旅的详细信息",
    parameters: {
      type: "object",
      properties: {
        trip_id: {
          type: "string",
          description: "订单号",
        },
      },
      required: ["trip_id"],
    },
  },
  {
    name: "cancel_trip",
    description: "取消已预订的差旅",
    parameters: {
      type: "object",
      properties: {
        trip_id: {
          type: "string",
          description: "订单号",
        },
        reason: {
          type: "string",
          description: "取消原因(可选)",
        },
      },
      required: ["trip_id"],
    },
  },
]

export const CALENDAR_TOOLS = [
  {
    name: "add_calendar_event",
    description: "添加日历事件",
    parameters: {
      type: "object",
      properties: {
        title: {
          type: "string",
          description: "事件标题",
        },
        start_date: {
          type: "string",
          description: "开始时间(格式:YYYY-MM-DD HH:mm)",
        },
        end_date: {
          type: "string",
          description: "结束时间(格式:YYYY-MM-DD HH:mm)",
        },
        description: {
          type: "string",
          description: "事件描述",
        },
      },
      required: ["title", "start_date", "end_date"],
    },
  },
  {
    name: "get_calendar_events",
    description: "查询特定日期的日历事件",
    parameters: {
      type: "object",
      properties: {
        date: {
          type: "string",
          description: "查询日期(格式:YYYY-MM-DD)",
        },
      },
      required: ["date"],
    },
  },
]

export const PAYMENT_TOOLS = [
  {
    name: "process_payment",
    description: "处理支付请求",
    parameters: {
      type: "object",
      properties: {
        order_id: {
          type: "string",
          description: "订单号",
        },
        amount: {
          type: "number",
          description: "金额(单位:元)",
        },
        payment_method: {
          type: "string",
          enum: ["credit_card", "debit_card", "wechat", "alipay"],
          description: "支付方式",
        },
      },
      required: ["order_id", "amount", "payment_method"],
    },
  },
]

export const ALERT_TOOLS = [
  {
    name: "send_notification",
    description: "发送通知给用户",
    parameters: {
      type: "object",
      properties: {
        title: {
          type: "string",
          description: "通知标题",
        },
        content: {
          type: "string",
          description: "通知内容",
        },
        channels: {
          type: "array",
          items: { type: "string", enum: ["email", "sms", "app"] },
          description: "通知渠道",
        },
      },
      required: ["title", "content", "channels"],
    },
  },
]

MCP设计

Agent基于多个Mcp能力的提供从而实现更垂直的领域能力。

因此Mcp也可以单独设计出来。

// src/mcp/types.ts

/**
 * MCP 工具定义
 */
export interface MCPTool {
  name: string
  description: string
  inputSchema: {
    type: "object"
    properties: Record<string, any>
    required: string[]
  }
}

/**
 * MCP 资源定义
 */
export interface MCPResource {
  uri: string
  name: string
  description: string
  mimeType: string
  contents: string
}

/**
 * MCP 提示词定义
 */
export interface MCPPrompt {
  name: string
  description: string
  arguments?: Array<{
    name: string
    description: string
    required?: boolean
  }>
}

/**
 * MCP 工具调用请求
 */
export interface MCPToolCallRequest {
  toolName: string
  arguments: Record<string, any>
}

/**
 * MCP 工具执行结果
 */
export interface MCPToolResult {
  success: boolean
  data?: any
  error?: string
}

/**
 * MCP 服务器接口
 */
export interface IMCPServer {
  // 获取服务器信息
  getServerInfo(): Promise<{
    name: string
    version: string
    capabilities: string[]
  }>

  // 列出所有可用工具
  listTools(): Promise<MCPTool[]>

  // 执行工具
  callTool(request: MCPToolCallRequest): Promise<MCPToolResult>

  // 列出所有可用资源
  listResources(): Promise<MCPResource[]>

  // 获取资源内容
  getResource(uri: string): Promise<MCPResource>

  // 列出所有可用提示词
  listPrompts(): Promise<MCPPrompt[]>

  // 获取提示词内容
  getPrompt(name: string, arguments?: Record<string, string>): Promise<string>
}

有了AgentMcp,本质上完整的一次自然语言对话 -> 反馈的系统流转图就很清晰了。

基于这套框架来扩展即可。

一次完整对话到反馈的时序图大概是这样:

用户                主Agent              子Agent           MCP服务器         LLM模型          数据库
 │                   │                   │                 │                │                │
 │ 用户请求:         │                   │                 │                │                │
 │ "帮我订一张      │                   │                 │                │                │
 │  明天北京到      │                   │                 │                │                │
 │  上海的机票      │                   │                 │                │                │
 │  和酒店"        │                   │                 │                │                │
 │──────────────────>│                   │                 │                │                │
 │                   │                   │                 │                │                │
 │                   │ 1. 初始化对话      │                 │                │                │
 │                   │    构建系统提示词  │                 │                │                │
 │                   │────────────────────────────────────>│                │                │
 │                   │                   │                 │                │                │
 │                   │ 2. 请求可用工具列表│                 │                │                │
 │                   │──────────────────────────────────────────────────────>│                │
 │                   │                   │                 │                │                │
 │                   │ 3. 返回工具列表    │                 │                │                │
 │                   │<──────────────────────────────────────────────────────│                │
 │                   │    (search_flights, search_hotels,   │                │                │
 │                   │     book_trip, etc.)                 │                │                │
 │                   │                   │                 │                │                │
 │                   │ 4. 获取提示词模板  │                 │                │                │
 │                   │──────────────────────────────────────>│                │                │
 │                   │                   │                 │                │                │
 │                   │ 5. 返回提示词      │                 │                │                │
 │                   │<──────────────────────────────────────│                │                │
 │                   │   (booking_recommendation等)         │                │                │
 │                   │                   │                 │                │                │
 │                   │ 6. 构建系统消息    │                 │                │                │
 │                   │    (系统提示词+工具定义+提示词)      │                │                │
 │                   │    users消息="用户请求内容"         │                │                │
 │                   │                   │                 │                │                │
 │                   │ 7. 调用 LLM        │                 │                │                │
 │                   │──────────────────────────────────────────────────────>│                │
 │                   │                   │                 │                │  分析意图       │
 │                   │                   │                 │                │  (BOOK_TRIP)    │
 │                   │                   │                 │                │  提取参数       │
 │                   │                   │                 │                │  (from, to,date)│
 │                   │                   │                 │                │  生成工具调用   │
 │                   │                   │                 │                │                │
 │                   │ 8. LLM 响应        │                 │                │                │
 │                   │<──────────────────────────────────────────────────────│                │
 │                   │    {                                 │                │                │
 │                   │      "type": "tool_call",            │                │                │
 │                   │      "tool_name": "search_flights",  │                │                │
 │                   │      "arguments": {                  │                │                │
 │                   │        "from": "北京",                │                │                │
 │                   │        "to": "上海",                  │                │                │
 │                   │        "date": "明天"                 │                │                │
 │                   │      }                               │                │                │
 │                   │    }                                 │                │                │
 │                   │                   │                 │                │                │
 │                   │ 9. 检测到工具调用, │                 │                │                │
 │                   │    路由到子Agent   │                 │                │                │
 │                   │────────────────────>│                │                │                │
 │                   │                   │                 │                │                │
 │                   │                   │ 10. 子Agent     │                │                │
 │                   │                   │     处理工具调用 │                │                │
 │                   │                   │──────────────────>│                │                │
 │                   │                   │                 │                │                │
 │                   │                   │ 11. Travel MCP  │                │                │
 │                   │                   │     执行         │                │                │
 │                   │                   │     search_flights│               │                │
 │                   │                   │                 │ 查询数据库     │                │
 │                   │                   │                 │────────────────────────────────>│
 │                   │                   │                 │                │                │
 │                   │                   │                 │ 返回机票列表   │                │
 │                   │                   │                 │<────────────────────────────────│
 │                   │                   │                 │                │                │
 │                   │ 12. 返回工具结果   │                 │                │                │
 │                   │<────────────────────                 │                │                │
 │                   │    [                                │                │                │ │                   │      {                               │                │                │ │                   │        "id": "CA123",                │                │                │ │                   │        "airline": "国航",             │                │                │ │                   │        "departure": "10:00",         │                │                │ │                   │        "price": 1200                 │                │                │ │                   │      },                              │                │                │ │                   │      ...                             │                │                │ │                   │    ]                                 │                │                │
 │                   │                   │                 │                │                │
 │                   │ 13. 添加工具结果   │                 │                │                │
 │                   │     到对话历史     │                 │                │                │
 │                   │     再次调用 LLM   │                 │                │                │
 │                   │──────────────────────────────────────────────────────>│                │
 │                   │                   │                 │                │  分析机票      │
 │                   │                   │                 │                │  生成下一个工具│
 │                   │                   │                 │                │  调用:         │
 │                   │                   │                 │                │  search_hotels │
 │                   │                   │                 │                │                │
 │                   │ 14. LLM 响应(第2次)│                 │                │                │
 │                   │<──────────────────────────────────────────────────────│                │
 │                   │    {                                 │                │                │
 │                   │      "type": "tool_call",            │                │                │
 │                   │      "tool_name": "search_hotels",   │                │                │
 │                   │      "arguments": {                  │                │                │
 │                   │        "city": "上海",                │                │                │
 │                   │        "check_in": "明天",            │                │                │
 │                   │        "check_out": "后天"            │                │                │
 │                   │      }                               │                │                │
 │                   │    }                                 │                │                │
 │                   │                   │                 │                │                │
 │                   │ 15. 再次路由到子Agent│                │                │                │
 │                   │────────────────────>│                │                │                │
 │                   │                   │                 │                │                │
 │                   │                   │ 16. 执行        │                │                │
 │                   │                   │     search_hotels│               │                │
 │                   │                   │──────────────────>│                │                │
 │                   │                   │                 │                │ 查询酒店       │
 │                   │                   │                 │────────────────────────────────>│
 │                   │                   │                 │                │                │
 │                   │                   │                 │ 返回酒店列表   │                │
 │                   │                   │                 │<────────────────────────────────│
 │                   │                   │                 │                │                │
 │                   │ 17. 返回酒店结果   │                 │                │                │
 │                   │<────────────────────                 │                │                │
 │                   │                   │                 │                │                │
 │                   │ 18. 再次调用 LLM   │                 │                │                │
 │                   │     (决定下一步)   │                 │                │                │
 │                   │──────────────────────────────────────────────────────>│                │
 │                   │                   │                 │                │  分析酒店      │
 │                   │                   │                 │                │  推荐最佳套餐  │
 │                   │                   │                 │                │  生成工具调用: │
 │                   │                   │                 │                │  book_trip     │
 │                   │                   │                 │                │                │
 │                   │ 19. LLM 响应(第3次)│                 │                │                │
 │                   │<──────────────────────────────────────────────────────│                │
 │                   │    {                                 │                │                │
 │                   │      "type": "tool_call",            │                │                │
 │                   │      "tool_name": "book_trip",       │                │                │
 │                   │      "arguments": {                  │                │                │
 │                   │        "flight_id": "CA123",         │                │                │
 │                   │        "hotel_id": "SH001"           │                │                │
 │                   │      }                               │                │                │
 │                   │    }                                 │                │                │
 │                   │                   │                 │                │                │
 │                   │ 20. 路由到子Agent  │                 │                │                │
 │                   │ (预订差旅)         │                 │                │                │
 │                   │────────────────────>│                │                │                │
 │                   │                   │                 │                │                │
 │                   │                   │ 21. 执行book_trip│               │                │
 │                   │                   │──────────────────>│                │                │
 │                   │                   │                 │                │ 创建订单       │
 │                   │                   │                 │────────────────────────────────>│
 │                   │                   │                 │                │                │
 │                   │                   │                 │ 返回订单号     │                │
 │                   │                   │                 │<────────────────────────────────│
 │                   │                   │                 │                │                │
 │                   │ 22. 返回预订结果   │                 │                │                │
 │                   │<────────────────────                 │                │                │
 │                   │    {                                 │                │                │
 │                   │      "trip_id": "TRIP_001",          │                │                │
 │                   │      "status": "confirmed",          │                │                │
 │                   │      "total_cost": 3000              │                │                │
 │                   │    }                                 │                │                │
 │                   │                   │                 │                │                │
 │                   │ 23. 调用Calendar MCP│                │                │                │
 │                   │     添加日程        │                │                │                │
 │                   │────────────────────────────────────────────────────────>│               │
 │                   │                   │                 │                │ 添加日历事件   │
 │                   │                   │                 │────────────────────────────────>│
 │                   │                   │                 │                │                │
 │                   │                   │                 │ 返回事件ID     │                │
 │                   │                   │                 │<────────────────────────────────│
 │                   │                   │                 │                │                │
 │                   │ 24. 调用Payment MCP│                 │                │                │
 │                   │     处理支付        │                 │                │                │
 │                   │────────────────────────────────────────────────────────>│               │
 │                   │                   │                 │                │ 创建支付单     │
 │                   │                   │                 │────────────────────────────────>│
 │                   │                   │                 │                │                │
 │                   │                   │                 │ 返回交易ID     │                │
 │                   │                   │                 │<────────────────────────────────│
 │                   │                   │                 │                │                │
 │                   │ 25. 调用Alert MCP  │                 │                │                │
 │                   │     发送通知        │                 │                │                │
 │                   │────────────────────────────────────────────────────────>│               │
 │                   │                   │                 │                │ 记录通知        │
 │                   │                   │                 │────────────────────────────────>│
 │                   │                   │                 │                │                │
 │                   │ 26. 最后调用 LLM   │                 │                │                │
 │                   │     生成友好回复   │                 │                │                │
 │                   │──────────────────────────────────────────────────────>│                │
 │                   │                   │                 │                │ 总结整个过程    │
 │                   │                   │                 │                │ 生成用户友好    │
 │                   │                   │                 │                │ 的文字回复      │
 │                   │                   │                 │                │                │
 │                   │ 27. LLM 最终响应   │                 │                │                │
 │                   │<──────────────────────────────────────────────────────│                │
 │                   │    "好的,已为您预订了从北京       │                │                │
 │                   │     到上海的差旅。您的订单号是    │                │                │
 │                   │     TRIP_001,总费用3000元。     │                │                │
 │                   │     已添加到您的日程,并发送

本质上一句话总结:对话发起后,主Agent构建基础提示词进行首轮行为分析后,然后按需注入子Agent来递归/循环完成一轮对话。

结尾

如上就非常简单直观的结合代码,讲解了现在LLM大模型应用的核心架构和角色拆解。

希望对大家有所帮助。