Перейти к основному содержимому

🪄 Функция фильтра: изменение входных и выходных данных

Добро пожаловать в подробное руководство по функциям фильтра в Open WebUI! Фильтры — это гибкая и мощная система плагинов для изменения данных до их отправки в модель большого языка (LLM) (ввод) или после их возврата из LLM (вывод). Независимо от того, улучшаете ли вы контекст входных данных или очищаете выходные данные для улучшения читаемости, функции фильтра позволят вам сделать все это.

Это руководство разложит по полочкам, что такое фильтры, как они работают, их структура и все, что вам нужно знать, чтобы создать мощные и удобные фильтры для собственного использования. Давайте начнем, и не беспокойтесь — я буду использовать метафоры, примеры и советы, чтобы все стало предельно ясно! 🌟


🌊 Что такое фильтры в Open WebUI?

Представьте Open WebUI как поток воды, текущий через трубы:

  • Вводы пользователей и выводы LLM — это вода.
  • Фильтры — это этапы очистки воды, которые чистят, изменяют и адаптируют воду перед тем, как она достигнет конечного назначения.

Фильтры располагаются посередине потока — как контрольные точки — где вы решаете, что нужно отрегулировать.

Вот краткое описание того, что делают фильтры:

  1. Изменение пользовательских вводов (функция Inlet): Настройка входных данных перед тем, как они попадут в модель ИИ. Здесь можно улучшить ясность, добавить контекст, очистить текст или переформатировать сообщения для соответствия конкретным требованиям.
  2. Перехват выводов модели (функция Stream): Захват и настройка ответов ИИ в момент их генерации моделью. Это полезно для реальных изменений, таких как удаление конфиденциальной информации или форматирование выходных данных для улучшения читаемости.
  3. Изменение выводов модели (функция Outlet): Корректировка ответа ИИ после обработки, перед его демонстрацией пользователю. Это может помочь улучшить, записать в лог или адаптировать данные для более чистого пользовательского опыта.

Основное понятие: Фильтры — это не автономные модели, а инструменты, которые улучшают или трансформируют данные, проходящие к и от моделей.

Фильтры похожи на переводчиков или редакторов в последовательности работы ИИ: вы можете перехватить и изменить разговор, не прерывая его поток.


🗺️ Структура функции фильтра: скелет

Начнем с самого простого представления структуры функции фильтра. Не беспокойтесь, если некоторые части сначала покажутся слишком техническими — мы все разберем шаг за шагом!

🦴 Базовый скелет фильтра

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 вызван: {body}")
return body

def stream(self, event: dict) -> dict:
# Здесь вы обрабатываете потоковые части вывода модели.
print(f"stream событие: {event}")
return event

def outlet(self, body: dict) -> None:
# Здесь вы манипулируете выводами модели.
print(f"outlet вызван: {body}")

🆕 🧲 Пример с переключателем фильтра: добавление интерактивности и значков (Новинка в Open WebUI 0.6.10)

Фильтры могут делать больше, чем просто изменять текст — они могут предоставлять переключатели в интерфейсе пользователя и отображать собственные значки. Например, можно создать фильтр, который можно включить/выключить с помощью кнопки в пользовательском интерфейсе, и который отображает специальный значок в UI поля ввода сообщений Open WebUI.

Вот как можно создать такой фильтр с переключателем:

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 # ВАЖНО: Это создает переключатель UI в Open WebUI
# СОВЕТ: Используйте 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": Ложь,
},
}
)
возвращает body

🖼️ Что происходит?

  • toggle = True создает переключатель в Open WebUI, который позволяет пользователям вручную включать или отключать фильтр в реальном времени.
  • icon (с кодировкой Data URI) будет отображаться в виде маленькой картинки рядом с названием фильтра. Вы можете использовать любое SVG, если оно закодировано в формате Data URI!
  • Функция inlet использует специальный аргумент __event_emitter__ для передачи обратной связи/статуса в интерфейс, например, небольшой уведомления/оповещения с текстом "Переключено!"

Переключатель Фильтра

Вы можете использовать эти механизмы для создания динамических, интерактивных и визуально уникальных фильтров в экосистеме плагинов Open WebUI.


🎯 Объяснение ключевых компонентов

1️⃣ Класс Valves (опциональные настройки)

Представьте себе Valves как ручки и ползунки для вашего фильтра. Если вы хотите дать пользователю возможность настраивать поведение вашего фильтра, вы должны определить параметры здесь.

class Valves(BaseModel):
OPTION_NAME: str = "Default Value"

Например: Если вы создаете фильтр, который преобразует ответы в верхний регистр, вы можете позволить пользователю настроить, будет ли каждый вывод полностью заглавным с помощью настройки, подобной TRANSFORM_UPPERCASE: bool = True/False.


2️⃣ Функция inlet (предварительная обработка ввода)

Функция inlet похожа на подготовку еды перед приготовлением. Представьте, что вы шеф-повар: прежде чем ингредиенты попадут в рецепт (в данном случае LLM), вы можете помыть овощи, нарезать лук или приправить мясо. Без этого шага ваше финальное блюдо может быть лишено вкуса, состоять из немытых продуктов или быть просто несогласованным.

В мире Open WebUI функция inlet выполняет эту важную подготовительную работу над вводом пользователя перед отправкой его модели. Она гарантирует, что ввод будет максимально чистым, контекстным и полезным для обработки со стороны ИИ.

📥 Ввод:

  • 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-теги или лишние знаки препинания.
  • Обеспечивает последовательность, форматируя пользовательский ввод в соответствии с ожидаемыми шаблонами или схемами модели (например, JSON для конкретного сценария использования).

💭 Подумайте о inlet как о су-шефе на вашей кухне — он гарантирует, что всё, что поступает в модель (ваш "рецепт ИИ"), предварительно подготовлено, очищено и приправлено до совершенства. Чем лучше ввод, тем лучше вывод!


🆕 3️⃣ stream Hook (Новый в Open WebUI 0.5.17)

🔄 Что такое stream Hook?

Функция stream — это новая возможность, представленная в Open WebUI 0.5.17, которая позволяет перехватывать и изменять потоковые ответы модели в реальном времени.

В отличие от outlet, обрабатывающего весь завершённый ответ, stream работает с индивидуальными частями данных по мере их получения от модели.

🛠️ Когда использовать Stream Hook?
  • Изменение потоковых ответов перед их отображением пользователям.
  • Реализация цензуры или очистки в реальном времени.
  • Мониторинг потоковых данных для логирования/отладки.
📜 Пример: Логирование потоковых частей

Вот как можно инспектировать и изменять потоковые ответы LLM:

def stream(self, event: dict) -> dict:
print(event) # Печать каждой входящей части для анализа
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("😊", "") # Удаление эмодзи
return event

📖 До: "Hi 😊"
📖 После: "Hi"


4️⃣ Функция outlet (Постобработка вывода)

Функция outlet похожа на корректора: уточняет ответ ИИ (или вносит окончательные изменения) после его обработки LLM.

📤 Ввод:

  • body: Содержит все текущие сообщения в чате (историю пользователя + ответы LLM).

🚀 Ваша задача: Изменить этот body. Можно очищать, добавлять или логировать изменения, но учитывайте, как каждое корректирование влияет на пользовательский опыт.

💡 Лучшие практики:

  • Предпочитайте логирование вместо прямого редактирования в outlet (например, для отладки или аналитики).
  • Если необходимы значительные изменения (например, форматирование выводов), рассмотрите возможность использования 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>", "[REDACTED]")
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

🚧 Потенциальные вопросы: Чёткие ответы 🛑

Q: Чем фильтры отличаются от функций Pipe?

Фильтры изменяют данные перед моделью и после модели, но не существенно взаимодействуют с логикой вне этих этапов. Напротив, Pipe-функции:

  • Могут интегрировать внешние API или существенно трансформировать, как бэкенд обрабатывает операции.
  • Представляют пользовательскую логику как совершенно новые "модели".

Q: Могу ли я проводить сложную постобработку внутри outlet?

Вы можете, но это не лучшая практика.:

  • Фильтры предназначены для внесения легких изменений или применения логирования.
  • Если требуются значительные модификации, рассмотрите использование Функции трубопровода вместо этого.

🎉 Подведем итоги: Зачем создавать функции фильтров?

К настоящему моменту вы узнали:

  1. Вход обрабатывает вводимые пользователем данные (предварительная обработка).
  2. Поток перехватывает и модифицирует выводы модели в реальном времени.
  3. Выход корректирует результаты ИИ (постобработка).
  4. Фильтры лучше всего подходят для легких, реальных изменений в потоке данных.
  5. С помощью Клапанов вы даете пользователям возможность динамически настраивать Фильтры для адаптированного поведения.

🚀 Ваш ход: Начните эксперименты! Какое небольшое изменение или добавление контекста могло бы улучшить ваш опыт работы с Open WebUI? Создавать фильтры увлекательно, пользоваться ими удобно, а ваши модели могут выйти на новый уровень!

Успешного программирования! ✨