LOADING

页面加载中...

Astrbot Message Decorator Debugger 插件开发记录

astrbot_plugin_decorator_debugger

从 FlainBot 引入设计理念

熟悉我的朋友知道,我曾经想过开发一个 以消息装饰链为设计核心 的聊天机器人框架,但是不幸的是项目很快黄了。

但是,消息装饰链 这个设计在支持多插件集成的聊天机器人框架(如 AstrBot)中具有重要的应用价值。

为了让更多开发者能够调式 Astrbot 消息装饰链的执行过程,我决定把这个设计理念提炼出来,开发一个 AstrBot Message Decorator Debugger 插件。

让 AstrBot 的消息装饰链不再是黑盒

在 AstrBot 中,一条看似简单的消息,真正交给模型之前可能已经经过长期记忆、人格设定、日程提醒、知识检索、工具注册等多个插件;模型生成结果之后,又可能继续经过回复前缀、格式整理、文本转图片、语音处理等装饰逻辑。

这些能力让机器人变得丰富,但也带来了一个很现实的开发问题:当最终结果不符合预期时,很难快速判断是哪一个环节改了什么。

日志可以告诉我某个处理器“运行过”,却很难回答下面这些问题:

  • system prompt 中的一段内容是谁注入的?
  • contexts 为什么突然多了几十条记录?
  • 同一条消息为什么触发了多轮 LLM 请求?
  • 某个 on_llm_requeston_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_requeston_decorating_result

左侧每条记录会明确显示实际包含的 hook:

on_llm_request × 2
on_decorating_result × 1

同一条消息可能先后经历多轮模型请求,最后再进入结果装饰阶段,所以一条 trace 可以同时包含两个 hook。相比把记录简单归类为“请求”或“回复”,展示实际阶段和次数更接近真实执行过程。

按 Round 组织多轮请求

Agent、工具调用或重试流程可能让一条消息触发多次 LLM 请求。插件会将它们组织为 Round 1Round 2 等分组,并保留每一轮的入口、处理步骤与最终状态。

这让开发者能够区分:某段上下文是在第一次请求前就存在,还是在工具执行后进入了下一轮请求。

字段、组件和字符级差异

请求阶段重点关注:

  • prompt
  • system_prompt
  • contexts
  • 临时内容部分
  • 模型与会话信息
  • 工具集合与工具调用结果

结果阶段则关注消息组件链的增加、删除和替换。

文本差异不只把整行标成新增或删除,还会在对应行内继续比较 Unicode 字符。中文、普通文本和组合 emoji 都会以可见字符为单位高亮具体变化。面对很长的 prompt 时,不必再人工寻找两行之间究竟差了哪几个字。

查看执行顺序、耗时和异常

每个处理器都会显示执行状态和耗时。如果处理器抛出异常,trace 会记录异常信息,但不会吞掉异常或改变原有传播语义。

这既可以用来排查错误,也能发现一些并不报错、但在消息链中耗时明显偏高的装饰逻辑。

临时跳过处理器,验证问题来源

知道“某个处理器修改了内容”还不够,有时还需要验证它是不是问题的真正来源。

插件允许只对命中调试规则的消息临时跳过指定处理器,作用域可以是当前会话,也可以是全局调试规则。正常消息不会因为一次排障操作永久失去某个插件能力。

这相当于为装饰链提供了一个轻量的对照实验:保留其他条件不变,只移除一个处理步骤,再观察结果是否恢复正常。

一次典型的排障过程

假设机器人最终发送的回复中出现了一段意外内容,同时模型请求的上下文长度也异常增加。

过去的排查方式通常是打开多个插件日志、猜测执行顺序,再逐个关闭插件重试。使用 Message Decorator Debugger 后,过程可以缩短为:

  1. 在问题会话执行 /decorator-debug next
  2. 发送能够稳定复现问题的消息。
  3. 打开插件 Page,选择最新 trace。
  4. on_llm_request 分组中查看哪些步骤修改了 contextssystem_prompt
  5. 通过字符级差异定位具体注入内容。
  6. on_decorating_result 分组中检查最终回复是否再次被修改。
  7. 临时跳过可疑处理器,重新采集下一条消息进行对照。

最终得到的不是“可能和某个插件有关”,而是一条完整证据链:哪个处理器在什么阶段执行、修改了哪些字段、修改前后是什么、跳过之后结果是否变化。

技术实现

插件主要观察 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_requeston_decorating_result,不会试图替代完整的分布式追踪系统。但对于插件开发、组合调试和问题复现来说,一条带有步骤、快照、差异与隔离控制的本地 trace,已经能够节省大量时间。

项目仍在持续完善,代码与使用说明可以在 GitHub 查看:

https://github.com/FloranceYeh/astrbot_plugin_decorator_debugger