想象你走进一家大型医院。分诊台前人来人往,但整个医院并不混乱——因为每位专科医生都戴着属于自己的胸牌(身份),手里拎着工具箱(技能),面前还挂着一张关注清单(订阅)。心内科医生只关心“心电图报告”,放射科医生只留意“CT申请单”,皮肤科医生看到化验单也直接跳过。分诊台把每份新报告投递给对应的医生,医生处理完又产出新报告放回公共病历架,继续流转。MetaGPT 的多智能体协作,正是把这套“专科医院”机制搬进了大模型世界:每个角色(Role)有 Profile 定义身份、Actions 定义能力、订阅机制定义“我关心什么消息”,消息路由器负责精准投递。本篇文章将拆解这套机制,让你能像科主任一样,自由组建自己的“AI 专科团队”。

岗位关联

对 AI 从业者而言,理解角色订阅机制是设计稳健多智能体系统的分水岭——它能让你从“跑通 demo”进化到“掌控协作流程”,批量产出可靠的多角色协作方案。

目录
  1. 可视化演示
  2. 角色框架总览
  3. Profile身份定义
  4. Actions能力封装
  5. 订阅机制原理
  6. 消息路由机制
  7. 自定义角色实战
  8. 调试与最佳实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示MetaGPT角色订阅机制深的核心机制,通过开始/执行/监控/完成四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

MetaGPT角色订阅机制深:Meta MetaGPT角色架构与Pro 消息订阅机制与角色间通信 核心机制可视化 MetaGPT角色订阅机制深整体架构与关键组件

角色框架总览

医院里每一位专科医生都是一个“角色”:胸牌说明他是谁,工具箱决定他能做什么,关注清单决定他会响应什么。三样东西组合在一起,医生才能在分诊台前有条不紊地工作。MetaGPT 也正是用这个组合来定义多智能体协作的最小单元——每个 Role 对象相当于一位有明确职责的“数字专科医生”。

在代码层面,角色的三要素各有归属:profile 字段保存身份档案,actions 列表挂载能力工具,watch 集合声明订阅的消息类型。角色一旦被注册进团队环境,就自动开启“观察 → 思考 → 行动”的循环:从环境里收取新消息,根据订阅判断哪些重要,再调用某个 Action 产出新消息。整个过程不依赖中心调度表,全靠角色自己的判断,就像医生不需要护士长逐条指挥也会主动接诊自己领域的报告。

回到医院的比喻:只要一个医生同时具备胸牌、工具箱和关注清单,他就能独立执业;MetaGPT 中的 Role 同理,三者就绪即可加入任何协作团队。这正是多智能体系统优雅的地方——协作秩序不是强加的管理层级,而是每个成员自我约束的结果。

Role=⟨D, A, Sw⟩\displaystyle \text{Role} = \langle D,\ A,\ S_w\rangle
公式说明:一个角色由三部分构成——身份档案 D、动作集合 A、订阅集合 S_w。三者齐备,角色才能独立运转。
  1. 身份档案 D描述“我是谁、我的职责边界”,是角色所有行为的人格基础。
    D=(name, profile, goal, constraints)\displaystyle D = (\text{name},\ \text{profile},\ \text{goal},\ \text{constraints})
  2. 动作集合 A角色拥有的全部专业技能,每个技能是一个可独立执行的 Action。
    A={Action1, Action2, …, Actionn}\displaystyle A = \{Action_1,\ Action_2,\ \dots,\ Action_n\}
  3. 订阅集合 S_w角色会主动响应的消息类型清单,决定哪些消息能“叫醒”他。
    Sw={PRD, 任务, 代码}\displaystyle S_w = \{\text{PRD},\ \text{任务},\ \text{代码}\}

Profile身份定义

胸牌上写什么,决定护士遇到问题会找谁。心内科胸牌写着“姓名:张医生,专长:心脏电生理,目标:快速诊断心律失常,约束:不处理外伤”。MetaGPT 中 Profile 就是角色的数字胸牌,核心字段与之一一对应:name 姓名、profile 专长描述、goal 工作目标、constraints 行为约束。

这些字段不只是展示信息,它们会被拼进系统提示词,让大模型“入戏”。比如给一个角色设置 profile 为“资深产品经理”,模型写信时自然采用产品经理的口吻;设置 constraints 为“写需求文档时必须给出验收标准”,模型输出风格会明显收敛。Profile 之外,角色还有持续的观察能力——watch 让角色对环境中的新消息保持警觉,每次观察的结果被保存为环境快照,供后续思考使用。

一张写得越清晰的胸牌,医生越清楚自己该干什么、不该干什么。实际调试中你会发现,很多“角色行为怪异”的问题,根源不是模型不行,而是 Profile 字段写得含糊。把身份写具体,角色才能像经验丰富的专科医生一样,一上岗就进入状态。

Profile={name, profile, goal, constraints}\displaystyle \text{Profile} = \{\text{name},\ \text{profile},\ \text{goal},\ \text{constraints}\}
公式说明:Profile 是四个关键字段的集合,它们共同刻画了“这个角色在团队中应该以什么方式存在”。
  1. name 姓名角色在消息往来中显示的名字,方便其他角色识别发送者。
    name="产品经理_小李"\displaystyle \text{name} = \text{"产品经理\_小李"}
  2. profile 专长用自然语言描述角色的专业领域和职责,是提示词的灵魂。
    profile="擅长需求分析与文档撰写"\displaystyle \text{profile} = \text{"擅长需求分析与文档撰写"}
  3. goal 目标说明角色在协作中要达成的最终成果,牵引每一步行动。
    goal="产出高质量PRD文档"\displaystyle \text{goal} = \text{"产出高质量PRD文档"}
  4. constraints 约束划定行为的禁区,防止模型自由发挥越界。
    constraints="不编写代码,只做需求设计"\displaystyle \text{constraints} = \text{"不编写代码,只做需求设计"}

Actions能力封装

医生不能只靠胸牌看病,他需要听诊器、手术刀这些工具;MetaGPT 角色同样需要“技能工具”,这就是 Action。一个 Action 类就是一张技能卡,上面写清楚三件事:name(技能名,如WritePRD)、instruction(操作手册,告诉大模型怎么做)、run(动手执行的方法)。

当角色决定使用某张技能卡时,run 方法被调用:它接收触发消息和当前环境节点 node,执行大模型推理或工具调用,最后返回一条新的 Message。这条新消息的类型通常就是该 Action 的名字,例如 WritePRD 产出 PRD 类型消息,WriteCode 产出 代码 类型消息。这种“技能名 = 消息类型”的约定,让下游角色的订阅变得极其直观:架构师想接 PRD,就把 PRD 写进关注清单。有些 Action 还可以接入外部 Agent,比如将某个 Action 包装成一个 LMM Agent 调用,从而把第三方能力无缝编织进协作链。

工具箱的丰富程度,决定了医生能接多少种病。MetaGPT 中给角色装配 Action,就是在扩充他的“执业范围”;而每张技能卡自带的“产出类型标签”,又为消息系统提供了天然的寻址依据。这正是 Actions 与订阅机制深度咬合的设计智慧。

Action=(name, instruction, run)\displaystyle Action = (\text{name},\ \text{instruction},\ run)
公式说明:一个动作由名称、说明和执行函数三元组构成;执行函数在给定上下文节点上产生新消息。
  1. name 技能名动作的唯一标识,同时预定产出消息的类型,是协作路由的“路标”。
    name="WritePRD"\displaystyle \text{name} = \text{"WritePRD"}
  2. instruction 操作手册插入提示词的指令文本,告诉大模型“按什么标准执行这个技能”。
    instruction="根据需求描述,写出包含功能清单的PRD"\displaystyle \text{instruction} = \text{"根据需求描述,写出包含功能清单的PRD"}
  3. run 执行函数动作的实战逻辑,接收输入消息与上下文节点,返回新消息。
    newMessage=run(inputMessage, node)\displaystyle \text{newMessage} = run(\text{inputMessage},\ node)

订阅机制原理

关注清单是医生不被杂事淹没的关键。心内科医生若对每份化验单都仔细研究,一天也看不了几个病人。MetaGPT 角色通过 watch(老版本中也叫 subscribe)声明自己关心的消息类型集合——类似于关注清单。当环境中出现新消息,角色先做一次“类型比对”:消息类型在关注清单里,才被收入 important_messages 作为“需处理的要事”;不在清单里的消息,哪怕内容再丰富,也直接忽略。

这套匹配机制在角色启动时就生效。新角色加入环境后,MetaGPT 自动为其注册订阅:以后每条消息到来,路由器都会拿着角色的关注清单做比对。这里的关键细节是“按类型而非按内容”匹配:消息的类型由产生它的 Action 决定(即 cause_by 字段),所以只要 Action 命名规范,订阅关系就清晰可预测。想调整角色的响应行为,通常只需改 watch 集合,不必动任何业务代码。

订阅机制给了每个角色“选择性失聪”的能力。医院里没有哪个医生会抱怨“为什么这份报告不是给我的”,因为关注清单早已划清了边界。MetaGPT 角色的专注力同样来自这套明确拒绝的纪律——只有学会忽略大量无关消息,才能对真正重要的消息做出高质量响应。

Match(R, m)=1  ⟺  type(m)∈Sw(R)\displaystyle Match(R,\ m) = 1 \iff type(m) \in S_w(R)
公式说明:角色 R 是否响应消息 m,完全取决于消息类型是否属于 R 的订阅集合 S_w。
  1. type(m) 消息类型消息自带的类型标签,由产生该消息的 Action 名决定。
    type(m)="PRD"\displaystyle type(m) = \text{"PRD"}
  2. S_w(R) 订阅集合角色 R 声明关注的消息类型集合,是路由过滤的依据。
    Sw(架构师)={"PRD"}\displaystyle S_w(架构师) = \{\text{"PRD"}\}
  3. iff 条件成立判定类型匹配则响应(值为 1),不匹配则忽略(值为 0),无中间地带。
    Match(R,m)=1  ⟺  "PRD"∈{"PRD"}\displaystyle Match(R, m) = 1 \iff \text{"PRD"} \in \{\text{"PRD"}\}

消息路由机制

分诊台是医院消息流转的枢纽。每个医生把关注清单报给分诊台,分诊台据此决定每份新报告送到谁手上。MetaGPT 中的 MessageRouter 就是这样一个中央路由器:角色发布消息后,路由器查阅所有角色的订阅集合,把消息投递给全部“订阅了该类型”的角色;没有订阅者时,消息就留在环境中无人处理,可能出现协作流程等待,这正是需要后期排查的超时问题。

路由器除了按类型投递,还负责两件精细活:一是用 role_id 区分同名类型消息的定向发送,支持“指定角色接收”的场景;二是配合 timeout 机制做状态清理——当某个合作步骤等待回复超时时,路由器会处理超时状态,恢复协作流程,防止整个团队卡死在一次无人响应的消息上。调试时,路由器还能输出“路由图谱”,把每条消息从产生者到所有消费角色的路径绘制出来,相当于医院的分诊记录总览。

配合超时约束公式,路由器行为更加严谨:角色收到消息后应在时限内回复结果,超时则触发清理与恢复逻辑。分诊台的高效,在于它把“谁知道什么”登记得明明白白;路由器的可靠,则在于它对“无人接诊”的超时情况有兜底方案。这是从单角色 demo 走向生产级多智能体系统必须跨过的一道坎。

Target(m)={r∈Roles ∣ type(m)∈Sw(r)}\displaystyle Target(m) = \{r \in Roles\ |\ type(m) \in S_w(r)\}
公式说明:消息 m 的投递目标集合,是环境中所有订阅了该消息类型的角色。
  1. Target(m) 投递目标集对每条消息唯一确定的接收方集合,由路由器统一计算。
    Target(PRD)={架构师, 技术评审}\displaystyle Target(PRD) = \{\text{架构师},\ \text{技术评审}\}
  2. r ∈ Roles 遍历角色路由器逐个检查环境中的每个角色,判断是否在目标集中。
    r∈{产品经理, 架构师, 工程师, 测试}\displaystyle r \in \{\text{产品经理},\ \text{架构师},\ \text{工程师},\ \text{测试}\}
  3. 订阅判定条件只有消息类型命中角色订阅集合时,该角色才会被纳入目标集。
    "PRD"∈Sw(架构师)⇒架构师∈Target(PRD)\displaystyle \text{"PRD"} \in S_w(\text{架构师}) \Rightarrow 架构师 \in Target(PRD)

自定义角色实战

理解了上述机制,你就会发现定制角色并不神秘——就像医院要开一个新科室:先做一块胸牌(Profile),再采购一批工具箱(Actions),最后把关注清单报给分诊台(watch)。MetaGPT 鼓励的典型做法是:直接继承 Role 类写子类,在 __init__ 中设置身份字段,在 _init_actions 中装配 Action 列表,再调用 _watch 注册订阅类型。

当角色收到消息时,_think 负责选动作。对于只有一个 Action 的简单角色,直接调用即可;对于多技能角色,则需按消息类型路由:PRD 类型消息调度给“写设计”动作,任务类型消息调度给“写代码”动作。MetaGPT 还提供零代码自定义路径——你只需定义好 Action 类,框架能自动生成配套的角色体验,大大降低上手门槛。若要在角色中接入外部 Agent,只需让某个 Action 的 run 方法把请求转发给外部系统,并把返回值包装成标准消息即可。

定制角色的流程,本质是给大模型一套清晰的“执业规则”。医院的每个科室都有标准化的建科流程,MetaGPT 的角色定制同样有迹可循。当你把胸牌、工具箱、关注清单三件事做扎实,一个能稳定协作的新角色就诞生了;多角色之间的配合,则由订阅和路由机制自动完成。

Choose(m)={a1,type(m)∈T1a2,type(m)∈T2idle,otherwise\displaystyle Choose(m)=\begin{cases} a_1,& type(m)\in T_1\\ a_2,& type(m)\in T_2\\ \text{idle},& \text{otherwise} \end{cases}
公式说明:角色的动作选择函数是一个分段函数:按消息类型决定调用哪个动作,都不匹配则保持空闲。
  1. 分段条件匹配系统先读取消息类型,与各动作声明的处理类型逐一比对。
    type(m)∈T1⇒使用动作a1\displaystyle type(m)\in T_1 \Rightarrow 使用动作 a_1
  2. 动作绑定每个动作对应它能处理的消息类型区间,绑定关系在装配阶段注册。
    T1↔a1=writePRD,T2↔a2=writeCode\displaystyle T_1 \leftrightarrow a_1=writePRD,\quad T_2 \leftrightarrow a_2=writeCode
  3. idle 空闲态所有条件都不满足时角色不行动,等待新消息,避免盲目消费。
    otherwise⇒idle\displaystyle \text{otherwise} \Rightarrow \text{idle}

调试与最佳实践

当医院协作出现混乱——某份报告没人接、某位医生忙不过来,科主任不会逐个医生口头询问,而是调出分诊记录分析问题。MetaGPT 提供了类似的“协作路线图”:在调试模式下打印 env.repr 或用可视化工具渲染路由图谱,你能看到每条消息由谁产生、被哪些角色消费、最终去向何处。消息链路上任何一个断点,都会在图上清晰暴露。

实践中有几条黄金法则值得内化。第一,订阅最小化:角色只订阅自己真正要处理的类型,贪多会让角色被无关消息干扰,增加推理成本与出错概率。第二,消息类型规范化:Action 命名尽量遵循“动词+对象”模式(如 WritePRD、RunTest),让类型即文档。第三,善用调试工具:MetaGPT 的日志系统与 Tool 类可以单独验证某个 Action 的输入输出是否合规,不必每次都在完整协作链上排错。第四,组合子任务而非堆角色:订阅机制让团队可以动态增减成员,但消息类型越多,路由图谱越复杂,建议将强耦合的子步骤封装为单个角色的多个 Action。

维护一个多智能体系统,就像经营一家不断扩张的医院。定期审视协作路线图上的每一条边,砍掉没人订阅的消息类型、合并过度细分的角色,系统就能保持轻盈和高效。理解订阅机制绝非终点——把它用于设计边界清晰、路由可预测的协作系统,才是真正的价值所在。

G=(R, E),E={(ri, rj)∣∃ m: type(m)∈Sw(rj)∧ri 产出 m}\displaystyle G=(R,\ E),\quad E=\{(r_i,\ r_j)\mid \exists\, m:\ type(m)\in S_w(r_j) \land r_i\ \text{产出}\ m\}
公式说明:协作网络是一个有向图,节点是角色,边表示“前者产出的消息被后者订阅消费”。
  1. 节点集合 R图中每个节点是一个角色,独立承担一类职责。
    R={PM, 架构, 开发, 测试}\displaystyle R=\{\text{PM},\ \text{架构},\ \text{开发},\ \text{测试}\}
  2. 边集合 E从产出者指向订阅者的一条有向边,代表一次成功的消息投递。
    (PM, 架构)∈E  ⟺  PM产出的PRD被架构订阅\displaystyle (PM,\ 架构)\in E \iff PM产出的PRD被架构订阅
  3. 存在性条件只要存在一条消息把两个角色连通,图中就产生协作边。
    ∃ m: type(m)="PRD"⇒(PM, 架构)∈E\displaystyle \exists\, m:\ type(m)=\text{"PRD"} \Rightarrow (PM,\ 架构)\in E

AI 应用场景

自动化软件交付流水线

阶段:多智能体协作设计

解决的问题:传统软件开发中,需求、设计、编码、测试各环节靠人力传递文档,信息经常错位、延迟。MetaGPT 用角色订阅机制,让产品经理订阅“需求”,架构师订阅“PRD“,工程师订阅”任务“,测试订阅”代码”,实现需求到测试的自动化流转,每个环节只要产出标准类型消息,下游自动接单。

具体案例:某团队用 MetaGPT 搭建“软件公司”:负责人发布一条“需求”消息,产品经理角色自动产出 PRD,架构师收到 PRD 后产出任务拆分,工程师实现代码并提交,测试角色自动执行质量检查并反馈缺陷报告。整条流水线零人工干预,文档类型规范统一。

企业客服工单自动分诊

阶段:消息路由与角色协作

解决的问题:大型企业的客服工单种类繁杂(投诉、技术故障、退款咨询),人工分诊慢且容易错。利用订阅机制,为每类问题创建专家角色:投诉专员订阅“投诉”消息,技术支持订阅“技术故障”消息,财务专员订阅“退款”消息。工单进入系统后,路由器按类型精准投递,各专家并行处理。

具体案例:一家电商平台接入该方案后,工单平均响应时间从 2 小时缩短到 15 分钟。当遇到“技术故障+退款”的复合工单时,可同时投递给两个角色,由后续环节合并处理结果,避免单点瓶颈。

多角色内容生产协作

阶段:内容工厂与角色编排

解决的问题:自媒体团队生产长图文时,选题、大纲、初稿、校对、配图等多个环节相互依赖。用 MetaGPT 定义编辑(订阅“选题”并产出“大纲”)、写手(订阅“大纲”并产出“初稿”)、校对(订阅“初稿”并产出“终稿”)、美工(订阅“终稿”并产出“配图”)等角色,内容生产如同流水线高效推进。

具体案例:某内容团队用该方案将科普文章生产周期从 3 天压缩到半天。编辑只需发一条选题消息,后续大纲、初稿、终稿自动产出,人工只做最终把关,大幅释放创作精力。

Python 演示

python84 行
# -*- coding: utf-8 -*-"""演示 MetaGPT 角色订阅机制的极简实现。"""  class Message:    def __init__(self, msg_type, content, sender=""):        self.type = msg_type        self.content = content        self.sender = sender     def __repr__(self):        return "Message({}, {}, {})".format(self.type, self.content, self.sender)  class Role:    def __init__(self, name, profile, subscribed_types, actions=None):        self.name = name        self.profile = profile        self.subscribed = set(subscribed_types)        self.actions = actions or []        self.processed = 0     def observe(self, msg):        """只接收订阅类型匹配的消息, 这是订阅机制的核心。"""        return msg.type in self.subscribed     def act(self, msg):        """取出第一条动作执行, 返回结果消息并计数。"""        self.processed += 1        action = self.actions[0]        return action(msg)  class MessageRouter:    def __init__(self):        self.roles = []     def register(self, role):        self.roles.append(role)     def publish(self, msg):        replies = []        receivers = []        for role in self.roles:            if role.name == msg.sender:                continue            if role.observe(msg):                receivers.append(role.name)                reply = role.act(msg)                reply.sender = role.name                replies.append(reply)        print("[路由] {} -> 接收: {}".format(msg.type, receivers or "无"))        for reply in replies:            self.publish(reply)  def write_prd(msg):    return Message("PRD", "根据需求「{}」编写PRD".format(msg.content))  def make_task(msg):    return Message("任务", "按PRD拆分开发任务")  def write_code(msg):    return Message("代码", "实现: " + msg.content)  def run_test(msg):    return Message("测试报告", "代码已测试通过")  if __name__ == "__main__":    router = MessageRouter()    pm = Role("产品经理", "需求分析", ["需求"], [write_prd])    arch = Role("架构师", "系统设计", ["PRD"], [make_task])    dev = Role("工程师", "编码实现", ["任务"], [write_code])    tester = Role("测试", "质量保障", ["代码"], [run_test])    for r in [pm, arch, dev, tester]:        router.register(r)    router.publish(Message("需求", "做一个智能客服小程序", sender="客户"))    print("\n[订阅统计]")    for r in router.roles:        print("{} 订阅{} 处理{}条".format(r.name, sorted(r.subscribed), r.processed))
目的:用约 80 行代码复刻 MetaGPT 的角色订阅核心逻辑,展示“消息只被订阅者接收”的完整链路。
  1. class Role:定义角色基类,包含名称、身份、订阅集合与动作列表。
  2. self.subscribed = set(subscribed_types)关注清单用集合存储,天然支持快速类型查重。
  3. def observe(self, msg): return msg.type in self.subscribed订阅过滤的核心:类型匹配才返回真。
  4. def act(self, msg)执行第一个动作并返回新消息,模拟角色产出。
  5. class MessageRouter:中央路由器,负责把消息分发给所有检查过订阅的角色。
  6. if role.name == msg.sender: continue跳过消息发送者,避免角色处理自己刚发的消息。
  7. if role.observe(msg): receivers.append(role.name)只有订阅者进入接收名单,参与流程推进。
  8. for reply in replies: self.publish(reply)递归发布所有回复,让协作链自动向下游传递。
  9. pm = Role("产品经理", "需求分析", ["需求"], [write_prd])一行声明一个角色:身份、专长、订阅、动作一次配齐。
  10. router.publish(Message("需求", "做一个智能客服小程序", sender="客户"))从一条“需求”消息启动整条协作流水线。

常见误区

误区我以为给角色发了消息,它就会自动读取并处理,就像微信收到消息一样会弹出提醒,不可能忽略。

正解MetaGPT 角色的收件箱与订阅机制是两回事。消息会进入角色视野(rc.news),但只有 type(m) ∈ watch 的消息才会被标记为“重要消息”进入处理流程。订阅之外的消息,角色会主动忽略——就像医生看到自己不专业的报告会直接跳过一样。想验证某个消息是否被处理,应检查角色 watch 里是否包含该消息类型。

为何常见:日常通讯软件都是“收到即提醒”,容易让人默认消息到达必被消费;忽略了 AI 协作中“选择性接收”才是正确模型。

误区我认为 Action 定义了角色能做什么,所以装了 WritePRD 这个动作,角色自然就会接收 PRD 消息来写。

正解Action 只代表“能力”,watch 才代表“意愿”。角色装了 WritePRD 动作但不订阅 PRD 类型,就永远不会被 PRD 消息触发。能力与订阅是独立配置的两件事:先有工具箱,再把对应的消息类型写进关注清单,动作才会被调用。

为何常见:人的直觉里“会做什么”和“关注什么”通常一致,但框架分离这两个概念正是为了支持“有能力但不响应”的灵活控制。

误区我认为每个角色就像单线程工人,一次只能携带一种技能,想处理多类消息就得建多个角色。

正解角色的 _actions 是一个列表,天然支持多技能。真正的难点在于 _think 如何根据消息类型选择正确的 Action——这通常用类型匹配分支实现,也就是文中公式 Choose(m) 的分段逻辑。多技能角色不仅合法,还是减少角色数量、简化路由图谱的推荐做法。

为何常见:早期示例代码大多展示单 Action 角色,初学者产生了“一角色一动作”的刻板印象。

误区我觉得多订阅一些消息类型能提高响应率,避免漏掉关键信息,反正模型能力强,多处理点任务没坏处。

正解订阅过载会带来三重代价:模型要过滤大量无关消息导致推理成本上升;无关消息可能触发错误的 Action 产生噪声消息;路由图谱变复杂,排查问题难度陡增。最佳实践是“最小化订阅”——只订阅自己真正必须处理的类型,让角色保持专注,正如心内科医生不该订阅骨科报告。

为何常见:人类面对信息过载时也会产生“多多益善”的焦虑,但系统设计恰恰需要克制与精确。

误区我发布了一条 PRD 消息,但架构师角色没有反应,我立刻怀疑是路由器坏了,花半天排查消息丢失问题。

正解首先要检查架构师角色的 watch 是否包含 PRD 类型。如果没订阅,那路由器行为完全正常——消息被“正确地忽略了”。这种现象不是 bug,而是订阅过滤的预期效果。常见做法是打印路由图谱查看消息类型与各角色订阅的匹配情况,一图胜千言。

为何常见:调试者容易假设“发出必达”,但发布-订阅系统的核心语义之一就是“未订阅则必不达”。

面试题

Q1. Role 的三要素是哪三项?

单选题 · easy

  • A.身份、动作、订阅← 正确答案
  • B.姓名、邮箱、工号
  • C.环境、工具、日志
  • D.内存、缓存、队列

答案:A。Role 由身份档案 Profile、能力集合 Actions、订阅集合 watch 三部分构成,对应医院比喻中的胸牌、工具箱和关注清单。其他选项均为无关的系统组件。

Q2. 角色收到一条消息,决定它是否响应的关键是?

单选题 · easy

  • A.消息内容是否足够长
  • B.消息类型是否在订阅集合中← 正确答案
  • C.路由器是否随机抽取
  • D.发送者的身份是否高级

答案:B。订阅机制按消息类型匹配,type(m) ∈ S_w(R) 成立才响应。内容长度、发送者职级都不影响判定,路由器也从不随机投递。

Q3. 角色通过什么方法声明自己关注的消息类型?

单选题 · easy

  • A.actions 列表
  • B.profile 字段
  • C.watch 订阅集合← 正确答案
  • D.constraints 约束

答案:C。watch(老版本也叫 subscribe)是订阅集合的载体,声明角色关注哪些消息类型。actions 管能力,profile 管身份,constraints 管行为边界。

Q4. 一条消息发布后没有任何角色订阅,路由器的行为是?

单选题 · mid

  • A.消息被正确忽略并停留在环境中← 正确答案
  • B.路由器抛出异常终止协作
  • C.路由器复制消息发给所有角色
  • D.路由器自动创建新角色来处理

答案:A。发布-订阅系统的语义是“未订阅必不达”。无订阅者时消息不会投递给任何角色,可能造成流程等待,需要通过超时机制或路由图谱排查,而非立即报错。

Q5. Action 的 name 字段在协作中还扮演什么角色?

单选题 · mid

  • A.决定角色的身份档案
  • B.常作为产出消息的类型标签← 正确答案
  • C.控制消息的发送优先级
  • D.指定路由器的工作频率

答案:B。Action 的 name 通常就是产出消息的 cause_by 类型,例如 WritePRD 产出 PRD 消息。这个约定让下游角色能直观地通过类型订阅接力协作。

Q6. 调试协作全貌最合适的工具是什么?

单选题 · mid

  • A.给每个角色加上延时等待
  • B.查看环境路由图谱总览← 正确答案
  • C.重启项目并观察日志
  • D.删除所有订阅重新配置

答案:B。路由图谱把每条消息的产出者与消费者绘制成图,断点一目了然。逐个加延时或重启只能碰运气,删订阅则破坏配置,都不是系统性调试手段。

Q7. 关于 Profile 的身份字段,正确的说法是?

多选题 · mid

  • A.name 是角色在消息中的显示名← 正确答案
  • B.profile 描述角色的专业领域← 正确答案
  • C.constraints 限制角色的行为边界← 正确答案
  • D.goal 决定角色的内存大小

答案:ABC。Profile 四字段为 name、profile、goal、constraints,分别对应显示名、专长、目标、约束。goal 是协作目标,与内存容量毫无关系。

Q8. 让一个角色能同时处理 PRD 和 任务 两类消息,最合理的配置是?

单选题 · mid

  • A.只订阅 PRD 类型就够了
  • B.订阅两类并为每类配置动作← 正确答案
  • C.把两类消息合并成一种类型
  • D.增加角色的内存容量

答案:B。多技能角色的正确姿势是“订阅两类 + 每类一个 Action”,并让 _think 按类型选择动作。只订阅一类会漏消息,合并类型会破坏路由语义,加内存则完全与问题无关。

Q9. 关于订阅与 Actions 的关系,正确的描述是?

多选题 · hard

  • A.订阅决定消息何时触发角色← 正确答案
  • B.Actions 提供角色执行的能力← 正确答案
  • C.两者相互独立可分别配置← 正确答案
  • D.必须委托路由器统一分配

答案:ABC。订阅管“何时响应”,Actions 管“如何执行”,两者是独立的配置维度,可分别设置。路由器只负责投递,不负责分配能力,D 错误。

Q10. 订阅等待超时后,路由器通常会做什么?

多选题 · extreme

  • A.清理未完成的等待状态← 正确答案
  • B.永久阻塞整个协作流程
  • C.记录超时并恢复流程运转← 正确答案
  • D.强制把消息塞给所有角色

答案:AC。超时机制为防止协作死锁而设:路由器清理等待状态并记录超时,让流程继续运转,避免永久阻塞。强制广播会破坏订阅语义,不在设计范围内。