面试09-Agent 的团队协作

Agent 团队协作:从 "异步通信" 到 "结构化协议"

作为编程学习的视角,我们先把这两季的核心逻辑串起来:第九季解决了 "团队能沟通" 的问题(基础通信层),第十季解决了 "团队沟通有规则" 的问题(协议层) ,就像先给团队装了邮箱,再制定了 "请假审批" "方案评审" 的 公司制度

一、核心背景:为什么要做这两季的升级?

在第九季之前(第八季及更早),AI代理存在两个致命问题:

  1. 一次性死亡:子 Agent 用完就销毁,没有 "身份" 和 "记忆" ,没法持续协作;
  2. 无规则沟通:就算能通信,也只是零散消息,没有 "请求-响应" 的规范,比如关线程时容易数据丢失、改代码时没有审批流程。

第九季先搭建 "能持续沟通的团队",第十季再给沟通加"规则和流程",一步步接近真实的人类团队协作。

二、第九季:搭建 "有身份、能异步通信的持久 Agent 团队"

核心目标

AI Agent 从 "一次性工具" 变成 "持久存在的队友" ,支持 跨 Agent 的异步消息传递(比如A发消息给B,B忙完再看)。

关键组件拆解(带代码解释)

1. 团队管理核心:TeammateManager(队友生命周期管理)

作用: 创建、跟踪队友(Agent),给每个队友(Agent)分配独立线程运行(保证同时工作)。

python 复制代码
class TeammateManager:
    def __init__(self, team_dir: Path):
        self.dir = team_dir  # 团队数据目录(存配置、邮箱)
        self.dir.mkdir(exist_ok=True)  # 目录不存在则创建
        self.config_path = self.dir / "config.json"  # 团队配置文件(存队友名单、状态)
        self.config = self._load_config()  # 加载配置(比如{"members": [{"name":"alice","role":"coder","status":"working"}]})
        self.threads = {}  # 存储每个队友(Agent)的运行线程
    
    # 查找对应 Agent 的信息    
		def _find_member(self, name: str) -> dict:
        for m in self.config["members"]:
            if m["name"] == name:
                return m
        return None
        
		def _save_config(self, config=None):
        """保存团队配置到文件"""
        config = config or self.config
        self.config_path.write_text(json.dumps(config, indent=2))
        
    def spawn(self, name: str, role: str, prompt: str) -> str:
        # 1. 记录队友信息存储到配置文件(持久化,不会丢)
        member = {"name": name, "role": role, "status": "working"}
        self.config["members"].append(member)
        self._save_config()  # 写入config.json,就算程序重启也能找到队友
        
        # 2. 启动独立线程运行队友(持久存在,不是用完就扔)
        thread = threading.Thread(
            target=self._teammate_loop,  # 队友的核心工作循环
            args=(name, role, prompt), 
            daemon=True  # 守护线程:主程序退出时自动结束
        )
        thread.start()
        self.threads[name] = thread  # 跟踪线程状态
        return f"Spawned teammate '{name}' (role: {role})"

问题一:从 Agent 的创建上,是如何实现异步通信的?

这和 Agent 的生命周期相关。具体可分为,(1)Agent 的持久化、(2)线程创建(跟管理 Agent 工作逻辑相关)、(3)线程的管理;

  • 第一部分 - 持久化: 通过对每个 Agent 创建 json 文件的方式,持久化 Agent 的信息(包含 Agent 的名称、角色、状态);
  • 第二部分 - 创建线程: 给每个 Agent 开独立线程,让他们一直活着;为什么要开独立线程呢?,是为了让每个 Agent 的工作循环互不干扰(比如 A 队友工作时,B 队友可以摸鱼),实现异步通信功能;
  • 第三部分 - 管理线程: 通过字典的形式存储所有的 Agent 线程,方便管理(比如,(1)查询线程是否存活;(2)线程状态是否空闲;(3)防止重复创建线程;

问题二:Agent 的工作逻辑是如何实现异步通信的呢?

如以下代码所示:

python 复制代码
def _teammate_loop(self, name: str, role: str, prompt: str):
    """
    单个队友的核心工作循环(独立线程中运行)
    核心功能:
    1. 初始化队友的系统提示和对话上下文
    2. 循环读取消息队列(inbox)接收其他队友/管理器的消息
    3. 调用大模型(LLM)生成响应,支持工具调用
    4. 执行工具调用并返回结果给大模型
    5. 循环最多50次或直到停止(非工具调用)
    6. 循环结束后更新队友状态为idle并保存配置
    
    参数:
        name: str - 队友名称(唯一标识)
        role: str - 队友角色(如"后端开发"、"测试工程师")
        prompt: str - 初始任务提示(队友的核心任务)
    """
    # 1. 构建系统提示词:定义队友的身份、工作目录、行为规则
    # WORKDIR是全局变量,代表队友的工作目录;send_message是预设的通信方式
    sys_prompt = (
        f"You are '{name}', role: {role}, at {WORKDIR}. "  # 身份与环境
        f"Use send_message to communicate. Complete your task."  # 行为规则:用send_message通信,完成任务
    )
    
    # 2. 初始化对话消息列表:初始只有用户(管理器)下发的任务提示
    # messages是大模型的对话上下文,格式遵循OpenAI API规范
    messages = [{"role": "user", "content": prompt}]
    
    # 3. 获取该队友可使用的工具列表(如文件操作、命令执行、消息发送等)
    # _teammate_tools()是管理器的私有方法,返回该队友支持的工具定义(符合OpenAI工具调用规范)
    tools = self._teammate_tools()
    
    # 4. 核心工作循环:最多执行50轮(避免无限循环)
    for _ in range(50):
        # 4.1 读取当前队友的收件箱消息(从全局消息总线BUS中读取)
        # BUS是全局消息总线,read_inbox(name)获取发给该队友的所有未读消息
        inbox = BUS.read_inbox(name)
        
        # 4.2 将收件箱的新消息添加到对话上下文(转JSON保证格式统一)
        for msg in inbox:
            messages.append({"role": "user", "content": json.dumps(msg)})
        
        try:
            # 4.3 调用大模型API生成响应
            # model=MODEL:全局变量,指定使用的大模型(如gpt-4o)
            # system=sys_prompt:设置大模型的系统角色
            # tools=tools:传入该队友可调用的工具列表
            # max_tokens=8000:限制单次响应的最大token数
            response = client.messages.create(
                model=MODEL,
                system=sys_prompt,
                messages=messages,
                tools=tools,
                max_tokens=8000,
            )
        except Exception:
            # 捕获API调用异常(如网络、额度、模型服务问题),直接终止循环
            break
        
        # 4.4 将大模型的响应添加到对话上下文(维护多轮对话状态)
        messages.append({"role": "assistant", "content": response.content})
        
        # 4.5 判断是否停止循环:如果响应不是因为工具调用停止,则结束工作
        # stop_reason="tool_use"表示大模型要求调用工具,需要继续循环;否则任务完成
        if response.stop_reason != "tool_use":
            break
        
        # 4.6 处理工具调用:遍历响应中的所有工具调用块
        results = []
        for block in response.content:
            # 筛选出类型为"tool_use"的响应块(大模型要求执行的工具)
            if block.type == "tool_use":
                # 执行工具调用:_exec是管理器的私有方法,封装工具执行逻辑
                # 参数:调用者名称、工具名称、工具入参
                output = self._exec(name, block.name, block.input)
                
                # 打印工具执行结果(截断到120字符,避免输出过长)
                print(f"  [{name}] {block.name}: {str(output)[:120]}")
                
                # 整理工具执行结果:符合OpenAI工具结果格式,用于返回给大模型
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,  # 关联对应的工具调用ID
                    "content": str(output),   # 工具执行的结果内容
                })
        
        # 4.7 将工具执行结果作为用户消息添加到上下文,供下一轮大模型处理
        messages.append({"role": "user", "content": results})
    
    # 5. 循环结束后:更新队友状态并保存配置
    # 5.1 查找该队友的信息(从管理器的成员列表中)
    member = self._find_member(name)
    
    # 5.2 如果队友存在且未被关闭(shutdown),将状态改为idle(闲置)
    if member and member["status"] != "shutdown":
        member["status"] = "idle"
        # 5.3 保存配置到文件(如config.json),持久化状态变更
        self._save_config()

异步功能实现的逻辑如下:

  • 读取消息队列中的内容:读取类似异步邮箱中的内容,并将其封装到 messages 列表中;
  • 第一次回答:大模型回答,解决邮箱中的问题;(两种情况:(1)无需调用工具,说明一次 QA 就解决了;(2)需要调用工具,则和原来的 Agent Loop 一样的)
  • 调用所需工具:调用工具,得到 tool response;然后封装到 messages 列表中;
  • Agent Loop:循环直至,无需使用工具,说明任务解决;
  • 更新线程状态:更新线程状态;

2. 通信核心:MessageBus(基于JSONL的异步邮箱)

作用: 实现队友间的异步消息传递,就像公司的邮箱系统------A发消息给B,B忙完再看,消息存在文件里不会丢。

python 复制代码
class MessageBus:
    def send(self, sender, to, content, msg_type="message", extra=None):
        # 构造标准化消息(谁发的、发给谁、内容、时间、类型)
        msg = {
            "type": msg_type,  # 消息类型(普通消息/关机请求/计划审批)
            "from": sender,
            "content": content,
            "timestamp": time.time()  # 时间戳,保证顺序
        }
        if extra:
            msg.update(extra)  # 附加字段(比如request_id)
        
        # 追加写入收件人的JSONL文件(append-only,只加不删,保证不丢消息)
        with open(self.dir / f"{to}.jsonl", "a") as f:
            f.write(json.dumps(msg) + "\n")

    def read_inbox(self, name):
        path = self.dir / f"{name}.jsonl"
        if not path.exists(): return "[]"
        
        # 读取所有消息并解析
        msgs = [json.loads(l) for l in path.read_text().strip().splitlines() if l]
        path.write_text("")  # 读完清空(drain),避免重复处理
        return json.dumps(msgs, indent=2)

MessageBus 的目的

  • 持久化 Agent 的异步邮箱: 每个 Agent 有自己的专属邮箱文件(比如 alice.jsonl、bob.jsonl);
  • 异步操作: 实现每个 Agent 发消息、读消息的功能,消息存储到 json 文件中;

3. 队友的工作循环:_teammate_loop

作用:让队友持续运行,定期查邮箱,和LLM交互,实现"持久工作"。

python 复制代码
def _teammate_loop(self, name, role, prompt):
    # 初始化对话上下文
    messages = [{"role": "user", "content": prompt}]
    # 循环运行(最多50轮,避免无限循环)
    for _ in range(50):
        # 第一步:查邮箱,有消息就注入上下文
        inbox = BUS.read_inbox(name)
        if inbox != "[]":
            messages.append({"role": "user", "content": f"<inbox>{inbox}</inbox>"})
            messages.append({"role": "assistant", "content": "Noted inbox messages."})
        
        # 第二步:调用LLM,获取响应
        response = client.messages.create(...)
        
        # 第三步:如果LLM不调用工具了,就停止本轮工作
        if response.stop_reason != "tool_use":
            break
        
        # 第四步:执行工具(比如发消息、写代码),把结果追加到上下文
        # ...(工具执行逻辑)
    
    # 第五步:标记队友状态为"空闲"
    self._find_member(name)["status"] = "idle"

关键理解

  • 队友不是"一次性的",而是在循环里持续运行,直到完成任务或达到循环上限;
  • 每次和LLM对话前先查邮箱,保证队友能及时收到其他成员的消息;
  • 状态从"working"变为"idle",实现生命周期管理。

第九季核心亮点

特性 解决的问题 实现方式
持久化队友 Agent 用完就丢,无身份 线程+config.json持久化队友信息
异步通信 Agent 间无法传递消息 JSONL邮箱(send/read_inbox)
生命周期管理 Agent 只有"运行/死亡"两种状态 working/idle状态切换

三、第十季:给 Agent 团队沟通加"结构化协议"

核心目标

第九季的队友能通信,但沟通是"无规则的"------比如领导让队友关机,队友可能直接退出导致文件没写完;队友改代码,没有审批流程直接动手。第十季通过"结构化协议"解决这些问题。

python 复制代码
"""
s10_team_protocols.py - 团队协议

关机协议和计划审批协议,均使用相同的request_id关联模式。基于s09的团队消息功能扩展。

    关机状态机(FSM):待处理(pending)→ 批准(approved) | 拒绝(rejected)

    领导                              队友
    +---------------------+          +---------------------+
    | 关机请求             |          |                     |
    | {                    | -------> | 接收请求            |
    |   request_id: abc    |          | 决策:是否批准?     |
    | }                    |          |                     |
    +---------------------+          +---------------------+
                                             |
    +---------------------+          +-------v-------------+
    | 关机响应             | <------- | 关机响应            |
    | {                    |          | {                   |
    |   request_id: abc    |          |   request_id: abc   |
    |   approve: true      |          |   approve: true     |
    | }                    |          | }                   |
    +---------------------+          +---------------------+
            |
            v
    状态 -> "关机完成",线程停止

    计划审批状态机(FSM):待处理(pending)→ 批准(approved) | 拒绝(rejected)

    队友                          领导
    +---------------------+          +---------------------+
    | 计划审批             |          |                     |
    | 提交:{plan:"..."}| -------> | 审核计划文本        |
    +---------------------+          | 批准/拒绝?         |
                                     +---------------------+
                                             |
    +---------------------+          +-------v-------------+
    | 计划审批响应         | <------- | 计划审批            |
    | {approve: true}      |          | 审核:{req_id,      |
    +---------------------+          |   approve: true}     |
                                     +---------------------+

    跟踪器:{request_id: {"目标|发起方": 名称, "状态": "待处理|已批准|已拒绝"}}

核心思路:"相同的request_id关联模式,适配两个业务场景。"
"""

核心概念:请求-响应协议 + FSM(有限状态机)

1. 核心思路

所有重要沟通都遵循"请求(带唯一ID)→ 响应(引用同一ID)→ 状态更新"的流程,用FSM(有限状态机)管理请求的状态(pending→approved/rejected)。

2. 协议1:关机协议(Shutdown Protocol)

解决"强制关机导致数据丢失"的问题,要求"领导发关机请求→队友确认(批准/拒绝)→再关机"。

步骤1:领导发送关机请求(生成唯一request_id)
python 复制代码
shutdown_requests = {}  # 跟踪所有关机请求的状态

def handle_shutdown_request(teammate: str) -> str:
    # 生成8位唯一请求ID(避免冲突)
    req_id = str(uuid.uuid4())[:8]
    # 记录请求状态:待处理
    shutdown_requests[req_id] = {"target": teammate, "status": "pending"}
    # 发消息给目标队友(消息类型是shutdown_request,带request_id)
    BUS.send(
        "lead",  # 发送者
        teammate,  # 接收者
        "Please shut down gracefully.",  # 内容
        "shutdown_request",  # 消息类型
        {"request_id": req_id}  # 附加请求ID
    )
    return f"Shutdown request {req_id} sent (status: pending)"
步骤2:队友处理关机请求并响应
python 复制代码
# 队友收到shutdown_request后,调用shutdown_response工具
if tool_name == "shutdown_response":
    req_id = args["request_id"]  # 引用领导的请求ID
    approve = args["approve"]    # 批准/拒绝关机
    # 更新请求状态
    shutdown_requests[req_id]["status"] = "approved" if approve else "rejected"
    # 回复领导(同样带request_id,保证关联)
    BUS.send(
        sender,  # 队友自己
        "lead",  # 接收者是领导
        args.get("reason", ""),  # 拒绝理由(可选)
        "shutdown_response",  # 消息类型
        {"request_id": req_id, "approve": approve}
    )

3. 协议2:计划审批协议(Plan Approval Protocol)

解决"高风险任务无审批"的问题,要求"队友提交计划(带ID)→领导审批(引用ID)→队友执行/修改"。

python 复制代码
plan_requests = {}  # 跟踪所有计划请求的状态

# 队友提交计划
def submit_plan(sender, plan_content):
    req_id = str(uuid.uuid4())[:8]
    plan_requests[req_id] = {
        "from": sender,
        "plan": plan_content,
        "status": "pending"
    }
    # 发消息给领导请求审批
    BUS.send(sender, "lead", plan_content, "plan_request", {"request_id": req_id})
    return f"Plan submitted (ID: {req_id}), waiting for approval."

# 领导审批计划
def handle_plan_review(request_id, approve, feedback=""):
    req = plan_requests[request_id]  # 通过ID找到对应的计划
    req["status"] = "approved" if approve else "rejected"
    # 回复队友审批结果
    BUS.send(
        "lead",
        req["from"],
        feedback,  # 审批意见(比如"代码需要加注释")
        "plan_approval_response",
        {"request_id": request_id, "approve": approve}
    )

4. 统一的FSM(有限状态机)

两个协议共享同一个状态机逻辑,简化代码:

复制代码
[pending(待处理)] 
    ↙      ↘
[approved(批准)]  [rejected(拒绝)]

不管是关机请求还是计划请求,都只需要维护这三种状态,逻辑统一,容易扩展。

第十季核心亮点

特性 解决的问题 实现方式
结构化协议 沟通无规则,易出错 请求-响应模式+唯一request_id
状态跟踪 不知道请求的处理结果 shutdown_requests/plan_requests字典
优雅关机 强制关机导致数据丢失 关机请求-响应握手
计划审批 高风险任务无审核直接执行 计划提交-审批流程

四、两季核心对比(学生视角)

维度 第九季(基础版) 第十季(进阶版)
核心能力 队友能持久运行、异步发消息 在第九季基础上,沟通有规则、可追溯
消息特点 无结构化(普通文本) 结构化(带类型、request_id)
状态管理 仅队友的工作状态(working/idle) 新增请求状态(pending/approved/rejected)
工具数量 9个(基础通信、执行) 12个(+关机请求/响应、计划审批)
协作规范 无明确规范 有请求-响应的协议规范

五、代码运行与验证(实操步骤)

作为学生,你可以按以下步骤跑通代码,理解实际效果:

1. 第九季运行

bash 复制代码
# 进入代码目录
cd learn-claude-code
# 运行第九季代码
python agents/s09_agent_teams.py

# 交互操作(示例):
# 1. 创建alice(编码员)和bob(测试员)
# 2. 让alice给bob发消息
# 3. 广播"status update: phase 1 complete"给所有队友
# 4. 查看领导的收件箱
# 5. 查看团队名单(/team)和收件箱(/inbox)

2. 第十季运行

bash 复制代码
cd learn-claude-code
python agents/s10_team_protocols.py

# 交互操作(示例):
# 1. 创建alice(编码员),发送关机请求,查看她的状态
# 2. 创建bob(高风险重构任务),提交计划,领导拒绝
# 3. 创建charlie,提交计划,领导批准
# 4. 查看团队状态(/team)

总结

核心知识点回顾

  1. 第九季核心 :用TeammateManager实现队友持久化(线程+配置文件),用MessageBus(JSONL文件)实现异步通信,解决"代理能组队、能发消息"的基础问题;
  2. 第十季核心 :在通信基础上增加"请求-响应协议",通过request_id关联请求和响应,用状态机管理请求状态,解决"沟通有规则、可追溯、不混乱"的进阶问题;
  3. 关键设计思想:从"无结构"到"结构化"是编程中常见的升级思路,比如先做功能,再做规范,这和真实团队协作的逻辑一致。

这两季的代码核心是"模拟人类团队的协作模式",把编程中的"线程、文件IO、状态管理、协议设计"等知识点结合起来,是非常好的实战案例。

相关推荐
前端摸鱼匠2 小时前
面试题6:因果掩码(Causal Mask)在Decoder中的作用是什么?训练、推理阶段如何使用?
人工智能·ai·语言模型·自然语言处理·面试
yusheng_xyb2 小时前
互联网大厂Java求职面试实录
java·面试·互联网·技术面试
前端摸鱼匠2 小时前
面试题7:Encoder-only、Decoder-only、Encoder-Decoder三种架构的差异与适用场景?
人工智能·深度学习·ai·面试·职场和发展·架构·transformer
敲代码的嘎仔13 小时前
Java后端面试——SSM框架面试题
java·面试·职场和发展·mybatis·ssm·springboot·八股
xlp666hub16 小时前
【Linux驱动实战】:字符设备之ioctl与mutex全解析
linux·面试
进击的cc16 小时前
拒绝背诵!一文带你打穿 Android ANR 发生的底层全链路
android·面试
进击的cc16 小时前
App 启动优化全家桶:别再只盯着 Application 了,热启动优化你真的做对了吗?
android·面试
逆境不可逃18 小时前
LeetCode 热题 100 之 33. 搜索旋转排序数组 153. 寻找旋转排序数组中的最小值 4. 寻找两个正序数组的中位数
java·开发语言·数据结构·算法·leetcode·职场和发展
似水明俊德18 小时前
04-C#.Net-委托和事件-面试题
java·开发语言·面试·c#·.net