插件(Plugins)
概述
插件是一种 Runner 作用域(Runner-scoped)的扩展机制,可以在以下生命周期点插入 自定义逻辑:
- Agent 执行
- 模型调用(大语言模型(Large Language Model, LLM)请求)
- 工具(Tool)调用
- 事件(Event)发出
插件主要解决的是 横切关注点:也就是“你希望所有 Agent 都统一具备的能力”,而 不是某个 Agent 的业务逻辑。
常见场景:
- 统一日志与调试
- 安全与策略(policy)拦截
- 请求改写(例如统一追加 system instruction)
- 审计、事件打标
如果你的需求只针对某一个 Agent,通常用回调(Callbacks)会更合适。
名词解释(Glossary)
为了避免“插件 / 回调 / hook(钩子)”混在一起,先把几个词说清楚:
- 生命周期(lifecycle):Runner 处理一次输入的完整过程(创建 Invocation(一次运行 的上下文)→ 调用 Agent → 触发模型调用/工具调用 → 发出事件 → 结束)。
- Hook 点(Hook Point):生命周期里的一个“时机/插槽”。框架在这个时机会去调用
你注册的函数(回调)。在本项目里,它对应
BeforeModel、AfterTool、OnEvent这类位置。 - 回调(callback):你提供给框架的函数。框架在某个 Hook 点被触发时,会调用这 个函数。
- hook(钩子):一个通用说法,通常指“Hook 点”或“挂在 Hook 点上的回调函数”。 在这个仓库里,hook 是通过回调实现的,所以文档里有时会混用“hook/回调”,本质是 同一件事:框架在某个时机调用你的函数。
- 插件(plugin):一个实现了
plugin.Plugin的组件。它在Register(reg *plugin.Registry)里把一组回调注册到多个 Hook 点,然后通过runner.WithPlugins(...)在 Runner 上一次性启用。
一句话:
- Hook 点 = 框架预留的“时机/插槽”
- 回调 = 你写的函数,挂在 Hook 点上
- 插件 = 把一组回调打包,并全局生效
文档示例里常见的变量名:
runnerInstance:Runner 实例(*runner.Runner)reg:注册表(registry),用于注册回调(*plugin.Registry)ctx:上下文(context.Context)
插件和回调有什么区别?
一句话:插件跟着 Runner 走;回调跟着 Agent 走。
如果你只用回调(callback)来实现“全局生效”的效果,你需要把同一套回调逻辑手动加到 这个 Runner 会用到的每一个 Agent 上;插件(plugin)就是把这件事“收拢到一个地方”, 让你只在 Runner 上注册一次,然后自动应用到该 Runner 管理的所有 Agent、工具(Tool) 和模型调用(大语言模型(Large Language Model, LLM)请求)。
回调(callback)是“一个函数”:框架在特定时机(before/after)去调用它。你需要把 它绑定到你想生效的地方(很多时候是某个 Agent 上)。
插件(plugin)是“一个组件”:它把“一组回调 + 配置 + 可选的生命周期管理”打包在 一起,然后 只在 Runner 上注册一次,就能自动对该 Runner 管理的所有 Invocation (一次运行的上下文)生效。
换句话说:
- Hook 点(Hook Point):生命周期中的“时机/插槽”。
- 回调(callback):挂在 Hook 点上的函数(也常被称为 hook)。
- 插件(plugin):把多组回调打包,并在 Runner 上一次注册让它全局生效。
插件和回调的关系(最关键)
插件不是“另一套新的回调系统”。插件的本质就是:在 Runner 级别集中注册一批回调。
具体来说:
- 插件通过
Register(reg *plugin.Registry)把回调注册到各个 Hook 点; reg.BeforeModel(...)/reg.AfterTool(...)这类方法注册的就是“回调函数”;- 运行时框架仍然在对应的生命周期点执行回调:
- 先执行“插件注册的全局回调”
- 再执行“Agent 自己配置的回调”(如果有)
你可以把插件理解为:一组回调(Callbacks)的打包与全局应用。
如果你想了解“回调如何绑定到某一个 Agent(局部生效)”,可以参考 callbacks.md
页面。
一张图看懂:从注册到执行
插件生效分两步:注册 与 执行。
sequenceDiagram
autonumber
participant User as 你的代码
participant Runner as Runner
participant Plugin as 插件(plugin.Plugin)
participant Reg as 注册表(plugin.Registry)
participant Agent as Agent
participant ModelTool as 模型/工具
Note over User,Reg: 1) 注册阶段:只发生一次(创建 Runner 时)
User->>Runner: NewRunner(..., WithPlugins(Plugin))
Runner->>Plugin: Register(Reg)
Plugin->>Reg: reg.BeforeModel(回调)
Plugin->>Reg: reg.OnEvent(回调)
Note over User,ModelTool: 2) 执行阶段:每次 Run 都会发生
User->>Runner: Run(ctx, input)
Runner->>Agent: 调用 Agent
Note over Runner,Agent: 运行到某个 Hook 点(例如 BeforeModel)
Runner->>Runner: 先执行插件回调(全局)
Runner->>Agent: 再执行 Agent 回调(局部,可选)
Runner->>ModelTool: 调用模型/工具(除非被短路)
Runner-->>User: 返回结果
1) 注册:把回调挂到 Hook 点
你在创建 Runner 时启用插件:
其中 MyPlugin 是你自定义的插件类型(实现 plugin.Plugin 接口)。
然后框架会调用插件的 Register(reg *plugin.Registry)。在这个函数里,你通过
reg.BeforeModel(...) / reg.OnEvent(...) 等方法,把“回调函数”注册到对应的
Hook 点。
2) 执行:运行到 Hook 点时,框架调用你的回调
当运行过程中走到某个 Hook 点(例如 BeforeModel)时,框架会按顺序执行:
1) 插件注册的全局回调 2) Agent 自己配置的回调(如果有)
这就是为什么插件能“一次注册,全局生效”:它只是把同样的回调逻辑提前挂到了 Runner 统一管理的生命周期点上。
什么时候用插件?
当你希望“对一个 Runner 管理的所有执行都统一生效”时,用插件更合适:
- 想对所有 Agent 做统一策略(例如拦截某些输入)。
- 想统一做日志、指标(metrics)、链路追踪(tracing)。
- 想对每一次模型请求做统一改写(例如加 system instruction)。
- 想给所有事件统一打标,便于审计与排查。
什么时候用回调?
当你的需求只影响某个特定 Agent 时,用回调更合适:
- 只有一个 Agent 需要特殊的请求改写。
- 只有一个 Agent 的工具需要自定义参数处理。
- 你在快速试验,不希望全局影响其他 Agent。
你也可以混用:插件负责全局默认行为,回调负责单个 Agent 的定制。
快速开始
创建 Runner 时注册插件(只需要注册一次):
单次 Run 插件
如果插件只应该影响当前请求,而不是挂到整个 Runner 上,可以在
Runner.Run(...) 中通过 plugin.WithPlugins(...) 临时注入:
plugin.WithPlugins(...) 是 RunOption,只对本次运行生效。它复用同一套
plugin.Plugin 接口和 Hook 点,适合请求级策略、测试替身和临时审计打标。同一次
plugin.WithPlugins(...) 里的插件名仍然需要唯一;配置无效时,框架会记录错误并跳过
这组 Run 级插件。Runner 不会因为单次 Run 结束而调用这些插件的 Close(),如果插件
持有资源,需要调用方自行管理。
工具身份注入
插件可以通过 BeforeTool 和 AfterTool 对所有工具调用增加前置或后置处理。
对于已经在 Web 层拿到当前用户身份的业务场景,plugin/identity 提供了一套可复用的
身份透传插件:
插件会在 Agent 运行前解析身份并写入 Invocation 状态;每次工具调用前,它会把身份放进
工具调用的 context。MCP HTTP 传输可以通过 WithMCPOptions 传入一个
mcp.WithHTTPBeforeRequest hook,在 hook 中从 context 读取
Identity.Headers 并写到请求头,这样每次外发的 HTTP 请求都会带上当前用户
的 header。命令执行类工具则应当在真正执行时从 context 读取
Identity.EnvVars,这样密钥不会进入模型可见的工具参数。对于 workspace_exec
和 skill_run,可以用
codeexecutor.NewEnvInjectingCodeExecutor(exec, identity.EnvVarsFromContext)
包装执行器。
如果你是通过 llmagent.WithToolSets(...) 挂载这个 MCP ToolSet,并且希望
initialize / tools/list 也拿到请求级身份 header,记得同时开启
llmagent.WithRefreshToolSetsOnRun(true)。这会让每次 Run 额外执行一轮
initialize / tools/list,对工具较多或 tools/list 较慢的 MCP Server
会有额外开销,请按需开启。如果你更希望用一个固定的发现上下文,则可以手动调用
toolSet.Tools(ctx),再通过 llmagent.WithTools(...) 注入。
插件是如何执行的?
作用域与传播
- 插件在 Runner 创建时初始化一次,并存放到 Invocation 的
Invocation.Plugins中。 - 当 Invocation 被 Clone(例如子 Agent、AgentTool、transfer 等)时,同一个插件管理器 会被带过去,所以插件在嵌套执行中仍然一致生效。
在回调里拿到 Invocation
在 BeforeModel / AfterModel / BeforeTool / AfterTool 这类回调里,你通常只拿到
context.Context。如果你需要当前 Invocation,可以从 context 里取出来。
下面示例用 fmt.Printf 打印(示例省略了 import "fmt"):
在工具回调里,框架还会把工具调用标识符(identifier, ID)注入 context:
执行顺序与短路(short-circuit)
插件会按作用域和注册顺序执行:
- 先执行
runner.WithPlugins(...)注册的 Runner 级插件。 - 再执行
plugin.WithPlugins(...)注入的单次 Run 插件;多个 RunOption 按传入Runner.Run(...)的顺序执行。 - 最后执行 Agent 自己配置的回调(如果有)。
同一个 WithPlugins(...) 调用里的多个插件仍按传入顺序执行。
某些 Before* 回调支持“短路”默认行为:
- BeforeAgent:可以返回自定义响应,直接跳过 Agent 执行。
- BeforeModel:可以返回自定义响应,直接跳过模型接口调用 (应用程序编程接口(Application Programming Interface, API))。
- BeforeTool:可以返回自定义结果,直接跳过工具执行。
某些 After* 回调支持“覆盖”输出:
- AfterAgent:可以返回自定义响应,作为一条额外的“终态”响应事件追加到 Agent 事件流末尾(不替换之前的事件)。
- AfterModel:可以返回自定义响应,替换模型响应。
- AfterTool:可以返回自定义结果,替换工具结果。
多 Agent 场景下的注意事项(ChainAgent、ParallelAgent、CycleAgent、Graph Agent 节点)
BeforeAgent/AfterAgent会为每一个子 Agent invocation 各触发一次, 而不是每个 Runner 执行只触发一次。如果你的 Hook 假设“一次 turn 只调用一次”, 请通过args.Invocation.Agent(或AgentName)来区分当前是哪一层。
BeforeAgent.CustomResponse会完全短路子 Agent:Run不会被调用, 子 Agent 自己发出的终态事件(例如 Graph Agent 节点依赖的GraphCompletionEvent,它负责填充SubgraphResult.FinalState)也不会 被发出。自定义的outputMapper需要在FinalState为nil时也能正常处理。
AfterAgent.CustomResponse会追加一条终态响应事件。在 Graph Agent 节点 里,这条追加的响应会成为下游节点看到的StateKeyLastResponse。如果你就是 想让 Runner 作用域插件覆盖子 Agent 输出,请有意识地使用它。
错误处理
- agent/model/tool 回调返回 error 会让本次运行失败(错误返回给调用方)。
OnEvent返回 error 时,Runner 会记录日志并继续使用原始事件。
并发(很重要)
工具可能并发执行,某些 Agent 也可能并发运行。如果你的插件会保存共享状态,请确保
并发安全(例如使用 sync.Mutex 或原子(atomic)类型)。
Close(资源释放)
如果通过 runner.WithPlugins(...) 注册的插件实现了 plugin.Closer,当你调用
Runner.Close() 时,Runner 会调用插件的 Close() 来释放资源。关闭顺序是
按注册顺序的反向(后注册的先关闭)。
通过 plugin.WithPlugins(...) 注入的单次 Run 插件不会在本次运行结束时被 Runner
关闭。它们通常是调用方临时借给本次运行的对象;如果插件持有需要释放的资源,请由
调用方在合适的时机关闭。
Hook 点(Hook Points)
Agent Hook 点
BeforeAgent:Agent 开始前AfterAgent:Agent 事件流结束后
Model Hook 点
BeforeModel:模型请求发出前AfterModel:模型响应产生后
Tool Hook 点
BeforeTool:工具调用前,可以修改工具参数(JSON(JavaScript Object Notation) 字节)AfterTool:工具调用后,可以替换结果
Event Hook 点
OnEvent:Runner 发出每一个事件时都会调用(包括 runner completion 事件)。你可以 原地修改事件,或者返回一个新的事件作为替代。
Graph 节点 Hook(StateGraph / GraphAgent)
Runner 插件不会提供 BeforeNode / AfterNode 这类“节点级”的 Hook 点。
Graph 的节点执行发生在图引擎内部(graph.Executor),它有自己独立的一套回调机制:
graph.NodeCallbacks。
如果你希望对 Graph 节点 做“切面/横切”,常见有三种方式:
1) 使用 Graph 节点回调(推荐)
Graph 同时支持:
- 全局(图级)回调:构图时通过 (*graph.StateGraph).WithNodeCallbacks(...)
一次性注册,对该图内所有节点生效。
- 单节点回调:通过 graph.WithPreNodeCallback /
graph.WithPostNodeCallback / graph.WithNodeErrorCallback 只挂到某个节点上。
详细的手把手教程见 graph.md。
2) 用 OnEvent 观测 Graph 节点生命周期事件
Graph 会发出任务生命周期事件(event object):
- graph.node.start
- graph.node.complete
- graph.node.error
插件可以在 OnEvent 里对这些事件做打标/日志/审计等:
注意:如果你显式开启了 StreamMode 过滤,需要在 modes 里包含 tasks
(或 debug)才能把这些事件转发给调用方;如果你没有配置 StreamMode(默认),
Runner 不会做过滤,会把所有事件都转发。无论哪种情况,插件都能看到这些事件,
因为 OnEvent 在 StreamMode 过滤之前执行。
3) 从插件里注入 Graph 全局节点回调(进阶)
如果你希望“Runner 作用域”的逻辑也能影响 Graph 节点回调,可以在 BeforeAgent 里
往 Invocation.RunOptions.RuntimeState 写入 graph.StateKeyNodeCallbacks。
Graph 会把这个 key 当作“本次运行的全局节点回调”来使用。
可运行的端到端示例见 examples/graph/runner_plugin_node_callbacks。
常见用法(Recipes)
1) 拦截输入并短路模型调用(策略)
用 BeforeModel 在模型调用前直接返回自定义响应:
2) 给所有事件打标(审计/排查)
用 OnEvent 给事件追加 tag,便于 UI(User Interface)过滤或日志检索:
3) 改写工具参数(清洗/规范化)
用 BeforeTool 替换工具参数(JSON(JavaScript Object Notation)字节):
内置插件
Logging
plugin.NewLogging() 会记录 agent/model/tool 的开始与结束信息,适合用于调试与
性能分析。
DebugLog
debuglog.New(opts...) 通过框架 debug logger 输出单行 JSON,用于临时排查 model
request/response 和 tool arguments/result。
默认跳过 Runner event 和 model partial response;需要时打开
debuglog.WithEventEnabled(true)、debuglog.WithModelPartialResponseEnabled(true)。
使用示例如下:
也可以只在单次 Run 临时启用:
输出是一行 JSON debug message,例如:
GlobalInstruction
plugin.NewGlobalInstruction(text) 会在每一次模型请求前,统一追加一条 system
message。适合用来实现全局策略或统一行为(例如安全约束、风格要求)。
ToolCallID
plugin/toolcallid 下的 toolcallid.New() 用于在模型返回最终 ToolCall.ID 后统一改写为框架使用的 tool call ID。当 provider / model 不能稳定保证 ToolCall.ID 足够唯一时,可以启用这个插件。
使用示例如下:
该插件挂载在 AfterModel,会在最终可用的 tool call ID 上做统一改写。改写完成后,框架后续处理会继续使用该 ID。
如果其他插件也在 AfterModel 阶段依赖最终 ToolCall.ID,应把 toolcallid.New() 放在它们前面。
完整示例见 examples/toolcallid。
Guardrail(护栏)
plugin/guardrail 下的 guardrail.New(...) 是顶层插件入口,用来把一个或多个护栏能力接到 Runner 上。
当前内置的 capability 包括:
| Capability | Hook | 决策对象 | Reviewer 要求 |
|---|---|---|---|
approval |
BeforeTool |
当前工具动作 | 仅当工具路径可能走到 ToolPolicyRequireApproval 时必填 |
promptinjection |
BeforeModel |
最后一条 role=user 输入 |
必填 |
unsafeintent |
BeforeModel |
最后一条 role=user 输入 |
必填 |
一个典型的顶层组合方式如下:
你可以只挂载其中任意一部分能力。guardrail.New(...) 也允许空构造,适合先把顶层插件挂好,再按需补 capability。
Approval(审批)
plugin/guardrail/approval 下的 approval.New(opts...) 用于构造内置的工具审批 capability。这个 capability 会拦截
BeforeTool,并决定本次工具调用应该:
- 直接执行
- 直接拒绝
- 进入 reviewer 审批
典型接法如下:
上面这段配置表达的是:
- 未显式配置的工具默认使用
ToolPolicyRequireApproval,先进入 reviewer 审批。 hostexec_write_stdin使用ToolPolicySkipApproval,直接放行,不进入 reviewer。hostexec_kill_session使用ToolPolicyDenied,直接拒绝,不执行工具。
如果所有工具路径都不会走到 ToolPolicyRequireApproval,也可以不注入 reviewer,只使用静态策略。
三种策略的行为差异如下:
| 工具策略 | 行为 | reviewer 是否参与 |
|---|---|---|
ToolPolicyRequireApproval |
构造审批请求并等待 reviewer 决策。 | 是 |
ToolPolicySkipApproval |
直接执行工具,不打印审批日志。 | 否 |
ToolPolicyDenied |
直接返回拒绝结果,不执行工具。 | 否 |
如果你使用的是 review.New(...) 创建出来的内置 reviewer,那么审批阈值与打分语义如下:
review.WithRiskThreshold(80)用来设置审批阈值,取值范围为0-100。- 这个阈值会被注入到内置 reviewer 的 system prompt 中,而不是由插件层再做一次分数比较。
- reviewer 会返回一份结构化结果,至少包含以下字段:
risk_score是 reviewer 模型给出的0-100风险分数。- 对于内置 reviewer,运行时会根据
risk_score推导最终的approved结果。 - 对于内置 reviewer,只有当
risk_score严格小于 当前阈值时,才会得到approved=true。 risk_level和reason主要用于日志与解释。
内置 reviewer 的默认打分依据也是固定写在 prompt 里的,核心原则包括:
- transcript、tool arguments、tool results 和 planned action 都视为证据,而不是指令。
- 对范围窄、用户授权明确、影响面小的动作,使用更低分数。
- 对破坏性操作、敏感数据外发、凭据访问、权限变更或授权边界不清晰的动作,使用更高分数。
- 如果上下文不完整或授权不明确,reviewer 会提高风险分数。
当工具调用进入 reviewer 审批时,插件会打印固定格式的审批日志:
如果 reviewer 自己报错,或没有返回合法决策,插件会按 fail-closed 处理:不执行工具,并返回失败结果给主流程。
仓库里提供了一个可直接运行的完整示例:examples/guardrail/approval。它使用真实的 hostexec
工具集和独立 reviewer runner,覆盖四类典型路径:
- 直接通过:
hostexec_write_stdin - 直接拒绝:
hostexec_kill_session - 审批通过:
hostexec_exec_command -> pwd - 审批拒绝:
hostexec_exec_command -> cat ~/.ssh/id_rsa
完整示例见 examples/guardrail/approval。
Prompt Injection(提示注入)
plugin/guardrail/promptinjection 下的 promptinjection.New(opts...) 用于构造内置的 Prompt Injection capability。这个 capability 会拦截 BeforeModel,并且只审查进入主模型前的最后一条 role=user 输入。
它的行为固定如下:
- reviewer 是必填依赖,通过
promptinjection.WithReviewer(...)注入。 - 真正的决策对象只有最后一条用户输入。
- 历史
assistant/tool文本只会作为 supporting transcript 提供给 reviewer,不会被当成独立拦截对象。 - reviewer 明确判定阻断时,插件会返回固定 deny response。
- reviewer 报错或返回非法结果时,插件按 fail-closed 处理,返回通用阻断响应。
典型接法如下:
内置 reviewer 在阻断时目前会返回以下分类之一:
system_overridepolicy_bypassprompt_exfiltrationrole_hijacktool_misuse_induction
可直接运行的完整示例见 examples/guardrail/promptinjection。 示例里已经整理了三类真实场景:
- 正常请求通过
- 直接的 prompt injection 被阻断
- 引用/翻译注入文本但本身不是攻击的输入被放行
Unsafe Intent(危险意图)
plugin/guardrail/unsafeintent 下的 unsafeintent.New(opts...) 用于构造内置的 Unsafe Intent capability。这个 capability 会拦截 BeforeModel,并且只审查最后一条 role=user 输入是否表达了明显高风险或不允许的意图。
它的行为固定如下:
- reviewer 是必填依赖,通过
unsafeintent.WithReviewer(...)注入。 - 真正的决策对象只有最后一条用户输入。
- 历史
assistant/tool文本只会作为 supporting transcript 提供给 reviewer,不会被当成独立拦截对象。 - reviewer 明确判定阻断时,插件会返回固定 deny response。
- reviewer 报错或返回非法结果时,插件按 fail-closed 处理,返回通用阻断响应。
典型接法如下:
内置 reviewer 在阻断时目前会返回以下分类之一:
cyber_abusecredential_theftfraud_deceptionprivacy_abusephysical_harmself_harmsexual_abuseother_unsafe_intent
可直接运行的完整示例见 examples/guardrail/unsafeintent。 示例里已经整理了三类真实场景:
- 正常请求通过
- 明显 unsafe intent 被阻断
- 偏防御/分析型请求被放行
MessageMerger(消息合并)
plugin/messagemerger 下的 messagemerger.New(opts...) 会在每一次模型请求前,把连续的 system、user、assistant 消息合并成一条。这适用于某些第三方模型平台要求消息严格交替、不能出现连续同 role 消息的场景,例如调用方传入的历史里出现 user,user 或 assistant,assistant。
这个插件不会合并 tool 消息,以保留 tool_id、tool_name 等逐次调用语义。文本合并时插入的分隔符可通过 messagemerger.WithSeparator(...) 配置。
代码示例如下:
完整示例见 examples/plugin/messagemerger。
ErrorMessage(错误消息改写)
plugin/errormessage 下的 errormessage.New(opts...) 会在 Runner 把错误事件写入 session 之前,改写这条错误事件里对用户可见的 Choices[].Message.Content。
当一个 event 带有 Response.Error 但还没有 Choices[].Message.Content 时(例如 llmflow 把 agent.StopError 转成的 stop_agent_error 事件、或者任何直接 event.NewErrorEvent(...) 产出的事件),Runner 会用一句固定的英文兜底文案 "An error occurred during execution. Please contact the service provider." 补齐。这个插件在 OnEvent 中先一步把 content 填好,方便业务侧展示更友好、本地化或按租户定制的提示信息。结构化的 Response.Error 不会被修改,调试和下游消费方仍能看到原始原因。
插件只会改写尚未带有有效 content 的错误事件,所以失败前已经产出的流式助手消息不会被覆盖。
静态文案:
动态 resolver(例如对 stop_agent_error 使用更友好的话术):
resolver 返回 ok=false 或空字符串时,事件保持不变,Runner 内置的兜底文案仍然生效。
FinishReason:
插件默认会把合成 choice 的 FinishReason 设为 "error"。如果下游协议期望别的取值,可以通过 errormessage.WithFinishReason("stop") 等方式覆盖。如果原始 choice 已经带有 FinishReason,插件会原样保留,不会强制覆盖下游的预期。
适用范围:
插件通过 runner.WithPlugins(...) 注册,会在 Runner 处理每一条事件时触发,因此能覆盖 agent 从事件通道发出的错误事件(比如 llmflow 针对 agent.StopError 产出的 stop_agent_error 事件,以及任何 event.NewErrorEvent(...) 产出的事件)。但对于 agent.Run 同步返回 error(尚未建立事件通道)这一路径,Runner 会直接用内置兜底文案写入 session,此时本插件无法改写持久化内容。
完整示例见 examples/plugin/errormessage。
说明:目前仓库内置了 Logging、DebugLog、GlobalInstruction、ToolCallID、MessageMerger、ErrorMessage、Guardrail 七类插件。其中 Guardrail 插件当前提供的内置 capability 包括工具审批、Prompt Injection 和 Unsafe Intent。更多插件可通过自定义插件实现。
如何扩展:写一个自己的插件
1) 实现接口
自定义一个类型,实现:
Name() string:同一个 Runner 内必须唯一Register(reg *plugin.Registry):在这里注册回调(挂到 Hook 点)
2) 在 Register 里注册回调
可用的注册方法:
BeforeAgent,AfterAgentBeforeModel,AfterModelBeforeTool,AfterToolOnEvent
注册回调的方式就是:在 Register(reg *plugin.Registry) 中调用这些方法。例如:
注册完插件后,需要在创建 Runner 时启用它:
3)(可选)实现 plugin.Closer
如果插件需要释放资源(文件、后台 goroutine、缓冲区等),实现 Close(ctx) 让 Runner
在关闭时统一清理。
完整示例
可运行的完整示例(包含一个自定义策略插件)见:
examples/plugin