🪄 过滤器函数:修改输入和输出
欢迎来到Open WebUI中过滤器函数的完整指南!过滤器是一个灵活而强大的插 件系统,用于在数据发送到大型语言模型(LLM)之前(输入)或从LLM返回之后(输出)修改数据。无论您是转换输入以获得更好的上下文,还是清理输出以改善可读性,过滤器函数都能让您完成这一切。
本指南将分解什么是过滤器、它们如何工作、它们的结构,以及您需要知道的一切来构建强大且用户友好的过滤器。让我们深入了解,不要担心——我会使用比喻、示例和技巧来让一切变得清晰明了!🌟
🌊 什么是Open WebUI中的过滤器?
想象Open WebUI是一股通过管道流动的水流:
- 用户输入和LLM输出是水。
- 过滤器是水处理阶段,在水到达最终目的地之前清洁、修改和适配水。
过滤器位于流动的中间——像检查点——您可以在那里决定需要调整什么。
以下是过滤器功能的快速总结:
- 修改用户输入(入口函数):在输入数据到达AI模型之前对其进行调整。这是您增强清晰度、添加上下文、净化文本或重新格式化消息以匹配特定要求的地方。
- 拦截模型输出(流函数):在AI响应由模型生成时捕获并调整它们。这对于实时修改非常有用,比如过滤敏感信息或格式化输出以获得更好的可读性。
- 修改模型输出(出口函数):在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
?
-
添加上下文:自动向用户的输入添加关键信息,特别是当他们的文本模糊或不完整时。例如,您可能添加"您是一个友好的助手"或"帮助这个用户排除软件错误"。
-
格式化数据:如果输入需要特定格式,如JSON或Markdown,您可以在发送给模型之前转换它。
-
净化输入:删除不需要的字符,剥离可能有害或令人困惑的符号(如过多的空格或表情符号),或替换敏感信息。
-
简化用户输入:如果您的模型输出通过额外指导得到改善,您可以使用
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