🪄 过滤器函数:修改输入与输出
欢迎阅读 Open WebUI 过滤器函数 (Filter Functions) 全面指南!过滤器是一个灵活且强大的插件系统,用于在数据发送到大语言模型 (LLM) 之前(输入)或从 LLM 返回之后(输出)对其进行修改。无论您是为了获得更好的上下文而转换输入,还是为了提高可读性而清理输出,过滤器函数都能帮您实现。
本指南将详细介绍什么是过滤器、它们如何工作、它们的结构,以及构建您自己的强大且用户友好的过滤器所需了解的一切。让我们深入探讨,别担心——我会使用比喻、示例和技巧来让一切变得清晰明了!🌟
🌊 什么是 Open WebUI 中的过滤器?
将 Open WebUI 想象成流经管道的水流:
- 用户输入和 LLM 输出就是水。
- 过滤器是水处理阶段,在水到达最终目的地之前对其进行清洁、修改和调整。
过滤器位于流程的中间——就像检查站——在这里您可以决定需要调整什么。
以下是过滤器作用的简要总结:
- 修改用户输入 (Inlet 函数):在输入数据到达 AI 模型之前对其进行微调。在这里,您可以增强清晰度、添加上下文、清理文本或重新格式化消息以满足特定要求。
- 拦截模型输出 (Stream 函数):在模型生成响应时捕获并调整 AI 的响应。这对于实时修改非常有用,例如过滤敏感信息或格式化输出以提高可读性。
- 修改模型输出 (Outlet 函数):在 AI 响应处理完成后、展示给用户之前对其进行调整。这有助于细化、记录或调整数据,以获得更简洁的用户体验。
核心概念: 过滤器不是独立的模型,而是增强或转换往返于模型的数据的工具。
过滤器就像 AI 工作流中的翻译器或编辑器:您可以拦截并更改对话,而不会中断流程。
🗺️ 过滤器函数的结构:骨架
让我们从过滤器函数最简单的表示开始。如果某些部分起初感觉很技术化,请不要担心——我们将一步步分解!
🦴 过滤器的基本骨架
from pydantic import BaseModel
from typing import Optional
class Filter:
# Valves: 过滤器的配置选项
class Valves(BaseModel):
pass
def __init__(self):
# 初始化 valves(过滤器的可选配置)
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 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": "已切换!",
"done": True,
"hidden": False,
},
}
)
return body
🖼️ 发生了什么?
- toggle = True 在 Open WebUI 中创建了一个开关 UI——用户可以实时手动启用或禁用该过滤器。
- icon(带有 Data URI)将显示在过滤器名称旁边的小图标。只要经过 Data URI 编码,您可以使用任何 SVG!
inlet函数使用__event_emitter__特殊参数向 UI 广播反馈/状态,例如显示“已切换!”的小提示/通知。

您可以使用这些机制让您的过滤器在 Open WebUI 的插件生态系统中变得动态、交互且视觉上独特。
⚙️ 过滤器管理与配置
🌐 全局过滤器 vs. 特定模型过滤器
Open WebUI 提供了一个灵活的多级过滤器系统,允许您控制哪些过滤器处于活动状态、如何启用它们以及谁可以切换它们。理解这个系统对于有效的过滤器管理至关重要。
过滤器激活状态
过滤器可以处于以下四种状态之一,由数据库中的两个布尔标志控制:
| 状态 | is_active | is_global | 效果 |
|---|---|---|---|
| 全局启用 | ✅ True | ✅ True | 自动应用于所有模型,不能针对单个模型禁用 |
| 全局禁用 | ❌ False | True | 不应用于任何地方 - 即使过滤器已全局启用,过滤器本身也是禁用的 |
| 特定模型 | ✅ True | ❌ False | 仅应用于管理员明确启用它的模型 |
| 不活跃 | ❌ False | False | 不应用于任何地方,即使管理员为某个模型启用了该过滤器 - 过滤器本身也是关闭的 |
当过滤器被设置为全局 (is_global=True) 且活跃 (is_active=True) 时,它会变成所有模型的强制启用项:
- 它出现在每个模型的过滤器 列表中,显示为已勾选且灰色不可用
- 管理员不能在模型设置中取消勾选它
- 它会在每个聊天补全请求上运行,无论是什么模型
管理面板:将过滤器设为全局
位置: 管理面板 → 函数 (Functions) → 过滤器管理 (Filter Management)
要将过滤器设为全局:
- 导航到管理面板
- 点击侧边栏中的 函数 (Functions)
- 在列表中找到您的过滤器
- 点击过滤器旁边的 三点菜单 (⋮)
- 点击 🌐 地球图标 以切换
is_global - 确保过滤器也是 活跃的 (绿色开关)
API 端点:
POST /functions/id/{filter_id}/toggle/global
视觉指示器:
- 🟢 绿色开关 =
is_active=True(过滤器活跃) - 🌐 高亮地球图标 =
is_global=True(应用于所有模型)
🎛️ 二级过滤器系统
Open WebUI 使用一套精细的二级系统来管理每个模型的过滤器。这起初可能令人困惑,但它旨在同时支持始终开启的过滤器和用户可切换的过滤器。
第一级:FiltersSelector (哪些过滤器可用?)
位置: 模型设置 → 过滤器 → "过滤器" 部分
这控制了哪些过滤器对特定模型可用。
行为:
- 显示所有过滤器(包括全局和特定模型的)
- 全局过滤器显示为已勾选且禁用(无法取消勾选)
- 普通过滤器可以手动开启/关闭
- 保存到数据库中的:
model.meta.filterIds
示例:
{
"meta": {
"filterIds": ["filter-uuid-1", "filter-uuid-2"]
}
}
第二级:DefaultFiltersSelector (哪些可切换过滤器默认开启?)
位置: 模型设置 → 过滤器 → "默认过滤器" 部分
该部分仅在至少选择了一个可切换过滤器(或者是全 局的)时才会出现。
用途: 控制哪些可切换过滤器在每次新聊天中默认启用。
什么是“可切换”过滤器?
当过滤器的 Python 代码包含以下内容时,它就变成了可切换的:
class Filter:
def __init__(self):
self.toggle = True # 这使它变得可切换!
行为:
- 仅显示带有
toggle=True的过滤器 - 仅显示满足以下条件的过滤器:
- 在
filterIds中(已为此模型选择),或者 is_global=true(已全局启用)
- 在
- 控制过滤器在聊天 UI 中默认是 开启 还是 关闭 状态
- 保存到:
model.meta.defaultFilterIds
示例:
{
"meta": {
"filterIds": ["filter-uuid-1", "filter-uuid-2", "filter-uuid-3"],
"defaultFilterIds": ["filter-uuid-2"]
}
}
解释:
- 这三个过滤器对此模型都可用
- 只有
filter-uuid-2默认初始启用 - 如果
filter-uuid-1和filter-uuid-3带有toggle=True,用户可以在聊天 UI 中手动启用它们
🔄 可切换过滤器 vs. 始终开启的过滤器
理解这两类过滤器的区别是有效使用过滤器系统的关键。
始终开启的过滤器 (无 toggle 属性)
特征:
- 只要过滤器对模型处于活跃状态,就会自动运行
- 用户在聊天界面中无法控制
- 不会出现在“默认过滤器”部分
- 不会出现在聊天集成菜单(⚙️ 图标)中
用例:
- 内容审核(始终过滤不当内容)
- PII 擦除(始终移除敏感数据)
- 系统级转换(始终应用某些格式化)
- 安全/合规性过滤器
示例:
class ContentModerationFilter:
def __init__(self):
# 没有 toggle 属性 - 这是一个始终开启的过滤器
pass
def inlet(self, body: dict) -> dict:
# 在发送到模型之前始终擦除 PII
last_message = body["messages"][-1]["content"]
body["messages"][-1]["content"] = self.scrub_pii(last_message)
return body
可切换过滤器 (toggle=True)
特征:
- 在聊天 UI 中显示为开关(在集成菜单中 - ⚙️ 图标)
- 用户可以按聊天会话启用/禁用它们
- 会出现在“默认过滤器”部分
defaultFilterIds控制它们的初始状态(开启或关闭)
用例:
- 联网搜索集成(用户决定何时搜索)
- 引用模式(用户控制何时需要来源)
- 详细输出模式(用户切换详细响应)
- 翻译过滤器(用户在需要时启用)
- 代码格式化(用户选择何时应用)
- 思考/推理开关(用户控制是否显示模型推理过程)
示例:
class WebSearchFilter:
def __init__(self):
self.toggle = True # 用户可以开启/关闭
self.icon = "data:image/svg+xml;base64,..." # 在 UI 中显示
async def inlet(self, body: dict, __event_emitter__) -> dict:
# 仅在用户启用了此过滤器时运行
await __event_emitter__({
"type": "status",
"data": {"description": "正在搜索网络...", "done": False}
})
# ... 执行网络搜索 ...
return body
可切换过滤器出现在哪里:
- 模型设置 → 默认过滤器部分
- 配置哪些过滤器初始启用
- 聊天 UI → 集成菜单 (⚙️ 图标)
- 用户可以按聊天切换过滤器开启/关闭
- 如果提供了自定义图标,则会显示
- 实时启用/禁用
📊 过滤器执行流程
这是从管理员配置到过滤器执行的完整流程:
1. 管理面板 (过滤器创建与全局设置)
- 管理面板 → 函数 (Functions) → 创建新函数
- 设置 type="filter"
- 切换 is_active (全局启用/禁用过滤器)
- 切换 is_global (应用于所有模型)
2. 模型配置 (每个模型的过滤器选择)
- 模型设置 → 过滤器部分
- FiltersSelector: 选择此模型要使用的过滤器
- DefaultFiltersSelector: 设置默认启用状态(仅针对可切换过滤器)
3. 聊天 UI (用户交互 - 仅限可切换过滤器)
- 聊天 → 集成菜单 (⚙️) → 切换过滤器
- 用户可以启用/禁用可切换过滤器
- 始终开启的过滤器自动运行(无 UI 控制)
4. 请求处理 (过滤器编译)
- 后端:get_sorted_filter_ids()
- 获取全局过滤器 (is_global=True, is_active=True)
- 从 model.meta.filterIds 添加特定模型过滤器
- 按 is_active 状态过滤
- 对于可切换过滤器:检查用户的启用状态
- 按优先级排序 (来自 valves)
5. 过滤器执行
- 执行 inlet() 过滤器(请求前)
- 将修改后的请求发送给 LLM
- 执行 stream() 过滤器(流式传输期间)
- 执行 outlet() 过滤器(响应后)
🎨 UI 指示器与视觉反馈
在管理函数面板中
| 指示器 | 含义 |
|---|---|
| 🟢 绿色开关 | 过滤器活跃 (is_active=True) |
| ⚪ 灰色开关 | 过滤器不活跃 (is_active=False) |
| 🌐 高亮地球 | 过滤器是全局的 (is_global=True) |
| 🌐 变灰地球 | 过滤器不是全局的 (is_global=False) |
在模型设置中 (FiltersSelector)
| 状态 | 复选框 | 描述 |
|---|---|---|
| 全局过滤器 | ✅ 已勾选且禁用 (变灰) | "此过滤器已全局启用" |
| 已选过滤器 | ✅ 已勾选且启用 | "此过滤器已为此模型选中" |
| 未选过滤器 | ☐ 未勾选且启用 | "点击以包含此过滤器" |
在聊天 UI 中 (集成菜单)
| 元素 | 描述 |
|---|---|
| 过滤器名称 | 显示过滤器的显示名称 |
| 自定义图标 | 来自 self.icon 的 SVG 图标 (如果提供) |
| 切换开关 | 为此聊天启用/禁用过滤器 |
| 状态徽章 | 显示过滤器当前是否活跃 |
💡 过滤器配置最佳实践
1. 何时使用全局过滤器
✅ 对以下情况使用全局过滤器:
- 安全与合规(PII 擦除、内容审核)
- 系统范围的格式化(标准化所有输出)
- 日志与分析(跟踪所有请求)
- 组织范围的策略(执行公司指南)
❌ 不要对以下情况使用全局过滤器:
- 可选功能(改用可切换过滤器)
- 特定模型的行为(使用特定模型过滤器)
- 用户偏好功能(让用户通过开关控制)
2. 何时使用可切换过滤器
✅ 在以下情况下使过滤器可切换 (toggle=True):
- 用户应控制它何时活跃(联网搜索、翻译)
- 它是一个可选增强功能(引用模式、详细输出)
- 它添加了用户可能并不总是想要的功能(代码格式化)
- 它具有应该是可选的性能成本
❌ 在以下情况下不要使过滤器可切换:
- 它是安全/合规所必需的(始终开启更好)
- 用户不应能够禁用它(使用始终开启)
- 它是系统级转换(全局更好)
3. 为您的组织组织过滤器
推荐结构:
全局始终开启过滤器:
├─ PII 擦除器 (安全)
├─ 内容审核器 (合规)
└─ 请求记录器 (分析)
特定模型始终开启过滤器:
├─ 代码格式化器 (仅限代码模型)
├─ 医学术语纠错器 (仅限医学模型)
└─ 法律引用验证器 (仅限法律模型)
可切换过滤器 (用户选择):
├─ 联网搜索集成
├─ 引用模式
├─ 翻译过滤器
├─ 详细输出模式
└─ 图像描述生成器
🎯 核心组件解析
1️⃣ Valves 类 (可选设置)
将 Valves 想象成过滤器的旋钮和滑块。如果您想给用户提供可配置的选项来调整过滤器的行为,您可以在这里定义它们。
class Valves(BaseModel):
OPTION_NAME: str = "默认值"
例如:
如果您正在创建一个将响应转换为大写字母的过滤器,您可以通过像 TRANSFORM_UPPERCASE: bool = True/False 这样的阀门允许用户配置是否将每个输出完全大写。
使用下拉菜单配置 Valves (枚举)
您可以通过为某些 Valves 提供下拉菜单而不是自由文本输入,来增强过滤器设置的用户体验。这可以 通过在 Pydantic Field 定义中使用带有 enum 关键字的 json_schema_extra 来实现。
enum 关键字允许您指定一个预定义值的列表,UI 应将其作为下拉列表中的选项呈现。
示例: 在过滤器中为颜色主题创建一个下拉菜单。
from pydantic import BaseModel, Field
from typing import Optional
# 定义您的可用选项(例如,颜色主题)
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="为 LLM 响应选择一个预定义的颜色主题。'Plain (No Color)' 禁用着色。",
json_schema_extra={"enum": list(COLOR_THEMES.keys())}, # 关键:这会创建下拉菜单
)
custom_colors_csv: str = Field(
"",
description="'Custom Sequence' 主题的颜色 CSV(例如,'red,blue,green')。使用 xcolor 名称。",
)
strip_existing_latex: bool = Field(
True,
description="如果为 true,尝试移除现有的 LaTeX 颜色命令。建议启用以避免嵌套渲染问题。",
)
colorize_type: str = Field(
"sequential_word",
description="如何应用颜色:'sequential_word' (逐词), 'sequential_line' (逐行), 'per_letter' (逐字母), 'full_message' (整个消息)。",
json_schema_extra={
"enum": [
"sequential_word",
"sequential_line",
"per_letter",
"full_message",
]
}, # 另一个枚举下拉菜单的例子
)
color_cycle_reset_per_message: bool = Field(
True,
description="如果为 true,颜色序列在每个新的 LLM 响应消息中重新开始。如果为 false,则跨消息继续。",
)
debug_logging: bool = Field(
False,
description="启用控制台的详细日志记录,用于调试过滤器操作。",
)
def __init__(self):
self.valves = self.Valves()
# ... 您的 __init__ 逻辑的其余部分 ...
发生了什么?
json_schema_extra:Field中的这个参数允许您注入 Pydantic 不显式支持但可以被下游工具(如 Open WebUI 的 UI 渲染器)使用的任意 JSON Schema 属性。"enum": list(COLOR_THEMES.keys()): 这告诉 Open WebUI,selected_theme字段应呈现一组可选值,具体来说就是我们COLOR_THEMES字典的键。然后 UI 将渲染一个下拉菜单,其中包含 "Plain (No Color)"、"Monochromatic Blue"、"Warm & Energetic" 等作为可选项。colorize_type字段也展示了另一个用于不同着色方法的enum下拉菜单。
为您的 Valves 选项使用 enum 使您的过滤器更易于使用,并防止无效输入,从而带来更流畅的配置体验。
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 在从模型接收单个分块 (chunks) 时对其进行操作。
🛠️ 何时使用 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 中,比起直接编辑,更推荐记录日志(例如用于调试或分析)。
- 如果需要进行大量修改(如格式化输出),请考虑改用 Pipe 函数 (pipe function)。
💡 示例用例:剥离出您不想让用户看到的敏感 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
🚧 潜在困惑:常见问题解答 🛑
问:过滤器与 Pipe 函数有什么不同?
过滤器修改往返于模型的数据,但不会在这些阶段之外与逻辑产生显著交互。而 Pipes (管道):
- 可以集成外部 API,或显著转换后端处理操作的方式。
- 将自定义逻辑公开为全新的“模型”。
问:我可以在 outlet 内部进行繁重的后处理吗?
您可以,但这并不是最佳实践:
- 过滤器旨在进行轻量级更改或应用日志记录。
- 如果需要进行大量修改,请考虑使用 Pipe 函数。
🎉 回顾:为什么要构建过滤器函数?
到目前为止,您已经学到:
- Inlet 操作用户输入(预处理)。
- Stream 拦截并修改流式的模型输出(实时)。
- Outlet 微调 AI 输出(后处理)。
- 过滤器最适合对数据流进行轻量级、实时的改动。
- 通过 Valves,您可以授权用户动态配置过滤器以获得定制行为。
🚀 轮到你了:开始实验吧!什么样的微调或上下文添加可以提升您的 Open WebUI 体验?过滤器构建起来很有趣,使用起来很灵活,并且可以将您的模型提升到新的水平!
祝您编码愉快!✨