多智能体与A2A

多智能体定义:

多智能体不是“多开几个模型调用”这么简单。它指的是:把复杂任务拆给多个专精的 Agent,让它们分工、路由、协作,再共同完成整体任务。

多智能体的核心变化不是“数量变多”,而是:角色开始分工、上下文开始隔离、控制流开始显式编排

A2A协议定义:

全称是 Agent-to-Agent。它是一种面向 Agent 系统互操作的开放协议,目标是让不同 Agent 能以更标准化的方式发现彼此、发送任务、交换消息、返回结果。

 

A2A 里面几个很核心的概念包括:

  • Agent Card:相当于 Agent 的“名片 / 能力说明”
  • Task:一项被发给远程 Agent 的任务
  • Message:围绕任务交换的消息
  • Artifact:任务过程或结果产出的内容

如果把它们串成一条最小调用链,可以这样记:

  1. 先发现 Agent:调用方先读取 Agent Card,确认对方会什么、支持什么输入输出。
  2. 再提交 Task:把任务目标、上下文消息、必要参数发给远程 Agent。
  3. 过程中跟状态:长任务通常不是一次就结束,调用方会通过轮询、流式更新或通知拿到任务进度。
  4. 最后取结果:读取最终 Message / Artifact,把它当成另一套 Agent 的产出继续接到自己的系统里。

A2A和MCP的区别:

  • MCP:Agent / 模型怎么接工具、资源、上下文
  • A2A:Agent 和 Agent 怎么协作

多智能体模式

  • Subagents(子代理):主 Agent 把子 Agent 当工具来调
  • Handoffs(交接):不同 Agent 之间可以交接控制权
  • Skills(技能):单 Agent 按需加载专业上下文 / 技能包
  • Route(路由):先做路由,再把任务交给更合适的 Agent
  • Custom workflow(自定义工作流):直接用 LangGraph 自定义工作流,把上述模式混合起来

从教学角度看,可以把它们分成两大类:

  • 一个主控中心在调别人:Subagents、Router
  • 控制权本身会转移:Handoffs

多智能体常见形态

  • 单智能体(Single Agent):一个 Agent 负责整条任务链路,适合简单任务和教程起步。
  • Supervisor(主管型):一个中心主管负责决定调用哪个子 Agent,适合统一入口和集中调度。
  • Handoff(交接型):当前 Agent 可以把控制权交给别的 Agent,更适合角色切换和会话延续。
  • Router(路由型):先判断任务属于哪类,再把任务交给某个专门 Agent。
  • Network / Peer-to-peer(网络型):多个 Agent 更平等地交换信息,没有单一主管,适合研究、协作式问题求解。
  • Hierarchical(层级型):多层主管和子主管分层拆任务,适合复杂组织结构。

使用场景:

场景 更适合的模式
单领域、小任务、统一控制 单智能体 / Skills
多工具、多专业角色、统一入口 Supervisor / Subagents
用户与不同角色来回切换 Handoffs
先分类再交给不同专家 Router
复杂系统、强流程编排 Custom workflow(LangGraph)

SuperVisor:

Supervisor(主管型)是最适合初学者入门的一种。原因很简单:有一个中心主管,结构更好理解;控制流更集中;更接近“项目经理分配任务”的直觉。

 Supervisor 结构里,最核心的不是“子 Agent 有几个”,而是:

  • 主管是不是唯一调度中心
  • 子 Agent 是否专注自己的狭窄领域
  • 用户是否主要和主管交互

这个模式特别适合:企业助手、旅行预订、客服分流、多部门协作场景

案例:

# 子 Agent 用 langchain.agents.create_agent,主管用 langgraph_supervisor.create_supervisor;交互式输入 + 流式输出 + 简单中文过滤
# 用 create_agent 定义子 Agent,再由 create_supervisor 统一调度,形成更贴近当前主流写法的多智能体结构
# 这里的“主管调子 Agent”本质上对应官方多智能体文档里的 Subagents 模式;主管负责统一入口与路由,子 Agent 负责狭窄领域任务
# pip install langgraph-supervisor;子 Agent 的工具函数必须具备清晰 docstring,便于模型绑定工具模式
# create_supervisor(...).compile() 得到可 stream/invoke 的图;主管 prompt 不只是提示词,更是在约束整个调度流程与角色边界
# 

import os
import re

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langgraph_supervisor import create_supervisor
from dotenv import load_dotenv

load_dotenv(encoding="utf-8")

# 1. 初始化大语言模型
def init_llm_model() -> ChatOpenAI:
    return ChatOpenAI(
        model="qwen-plus",
        api_key=os.getenv("aliQwen-api"),
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
        temperature=0.1,
        max_tokens=1024,
    )
# 2. Tools(必须有 docstring)
def book_flight(from_airport: str, to_airport: str) -> str:
    """预订航班工具。根据出发机场和到达机场预订一张机票,并返回预订结果。"""
    return f"✅ 成功预订了从 {from_airport} 到 {to_airport} 的航班"


def book_hotel(hotel_name: str) -> str:
    """预订酒店工具。根据酒店名称完成酒店预订,并返回预订结果。"""
    return f"✅ 成功预订了 {hotel_name} 的住宿"

# 3. 子 Agent
flight_assistant = create_agent(
    model=init_llm_model(), tools=[book_flight], name="flight_assistant"
)

hotel_assistant = create_agent(
    model=init_llm_model(), tools=[book_hotel], name="hotel_assistant"
)

# 4. 创建 Supervisor,协调者主管
supervisor = create_supervisor(
    agents=[flight_assistant, hotel_assistant],
     model=init_llm_model(),
    prompt=(
        "你是旅行预订系统的调度主管,负责协调航班预订和酒店预订。\n\n"
        "当用户提出航班和酒店预订请求时,你的工作流程是:\n"
        "1. 首先调用flight_assistant来预订航班\n"
        "2. 然后调用hotel_assistant来预订酒店\n"
        "3. 收到两个助手的结果后,汇总并向用户报告\n"
        "4. 完成后结束对话\n\n"
        "重要规则:\n"
        "- 每个助手只能调用一次\n"
        "- 不要重复任何内容\n"
        "- 不要输出任何英文\n"
        "- 所有通信都使用中文\n"
    ),
    ).compile()
    
    
# 5. 消息过滤器:只服务于教学演示,帮助更清楚地观察主管和子 Agent 的有效中文输出
def filter_messages(chunk: dict) -> str:
    """提取并过滤消息,只返回中文内容,去除重复和英文"""
    output = ""

    if isinstance(chunk, dict):
        for role, payload in chunk.items():
            if isinstance(payload, dict) and "messages" in payload:
                for msg in payload["messages"]:
                    if hasattr(msg, "content") and msg.content:
                        content = msg.content.strip()

                        # 过滤英文系统消息
                        if (
                            content
                            and not content.startswith("Successfully")
                            and not content.startswith("Transferring")
                            and "Successfully transferred" not in content
                            and "transferred back to" not in content
                            and not content.startswith("帮我预订从")
                        ):

                            # 只保留中文内容
                            chinese_content = re.sub(
                                r'[^\u4e00-\u9fff,。!?:;""、\s\d✅]', "", content
                            )
                            if chinese_content and len(chinese_content.strip()) > 5:
                                output += f"{role}: {chinese_content.strip()}\n"

    return output


# 6. 主程序
def main():
    print("=" * 60)
    print(
        "智能旅行预订系统,由于大模型每次调用,可能出现预定不成功情况,这是正常反馈,主要是2026.2.8千问赠送奶茶活动,调用失败"
    )
    print("=" * 60)
    print()

    # 收集用户信息
    print("请按顺序提供以下信息:")
    print("-" * 40)

    # 1. 询问出发机场
    from_airport = input("1. 您的出发机场是哪里?: ").strip()
    while not from_airport:
        print("请输入有效的出发机场名称")
        from_airport = input("1. 您的出发机场是哪里?: ").strip()

    # 2. 询问到达机场
    to_airport = input("\n2. 您的到达机场是哪里?: ").strip()
    while not to_airport:
        print("请输入有效的到达机场名称")
        to_airport = input("2. 您的到达机场是哪里?: ").strip()

    # 3. 询问酒店名称
    hotel_name = input("\n3. 您要预订的酒店名称是什么?: ").strip()
    while not hotel_name:
        print("请输入有效的酒店名称")
        hotel_name = input("3. 您要预订的酒店名称是什么?: ").strip()

    # 构造更明确的用户请求
    user_request = (
        f"请帮我预订以下旅行安排:\n"
        f"1. 航班:从 {from_airport} 飞往 {to_airport}\n"
        f"2. 酒店:{hotel_name}\n"
        f"请完成这两个预订。"
    )

    print("\n" + "=" * 60)
    print("正在处理您的预订请求...")
    print("=" * 60)
    print()

    # 准备输入数据:Supervisor 图和普通 Agent 一样,入口仍然是 messages
    input_data = {"messages": [{"role": "user", "content": user_request}]}

    # 使用流式处理,便于观察主管如何依次调度两个子 Agent
    try:
        # 记录已打印内容,避免在演示时重复刷屏
        seen_contents = set()

        for chunk in supervisor.stream(input_data):
            filtered_output = filter_messages(chunk)
            if filtered_output:
                lines = filtered_output.strip().split("\n")
                for line in lines:
                    if line and line not in seen_contents:
                        print(line)
                        seen_contents.add(line)

        # 如果流式输出过少,就给一个兜底总结,避免读者误以为程序没有完成
        if len(seen_contents) < 2:
            print("\n" + "=" * 60)
            print("预订已完成!")
            print(f"航班:从 {from_airport} 到 {to_airport}")
            print(f"酒店:{hotel_name}")
            print("=" * 60)
    except Exception as e:
        print(f"\n处理过程中出现错误: {e}")
        # 教学兜底:即使多智能体流程异常,也能直接调用工具帮助理解业务目标
        print("\n正在直接执行预订...")
        flight_result = book_flight(from_airport, to_airport)
        hotel_result = book_hotel(hotel_name)
        print(flight_result)
        print(hotel_result)

    print("\n感谢使用智能旅行预订系统!")        
    
# 7. 运行主程序
if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n\n程序被用户中断。")
    except Exception as e:
        print(f"\n系统出现错误: {e}")

Handoff定义

  Supervisor 更像“主管一直在调度别人”,Handoff 更像“当前角色把控制权正式交给下一个角色”。也就是说:Supervisor 的重心是“中心调度”;Handoff 的重心是“控制权转移”。

使用场景:

  • 会话中角色真正切换
  • 下一位 Agent 需要接着当前上下文继续和用户互动
  • 不只是内部调用工具,而是“换一个会说话的角色接手”

例如:

  • 先由总客服接待,再把问题交给技术支持
  • 先由通用顾问判断,再交给航班专家
  • 先由一个 Agent 完成分析,再把控制权交给执行型 Agent

案例:

这个案例非常值得保留,因为它展示的是:

  • 不只是“主管调用子 Agent”
  • 而是如何显式构造交接
  • 如何把状态和任务说明一起转给下一个 Agent
# 用 Command + Send 把控制权与消息状态交给指定 Agent;create_task_description_handoff_tool 生成「移交」工具,子 Agent 可互相转接
# Handoff 与“把子 Agent 当工具调”不同:这里显式构造下一跳输入 state,并用 Command(goto=[Send(...)], graph=Command.PARENT) 跳转到兄弟节点
# InjectedState 把当前 MessagesState 注入工具,便于携带对话历史;task_description 充当“交给下一位的工单说明”,这正是 Handoff 里最值得关注的上下文工程
# flight_assistant / hotel_assistant 由 create_agent 构建并作为节点加入同一 StateGraph,START 指向默认入口 Agent;这说明 Agent 完全可以作为 LangGraph 图中的节点来组织
# 


import os
from typing import Annotated

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langgraph.graph import StateGraph, START
from langgraph.graph.message import MessagesState
from langgraph.prebuilt.tool_node import InjectedState
from langgraph.types import Command, Send
from dotenv import load_dotenv

load_dotenv(encoding="utf-8")


# ===============================
# 1. 初始化大语言模型
# ===============================
def init_llm_model() -> ChatOpenAI:
    return ChatOpenAI(
        model="qwen-plus",
        api_key=os.getenv("aliQwen-api"),
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
        temperature=0.1,
        max_tokens=1024,
    )


model = init_llm_model()


# 2. 通用 Handoff 工具工厂
def create_task_description_handoff_tool(
    *, agent_name: str, description: str | None = None
):
    name = f"transfer_to_{agent_name}"
    description = description or f"移交给 {agent_name}"

    @tool(name, description=description)
    def handoff_tool(
        task_description: Annotated[
            str, "描述下一个 Agent 应该做什么,包括所有必要信息"
        ],
        state: Annotated[MessagesState, InjectedState],
    ) -> Command:
        task_description_message = {
            "role": "user",
            "content": task_description,
        }
        agent_input = {
            **state,
            "messages": [task_description_message],
        }

        return Command(
            goto=[Send(agent_name, agent_input)],
            graph=Command.PARENT,
        )

    return handoff_tool


# 3. 业务工具(必须有 docstring)
# ===============================
@tool("book_flight")
def book_flight(from_airport: str, to_airport: str) -> str:
    """预订航班,根据出发地和目的地完成机票预订"""
    print(f"✅ 成功预订了从 {from_airport} 到 {to_airport} 的航班")
    return f"成功预订了从 {from_airport} 到 {to_airport} 的航班。"


@tool("book_hotel")
def book_hotel(hotel_name: str) -> str:
    """预订酒店,根据酒店名称完成预订"""
    print(f"✅ 成功预订了 {hotel_name} 的住宿")
    return f"成功预订了 {hotel_name} 的住宿。"


# 4. Handoff 工具
# ===============================
transfer_to_flight_assistant = create_task_description_handoff_tool(
    agent_name="flight_assistant",
    description="将任务移交给航班预订助手",
)

transfer_to_hotel_assistant = create_task_description_handoff_tool(
    agent_name="hotel_assistant",
    description="将任务移交给酒店预订助手",
)

# ===============================
# 5. 定义 Agent(create_agent 新接口)
# 这里不额外写长 prompt,而是更多依赖:
# 1. 工具 schema / 名称 / docstring
# 2. Handoff 工具本身描述的交接语义
# 3. MessagesState 中持续携带的历史消息
# ===============================
flight_assistant = create_agent(
    model=model,
    tools=[book_flight, transfer_to_hotel_assistant],  # 包含移交工具
    name="flight_assistant",
)

hotel_assistant = create_agent(
    model=model,
    tools=[book_hotel, transfer_to_flight_assistant],  # 包含移交工具
    name="hotel_assistant",
)


# ===============================
# 6. 构建多 Agent Graph
# ===============================
multi_agent_graph = (
    StateGraph(MessagesState)
    .add_node(flight_assistant)
    .add_node(hotel_assistant)
    .add_edge(START, "flight_assistant")
    .compile()
)


# ===============================
# 7. 运行
# ===============================
if __name__ == "__main__":
    result = multi_agent_graph.invoke(
        {
            "messages": [
                HumanMessage(content="帮我预订从北京到上海的航班,并预订如家酒店")
            ]
        }
    )

    print("\n====== 最终对话结果 ======")
    for msg in result["messages"]:
        if msg.type in ("human", "ai"):
            print(msg.content)

Skills

Skill 是可复用能力包,通常包含提示词、流程、模板、脚本和参考资料。它回答的是:

遇到这类任务时,应该按什么方法做?

Agent 回答的是:

当前任务该怎么拆、先做什么、调用什么工具、什么时候交给谁?

什么时候需要多智能体

判断问题 更可能的选择 原因
只是复用一套流程、模板或检查清单 Skill 能力复用即可,不需要独立角色
同一个 Agent 的提示词太长 Skill 用按需加载减少主上下文压力
工具很多但任务角色仍然单一 Skill + 工具治理 先整理能力和工具选择边界
已经有检索、分析、执行、审核分工 多智能体 角色职责不同,需要独立上下文
不同角色需要不同工具权限 多智能体 权限隔离比提示词复用更重要
需要显式交接控制权 Handoff 当前对话状态要由另一个 Agent 接手
需要中心化调度多个专家 Supervisor 主控 Agent 需要分发任务并汇总结果

可以把三者关系记成一句话:

Supervisor 负责调度,Handoff 负责交接,Skills 负责沉淀可复用能力。

 

posted @ 2026-05-20 11:44  幻影之舞  阅读(28)  评论(0)    收藏  举报