Skip to main content

🪄 过滤器函数:修改输入和输出

欢迎来到Open WebUI中过滤器函数的完整指南!过滤器是一个灵活而强大的插件系统,用于在数据发送到大型语言模型(LLM)之前(输入)或从LLM返回之后(输出)修改数据。无论您是转换输入以获得更好的上下文,还是清理输出以改善可读性,过滤器函数都能让您完成这一切。

本指南将分解什么是过滤器、它们如何工作、它们的结构,以及您需要知道的一切来构建强大且用户友好的过滤器。让我们深入了解,不要担心——我会使用比喻、示例和技巧来让一切变得清晰明了!🌟


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

想象Open WebUI是一股通过管道流动的水流

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

过滤器位于流动的中间——像检查点——您可以在那里决定需要调整什么。

以下是过滤器功能的快速总结:

  1. 修改用户输入(入口函数):在输入数据到达AI模型之前对其进行调整。这是您增强清晰度、添加上下文、净化文本或重新格式化消息以匹配特定要求的地方。
  2. 拦截模型输出(流函数):在AI响应由模型生成时捕获并调整它们。这对于实时修改非常有用,比如过滤敏感信息或格式化输出以获得更好的可读性。
  3. 修改模型输出(出口函数):在AI响应处理完成后,在向用户显示之前对其进行调整。这可以帮助完善、记录或适配数据以获得更清洁的用户体验。

**关键概念:**过滤器不是独立的模型,而是增强或转换传输来自模型的数据的工具。

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


🗺️ 过滤器函数的结构:框架

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

🦴 过滤器的基本框架

from pydantic import BaseModel
from typing import Optional

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

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

def inlet(self, body: dict) -> dict:
# 这是您操作用户输入的地方。
print(f"inlet called: {body}")
return body

def stream(self, event: dict) -> dict:
# 这是您修改模型输出流式块的地方。
print(f"stream event: {event}")
return event

def outlet(self, body: dict) -> None:
# 这是您操作模型输出的地方。
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 # 重要:这在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)将显示为过滤器名称旁边的小图像。您可以使用任何SVG,只要它是数据URI编码的!
  • inlet函数使用__event_emitter__特殊参数向UI广播反馈/状态,比如显示"已切换!"的小提示/通知。

切换过滤器

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


🎯 关键组件解释

1️⃣ 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钩子(Open WebUI 0.5.17中的新功能)

🔄 什么是stream钩子?

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

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

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

以下是如何检查和修改流式LLM响应:

def stream(self, event: dict) -> dict:
print(event) # 打印每个传入块以进行检查
return event

流式事件示例:

{"id": "chatcmpl-B4l99MMaP3QLGU5uV7BaBM0eDS0jb","choices": [{"delta": {"content": "你好"}}]}
{"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

📖 之前:"你好 😊" 📖 之后:"你好"


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>", "[已编辑]")
return body

🌟 过滤器实践:构建实际示例

让我们构建一些现实世界的示例来看看如何使用过滤器!

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

希望LLM始终知道它在协助客户排除软件错误故障?您可以向每个用户查询添加像**"您是软件故障排除助手"**这样的指令。

class Filter:
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:突出显示输出以便轻松阅读

返回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

🚧 潜在混淆:清晰FAQ 🛑

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

过滤器修改传向来自模型的数据,但不会与这些阶段之外的逻辑大量交互。另一方面,管道:

  • 可以集成外部API或大幅转换后端处理操作的方式。
  • 将自定义逻辑公开为全新的"模型"。

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

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

  • 过滤器设计用于进行轻量级更改或应用记录。
  • 如果需要大量修改,考虑使用管道函数

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

到现在,您已经学到:

  1. Inlet操作用户输入(预处理)。
  2. Stream拦截和修改流式模型输出(实时)。
  3. Outlet调整AI输出(后处理)。
  4. 过滤器最适合对数据流进行轻量级、实时更改。
  5. 通过阀门,您使用户能够动态配置过滤器以获得定制行为。

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

编程愉快!✨