🪄 过滤器功能:修改输入和输出
欢迎阅读 Open WebUI 中关于过滤器功能的全面指南!过滤器是一个灵活而强大的插件系统,用于修改数据,可以在数据发送到大型语言模型 (LLM) 之前(输入)或从 LLM 返回之后(输出)进行修改。无论您是转换输入以获得更好的上下文,还是清理输出以提高可读性,过滤器功能都能让您轻松完成。
本指南将详细介绍什么是过滤器、它们如何工作、它们的结构以及构建强大且用户友好的自定义过滤器所需了解的一切。让我们深入探讨,别担心——我将使用比喻、示例和技巧,让一切清晰明了!🌟
🌊 Open WebUI 中的过滤器是什么?
想象一下 Open WebUI 就像一条流经管道的水流
- 用户输入和 LLM 输出就是水。
- 过滤器是水处理阶段,用于在水到达最终目的地之前对其进行清洁、修改和调整。
过滤器位于流程的中间——就像检查点一样——您可以在这里决定需要调整什么。
以下是过滤器功能的快速总结
- 修改用户输入(入站功能):在输入数据到达 AI 模型之前对其进行调整。您可以在此处增强清晰度、添加上下文、清理文本或重新格式化消息以匹配特定要求。
- 拦截模型输出(流功能):在模型生成 AI 响应时捕获并调整它们。这对于实时修改非常有用,例如过滤掉敏感信息或格式化输出以提高可读性。
- 修改模型输出(出站功能):在 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}")
🎯 关键组件解释
1️⃣ Valves
类(可选设置)
将 Valves 视为过滤器的旋钮和滑块。如果您想为用户提供可配置选项来调整过滤器的行为,您可以在此处定义它们。
class Valves(BaseModel):
OPTION_NAME: str = "Default Value"
例如
如果您正在创建一个将响应转换为大写字母的过滤器,您可以允许用户通过一个阀门(例如 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:
# 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
📖 会发生什么?
- 之前:
"如何调试这个问题!!!"
➡️ 发送给模型的为"如何调试这个问题"
注意:用户的感受是一样的,但模型处理的是一个更干净、更容易理解的查询。
📊 inlet
如何帮助优化 LLM 的输入:
- 通过澄清模糊的查询来提高准确性。
- 通过移除表情符号、HTML 标签或多余标点符号等不必要的噪声,使 AI 更高效。
- 通过将用户输入格式化以匹配模型的预期模式或 schema(例如,特定用例的 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 始终知道它正在协助客户解决软件错误?您可以将诸如“您是软件故障排除助手”之类的指令添加到每个用户查询中。
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
中进行大量后处理吗?
您可以,但这不是最佳实践。
- 过滤器旨在进行轻量级更改或应用日志记录。
- 如果需要大量修改,请考虑使用管道函数。
🎉 回顾:为什么要构建过滤器函数?
到目前为止,您已经了解了
- Inlet 处理用户输入(预处理)。
- Stream 拦截并修改流式模型输出(实时)。
- Outlet 调整 AI 输出(后处理)。
- 过滤器最适合对数据流进行轻量级、实时的修改。
- 借助 Valves,您可以让用户动态配置过滤器以实现定制行为。
🚀 轮到您了:开始实验吧!哪些微小的调整或上下文添加可以提升您的 Open WebUI 体验?过滤器构建起来很有趣,使用起来很灵活,并且可以将您的模型提升到新的水平!
祝您编程愉快!✨