Ohhnews

分类导航

$ cd ..
foojay原文

BoxLang AI 深度解析:中间件——AI 框架中不可或缺的层级

#boxlang 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 调用和工具调用之间。AiModelAiAgent 都支持它。当代理运行时,其中间件会预置在模型的中间件之前——代理的钩子先触发,然后是模型的钩子。

有两种钩子风格:

顺序钩子(Sequential hooks) —— 按注册顺序触发(after* 钩子则按相反顺序)。返回 AiMiddlewareResult 以控制流程。

钩子触发时间方向
beforeAgentRun( context )代理启动前正向
afterAgentRun( context )代理完成后反向
beforeLLMCall( context ) 每个 LLM 调用前正向
afterLLMCall( context ) 每个 LLM 调用后反向
beforeToolCall( context )每个工具调用前正向
afterToolCall( context )每个工具返回后反向
onError( context )任何钩子抛出异常时---

包装钩子(Wrap hooks) —— 嵌套闭包。调用 handler() 继续执行,并拦截结果。

钩子用途
wrapLLMCall( context, handler )包裹每个 LLM 提供商调用
wrapToolCall( context, handler )包裹每个工具调用

🎯 AiMiddlewareResult --- 类型化流程控制

每个顺序钩子都必须返回一个 AiMiddlewareResult。静态工厂方法使其表达力更强:

(代码示例 1)

终止结果(cancelrejectsuspend)会立即停止链条。非终止结果则继续执行下一个中间件。

(代码示例 2)

📝 LoggingMiddleware --- 即时可观测性

加入此中间件,每个 LLM 调用、工具调用、代理运行的开始/结束以及错误都会记录到 BoxLang 的 ai 日志文件中,并可选择输出到控制台——无需对你的代理进行任何代码修改:

(代码示例 3)

其实现是顺序钩子如何组合的一个清晰示例:

(代码示例 4)

选项:

选项默认值描述
logToFile true写入 BoxLang ai 日志
logToConsolefalse同时输出到 stdout
logLevel"info"info, debug, warning, error
prefix"[AI Middleware]"每个消息的前缀

🔁 RetryMiddleware --- 无样板代码的韧性

LLM 提供商有速率限制,网络有瞬时故障。RetryMiddleware 以指数退避方式包裹 LLM 调用和工具调用——透明且无需在你的工具或代理中编写任何代码:

(代码示例 5)

它使用 wrapLLMCallwrapToolCall 钩子——外部包装捕获异常、休眠并重试直到 maxRetries 次数。不可重试的异常(如 InvalidInputMaxInteractionsExceeded)会立即抛出:

选项默认值描述
maxRetries3首次失败后的尝试次数
initialDelay1000首次重试延迟(毫秒)
backoffMultiplier2 每次失败后的乘数
maxDelay30000延迟的硬上限
nonRetryableTypes"InvalidInput,MaxInteractionsExceeded"跳过的异常类型

🛡️ GuardrailMiddleware --- 深度防御

完全阻止危险工具,或在工具调用到达前,根据正则表达式模式拒绝参数匹配的调用:

(代码示例 6)

该钩子在 beforeToolCall 中触发——它首先根据 blockedTools 检查工具名称,然后根据配置的正则表达式验证每个参数:

(代码示例 7)

选项默认值描述
blockedTools[]始终被拒绝的工具名称(不区分大小写)
argPatterns{}{ toolName: [{ paramName: "regex" }] }

🙋 HumanInTheLoopMiddleware --- 保持人类控制

此中间件拦截特定的工具调用,并要求人类在执行前进行批准、拒绝或编辑。它有两种模式,适用于完全不同的用例。

CLI 模式 —— 阻塞标准输入(stdin)。非常适合本地脚本、自动化工具和开发工作流:

(代码示例 8)

当 LLM 调用 transferFunds 时,终端显示:

╔══════════════════════════════════════════════════╗
║         HUMAN APPROVAL REQUIRED                  ║
╚══════════════════════════════════════════════════╝
 Tool: transferFunds
 Args: {"amount": 5000, "account": "12345"}

 [A]pprove  [R]eject  [Q]uit
 Decision:

Web 模式 —— 挂起运行并返回 AiMiddlewareResult.suspend()。调用代码保存状态并异步呈现批准请求——通过电子邮件、Slack、Web UI 或任何适合你工作流的方式:

(代码示例 9)

HumanInTheLoopMiddleware 中的恢复路径会读取由 AiAgent.resume() 注入的 _resumeContext,执行决定,并选择继续、拒绝或修补工具参数——然后清除上下文,以便同一次运行中的后续工具调用再次经过正常的 HITL 流程。

选项默认值描述
toolsRequiringApproval[]需要签名的工具
mode"cli""cli""web"
showArgumentstrue 在 CLI 提示符中显示参数
approvalCallback---自定义批准函数

🎙️ FlightRecorderMiddleware --- AI 测试难题的终结

这是改变你对 AI 代理测试看法的一个中间件。

问题在于:代理行为是非确定性的。LLM 每次运行的表达可能不同,工具调用顺序可能变化。直接对代理输出进行断言非常脆弱。而且在实时提供商上运行测试既缓慢又昂贵,且在 CI 中需要网络访问。

FlightRecorderMiddleware 通过记录/重放方法解决了这个问题。记录一次真实的运行——将每个 LLM 往返和工具调用捕获到 JSON 固定文件(fixture)中。然后在 CI 中重放该固定文件,无需任何实时调用。

三种模式:

(代码示例 10, 11, 12)

固定文件格式 —— 人类可读的 JSON,你可以检查、编辑并提交到版本控制:

(代码示例 13)

值得注意的一个实现细节:记录器会在每次交互后刷新到磁盘,而不仅仅是在最后。这意味着如果你的代理在运行中途崩溃,部分记录也会被保留并可供检查:

(代码示例 14)

严格 vs 宽松重放:

(代码示例 15)

选项默认值描述
mode"passthrough""passthrough", "record", 或 "replay"
fixturePath""固定文件路径
fixtureDir".ai/flight-recorder"自动生成的固定文件目录
recordToolstrue是否捕获工具交互
stricttrue重放时类型不匹配则抛出异常

🔢 MaxToolCallsMiddleware --- 预防失控代理

在生产环境中简单但必不可少——限制每个代理运行的工具调用总数:

(代码示例 15)

计数器在每次新的 run() 调用开始时重置。如果运行中途达到上限,链条将被取消并显示清晰的错误消息。这对于防止复杂的多步推理任务中的无限工具调用循环至关重要。

✍️ 编写你自己的中间件

根据你想要的结构化程度,有两种方法。

闭包结构体(Struct of closures) —— 轻量级,无需类:

(代码示例 16)

结构体会自动被包裹在 StructMiddlewareAdapter 中——你只需要定义你需要的钩子。

基于类(Class-based) —— 可重用、可配置、可独立测试:

(代码示例 17)

🚀 组合中间件

中间件堆栈可以干净地组合——只需传递一个数组:

(代码示例 18)

或者以流式方式,一个接一个地添加:

(代码示例 19)

在生产中,日志记录 + 重试 + 防护栏是基础堆栈。对于复杂的推理代理,添加 MaxToolCallsMiddleware;对于任何涉及金钱、数据或外部系统的代理,添加 HumanInTheLoopMiddleware;在 QA 期间使用记录模式的 FlightRecorderMiddleware,并在 CI 中使用重放模式。

下一步

第 5 部分中,我们将深入探讨 BoxLang AI 的提供商架构——能力系统如何工作、BaseServiceOpenAIService 的结构、如何添加自定义提供商,以及对完整的 17 个提供商生态系统的巡礼。

📖 完整文档 📦 即刻安装:install-bx-module bx-ai 🫶 专业支持