从 FlainBot 引入设计理念
熟悉我的朋友知道,我曾经想过开发一个 以消息装饰链为设计核心 的聊天机器人框架,但是不幸的是项目很快黄了。
但是,消息装饰链 这个设计在支持多插件集成的聊天机器人框架(如 AstrBot)中具有重要的应用价值。
为了让更多开发者能够调式 Astrbot 消息装饰链的执行过程,我决定把这个设计理念提炼出来,开发一个 AstrBot Message Decorator Debugger 插件。
让 AstrBot 的消息装饰链不再是黑盒
在 AstrBot 中,一条看似简单的消息,真正交给模型之前可能已经经过长期记忆、人格设定、日程提醒、知识检索、工具注册等多个插件;模型生成结果之后,又可能继续经过回复前缀、格式整理、文本转图片、语音处理等装饰逻辑。
这些能力让机器人变得丰富,但也带来了一个很现实的开发问题:当最终结果不符合预期时,很难快速判断是哪一个环节改了什么。
日志可以告诉我某个处理器“运行过”,却很难回答下面这些问题:
- system prompt 中的一段内容是谁注入的?
- contexts 为什么突然多了几十条记录?
- 同一条消息为什么触发了多轮 LLM 请求?
- 某个
on_llm_request或on_decorating_result到底有没有执行? - 回复中的一小段文字,是在哪个步骤被增加、删除或替换的?
- 暂时跳过一个装饰器后,问题是否还会出现?
为了解决这些问题,我开发了 AstrBot Message Decorator Debugger。它不是新的消息处理框架,而是一层按需启用的运行时观察工具:把原本隐藏在消息管线里的装饰过程,整理成可以逐步检查、比较和导出的 trace。
项目地址:FloranceYeh/astrbot_plugin_decorator_debugger
插件的核心理念
1. 调试工具首先不能干扰被调试对象
调试插件最危险的失败方式,不是“没有记录到数据”,而是为了记录数据改变了原本的消息行为。
因此,这个插件默认只观察命中采集规则的消息。它会代理装饰处理器,记录执行前后的结构化快照、耗时、状态和异常,但保留原有参数、调用顺序、返回值和异常传播方式。只有管理员明确设置“临时跳过某个处理器”时,它才会对对应的调试消息改变执行路径。
插件卸载或重载时,所有运行时插桩都会恢复,避免留下难以察觉的全局副作用。
2. 记录“因果步骤”,而不只是最终结果
只保存请求进入前和离开后的两个快照,能够证明内容发生了变化,却无法说明变化来自哪里。
Message Decorator Debugger 会把每个装饰处理器记录成独立步骤:
- 处理器所属插件、方法名、类别和优先级
- 属于
on_llm_request还是on_decorating_result - 执行状态与耗时
- 处理前、处理后的快照
- 发生变化的字段或消息组件
- 文本中的具体增删字符
- 异常类型、消息和调用栈
这样看到的不只是“prompt 变了”,而是“某个记忆插件在第 2 步向 contexts 中加入了内容,随后另一个插件又修改了 system prompt”。
3. 调试范围必须可控
全局记录每一条消息既浪费资源,也容易收集过多敏感数据。因此插件提供了三种采集粒度:
- 只追踪当前会话的下一条消息
- 在当前会话中追踪一段限定时间
- 在明确需要时开启全局追踪
日常排障时,我通常使用“下一条消息”模式。它足够精确,也不会让调试数据迅速淹没真正的问题。
4. 快照应描述业务语义,而不是倾倒运行时对象
这是开发过程中非常重要的一次修正。
早期实现曾递归读取工具对象的 __dict__。结果不仅包含工具 schema,还把 _pending_tasks、异步 Task、客户端状态等运行时内部对象一起记录下来。它们会随着事件循环不断变化,产生大量重复且没有业务意义的差异。
现在,工具快照只保留稳定信息:工具名称、启用状态、来源模块、是否为后台任务以及参数 schema。调试器关心的是“工具集合发生了什么变化”,而不是 asyncio 此刻维护了多少任务。
这也是我对可观测性工具的一条经验:快照不是对象序列化,快照是经过设计的语义模型。
追踪 on_llm_request 与 on_decorating_result
左侧每条记录会明确显示实际包含的 hook:
on_llm_request × 2
on_decorating_result × 1
同一条消息可能先后经历多轮模型请求,最后再进入结果装饰阶段,所以一条 trace 可以同时包含两个 hook。相比把记录简单归类为“请求”或“回复”,展示实际阶段和次数更接近真实执行过程。
按 Round 组织多轮请求
Agent、工具调用或重试流程可能让一条消息触发多次 LLM 请求。插件会将它们组织为 Round 1、Round 2 等分组,并保留每一轮的入口、处理步骤与最终状态。
这让开发者能够区分:某段上下文是在第一次请求前就存在,还是在工具执行后进入了下一轮请求。
字段、组件和字符级差异
请求阶段重点关注:
promptsystem_promptcontexts- 临时内容部分
- 模型与会话信息
- 工具集合与工具调用结果
结果阶段则关注消息组件链的增加、删除和替换。
文本差异不只把整行标成新增或删除,还会在对应行内继续比较 Unicode 字符。中文、普通文本和组合 emoji 都会以可见字符为单位高亮具体变化。面对很长的 prompt 时,不必再人工寻找两行之间究竟差了哪几个字。
查看执行顺序、耗时和异常
每个处理器都会显示执行状态和耗时。如果处理器抛出异常,trace 会记录异常信息,但不会吞掉异常或改变原有传播语义。
这既可以用来排查错误,也能发现一些并不报错、但在消息链中耗时明显偏高的装饰逻辑。
临时跳过处理器,验证问题来源
知道“某个处理器修改了内容”还不够,有时还需要验证它是不是问题的真正来源。
插件允许只对命中调试规则的消息临时跳过指定处理器,作用域可以是当前会话,也可以是全局调试规则。正常消息不会因为一次排障操作永久失去某个插件能力。
这相当于为装饰链提供了一个轻量的对照实验:保留其他条件不变,只移除一个处理步骤,再观察结果是否恢复正常。
一次典型的排障过程
假设机器人最终发送的回复中出现了一段意外内容,同时模型请求的上下文长度也异常增加。
过去的排查方式通常是打开多个插件日志、猜测执行顺序,再逐个关闭插件重试。使用 Message Decorator Debugger 后,过程可以缩短为:
- 在问题会话执行
/decorator-debug next。 - 发送能够稳定复现问题的消息。
- 打开插件 Page,选择最新 trace。
- 在
on_llm_request分组中查看哪些步骤修改了contexts或system_prompt。 - 通过字符级差异定位具体注入内容。
- 在
on_decorating_result分组中检查最终回复是否再次被修改。 - 临时跳过可疑处理器,重新采集下一条消息进行对照。
最终得到的不是“可能和某个插件有关”,而是一条完整证据链:哪个处理器在什么阶段执行、修改了哪些字段、修改前后是什么、跳过之后结果是否变化。
技术实现
插件主要观察 AstrBot 的两个扩展面:LLM 请求 hook 与结果装饰阶段。
加载时,它会先检查目标方法、处理器注册表和调用签名是否符合预期。检查通过后才安装可逆插桩;如果 AstrBot 内部接口发生变化,插件会停止安装观察逻辑,而不是冒险修改未知调用链。
在一次 trace 中,插件使用上下文变量关联当前消息、请求轮次和处理器代理。每个代理执行以下流程:
采集处理前快照
↓
调用原始处理器
↓
采集处理后快照
↓
计算结构化差异并记录耗时、状态和异常
请求阶段和结果阶段使用不同的快照模型。请求快照关注 ProviderRequest 的逻辑字段;结果快照关注 MessageEventResult 的组件链。对于图片、语音和文件,插件只记录必要的结构信息,不读取或下载二进制内容。
插件 Page 通过 API 获取状态和完整 trace,并通过 SSE 接收新记录通知。新 trace 完成后,页面可以自动刷新并选中最新记录。
数据边界与安全设计
调试数据天然可能包含 prompt、用户标识、消息正文和媒体地址,因此这个插件没有把“记录得越多”当成唯一目标。
当前限制包括:
- 内存中最多保留 200 条 trace
- 单条 trace 最多记录 100 个步骤
- 单个文本字段最多保存 20,000 个字符
- 单条 trace 的快照数据约限制为 2 MB
- 超出限制后使用明确的截断摘要,不再制造整份请求都发生变化的伪差异
- 默认 JSON 导出会脱敏,包括文本差异中的正文
- 原始导出必须在页面中再次确认
- 重启或重载插件后,内存 trace 自动清空
这些限制并不能替代运维层面的访问控制,但至少保证调试器不会在无人注意时无限收集数据。
快速使用
插件当前面向 AstrBot 4.26.5 及以上版本。安装并重载后,可以从一次性采集开始:
/decorator-debug status
/decorator-debug next
发送待调试消息后,打开插件 Page 查看 trace。
需要在当前会话持续观察时:
/decorator-debug on 10m
/decorator-debug off
查看或隔离处理器:
/decorator-debug handlers
/decorator-debug disable <handler-keyword> session
/decorator-debug enable <handler-keyword> session
/decorator-debug enable-all session
清空内存记录:
/decorator-debug clear
开发这个插件后的几点体会
第一,日志与 trace 解决的是不同问题。日志适合记录事件,trace 更适合解释一条请求如何经过多个步骤演化成最终结果。
第二,调试信息必须稳定。对象内部状态、内存地址、异步任务列表看起来“信息量很大”,实际上只会制造噪声。真正有价值的是经过筛选的业务字段。
第三,聚合视图不能简单重复步骤详情。逐步差异用于定位责任,聚合步骤应该只回答这一轮整体改了哪些字段,而不是再次输出几百行相同内容。
第四,调试工具同样需要安全边界。尤其是差异文本,它经常比快照本身更容易遗漏脱敏,因为敏感内容已经被拼进普通字符串。
结语
Message Decorator Debugger 的目标很直接:当 AstrBot 中多个插件共同参与一条消息时,让开发者能够看到真实的执行顺序和修改结果,而不是依靠猜测逐个关闭插件。
它目前聚焦于 on_llm_request 与 on_decorating_result,不会试图替代完整的分布式追踪系统。但对于插件开发、组合调试和问题复现来说,一条带有步骤、快照、差异与隔离控制的本地 trace,已经能够节省大量时间。
项目仍在持续完善,代码与使用说明可以在 GitHub 查看:
https://github.com/FloranceYeh/astrbot_plugin_decorator_debugger