跳到主要内容

🪄 过滤功能:修改输入与输出

欢迎来到 Open WebUI 中关于过滤功能的全面指南!过滤器是一种灵活且强大的插件系统,用于在数据发送到大型语言模型(LLM)之前(输入)或从 LLM 返回之后(输出)进行修改。不管您是为了更好地设置上下文而转化输入,还是为了提高可读性整理输出,过滤功能都可以胜任。

本指南将详细介绍什么是过滤器、它们如何工作、它们的结构,以及构建您自己的强大且用户友好的过滤器所需的一切知识。让我们开始吧,别担心—我会用比喻、实例和提示来让一切变得清晰易懂!🌟


🌊 什么是 Open WebUI 中的过滤器?

将 Open WebUI 想象成一股流经管道的水流

  • 用户输入LLM 输出就是水。
  • 过滤器水处理阶段,在最终目的地之前对水进行清洁、修改和调整。

过滤器坐在数据流中间——类似于检查点——您可以决定需要调整什么。

以下是过滤器的功能概述:

  1. 修改用户输入(入口函数):在数据到达 AI 模型之前调整输入数据。在这里,您可以增强清晰度、添加上下文、清理文本或重新格式化消息以满足特定需求。
  2. 拦截模型输出(流函数):在模型生成回复的过程中捕获并调整输出。这在实时修改中非常有用,比如过滤敏感信息或为了提高可读性对输出进行格式化。
  3. 修改模型输出(出口函数):在 AI 的回复被处理之后,在展示给用户之前再进行调整。这有助于优化数据、记录日志或改善用户体验。

关键概念: 过滤器不是独立的模型,而是增强或转化数据在模型之间流动的工具。

过滤器就像 AI 工作流中的翻译员或编辑:您可以拦截并改变对话,而不打断流程。


🗺️ 过滤功能的结构:骨架

让我们从过滤功能的最简单表示开始。如果有些部分乍一看感觉技术性较强,请别担心——我们会逐步解析!

🦴 过滤器的基本骨架

from pydantic import BaseModel
from typing import Optional

class Filter:
# Valves: 过滤器的配置选项
class Valves(BaseModel):
pass

def __init__(self):
# 初始化阀门(过滤器的可选配置)
self.valves = self.Valves()

def inlet(self, body: dict) -> dict:
# 在此处操作用户输入。
print(f"入口函数被调用:{body}")
return body

def stream(self, event: dict) -> dict:
# 在此处修改模型输出的流数据块。
print(f"流事件:{event}")
return event

def outlet(self, body: dict) -> None:
# 在此处操作模型输出。
print(f"出口函数被调用:{body}")

🆕 🧲 切换过滤器示例:添加交互性和图标(Open WebUI 0.6.10 新功能)

过滤器不仅可以修改文本,它们还可以提供 UI 切换按钮和显示自定义图标。例如,您可能需要一个过滤器可以通过用户界面的按钮开启/关闭,并在 Open WebUI 的消息输入界面中显示特殊图标。

以下是如何创建这样一个切换过滤器:

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 # 重要: 这将在 Open WebUI 中创建一个切换 UI
# 提示: 使用 SVG 数据 URI!
self.icon = """data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBjbGFzcz0ic2l6ZS02Ij4KICA8cGF0aCBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik0xMiAxOHYtNS4yNW0wIDBhNi4wMSA2LjAxIDAgMCAwIDEuNS0uMTg5bS0xLjUuMTg5YTYuMDEgNi4wMSAwIDAgMS0xLjUtLjE4OW0zLjc1IDcuNDc4YTEyLjA2IDEyLjA2IDAgMCAxLTQuNSAwbTMuNzUgMi4zODNhMTQuNDA2IDE0LjQwNiAwIDAgMS0zIDBNMTQuMjUgMTh2LS4xOTJjMC0uOTgzLjY1OC0xLjgyMyAxLjUwOC0yLjMxNmE3LjUgNy41IDAgMSAwLTcuNTE3IDBjLjg1LjQ5MyAxLjUwOSAxLjMzMyAxLjUwOSAyLjMxNlYxOCIgLz4KPC9zdmc+Cg=="""
pass

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

🖼️ 什么事情正在发生?

  • toggle = True 在 Open WebUI 中创建一个切换开关 UI——用户可以实时手动启用或禁用过滤器。
  • icon(携带一个数据 URI)将作为一个小图标显示在过滤器名称的旁边。您可以使用任何经过数据 URI 编码的 SVG!
  • inlet 函数 使用 __event_emitter__ 特殊参数向 UI 广播反馈/状态,例如一个小的 toast/通知提示 "已切换!"

切换过滤器

您可以使用这些机制使您的过滤器在 Open WebUI 的插件生态系统中变得动态、互动、并具有视觉上的独特性。


🎯 核心组件说明

1️⃣ Valves 类(可选设置)

Valves 想象成过滤器的旋钮和滑块。如果您希望提供用户配置选项以调整过滤器的行为,则可以在这里定义。

class Valves(BaseModel):
OPTION_NAME: str = "默认值"

例如: 如果您正在创建一个将响应转换为大写的过滤器,您可能允许用户通过一个类似 TRANSFORM_UPPERCASE: bool = True/False 的阀门配置是否将每个输出全部大写。


2️⃣ inlet 函数(输入预处理)

inlet 函数就像 做饭前的准备步骤。想象您是个厨师:在将食材投入烹饪(在这种情况下是 LLM)之前,您可能会清洗蔬菜、切洋葱或给肉调味。没有该步骤,您的最终菜品可能缺乏风味、有未清洗的食材,或者结果不一致。

在 Open WebUI 的世界中,inlet 函数在将 用户输入 发送给模型之前执行此重要的准备工作。它确保输入尽可能的干净、具有上下文,并且对 AI 来说是具有帮助性的。

📥 输入:

  • body: 从 Open WebUI 输入到模型的原始输入。它通常是一个聊天补全请求的格式(通常是一个字典,包括诸如会话消息、模型设置和其他元数据等字段)。想象这些就是您的食材。

🚀 您的任务: 修改并返回 body。修改后的 body 是 LLM 处理的内容,因此这是您提供清晰度、结构和上下文给输入的机会。

🍳 为什么使用 inlet?
  1. 添加上下文: 自动将重要的信息附加到用户的输入,特别是在文本模糊或不完整的情况下。例如,您可以添加 "你是一位友好的助手" 或 "帮助用户排查软件错误"。

  2. 格式化数据: 如果输入需要特定格式,例如 JSON 或 Markdown,您可以在将其传递给模型之前对其进行转换。

  3. 清理输入: 删除不需要的字符,去掉可能有害或令人困惑的符号(如过多空格或表情符号),或替换敏感信息。

  4. 优化用户输入: 如果模型的输出在额外指导下有所改善,您可以使用 inlet 自动注入说明性指导!

💡 示例用例: 建立与餐前准备的类比
🥗 示例 1: 添加系统上下文

假设 LLM 是一位厨师,正在准备意大利菜肴,但用户并未提及 "这是为意大利烹饪准备的"。您可以确保消息清晰,在将数据发送给模型之前追加这一上下文。

def inlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
# 为意大利语上下文添加系统消息到对话中
context_message = {
"role": "system",
"content": "您正在帮助用户准备一顿意大利餐。"
}
# 将上下文插入到聊天记录的开头
body.setdefault("messages", []).insert(0, context_message)
return body

📖 会发生什么?

  • 用户的任何输入,例如 "有什么好的晚餐点子?",现在都有意大利主题因为我们设定了系统上下文!芝士蛋糕可能无法作为答案出现,但意大利面肯定会。
🔪 示例 2: 清理输入(移除异常字符)

假设用户输入看起来很凌乱或包含不需要的符号如 !!!,使得对话效率低下或模型解析变得困难。您可以在保留核心内容的同时进行清理。

def inlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
# 清理用户最后的输入(来自 'messages' 列表的末尾)
last_message = body["messages"][-1]["content"]
body["messages"][-1]["content"] = last_message.replace("!!!", "").strip()
return body

📖 会发生什么?

  • 之前: "我该如何调试这个问题!!!" ➡️ 发送至模型为 "我该如何调试这个问题"

注意:用户的感觉没有变化,但模型处理的是一个更清晰、更易于理解的查询。

📊 inlet 如何优化 LLM 输入:
  • 通过澄清模糊查询提升准确性
  • 通过移除不必要的噪音(如表情符号、HTML标签或额外的标点符号)使 AI 更高效
  • 通过将用户输入格式化为模型预期的模式或方案(例如,为特定用例生成 JSON)确保一致性

💭 inlet 想象成厨房里的副厨师——确保输入模型(你的 AI "配方")的所有内容都经过准备、清洗和妥善调味。输入越好,输出越好!


🆕 3️⃣ stream Hook(Open WebUI 0.5.17 新功能)

🔄 什么是 stream Hook?

stream 函数是 Open WebUI 0.5.17 新推出的功能,允许你实时拦截和修改流式的模型响应

与处理完成响应的 outlet 不同,stream 在接收到模型的每个片段时运行。

🛠️ 何时使用 Stream Hook?
  • 在展示给用户之前修改流式响应
  • 实现实时审查或清理
  • 监控流式数据以进行日志记录/调试。
📜 示例:记录流式的片段

下面是检查和修改流式 LLM 响应的方法:

def stream(self, event: dict) -> dict:
print(event) # 打印每个接收到的片段以供检查
return event

示例流式事件:

{"id": "chatcmpl-B4l99MMaP3QLGU5uV7BaBM0eDS0jb","choices": [{"delta": {"content": "Hi"}}]}
{"id": "chatcmpl-B4l99MMaP3QLGU5uV7BaBM0eDS0jb","choices": [{"delta": {"content": "!"}}]}
{"id": "chatcmpl-B4l99MMaP3QLGU5uV7BaBM0eDS0jb","choices": [{"delta": {"content": " 😊"}}]}

📖 发生了什么?

  • 每一行代表模型流式响应的小片段
  • delta.content 字段包含逐步生成的文本。
🔄 示例:从流式数据中过滤表情符号
def stream(self, event: dict) -> dict:
for choice in event.get("choices", []):
delta = choice.get("delta", {})
if "content" in delta:
delta["content"] = delta["content"].replace("😊", "") # 移除表情符号
return event

📖 之前: "Hi 😊"
📖 之后: "Hi"


4️⃣ outlet 函数(输出后处理)

outlet 函数就像一个校对员:在 LLM 处理后对 AI 的响应进行整理(或最终修改)。

📤 输入:

  • body:包含聊天中所有当前消息(用户历史 + LLM 回复)。

🚀 你的任务: 修改这个 body。你可以清理、追加或记录更改,但要注意每个调整对用户体验的影响。

💡 最佳实践:

  • outlet 中更倾向于记录而不是直接编辑(例如调试或分析)。
  • 如果需要进行大量修改(如格式化输出),可以考虑使用 pipe 函数代替。

💡 示例用例: 删除不希望用户看到的敏感 API 响应:

def outlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
for message in body["messages"]:
message["content"] = message["content"].replace("<API_KEY>", "[REDACTED]")
return body

🌟 实际操作中的过滤器:构建实用示例

让我们通过示例构建一些实际用例,了解如何使用过滤器!

📚 示例 #1:为每个用户输入添加上下文

希望 LLM 始终知道它在协助客户排查软件问题?你可以在每个用户查询中添加说明,例如 "你是一名软件问题排查助手"

class Filter:
def inlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
context_message = {
"role": "system",
"content": "You're a software troubleshooting assistant."
}
body.setdefault("messages", []).insert(0, context_message)
return body

📚 示例 #2:高亮输出以便易于阅读

返回以 Markdown 或其他格式化样式呈现的输出?使用 outlet 函数!

class Filter:
def outlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
# 为每个响应添加“高亮” markdown
for message in body["messages"]:
if message["role"] == "assistant": # 目标为模型响应
message["content"] = f"**{message['content']}**" # 使用 Markdown 高亮
return body

🚧 潜在困惑:常见问题解答 🛑

问:过滤器与管道函数有何不同?

过滤器用于修改发送到从模型返回的数据,而不会显著影响这些阶段之外的逻辑。而管道函数:

  • 可以整合外部 API 或大幅改变后端操作处理的方式。
  • 将自定义逻辑暴露为全新的“模型”。

问:可以在 outlet 中进行大量后处理吗?

你可以这样做,但这不是最佳实践

  • 过滤器旨在进行轻量级的更改或应用日志记录。
  • 如果需要进行大量修改,请考虑使用管道函数

🎉 回顾:为什么构建过滤器函数?

到目前为止,你已经学到:

  1. 入口用于操作用户输入(预处理)。
  2. 拦截并修改流式模型输出(实时)。
  3. 出口调整AI输出(后处理)。
  4. 过滤器最适合对数据流进行轻量级的实时调整。
  5. 借助阀门,你可以为用户动态配置过滤器以实现定制行为赋能。

🚀 你的行动:开始实验吧!添加哪些小调整或上下文可以提升你的 Open WebUI 体验?过滤器既有趣又灵活,可以让你的模型达到更高水平!

编码愉快!✨