这篇讲一条多角色协作链路:团队编制、职责绑定、消息分发、记忆分层与上下文接续。
背景很直接:一件事拆成几段之后,如果每段之间靠口头衔接,链路就等于没建。真正要处理的是三件事:每段由谁接、接不上怎么办、开销怎么收敛。
链路形态
四个模块串起来,职责各自独立。
| 模块 | 职责 |
| team_scaffold | 按分工描述创建成员,完成入群绑定 |
| role_bind | 把名字、职责、指令、模型四项作为独立字段绑定 |
| dispatch_rule | 决定消息由谁先应答、何时转派 |
| memory_tier | 维护成员级与团队级两层记忆 |
顺序不能颠倒。跳过 role_bind 直接配分发规则,成员之间会因为职责重叠而互相抢答。
职责绑定是独立字段
四项分开绑的意义在于替换成本:改其中一项,其余三项不受影响。
ROLE_FIELDS = (“name”, “duty”, “prompt”, “model”)
def bind_role(role, patch):
merged = dict(role)
for k, v in patch.items():
if k not in ROLE_FIELDS:
raise KeyError(“unknown field: %s” % k)
merged[k] = v
return merged
四项各管一件事。名字只影响界面上怎么显示、调用时怎么指代;职责决定它能接住哪些消息;指令规定拿到消息后按什么口径作答;模型决定回答的快慢与表达习惯。四项存在同一份记录里,修改走补丁合并,所以换名字不会牵动指令,换模型也不会牵动职责。
任务也不是一次性的。同一个成员留着重用,下次可以直接接着处理之前那件事,不必重建上下文。
职责位还分两层:团队统筹与群组管理各为一层,同一实例可同时挂两个职责。一层管队伍层面的组成,另一层只管单个群组实例内部的成员与参数。
消息分发决定开销dispatch_rule定的是默认单点应答加按需分派,避免广播式触发。
def dispatch(msg, members, lead):
# 默认路径:先由群组管理实例应答
if lead.can_handle(msg):
return [lead.handle(msg)]
# 按需分派:只唤醒相关成员,不广播
return [m.handle(msg) for m in members if m.matches(msg)]
默认单点先应答的理由是省下重复判断:每条消息先交给群组管理实例,能接就接,接不了才按条件唤醒相关成员。广播式触发的写法是把每条消息推给全部成员,每个成员都得跑一遍判断,参与面直接等于成员总数,轮次也随对话条数往上走。budget_guard的依据就在这里:参与面与轮次线性推高开销,收敛入口即收敛成本。
计数视图支持按日期间隔、成员、模型筛选。统计结果停在使用量本身,不做任何货币换算。这个口径只回答「用了多少」,不回答「花了多少」;单价由所选模型决定,把折算写进统计会让历史数据失去可比性。预算控制的入口因此落在两处:压住参与面,以及让单次对话的轮次停在该停的地方。
记忆分层与上下文接续memory_tier把记忆拆成成员级与团队级两层,均以纯文本落盘。
文件层面看,成员级记忆在成员目录下各存一份,团队级记忆在团队目录下存一份,都是可以按行编辑的纯文本。成员级记的是这个成员的偏好、它自己做过的决定、以及它需要长期记住的口径;团队级记的是全体成员都要遵守的信息,比如分工边界与共享约定。
写入不是全收:每轮对话结束后有一次审查,判断新增信息是否需要保存、已有条目是否需要更新或删除。
上下文接近窗口上限的时候,handoff_note输出三类产物:摘要、压缩后的上下文、可跨会话续跑的接续记录。三类各有用途:摘要是给人看的,压缩后的上下文是给下一个会话当输入用的,接续记录则把当前进展、待办与已确认的结论单独列出来。调用方看到的是连续会话。更早的内容落在本地文件,可以按需读取。
每个成员各选模型
每个成员可以各选各的模型,这是字段独立带来的便利。但同一支链路里尽量用同一个模型更省心:不同模型对同一份指令的理解偏差会累积,排查问题时多一层变量。
踩坑记录
- 把职责当成描述性文字。职责必须能落成判定条件,否则两个成员会同时认领同一条消息,同一条消息被处理两次,还得分出人力去判断哪一份结果算数。
- 用广播式分发省事。成员一多,开销按参与面放大,链路反而更慢。
- 把记忆当成全量日志。记忆只该放以后还要用的信息,历史细节留给会话记录,否则每一轮都要读一堆用不上的内容。
- 忽略接续环节。上下文断掉之后如果只能重新描述一遍,前面攒的所有配置都白做,跨会话的那部分能力等于没启用。
两处常见误解
一是把成员数量当成能力上限。链路能不能跑,取决于段落划分是否清晰,与成员个数没有直接关系。
二是把本地存储理解成临时缓存。配置、会话与记忆都落盘在本地目录,卸载插件只移除程序本身,不会替你删掉这些文件。重装之后旧数据仍在,需要清理时得自己动手,排障时应当先确认这一点。
自建的分发入口可以先挂在your-domain.test下的内网地址,跑顺再考虑对外开放。
前置依赖两项:运行框架本体,以及至少一个已配置可调用的模型。这两项缺一,链路在头一步就起不来。
一条协作链路好不好用,看它能不能在会话断掉之后接着跑。