BoxLang AI 深度解析:中间件——AI 框架中不可或缺的层级
BoxLang AI 深度解析 — 第 4 部分(共 7 部分):中间件 — 每个 AI 框架中缺失的层级 🧵
BoxLang AI 3.0 系列 · 第 4 部分(共 7 部分)
每个团队最终都会向他们的 AI 代理提出这样一个问题:我们该如何测试这些东西?
代理会进行实时的 LLM 调用,会触发真实的工具,且具有非确定性的输出。标准的单元测试方法在这里完全失效:你无法 Mock 每个提供商,无法重放三周前的对话,也无法自信地告诉利益相关者,你今天部署的代理与你最初批准时表现的一致。
在测试之前,还有生产环境的问题:如何在不触碰提供商代码的情况下添加日志?如何在不包裹每次调用的情况下重试瞬时故障?如何在不分叉(Fork)代理逻辑的情况下拦截危险的工具调用?
BoxLang AI 3.0 通过中间件解决了所有这些问题。它内置了六个经过实战检验的中间件类,涵盖了最常见的横切关注点(Cross-cutting concerns)。如果它们都不符合你的用例,编写自己的中间件也非常简单,只需扩展一个类或定义一个闭包结构体即可。
🏗️ 中间件架构
中间件位于代理的 run() 调用与实际的 LLM 调用和工具调用之间。AiModel 和 AiAgent 都支持它。当代理运行时,其中间件会预置在模型的中间件之前——代理的钩子先触发,然后是模型的钩子。
有两种钩子风格:
顺序钩子(Sequential hooks) —— 按注册顺序触发(after* 钩子则按相反顺序)。返回 AiMiddlewareResult 以控制流程。
包装钩子(Wrap hooks) —— 嵌套闭包。调用 handler() 继续执行,并拦截结果。
🎯 AiMiddlewareResult --- 类型化流程控制
每个顺序钩子都必须返回一个 AiMiddlewareResult。静态工厂方法使其表达力更强:
(代码示例 1)
终止结果(cancel、reject、suspend)会立即停止链条。非终止结果则继续执行下一个中间件。
(代码示例 2)
📝 LoggingMiddleware --- 即时可观测性
加入此中间件,每个 LLM 调用、工具调用、代理运行的开始/结束以及错误都会记录到 BoxLang 的 ai 日志文件中,并可选择输出到控制台——无需对你的代理进行任何代码修改:
(代码示例 3)
其实现是顺序钩子如何组合的一个清晰示例:
(代码示例 4)
选项:
🔁 RetryMiddleware --- 无样板代码的韧性
LLM 提供商有速率限制,网络有瞬时故障。RetryMiddleware 以指数退避方式包裹 LLM 调用和工具调用——透明且无需在你的工具或代理中编写任何代码:
(代码示例 5)
它使用 wrapLLMCall 和 wrapToolCall 钩子——外部包装捕获异常、休眠并重试直到 maxRetries 次数。不可重试的异常(如 InvalidInput 或 MaxInteractionsExceeded)会立即抛出:
🛡️ GuardrailMiddleware --- 深度防御
完全阻止危险工具,或在工具调用到达前,根据正则表达式模式拒绝参数匹配的调用:
(代码示例 6)
该钩子在 beforeToolCall 中触发——它首先根据 blockedTools 检查工具名称,然后根据配置的正则表达式验证每个参数:
(代码示例 7)
🙋 HumanInTheLoopMiddleware --- 保持人类控制
此中间件拦截特定的工具调用,并要求人类在执行前进行批准、拒绝或编辑。它有两种模式,适用于完全不同的用例。
CLI 模式 —— 阻塞标准输入(stdin)。非常适合本地脚本、自动化工具和开发工作流:
(代码示例 8)
当 LLM 调用 transferFunds 时,终端显示:
Web 模式 —— 挂起运行并返回 AiMiddlewareResult.suspend()。调用代码保存状态并异步呈现批准请求——通过电子邮件、Slack、Web UI 或任何适合你工作流的方式:
(代码示例 9)
HumanInTheLoopMiddleware 中的恢复路径会读取由 AiAgent.resume() 注入的 _resumeContext,执行决定,并选择继续、拒绝或修补工具参数——然后清除上下文,以便同一次运行中的后续工具调用再次经过正常的 HITL 流程。
🎙️ FlightRecorderMiddleware --- AI 测试难题的终结
这是改变你对 AI 代理测试看法的一个中间件。
问题在于:代理行为是非确定性的。LLM 每次运行的表达可能不同,工具调用顺序可能变化。直接对代理输出进行断言非常脆弱。而且在实时提供商上运行测试既缓慢又昂贵,且在 CI 中需要网络访问。
FlightRecorderMiddleware 通过记录/重放方法解决了这个问题。记录一次真实的运行——将每个 LLM 往返和工具调用捕获到 JSON 固定文件(fixture)中。然后在 CI 中重放该固定文件,无需任何实时调用。
三种模式:
(代码示例 10, 11, 12)
固定文件格式 —— 人类可读的 JSON,你可以检查、编辑并提交到版本控制:
(代码示例 13)
值得注意的一个实现细节:记录器会在每次交互后刷新到磁盘,而不仅仅是在最后。这意味着如果你的代理在运行中途崩溃,部分记录也会被保留并可供检查:
(代码示例 14)
严格 vs 宽松重放:
(代码示例 15)
🔢 MaxToolCallsMiddleware --- 预防失控代理
在生产环境中简单但必不可少——限制每个代理运行的工具调用总数:
(代码示例 15)
计数器在每次新的 run() 调用开始时重置。如果运行中途达到上限,链条将被取消并显示清晰的错误消息。这对于防止复杂的多步推理任务中的无限工具调用循环至关重要。
✍️ 编写你自己的中间件
根据你想要的结构化程度,有两种方法。
闭包结构体(Struct of closures) —— 轻量级,无需类:
(代码示例 16)
结构体会自动被包裹在 StructMiddlewareAdapter 中——你只需要定义你需要的钩子。
基于类(Class-based) —— 可重用、可配置、可独立测试:
(代码示例 17)
🚀 组合中间件
中间件堆栈可以干净地组合——只需传递一个数组:
(代码示例 18)
或者以流式方式,一个接一个地添加:
(代码示例 19)
在生产中,日志记录 + 重试 + 防护栏是基础堆栈。对于复杂的推理代理,添加 MaxToolCallsMiddleware;对于任何涉及金钱、数据或外部系统的代理,添加 HumanInTheLoopMiddleware;在 QA 期间使用记录模式的 FlightRecorderMiddleware,并在 CI 中使用重放模式。
下一步
在第 5 部分中,我们将深入探讨 BoxLang AI 的提供商架构——能力系统如何工作、BaseService 和 OpenAIService 的结构、如何添加自定义提供商,以及对完整的 17 个提供商生态系统的巡礼。