跳到主要内容

🪄 过滤函数:改写输入与输出

⚠️ 关键安全警告

Filter Functions 会在你的服务器上执行任意 Python 代码。 Function 创建权限仅限管理员。只应从可信来源安装,导入前必须审阅代码。恶意 Function 可能访问文件系统、外传数据,甚至接管整个系统。完整背景见 Plugin Security Warning

Filter 是 OPL 数据空间中最灵活的插件机制之一。它可以在消息进入模型之前修改输入,也可以在模型输出返回后做后处理,甚至在流式输出过程中逐段拦截和改写内容。

Filter 是什么

可以把 OPL 数据空间想象成一条水流:

  • 用户输入和模型输出是水流
  • Filter 是中途的处理工序

它通常承担三类工作:

  1. inlet():模型收到请求前改写输入
  2. stream():模型流式输出时逐段处理事件
  3. outlet():模型生成完成后再加工结果

常见用途包括:

  • 增加上下文
  • 文本清洗或脱敏
  • 格式标准化
  • 实时翻译
  • 内容审计与合规日志
  • 输出增强或摘要整理

基本结构

from pydantic import BaseModel
from typing import Optional

class Filter:
    class Valves(BaseModel):
        pass

    def __init__(self):
        self.valves = self.Valves()

    async def inlet(self, body: dict) -> dict:
        print(f"inlet called: {body}")
        return body

    async def stream(self, event: dict) -> dict:
        print(f"stream event: {event}")
        return event

    async def outlet(self, body: dict) -> dict:
        print(f"outlet called: {body}")
        return body

可切换 Filter:self.toggle = True

如果一个 Filter 是激活状态并且在作用域内,它默认会对每个请求都生效。很多场景需要这样做,例如日志、审计、强制护栏。

但有时你希望用户按会话自己决定是否启用某个 Filter,这时就把它设成可切换:

from pydantic import BaseModel, Field
from typing import Optional

class Filter:
    class Valves(BaseModel):
        pass

    def __init__(self):
        self.valves = self.Valves()
        self.toggle = True
        self.icon = "https://example.com/icons/lightbulb.svg"

    async def inlet(
        self, body: dict, __event_emitter__, __user__: Optional[dict] = None
    ) -> dict:
        await __event_emitter__(
            {
                "type": "status",
                "data": {"description": "Running!", "done": True, "hidden": False},
            }
        )
        return body

self.toggle = True 真正表示什么

  • 它决定这个 Filter 是否在聊天 UI 中可见、可由用户切换
  • 它不是一个“运行时会被 UI 改写”的布尔状态
  • 如果用户没有选中这个 Filter,那么 inlet() / stream() / outlet() 根本不会执行

换句话说,不要在运行时依赖 self.toggle 来判断“当前是否启用”。它只是一个声明:这个 Filter 是否允许用户控制。

从旧版 Filter 升级

旧版里有些 Filter 会在 inlet() 中写 if self.toggle: ... else: ...,试图读取 UI 的实时开关状态。这个思路在 0.9.0+ 已经不可靠,因为用户关闭时 inlet() 根本不会被调用。正确做法是:需要用户可调参数时,用 UserValves,不要在运行中分支判断 self.toggle

用户如何操作可切换 Filter

当可切换 Filter 在当前模型作用域内时,用户会在两个地方看到它:

  • 聊天输入栏中的 inline chip
  • Integrations 菜单

chip 在,表示当前聊天会启用它;chip 不在但菜单里可见,表示它目前被关闭了。

Filter 管理与配置

全局 Filter 与模型级 Filter

OPL 数据空间支持全局 Filter 和模型级 Filter 两层管理。

状态is_activeis_global效果
全局启用TrueTrue自动应用到所有模型
全局但停用FalseTrue不会生效
模型级启用TrueFalse仅对指定模型生效
完全停用FalseFalse不会生效

当 Filter 同时满足 is_global=Trueis_active=True 时,它会被强制挂到所有模型上,管理员也无法在模型设置里取消勾选。

如何把 Filter 设为全局

位置:管理员面板 → Functions

操作:

  1. 找到目标 Filter
  2. 打开三点菜单
  3. 点击 🌐 图标切换 is_global
  4. 确保同时为 Active 状态

双层模型配置

OPL 数据空间在模型层面对 Filter 有两层配置:

Tier 1:FiltersSelector

决定某个模型“有哪些 Filter 可用”。

  • 显示所有 global 和 model-specific Filter
  • global Filter 会显示为已勾选且不可取消
  • 结果存到 model.meta.filterIds

Tier 2:DefaultFiltersSelector

决定“哪些可切换 Filter 在新聊天里默认开启”。

  • 只显示 toggle=True 的 Filter
  • 只针对已选中或全局启用的 Filter
  • 结果存到 model.meta.defaultFilterIds

这意味着:

  • 一个 Filter 可以对某模型“可用但默认关闭”
  • 用户仍然可以在聊天里手动打开它

开发建议

  • 强制安全或合规逻辑,用全局 Filter
  • 用户偏好型能力,用 toggle=True + UserValves
  • 会长期存在的配置放进 Valves
  • 需要用户按会话自定义的配置放进 UserValves

如果你的 Filter 需要更复杂的 UI 反馈或用户交互,可以结合事件系统与 status/confirmation 等机制,但核心逻辑仍应保持在 Filter 的三个阶段函数中。

Enabling outlet for pure API callers

如果你的调用链不是通过标准聊天 UI 进入,而是走纯 API 场景,需要额外确认该路径是否会触发完整的 Filter 生命周期。对于依赖 outlet() 的 Filter,集成方应验证请求最终是否经过了会执行后处理的聊天/推理管线,否则就不要把关键逻辑只放在 outlet()