跳到主要内容

模拟框架:5 组件 Rule 引擎

本章讲什么: AgentVerse 模拟框架怎么把「多个智能体自由互动」这件事跑起来。核心是一个 step() 循环,它把每一轮拆成五个独立的问题交给五个可插拔组件回答。理解这五件套,你就理解了整个模拟框架。

1. 它要解决的小问题

让一堆 LLM「自由对话」听起来简单,真做起来到处是协调决策:

  • 这一轮谁该开口?(全员?只有教授?被点名的学生?)
  • 一个智能体说的话谁听得见?(全场广播?只有同组?只有自己?)
  • 大家都说了,哪些消息算数?(全要?只留一条?)
  • 这些消息写进谁的记忆?
  • 给每个智能体的「当前环境长什么样」的描述是什么?

这五个问题彼此正交——「谁说话」和「谁听得见」是两回事。AgentVerse 的设计精华就是:给每个问题一个独立的可插拔组件,而不是把它们揉成一坨 if-else。

2. 思路/直觉:一轮 = 五个组件接力

SimulationRule 就是这五个组件的容器(simulation_env/rules/base.py:32-63):

组件回答的问题接口方法
order这轮谁说话?get_next_agent_idx
describer给每人什么环境描述?get_env_description
selector哪些消息算数?select_message
updater消息写进谁的记忆?update_memory
visibility之后谁能看见谁?update_visible_agents

它们各自有一个 registry(如 order_registry),YAML 里写 order: {type: classroom},装配时就取出 ClassroomOrderSimulationRule.__init__ 一次性把五个都 build 出来:

# 示意,非源码 —— 对应 simulation_env/rules/base.py:44-63 的逻辑
order = order_registry.build(**order_config) # type: "sequential"
visibility = visibility_registry.build(**visibility_config)
selector = selector_registry.build(**selector_config)
updater = updater_registry.build(**updater_config)
describer = describer_registry.build(**describer_config)
# 重点看:每个组件只是按 type 字符串取出的一个对象,互不耦合

3. 主线:一次 step() 的五步接力

这是整章最关键的一段代码,模拟框架的全部「调度逻辑」都在这 ~30 行里。

图示:step() 的数据流

order.get_next_agent_idx ──► [要发言的 agent 下标列表]

describer.get_env_description ──► [给每个 agent 的环境描述]

asyncio.gather(agents[i].astep(...)) ──► [本轮所有消息] ← 并发调 LLM

selector.select_message ──► [被选中的消息] (存进 last_messages)

updater.update_memory ──► 把消息写进对应 agent 的记忆

visibility.update_visible_agents ──► 更新 receiver 集合

cnt_turn += 1

真实实现

BasicEnvironment.step 严格按上图执行(simulation_env/basic.py:57-84):

# simulation_env/basic.py:57-84(节选)
async def step(self) -> List[Message]:
agent_ids = self.rule.get_next_agent_idx(self) # ① 谁说话
env_descriptions = self.rule.get_env_description(self) # ② 环境描述
messages = await asyncio.gather( # ③ 并发生成
*[self.agents[i].astep(env_descriptions[i]) for i in agent_ids]
)
selected_messages = self.rule.select_message(self, messages) # ④ 选消息
self.last_messages = selected_messages
self.rule.update_memory(self) # ⑤ 写记忆
self.rule.update_visible_agents(self) # ⑥ 更新可见
self.cnt_turn += 1
return selected_messages

重点看 ③: 同一轮里多个智能体是用 asyncio.gather 并发调 LLM 的——这是 AgentVerse 全框架的并发基调(顶层 run()asyncio.run 驱动)。

循环何时停?is_done() 只看轮数:cnt_turn >= max_turns(simulation_env/basic.py:99-101)。

4. 逐组件看:换一个组件就换一种玩法

这节用「默认 basic 版」对照「特化版」,让你看到组件化的威力。

4.1 order:从「轮流」到「课堂举手」

默认 sequential 就是按下标轮流。但 classroom 这个 order 把「教授主持 + 学生举手 + 被点名才发言 + 分组讨论」整套课堂礼仪都编码进了一个组件。

它的精华是靠解析上一轮消息内容来决定下一轮谁说话(order/classroom.py:34-67):

# order/classroom.py:42-54(节选,ungrouped 分支)
if sender.startswith("Professor"):
if content.startswith("[CallOn]"):
# 教授点名 "[CallOn] Yes, Student X" → 解析出名字 → 只让那个学生说
result = re.search(r"\[CallOn\] Yes, ([sS]tudent )?(\w+)", content)
...
return [name_to_id[result.group(2)]]
else:
# 教授普通发言 → 全场都可以行动(举手/接话)
return list(range(len(environment.agents)))

关键细节: order 组件可以读 environment.rule_params(一个共享字典)来维护跨轮状态,比如分组讨论时的 group_speaker_mapping 轮转(order/classroom.py:84-98)。这是组件之间间接通信的唯一通道——visibility 组件设好 groups,order 组件读它来排发言顺序。

4.2 visibility:从「全场可听」到「只对自己」

visibility 决定每个智能体的 receiver 集合(消息发给谁)。对比两端:

  • all:所有人互相可见(默认,NLP 课堂用)。
  • oneself:每个智能体只能看见自己——一行代码(visibility/oneself.py:16-18):
# visibility/oneself.py:16-18
def update_visible_agents(self, environment):
for agent in environment.agents:
agent.set_receiver(set({agent.name})) # receiver 只剩自己

4.3 selector / updater:消息怎么落地

selector 默认 basic 就是「全要,原样返回」(selector/basic.py:20-24)——它的存在是为了让特殊场景(如 SDE 团队、宝可梦)能过滤/改写消息。

updater 决定消息写进谁的记忆。basic 版的逻辑(updater/basic.py:24-37)有两个值得学的细节:

  1. 按 receiver 定向投递:receiver"all" 就广播,否则只投给名字匹配的智能体(add_message_to_all_agents,updater/basic.py:52-75)。
  2. 静默补位:如果整轮没人说话,给所有智能体塞一条 [Silence],避免记忆空洞(updater/basic.py:35-37)。

5. 巧妙之处

  • 正交拆分:把「一次对话轮」拆成五个互不依赖的问题,是整个模拟框架最值得带走的设计。新增一种互动模式 = 写一个新组件 + 改一行 YAML,框架代码零改动。
  • 内容驱动的调度:ClassroomOrder 用正则解析消息内容([CallOn])来决定下一个发言者(order/classroom.py:45),让「调度逻辑」也能被 LLM 的输出影响——这是把自然语言当控制信号的典型手法。
  • rule_params 作为组件间黑板:组件之间不直接调用,而是通过 environment.rule_params 这个共享字典传状态(order/classroom.py:77-98),保持各组件解耦。

6. 边界与局限

  • order 组件里有不少硬编码假设:ClassroomOrder 假定第 0 个智能体是教授、靠名字前缀 "Professor"/"Student " 区分角色(order/classroom.py:42-55)。换个命名就崩。
  • selector/describer 的 basic 版基本是空操作,真正的复杂模拟(宝可梦、囚徒困境)各自有专门子类,通用性有限。
  • 停止条件只看轮数,没有「对话自然结束」的判定。

7. 横向对比

模拟框架的「5 组件 Rule」是一种声明式调度思路。对照解题框架(见 02-tasksolving-pipeline.md):解题框架是固定四阶段流水线 + 反馈循环,组件是流水线上的工序;模拟框架是自由轮转 + 五个正交维度,组件是每轮要回答的子问题。前者目标明确要收敛,后者只为观察涌现。

8. 代码地图

主题文件符号
模拟环境主循环agentverse/environments/simulation_env/basic.pyBasicEnvironment.step
5 组件容器agentverse/environments/simulation_env/rules/base.pySimulationRule
发言顺序基类agentverse/environments/simulation_env/rules/order/base.pyBaseOrder
课堂顺序(内容驱动)agentverse/environments/simulation_env/rules/order/classroom.pyClassroomOrder.get_next_agent_idx
可见性:只对自己agentverse/environments/simulation_env/rules/visibility/oneself.pyOneselfVisibility
记忆更新(定向投递+静默补位)agentverse/environments/simulation_env/rules/updater/basic.pyBasicUpdater.update_memory
消息选择(默认全要)agentverse/environments/simulation_env/rules/selector/basic.pyBasicSelector.select_message