跳转到内容

Environment 消息路由与多角色协同

学习目标

理解 Environment 如何实现消息路由、角色协同执行,以及 ExtEnv 如何集成外部游戏环境。


项目实践

Environment 核心职责

Environment 是角色运行的容器,负责:

  1. 角色管理:注册角色、设置环境引用
  2. 消息路由:发布消息到订阅者
  3. 并行执行:异步运行所有角色
  4. 历史记录:记录所有消息用于调试
  5. 成本追踪:共享 CostManager

消息路由机制

消息路由是 Environment 最核心的功能,涉及三个要素:

要素说明定义位置
send_to消息接收者地址集合Message.send_to: set[str]
cause_by消息的触发 actionMessage.cause_by: str
addresses角色订阅的地址Role.addresses: set[str]

publish_message() 实现:

def publish_message(self, message: Message, peekable: bool = True) -> bool:
found = False
for role, addrs in self.member_addrs.items():
if is_send_to(message, addrs): # 匹配 send_to 和 addresses
role.put_message(message) # 投递到角色的 msg_buffer
found = True
if not found:
logger.warning(f"Message no recipients: {message.dump()}")
self.history.add(message) # 记录到历史
return True

角色地址

角色默认订阅自己的名称和类型:

@model_validator(mode="after")
def check_addresses(self):
if not self.addresses:
self.addresses = {any_to_str(self), self.name} if self.name else {any_to_str(self)}
return self

这意味着角色默认接收发给自己的消息和以自身类型标记的消息。

并行执行

Environment.run() 使用 asyncio.gather() 并行执行所有非 idle 角色:

async def run(self, k=1):
for _ in range(k):
futures = []
for role in self.roles.values():
if role.is_idle:
continue
future = role.run()
futures.append(future)
if futures:
await asyncio.gather(*futures)

关键细节

  • is_idle 检查:not rc.news and not rc.todo and rc.msg_buffer.empty()
  • 所有角色在同一轮次并行执行,不是串行
  • 一轮中角色的 publish_message() 投递的消息会在下一轮被处理

ExtEnv:外部游戏环境

ExtEnv 继承 BaseEnvironment + BaseModel,提供 Gymnasium 风格接口:

class ExtEnv(BaseEnvironment, BaseModel):
action_space: spaces.Space[ActType]
observation_space: spaces.Space[ObsType]
def reset(self, seed=None, options=None) -> tuple[dict, dict]: ...
def observe(self, obs_params=None) -> Any: ...
def step(self, action: BaseEnvAction) -> tuple[dict, float, bool, bool, dict]: ...

API 注册装饰器:

def mark_as_readable(func):
"""标记为 ExtEnv 中的可读 API"""
env_read_api_registry[func.__name__] = get_function_schema(func)
return func
def mark_as_writeable(func):
"""标记为 ExtEnv 中的可写 API"""
env_write_api_registry[func.__name__] = get_function_schema(func)
return func

角色通过 read_from_api() / write_thru_api() 调用环境能力,而不需要直接引用环境对象。

已实现的 ExtEnv

环境用途特殊能力
AndroidEnvAndroid 操控ADB 命令、屏幕截图、点击/滑动
MinecraftEnvMinecraft 游戏Mineflayer JS 集成、方块操作
WerewolfEnv狼人杀游戏逻辑、角色分配、投票
StanfordTownEnv社交模拟Stanford小镇模拟、角色行为

问题与规避

消息静默丢弃

  • 如果角色没有 set_env()publish_message() 直接 return,消息静默丢弃
  • 对策:确保在 run() 前调用 env.add_roles()role.set_env()

并行执行的竞态条件

  • 多个角色并行执行时,可能同时向同一文件写入
  • 对策:通过 cause_bywatch 机制确保角色按序触发,避免并行冲突
  • 软件公司场景天然串行:PRD → Design → Code 是流水线式的

消息堆积

  • 如果角色处理速度慢于消息产生速度,msg_buffer 会持续增长
  • 对策:_observe() 中的 pop_all() 一次性清空缓冲区,max_react_loop 限制处理轮次

设计取舍

异步并行 vs 串行执行

  • 异步并行提高了吞吐量,但增加了调试复杂度
  • 软件公司的角色间依赖关系(PRD → Design → Code)天然串行,并行执行不会导致问题
  • 对于强依赖顺序的场景(如 Werewolf 的夜间阶段),需要额外的状态同步

Gymnasium 接口

  • 采用 Gymnasium 标准接口使 ExtEnv 兼容强化学习框架
  • 代价是增加了抽象层,简单的场景可能不需要 action_space/observation_space
  • 好处:可以直接使用 RL 训练 Agent 在环境中行动

参考来源

  • 源码验证: metagpt/environment/base_env.py:124 — Environment 类
  • 源码验证: metagpt/environment/base_env.py:175publish_message() 路由实现
  • 源码验证: metagpt/environment/base_env.py:197run() 并行执行
  • 源码验证: metagpt/environment/base_env.py:53 — ExtEnv 类与 Gymnasium 接口
  • 源码验证: metagpt/roles/role.py:429publish_message() 角色端实现