跳到主要内容

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

欢迎阅读 Open WebUI 过滤功能的详细指南!过滤器是一个灵活且强大的插件系统,用于修改数据,无论是在发送到大型语言模型 (LLM) 之前(输入),还是在从 LLM 返回之后(输出)。无论您是为提供更好的上下文而转换输入,还是为提高可读性而清理输出,过滤功能都能帮您实现。

本指南将详细介绍什么是过滤器、它们的工作原理、结构,以及构建强大且用户友好的自定义过滤器所需了解的一切。让我们深入探讨,别担心——我将使用比喻、示例和提示,让一切都清晰明了!🌟


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

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

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

过滤器位于流程中间,就像检查点一样,您可以在这里决定需要进行哪些调整。

以下是过滤器功能的快速摘要

  1. 修改用户输入(Inlet 函数):在数据到达 AI 模型之前对其进行调整。您可以在此处增强清晰度、添加上下文、清理文本或重新格式化消息以满足特定要求。
  2. 拦截模型输出(Stream 函数):捕获并调整 AI 在模型生成时的响应。这对于实时修改非常有用,例如过滤掉敏感信息或格式化输出以提高可读性。
  3. 修改模型输出(Outlet 函数):在 AI 的响应处理完毕后、向用户显示之前对其进行调整。这有助于优化、记录或调整数据,以提供更简洁的用户体验。

核心概念:过滤器并非独立的模型,而是增强或转换数据在模型之间传输()的工具。

过滤器在 AI 工作流程中就像翻译器或编辑器:您可以在不中断流程的情况下拦截并更改对话。


🗺️ 过滤函数结构:骨架

让我们从过滤函数最简单的表示开始。如果有些部分一开始感觉技术性很强,请不要担心——我们将逐步进行拆解!

🦴 过滤器的基本骨架

from pydantic import BaseModel
from typing import Optional

class Filter:
# Valves: Configuration options for the filter
class Valves(BaseModel):
pass

def __init__(self):
# Initialize valves (optional configuration for the Filter)
self.valves = self.Valves()

def inlet(self, body: dict) -> dict:
# This is where you manipulate user inputs.
print(f"inlet called: {body}")
return body

def stream(self, event: dict) -> dict:
# This is where you modify streamed chunks of model output.
print(f"stream event: {event}")
return event

def outlet(self, body: dict) -> None:
# This is where you manipulate model outputs.
print(f"outlet called: {body}")

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

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

以下是创建此类开关过滤器的方法

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 # IMPORTANT: This creates a switch UI in Open WebUI
# TIP: Use SVG Data 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": "Toggled!",
"done": True,
"hidden": False,
},
}
)
return body

🖼️ 发生了什么?

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

Toggle Filter

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


🎯 关键组件解释

1️⃣ Valves 类(可选设置)

Valves 视为您过滤器的旋钮和滑块。如果您希望为用户提供可配置的选项来调整过滤器的行为,您可以在此处定义它们。

class Valves(BaseModel):
OPTION_NAME: str = "Default Value"

例如
如果您正在创建一个将响应转换为大写的过滤器,您可以通过诸如 TRANSFORM_UPPERCASE: bool = True/False 这样的阀门,允许用户配置是否将所有输出完全大写。

使用下拉菜单配置阀门(枚举)

您可以通过为某些 Valves 提供下拉菜单而不是自由文本输入来增强过滤器的设置用户体验。这可以通过在您的 Pydantic Field 定义中使用 json_schema_extraenum 关键字来实现。

enum 关键字允许您指定 UI 应作为下拉选项呈现的预定义值列表。

示例:在过滤器中为颜色主题创建下拉菜单。

from pydantic import BaseModel, Field
from typing import Optional

# Define your available options (e.g., color themes)
COLOR_THEMES = {
"Plain (No Color)": [],
"Monochromatic Blue": ["blue", "RoyalBlue", "SteelBlue", "LightSteelBlue"],
"Warm & Energetic": ["orange", "red", "magenta", "DarkOrange"],
"Cool & Calm": ["cyan", "blue", "green", "Teal", "CadetBlue"],
"Forest & Earth": ["green", "DarkGreen", "LimeGreen", "OliveGreen"],
"Mystical Purple": ["purple", "DarkOrchid", "MediumPurple", "Lavender"],
"Grayscale": ["gray", "DarkGray", "LightGray"],
"Rainbow Fun": [
"red",
"orange",
"yellow",
"green",
"blue",
"indigo",
"violet",
],
"Ocean Breeze": ["blue", "cyan", "LightCyan", "DarkTurquoise"],
"Sunset Glow": ["DarkRed", "DarkOrange", "Orange", "gold"],
"Custom Sequence (See Code)": [],
}

class Filter:
class Valves(BaseModel):
selected_theme: str = Field(
"Monochromatic Blue",
description="Choose a predefined color theme for LLM responses. 'Plain (No Color)' disables coloring.",
json_schema_extra={"enum": list(COLOR_THEMES.keys())}, # KEY: This creates the dropdown
)
custom_colors_csv: str = Field(
"",
description="CSV of colors for 'Custom Sequence' theme (e.g., 'red,blue,green'). Uses xcolor names.",
)
strip_existing_latex: bool = Field(
True,
description="If true, attempts to remove existing LaTeX color commands. Recommended to avoid nested rendering issues.",
)
colorize_type: str = Field(
"sequential_word",
description="How to apply colors: 'sequential_word' (word by word), 'sequential_line' (line by line), 'per_letter' (letter by letter), 'full_message' (entire message).",
json_schema_extra={
"enum": [
"sequential_word",
"sequential_line",
"per_letter",
"full_message",
]
}, # Another example of an enum dropdown
)
color_cycle_reset_per_message: bool = Field(
True,
description="If true, the color sequence restarts for each new LLM response message. If false, it continues across messages.",
)
debug_logging: bool = Field(
False,
description="Enable verbose logging to the console for debugging filter operations.",
)

def __init__(self):
self.valves = self.Valves()
# ... rest of your __init__ logic ...

发生了什么?

  • json_schema_extraField 中的此参数允许您注入 Pydantic 不明确支持但可由下游工具(如 Open WebUI 的 UI 渲染器)使用的任意 JSON Schema 属性。
  • "enum": list(COLOR_THEMES.keys()):这告诉 Open WebUI,selected_theme 字段应该呈现一组值,特别是来自我们 COLOR_THEMES 字典的键。然后,UI 将渲染一个下拉菜单,其中包含“纯色(无颜色)”、“单色蓝”、“温暖活力”等可选选项。
  • colorize_type 字段还演示了另一种用于不同着色方法的 enum 下拉菜单。

为您的 Valves 选项使用 enum 可以使您的过滤器更加用户友好,并防止无效输入,从而带来更顺畅的配置体验。


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:
# Add system message for Italian context in the conversation
context_message = {
"role": "system",
"content": "You are helping the user prepare an Italian meal."
}
# Insert the context at the beginning of the chat history
body.setdefault("messages", []).insert(0, context_message)
return body

📖 发生了什么?

  • 任何像“有什么好的晚餐主意?”这样的用户输入现在都带有意大利主题,因为我们已经设置了系统上下文!芝士蛋糕可能不会出现在答案中,但意大利面肯定会出现。
🔪 示例 2:清理输入(删除奇怪字符)

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

def inlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
# Clean the last user input (from the end of the 'messages' list)
last_message = body["messages"][-1]["content"]
body["messages"][-1]["content"] = last_message.replace("!!!", "").strip()
return body

📖 发生了什么?

  • 之前:"How can I debug this issue!!!" ➡️ 发送给模型的为 "How can I debug this issue"

注意:用户感受相同,但模型处理的是一个更清晰、更容易理解的查询。

📊 inlet 如何帮助优化 LLM 输入:
  • 通过澄清模糊查询来提高准确性
  • 通过删除不必要的噪音,如表情符号、HTML 标签或多余标点符号,使 AI 更高效
  • 通过格式化用户输入以匹配模型预期的模式或模式(例如,特定用例的 JSON),确保一致性

💭 inlet 看作您厨房里的副厨师长——确保进入模型(您的 AI “食谱”)的一切都已完美准备、清洗和调味。输入越好,输出越好!


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

🔄 什么是 stream 钩子?

stream 函数是 Open WebUI 0.5.17 中引入的新功能,它允许您实时拦截和修改流式模型响应

与处理整个已完成响应的 outlet 不同,stream 对从模型接收到的单个块进行操作。

🛠️ 何时使用 Stream 钩子?
  • 在向用户显示之前修改流式响应
  • 实现实时审查或清理
  • 监控流式数据进行日志记录/调试。
📜 示例:记录流式块

以下是您如何检查和修改流式 LLM 响应的方法

def stream(self, event: dict) -> dict:
print(event) # Print each incoming chunk for inspection
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("😊", "") # Strip emojis
return event

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


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

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

📤 输入

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

🚀 您的任务:修改此 body。您可以清理、追加或记录更改,但请注意每个调整如何影响用户体验。

💡 最佳实践

  • 优先选择在 outlet 中进行日志记录,而非直接编辑(例如,用于调试或分析)。
  • 如果需要大量修改(如格式化输出),请考虑使用管道函数代替。

💡 示例用例:剥离您不想让用户看到的敏感 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 始终知道它正在协助客户排除软件 bug 吗?您可以将诸如“您是软件故障排除助手”之类的指令添加到每个用户查询中。

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:
# Add "highlight" markdown for every response
for message in body["messages"]:
if message["role"] == "assistant": # Target model response
message["content"] = f"**{message['content']}**" # Highlight with Markdown
return body

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

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

过滤器修改进出模型的数据,但不会与这些阶段之外的逻辑进行显著交互。另一方面,管道

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

问:我可以在 outlet 内部进行大量后处理吗?

可以,但这不是最佳实践。

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

🎉 回顾:为何构建过滤函数?

现在,您已经了解了

  1. Inlet 操作用户输入(预处理)。
  2. Stream 拦截并修改流式模型输出(实时)。
  3. Outlet 调整AI 输出(后处理)。
  4. 过滤器最适合对数据流进行轻量级的实时修改。
  5. 通过 Valves,您可以让用户动态配置过滤器以实现定制行为。

🚀 轮到你了:开始实验吧!哪些小调整或上下文添加可以提升您的 Open WebUI 体验?过滤器构建起来很有趣,使用起来很灵活,并且可以将您的模型提升到新的水平!

编程愉快!✨