Filter Function: Modify Inputs and Outputs
Filter Functions execute arbitrary Python code on your server. Function creation is restricted to administrators only. Only install from trusted sources and review code before importing. A malicious Function could access your file system, exfiltrate data, or compromise your entire system. For full details, see the Plugin Security Warning.
Welcome to the comprehensive guide on Filter Functions in Open WebUI! Filters are a flexible and powerful plugin system for modifying data before it's sent to the Large Language Model (LLM) (input) or after it’s returned from the LLM (output). Whether you’re transforming inputs for better context or cleaning up outputs for improved readability, Filter Functions let you do it all.
This guide will break down what Filters are, how they work, their structure, and everything you need to know to build powerful and user-friendly filters of your own. Let’s dig in, and don’t worry, I’ll use metaphors, examples, and tips to make everything crystal clear! 🌟
What Are Filters in Open WebUI?
Imagine Open WebUI as a stream of water flowing through pipes:
- User inputs and LLM outputs are the water.
- Filters are the water treatment stages that clean, modify, and adapt the water before it reaches the final destination.
Filters sit in the middle of the flow (like checkpoints) where you decide what needs to be adjusted.
Here’s a quick summary of what Filters do:
- Modify User Inputs (Inlet Function): Tweak the input data before it reaches the AI model. This is where you enhance clarity, add context, sanitize text, or reformat messages to match specific requirements.
- Modify Every Outgoing Call (Request Function): Adjust the payload immediately before it is handed to the model provider, every single time one is sent. Added in v0.11.2, and covered in The
requeststep below. - Intercept Model Outputs (Stream Function): Capture and adjust the AI’s responses as they’re generated by the model. This is useful for real-time modifications, like filtering out sensitive information or formatting the output for better readability.
- Modify Model Outputs (Outlet Function): Adjust the AI's response after it’s processed, before showing it to the user. This can help refine, log, or adapt the data for a cleaner user experience.
Key Concept: Filters are not standalone models but tools that enhance or transform the data traveling to and from models.
Filters are like translators or editors in the AI workflow: you can intercept and change the conversation without interrupting the flow.
Structure of a Filter Function: The Skeleton
Let's start with the simplest representation of a Filter Function. Don't worry if some parts feel technical at first, we’ll break it all down step by step!
Basic Skeleton of a Filter
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()
async def inlet(self, body: dict) -> dict:
# This is where you manipulate user inputs.
print(f"inlet called: {body}")
return body
async def request(self, body: dict) -> dict:
# This is where you manipulate the payload for each outgoing model call.
print(f"request called: {body}")
return body
async def stream(self, event: dict) -> dict:
# This is where you modify streamed chunks of model output.
print(f"stream event: {event}")
return event
async def outlet(self, body: dict) -> dict:
# This is where you manipulate model outputs.
print(f"outlet called: {body}")
return bodyThe request step
request was added in v0.11.2. It receives the payload that is about to be sent to the model provider, and whatever it returns is what gets sent.
async def request(self, body: dict, __user__: Optional[dict] = None) -> dict:
body["metadata"] = {**body.get("metadata", {}), "department": "research"}
return bodyHow it differs from inlet
inlet runs once per user turn. request runs once per call to the model, and those are not the same number.
A turn that uses tools makes several calls: one to decide which tool to use, then another after each set of tool results comes back, until the model produces a final answer. inlet saw only the first of those. request runs before every one of them, including the call made after a tool run the user had to approve. After an approval, request filters run before the approved tool executes, so a filter can still reject the request before the tool has any effect.
That difference decides which one you want:
Use inlet when | Use request when |
|---|---|
| You are reacting to what the user typed | You are enforcing something on the wire |
| Running your logic more than once per turn would be wrong, such as billing, rate limiting or logging a turn | Running it on every call is the point, such as stamping a parameter, injecting a header or redacting content |
| You want to change the user's message | You want to change the request, whether the messages in it came from the user, the model or a tool |
Something added in inlet reaches the first call and can be dropped from later ones as the message list is rebuilt for each follow-up. Something added in request is on every call by construction, which is why enforcement belongs here.