中间件 · 钩子 · 沙箱 · 插件¶
Rein 的扩展性靠一套机制:洋葱中间件。钩子是它的糖,事件是它的只读旁路。不搞三套并列的重叠 API。
洋葱中间件¶
在「每一步」前后插入你的逻辑(计时、日志、改输入、拦截):
@agent.middleware
async def timing(ctx, call_next):
# before:可读改 ctx.session.messages、可短路(不调 call_next)
import time; t0 = time.monotonic()
ctx = await call_next(ctx) # 调下一层,最内层是真正的 step
# after:可读 ctx.steps / ctx.interrupt
print(f"{ctx.stage.value} 耗时 {(time.monotonic()-t0)*1000:.1f}ms")
return ctx
- 短路:不调
call_next直接 return → 跳过这一步。 - 统一 try/except:把
call_next包在 try 里即可统一兜错。
为什么能兼容「中断/恢复」
中间件环绕的是单步 step(不是整个 loop),每个阶段都重新过一遍栈。所以中间件栈无状态、每步重建 —— 要持有状态就放进 Session。于是中断→存盘→恢复时,栈能按需重建,不会丢东西。
钩子(中间件的糖)¶
@agent.before_tool # 工具执行前;返回 False 可短路
async def guard(ctx):
print("即将执行:", [tc.name for tc in ctx.session.pending_tool_calls])
@agent.after_model # 问模型后
async def log(ctx):
print("模型这步:", [s.kind for s in ctx.steps])
四种:@before_model / @after_model / @before_tool / @after_tool。
事件(只读观测)¶
agent.on("step", lambda ctx: print("走过", ctx.stage.value))
agent.on("tool", lambda ctx: metrics.incr("tool_calls"))
事件只用于观测(打日志 / 上报),不改流程 —— 要改流程用中间件/钩子。
权限即钩子¶
Rein 自己的权限(ask 审批)就是一个内置中间件实现的 —— loop 里没有任何权限特例。机制统一,你也能用同样的方式做自定义权限策略。
DockerRuntime:容器沙箱¶
把工具放进容器执行,隔离风险(为「跑 LLM 生成的代码」准备)。接口和 LocalRuntime 一致,改配置即切换:
from rein import Agent, DockerRuntime
from rein.loop import arun
# 进阶:把 runtime 传给 loop(Agent 默认用 LocalRuntime)
务实范围
工具是宿主进程的 Python 函数,没法把函数对象塞进容器。DockerRuntime 用 inspect.getsource 取函数源码在容器里执行 —— 适合纯函数 + 标准库的工具;依赖闭包/第三方库的需要自定义镜像。docker 走 extras(pip install "rein-agent[docker]"),默认网络隔离 + 内存上限。
插件发现¶
第三方包在自己的 pyproject 里声明 entry points,运行时自动发现:
用标准库 importlib.metadata,零额外依赖;坏插件不拖垮整体。