feat: 新增插件系统、多种插件类型、开发指南及多语言文档。
This commit is contained in:
45
docs/en/gemini_manifold_plugin_philosophy.md
Normal file
45
docs/en/gemini_manifold_plugin_philosophy.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Gemini Manifold 插件开发哲学
|
||||
|
||||
## 概览
|
||||
|
||||
- 源码位于 `plugins/pipes/gemini_mainfold/gemini_manifold.py`,作为 Open WebUI 的 Pipe 插件,主要负责把前端的请求转化成 Google Gemini/Vertex AI 的调用,并将结果通过 `AsyncGenerator` 回流给前端。
|
||||
- 插件采用了 `Valves + UserValves` 的配置模式、异步事件与状态汇报、细粒度日志、文件缓存与上传中间件,以及统一响应处理器,充分体现了 Open WebUI 通用插件的开发模式。
|
||||
|
||||
## 核心模块
|
||||
|
||||
1. **`Pipe` 类(入口)**
|
||||
- `pipes` 方法注册可选模型,缓存模型列表并仅在配置变更时刷新。
|
||||
- `pipe` 方法为每个请求建立 Google GenAI 客户端、`EventEmitter` 与 `FilesAPIManager`,构建 `GeminiContentBuilder`,并统一把返回值交给 `_unified_response_processor`。
|
||||
|
||||
2. **`GeminiContentBuilder`(请求构建)**
|
||||
- 解析 Open WebUI 消息、引用历史、文件上传、YouTube/Markdown 媒体等内容,并通过 `UploadStatusManager` 与 `FilesAPIManager` 协作,确保上传进度可视。
|
||||
|
||||
3. **`FilesAPIManager`(文件缓存+上传)**
|
||||
- 采用 xxHash 内容地址、热/暖/冷路径、自定义锁、 TTL 缓存等手段防止重复上传,同时会在发生错误时用 `FilesAPIError` 抛出并告知前端。
|
||||
|
||||
4. **`EventEmitter` + `UploadStatusManager`(状态反馈)**
|
||||
- 抽象 Toast/Status/Completion 的交互,按需异步发送,赋予前端实时反馈能力,避免阻塞主流程。
|
||||
|
||||
5. **统一响应处理与后置处理**
|
||||
- `_unified_response_processor` 兼容流式/一次性响应,调用 `_process_part`、`_disable_special_tags` 保护前端,再在 `_do_post_processing` 发出 usage、grounding 等数据。
|
||||
|
||||
## 与 Open WebUI 插件哲学契合的实践
|
||||
|
||||
- **配置层叠与安全**:`Valves` 提供 admin 默认,`UserValves` 允许用户按需覆盖。`USER_MUST_PROVIDE_AUTH_CONFIG` + `AUTH_WHITELIST` 确保敏感场景必须使用各自凭证。
|
||||
- **异步状态与进度可视**:所有上传/调用都在 `EventEmitter` 中报告 toast/status,`UploadStatusManager` 用 queue 追踪并呈现进度,流式响应直接产出 `choices` chunk 与 `[DONE]`,前端无需额外猜测。
|
||||
- **功能可拓展性**:基于 `Functions.get_function_by_id` 检查 filter、依据 `features`/`toggle` 开启 Search、Code Execution、URL Context、Maps grounding,体现 Open WebUI 组件可组合的插件模型。
|
||||
- **文件与资源复用**:`FilesAPIManager` 通过 deterministic name、缓存、stateless GET,提高效率;生成图片也回写到 Open WebUI 的 `Files` 模型,为前端提供可持久化的 URL。
|
||||
- **透明日志与错误可控**:自定义 loguru handler(截断 `payload`)、统一的异常类、对 SDK 错误的 toast+error emission、对特殊 tag 的 ZWS 处理,确保插件运行时的状态始终可追踪并兼容前端。
|
||||
- **统一流程**:全链路从 request -> builder -> client -> response processor -> post-processing,严格对齐 Open WebUI pipe+filter 结构,便于扩展和维护。
|
||||
|
||||
## 下一步建议
|
||||
|
||||
- 如果需要,可将上述内容整理到 Plugin README/开发指南中。也可以基于该文档再绘制流程图或生成寄语性文档,供团队使用。
|
||||
|
||||
## 进一步参考
|
||||
|
||||
详细的代码示例与使用场景请参见 `docs/gemini_manifold_plugin_examples.md`,包括:
|
||||
|
||||
- 配置层叠、异步事件、文件缓存等基础模式
|
||||
- 响应处理、标签防护、异常管理等中级技巧
|
||||
- 后处理流程、日志控制等高级实践
|
||||
50
docs/en/implementation_plan.md
Normal file
50
docs/en/implementation_plan.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 开源项目重组实施计划
|
||||
|
||||
## 1. 目标
|
||||
将 `openwebui-extras` 打造为一个 **OpenWebUI 增强功能集合库**,专注于分享个人开发和收集的优质插件、提示词,而非作为一个独立的 Python 应用程序发布。
|
||||
|
||||
## 2. 当前状态分析
|
||||
- **定位明确**:项目核心价值在于内容(Plugins, Prompts, Docs),而非运行环境。
|
||||
- **结构已优化**:
|
||||
- `plugins/`:核心插件资源。
|
||||
- `prompts/`:提示词资源。
|
||||
- `docs/`:详细的使用和开发文档。
|
||||
- `scripts/`:辅助工具脚本(如本地测试用的 `run.py`)。
|
||||
- **已移除不必要文件**:移除了 `requirements.txt`,避免用户误以为需要配置 Python 环境。
|
||||
|
||||
## 3. 重组方案
|
||||
|
||||
### 3.1 目录结构
|
||||
保持当前的清晰结构,强调“拿来即用”:
|
||||
|
||||
```
|
||||
openwebui-extras/
|
||||
├── docs/ # 文档与教程
|
||||
├── plugins/ # 插件库 (核心资源)
|
||||
│ ├── actions/
|
||||
│ ├── filters/
|
||||
│ ├── pipelines/
|
||||
│ └── pipes/
|
||||
├── prompts/ # 提示词库 (核心资源)
|
||||
├── scripts/ # 维护者工具 (非用户必须)
|
||||
├── LICENSE # MIT 许可证
|
||||
├── README.md # 项目入口与资源索引
|
||||
└── index.html # 项目展示页
|
||||
```
|
||||
|
||||
### 3.2 核心调整
|
||||
1. **移除依赖管理**:删除了 `requirements.txt`。用户不需要 `pip install` 任何东西,只需下载对应的 `.py` 或 `.md` 文件导入 OpenWebUI 即可。
|
||||
2. **文档侧重**:README 和文档将侧重于“如何下载”和“如何导入”,而不是“如何安装项目”。
|
||||
|
||||
### 3.3 后续建议
|
||||
1. **资源索引**:建议在 `README.md` 中维护一个高质量的插件/提示词索引表,方便用户快速查找。
|
||||
2. **贡献指南**:制定简单的 `CONTRIBUTING.md`,告诉其他人如何提交他们的插件或提示词(例如:只需提交文件到对应目录)。
|
||||
3. **版本控制**:虽然不需要 Python 环境,但建议在插件文件的头部注释中保留版本号和兼容性说明(如 `Compatible with OpenWebUI v0.3.x`)。
|
||||
|
||||
## 4. 发布流程
|
||||
1. **提交更改**:`git add . && git commit -m "Update project structure for resource sharing"`
|
||||
2. **推送到 GitHub**。
|
||||
3. **宣传**:在 OpenWebUI 社区分享此仓库链接。
|
||||
|
||||
---
|
||||
*生成时间:2025-12-19*
|
||||
234
docs/en/plugin_development_guide.md
Normal file
234
docs/en/plugin_development_guide.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# OpenWebUI Plugin Development Guide
|
||||
|
||||
> This guide consolidates official documentation, SDK details, and best practices to provide a systematic tutorial for developers, from beginner to expert.
|
||||
|
||||
## 📚 Table of Contents
|
||||
|
||||
1. [Quick Start](#1-quick-start)
|
||||
2. [Core Concepts & SDK Details](#2-core-concepts--sdk-details)
|
||||
3. [Deep Dive into Plugin Types](#3-deep-dive-into-plugin-types)
|
||||
* [Action](#31-action)
|
||||
* [Filter](#32-filter)
|
||||
* [Pipe](#33-pipe)
|
||||
4. [Advanced Development Patterns](#4-advanced-development-patterns)
|
||||
5. [Best Practices & Design Principles](#5-best-practices--design-principles)
|
||||
6. [Troubleshooting](#6-troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## 1. Quick Start
|
||||
|
||||
### 1.1 What are OpenWebUI Plugins?
|
||||
|
||||
OpenWebUI Plugins (officially called "Functions") are the primary way to extend the platform's capabilities. Running in a backend Python environment, they allow you to:
|
||||
* 🔌 **Integrate New Models**: Connect to Claude, Gemini, or custom RAGs via Pipes.
|
||||
* 🎨 **Enhance Interaction**: Add buttons (e.g., "Export", "Generate Chart") next to messages via Actions.
|
||||
* 🔧 **Intervene in Processes**: Modify data before requests or after responses (e.g., inject context, filter sensitive words) via Filters.
|
||||
|
||||
### 1.2 Your First Plugin (Hello World)
|
||||
|
||||
Save the following code as `hello.py` and upload it to the **Functions** panel in OpenWebUI:
|
||||
|
||||
```python
|
||||
"""
|
||||
title: Hello World Action
|
||||
author: Demo
|
||||
version: 1.0.0
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
class Action:
|
||||
class Valves(BaseModel):
|
||||
greeting: str = Field(default="Hello", description="Greeting message")
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
async def action(
|
||||
self,
|
||||
body: dict,
|
||||
__event_emitter__=None,
|
||||
__user__=None
|
||||
) -> Optional[dict]:
|
||||
user_name = __user__.get("name", "Friend") if __user__ else "Friend"
|
||||
|
||||
if __event_emitter__:
|
||||
await __event_emitter__({
|
||||
"type": "notification",
|
||||
"data": {"type": "success", "content": f"{self.valves.greeting}, {user_name}!"}
|
||||
})
|
||||
return body
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Core Concepts & SDK Details
|
||||
|
||||
### 2.1 ⚠️ Important: Sync vs Async
|
||||
|
||||
OpenWebUI plugins run within an `asyncio` event loop.
|
||||
* **Principle**: All I/O operations (database, file, network) must be non-blocking.
|
||||
* **Pitfall**: Calling synchronous methods directly (e.g., `time.sleep`, `requests.get`) will freeze the entire server.
|
||||
* **Solution**: Wrap synchronous calls using `await asyncio.to_thread(sync_func, ...)`.
|
||||
|
||||
### 2.2 Core Parameters
|
||||
|
||||
All plugin methods (`inlet`, `outlet`, `pipe`, `action`) support injecting the following special parameters:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `body` | `dict` | **Core Data**. Contains request info like `messages`, `model`, `stream`. |
|
||||
| `__user__` | `dict` | **Current User**. Contains `id`, `name`, `role`, `valves` (user config), etc. |
|
||||
| `__metadata__` | `dict` | **Metadata**. Contains `chat_id`, `message_id`. The `variables` field holds preset variables like `{{USER_NAME}}`, `{{CURRENT_TIME}}`. |
|
||||
| `__request__` | `Request` | **FastAPI Request Object**. Access `app.state` for cross-plugin communication. |
|
||||
| `__event_emitter__` | `func` | **One-way Notification**. Used to send Toast notifications or status bar updates. |
|
||||
| `__event_call__` | `func` | **Two-way Interaction**. Used to execute JS code, show confirmation dialogs, or input boxes on the frontend. |
|
||||
|
||||
### 2.3 Configuration System (Valves)
|
||||
|
||||
* **`Valves`**: Global admin configuration.
|
||||
* **`UserValves`**: User-level configuration (higher priority, overrides global).
|
||||
|
||||
```python
|
||||
class Filter:
|
||||
class Valves(BaseModel):
|
||||
API_KEY: str = Field(default="", description="Global API Key")
|
||||
|
||||
class UserValves(BaseModel):
|
||||
API_KEY: str = Field(default="", description="User Private API Key")
|
||||
|
||||
def inlet(self, body, __user__):
|
||||
# Prioritize user's Key
|
||||
user_valves = __user__.get("valves", self.UserValves())
|
||||
api_key = user_valves.API_KEY or self.valves.API_KEY
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Deep Dive into Plugin Types
|
||||
|
||||
### 3.1 Action
|
||||
|
||||
**Role**: Adds buttons below messages that trigger upon user click.
|
||||
|
||||
**Advanced Usage: Execute JavaScript on Frontend (File Download Example)**
|
||||
|
||||
```python
|
||||
import base64
|
||||
|
||||
async def action(self, body, __event_call__):
|
||||
# 1. Generate content on backend
|
||||
content = "Hello OpenWebUI".encode()
|
||||
b64 = base64.b64encode(content).decode()
|
||||
|
||||
# 2. Send JS to frontend for execution
|
||||
js = f"""
|
||||
const blob = new Blob([atob('{b64}')], {{type: 'text/plain'}});
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = 'hello.txt';
|
||||
a.click();
|
||||
"""
|
||||
await __event_call__({"type": "execute", "data": {"code": js}})
|
||||
```
|
||||
|
||||
### 3.2 Filter
|
||||
|
||||
**Role**: Middleware that intercepts and modifies requests/responses.
|
||||
|
||||
* **`inlet`**: Before request. Used for injecting context, modifying model parameters.
|
||||
* **`outlet`**: After response. Used for formatting output, logging.
|
||||
* **`stream`**: During streaming. Used for real-time sensitive word filtering.
|
||||
|
||||
**Example: Injecting Environment Variables**
|
||||
|
||||
```python
|
||||
async def inlet(self, body, __metadata__):
|
||||
vars = __metadata__.get("variables", {})
|
||||
context = f"Current Time: {vars.get('{{CURRENT_DATETIME}}')}"
|
||||
|
||||
# Inject into System Prompt or first message
|
||||
if body.get("messages"):
|
||||
body["messages"][0]["content"] += f"\n\n{context}"
|
||||
return body
|
||||
```
|
||||
|
||||
### 3.3 Pipe
|
||||
|
||||
**Role**: Custom Model/Agent.
|
||||
|
||||
**Example: Simple OpenAI Wrapper**
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
class Pipe:
|
||||
def pipes(self):
|
||||
return [{"id": "my-gpt", "name": "My GPT Wrapper"}]
|
||||
|
||||
def pipe(self, body):
|
||||
# Modify body here, e.g., force add prompt
|
||||
headers = {"Authorization": f"Bearer {self.valves.API_KEY}"}
|
||||
r = requests.post("https://api.openai.com/v1/chat/completions", json=body, headers=headers, stream=True)
|
||||
return r.iter_lines()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Advanced Development Patterns
|
||||
|
||||
### 4.1 Pipe & Filter Collaboration
|
||||
Use `__request__.app.state` to share data between plugins.
|
||||
* **Pipe**: `__request__.app.state.search_results = [...]`
|
||||
* **Filter (Outlet)**: Read `search_results` and format them as citation links appended to the response.
|
||||
|
||||
### 4.2 Async Background Tasks
|
||||
Execute time-consuming operations (e.g., summarization, database storage) in the background without blocking the user response.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
async def outlet(self, body, __metadata__):
|
||||
asyncio.create_task(self.background_job(__metadata__["chat_id"]))
|
||||
return body
|
||||
|
||||
async def background_job(self, chat_id):
|
||||
# Execute time-consuming operation...
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Best Practices & Design Principles
|
||||
|
||||
### 5.1 Naming & Positioning
|
||||
* **Short & Punchy**: e.g., "FlashCard", "DeepRead". Avoid generic terms like "Text Analysis Assistant".
|
||||
* **Complementary**: Don't reinvent the wheel; clarify what specific problem your plugin solves.
|
||||
|
||||
### 5.2 User Experience (UX)
|
||||
* **Timely Feedback**: Send a `notification` ("Generating...") before time-consuming operations.
|
||||
* **Visual Appeal**: When Action outputs HTML, use modern CSS (rounded corners, shadows, gradients).
|
||||
* **Smart Guidance**: If text is too short, prompt the user: "Suggest entering more content for better results".
|
||||
|
||||
### 5.3 Error Handling
|
||||
Never let a plugin fail silently. Catch exceptions and inform the user via `__event_emitter__`.
|
||||
|
||||
```python
|
||||
try:
|
||||
# Business logic
|
||||
except Exception as e:
|
||||
await __event_emitter__({
|
||||
"type": "notification",
|
||||
"data": {"type": "error", "content": f"Processing failed: {str(e)}"}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Troubleshooting
|
||||
|
||||
* **HTML not showing?** Ensure it's wrapped in a ` ```html ... ``` ` code block.
|
||||
* **Database error?** Check if you called synchronous DB methods directly in an `async` function; use `asyncio.to_thread`.
|
||||
* **Parameters not working?** Check if `Valves` are defined correctly and if they are being overridden by `UserValves`.
|
||||
117
docs/examples/action_plugin_export_to_excel_example_cn.md
Normal file
117
docs/examples/action_plugin_export_to_excel_example_cn.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# `Export to Excel` 插件深度解析:文件生成与下载实战
|
||||
|
||||
## 引言
|
||||
|
||||
`Export to Excel` 是一个非常实用的 `Action` 插件,它能智能地从 AI 的回答中提取 Markdown 表格,将其转换为格式精美的 Excel 文件,并直接在用户的浏览器中触发下载。
|
||||
|
||||
这个插件是一个绝佳的实战案例,它完整地展示了如何实现一个“数据处理 -> 文件生成 -> 前端交互”的闭环。通过解析它,开发者可以学习到如何在 Open WebUI 插件中利用强大的 Python 数据科学生态(如 `pandas`),以及如何实现将后端生成的文件无缝传递给用户。
|
||||
|
||||
## 核心工作流
|
||||
|
||||
该插件的工作流程清晰而高效,可以概括为以下六个步骤:
|
||||
|
||||
1. **解析 (Parse)**: 使用正则表达式从最后一条聊天消息中精准地提取一个或多个 Markdown 表格。
|
||||
2. **分析 (Analyze)**: 智能地查找表格上下文中的 Markdown 标题(`#`, `##` 等),并以此为依据生成有意义的 Excel 工作簿及工作表(Sheet)的名称。
|
||||
3. **生成 (Generate)**: 将解析出的表格数据转换为 `pandas.DataFrame` 对象,这是进行后续处理的基础。
|
||||
4. **格式化与保存 (Format & Save)**: 利用 `pandas` 和 `XlsxWriter` 引擎,在服务器的临时目录中创建一个带有自定义样式(如颜色、对齐、自动列宽)的、符合专业规范的 `.xlsx` 文件。
|
||||
5. **传输与下载 (Transfer & Download)**: 将生成的 Excel 文件内容读取为字节流,进行 Base64 编码,然后通过 `__event_call__` 将编码后的字符串和一段 JavaScript 代码发送到前端。JS 代码在浏览器中解码数据、创建 Blob 对象并触发下载。
|
||||
6. **清理 (Cleanup)**: 下载触发后,立即删除服务器上的临时 Excel 文件,确保不占用服务器资源。
|
||||
|
||||
---
|
||||
|
||||
## 关键开发模式与技术剖析
|
||||
|
||||
### 1. 纯 Python 数据处理生态的威力
|
||||
|
||||
与一些需要深度集成 Open WebUI 后端模型的插件不同,`Export to Excel` 的核心功能完全由通用的 Python 库驱动,这展示了 Open WebUI 插件生态的开放性。
|
||||
|
||||
- **`re` (正则表达式)**: 用于从纯文本消息中稳健地解析出结构化的表格数据。
|
||||
- **`pandas`**: Python 数据分析的事实标准。插件用它来将原始的列表数据转换为强大的 DataFrame,为写入 Excel 提供了极大的便利。
|
||||
- **`xlsxwriter`**: 一个与 `pandas` 无缝集成的库,用于创建具有丰富格式的 Excel 文件,远比 `pandas` 默认的引擎功能更强大。
|
||||
|
||||
**启示**: 开发者可以将庞大而成熟的 Python 第三方库生态无缝地引入到 Open WebUI 插件中,以实现各种复杂的功能。
|
||||
|
||||
### 2. 智能文本上下文分析
|
||||
|
||||
一个优秀的插件不仅应完成任务,还应尽可能“智能”地理解用户意图。该插件的 `generate_names_from_content` 方法就是一个很好的例子。
|
||||
|
||||
- **目标**: 避免生成如 `output.xlsx` 或 `Sheet1` 这样无意义的文件/工作表名。
|
||||
- **实现**:
|
||||
1. 首先,遍历消息内容,找出所有的 Markdown 标题(`#` 到 `######`)及其所在的行号。
|
||||
2. 对于每一个提取出的表格,在所有位于其上方的标题中,选择**行号最大**(即距离最近)的一个作为该表格的名称。
|
||||
3. 如果只有一个表格,则直接使用其名称作为工作簿的名称。
|
||||
4. 如果有多个表格,则使用整篇消息中的**第一个标题**作为工作簿的名称。
|
||||
5. 如果找不到任何标题,则优雅地回退到默认命名方案(如 `用户_20231026.xlsx`)。
|
||||
|
||||
**启示**: 通过对上下文(而不只是目标数据本身)的简单分析,可以极大地提升插件的用户体验。
|
||||
|
||||
### 3. 高质量文件生成 (`pandas` + `xlsxwriter`)
|
||||
|
||||
简单地调用 `df.to_excel()` 只能生成一个“能用”的文件。而此插件通过 `apply_chinese_standard_formatting` 方法展示了如何生成一个“专业”的文件。
|
||||
|
||||
- **引擎选择**: `pd.ExcelWriter(file_path, engine="xlsxwriter")` 是关键,它允许我们访问底层的 `workbook` 和 `worksheet` 对象。
|
||||
- **核心格式化技术**:
|
||||
- **自定义单元格样式**: 通过 `workbook.add_format()` 创建多种样式(如表头、文本、数字、日期),并分别定义字体、颜色、边框、对齐方式等。
|
||||
- **智能内容对齐**: 遵循标准的制表规范,实现了“文本左对齐、数值右对齐、标题/日期/序号居中对齐”。
|
||||
- **中文字符感知列宽**: `calculate_text_width` 方法在计算内容宽度时,将中文字符(及标点)的宽度视为英文字符的两倍,确保了自动调整列宽 (`worksheet.set_column`) 对中文内容同样有效,避免了文字溢出。
|
||||
- **动态行高**: `calculate_text_height` 方法会根据单元格内容的换行符和折行情况计算所需行数,并以此为依据设置行高 (`worksheet.set_row`),确保了包含长文本的单元格也能完整显示。
|
||||
|
||||
**启示**: 魔鬼在细节中。对生成文件的精细格式化是区分“玩具”和“工具”的重要标准。
|
||||
|
||||
### 4. 后端文件生成与下载的标准模式
|
||||
|
||||
如何在 `Action` 插件中安全、高效地让用户下载后端生成的文件?`export_to_excel` 展示了目前**最佳的、也是标准的实现模式**。
|
||||
|
||||
**流程详解**:
|
||||
|
||||
1. **在服务器临时位置创建文件**:
|
||||
```python
|
||||
filename = f"{workbook_name}.xlsx"
|
||||
excel_file_path = os.path.join("app", "backend", "data", "temp", filename)
|
||||
# ... 使用 pandas 保存文件到 excel_file_path ...
|
||||
```
|
||||
2. **将文件读入内存并编码**:
|
||||
```python
|
||||
with open(excel_file_path, "rb") as file:
|
||||
file_content = file.read()
|
||||
base64_blob = base64.b64encode(file_content).decode("utf-8")
|
||||
```
|
||||
3. **通过 `__event_call__` 发送数据和下载指令**:
|
||||
- 将 Base64 字符串和文件名嵌入一段预设的 JavaScript 代码中。
|
||||
- 这段 JS 的作用是在浏览器端解码 Base64、创建文件 Blob、生成一个隐藏的下载链接 (`<a>` 标签),然后模拟用户点击该链接。
|
||||
|
||||
```python
|
||||
js_code = f"""
|
||||
const base64Data = "{base64_blob}";
|
||||
// ... JS 解码并创建下载链接的代码 ...
|
||||
a.download = "{filename}";
|
||||
a.click();
|
||||
"""
|
||||
await __event_call__({"type": "execute", "data": {"code": js_code}})
|
||||
```
|
||||
4. **立即清理临时文件**:
|
||||
```python
|
||||
if os.path.exists(excel_file_path):
|
||||
os.remove(excel_file_path)
|
||||
```
|
||||
|
||||
**模式优势**:
|
||||
- **安全**: 不会暴露服务器的任何文件路径或创建公共的下载 URL。
|
||||
- **无状态**: 服务器上不保留任何用户生成的文件,请求结束后立即清理,节约了存储空间。
|
||||
- **体验好**: 对用户来说,点击按钮后直接弹出浏览器下载框,体验非常流畅。
|
||||
|
||||
### 5. 优雅的错误处理
|
||||
|
||||
插件的 `action` 方法被一个完整的 `try...except` 块包裹。
|
||||
- 当 `extract_tables_from_message` 找不到表格时,它会主动抛出 `HTTPException`。
|
||||
- 在 `except` 块中,插件会通过 `__event_emitter__` 向前端发送一个内容为“没有找到可以导出的表格!”的错误通知 (`notification`),并更新状态栏 (`status`),清晰地告知用户发生了什么。
|
||||
|
||||
**启示**: 任何可能失败的操作都应被捕获,并向用户提供清晰、友好的错误反馈。
|
||||
|
||||
## 总结
|
||||
|
||||
`Export to Excel` 插件是一个将数据处理与前端交互完美结合的典范。通过学习它,我们可以掌握:
|
||||
- 如何利用 `pandas` 和 `xlsxwriter` 等库在后端生成专业品质的二进制文件。
|
||||
- 如何通过 `__event_call__` 这一强大的机制,实现从后端到前端的文件传输和下载触发。
|
||||
- 服务器临时文件的创建、使用和清理这一完整的、安全的生命周期管理模式。
|
||||
- 如何通过解析上下文来提升插件的“智能化”和用户体验。
|
||||
291
docs/examples/action_plugin_smart_mind_map_example_cn.md
Normal file
291
docs/examples/action_plugin_smart_mind_map_example_cn.md
Normal file
@@ -0,0 +1,291 @@
|
||||
# Open WebUI Action 插件开发范例:智绘心图
|
||||
|
||||
## 引言
|
||||
|
||||
“智绘心图” (`smart-mind-map`) 是一个功能强大的 Open WebUI Action 插件。它通过分析用户提供的文本,利用大语言模型(LLM)提取关键信息,并最终生成一个可交互的、可视化的思维导图。本文档将深入解析其源码 (`思维导图.py`),提炼其中蕴含的插件开发知识与最佳实践,为开发者提供一个高质量的参考范例。
|
||||
|
||||
## 核心开发知识点
|
||||
|
||||
- **插件元数据定义**: 如何通过文件头注释定义插件的标题、图标、版本和描述。
|
||||
- **可配置参数 (`Valves`)**: 如何为插件提供灵活的配置选项。
|
||||
- **异步 `action` 方法**: 插件主入口的实现方式及其核心参数的使用。
|
||||
- **实时前端交互 (`EventEmitter`)**: 如何向用户发送实时状态更新和通知。
|
||||
- **与 LLM 交互**: 如何构建动态 Prompt、调用内置 LLM 服务并处理返回结果。
|
||||
- **富文本 (HTML/JS) 输出**: 如何生成包含复杂前端逻辑的 HTML 内容,并将其嵌入聊天响应中。
|
||||
- **健壮性设计**: 如何实现输入验证、全面的错误处理和日志记录。
|
||||
- **访问 Open WebUI 核心模型**: 如何与 Open WebUI 的数据模型(如 `Users`)交互。
|
||||
|
||||
---
|
||||
|
||||
### 1. 插件元数据定义
|
||||
|
||||
Open WebUI 通过文件顶部的特定格式注释来识别和展示插件信息。
|
||||
|
||||
**代码示例 (`思维导图.py`):**
|
||||
```python
|
||||
"""
|
||||
title: 智绘心图
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+CiAgPGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMyIgZmlsbD0iY3VycmVudENvbG9yIi8+CiAgPGxpbmUgeDE9IjEyIiB5MT0iOSIgeDI9IjEyIiB5Mj0iNCIvPgogIDxjaXJjbGUgY3g9IjEyIiBjeT0iMyIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjEyIiB5MT0iMTUiIHgyPSIxMiIgeTI9IjIwIi8+CiAgPGNpcmNsZSBjeD0iMTIiIGN5PSIyMSIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjkiIHkxPSIxMiIgeDI9IjQiIHkyPSIxMiIvPgogIDxjaXJjbGUgY3g9IjMiIGN5PSIxMiIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjE1IiB5MT0iMTIiIHgyPSIyMCIgeTI9IjEyIi8+CiAgPGNpcmNsZSBjeD0iMjEiIGN5PSIxMiIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjEwLjUiIHkxPSྡ1LjUiIHgyPSI2IiB5Mj0iNiIvPgogIDxjaXJjbGUgY3g9IjUiIGN5PSI1Iigcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjEzLjUiIHkxPSྡ5LjUgeDI9IjE1IiB5Mj0iNiIvPgogIDxjaXJjbGUgY3g9IjE5IiBjeT0iNSIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9ྡ1LjUgeTE9ྡ3MuNSB4Mj0iNiIgeTI9IjE4Ii8+CiAgPGNpcmNsZSBjeD0iNSIgY3k9IjE5IiByPSྡ1LjUiLz4KICA8bGluZSB4MT0ྡzIuNSB5MT0ྡzIuNSB4Mj0iNSIgeTI9IjE4Ii8+CiAgPGNpcmNsZSBjeD0ྡ5IiBjeT0ྡ5IiByPSྡ1LjUiLz4KPC9zdmc+Cg==
|
||||
version: 0.7.2
|
||||
description: 智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。
|
||||
"""
|
||||
```
|
||||
**知识点**:
|
||||
- `title`: 插件在 UI 中显示的名称。
|
||||
- `icon_url`: 插件的图标,支持 base64 编码的 SVG,以实现无依赖的矢量图标。
|
||||
- `version`: 插件的版本号。
|
||||
- `description`: 插件的功能简介。
|
||||
|
||||
---
|
||||
|
||||
### 2. 可配置参数 (`Valves`)
|
||||
|
||||
通过在 `Action` 类内部定义一个 `Valves` Pydantic 模型,可以为插件创建可在 Web UI 中配置的参数。
|
||||
|
||||
**代码示例 (`思维导图.py`):**
|
||||
```python
|
||||
class Action:
|
||||
class Valves(BaseModel):
|
||||
show_status: bool = Field(
|
||||
default=True, description="是否在聊天界面显示操作状态更新。"
|
||||
)
|
||||
LLM_MODEL_ID: str = Field(
|
||||
default="gemini-2.5-flash",
|
||||
description="用于文本分析的内置LLM模型ID。",
|
||||
)
|
||||
MIN_TEXT_LENGTH: int = Field(
|
||||
default=100, description="进行思维导图分析所需的最小文本长度(字符数)。"
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
```
|
||||
**知识点**:
|
||||
- `Valves` 类继承自 `pydantic.BaseModel`。
|
||||
- 每个字段都是一个配置项,`default` 是默认值,`description` 会在 UI 中作为提示信息显示。
|
||||
- 在 `__init__` 中实例化 `self.valves`,之后可以通过 `self.valves.PARAMETER_NAME` 来访问配置值。
|
||||
|
||||
---
|
||||
|
||||
### 3. 异步 `action` 方法
|
||||
|
||||
`action` 方法是插件的执行入口,它是一个异步函数,接收 Open WebUI 传入的上下文信息。
|
||||
|
||||
**代码示例 (`思维导图.py`):**
|
||||
```python
|
||||
async def action(
|
||||
self,
|
||||
body: dict,
|
||||
__user__: Optional[Dict[str, Any]] = None,
|
||||
__event_emitter__: Optional[Any] = None,
|
||||
__request__: Optional[Request] = None,
|
||||
) -> Optional[dict]:
|
||||
# ... 插件逻辑 ...
|
||||
return body
|
||||
```
|
||||
**知识点**:
|
||||
- `body`: 包含当前聊天上下文的字典,最重要的是 `body.get("messages")`,它包含了完整的消息历史。
|
||||
- `__user__`: 包含当前用户信息的字典,如 `id`, `name`, `language` 等。插件中演示了如何兼容其为 `dict` 或 `list` 的情况。
|
||||
- `__event_emitter__`: 一个可调用的异步函数,用于向前端发送事件,是实现实时反馈的关键。
|
||||
- `__request__`: FastAPI 的 `Request` 对象,用于访问底层请求信息,例如在调用 `generate_chat_completion` 时需要传递。
|
||||
- **返回值**: `action` 方法需要返回修改后的 `body` 字典,其中包含了插件生成的响应。
|
||||
|
||||
---
|
||||
|
||||
### 4. 实时前端交互 (`EventEmitter`)
|
||||
|
||||
使用 `__event_emitter__` 可以极大地提升用户体验,让用户了解插件的执行进度。
|
||||
|
||||
**代码示例 (`思维导图.py`):**
|
||||
```python
|
||||
# 发送通知 (Toast)
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {
|
||||
"type": "info", # 'info', 'success', 'warning', 'error'
|
||||
"content": "智绘心图已启动,正在为您生成思维导图...",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# 发送状态更新 (Status Bar)
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status",
|
||||
"data": {
|
||||
"description": "智绘心图: 深入分析文本结构...",
|
||||
"done": False, # False 表示进行中
|
||||
"hidden": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# 任务完成
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status",
|
||||
"data": {
|
||||
"description": "智绘心图: 绘制完成!",
|
||||
"done": True, # True 表示已完成
|
||||
"hidden": False, # True 可以让成功状态自动隐藏
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
**知识点**:
|
||||
- **通知 (`notification`)**: 在屏幕角落弹出短暂的提示信息,适合用于触发、成功或失败的即时反馈。
|
||||
- **状态 (`status`)**: 在聊天输入框上方显示一个持久的状态条,适合展示多步骤任务的当前进度。`done: True` 会标记任务完成。
|
||||
|
||||
---
|
||||
|
||||
### 5. 与 LLM 交互
|
||||
|
||||
插件的核心功能通常依赖于 LLM。`智绘心图` 演示了如何构建一个结构化的 Prompt 并调用 LLM。
|
||||
|
||||
**代码示例 (`思维导图.py`):**
|
||||
```python
|
||||
# 1. 构建动态 Prompt
|
||||
SYSTEM_PROMPT_MINDMAP_ASSISTANT = "..." # 系统指令
|
||||
USER_PROMPT_GENERATE_MINDMAP = "..." # 用户指令模板
|
||||
|
||||
formatted_user_prompt = USER_PROMPT_GENERATE_MINDMAP.format(
|
||||
user_name=user_name,
|
||||
# ... 注入其他上下文信息
|
||||
long_text_content=long_text_content,
|
||||
)
|
||||
|
||||
# 2. 准备 LLM 请求体
|
||||
llm_payload = {
|
||||
"model": self.valves.LLM_MODEL_ID,
|
||||
"messages": [
|
||||
{"role": "system", "content": SYSTEM_PROMPT_MINDMAP_ASSISTANT},
|
||||
{"role": "user", "content": formatted_user_prompt},
|
||||
],
|
||||
"temperature": 0.5,
|
||||
"stream": False,
|
||||
}
|
||||
|
||||
# 3. 获取用户对象并调用 LLM
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
from open_webui.models.users import Users
|
||||
|
||||
user_obj = Users.get_user_by_id(user_id)
|
||||
llm_response = await generate_chat_completion(
|
||||
__request__, llm_payload, user_obj
|
||||
)
|
||||
|
||||
# 4. 处理响应
|
||||
assistant_response_content = llm_response["choices"][0]["message"]["content"]
|
||||
markdown_syntax = self._extract_markdown_syntax(assistant_response_content)
|
||||
```
|
||||
**知识点**:
|
||||
- **Prompt 工程**: 将系统指令和用户指令分离。在用户指令中动态注入上下文信息(如用户名、时间、语言),可以使 LLM 的输出更具个性化和准确性。
|
||||
- **调用工具**: 使用 `open_webui.utils.chat.generate_chat_completion` 是与 Open WebUI 内置 LLM 服务交互的标准方式。
|
||||
- **用户上下文**: 调用 `generate_chat_completion` 需要传递 `user_obj`,这可能用于权限控制、计费或模型特定的用户标识。通过 `open_webui.models.users.Users.get_user_by_id` 获取该对象。
|
||||
- **响应解析**: LLM 的响应需要被解析。该插件使用正则表达式从返回的文本中提取核心的 Markdown 内容,并提供了回退机制。
|
||||
|
||||
---
|
||||
|
||||
### 6. 富文本 (HTML/JS) 输出
|
||||
|
||||
Action 插件的一大亮点是能够生成 HTML,从而在聊天界面中渲染丰富的交互式内容。
|
||||
|
||||
**代码示例 (`思维导图.py`):**
|
||||
```python
|
||||
# 1. 定义 HTML 模板
|
||||
HTML_TEMPLATE_MINDMAP = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- 引入 Markmap.js 等外部库 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/markmap-view@0.17"></script>
|
||||
<style>...</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 动态内容占位符 -->
|
||||
<div id="markmap-container-{unique_id}"></div>
|
||||
<script type="text/template" id="markdown-source-{unique_id}">{markdown_syntax}</script>
|
||||
<script>
|
||||
// 嵌入的 JavaScript 逻辑
|
||||
(function() {
|
||||
const uniqueId = "{unique_id}";
|
||||
// ... 渲染逻辑、事件处理 ...
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# 2. 注入动态内容
|
||||
final_html_content =
|
||||
HTML_TEMPLATE_MINDMAP.replace("{unique_id}", unique_id)
|
||||
.replace("{markdown_syntax}", markdown_syntax)
|
||||
# ... 替换其他占位符
|
||||
|
||||
# 3. 嵌入到聊天响应中
|
||||
html_embed_tag = f"```html\n{final_html_content}\n```"
|
||||
body["messages"][-1]["content"] = f"{long_text_content}\n\n{html_embed_tag}"
|
||||
```
|
||||
**知识点**:
|
||||
- **HTML 模板**: 将静态 HTML/CSS/JS 代码定义为模板字符串,使用占位符(如 `{unique_id}`)来注入动态数据。
|
||||
- **嵌入 JS**: 可以在 HTML 中直接嵌入 JavaScript 代码,用于处理前端交互逻辑,如渲染图表、绑定按钮事件等。`智绘心图` 的 JS 代码负责调用 Markmap.js 库来渲染思维导图,并实现了“复制 SVG”和“复制 Markdown”的按钮功能。
|
||||
- **唯一 ID**: 使用 `unique_id` 是一个好习惯,可以防止在同一页面上多次使用该插件时发生 DOM 元素 ID 冲突。
|
||||
- **响应格式**: 最终的 HTML 内容需要被包裹在 ````html\n...\n```` 代码块中,Open WebUI 的前端会自动识别并渲染它。
|
||||
- **内容追加**: 插件将生成的 HTML 追加到原始用户输入之后,而不是替换它,保留了上下文。
|
||||
|
||||
---
|
||||
|
||||
### 7. 健壮性设计
|
||||
|
||||
一个生产级的插件必须具备良好的健壮性。
|
||||
|
||||
**代码示例 (`思维导图.py`):**
|
||||
```python
|
||||
# 输入验证
|
||||
if len(long_text_content) < self.valves.MIN_TEXT_LENGTH:
|
||||
# ... 返回警告信息 ...
|
||||
return {"messages": [...]}
|
||||
|
||||
# 完整的异常捕获
|
||||
try:
|
||||
# ... 核心逻辑 ...
|
||||
except Exception as e:
|
||||
error_message = f"智绘心图处理失败: {str(e)}"
|
||||
logger.error(f"智绘心图错误: {error_message}", exc_info=True)
|
||||
|
||||
# 向前端发送错误通知
|
||||
if __event_emitter__:
|
||||
await __event_emitter__(...)
|
||||
|
||||
# 在聊天中显示错误信息
|
||||
body["messages"][-1]["content"] = f"❌ **错误:** {user_facing_error}"
|
||||
|
||||
# 日志记录
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info("Action started")
|
||||
logger.error("Error occurred", exc_info=True)
|
||||
```
|
||||
**知识点**:
|
||||
- **输入验证**: 在执行核心逻辑前,对输入(如文本长度)进行检查,可以避免不必要的资源消耗和潜在错误。
|
||||
- **`try...except` 块**: 将主要逻辑包裹在 `try` 块中,并捕获 `Exception`,确保任何意外失败都能被优雅地处理。
|
||||
- **用户友好的错误反馈**: 在 `except` 块中,不仅要记录详细的错误日志(`logger.error`),还要通过 `EventEmitter` 和聊天消息向用户提供清晰、可操作的错误提示。
|
||||
- **日志**: 使用 `logging` 模块记录关键步骤和错误信息,是调试和监控插件运行状态的重要手段。`exc_info=True` 会记录完整的堆栈跟踪。
|
||||
|
||||
---
|
||||
|
||||
### 总结
|
||||
|
||||
`智绘心图` 插件是一个优秀的 Open WebUI Action 开发学习案例。它全面展示了如何利用 Action 插件的各项功能,构建一个交互性强、用户体验好、功能完整且健壮的 AI 应用。
|
||||
|
||||
**最佳实践总结**:
|
||||
- **明确元数据**: 为你的插件提供清晰的 `title`, `icon`, `description`。
|
||||
- **提供配置**: 使用 `Valves` 让插件更灵活。
|
||||
- **善用反馈**: 积极使用 `EventEmitter` 提供实时状态和通知。
|
||||
- **结构化 Prompt**: 精心设计的 Prompt 是高质量输出的保证。
|
||||
- **拥抱富文本**: 利用 HTML 和 JS 创造丰富的交互体验。
|
||||
- **防御性编程**: 始终考虑输入验证和错误处理。
|
||||
- **详细日志**: 记录日志是排查问题的关键。
|
||||
|
||||
通过学习和借鉴`智绘心图`的设计模式,开发者可以更高效地构建出属于自己的高质量 Open WebUI 插件。
|
||||
@@ -0,0 +1,235 @@
|
||||
# Open WebUI Filter 插件开发范例:异步上下文压缩
|
||||
|
||||
## 引言
|
||||
|
||||
“异步上下文压缩” (`async-context-compression`) 是一个功能先进的 Open WebUI `Filter` 插件。它旨在通过在后台异步地对长对话历史进行智能摘要,来显著减少发送给大语言模型(LLM)的 Token 数量,从而在节约成本的同时保持对话的连贯性。
|
||||
|
||||
本文档将深入剖析其源码,提炼其作为高级 `Filter` 插件所展示的设计模式与开发技巧,特别是关于**异步处理**、**数据库集成**和**复杂消息流控制**等方面。
|
||||
|
||||
## 核心开发知识点
|
||||
|
||||
- **Filter 插件结构 (`inlet` / `outlet`)**: 掌握过滤器在请求生命周期中的两个核心切入点。
|
||||
- **异步后台任务**: 如何使用 `asyncio.create_task` 执行耗时操作而不阻塞用户响应。
|
||||
- **数据库持久化**: 如何使用 SQLAlchemy 与数据库(PostgreSQL/SQLite)集成,实现数据的持久化存储。
|
||||
- **高级 `Valves` 配置**: 如何使用 Pydantic 的 `@model_validator` 实现复杂的跨字段配置验证。
|
||||
- **复杂消息体处理**: 如何安全地操作和修改包含多模态内容的消息结构。
|
||||
- **从插件内部调用 LLM**: 在插件中调用 LLM 服务以实现“插件调用插件”的元功能。
|
||||
- **环境变量依赖与初始化**: 如何处理对外部环境变量的依赖,并在插件初始化时进行安全配置。
|
||||
|
||||
---
|
||||
|
||||
### 1. Filter 插件结构 (`inlet` / `outlet`)
|
||||
|
||||
`Filter` 插件通过 `inlet` 和 `outlet` 两个方法,在请求发送给 LLM **之前**和 LLM 响应返回 **之后**对消息进行处理。
|
||||
|
||||
- `inlet(self, body: dict, ...)`: 在请求发送前执行。此插件用它来检查是否存在历史摘要,如果存在,则用摘要替换部分历史消息,从而“压缩”上下文。
|
||||
- `outlet(self, body: dict, ...)`: 在收到 LLM 响应后执行。此插件用它来判断对话是否达到了需要生成摘要的长度阈值,如果是,则触发一个后台任务来生成新的摘要,以供**下一次**对话使用。
|
||||
|
||||
这种“读旧,写新”的异步策略是该插件的核心设计。
|
||||
|
||||
**代码示例 (`async_context_compression.py`):**
|
||||
```python
|
||||
class Filter:
|
||||
def inlet(self, body: dict, ...) -> dict:
|
||||
"""
|
||||
在发送到 LLM 之前执行。
|
||||
应用已有的摘要来压缩本次请求的上下文。
|
||||
"""
|
||||
# 1. 从数据库加载已保存的摘要
|
||||
saved_summary = self._load_summary(chat_id, body)
|
||||
|
||||
# 2. 如果摘要存在且消息足够长
|
||||
if saved_summary and len(messages) > total_kept_count:
|
||||
# 3. 替换中间的消息为摘要
|
||||
body["messages"] = compressed_messages
|
||||
|
||||
return body
|
||||
|
||||
async def outlet(self, body: dict, ...) -> dict:
|
||||
"""
|
||||
在 LLM 响应完成后执行。
|
||||
检查是否需要为下一次请求生成新的摘要。
|
||||
"""
|
||||
# 1. 检查消息总数是否达到阈值
|
||||
if len(messages) >= self.valves.compression_threshold:
|
||||
# 2. 创建一个异步后台任务来生成摘要,不阻塞当前响应
|
||||
asyncio.create_task(
|
||||
self._generate_summary_async(...)
|
||||
)
|
||||
|
||||
return body
|
||||
```
|
||||
**知识点**:
|
||||
- `inlet` 和 `outlet` 分别作用于请求流的不同阶段,实现了功能的解耦。
|
||||
- `inlet` 负责**消费**摘要,`outlet` 负责**生产**摘要,两者通过数据库解耦。
|
||||
|
||||
---
|
||||
|
||||
### 2. 异步后台任务
|
||||
|
||||
对于耗时操作(如调用 LLM 生成摘要),为了不让用户等待,必须采用异步后台处理。这是高级插件必备的技巧。
|
||||
|
||||
**代码示例 (`async_context_compression.py`):**
|
||||
```python
|
||||
# 在 outlet 方法中
|
||||
async def outlet(self, ...):
|
||||
if len(messages) >= self.valves.compression_threshold:
|
||||
# 核心:创建一个后台任务,并立即返回,不等待其完成
|
||||
asyncio.create_task(
|
||||
self._generate_summary_async(messages, chat_id, body, __user__)
|
||||
)
|
||||
return body
|
||||
|
||||
# 后台任务的具体实现
|
||||
async def _generate_summary_async(self, ...):
|
||||
"""
|
||||
在后台异步生成摘要。
|
||||
"""
|
||||
try:
|
||||
# 1. 提取需要被摘要的消息
|
||||
messages_to_summarize = ...
|
||||
|
||||
# 2. 将消息格式化为纯文本
|
||||
conversation_text = self._format_messages_for_summary(messages_to_summarize)
|
||||
|
||||
# 3. 调用 LLM 生成摘要
|
||||
summary = await self._call_summary_llm(conversation_text, body, user_data)
|
||||
|
||||
# 4. 将新摘要存入数据库
|
||||
self._save_summary(chat_id, summary, body)
|
||||
except Exception as e:
|
||||
# 错误处理
|
||||
...
|
||||
```
|
||||
**知识点**:
|
||||
- `asyncio.create_task()`: 这是实现“即发即忘”(fire-and-forget)模式的关键。它将一个协程(`_generate_summary_async`)提交到事件循环中运行,而当前函数(`outlet`)可以继续执行并立即返回,从而确保了前端的快速响应。
|
||||
- **健壮性**: 后台任务必须有自己独立的 `try...except` 块,以防止其内部的失败影响到主程序的稳定性。
|
||||
|
||||
---
|
||||
|
||||
### 3. 数据库持久化 (SQLAlchemy)
|
||||
|
||||
为了在不同对话回合乃至服务重启后都能保留摘要,插件集成了数据库。
|
||||
|
||||
**代码示例 (`async_context_compression.py`):**
|
||||
```python
|
||||
# 1. 依赖环境变量
|
||||
database_url = os.getenv("DATABASE_URL")
|
||||
|
||||
# 2. 定义数据模型
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
Base = declarative_base()
|
||||
|
||||
class ChatSummary(Base):
|
||||
__tablename__ = "chat_summary"
|
||||
id = Column(Integer, primary_key=True)
|
||||
chat_id = Column(String(255), unique=True, index=True)
|
||||
summary = Column(Text)
|
||||
# ... 其他字段
|
||||
|
||||
# 3. 初始化数据库连接
|
||||
def _init_database(self):
|
||||
database_url = os.getenv("DATABASE_URL")
|
||||
if not database_url:
|
||||
# ... 错误处理 ...
|
||||
return
|
||||
|
||||
# 根据 URL 前缀选择驱动 (PostgreSQL/SQLite)
|
||||
if database_url.startswith("sqlite"): ...
|
||||
elif database_url.startswith("postgres"): ...
|
||||
|
||||
self._db_engine = create_engine(database_url, ...)
|
||||
self._SessionLocal = sessionmaker(bind=self._db_engine)
|
||||
Base.metadata.create_all(bind=self._db_engine) # 自动建表
|
||||
|
||||
# 4. 封装 CRUD 操作
|
||||
def _save_summary(self, chat_id: str, summary: str, body: dict):
|
||||
session = self._SessionLocal()
|
||||
try:
|
||||
# ... 查询、更新或创建记录 ...
|
||||
session.commit()
|
||||
finally:
|
||||
session.close()
|
||||
```
|
||||
**知识点**:
|
||||
- **配置驱动**: 插件依赖 `DATABASE_URL` 环境变量,并在 `_init_database` 中进行解析,实现了对不同数据库(PostgreSQL, SQLite)的兼容。
|
||||
- **ORM 模型**: 使用 SQLAlchemy 的声明式基类定义 `ChatSummary` 表结构,使数据库操作对象化,更易于维护。
|
||||
- **自动建表**: `Base.metadata.create_all()` 会在插件首次运行时自动检查并创建不存在的表,简化了部署。
|
||||
- **会话管理**: 使用 `sessionmaker` 创建会话,并通过 `try...finally` 确保会话在使用后被正确关闭,这是管理数据库连接的标准实践。
|
||||
|
||||
---
|
||||
|
||||
### 4. 高级 `Valves` 配置
|
||||
|
||||
除了简单的默认值,`Valves` 还可以通过 Pydantic 的验证器实现更复杂的逻辑。
|
||||
|
||||
**代码示例 (`async_context_compression.py`):**
|
||||
```python
|
||||
from pydantic import model_validator
|
||||
|
||||
class Valves(BaseModel):
|
||||
compression_threshold: int = Field(...)
|
||||
keep_first: int = Field(...)
|
||||
keep_last: int = Field(...)
|
||||
# ... 其他配置
|
||||
|
||||
@model_validator(mode="after")
|
||||
def check_thresholds(self) -> "Valves":
|
||||
kept_count = self.keep_first + self.keep_last
|
||||
if self.compression_threshold <= kept_count:
|
||||
raise ValueError(
|
||||
f"compression_threshold ({self.compression_threshold}) 必须大于 "
|
||||
f"keep_first 和 keep_last 的总和。"
|
||||
)
|
||||
return self
|
||||
```
|
||||
**知识点**:
|
||||
- `@model_validator(mode="after")`: 这个装饰器允许你在所有字段都已赋值**之后**,执行一个自定义的验证函数。
|
||||
- **跨字段验证**: 该插件用它来确保 `compression_threshold` 必须大于 `keep_first` 和 `keep_last` 之和,保证了插件逻辑的正确性,避免了无效配置。
|
||||
|
||||
---
|
||||
|
||||
### 5. 复杂消息体处理
|
||||
|
||||
Open WebUI 的消息体 `content` 可能是简单的字符串,也可能是用于多模态的列表。插件必须能稳健地处理这两种情况。
|
||||
|
||||
**代码示例 (`async_context_compression.py`):**
|
||||
```python
|
||||
def _inject_summary_to_first_message(self, message: dict, summary: str) -> dict:
|
||||
content = message.get("content", "")
|
||||
summary_block = f"【历史对话摘要】\n{summary}\n..."
|
||||
|
||||
if isinstance(content, list): # 多模态内容
|
||||
new_content = []
|
||||
summary_inserted = False
|
||||
for part in content:
|
||||
if part.get("type") == "text" and not summary_inserted:
|
||||
# 将摘要追加到第一个文本部分的前面
|
||||
new_content.append({"type": "text", "text": summary_block + part.get("text", "")})
|
||||
summary_inserted = True
|
||||
else:
|
||||
new_content.append(part)
|
||||
message["content"] = new_content
|
||||
elif isinstance(content, str): # 纯文本
|
||||
message["content"] = summary_block + content
|
||||
|
||||
return message
|
||||
```
|
||||
**知识点**:
|
||||
- **类型检查**: 通过 `isinstance(content, list)` 判断消息是否为多模态类型。
|
||||
- **安全注入**: 在处理多模态列表时,代码会遍历所有 `part`,找到第一个文本部分进行注入,同时保持其他部分(如图片)不变。这确保了插件的兼容性和稳定性。
|
||||
|
||||
---
|
||||
|
||||
### 总结
|
||||
|
||||
`异步上下文压缩` 插件是学习如何构建生产级 Open WebUI `Filter` 的绝佳案例。它不仅展示了 `Filter` 的基本用法,更深入地探讨了在 Web 服务中至关重要的**异步处理**和**持久化存储**。
|
||||
|
||||
**高级实践总结**:
|
||||
- **分离读写**: 利用 `inlet` 和 `outlet` 的生命周期,结合数据库,实现异步的“读写分离”模式。
|
||||
- **非阻塞设计**: 通过 `asyncio.create_task` 将耗时操作移出主请求/响应循环,保证用户体验的流畅性。
|
||||
- **外部依赖管理**: 优雅地处理对环境变量和数据库的依赖,并在初始化时提供清晰的日志和错误提示。
|
||||
- **健壮配置**: 利用模型验证器 (`@model_validator`) 防止用户设置出不符合逻辑的参数。
|
||||
- **兼容性处理**: 在操作消息体时,充分考虑多模态等复杂数据结构,确保插件的广泛适用性。
|
||||
|
||||
通过研究此插件,开发者可以掌握构建需要与外部服务(如数据库)交互、执行复杂后台任务的高级 `Filter` 的核心技能。
|
||||
@@ -0,0 +1,163 @@
|
||||
# `Gemini Manifold Companion` 深度解析:高级 `Filter` 与 `Pipe` 协同开发
|
||||
|
||||
## 引言
|
||||
|
||||
`Gemini Manifold Companion` 是一个 `Filter` 插件,但它的设计目标并非独立运作,而是作为 `Gemini Manifold` 这个 `Pipe` 插件的“伴侣”或“增强包”。它通过在请求到达 `Pipe` 之前和响应返回给用户之后进行一系列精巧的操作,解锁了许多 Open WebUI 原生界面不支持的、`Pipe` 专属的强大功能(如 Google Search, Code Execution 等)。
|
||||
|
||||
本文档将深度解析这个“伴侣插件”的设计模式,重点阐述其如何通过**拦截与翻译**、**跨阶段通信**和**异步 I/O** 等高级技巧,实现与 `Pipe` 插件的完美协同。
|
||||
|
||||
## 核心工作流:拦截与翻译 (Hijack and Translate)
|
||||
|
||||
`Companion` 插件的核心价值体现在其 `inlet` 方法中。它像一个智能的“请求路由器”,在不修改 Open WebUI 前端代码的情况下,将前端的通用功能开关“翻译”成 `Pipe` 插件能理解的专属指令。
|
||||
|
||||
**目标**: 拦截前端通用的功能请求(如“网络搜索”),阻止 Open WebUI 的默认行为,并将其转换为 `Pipe` 插件的专属功能标记。
|
||||
|
||||
#### 实现步骤 (`inlet` 方法):
|
||||
|
||||
1. **识别目标 `Pipe`**: 过滤器首先会检查当前请求是否发往它需要辅助的 `gemini_manifold`。如果不是,则直接返回,不做任何操作。这是伴侣插件模式的基础。
|
||||
|
||||
```python
|
||||
# _get_model_name 会判断当前模型是否由 gemini_manifold 提供
|
||||
canonical_model_name, is_manifold = self._get_model_name(body)
|
||||
if not is_manifold:
|
||||
return body # 不是目标,直接放行
|
||||
```
|
||||
|
||||
2. **拦截功能开关**: 插件检查前端请求的 `body["features"]` 中,`web_search` 是否为 `True`。
|
||||
|
||||
3. **执行“拦截与翻译”**:
|
||||
- **拦截 (Hijack)**: 如果 `web_search` 为 `True`,插件会立即将其改回 `False`。这一步至关重要,它阻止了 Open WebUI 触发其内置的、通用的 RAG 或网页搜索流程。
|
||||
```python
|
||||
features["web_search"] = False
|
||||
```
|
||||
- **翻译 (Translate)**: 紧接着,插件会在一个更深层的、用于插件间通信的 `metadata` 字典中,添加一个**自定义的**、`Pipe` 插件能识别的标志。
|
||||
```python
|
||||
# metadata["features"] 是一个专为插件间通信设计的字典
|
||||
metadata_features["google_search_tool"] = True
|
||||
```
|
||||
|
||||
4. **传递其他指令**: 除了功能开关,`Companion` 还会做一些其他的预处理,例如:
|
||||
- **绕过 RAG**: 如果用户开启了 `BYPASS_BACKEND_RAG`,它会清空 `body["files"]` 数组,并设置 `metadata_features["upload_documents"] = True`,告知 `Pipe` 插件“文件由你来处理”。
|
||||
- **强制流式**: `Pipe` 插件通常返回 `AsyncGenerator`,需要前端以流式模式处理。`Companion` 会强制设置 `body["stream"] = True`,同时将用户原始的流式/非流式选择保存在 `metadata` 中,供 `Pipe` 后续判断。
|
||||
|
||||
**设计模式的价值**: 这种模式实现了极高的解耦。前端只需使用标准的功能开关,而 `Pipe` 插件可以定义任意复杂的、私有的功能集。`Companion` 过滤器则充当了两者之间的智能适配器,使得在不改动核心代码的情况下,扩展后端功能成为可能。
|
||||
|
||||
---
|
||||
|
||||
## 高级技巧 1: `Pipe` -> `Filter` 的跨阶段通信
|
||||
|
||||
**问题**: `Pipe` 在处理过程中生成了重要数据(如包含搜索结果的 `grounding_metadata`),但 `Filter` 的 `outlet` 方法在 `Pipe` 执行**之后**才运行。如何将数据从 `Pipe` 安全地传递给 `Filter`?
|
||||
|
||||
**解决方案**: `request.app.state`,一个在单次 HTTP 请求生命周期内持续存在的共享状态对象。
|
||||
|
||||
#### 实现流程:
|
||||
|
||||
1. **`Pipe` 插件中 (数据写入)**:
|
||||
- 在 `gemini_manifold.py` 的 `_do_post_processing` 阶段(响应流结束后),`Pipe` 会从 Google API 的响应中提取 `grounding_metadata`。
|
||||
- 然后,它使用 `setattr` 将这些数据动态地附加到 `request.app.state` 对象上,使用一个包含 `chat_id` 和 `message_id` 的唯一键。
|
||||
|
||||
```python
|
||||
# 在 gemini_manifold.py 中 (示意代码)
|
||||
def _do_post_processing(self, ..., __request__: Request):
|
||||
app_state = __request__.app.state
|
||||
grounding_key = f"grounding_{chat_id}_{message_id}"
|
||||
|
||||
# 将数据存入请求状态
|
||||
setattr(app_state, grounding_key, grounding_metadata)
|
||||
```
|
||||
|
||||
2. **`Companion Filter` 中 (数据读取)**:
|
||||
- 在 `outlet` 方法中,`Filter` 可以访问同一个 `__request__` 对象。
|
||||
- 它使用 `getattr` 和相同的唯一键,从 `request.app.state` 中安全地取出 `Pipe` 之前存入的数据。
|
||||
|
||||
```python
|
||||
# 在 gemini_manifold_companion.py 的 outlet 方法中
|
||||
async def outlet(self, ..., __request__: Request, ...):
|
||||
app_state = __request__.app.state
|
||||
grounding_key = f"grounding_{chat_id}_{message_id}"
|
||||
|
||||
# 从请求状态中读取数据
|
||||
stored_metadata = getattr(app_state, grounding_key, None)
|
||||
|
||||
if stored_metadata:
|
||||
# 成功获取 Pipe 传来的数据,进行后续处理
|
||||
# (如注入引用标记、解析 URL 等)
|
||||
...
|
||||
|
||||
# 清理状态,避免内存泄漏
|
||||
delattr(app_state, grounding_key)
|
||||
```
|
||||
|
||||
**设计模式的价值**: `request.app.state` 充当了在同一次请求处理链中、不同插件(特别是 `Pipe` 和 `Filter`)之间传递复杂数据的“秘密信道”,是实现高级协同功能的关键。
|
||||
|
||||
---
|
||||
|
||||
## 高级技巧 2: 在 `outlet` 中执行异步 I/O
|
||||
|
||||
**问题**: `grounding_metadata` 中的搜索结果 URL 是 Google 的重定向链接,需要通过网络请求解析成最终的真实网址才能展示给用户。如果在 `outlet` 中同步执行这些请求,会阻塞整个响应流程。
|
||||
|
||||
**解决方案**: 利用 `outlet` 是 `async` 函数的特性,执行并发的异步网络请求。
|
||||
|
||||
#### 实现流程 (`_resolve_and_emit_sources` 方法):
|
||||
|
||||
1. **收集任务**: 从 `grounding_metadata` 中提取所有需要解析的 URL。
|
||||
2. **创建会话**: 使用 `aiohttp.ClientSession` 创建一个异步 HTTP 客户端会话。
|
||||
3. **并发执行**:
|
||||
- 为每个 URL 创建一个 `_resolve_url` 协程任务。
|
||||
- 使用 `asyncio.gather` 并发地执行所有 URL 解析任务。
|
||||
4. **处理结果**: 等待所有解析完成后,将最终的真实 URL 和其他元数据组合成 `sources` 列表。
|
||||
5. **发送事件**: 通过 `__event_emitter__` 将包含最终 `sources` 的 `chat:completion` 事件发送给前端。
|
||||
|
||||
**代码示例 (逻辑简化):**
|
||||
```python
|
||||
async def _resolve_and_emit_sources(self, ...):
|
||||
# ... 提取所有待解析的 URL ...
|
||||
urls_to_resolve = [...]
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# 为每个 URL 创建一个异步解析任务
|
||||
tasks = [self._resolve_url(session, url) for url in urls_to_resolve]
|
||||
# 并发执行所有任务
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
# ... 处理解析结果 ...
|
||||
resolved_sources = [...]
|
||||
|
||||
# 通过事件发射器将最终结果发送给前端
|
||||
await event_emitter({"type": "chat:completion", "data": {"sources": resolved_sources}})
|
||||
```
|
||||
**设计模式的价值**: 即使在请求处理的最后阶段 (`outlet`),也能够执行高效、非阻塞的 I/O 操作,极大地丰富了插件的能力,而不会牺牲用户体验。
|
||||
|
||||
---
|
||||
|
||||
## 高级技巧 3: 动态日志级别
|
||||
|
||||
**问题**: 如何在不重启服务的情况下,动态调整一个插件的日志详细程度,以便于在线上环境中进行调试?
|
||||
|
||||
**解决方案**: 在 `inlet` 中检查配置变化,并动态地添加/移除 `loguru` 的日志处理器 (Handler)。
|
||||
|
||||
#### 实现流程:
|
||||
|
||||
1. **`__init__`**: 插件初始化时,根据 `Valves` 中的 `LOG_LEVEL` 配置,添加一个带特定过滤器(只输出本插件日志)和格式化器的 `loguru` handler。
|
||||
2. **`inlet`**: 在每次请求进入时,都比较当前阀门中的 `LOG_LEVEL` 与插件实例中保存的 `self.log_level` 是否一致。
|
||||
3. **动态更新**:
|
||||
- 如果不一致,说明管理员修改了配置。
|
||||
- 插件会调用 `log.remove()` 移除旧的 handler。
|
||||
- 然后调用 `log.add()`,使用新的日志级别添加一个新的 handler。
|
||||
- 最后更新 `self.log_level`。
|
||||
|
||||
**设计模式的价值**: 这使得插件的日志管理变得极其灵活。管理员只需在 Web UI 中修改插件的 `LOG_LEVEL` 配置,即可立即(在下一次请求时)看到更详细或更简洁的日志输出,极大地提升了生产环境中的问题排查效率。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`Gemini Manifold Companion` 是一个教科书级的“伴侣插件”范例,它揭示了 `Filter` 插件的巨大潜力。通过学习它,我们可以掌握:
|
||||
|
||||
- **协同设计模式**: 如何让 `Filter` 与 `Pipe` 协同工作,以实现标准 UI 之外的复杂功能。
|
||||
- **指令翻译**: 使用 `metadata` 作为 `Filter` 向 `Pipe` 传递“秘密指令”的通信渠道。
|
||||
- **跨阶段状态共享**: 使用 `request.app.state` 作为 `Pipe` 向 `Filter` 回传数据的“临时内存”。
|
||||
- **全异步流程**: 即使在请求的末端 (`outlet`),也能利用 `asyncio` 和 `aiohttp` 执行高效的异步 I/O 操作。
|
||||
- **动态运维能力**: 实现如动态日志级别这样的功能,让插件更易于在生产环境中管理和调试。
|
||||
|
||||
这些高级技巧共同构成了一个强大、解耦且可扩展的插件生态系统,是所有 Open WebUI 插件开发者进阶的必经之路。
|
||||
134
docs/examples/filter_plugin_inject_env_example_cn.md
Normal file
134
docs/examples/filter_plugin_inject_env_example_cn.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# `Inject Env` 插件深度解析:动态修改请求与上下文注入
|
||||
|
||||
## 引言
|
||||
|
||||
`Inject Env` 是一个 `Filter` 插件的绝佳范例,它清晰地展示了过滤器的核心价值:在请求到达 LLM **之前** (`inlet` 阶段) 对其进行拦截和动态修改。
|
||||
|
||||
该插件的核心功能包括:
|
||||
1. 将用户的环境变量(如姓名、当前时间)自动注入到对话的起始位置。
|
||||
2. 根据当前使用的模型和用户信息,智能地开启、关闭或重定向“网络搜索”功能。
|
||||
3. 为特定模型补充必要的 API 参数(如 `chat_id`)。
|
||||
|
||||
通过解析这个插件,开发者可以掌握如何构建一个能够感知上下文(用户、模型、环境变量)并据此动态调整请求内容的智能过滤器。
|
||||
|
||||
---
|
||||
|
||||
## 核心工作流 (`inlet` 方法)
|
||||
|
||||
该插件的所有逻辑都集中在 `inlet` 方法中,其工作流程可以分解为:
|
||||
|
||||
1. **注入上下文**: 从 `__metadata__` 参数中获取用户环境变量,并将其作为一个格式化的 Markdown 块,智能地插入到第一条用户消息的开头。
|
||||
2. **控制功能**: 分析当前请求的模型名称 (`body['model']`) 和用户信息 (`__user__`),应用一系列规则来决定如何处理“网络搜索”功能。
|
||||
3. **补充参数**: 根据模型信息 (`__model__`),为特定的模型(如 `cfchatqwen`)在请求体 `body` 中补充其所需的 `chat_id` 等参数。
|
||||
|
||||
---
|
||||
|
||||
## 关键开发模式与技术剖析
|
||||
|
||||
### 1. 利用 `__metadata__` 和 `__model__` 获取丰富上下文
|
||||
|
||||
`Filter` 插件的 `inlet` 方法可以接收 `__metadata__` 和 `__model__` 这两个非常有用的参数,它们是插件感知上下文、实现智能化逻辑的关键。
|
||||
|
||||
- **`__metadata__["variables"]` (环境变量)**:
|
||||
- **功能**: 这是一个由 Open WebUI 自动填充的、包含当前请求上下文信息的字典。
|
||||
- **内容**: 它预置了一系列模板变量,如:
|
||||
- `{{USER_NAME}}`: 当前用户名
|
||||
- `{{CURRENT_DATETIME}}`: 当前日期时间
|
||||
- `{{CURRENT_WEEKDAY}}`: 当前星期
|
||||
- `{{CURRENT_TIMEZONE}}`: 当前时区
|
||||
- `{{USER_LANGUAGE}}`: 用户的语言设置
|
||||
- **价值**: 这是在插件中获取用户和环境信息的**标准方式**,无需手动计算。`Inject Env` 插件正是利用这个字典来构建注入到消息中的 Markdown 文本。
|
||||
|
||||
- **`__model__` (模型信息)**:
|
||||
- **功能**: 这是一个包含了当前交易所用模型详细信息的字典。
|
||||
- **内容**: 开发者可以从中获取模型的 `id`、`info.base_model_id`(对于自定义模型,指向其基础模型)等。
|
||||
- **价值**: 允许插件根据不同的模型或模型家族(例如,检查 `base_model_id` 是否以 `qwen` 开头)来执行不同的逻辑分支。
|
||||
|
||||
**代码示例:**
|
||||
```python
|
||||
def inlet(
|
||||
self,
|
||||
body: dict,
|
||||
__metadata__: Optional[dict] = None,
|
||||
__model__: Optional[dict] = None,
|
||||
) -> dict:
|
||||
# 从 __metadata__ 获取环境变量
|
||||
variables = __metadata__.get("variables", {})
|
||||
if variables:
|
||||
variable_markdown = f"- **用户姓名**:{variables.get('{{USER_NAME}}', '')}\n"
|
||||
# ... 注入到消息中 ...
|
||||
|
||||
# 从 __model__ 获取模型基础 ID
|
||||
if "openai" in __model__:
|
||||
base_model_id = __model__["openai"]["id"]
|
||||
else:
|
||||
base_model_id = __model__["info"]["base_model_id"]
|
||||
|
||||
if base_model_id.startswith("cfchatqwen"):
|
||||
# ... 执行针对 qwen 模型的特定逻辑 ...
|
||||
```
|
||||
|
||||
### 2. 健壮的消息内容注入
|
||||
|
||||
向用户的消息中动态添加内容时,必须考虑多种情况以确保插件的健壮性。`insert_user_env_info` 函数为此提供了完美的示范。
|
||||
|
||||
- **幂等性注入 (Idempotent Injection)**:
|
||||
- **问题**: 如果每次都简单地在消息前添加内容,当用户连续对话时,环境变量块会被重复注入,造成内容冗余。
|
||||
- **解决方案**: 在注入前,先用正则表达式 `re.search()` 检查消息中是否**已存在**环境变量块。
|
||||
- 如果**存在**,则使用 `re.sub()` 将其**替换**为最新的内容。
|
||||
- 如果**不存在**,才在消息开头**添加**新内容。
|
||||
- **价值**: 保证了无论 `inlet` 被调用多少次,环境变量信息在消息中只会出现一次,并且始终保持最新。
|
||||
|
||||
- **兼容多模态消息**:
|
||||
- **问题**: 用户的消息 `content` 可能是纯文本字符串,也可能是一个包含文本和图片的列表(`[{'type':'text', ...}, {'type':'image_url', ...}]`)。简单地进行字符串拼接会破坏多模态结构。
|
||||
- **解决方案**:
|
||||
1. 使用 `isinstance(content, list)` 检查内容是否为列表。
|
||||
2. 如果是列表,则遍历它,找到 `type` 为 `text` 的那部分。
|
||||
3. 对文本部分执行上述的“幂等性注入”逻辑。
|
||||
4. 如果列表中**没有**文本部分(例如,用户只发了一张图片),则**主动插入**一个新的文本部分 `{'type': 'text', 'text': ...}` 到列表的开头。
|
||||
|
||||
**启示**: 对消息体的任何修改都必须考虑其数据结构(`str` 或 `list`),并进行相应的处理,以确保插件的广泛兼容性。
|
||||
|
||||
### 3. 基于模型的动态路由与功能切换
|
||||
|
||||
`change_web_search` 函数是“拦截与翻译”模式的又一个精彩应用,并且引入了更高级的“模型重定向”技巧。
|
||||
|
||||
- **模式一:参数翻译 (适用于通义千问)**
|
||||
- **场景**: `qwen-max` 模型可能不认识 Open WebUI 的标准 `web_search` 开关,而是需要一个名为 `enable_search` 的参数。
|
||||
- **实现**:
|
||||
1. 拦截:`features["web_search"] = False`
|
||||
2. 翻译:`body.setdefault("enable_search", True)`
|
||||
- **效果**: 对用户透明地将会话切换到了模型的原生搜索模式。
|
||||
|
||||
- **模式二:模型重定向 (适用于 Deepseek/Gemini 等)**
|
||||
- **场景**: 某个模型系列(如 `deepseek`)本身不支持搜索,但其提供商部署了一个带搜索功能的版本,其模型名称可能是 `deepseek-chat-search`。
|
||||
- **实现**:
|
||||
1. 检查当前模型是否为 `cfdeepseek-deepseek` 且**不**以 `-search` 结尾。
|
||||
2. 如果是,则**直接修改请求体中的模型名称**: `body["model"] = body["model"] + "-search"`。
|
||||
3. 最后,禁用标准的 `web_search` 开关:`features["web_search"] = False`。
|
||||
- **效果**: 这种方式巧妙地将用户的请求“重定向”到了一个功能更强的模型版本,而用户在前端选择的仍然是普通模型。这为插件开发者提供了极大的灵活性,可以创建功能增强的“虚拟模型”。
|
||||
|
||||
### 4. 用户特定逻辑
|
||||
|
||||
插件还可以根据用户信息执行特定逻辑,这对于 A/B 测试、灰度发布或为特定用户提供定制功能非常有用。
|
||||
|
||||
**代码示例:**
|
||||
```python
|
||||
# 从 __user__ 参数中获取用户邮箱
|
||||
user_email = __user__.get("email")
|
||||
|
||||
# 为特定用户禁用网络搜索
|
||||
if user_email == "yi204o@qq.com":
|
||||
features["web_search"] = False
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
`Inject Env` 插件虽然代码量不大,但它像一把精准的手术刀,展示了 `Filter` 插件在请求预处理阶段的强大能力。通过学习它,我们可以掌握:
|
||||
|
||||
- **利用上下文**: 如何充分利用 `__metadata__` 和 `__model__` 参数,让插件变得“智能”和“情境感知”。
|
||||
- **稳健地修改内容**: 如何在不破坏多模态结构和保证幂等性的前提下,向用户消息中注入信息。
|
||||
- **高级功能控制**: 如何通过“参数翻译”和“模型重定向”等高级技巧,实现对模型功能(如网络搜索)的精细化控制。
|
||||
- **构建模板**: 这个插件是任何需要在请求发送前注入动态信息(如 Prompt Engineering、上下文增强、参数调整)的过滤器的绝佳起点。
|
||||
|
||||
```
|
||||
1838
docs/examples/gemini_manifold_plugin_examples.md
Normal file
1838
docs/examples/gemini_manifold_plugin_examples.md
Normal file
File diff suppressed because it is too large
Load Diff
185
docs/examples/pipe_plugin_gemini_manifold_example_cn.md
Normal file
185
docs/examples/pipe_plugin_gemini_manifold_example_cn.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# `Gemini Manifold` 插件深度解析:高级 `Pipe` 插件开发指南
|
||||
|
||||
## 引言
|
||||
|
||||
`Gemini Manifold` (`gemini_manifold.py`) 不仅仅是一个连接到 Google AI 服务的 `Pipe` 插件,它更是一个集成了高级架构设计、复杂功能和最佳实践的“瑞士军刀”。它作为 Open WebUI 与 Google Gemini 及 Vertex AI 之间的桥梁,全面展示了如何构建一个生产级的、功能丰富的、高性能且用户体验良好的 `Pipe` 插件。
|
||||
|
||||
本文档是对该插件的**深度解析**,旨在帮助开发者通过剖析一个顶级的范例,掌握 Open WebUI 高级插件的开发思想与核心技术。
|
||||
|
||||
## Part 1: 复杂配置管理艺术 (`Valves` 系统)
|
||||
|
||||
在复杂的应用场景中,配置管理需要同时兼顾安全性、灵活性和多用户隔离。`Gemini Manifold` 通过一个精巧的双层 `Valves` 系统完美地解决了这个问题。
|
||||
|
||||
**目标**: 解决多用户、多环境下的配置灵活性与安全性问题。
|
||||
|
||||
#### 1.1 双层结构:`Valves` 与 `UserValves`
|
||||
|
||||
- **`Pipe.Valves` (管理员层)**: 定义了插件的全局默认配置,由管理员在 Open WebUI 的设置界面中配置。这些是插件运行的基础。
|
||||
|
||||
```python
|
||||
class Pipe:
|
||||
class Valves(BaseModel):
|
||||
GEMINI_API_KEY: str | None = Field(default=None)
|
||||
USE_VERTEX_AI: bool = Field(default=False)
|
||||
USER_MUST_PROVIDE_AUTH_CONFIG: bool = Field(default=False)
|
||||
AUTH_WHITELIST: str | None = Field(default=None)
|
||||
# ... 40+ 其他全局配置
|
||||
```
|
||||
|
||||
- **`Pipe.UserValves` (用户层)**: 允许每个用户在每次请求时,通过请求体(`body`)传入自己的配置,用于临时覆盖管理员的默认设置。
|
||||
|
||||
```python
|
||||
class Pipe:
|
||||
class UserValves(BaseModel):
|
||||
GEMINI_API_KEY: str | None = Field(default=None)
|
||||
USE_VERTEX_AI: bool | None | Literal[""] = Field(default=None)
|
||||
# ... 其他用户可覆盖的配置
|
||||
```
|
||||
|
||||
#### 1.2 核心合并逻辑 `_get_merged_valves`
|
||||
|
||||
该函数在每次请求时被调用,负责将 `UserValves` 合并到 `Valves` 中,生成最终生效的配置。
|
||||
|
||||
#### 1.3 关键模式:强制认证与白名单
|
||||
|
||||
这是该配置系统中最精妙的部分,专为需要进行成本分摊和安全管控的团队环境设计。
|
||||
|
||||
- **场景**: 公司希望员工使用自己的 API Key,而不是共用一个高额度的 Key。
|
||||
- **实现**:
|
||||
1. 管理员在 `Valves` 中设置 `USER_MUST_PROVIDE_AUTH_CONFIG: True`。
|
||||
2. 同时,可以将少数特权用户(如测试人员)的邮箱加入 `AUTH_WHITELIST`。
|
||||
3. 在合并配置时,插件会检查当前用户是否在白名单内。
|
||||
- **非白名单用户**: **强制**使用其在 `UserValves` 中提供的 `GEMINI_API_KEY`,并**禁用**管理员配置的 `USE_VERTEX_AI`。如果用户没提供 Key,请求会失败。
|
||||
- **白名单用户**: 不受此限制,可以正常使用管理员配置的默认值。
|
||||
|
||||
这种设计通过代码强制执行了组织的策略,比单纯的文档约定要可靠得多。
|
||||
|
||||
## Part 2: 高性能文件上传与缓存 (`FilesAPIManager`)
|
||||
|
||||
`FilesAPIManager` 是该插件的性能核心,它通过一套复杂但高效的机制,解决了文件上传中的重复、并发和性能三大难题。
|
||||
|
||||
**目标**: 避免重复上传,减少API调用,并在高并发下保持稳定。
|
||||
|
||||
#### 2.1 核心概念:内容寻址 (Content-Addressable Storage)
|
||||
|
||||
- **原理**: 文件的唯一标识符**不是文件名**,而是其**文件内容的哈希值**。插件使用 `xxhash`(一种速度极快的非加密哈希算法)来计算文件哈希。
|
||||
- **优势**: 无论一个文件被上传多少次,只要内容不变,其哈希值就永远相同。这意味着插件只需为每个独一无二的文件内容执行一次上传操作。
|
||||
|
||||
#### 2.2 实现:三级缓存路径 (Hot/Warm/Cold Path)
|
||||
|
||||
`FilesAPIManager` 的 `get_or_upload_file` 方法实现了精妙的三级缓存策略:
|
||||
|
||||
1. **Hot Path (内存缓存)**:
|
||||
- **实现**: 使用 `aiocache` 将“文件哈希 -> `types.File` 对象”的映射关系缓存在内存中。`types.File` 对象包含了 Google API 返回的文件 URI 和过期时间。
|
||||
- **流程**: 收到文件后,先查内存缓存。如果命中,直接返回 `types.File` 对象,无任何网络 I/O,速度最快。
|
||||
|
||||
2. **Warm Path (无状态恢复)**:
|
||||
- **场景**: 内存缓存未命中(例如服务重启,内存被清空)。
|
||||
- **实现**: 插件根据文件哈希构造一个**确定性的文件名**(`deterministic_name = f"files/owui-v1-{content_hash}"`),然后直接调用 `client.aio.files.get()` 尝试从 Google API 获取该文件。
|
||||
- **优势**: 如果文件之前被上传过,这次 `get` 调用就会成功,并返回文件的状态信息。这样**仅用一次轻量的 `GET` 请求就恢复了文件状态,避免了昂贵的重新上传**。
|
||||
|
||||
3. **Cold Path (文件上传)**:
|
||||
- **场景**: Hot 和 Warm 路径全部失败,说明这确实是一个新文件(或者在 Google 服务器上已过期)。
|
||||
- **实现**: 执行完整的文件上传流程,并将成功后的 `types.File` 对象存入内存缓存(Hot Path),以备后续使用。
|
||||
|
||||
#### 2.3 关键模式:并发上传安全
|
||||
|
||||
- **问题**: 如果 10 个用户同时上传同一个大文件,会发生什么?
|
||||
- **解决方案**: 使用 `asyncio.Lock` 结合 "双重检查锁定" (Double-Checked Locking) 模式。
|
||||
1. 为每一个**文件哈希**维护一个独立的 `asyncio.Lock`。
|
||||
2. 当一个任务进入 `get_or_upload_file` 时,它会先尝试获取该文件哈希对应的锁。
|
||||
3. **第一个任务**会成功获取锁,并继续执行 Warm/Cold Path 逻辑。
|
||||
4. **后续 9个任务**会被阻塞在 `async with lock:` 处,异步等待。
|
||||
5. 第一个任务完成后,它会将结果写入缓存并释放锁。
|
||||
6. 后续 9 个任务依次获取到锁,但它们在获取锁之后会**再次检查缓存**。此时,它们会发现缓存中已有数据,于是直接从缓存返回,不再执行任何网络操作。
|
||||
|
||||
这个模式优雅地解决了并发上传的资源浪费和竞态问题。
|
||||
|
||||
## Part 3: 异步并发与流程编排
|
||||
|
||||
为了在处理复杂请求(例如,包含多个文件的消息)时保持前端的流畅响应,插件大量使用了 `asyncio` 的高级特性。
|
||||
|
||||
**目标**: 最大化 I/O 效率,缩短用户的等待时间。
|
||||
|
||||
#### 3.1 `asyncio.gather`:并发处理所有消息
|
||||
|
||||
`GeminiContentBuilder.build_contents` 方法是并发处理的典范。它没有按顺序循环处理每条消息,而是:
|
||||
1. 为对话历史中的**每一条消息**创建一个 `_process_message_turn` 协程任务。
|
||||
2. 将所有任务放入一个列表。
|
||||
3. 使用 `await asyncio.gather(*tasks)` **同时启动并等待所有任务完成**。
|
||||
|
||||
这意味着,如果一条消息包含 5 个待上传的文件,另一条包含 3 个,这 8 个文件的上传和处理是**并行进行**的,总耗时取决于最慢的那个文件,而不是所有文件耗时的总和。
|
||||
|
||||
#### 3.2 `asyncio.Queue`:解耦的进度汇报
|
||||
|
||||
`UploadStatusManager` 展示了如何通过生产者-消费者模型实现优雅的进度汇报。
|
||||
|
||||
- **生产者 (上传任务)**:
|
||||
- 当一个 `_process_message_turn` 任务确定需要上传文件时,它会向一个共享的 `asyncio.Queue` 中 `put` 一个 `('REGISTER_UPLOAD',)` 元组。
|
||||
- 上传完成后,它会 `put` 一个 `('COMPLETE_UPLOAD',)` 元组。
|
||||
|
||||
- **消费者 (`UploadStatusManager`)**:
|
||||
- 它在一个独立的后台任务 (`asyncio.create_task`) 中运行,循环地从队列中 `get` 消息。
|
||||
- 每当收到 `REGISTER_UPLOAD`,它就将预期总数加一。
|
||||
- 每当收到 `COMPLETE_UPLOAD`,它就将完成数加一。
|
||||
- 每次计数变化后,它会重新计算进度(例如,“正在上传 3/8…”),并通过 `EventEmitter` 发送给前端。
|
||||
|
||||
这种设计将“执行业务逻辑”(上传)和“汇报进度”两个职责完全解耦。上传任务只管“生产”状态事件,进度管理器只管“消费”事件并更新 UI,代码非常清晰。
|
||||
|
||||
## Part 4: 响应处理与前端兼容性
|
||||
|
||||
**目标**: 提供流畅、信息丰富且绝对不会“搞乱”前端页面的用户体验。
|
||||
|
||||
#### 4.1 统一响应处理器 `_unified_response_processor`
|
||||
|
||||
- **问题**: Google API 同时支持流式(streaming)和非流式(non-streaming)两种响应模式,如果为两种模式都写一套处理逻辑,代码会很冗余。
|
||||
- **解决方案**: `pipe` 方法的核心返回部分,无论是哪种模式,最终都会调用 `_unified_response_processor`。
|
||||
- 对于**流式**响应,直接将 API 返回的异步生成器传入。
|
||||
- 对于**非流式**响应,它会先将单个响应对象包装成一个只含一项的简单异步生成器。
|
||||
- **效果**: `_unified_response_processor` 内部只需用一套 `async for` 循环逻辑即可处理所有情况,极大地简化了代码。
|
||||
|
||||
#### 4.2 后置元数据处理 `_do_post_processing`
|
||||
|
||||
- **问题**: 像 Token 使用量 (`usage`)、搜索引用来源 (`sources`) 等信息,只有在整个响应完全生成后才能获得。如果和内容混在一起发送,会影响流式输出的体验。
|
||||
- **解决方案**: `_unified_response_processor` 在主内容流(`choices`)完全结束后,会进入后置处理阶段。它会调用 `_do_post_processing` 来提取这些元数据,并通过 `EventEmitter` 的 `emit_completion` 或 `emit_usage` 方法,作为**独立的、附加的事件**发送给前端。
|
||||
|
||||
#### 4.3 前端兼容性技巧 `_disable_special_tags`
|
||||
|
||||
- **问题**: LLM 很可能在思考过程中生成 `<think>...</think>` 或 `<details>...</details>` 这样的 XML/HTML 风格标签。如果这些文本原样发送到前端,浏览器会尝试将其解析为 HTML 元素,导致页面布局错乱或内容丢失。
|
||||
- **解决方案**: 一个极其巧妙的技巧——在这些特殊标签的开头注入一个**零宽度空格(Zero-Width Space, ZWS, `\u200b`)**。
|
||||
- 例如,将 `<think>` 替换为 `<think>` (后者尖括号后多一个 ZWS)。
|
||||
- 这个改动对人类用户完全不可见,但对于浏览器的 HTML 解析器来说,`<think>` 不再是一个合法的标签名,因此它会被当作纯文本处理,从而保证了前端渲染的绝对安全。
|
||||
- 当需要将这段历史作为上下文发回给模型时,再通过 `_enable_special_tags` 将这些 ZWS 移除,恢复原始文本。
|
||||
|
||||
## Part 5: 与 Open WebUI 和 Google API 的深度集成
|
||||
|
||||
`Gemini Manifold` 充分利用了 Open WebUI 的框架特性和 Google API 的高级功能。
|
||||
|
||||
#### 5.1 `pipes` 方法与模型缓存
|
||||
|
||||
- `pipes()` 方法负责向 Open WebUI 注册所有可用的 Gemini 模型。
|
||||
- 它使用了 `@cached` 装饰器,这意味着对 Google API 的 `list_models` 调用结果会被缓存。只要插件配置(如 API Key, 白名单等)不变,后续的 `pipes` 调用会直接从缓存返回,避免了不必要的网络请求。
|
||||
|
||||
#### 5.2 多源内容处理 (`_genai_parts_from_text`)
|
||||
|
||||
`GeminiContentBuilder` 的核心能力之一是从一段文本中智能地解析出多种类型的内容。
|
||||
- 它使用正则表达式一次性地从用户输入中匹配出 Markdown 图片链接 (`![]()`) 和 YouTube 视频链接。
|
||||
- 对于匹配到的每一种 URI,它都会分派给统一的 `_genai_part_from_uri` 方法处理。
|
||||
- `_genai_part_from_uri` 内部进一步区分 URI 类型(是本地文件、data URI 还是 YouTube 链接),并调用相应的处理器(例如,从数据库读取文件、解码 base64、或解析 YouTube URL 参数)。
|
||||
|
||||
#### 5.3 与 Open WebUI 数据库交互
|
||||
|
||||
为了处理用户上传的文件,插件需要访问 Open WebUI 的内部数据库。
|
||||
- 它通过 `from open_webui.models.files import Files` 导入 `Files` 模型。
|
||||
- 在 `_get_file_data` 方法中,它调用 `Files.get_file_by_id(file_id)` 来获取文件的元数据(如存储路径、MIME 类型)。
|
||||
- **关键点**: 由于数据库 API 是同步阻塞的,插件明智地使用了 `await asyncio.to_thread(Files.get_file_by_id, file_id)`,将同步调用放入一个独立的线程中执行,从而避免了对主异步事件循环的阻塞。
|
||||
|
||||
## 总结
|
||||
|
||||
`Gemini Manifold` 是一个教科书级别的 Open WebUI `Pipe` 插件。它展示了超越简单 API 调用的高级插件应该具备的特质:
|
||||
- **架构思维**: 通过职责分离的类和清晰的流程编排来管理复杂性。
|
||||
- **性能意识**: 在所有 I/O 密集型操作中,都将性能优化(缓存、并发)放在首位。
|
||||
- **用户为本**: 通过丰富的、非阻塞的实时反馈,极大地提升了用户体验。
|
||||
- **健壮与安全**: 通过精巧的技巧和周密的错误处理,确保插件在各种异常情况下都能稳定运行。
|
||||
|
||||
对于任何希望超越基础,构建企业级、高性能 Open WebUI 插件的开发者而言,`Gemini Manifold` 的每一行代码都值得细细品味。
|
||||
7
docs/features/plugin/development/_category_.json
Normal file
7
docs/features/plugin/development/_category_.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "Development",
|
||||
"position": 800,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
}
|
||||
424
docs/features/plugin/development/events.mdx
Normal file
424
docs/features/plugin/development/events.mdx
Normal file
@@ -0,0 +1,424 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
title: "Events"
|
||||
---
|
||||
|
||||
# 🔔 Events: Using `__event_emitter__` and `__event_call__` in Open WebUI
|
||||
|
||||
Open WebUI's plugin architecture is not just about processing input and producing output—**it's about real-time, interactive communication with the UI and users**. To make your Tools, Functions, and Pipes more dynamic, Open WebUI provides a built-in event system via the `__event_emitter__` and `__event_call__` helpers.
|
||||
|
||||
This guide explains **what events are**, **how you can trigger them** from your code, and **the full catalog of event types** you can use (including much more than just `"input"`).
|
||||
|
||||
---
|
||||
|
||||
## 🌊 What Are Events?
|
||||
|
||||
**Events** are real-time notifications or interactive requests sent from your backend code (Tool, or Function) to the web UI. They allow you to update the chat, display notifications, request confirmation, run UI flows, and more.
|
||||
|
||||
- Events are sent using the `__event_emitter__` helper for one-way updates, or `__event_call__` when you need user input or a response (e.g., confirmation, input, etc.).
|
||||
|
||||
**Metaphor:**
|
||||
Think of Events like push notifications and modal dialogs that your plugin can trigger, making the chat experience richer and more interactive.
|
||||
|
||||
---
|
||||
|
||||
## 🧰 Basic Usage
|
||||
|
||||
### Sending an Event
|
||||
|
||||
You can trigger an event anywhere inside your Tool, or Function by calling:
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status", # See the event types list below
|
||||
"data": {
|
||||
"description": "Processing started!",
|
||||
"done": False,
|
||||
"hidden": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
You **do not** need to manually add fields like `chat_id` or `message_id`—these are handled automatically by Open WebUI.
|
||||
|
||||
### Interactive Events
|
||||
|
||||
When you need to pause execution until the user responds (e.g., confirm/cancel dialogs, code execution, or input), use `__event_call__`:
|
||||
|
||||
```python
|
||||
result = await __event_call__(
|
||||
{
|
||||
"type": "input", # Or "confirmation", "execute"
|
||||
"data": {
|
||||
"title": "Please enter your password",
|
||||
"message": "Password is required for this action",
|
||||
"placeholder": "Your password here",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# result will contain the user's input value
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📜 Event Payload Structure
|
||||
|
||||
When you emit or call an event, the basic structure is:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "event_type", // See full list below
|
||||
"data": { ... } // Event-specific payload
|
||||
}
|
||||
```
|
||||
|
||||
Most of the time, you only set `"type"` and `"data"`. Open WebUI fills in the routing automatically.
|
||||
|
||||
---
|
||||
|
||||
## 🗂 Full List of Event Types
|
||||
|
||||
Below is a comprehensive table of **all supported `type` values** for events, along with their intended effect and data structure. (This is based on up-to-date analysis of Open WebUI event handling logic.)
|
||||
|
||||
| type | When to use | Data payload structure (examples) |
|
||||
| -------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `status` | Show a status update/history for a message | `{description: ..., done: bool, hidden: bool}` |
|
||||
| `chat:completion` | Provide a chat completion result | (Custom, see Open WebUI internals) |
|
||||
| `chat:message:delta`,<br/>`message` | Append content to the current message | `{content: "text to append"}` |
|
||||
| `chat:message`,<br/>`replace` | Replace current message content completely | `{content: "replacement text"}` |
|
||||
| `chat:message:files`,<br/>`files` | Set or overwrite message files (for uploads, output) | `{files: [...]}` |
|
||||
| `chat:title` | Set (or update) the chat conversation title | Topic string OR `{title: ...}` |
|
||||
| `chat:tags` | Update the set of tags for a chat | Tag array or object |
|
||||
| `source`,<br/>`citation` | Add a source/citation, or code execution result | For code: See [below.](/features/plugin/development/events#source-or-citation-and-code-execution) |
|
||||
| `notification` | Show a notification ("toast") in the UI | `{type: "info" or "success" or "error" or "warning", content: "..."}` |
|
||||
| `confirmation` <br/>(needs `__event_call__`) | Ask for confirmation (OK/Cancel dialog) | `{title: "...", message: "..."}` |
|
||||
| `input` <br/>(needs `__event_call__`) | Request simple user input ("input box" dialog) | `{title: "...", message: "...", placeholder: "...", value: ...}` |
|
||||
| `execute` <br/>(needs `__event_call__`) | Request user-side code execution and return result | `{code: "...javascript code..."}` | |
|
||||
|
||||
**Other/Advanced types:**
|
||||
|
||||
- You can define your own types and handle them at the UI layer (or use upcoming event-extension mechanisms).
|
||||
|
||||
### ❗ Details on Specific Event Types
|
||||
|
||||
### `status`
|
||||
|
||||
Show a status/progress update in the UI:
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status",
|
||||
"data": {
|
||||
"description": "Step 1/3: Fetching data...",
|
||||
"done": False,
|
||||
"hidden": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `chat:message:delta` or `message`
|
||||
|
||||
**Streaming output** (append text):
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "chat:message:delta", # or simply "message"
|
||||
"data": {
|
||||
"content": "Partial text, "
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# Later, as you generate more:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "chat:message:delta",
|
||||
"data": {
|
||||
"content": "next chunk of response."
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `chat:message` or `replace`
|
||||
|
||||
**Set (or replace) the entire message content:**
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "chat:message", # or "replace"
|
||||
"data": {
|
||||
"content": "Final, complete response."
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `files` or `chat:message:files`
|
||||
|
||||
**Attach or update files:**
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "files", # or "chat:message:files"
|
||||
"data": {
|
||||
"files": [
|
||||
# Open WebUI File Objects
|
||||
]
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `chat:title`
|
||||
|
||||
**Update the chat's title:**
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "chat:title",
|
||||
"data": {
|
||||
"title": "Market Analysis Bot Session"
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `chat:tags`
|
||||
|
||||
**Update the chat's tags:**
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "chat:tags",
|
||||
"data": {
|
||||
"tags": ["finance", "AI", "daily-report"]
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `source` or `citation` (and code execution)
|
||||
|
||||
**Add a reference/citation:**
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "source", # or "citation"
|
||||
"data": {
|
||||
# Open WebUI Source (Citation) Object
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**For code execution (track execution state):**
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "source",
|
||||
"data": {
|
||||
# Open WebUI Code Source (Citation) Object
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `notification`
|
||||
|
||||
**Show a toast notification:**
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {
|
||||
"type": "info", # "success", "warning", "error"
|
||||
"content": "The operation completed successfully!"
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `confirmation` (**requires** `__event_call__`)
|
||||
|
||||
**Show a confirm dialog and get user response:**
|
||||
|
||||
```python
|
||||
result = await __event_call__(
|
||||
{
|
||||
"type": "confirmation",
|
||||
"data": {
|
||||
"title": "Are you sure?",
|
||||
"message": "Do you really want to proceed?"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if result: # or check result contents
|
||||
await __event_emitter__({
|
||||
"type": "notification",
|
||||
"data": {"type": "success", "content": "User confirmed operation."}
|
||||
})
|
||||
else:
|
||||
await __event_emitter__({
|
||||
"type": "notification",
|
||||
"data": {"type": "warning", "content": "User cancelled."}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `input` (**requires** `__event_call__`)
|
||||
|
||||
**Prompt user for text input:**
|
||||
|
||||
```python
|
||||
result = await __event_call__(
|
||||
{
|
||||
"type": "input",
|
||||
"data": {
|
||||
"title": "Enter your name",
|
||||
"message": "We need your name to proceed.",
|
||||
"placeholder": "Your full name"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
user_input = result
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {"type": "info", "content": f"You entered: {user_input}"}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `execute` (**requires** `__event_call__`)
|
||||
|
||||
**Run code dynamically on the user's side:**
|
||||
|
||||
```python
|
||||
result = await __event_call__(
|
||||
{
|
||||
"type": "execute",
|
||||
"data": {
|
||||
"code": "print(40 + 2);",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {
|
||||
"type": "info",
|
||||
"content": f"Code executed, result: {result}"
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ When & Where to Use Events
|
||||
|
||||
- **From any Tool, or Function** in Open WebUI.
|
||||
- To **stream responses**, show progress, request user data, update the UI, or display supplementary info/files.
|
||||
- `await __event_emitter__` is for one-way messages (fire and forget).
|
||||
- `await __event_call__` is for when you need a response from the user (input, execute, confirmation).
|
||||
|
||||
---
|
||||
|
||||
## 💡 Tips & Advanced Notes
|
||||
|
||||
- **Multiple types per message:** You can emit several events of different types for one message—for example, show `status` updates, then stream with `chat:message:delta`, then complete with a `chat:message`.
|
||||
- **Custom event types:** While the above list is the standard, you may use your own types and detect/handle them in custom UI code.
|
||||
- **Extensibility:** The event system is designed to evolve—always check the [Open WebUI documentation](https://github.com/open-webui/open-webui) for the most current list and advanced usage.
|
||||
|
||||
---
|
||||
|
||||
## 🧐 FAQ
|
||||
|
||||
### Q: How do I trigger a notification for the user?
|
||||
Use `notification` type:
|
||||
```python
|
||||
await __event_emitter__({
|
||||
"type": "notification",
|
||||
"data": {"type": "success", "content": "Task complete"}
|
||||
})
|
||||
```
|
||||
|
||||
### Q: How do I prompt the user for input and get their answer?
|
||||
Use:
|
||||
```python
|
||||
response = await __event_call__({
|
||||
"type": "input",
|
||||
"data": {
|
||||
"title": "What's your name?",
|
||||
"message": "Please enter your preferred name:",
|
||||
"placeholder": "Name"
|
||||
}
|
||||
})
|
||||
|
||||
# response will be: {"value": "user's answer"}
|
||||
```
|
||||
|
||||
### Q: What event types are available for `__event_call__`?
|
||||
- `"input"`: Input box dialog
|
||||
- `"confirmation"`: Yes/No, OK/Cancel dialog
|
||||
- `"execute"`: Run provided code on client and return result
|
||||
|
||||
### Q: Can I update files attached to a message?
|
||||
Yes—use the `"files"` or `"chat:message:files"` event type with a `{files: [...]}` payload.
|
||||
|
||||
### Q: Can I update the conversation title or tags?
|
||||
Absolutely: use `"chat:title"` or `"chat:tags"` accordingly.
|
||||
|
||||
### Q: Can I stream responses (partial tokens) to the user?
|
||||
Yes—emit `"chat:message:delta"` events in a loop, then finish with `"chat:message"`.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Conclusion
|
||||
|
||||
**Events** give you real-time, interactive superpowers inside Open WebUI. They let your code update content, trigger notifications, request user input, stream results, handle code, and much more—seamlessly plugging your backend intelligence into the chat UI.
|
||||
|
||||
- Use `__event_emitter__` for one-way status/content updates.
|
||||
- Use `__event_call__` for interactions that require user follow-up (input, confirmation, execution).
|
||||
|
||||
Refer to this document for common event types and structures, and explore Open WebUI source code or docs for breaking updates or custom events!
|
||||
|
||||
---
|
||||
|
||||
**Happy event-driven coding in Open WebUI! 🚀**
|
||||
340
docs/features/plugin/development/reserved-args.mdx
Normal file
340
docs/features/plugin/development/reserved-args.mdx
Normal file
@@ -0,0 +1,340 @@
|
||||
---
|
||||
sidebar_position: 999
|
||||
title: "Reserved Arguments"
|
||||
---
|
||||
|
||||
:::warning
|
||||
|
||||
This tutorial is a community contribution and is not supported by the Open WebUI team. It serves only as a demonstration on how to customize Open WebUI for your specific use case. Want to contribute? Check out the contributing tutorial.
|
||||
|
||||
:::
|
||||
|
||||
# 🪄 Special Arguments
|
||||
|
||||
When developping your own `Tools`, `Functions` (`Filters`, `Pipes` or `Actions`), `Pipelines` etc, you can use special arguments explore the full spectrum of what Open-WebUI has to offer.
|
||||
|
||||
This page aims to detail the type and structure of each special argument as well as provide an example.
|
||||
|
||||
### `body`
|
||||
|
||||
A `dict` usually destined to go almost directly to the model. Although it is not strictly a special argument, it is included here for easier reference and because it contains itself some special arguments.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"stream": true,
|
||||
"model": "my-cool-model",
|
||||
# lowercase string with - separated words: this is the ID of the model
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "What is in this picture?"
|
||||
},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAdYAAAGcCAYAAABk2YF[REDACTED]"
|
||||
# Images are passed as base64 encoded data
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "The image appears to be [REDACTED]"
|
||||
},
|
||||
],
|
||||
"features": {
|
||||
"image_generation": false,
|
||||
"code_interpreter": false,
|
||||
"web_search": false
|
||||
},
|
||||
"stream_options": {
|
||||
"include_usage": true
|
||||
},
|
||||
"metadata": "[The exact same dict as __metadata__]",
|
||||
"files": "[The exact same list as __files__]"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### `__user__`
|
||||
|
||||
A `dict` with user information.
|
||||
|
||||
Note that if the `UserValves` class is defined, its instance has to be accessed via `__user__["valves"]`. Otherwise, the `valves` keyvalue is missing entirely from `__user__`.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"email": "cheesy_dude@openwebui.com",
|
||||
"name": "Patrick",
|
||||
"role": "user",
|
||||
# role can be either `user` or `admin`
|
||||
"valves": "[the UserValve instance]"
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### `__metadata__`
|
||||
|
||||
A `dict` with wide ranging information about the chat, model, files, etc.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"chat_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"message_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"session_id": "xxxxxxxxxxxxxxxxxxxx",
|
||||
"tool_ids": null,
|
||||
# tool_ids is a list of str.
|
||||
"tool_servers": [],
|
||||
"files": "[Same as in body['files']]",
|
||||
# If no files are given, the files key exists in __metadata__ and its value is []
|
||||
"features": {
|
||||
"image_generation": false,
|
||||
"code_interpreter": false,
|
||||
"web_search": false
|
||||
},
|
||||
"variables": {
|
||||
"{{USER_NAME}}": "cheesy_username",
|
||||
"{{USER_LOCATION}}": "Unknown",
|
||||
"{{CURRENT_DATETIME}}": "2025-02-02 XX:XX:XX",
|
||||
"{{CURRENT_DATE}}": "2025-02-02",
|
||||
"{{CURRENT_TIME}}": "XX:XX:XX",
|
||||
"{{CURRENT_WEEKDAY}}": "Monday",
|
||||
"{{CURRENT_TIMEZONE}}": "Europe/Berlin",
|
||||
"{{USER_LANGUAGE}}": "en-US"
|
||||
},
|
||||
"model": "[The exact same dict as __model__]",
|
||||
"direct": false,
|
||||
"function_calling": "native",
|
||||
"type": "user_response",
|
||||
"interface": "open-webui"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### `__model__`
|
||||
|
||||
A `dict` with information about the model.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "my-cool-model",
|
||||
"name": "My Cool Model",
|
||||
"object": "model",
|
||||
"created": 1746000000,
|
||||
"owned_by": "openai",
|
||||
# either openai or ollama
|
||||
"info": {
|
||||
"id": "my-cool-model",
|
||||
"user_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"base_model_id": "gpt-4o",
|
||||
# this is the name of model that the model endpoint serves
|
||||
"name": "My Cool Model",
|
||||
"params": {
|
||||
"system": "You are my best assistant. You answer [REDACTED]",
|
||||
"function_calling": "native"
|
||||
# custom options appear here, for example "Top K"
|
||||
},
|
||||
"meta": {
|
||||
"profile_image_url": "/static/favicon.png",
|
||||
"description": "Description of my-cool-model",
|
||||
"capabilities": {
|
||||
"vision": true,
|
||||
"usage": true,
|
||||
"citations": true
|
||||
},
|
||||
"position": 17,
|
||||
"tags": [
|
||||
{
|
||||
"name": "for_friends"
|
||||
},
|
||||
{
|
||||
"name": "vision_enabled"
|
||||
}
|
||||
],
|
||||
"suggestion_prompts": null
|
||||
},
|
||||
"access_control": {
|
||||
"read": {
|
||||
"group_ids": [],
|
||||
"user_ids": []
|
||||
},
|
||||
"write": {
|
||||
"group_ids": [],
|
||||
"user_ids": []
|
||||
}
|
||||
},
|
||||
"is_active": true,
|
||||
"updated_at": 1740000000,
|
||||
"created_at": 1740000000
|
||||
},
|
||||
"preset": true,
|
||||
"actions": [],
|
||||
"tags": [
|
||||
{
|
||||
"name": "for_friends"
|
||||
},
|
||||
{
|
||||
"name": "vision_enabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### `__messages__`
|
||||
|
||||
A `list` of the previous messages.
|
||||
|
||||
See the `body["messages"]` value above.
|
||||
|
||||
### `__chat_id__`
|
||||
|
||||
The `str` of the `chat_id`.
|
||||
|
||||
See the `__metadata__["chat_id"]` value above.
|
||||
|
||||
### `__session_id__`
|
||||
|
||||
The `str` of the `session_id`.
|
||||
|
||||
See the `__metadata__["session_id"]` value above.
|
||||
|
||||
### `__message_id__`
|
||||
|
||||
The `str` of the `message_id`.
|
||||
|
||||
See the `__metadata__["message_id"]` value above.
|
||||
|
||||
### `__event_emitter__`
|
||||
|
||||
A `Callable` used to display event information to the user.
|
||||
|
||||
### `__event_call__`
|
||||
|
||||
A `Callable` used for `Actions`.
|
||||
|
||||
### `__files__`
|
||||
|
||||
A `list` of files sent via the chat. Note that images are not considered files and are sent directly to the model as part of the `body["messages"]` list.
|
||||
|
||||
The actual binary of the file is not part of the arguments for performance reason, but the file remain nonetheless accessible by its path if needed. For example using `docker` the python syntax for the path could be:
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
the_file = Path(f"/app/backend/data/uploads/{__files__[0]["files"]["id"]}_{__files__[0]["files"]["filename"]}")
|
||||
assert the_file.exists()
|
||||
```
|
||||
|
||||
Note that the same files dict can also be accessed via `__metadata__["files"]` (and its value is `[]` if no files are sent) or via `body["files"]` (but the `files` key is missing entirely from `body` if no files are sent).
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
```json
|
||||
|
||||
[
|
||||
{
|
||||
"type": "file",
|
||||
"file": {
|
||||
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"filename": "Napoleon - Wikipedia.pdf",
|
||||
"user_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"hash": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"data": {
|
||||
"content": "Napoleon - Wikipedia\n\n\nNapoleon I\n\nThe Emperor Napoleon in His Study at the\nTuileries, 1812\n\nEmperor of the French\n\n1st reign 18 May 1804 – 6 April 1814\n\nSuccessor Louis XVIII[a]\n\n2nd reign 20 March 1815 – 22 June 1815\n\nSuccessor Louis XVIII[a]\n\nFirst Consul of the French Republic\n\nIn office\n13 December 1799 – 18 May 1804\n\nBorn Napoleone Buonaparte\n15 August 1769\nAjaccio, Corsica, Kingdom of\nFrance\n\nDied 5 May 1821 (aged 51)\nLongwood, Saint Helena\n\nBurial 15 December 1840\nLes Invalides, Paris\n\nNapoleon\nNapoleon Bonaparte[b] (born Napoleone\nBuonaparte;[1][c] 15 August 1769 – 5 May 1821), later\nknown [REDACTED]",
|
||||
# The content value is the output of the document parser, the above example is with Tika as a document parser
|
||||
},
|
||||
"meta": {
|
||||
"name": "Napoleon - Wikipedia.pdf",
|
||||
"content_type": "application/pdf",
|
||||
"size": 10486578,
|
||||
# in bytes, here about 10Mb
|
||||
"data": {},
|
||||
"collection_name": "file-96xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
# always begins by 'file'
|
||||
},
|
||||
"created_at": 1740000000,
|
||||
"updated_at": 1740000000
|
||||
},
|
||||
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"url": "/api/v1/files/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"name": "Napoleon - Wikipedia.pdf",
|
||||
"collection_name": "file-96xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
"status": "uploaded",
|
||||
"size": 10486578,
|
||||
"error": "",
|
||||
"itemId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
# itemId is not the same as file["id"]
|
||||
}
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### `__request__`
|
||||
|
||||
An instance of `fastapi.Request`. You can read more in the [migration page](/docs/features/plugin/migration/index.mdx) or in [fastapi's documentation](https://fastapi.tiangolo.com/reference/request/).
|
||||
|
||||
### `__task__`
|
||||
|
||||
A `str` for the type of task. Its value is just a shorthand for `__metadata__["task"]` if present, otherwise `None`.
|
||||
|
||||
<details>
|
||||
<summary>Possible values</summary>
|
||||
|
||||
```json
|
||||
|
||||
[
|
||||
"title_generation",
|
||||
"tags_generation",
|
||||
"emoji_generation",
|
||||
"query_generation",
|
||||
"image_prompt_generation",
|
||||
"autocomplete_generation",
|
||||
"function_calling",
|
||||
"moa_response_generation"
|
||||
]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### `__task_body__`
|
||||
|
||||
A `dict` containing the `body` needed to accomplish a given `__task__`. Its value is just a shorthand for `__metadata__["task_body"]` if present, otherwise `None`.
|
||||
|
||||
Its structure is the same as `body` above, with modifications like using the appropriate model and system message etc.
|
||||
|
||||
### `__tools__`
|
||||
|
||||
A `list` of `ToolUserModel` instances.
|
||||
|
||||
For details the attributes of `ToolUserModel` instances, the code can be found in [tools.py](https://github.com/open-webui/open-webui/blob/main/backend/open_webui/models/tools.py).
|
||||
|
||||
77
docs/features/plugin/development/valves.mdx
Normal file
77
docs/features/plugin/development/valves.mdx
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
title: "Valves"
|
||||
---
|
||||
|
||||
## Valves
|
||||
|
||||
Valves and UserValves are used to allow users to provide dynamic details such as an API key or a configuration option. These will create a fillable field or a bool switch in the GUI menu for the given function. They are always optional, but HIGHLY encouraged.
|
||||
|
||||
Hence, Valves and UserValves class can be defined in either a `Pipe`, `Pipeline`, `Filter` or `Tools` class.
|
||||
|
||||
Valves are configurable by admins alone via the Tools or Functions menus. On the other hand UserValves are configurable by any users directly from a chat session.
|
||||
|
||||
<details>
|
||||
<summary>Commented example</summary>
|
||||
|
||||
```python
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Literal
|
||||
|
||||
# Define and Valves
|
||||
class Filter:
|
||||
# Notice the current indentation: Valves and UserValves must be declared as
|
||||
# attributes of a Tools, Filter or Pipe class. Here we take the
|
||||
# example of a Filter.
|
||||
class Valves(BaseModel):
|
||||
# Valves and UserValves inherit from pydantic's BaseModel. This
|
||||
# enables complex use cases like model validators etc.
|
||||
test_valve: int = Field( # Notice the type hint: it is used to
|
||||
# choose the kind of UI element to show the user (buttons,
|
||||
# texts, etc).
|
||||
default=4,
|
||||
description="A valve controlling a numberical value"
|
||||
# required=False, # you can enforce fields using True
|
||||
)
|
||||
# To give the user the choice between multiple strings, you can use Literal from typing:
|
||||
choice_option: Literal["choiceA", "choiceB"] = Field(
|
||||
default="choiceA",
|
||||
description="An example of a multi choice valve",
|
||||
)
|
||||
priority: int = Field(
|
||||
default=0,
|
||||
description="Priority level for the filter operations. Lower values are passed through first"
|
||||
)
|
||||
# The priority field is optional but if present will be used to
|
||||
# order the Filters.
|
||||
pass
|
||||
# Note that this 'pass' helps for parsing and is recommended.
|
||||
|
||||
# UserValves are defined the same way.
|
||||
class UserValves(BaseModel):
|
||||
test_user_valve: bool = Field(
|
||||
default=False, description="A user valve controlling a True/False (on/off) switch"
|
||||
)
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
# Because they are set by the admin, they are accessible directly
|
||||
# upon code execution.
|
||||
pass
|
||||
|
||||
# The inlet method is only used for Filter but the __user__ handling is the same
|
||||
def inlet(self, body: dict, __user__: dict):
|
||||
# Because UserValves are defined per user they are only available
|
||||
# on use.
|
||||
# Note that although __user__ is a dict, __user__["valves"] is a
|
||||
# UserValves object. Hence you can access values like that:
|
||||
test_user_valve = __user__["valves"].test_user_valve
|
||||
# Or:
|
||||
test_user_valve = dict(__user__["valves"])["test_user_valve"]
|
||||
# But this will return the default value instead of the actual value:
|
||||
# test_user_valve = __user__["valves"]["test_user_valve"] # Do not do that!
|
||||
```
|
||||
|
||||
</details>
|
||||
316
docs/features/plugin/functions/action.mdx
Normal file
316
docs/features/plugin/functions/action.mdx
Normal file
@@ -0,0 +1,316 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
title: "Action Function"
|
||||
---
|
||||
|
||||
Action functions allow you to write custom buttons that appear in the message toolbar for end users to interact with. This feature enables more interactive messaging, allowing users to grant permission before a task is performed, generate visualizations of structured data, download an audio snippet of chats, and many other use cases.
|
||||
|
||||
Actions are admin-managed functions that extend the chat interface with custom interactive capabilities. When a message is generated by a model that has actions configured, these actions appear as clickable buttons beneath the message.
|
||||
|
||||
A scaffold of Action code can be found [in the community section](https://openwebui.com/f/hub/custom_action/). For more Action Function examples built by the community, visit [https://openwebui.com/functions](https://openwebui.com/functions).
|
||||
|
||||
An example of a graph visualization Action can be seen in the video below.
|
||||
|
||||
<div align="center">
|
||||
<a href="#">
|
||||
<img
|
||||
src="/images/pipelines/graph-viz-action.gif"
|
||||
alt="Graph Visualization Action"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Action Function Architecture
|
||||
|
||||
Actions are Python-based functions that integrate directly into the chat message toolbar. They execute server-side and can interact with users through real-time events, modify message content, and access the full Open WebUI context.
|
||||
|
||||
### Function Structure
|
||||
|
||||
Actions follow a specific class structure with an `action` method as the main entry point:
|
||||
|
||||
```python
|
||||
class Action:
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
class Valves(BaseModel):
|
||||
# Configuration parameters
|
||||
parameter_name: str = "default_value"
|
||||
|
||||
async def action(self, body: dict, __user__=None, __event_emitter__=None, __event_call__=None):
|
||||
# Action implementation
|
||||
return {"content": "Modified message content"}
|
||||
```
|
||||
|
||||
### Action Method Parameters
|
||||
|
||||
The `action` method receives several parameters that provide access to the execution context:
|
||||
|
||||
- **`body`** - Dictionary containing the message data and context
|
||||
- **`__user__`** - Current user object with permissions and settings
|
||||
- **`__event_emitter__`** - Function to send real-time updates to the frontend
|
||||
- **`__event_call__`** - Function for bidirectional communication (confirmations, inputs)
|
||||
- **`__model__`** - Model information that triggered the action
|
||||
- **`__request__`** - FastAPI request object for accessing headers, etc.
|
||||
- **`__id__`** - Action ID (useful for multi-action functions)
|
||||
|
||||
## Event System Integration
|
||||
|
||||
Actions can utilize Open WebUI's real-time event system for interactive experiences:
|
||||
|
||||
### Event Emitter (`__event_emitter__`)
|
||||
|
||||
**For more information about Events and Event emitters, see [Events and Event Emitters](https://docs.openwebui.com/features/plugin/events/).**
|
||||
|
||||
Send real-time updates to the frontend during action execution:
|
||||
|
||||
```python
|
||||
async def action(self, body: dict, __event_emitter__=None):
|
||||
# Send status updates
|
||||
await __event_emitter__({
|
||||
"type": "status",
|
||||
"data": {"description": "Processing request..."}
|
||||
})
|
||||
|
||||
# Send notifications
|
||||
await __event_emitter__({
|
||||
"type": "notification",
|
||||
"data": {"type": "info", "content": "Action completed successfully"}
|
||||
})
|
||||
```
|
||||
|
||||
### Event Call (`__event_call__`)
|
||||
Request user input or confirmation during execution:
|
||||
|
||||
```python
|
||||
async def action(self, body: dict, __event_call__=None):
|
||||
# Request user confirmation
|
||||
response = await __event_call__({
|
||||
"type": "confirmation",
|
||||
"data": {
|
||||
"title": "Confirm Action",
|
||||
"message": "Are you sure you want to proceed?"
|
||||
}
|
||||
})
|
||||
|
||||
# Request user input
|
||||
user_input = await __event_call__({
|
||||
"type": "input",
|
||||
"data": {
|
||||
"title": "Enter Value",
|
||||
"message": "Please provide additional information:",
|
||||
"placeholder": "Type your input here..."
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Action Types and Configurations
|
||||
|
||||
### Single Actions
|
||||
Standard actions with one `action` method:
|
||||
|
||||
```python
|
||||
async def action(self, body: dict, **kwargs):
|
||||
# Single action implementation
|
||||
return {"content": "Action result"}
|
||||
```
|
||||
|
||||
### Multi-Actions
|
||||
Functions can define multiple sub-actions through an `actions` array:
|
||||
|
||||
```python
|
||||
actions = [
|
||||
{
|
||||
"id": "summarize",
|
||||
"name": "Summarize",
|
||||
"icon_url": "data:image/svg+xml;base64,..."
|
||||
},
|
||||
{
|
||||
"id": "translate",
|
||||
"name": "Translate",
|
||||
"icon_url": "data:image/svg+xml;base64,..."
|
||||
}
|
||||
]
|
||||
|
||||
async def action(self, body: dict, __id__=None, **kwargs):
|
||||
if __id__ == "summarize":
|
||||
# Summarization logic
|
||||
return {"content": "Summary: ..."}
|
||||
elif __id__ == "translate":
|
||||
# Translation logic
|
||||
return {"content": "Translation: ..."}
|
||||
```
|
||||
|
||||
### Global vs Model-Specific Actions
|
||||
- **Global Actions** - Turn on the toggle in the Action's settings, to globally enable it for all users and all models.
|
||||
- **Model-Specific Actions** - Configure enabled actions for specific models in the model settings.
|
||||
|
||||
## Advanced Capabilities
|
||||
|
||||
### Background Task Execution
|
||||
For long-running operations, actions can integrate with the task system:
|
||||
|
||||
```python
|
||||
async def action(self, body: dict, __event_emitter__=None):
|
||||
# Start long-running process
|
||||
await __event_emitter__({
|
||||
"type": "status",
|
||||
"data": {"description": "Starting background processing..."}
|
||||
})
|
||||
|
||||
# Perform time-consuming operation
|
||||
result = await some_long_running_function()
|
||||
|
||||
return {"content": f"Processing completed: {result}"}
|
||||
```
|
||||
|
||||
### File and Media Handling
|
||||
Actions can work with uploaded files and generate new media:
|
||||
|
||||
```python
|
||||
async def action(self, body: dict):
|
||||
message = body
|
||||
|
||||
# Access uploaded files
|
||||
if message.get("files"):
|
||||
for file in message["files"]:
|
||||
# Process file based on type
|
||||
if file["type"] == "image":
|
||||
# Image processing logic
|
||||
pass
|
||||
|
||||
# Return new files
|
||||
return {
|
||||
"content": "Analysis complete",
|
||||
"files": [
|
||||
{
|
||||
"type": "image",
|
||||
"url": "generated_chart.png",
|
||||
"name": "Analysis Chart"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### User Context and Permissions
|
||||
Actions can access user information and respect permissions:
|
||||
|
||||
```python
|
||||
async def action(self, body: dict, __user__=None):
|
||||
if __user__["role"] != "admin":
|
||||
return {"content": "This action requires admin privileges"}
|
||||
|
||||
user_name = __user__["name"]
|
||||
return {"content": f"Hello {user_name}, admin action completed"}
|
||||
```
|
||||
|
||||
## Example - Specifying Action Frontmatter
|
||||
|
||||
Each Action function can include a docstring at the top to define metadata for the button. This helps customize the display and behavior of your Action in Open WebUI.
|
||||
|
||||
Example of supported frontmatter fields:
|
||||
- `title`: Display name of the Action.
|
||||
- `author`: Name of the creator.
|
||||
- `version`: Version number of the Action.
|
||||
- `required_open_webui_version`: Minimum compatible version of Open WebUI.
|
||||
- `icon_url (optional)`: URL or Base64 string for a custom icon.
|
||||
|
||||
**Base64-Encoded Example:**
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
```python
|
||||
"""
|
||||
title: Enhanced Message Processor
|
||||
author: @admin
|
||||
version: 1.2.0
|
||||
required_open_webui_version: 0.5.0
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMTMuMDkgOC4yNkwyMCA5TDEzLjA5IDE1Ljc0TDEyIDIyTDEwLjkxIDE1Ljc0TDQgOUwxMC45MSA4LjI2TDEyIDJaIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHN2Zz4K
|
||||
requirements: requests,beautifulsoup4
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Action:
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
class Valves(BaseModel):
|
||||
api_key: str = ""
|
||||
processing_mode: str = "standard"
|
||||
|
||||
async def action(
|
||||
self,
|
||||
body: dict,
|
||||
__user__=None,
|
||||
__event_emitter__=None,
|
||||
__event_call__=None,
|
||||
):
|
||||
# Send initial status
|
||||
await __event_emitter__({
|
||||
"type": "status",
|
||||
"data": {"description": "Processing message..."}
|
||||
})
|
||||
|
||||
# Get user confirmation
|
||||
response = await __event_call__({
|
||||
"type": "confirmation",
|
||||
"data": {
|
||||
"title": "Process Message",
|
||||
"message": "Do you want to enhance this message?"
|
||||
}
|
||||
})
|
||||
|
||||
if not response:
|
||||
return {"content": "Action cancelled by user"}
|
||||
|
||||
# Process the message
|
||||
original_content = body.get("content", "")
|
||||
enhanced_content = f"Enhanced: {original_content}"
|
||||
|
||||
return {"content": enhanced_content}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Error Handling
|
||||
Always implement proper error handling in your actions:
|
||||
|
||||
```python
|
||||
async def action(self, body: dict, __event_emitter__=None):
|
||||
try:
|
||||
# Action logic here
|
||||
result = perform_operation()
|
||||
return {"content": f"Success: {result}"}
|
||||
except Exception as e:
|
||||
await __event_emitter__({
|
||||
"type": "notification",
|
||||
"data": {"type": "error", "content": f"Action failed: {str(e)}"}
|
||||
})
|
||||
return {"content": "Action encountered an error"}
|
||||
```
|
||||
|
||||
### Performance Considerations
|
||||
- Use async/await for I/O operations
|
||||
- Implement timeouts for external API calls
|
||||
- Provide progress updates for long-running operations
|
||||
- Consider using background tasks for heavy processing
|
||||
|
||||
### User Experience
|
||||
- Always provide clear feedback through event emitters
|
||||
- Use confirmation dialogs for destructive actions
|
||||
- Include helpful error messages
|
||||
|
||||
## Integration with Open WebUI Features
|
||||
|
||||
Actions integrate seamlessly with other Open WebUI features:
|
||||
- **Models** - Actions can be model-specific or global
|
||||
- **Tools** - Actions can invoke external tools and APIs
|
||||
- **Files** - Actions can process uploaded files and generate new ones
|
||||
- **Memory** - Actions can access conversation history and context
|
||||
- **Permissions** - Actions respect user roles and access controls
|
||||
|
||||
For more examples and community-contributed actions, visit [https://openwebui.com/functions](https://openwebui.com/functions) where you can discover, download, and explore custom functions built by the Open WebUI community.
|
||||
423
docs/features/plugin/functions/filter.mdx
Normal file
423
docs/features/plugin/functions/filter.mdx
Normal file
@@ -0,0 +1,423 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
title: "Filter Function"
|
||||
---
|
||||
|
||||
# 🪄 Filter Function: Modify Inputs and Outputs
|
||||
|
||||
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:
|
||||
|
||||
1. **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.
|
||||
2. **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.
|
||||
3. **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
|
||||
|
||||
```python
|
||||
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}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🆕 🧲 Toggle Filter Example: Adding Interactivity and Icons (New in Open WebUI 0.6.10)
|
||||
|
||||
Filters can do more than simply modify text—they can expose UI toggles and display custom icons. For instance, you might want a filter that can be turned on/off with a user interface button, and displays a special icon in Open WebUI’s message input UI.
|
||||
|
||||
Here’s how you could create such a toggle filter:
|
||||
|
||||
```python
|
||||
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 # IMPORTANT: This creates a switch UI in Open WebUI
|
||||
# TIP: Use 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": "Toggled!",
|
||||
"done": True,
|
||||
"hidden": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
return body
|
||||
```
|
||||
|
||||
#### 🖼️ What’s happening?
|
||||
- **toggle = True** creates a switch UI in Open WebUI—users can manually enable or disable the filter in real time.
|
||||
- **icon** (with a Data URI) will show up as a little image next to the filter’s name. You can use any SVG as long as it’s Data URI encoded!
|
||||
- **The `inlet` function** uses the `__event_emitter__` special argument to broadcast feedback/status to the UI, such as a little toast/notification that reads "Toggled!"
|
||||
|
||||

|
||||
|
||||
You can use these mechanisms to make your filters dynamic, interactive, and visually unique within Open WebUI’s plugin ecosystem.
|
||||
|
||||
---
|
||||
|
||||
### 🎯 Key Components Explained
|
||||
|
||||
#### 1️⃣ **`Valves` Class (Optional Settings)**
|
||||
|
||||
Think of **Valves** as the knobs and sliders for your filter. If you want to give users configurable options to adjust your Filter’s behavior, you define those here.
|
||||
|
||||
```python
|
||||
class Valves(BaseModel):
|
||||
OPTION_NAME: str = "Default Value"
|
||||
```
|
||||
|
||||
For example:
|
||||
If you're creating a filter that converts responses into uppercase, you might allow users to configure whether every output gets totally capitalized via a valve like `TRANSFORM_UPPERCASE: bool = True/False`.
|
||||
|
||||
##### Configuring Valves with Dropdown Menus (Enums)
|
||||
|
||||
You can enhance the user experience for your filter's settings by providing dropdown menus instead of free-form text inputs for certain `Valves`. This is achieved using `json_schema_extra` with the `enum` keyword in your Pydantic `Field` definitions.
|
||||
|
||||
The `enum` keyword allows you to specify a list of predefined values that the UI should present as options in a dropdown.
|
||||
|
||||
**Example:** Creating a dropdown for color themes in a filter.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
# Define your available options (e.g., color themes)
|
||||
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="Choose a predefined color theme for LLM responses. 'Plain (No Color)' disables coloring.",
|
||||
json_schema_extra={"enum": list(COLOR_THEMES.keys())}, # KEY: This creates the dropdown
|
||||
)
|
||||
custom_colors_csv: str = Field(
|
||||
"",
|
||||
description="CSV of colors for 'Custom Sequence' theme (e.g., 'red,blue,green'). Uses xcolor names.",
|
||||
)
|
||||
strip_existing_latex: bool = Field(
|
||||
True,
|
||||
description="If true, attempts to remove existing LaTeX color commands. Recommended to avoid nested rendering issues.",
|
||||
)
|
||||
colorize_type: str = Field(
|
||||
"sequential_word",
|
||||
description="How to apply colors: 'sequential_word' (word by word), 'sequential_line' (line by line), 'per_letter' (letter by letter), 'full_message' (entire message).",
|
||||
json_schema_extra={
|
||||
"enum": [
|
||||
"sequential_word",
|
||||
"sequential_line",
|
||||
"per_letter",
|
||||
"full_message",
|
||||
]
|
||||
}, # Another example of an enum dropdown
|
||||
)
|
||||
color_cycle_reset_per_message: bool = Field(
|
||||
True,
|
||||
description="If true, the color sequence restarts for each new LLM response message. If false, it continues across messages.",
|
||||
)
|
||||
debug_logging: bool = Field(
|
||||
False,
|
||||
description="Enable verbose logging to the console for debugging filter operations.",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
# ... rest of your __init__ logic ...
|
||||
```
|
||||
|
||||
**What's happening?**
|
||||
|
||||
* **`json_schema_extra`**: This argument in `Field` allows you to inject arbitrary JSON Schema properties that Pydantic doesn't explicitly support but can be used by downstream tools (like Open WebUI's UI renderer).
|
||||
* **`"enum": list(COLOR_THEMES.keys())`**: This tells Open WebUI that the `selected_theme` field should present a selection of values, specifically the keys from our `COLOR_THEMES` dictionary. The UI will then render a dropdown menu with "Plain (No Color)", "Monochromatic Blue", "Warm & Energetic", etc., as selectable options.
|
||||
* The `colorize_type` field also demonstrates another `enum` dropdown for different coloring methods.
|
||||
|
||||
Using `enum` for your `Valves` options makes your filters more user-friendly and prevents invalid inputs, leading to a smoother configuration experience.
|
||||
|
||||
---
|
||||
|
||||
#### 2️⃣ **`inlet` Function (Input Pre-Processing)**
|
||||
|
||||
The `inlet` function is like **prepping food before cooking**. Imagine you’re a chef: before the ingredients go into the recipe (the LLM in this case), you might wash vegetables, chop onions, or season the meat. Without this step, your final dish could lack flavor, have unwashed produce, or simply be inconsistent.
|
||||
|
||||
In the world of Open WebUI, the `inlet` function does this important prep work on the **user input** before it’s sent to the model. It ensures the input is as clean, contextual, and helpful as possible for the AI to handle.
|
||||
|
||||
📥 **Input**:
|
||||
- **`body`**: The raw input from Open WebUI to the model. It is in the format of a chat-completion request (usually a dictionary that includes fields like the conversation's messages, model settings, and other metadata). Think of this as your recipe ingredients.
|
||||
|
||||
🚀 **Your Task**:
|
||||
Modify and return the `body`. The modified version of the `body` is what the LLM works with, so this is your chance to bring clarity, structure, and context to the input.
|
||||
|
||||
##### 🍳 Why Would You Use the `inlet`?
|
||||
1. **Adding Context**: Automatically append crucial information to the user’s input, especially if their text is vague or incomplete. For example, you might add "You are a friendly assistant" or "Help this user troubleshoot a software bug."
|
||||
|
||||
2. **Formatting Data**: If the input requires a specific format, like JSON or Markdown, you can transform it before sending it to the model.
|
||||
|
||||
3. **Sanitizing Input**: Remove unwanted characters, strip potentially harmful or confusing symbols (like excessive whitespace or emojis), or replace sensitive information.
|
||||
|
||||
4. **Streamlining User Input**: If your model’s output improves with additional guidance, you can use the `inlet` to inject clarifying instructions automatically!
|
||||
|
||||
##### 💡 Example Use Cases: Build on Food Prep
|
||||
|
||||
###### 🥗 Example 1: Adding System Context
|
||||
Let’s say the LLM is a chef preparing a dish for Italian cuisine, but the user hasn’t mentioned "This is for Italian cooking." You can ensure the message is clear by appending this context before sending the data to the model.
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
📖 **What Happens?**
|
||||
- Any user input like "What are some good dinner ideas?" now carries the Italian theme because we’ve set the system context! Cheesecake might not show up as an answer, but pasta sure will.
|
||||
|
||||
###### 🔪 Example 2: Cleaning Input (Remove Odd Characters)
|
||||
Suppose the input from the user looks messy or includes unwanted symbols like `!!!`, making the conversation inefficient or harder for the model to parse. You can clean it up while preserving the core content.
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
📖 **What Happens?**
|
||||
- Before: `"How can I debug this issue!!!"` ➡️ Sent to the model as `"How can I debug this issue"`
|
||||
|
||||
:::note
|
||||
|
||||
Note: The user feels the same, but the model processes a cleaner and easier-to-understand query.
|
||||
|
||||
:::
|
||||
|
||||
##### 📊 How `inlet` Helps Optimize Input for the LLM:
|
||||
- Improves **accuracy** by clarifying ambiguous queries.
|
||||
- Makes the AI **more efficient** by removing unnecessary noise like emojis, HTML tags, or extra punctuation.
|
||||
- Ensures **consistency** by formatting user input to match the model’s expected patterns or schemas (like, say, JSON for a specific use case).
|
||||
|
||||
💭 **Think of `inlet` as the sous-chef in your kitchen**—ensuring everything that goes into the model (your AI "recipe") has been prepped, cleaned, and seasoned to perfection. The better the input, the better the output!
|
||||
|
||||
---
|
||||
|
||||
#### 🆕 3️⃣ **`stream` Hook (New in Open WebUI 0.5.17)**
|
||||
|
||||
##### 🔄 What is the `stream` Hook?
|
||||
The **`stream` function** is a new feature introduced in Open WebUI **0.5.17** that allows you to **intercept and modify streamed model responses** in real time.
|
||||
|
||||
Unlike `outlet`, which processes an entire completed response, `stream` operates on **individual chunks** as they are received from the model.
|
||||
|
||||
##### 🛠️ When to Use the Stream Hook?
|
||||
- Modify **streaming responses** before they are displayed to users.
|
||||
- Implement **real-time censorship or cleanup**.
|
||||
- **Monitor streamed data** for logging/debugging.
|
||||
|
||||
##### 📜 Example: Logging Streaming Chunks
|
||||
|
||||
Here’s how you can inspect and modify streamed LLM responses:
|
||||
```python
|
||||
def stream(self, event: dict) -> dict:
|
||||
print(event) # Print each incoming chunk for inspection
|
||||
return event
|
||||
```
|
||||
|
||||
> **Example Streamed Events:**
|
||||
```jsonl
|
||||
{"id": "chatcmpl-B4l99MMaP3QLGU5uV7BaBM0eDS0jb","choices": [{"delta": {"content": "Hi"}}]}
|
||||
{"id": "chatcmpl-B4l99MMaP3QLGU5uV7BaBM0eDS0jb","choices": [{"delta": {"content": "!"}}]}
|
||||
{"id": "chatcmpl-B4l99MMaP3QLGU5uV7BaBM0eDS0jb","choices": [{"delta": {"content": " 😊"}}]}
|
||||
```
|
||||
📖 **What Happens?**
|
||||
- Each line represents a **small fragment** of the model's streamed response.
|
||||
- The **`delta.content` field** contains the progressively generated text.
|
||||
|
||||
##### 🔄 Example: Filtering Out Emojis from Streamed Data
|
||||
```python
|
||||
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
|
||||
```
|
||||
📖 **Before:** `"Hi 😊"`
|
||||
📖 **After:** `"Hi"`
|
||||
|
||||
---
|
||||
|
||||
#### 4️⃣ **`outlet` Function (Output Post-Processing)**
|
||||
|
||||
The `outlet` function is like a **proofreader**: tidy up the AI's response (or make final changes) *after it’s processed by the LLM.*
|
||||
|
||||
📤 **Input**:
|
||||
- **`body`**: This contains **all current messages** in the chat (user history + LLM replies).
|
||||
|
||||
🚀 **Your Task**: Modify this `body`. You can clean, append, or log changes, but be mindful of how each adjustment impacts the user experience.
|
||||
|
||||
💡 **Best Practices**:
|
||||
- Prefer logging over direct edits in the outlet (e.g., for debugging or analytics).
|
||||
- If heavy modifications are needed (like formatting outputs), consider using the **pipe function** instead.
|
||||
|
||||
💡 **Example Use Case**: Strip out sensitive API responses you don't want the user to see:
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Filters in Action: Building Practical Examples
|
||||
|
||||
Let’s build some real-world examples to see how you’d use Filters!
|
||||
|
||||
### 📚 Example #1: Add Context to Every User Input
|
||||
|
||||
Want the LLM to always know it's assisting a customer in troubleshooting software bugs? You can add instructions like **"You're a software troubleshooting assistant"** to every user query.
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📚 Example #2: Highlight Outputs for Easy Reading
|
||||
|
||||
Returning output in Markdown or another formatted style? Use the `outlet` function!
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚧 Potential Confusion: Clear FAQ 🛑
|
||||
|
||||
### **Q: How Are Filters Different From Pipe Functions?**
|
||||
|
||||
Filters modify data **going to** and **coming from models** but do not significantly interact with logic outside of these phases. Pipes, on the other hand:
|
||||
- Can integrate **external APIs** or significantly transform how the backend handles operations.
|
||||
- Expose custom logic as entirely new "models."
|
||||
|
||||
### **Q: Can I Do Heavy Post-Processing Inside `outlet`?**
|
||||
|
||||
You can, but **it’s not the best practice.**:
|
||||
- **Filters** are designed to make lightweight changes or apply logging.
|
||||
- If heavy modifications are required, consider a **Pipe Function** instead.
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Recap: Why Build Filter Functions?
|
||||
|
||||
By now, you’ve learned:
|
||||
1. **Inlet** manipulates **user inputs** (pre-processing).
|
||||
2. **Stream** intercepts and modifies **streamed model outputs** (real-time).
|
||||
3. **Outlet** tweaks **AI outputs** (post-processing).
|
||||
4. Filters are best for lightweight, real-time alterations to the data flow.
|
||||
5. With **Valves**, you empower users to configure Filters dynamically for tailored behavior.
|
||||
|
||||
---
|
||||
|
||||
🚀 **Your Turn**: Start experimenting! What small tweak or context addition could elevate your Open WebUI experience? Filters are fun to build, flexible to use, and can take your models to the next level!
|
||||
|
||||
Happy coding! ✨
|
||||
133
docs/features/plugin/functions/index.mdx
Normal file
133
docs/features/plugin/functions/index.mdx
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
title: "Functions"
|
||||
---
|
||||
|
||||
## 🚀 What Are Functions?
|
||||
|
||||
Functions are like **plugins** for Open WebUI. They help you **extend its capabilities**—whether it’s adding support for new AI model providers like Anthropic or Vertex AI, tweaking how messages are processed, or introducing custom buttons to the interface for better usability.
|
||||
|
||||
Unlike external tools that may require complex integrations, **Functions are built-in and run within the Open WebUI environment.** That means they are fast, modular, and don’t rely on external dependencies.
|
||||
|
||||
Think of Functions as **modular building blocks** that let you enhance how the WebUI works, tailored exactly to what you need. They’re lightweight, highly customizable, and written in **pure Python**, so you have the freedom to create anything—from new AI-powered workflows to integrations with anything you use, like Google Search or Home Assistant.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Types of Functions
|
||||
|
||||
There are **three types of Functions** in Open WebUI, each with a specific purpose. Let’s break them down and explain exactly what they do:
|
||||
|
||||
---
|
||||
|
||||
### 1. [**Pipe Function** – Create Custom "Agents/Models"](./pipe.mdx)
|
||||
|
||||
A **Pipe Function** is how you create **custom agents/models** or integrations, which then appear in the interface as if they were standalone models.
|
||||
|
||||
**What does it do?**
|
||||
- Pipes let you define complex workflows. For instance, you could create a Pipe that sends data to **Model A** and **Model B**, processes their outputs, and combines the results into one finalized answer.
|
||||
- Pipes don’t even have to use AI! They can be setups for **search APIs**, **weather data**, or even systems like **Home Assistant**. Basically, anything you’d like to interact with can become part of Open WebUI.
|
||||
|
||||
**Use case example:**
|
||||
Imagine you want to query Google Search directly from Open WebUI. You can create a Pipe Function that:
|
||||
1. Takes your message as the search query.
|
||||
2. Sends the query to Google Search’s API.
|
||||
3. Processes the response and returns it to you inside the WebUI like a normal "model" response.
|
||||
|
||||
When enabled, **Pipe Functions show up as their own selectable model**. Use Pipes whenever you need custom functionality that works like a model in the interface.
|
||||
|
||||
For a detailed guide, see [**Pipe Functions**](./pipe.mdx).
|
||||
|
||||
---
|
||||
|
||||
### 2. [**Filter Function** – Modify Inputs and Outputs](./filter.mdx)
|
||||
|
||||
A **Filter Function** is like a tool for tweaking data before it gets sent to the AI **or** after it comes back.
|
||||
|
||||
**What does it do?**
|
||||
Filters act as "hooks" in the workflow and have two main parts:
|
||||
- **Inlet**: Adjust the input that is sent to the model. For example, adding additional instructions, keywords, or formatting tweaks.
|
||||
- **Outlet**: Modify the output that you receive from the model. For instance, cleaning up the response, adjusting tone, or formatting data into a specific style.
|
||||
|
||||
**Use case example:**
|
||||
Suppose you’re working on a project that needs precise formatting. You can use a Filter to ensure:
|
||||
1. Your input is always transformed into the required format.
|
||||
2. The output from the model is cleaned up before being displayed.
|
||||
|
||||
Filters are **linked to specific models** or can be enabled for all models **globally**, depending on your needs.
|
||||
|
||||
Check out the full guide for more examples and instructions: [**Filter Functions**](./filter.mdx).
|
||||
|
||||
---
|
||||
|
||||
### 3. [**Action Function** – Add Custom Buttons](./action.mdx)
|
||||
|
||||
An **Action Function** is used to add **custom buttons** to the chat interface.
|
||||
|
||||
**What does it do?**
|
||||
Actions allow you to define **interactive shortcuts** that trigger specific functionality directly from the chat. These buttons appear underneath individual chat messages, giving you convenient, one-click access to the actions you define.
|
||||
|
||||
**Use case example:**
|
||||
Let’s say you often need to summarize long messages or generate specific outputs like translations. You can create an Action Function to:
|
||||
1. Add a “Summarize” button under every incoming message.
|
||||
2. When clicked, it triggers your custom function to process that message and return the summary.
|
||||
|
||||
Buttons provide a **clean and user-friendly way** to interact with extended functionality you define.
|
||||
|
||||
Learn how to set them up in the [**Action Functions Guide**](./action.mdx).
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ How to Use Functions
|
||||
|
||||
Here's how to put Functions to work in Open WebUI:
|
||||
|
||||
### 1. **Install Functions**
|
||||
You can install Functions via the Open WebUI interface or by importing them manually. You can find community-created functions on the [Open WebUI Community Site](https://openwebui.com/functions).
|
||||
|
||||
⚠️ **Be cautious.** Only install Functions from trusted sources. Running unknown code poses security risks.
|
||||
|
||||
---
|
||||
|
||||
### 2. **Enable Functions**
|
||||
Functions must be explicitly enabled after installation:
|
||||
- When you enable a **Pipe Function**, it becomes available as its own **model** in the interface.
|
||||
- For **Filter** and **Action Functions**, enabling them isn’t enough—you also need to assign them to specific models or enable them globally for all models.
|
||||
|
||||
---
|
||||
|
||||
### 3. **Assign Filters or Actions to Models**
|
||||
- Navigate to `Workspace => Models` and assign your Filter or Action to the relevant model there.
|
||||
- Alternatively, enable Functions for **all models globally** by going to `Workspace => Functions`, selecting the "..." menu, and toggling the **Global** switch.
|
||||
|
||||
---
|
||||
|
||||
### Quick Summary
|
||||
- **Pipes** appear as standalone models you can interact with.
|
||||
- **Filters** modify inputs/outputs for smoother AI interactions.
|
||||
- **Actions** add clickable buttons to individual chat messages.
|
||||
|
||||
Once you’ve followed the setup process, Functions will seamlessly enhance your workflows.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Why Use Functions?
|
||||
|
||||
Functions are designed for anyone who wants to **unlock new possibilities** with Open WebUI:
|
||||
|
||||
- **Extend**: Add new models or integrate with non-AI tools like APIs, databases, or smart devices.
|
||||
- **Optimize**: Tweak inputs and outputs to fit your use case perfectly.
|
||||
- **Simplify**: Add buttons or shortcuts to make the interface intuitive and efficient.
|
||||
|
||||
Whether you’re customizing workflows for specific projects, integrating external data, or just making Open WebUI easier to use, Functions are the key to taking control of your instance.
|
||||
|
||||
---
|
||||
|
||||
### 📝 Final Notes:
|
||||
1. Always install Functions from **trusted sources only**.
|
||||
2. Make sure you understand the difference between Pipe, Filter, and Action Functions to use them effectively.
|
||||
3. Explore the official guides:
|
||||
- [Pipe Functions Guide](./pipe.mdx)
|
||||
- [Filter Functions Guide](./filter.mdx)
|
||||
- [Action Functions Guide](./action.mdx)
|
||||
|
||||
By leveraging Functions, you’ll bring entirely new capabilities to your Open WebUI setup. Start experimenting today! 🚀
|
||||
400
docs/features/plugin/functions/pipe.mdx
Normal file
400
docs/features/plugin/functions/pipe.mdx
Normal file
@@ -0,0 +1,400 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
title: "Pipe Function"
|
||||
---
|
||||
|
||||
# 🚰 Pipe Function: Create Custom "Agents/Models"
|
||||
Welcome to this guide on creating **Pipes** in Open WebUI! Think of Pipes as a way to **adding** a new model to Open WebUI. In this document, we'll break down what a Pipe is, how it works, and how you can create your own to add custom logic and processing to your Open WebUI models. We'll use clear metaphors and go through every detail to ensure you have a comprehensive understanding.
|
||||
|
||||
## Introduction to Pipes
|
||||
|
||||
Imagine Open WebUI as a **plumbing system** where data flows through pipes and valves. In this analogy:
|
||||
|
||||
- **Pipes** are like **plugins** that let you introduce new pathways for data to flow, allowing you to inject custom logic and processing.
|
||||
- **Valves** are the **configurable parts** of your pipe that control how data flows through it.
|
||||
|
||||
By creating a Pipe, you're essentially crafting a custom model with the specific behavior you want, all within the Open WebUI framework.
|
||||
|
||||
---
|
||||
|
||||
## Understanding the Pipe Structure
|
||||
|
||||
Let's start with a basic, barebones version of a Pipe to understand its structure:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Pipe:
|
||||
class Valves(BaseModel):
|
||||
MODEL_ID: str = Field(default="")
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
def pipe(self, body: dict):
|
||||
# Logic goes here
|
||||
print(self.valves, body) # This will print the configuration options and the input body
|
||||
return "Hello, World!"
|
||||
```
|
||||
|
||||
### The Pipe Class
|
||||
|
||||
- **Definition**: The `Pipe` class is where you define your custom logic.
|
||||
- **Purpose**: Acts as the blueprint for your plugin, determining how it behaves within Open WebUI.
|
||||
|
||||
### Valves: Configuring Your Pipe
|
||||
|
||||
- **Definition**: `Valves` is a nested class within `Pipe`, inheriting from `BaseModel`.
|
||||
- **Purpose**: It contains the configuration options (parameters) that persist across the use of your Pipe.
|
||||
- **Example**: In the above code, `MODEL_ID` is a configuration option with a default empty string.
|
||||
|
||||
**Metaphor**: Think of Valves as the knobs on a real-world pipe system that control the flow of water. In your Pipe, Valves allow users to adjust settings that influence how the data flows and is processed.
|
||||
|
||||
### The `__init__` Method
|
||||
|
||||
- **Definition**: The constructor method for the `Pipe` class.
|
||||
- **Purpose**: Initializes the Pipe's state and sets up any necessary components.
|
||||
- **Best Practice**: Keep it simple; primarily initialize `self.valves` here.
|
||||
|
||||
```python
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
```
|
||||
|
||||
### The `pipe` Function
|
||||
|
||||
- **Definition**: The core function where your custom logic resides.
|
||||
- **Parameters**:
|
||||
- `body`: A dictionary containing the input data.
|
||||
- **Purpose**: Processes the input data using your custom logic and returns the result.
|
||||
|
||||
```python
|
||||
def pipe(self, body: dict):
|
||||
# Logic goes here
|
||||
print(self.valves, body) # This will print the configuration options and the input body
|
||||
return "Hello, World!"
|
||||
```
|
||||
|
||||
**Note**: Always place `Valves` at the top of your `Pipe` class, followed by `__init__`, and then the `pipe` function. This structure ensures clarity and consistency.
|
||||
|
||||
---
|
||||
|
||||
## Creating Multiple Models with Pipes
|
||||
|
||||
What if you want your Pipe to create **multiple models** within Open WebUI? You can achieve this by defining a `pipes` function or variable inside your `Pipe` class. This setup, informally called a **manifold**, allows your Pipe to represent multiple models.
|
||||
|
||||
Here's how you can do it:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Pipe:
|
||||
class Valves(BaseModel):
|
||||
MODEL_ID: str = Field(default="")
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
def pipes(self):
|
||||
return [
|
||||
{"id": "model_id_1", "name": "model_1"},
|
||||
{"id": "model_id_2", "name": "model_2"},
|
||||
{"id": "model_id_3", "name": "model_3"},
|
||||
]
|
||||
|
||||
def pipe(self, body: dict):
|
||||
# Logic goes here
|
||||
print(self.valves, body) # Prints the configuration options and the input body
|
||||
model = body.get("model", "")
|
||||
return f"{model}: Hello, World!"
|
||||
```
|
||||
|
||||
### Explanation
|
||||
|
||||
- **`pipes` Function**:
|
||||
- Returns a list of dictionaries.
|
||||
- Each dictionary represents a model with unique `id` and `name` keys.
|
||||
- These models will show up individually in the Open WebUI model selector.
|
||||
|
||||
- **Updated `pipe` Function**:
|
||||
- Processes input based on the selected model.
|
||||
- In this example, it includes the model name in the returned string.
|
||||
|
||||
---
|
||||
|
||||
## Example: OpenAI Proxy Pipe
|
||||
|
||||
Let's dive into a practical example where we'll create a Pipe that proxies requests to the OpenAI API. This Pipe will fetch available models from OpenAI and allow users to interact with them through Open WebUI.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
import requests
|
||||
|
||||
class Pipe:
|
||||
class Valves(BaseModel):
|
||||
NAME_PREFIX: str = Field(
|
||||
default="OPENAI/",
|
||||
description="Prefix to be added before model names.",
|
||||
)
|
||||
OPENAI_API_BASE_URL: str = Field(
|
||||
default="https://api.openai.com/v1",
|
||||
description="Base URL for accessing OpenAI API endpoints.",
|
||||
)
|
||||
OPENAI_API_KEY: str = Field(
|
||||
default="",
|
||||
description="API key for authenticating requests to the OpenAI API.",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
def pipes(self):
|
||||
if self.valves.OPENAI_API_KEY:
|
||||
try:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.valves.OPENAI_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
r = requests.get(
|
||||
f"{self.valves.OPENAI_API_BASE_URL}/models", headers=headers
|
||||
)
|
||||
models = r.json()
|
||||
return [
|
||||
{
|
||||
"id": model["id"],
|
||||
"name": f'{self.valves.NAME_PREFIX}{model.get("name", model["id"])}',
|
||||
}
|
||||
for model in models["data"]
|
||||
if "gpt" in model["id"]
|
||||
]
|
||||
|
||||
except Exception as e:
|
||||
return [
|
||||
{
|
||||
"id": "error",
|
||||
"name": "Error fetching models. Please check your API Key.",
|
||||
},
|
||||
]
|
||||
else:
|
||||
return [
|
||||
{
|
||||
"id": "error",
|
||||
"name": "API Key not provided.",
|
||||
},
|
||||
]
|
||||
|
||||
def pipe(self, body: dict, __user__: dict):
|
||||
print(f"pipe:{__name__}")
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.valves.OPENAI_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# Extract model id from the model name
|
||||
model_id = body["model"][body["model"].find(".") + 1 :]
|
||||
|
||||
# Update the model id in the body
|
||||
payload = {**body, "model": model_id}
|
||||
try:
|
||||
r = requests.post(
|
||||
url=f"{self.valves.OPENAI_API_BASE_URL}/chat/completions",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
stream=True,
|
||||
)
|
||||
|
||||
r.raise_for_status()
|
||||
|
||||
if body.get("stream", False):
|
||||
return r.iter_lines()
|
||||
else:
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
```
|
||||
|
||||
### Detailed Breakdown
|
||||
|
||||
#### Valves Configuration
|
||||
|
||||
- **`NAME_PREFIX`**:
|
||||
- Adds a prefix to the model names displayed in Open WebUI.
|
||||
- Default: `"OPENAI/"`.
|
||||
- **`OPENAI_API_BASE_URL`**:
|
||||
- Specifies the base URL for the OpenAI API.
|
||||
- Default: `"https://api.openai.com/v1"`.
|
||||
- **`OPENAI_API_KEY`**:
|
||||
- Your OpenAI API key for authentication.
|
||||
- Default: `""` (empty string; must be provided).
|
||||
|
||||
#### The `pipes` Function
|
||||
|
||||
- **Purpose**: Fetches available OpenAI models and makes them accessible in Open WebUI.
|
||||
|
||||
- **Process**:
|
||||
1. **Check for API Key**: Ensures that an API key is provided.
|
||||
2. **Fetch Models**: Makes a GET request to the OpenAI API to retrieve available models.
|
||||
3. **Filter Models**: Returns models that have `"gpt"` in their `id`.
|
||||
4. **Error Handling**: If there's an issue, returns an error message.
|
||||
|
||||
- **Return Format**: A list of dictionaries with `id` and `name` for each model.
|
||||
|
||||
#### The `pipe` Function
|
||||
|
||||
- **Purpose**: Handles the request to the selected OpenAI model and returns the response.
|
||||
|
||||
- **Parameters**:
|
||||
- `body`: Contains the request data.
|
||||
- `__user__`: Contains user information (not used in this example but can be useful for authentication or logging).
|
||||
|
||||
- **Process**:
|
||||
1. **Prepare Headers**: Sets up the headers with the API key and content type.
|
||||
2. **Extract Model ID**: Extracts the actual model ID from the selected model name.
|
||||
3. **Prepare Payload**: Updates the body with the correct model ID.
|
||||
4. **Make API Request**: Sends a POST request to the OpenAI API's chat completions endpoint.
|
||||
5. **Handle Streaming**: If `stream` is `True`, returns an iterable of lines.
|
||||
6. **Error Handling**: Catches exceptions and returns an error message.
|
||||
|
||||
### Extending the Proxy Pipe
|
||||
|
||||
You can modify this proxy Pipe to support additional service providers like Anthropic, Perplexity, and more by adjusting the API endpoints, headers, and logic within the `pipes` and `pipe` functions.
|
||||
|
||||
---
|
||||
|
||||
## Using Internal Open WebUI Functions
|
||||
|
||||
Sometimes, you may want to leverage the internal functions of Open WebUI within your Pipe. You can import these functions directly from the `open_webui` package. Keep in mind that while unlikely, internal functions may change for optimization purposes, so always refer to the latest documentation.
|
||||
|
||||
Here's how you can use internal Open WebUI functions:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
from fastapi import Request
|
||||
|
||||
from open_webui.models.users import Users
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
|
||||
class Pipe:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def pipe(
|
||||
self,
|
||||
body: dict,
|
||||
__user__: dict,
|
||||
__request__: Request,
|
||||
) -> str:
|
||||
# Use the unified endpoint with the updated signature
|
||||
user = Users.get_user_by_id(__user__["id"])
|
||||
body["model"] = "llama3.2:latest"
|
||||
return await generate_chat_completion(__request__, body, user)
|
||||
```
|
||||
|
||||
### Explanation
|
||||
|
||||
- **Imports**:
|
||||
- `Users` from `open_webui.models.users`: To fetch user information.
|
||||
- `generate_chat_completion` from `open_webui.utils.chat`: To generate chat completions using internal logic.
|
||||
|
||||
- **Asynchronous `pipe` Function**:
|
||||
- **Parameters**:
|
||||
- `body`: Input data for the model.
|
||||
- `__user__`: Dictionary containing user information.
|
||||
- `__request__`: The request object from FastAPI (required by `generate_chat_completion`).
|
||||
- **Process**:
|
||||
1. **Fetch User Object**: Retrieves the user object using their ID.
|
||||
2. **Set Model**: Specifies the model to be used.
|
||||
3. **Generate Completion**: Calls `generate_chat_completion` to process the input and produce an output.
|
||||
|
||||
### Important Notes
|
||||
|
||||
- **Function Signatures**: Refer to the latest Open WebUI codebase or documentation for the most accurate function signatures and parameters.
|
||||
- **Best Practices**: Always handle exceptions and errors gracefully to ensure a smooth user experience.
|
||||
|
||||
---
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### Q1: Why should I use Pipes in Open WebUI?
|
||||
|
||||
**A**: Pipes allow you to add new "model" with custom logic and processing to Open WebUI. It's a flexible plugin system that lets you integrate external APIs, customize model behaviors, and create innovative features without altering the core codebase.
|
||||
|
||||
---
|
||||
|
||||
### Q2: What are Valves, and why are they important?
|
||||
|
||||
**A**: Valves are the configurable parameters of your Pipe. They function like settings or controls that determine how your Pipe operates. By adjusting Valves, you can change the behavior of your Pipe without modifying the underlying code.
|
||||
|
||||
---
|
||||
|
||||
### Q3: Can I create a Pipe without Valves?
|
||||
|
||||
**A**: Yes, you can create a simple Pipe without defining a Valves class if your Pipe doesn't require any persistent configuration options. However, including Valves is a good practice for flexibility and future scalability.
|
||||
|
||||
---
|
||||
|
||||
### Q4: How do I ensure my Pipe is secure when using API keys?
|
||||
|
||||
**A**: Never hard-code sensitive information like API keys into your Pipe. Instead, use Valves to input and store API keys securely. Ensure that your code handles these keys appropriately and avoids logging or exposing them.
|
||||
|
||||
---
|
||||
|
||||
### Q5: What is the difference between the `pipe` and `pipes` functions?
|
||||
|
||||
**A**:
|
||||
|
||||
- **`pipe` Function**: The primary function where you process the input data and generate an output. It handles the logic for a single model.
|
||||
|
||||
- **`pipes` Function**: Allows your Pipe to represent multiple models by returning a list of model definitions. Each model will appear individually in Open WebUI.
|
||||
|
||||
---
|
||||
|
||||
### Q6: How can I handle errors in my Pipe?
|
||||
|
||||
**A**: Use try-except blocks within your `pipe` and `pipes` functions to catch exceptions. Return meaningful error messages or handle the errors gracefully to ensure the user is informed about what went wrong.
|
||||
|
||||
---
|
||||
|
||||
### Q7: Can I use external libraries in my Pipe?
|
||||
|
||||
**A**: Yes, you can import and use external libraries as needed. Ensure that any dependencies are properly installed and managed within your environment.
|
||||
|
||||
---
|
||||
|
||||
### Q8: How do I test my Pipe?
|
||||
|
||||
**A**: Test your Pipe by running Open WebUI in a development environment and selecting your custom model from the interface. Validate that your Pipe behaves as expected with various inputs and configurations.
|
||||
|
||||
---
|
||||
|
||||
### Q9: Are there any best practices for organizing my Pipe's code?
|
||||
|
||||
**A**: Yes, follow these guidelines:
|
||||
|
||||
- Keep `Valves` at the top of your `Pipe` class.
|
||||
- Initialize variables in the `__init__` method, primarily `self.valves`.
|
||||
- Place the `pipe` function after the `__init__` method.
|
||||
- Use clear and descriptive variable names.
|
||||
- Comment your code for clarity.
|
||||
|
||||
---
|
||||
|
||||
### Q10: Where can I find the latest Open WebUI documentation?
|
||||
|
||||
**A**: Visit the official Open WebUI repository or documentation site for the most up-to-date information, including function signatures, examples, and migration guides if any changes occur.
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
By now, you should have a thorough understanding of how to create and use Pipes in Open WebUI. Pipes offer a powerful way to extend and customize the capabilities of Open WebUI to suit your specific needs. Whether you're integrating external APIs, adding new models, or injecting complex logic, Pipes provide the flexibility to make it happen.
|
||||
|
||||
Remember to:
|
||||
|
||||
- **Use clear and consistent structure** in your Pipe classes.
|
||||
- **Leverage Valves** for configurable options.
|
||||
- **Handle errors gracefully** to improve the user experience.
|
||||
- **Consult the latest documentation** for any updates or changes.
|
||||
|
||||
Happy coding, and enjoy extending your Open WebUI with Pipes!
|
||||
91
docs/features/plugin/index.mdx
Normal file
91
docs/features/plugin/index.mdx
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
sidebar_position: 300
|
||||
title: "Tools & Functions (Plugins)"
|
||||
---
|
||||
|
||||
# 🛠️ Tools & Functions
|
||||
|
||||
Imagine you've just stumbled upon Open WebUI, or maybe you're already using it, but you're a bit lost with all the talk about "Tools", "Functions", and "Pipelines". Everything sounds like some mysterious tech jargon, right? No worries! Let's break it down piece by piece, super clearly, step by step. By the end of this, you'll have a solid understanding of what these terms mean, how they work, and why know it's not as complicated as it seems.
|
||||
|
||||
## TL;DR
|
||||
|
||||
- **Tools** extend the abilities of LLMs, allowing them to collect real-world, real-time data like weather, stock prices, etc.
|
||||
- **Functions** extend the capabilities of the Open WebUI itself, enabling you to add new AI model support (like Anthropic or Vertex AI) or improve usability (like creating custom buttons or filters).
|
||||
- **Pipelines** are more for advanced users who want to transform Open WebUI features into API-compatible workflows—mainly for offloading heavy processing.
|
||||
|
||||
Getting started with Tools and Functions is easy because everything’s already built into the core system! You just **click a button** and **import these features directly from the community**, so there’s no coding or deep technical work required.
|
||||
|
||||
## What are "Tools" and "Functions"?
|
||||
|
||||
Let's start by thinking of **Open WebUI** as a "base" software that can do many tasks related to using Large Language Models (LLMs). But sometimes, you need extra features or abilities that don't come *out of the box*—this is where **tools** and **functions** come into play.
|
||||
|
||||
### Tools
|
||||
|
||||
**Tools** are an exciting feature because they allow LLMs to do more than just process text. They provide **external abilities** that LLMs wouldn't otherwise have on their own.
|
||||
|
||||
#### Example of a Tool:
|
||||
|
||||
Imagine you're chatting with an LLM and you want it to give you the latest weather update or stock prices in real time. Normally, the LLM can't do that because it's just working on pre-trained knowledge. This is where **tools** come in!
|
||||
|
||||
- **Tools are like plugins** that the LLM can use to gather **real-world, real-time data**. So, with a "weather tool" enabled, the model can go out on the internet, gather live weather data, and display it in your conversation.
|
||||
|
||||
Tools are essentially **abilities** you’re giving your AI to help it interact with the outside world. By adding these, the LLM can "grab" useful information or perform specialized tasks based on the context of the conversation.
|
||||
|
||||
#### Examples of Tools (extending LLM’s abilities):
|
||||
|
||||
1. **Real-time weather predictions** 🛰️.
|
||||
2. **Stock price retrievers** 📈.
|
||||
3. **Flight tracking information** ✈️.
|
||||
|
||||
### Functions
|
||||
|
||||
While **tools** are used by the AI during a conversation, **functions** help extend or customize the capabilities of Open WebUI itself. Imagine tools are like adding new ingredients to a dish, and functions are the process you use to control the kitchen! 🚪
|
||||
|
||||
#### Let's break that down:
|
||||
|
||||
- **Functions** give you the ability to tweak or add **features** inside **Open WebUI** itself.
|
||||
- You’re not giving new abilities to the LLM, but instead, you’re extending the **interface, behavior, or logic** of the platform itself!
|
||||
|
||||
For instance, maybe you want to:
|
||||
|
||||
1. Add a new AI model like **Anthropic** to the WebUI.
|
||||
2. Create a custom button in your toolbar that performs a frequently used command.
|
||||
3. Implement a better **filter** function that catches inappropriate or **spammy messages** from the incoming text.
|
||||
|
||||
Without functions, these would all be out of reach. But with this framework in Open WebUI, you can easily extend these features!
|
||||
|
||||
### Where to Find and Manage Functions
|
||||
|
||||
Functions are not located in the same place as Tools.
|
||||
|
||||
- **Tools** are about model access and live in your **Workspace tabs** (where you add models, prompts, and knowledge collections). They can be added by users if granted permissions.
|
||||
- **Functions** are about **platform customization** and are found in the **Admin Panel**.
|
||||
They are configured and managed only by admins who want to extend the platform interface or behavior for all users.
|
||||
|
||||
### Summary of Differences:
|
||||
|
||||
- **Tools** are things that allow LLMs to **do more things** outside their default abilities (such as retrieving live info or performing custom tasks based on external data).
|
||||
- **Functions** help the WebUI itself **do more things**, like adding new AI models or creating smarter ways to filter data.
|
||||
|
||||
Both are designed to be **pluggable**, meaning you can easily import them into your system with just one click from the community! 🎉 You won’t have to spend hours coding or tinkering with them.
|
||||
|
||||
## What are Pipelines?
|
||||
|
||||
And then, we have **Pipelines**… Here’s where things start to sound pretty technical—but don’t despair.
|
||||
|
||||
**Pipelines** are part of an Open WebUI initiative focused on making every piece of the WebUI **inter-operable with OpenAI’s API system**. Essentially, they extend what both **Tools** and **Functions** can already do, but now with even more flexibility. They allow you to turn features into OpenAI API-compatible formats. 🧠
|
||||
|
||||
### But here’s the thing…
|
||||
|
||||
You likely **won't need** pipelines unless you're dealing with super-advanced setups.
|
||||
|
||||
- **Who are pipelines for?** Typically, **experts** or people running more complicated use cases.
|
||||
- **When do you need them?** If you're trying to offload processing from your primary Open WebUI instance to a different machine (so you don’t overload your primary system).
|
||||
|
||||
In most cases, as a beginner or even an intermediate user, you won’t have to worry about pipelines. Just focus on enjoying the benefits that **tools** and **functions** bring to your Open WebUI experience!
|
||||
|
||||
## Want to Try? 🚀
|
||||
|
||||
Jump into Open WebUI, head over to the community section, and try importing a tool like **weather updates** or maybe adding a new feature to the toolbar with a function. Exploring these tools will show you how powerful and flexible Open WebUI can be!
|
||||
|
||||
🌟 There's always more to learn, so stay curious and keep experimenting!
|
||||
255
docs/features/plugin/migration/index.mdx
Normal file
255
docs/features/plugin/migration/index.mdx
Normal file
@@ -0,0 +1,255 @@
|
||||
---
|
||||
sidebar_position: 9999
|
||||
title: "Migrating Tools & Functions: 0.4 to 0.5"
|
||||
---
|
||||
|
||||
# 🚚 Migration Guide: Open WebUI 0.4 to 0.5
|
||||
|
||||
Welcome to the Open WebUI 0.5 migration guide! If you're working on existing projects or building new ones, this guide will walk you through the key changes from **version 0.4 to 0.5** and provide an easy-to-follow roadmap for upgrading your Functions. Let's make this transition as smooth as possible! 😊
|
||||
|
||||
---
|
||||
|
||||
## 🧐 What Has Changed and Why?
|
||||
|
||||
With Open WebUI 0.5, we’ve overhauled the architecture to make the project **simpler, more unified, and scalable**. Here's the big picture:
|
||||
|
||||
- **Old Architecture:** 🎯 Previously, Open WebUI was built on a **sub-app architecture** where each app (e.g., `ollama`, `openai`) was a separate FastAPI application. This caused fragmentation and extra complexity when managing apps.
|
||||
- **New Architecture:** 🚀 With version 0.5, we have transitioned to a **single FastAPI app** with multiple **routers**. This means better organization, centralized flow, and reduced redundancy.
|
||||
|
||||
### Key Changes:
|
||||
Here’s an overview of what changed:
|
||||
1. **Apps have been moved to Routers.**
|
||||
- Previous: `open_webui.apps`
|
||||
- Now: `open_webui.routers`
|
||||
|
||||
2. **Main app structure simplified.**
|
||||
- The old `open_webui.apps.webui` has been transformed into `open_webui.main`, making it the central entry point for the project.
|
||||
|
||||
3. **Unified API Endpoint**
|
||||
- Open WebUI 0.5 introduces a **unified function**, `chat_completion`, in `open_webui.main`, replacing separate functions for models like `ollama` and `openai`. This offers a consistent and streamlined API experience. However, the **direct successor** of these individual functions is `generate_chat_completion` from `open_webui.utils.chat`. If you prefer a lightweight POST request without handling additional parsing (e.g., files, tools, or misc), this utility function is likely what you want.
|
||||
|
||||
#### Example:
|
||||
```python
|
||||
|
||||
# Full API flow with parsing (new function):
|
||||
from open_webui.main import chat_completion
|
||||
|
||||
# Lightweight, direct POST request (direct successor):
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
```
|
||||
|
||||
Choose the approach that best fits your use case!
|
||||
|
||||
4. **Updated Function Signatures.**
|
||||
- Function signatures now adhere to a new format, requiring a `request` object.
|
||||
- The `request` object can be obtained using the `__request__` parameter in the function signature. Below is an example:
|
||||
|
||||
```python
|
||||
class Pipe:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def pipe(
|
||||
self,
|
||||
body: dict,
|
||||
__user__: dict,
|
||||
__request__: Request, # New parameter
|
||||
) -> str:
|
||||
# Write your function here
|
||||
```
|
||||
|
||||
📌 **Why did we make these changes?**
|
||||
- To simplify the codebase, making it easier to extend and maintain.
|
||||
- To unify APIs for a more streamlined developer experience.
|
||||
- To enhance performance by consolidating redundant elements.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Step-by-Step Migration Guide
|
||||
|
||||
Follow this guide to smoothly update your project.
|
||||
|
||||
---
|
||||
|
||||
### 🔄 1. Shifting from `apps` to `routers`
|
||||
|
||||
All apps have been renamed and relocated under `open_webui.routers`. This affects imports in your codebase.
|
||||
|
||||
Quick changes for import paths:
|
||||
|
||||
| **Old Path** | **New Path** |
|
||||
|-----------------------------------|-----------------------------------|
|
||||
| `open_webui.apps.ollama` | `open_webui.routers.ollama` |
|
||||
| `open_webui.apps.openai` | `open_webui.routers.openai` |
|
||||
| `open_webui.apps.audio` | `open_webui.routers.audio` |
|
||||
| `open_webui.apps.retrieval` | `open_webui.routers.retrieval` |
|
||||
| `open_webui.apps.webui` | `open_webui.main` |
|
||||
|
||||
### 📜 An Important Example
|
||||
|
||||
To clarify the special case of the main app (`webui`), here’s a simple rule of thumb:
|
||||
|
||||
- **Was in `webui`?** It’s now in the project’s root or `open_webui.main`.
|
||||
- For example:
|
||||
- **Before (0.4):**
|
||||
```python
|
||||
from open_webui.apps.webui.models import SomeModel
|
||||
```
|
||||
- **After (0.5):**
|
||||
```python
|
||||
from open_webui.models import SomeModel
|
||||
```
|
||||
|
||||
In general, **just replace `open_webui.apps` with `open_webui.routers`—except for `webui`, which is now `open_webui.main`!**
|
||||
|
||||
---
|
||||
|
||||
### 👩💻 2. Updating Import Statements
|
||||
|
||||
Let’s look at what this update looks like in your code:
|
||||
|
||||
#### Before:
|
||||
```python
|
||||
from open_webui.apps.ollama import main as ollama
|
||||
from open_webui.apps.openai import main as openai
|
||||
```
|
||||
|
||||
#### After:
|
||||
```python
|
||||
|
||||
# Separate router imports
|
||||
from open_webui.routers.ollama import generate_chat_completion
|
||||
from open_webui.routers.openai import generate_chat_completion
|
||||
|
||||
# Or use the unified endpoint
|
||||
from open_webui.main import chat_completion
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
Prioritize the unified endpoint (`chat_completion`) for simplicity and future compatibility.
|
||||
|
||||
:::
|
||||
|
||||
### 📝 **Additional Note: Choosing Between `main.chat_completion` and `utils.chat.generate_chat_completion`**
|
||||
|
||||
Depending on your use case, you can choose between:
|
||||
|
||||
1. **`open_webui.main.chat_completion`:**
|
||||
- Simulates making a POST request to `/api/chat/completions`.
|
||||
- Processes files, tools, and other miscellaneous tasks.
|
||||
- Best when you want the complete API flow handled automatically.
|
||||
|
||||
2. **`open_webui.utils.chat.generate_chat_completion`:**
|
||||
- Directly makes a POST request without handling extra parsing or tasks.
|
||||
- This is the **direct successor** to the previous `main.generate_chat_completions`, `ollama.generate_chat_completion` and `openai.generate_chat_completion` functions in Open WebUI 0.4.
|
||||
- Best for simplified and more lightweight scenarios.
|
||||
|
||||
#### Example:
|
||||
```python
|
||||
|
||||
# Use this for the full API flow with parsing:
|
||||
from open_webui.main import chat_completion
|
||||
|
||||
# Use this for a stripped-down, direct POST request:
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📋 3. Adapting to Updated Function Signatures
|
||||
|
||||
We’ve updated the **function signatures** to better fit the new architecture. If you're looking for a direct replacement, start with the lightweight utility function `generate_chat_completion` from `open_webui.utils.chat`. For the full API flow, use the new unified `chat_completion` function in `open_webui.main`.
|
||||
|
||||
#### Function Signature Changes:
|
||||
|
||||
| **Old** | **Direct Successor (New)** | **Unified Option (New)** |
|
||||
|-----------------------------------------|-----------------------------------------|-----------------------------------------|
|
||||
| `openai.generate_chat_completion(form_data: dict, user: UserModel)` | `generate_chat_completion(request: Request, form_data: dict, user: UserModel)` | `chat_completion(request: Request, form_data: dict, user: UserModel)` |
|
||||
|
||||
- **Direct Successor (`generate_chat_completion`)**: A lightweight, 1:1 replacement for previous `ollama`/`openai` methods.
|
||||
- **Unified Option (`chat_completion`)**: Use this for the complete API flow, including file parsing and additional functionality.
|
||||
|
||||
#### Example:
|
||||
|
||||
If you're using `chat_completion`, here’s how your function should look now:
|
||||
|
||||
### 🛠️ How to Refactor Your Custom Function
|
||||
Let’s rewrite a sample function to match the new structure:
|
||||
|
||||
#### Before (0.4):
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from open_webui.apps.ollama import generate_chat_completion
|
||||
|
||||
class User(BaseModel):
|
||||
id: str
|
||||
email: str
|
||||
name: str
|
||||
role: str
|
||||
|
||||
class Pipe:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def pipe(self, body: dict, __user__: dict) -> str:
|
||||
# Calls OpenAI endpoint
|
||||
user = User(**__user__)
|
||||
body["model"] = "llama3.2:latest"
|
||||
return await ollama.generate_chat_completion(body, user)
|
||||
```
|
||||
|
||||
#### After (0.5):
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from fastapi import Request
|
||||
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
|
||||
class User(BaseModel):
|
||||
id: str
|
||||
email: str
|
||||
name: str
|
||||
role: str
|
||||
|
||||
class Pipe:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def pipe(
|
||||
self,
|
||||
body: dict,
|
||||
__user__: dict,
|
||||
__request__: Request,
|
||||
) -> str:
|
||||
# Uses the unified endpoint with updated signature
|
||||
user = User(**__user__)
|
||||
body["model"] = "llama3.2:latest"
|
||||
return await generate_chat_completion(__request__, body, user)
|
||||
```
|
||||
|
||||
### Important Notes:
|
||||
- You must pass a `Request` object (`__request__`) in the new function signature.
|
||||
- Other optional parameters (like `__user__` and `__event_emitter__`) ensure flexibility for more complex use cases.
|
||||
|
||||
---
|
||||
|
||||
### 🌟 4. Recap: Key Concepts in Simple Terms
|
||||
|
||||
Here’s a quick cheat sheet to remember:
|
||||
- **Apps to Routers:** Update all imports from `open_webui.apps` ➡️ `open_webui.routers`.
|
||||
- **Unified Endpoint:** Use `open_webui.main.chat_completion` for simplicity if both `ollama` and `openai` are involved.
|
||||
- **Adapt Function Signatures:** Ensure your functions pass the required `request` object.
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Hooray! You're Ready!
|
||||
|
||||
That's it! You've successfully migrated from **Open WebUI 0.4 to 0.5**. By refactoring your imports, using the unified endpoint, and updating function signatures, you'll be fully equipped to leverage the latest features and improvements in version 0.5.
|
||||
|
||||
---
|
||||
|
||||
💬 **Questions or Feedback?**
|
||||
If you run into any issues or have suggestions, feel free to open a [GitHub issue](https://github.com/open-webui/open-webui) or ask in the community forums!
|
||||
|
||||
Happy coding! ✨
|
||||
1651
docs/features/plugin/tools/development.mdx
Normal file
1651
docs/features/plugin/tools/development.mdx
Normal file
File diff suppressed because it is too large
Load Diff
144
docs/features/plugin/tools/index.mdx
Normal file
144
docs/features/plugin/tools/index.mdx
Normal file
@@ -0,0 +1,144 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
title: "Tools"
|
||||
---
|
||||
|
||||
# ⚙️ What are Tools?
|
||||
|
||||
Tools are small Python scripts that add superpowers to your LLM. When enabled, they allow your chatbot to do amazing things — like search the web, scrape data, generate images, talk back using AI voices, and more.
|
||||
|
||||
Think of Tools as useful plugins that your AI can use when chatting with you.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 What Can Tools Help Me Do?
|
||||
|
||||
Here are just a few examples of what Tools let your AI assistant do:
|
||||
|
||||
- 🌍 Web Search: Get real-time answers by searching the internet.
|
||||
- 🖼️ Image Generation: Create images from your prompts.
|
||||
- 🔊 Voice Output: Generate AI voices using ElevenLabs.
|
||||
|
||||
Explore ready-to-use tools in the 🧰 [Tools Showcase](https://openwebui.com/tools)
|
||||
|
||||
---
|
||||
|
||||
## 📦 How to Install Tools
|
||||
|
||||
There are two easy ways to install Tools in Open WebUI:
|
||||
|
||||
1. Go to [Community Tool Library](https://openwebui.com/tools)
|
||||
2. Choose a Tool, then click the Get button.
|
||||
3. Enter your Open WebUI instance’s IP address or URL.
|
||||
4. Click “Import to WebUI” — done!
|
||||
|
||||
:::warning
|
||||
|
||||
Safety Tip: Never import a Tool you don’t recognize or trust. These are Python scripts and might run unsafe code.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 🔧 How to Use Tools in Open WebUI
|
||||
|
||||
Once you've installed Tools (we’ll show you how below), here’s how to enable and use them:
|
||||
|
||||
You have two ways to enable a Tool for your model:
|
||||
|
||||
### ➕ Option 1: Enable from the Chat Window
|
||||
|
||||
While chatting, click the ➕ icon in the input area. You’ll see a list of available Tools — you can enable any of them on the fly for that session.
|
||||
|
||||
:::tip
|
||||
|
||||
Tip: Enabling a Tool gives the model permission to use it — but it may not use it unless it's useful for the task.
|
||||
|
||||
:::
|
||||
|
||||
### ✏️ Option 2: Enable by Default (Recommended for Frequent Use)
|
||||
1. Go to: Workspace ➡️ Models
|
||||
2. Choose the model you’re using (like GPT-4 or LLaMa2) and click the ✏️ edit icon.
|
||||
3. Scroll down to the “Tools” section.
|
||||
4. ✅ Check the Tools you want your model to have access to by default.
|
||||
5. Click Save.
|
||||
|
||||
This ensures the model always has these Tools ready to use whenever you chat with it.
|
||||
|
||||
You can also let your LLM auto-select the right Tools using the AutoTool Filter:
|
||||
|
||||
🔗 [AutoTool Filter](https://openwebui.com/f/hub/autotool_filter/)
|
||||
|
||||
🎯 Note: Even when using AutoTool, you still need to enable your Tools using Option 2.
|
||||
|
||||
✅ And that’s it — your LLM is now Tool-powered! You're ready to supercharge your chats with web search, image generation, voice output, and more.
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Choosing How Tools Are Used: Default vs Native
|
||||
|
||||
Once Tools are enabled for your model, Open WebUI gives you two different ways to let your LLM use them in conversations.
|
||||
|
||||
You can decide how the model should call Tools by choosing between:
|
||||
|
||||
- 🟡 Default Mode (Prompt-based)
|
||||
- 🟢 Native Mode (Built-in function calling)
|
||||
|
||||
Let’s break it down:
|
||||
|
||||
### 🟡 Default Mode (Prompt-based Tool Triggering)
|
||||
|
||||
This is the default setting in Open WebUI.
|
||||
|
||||
Here, your LLM doesn’t need to natively support function calling. Instead, we guide the model using smart tool selection prompt template to select and use a Tool.
|
||||
|
||||
✅ Works with almost any model
|
||||
✅ Great way to unlock Tools with basic or local models
|
||||
❗ Not as reliable or flexible as Native Mode when chaining tools
|
||||
|
||||
### 🟢 Native Mode (Function Calling Built-In)
|
||||
|
||||
If your model does support “native” function calling (like GPT-4o or GPT-3.5-turbo-1106), you can use this powerful mode to let the LLM decide — in real time — when and how to call multiple Tools during a single chat message.
|
||||
|
||||
✅ Fast, accurate, and can chain multiple Tools in one response
|
||||
✅ The most natural and advanced experience
|
||||
❗ Requires a model that actually supports native function calling
|
||||
|
||||
### ✳️ How to Switch Between Modes
|
||||
|
||||
Want to enable native function calling in your chats? Here's how:
|
||||
|
||||

|
||||
|
||||
1. Open the chat window with your model.
|
||||
2. Click ⚙️ Chat Controls > Advanced Params.
|
||||
3. Look for the Function Calling setting and switch it from Default → Native
|
||||
|
||||
That’s it! Your chat is now using true native Tool support (as long as the model supports it).
|
||||
|
||||
➡️ We recommend using GPT-4o or another OpenAI model for the best native function-calling experience.
|
||||
🔎 Some local models may claim support, but often struggle with accurate or complex Tool usage.
|
||||
|
||||
💡 Summary:
|
||||
|
||||
| Mode | Who it’s for | Pros | Cons |
|
||||
|----------|----------------------------------|-----------------------------------------|--------------------------------------|
|
||||
| Default | Any model | Broad compatibility, safer, flexible | May be less accurate or slower |
|
||||
| Native | GPT-4o, etc. | Fast, smart, excellent tool chaining | Needs proper function call support |
|
||||
|
||||
Choose the one that works best for your setup — and remember, you can always switch on the fly via Chat Controls.
|
||||
|
||||
👏 And that's it — your LLM now knows how and when to use Tools, intelligently.
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Summary
|
||||
|
||||
Tools are add-ons that help your AI model do much more than just chat. From answering real-time questions to generating images or speaking out loud — Tools bring your AI to life.
|
||||
|
||||
- Visit: [https://openwebui.com/tools](https://openwebui.com/tools) to discover new Tools.
|
||||
- Install them manually or with one-click.
|
||||
- Enable them per model from Workspace ➡️ Models.
|
||||
- Use them in chat by clicking ➕
|
||||
|
||||
Now go make your AI waaaaay smarter 🤖✨
|
||||
176
docs/features/plugin/tools/openapi-servers/faq.mdx
Normal file
176
docs/features/plugin/tools/openapi-servers/faq.mdx
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
sidebar_position: 10
|
||||
title: "FAQ"
|
||||
---
|
||||
|
||||
#### 🌐 Q: Why isn't my local OpenAPI tool server accessible from the WebUI interface?
|
||||
|
||||
**A:** If your tool server is running locally (e.g., http://localhost:8000), browser-based clients may be restricted from accessing it due to CORS (Cross-Origin Resource Sharing) policies.
|
||||
|
||||
Make sure to explicitly enable CORS headers in your OpenAPI server. For example, if you're using FastAPI, you can add:
|
||||
|
||||
```python
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # or specify your client origin
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
```
|
||||
|
||||
Also, if Open WebUI is served over HTTPS (e.g., https://yourdomain.com), your local server must meet one of the following conditions:
|
||||
|
||||
- Be accessed from the same domain using HTTPS (e.g., https://localhost:8000).
|
||||
- OR run on localhost (127.0.0.1) to allow browsers to relax security for local development.
|
||||
- Otherwise, browsers may block insecure requests from HTTPS pages to HTTP APIs due to mixed-content rules.
|
||||
|
||||
To work securely in production over HTTPS, your OpenAPI servers must also be served over HTTPS.
|
||||
|
||||
---
|
||||
|
||||
#### 🚀 Q: Do I need to use FastAPI for my server implementation?
|
||||
|
||||
**A:** No! While our reference implementations are written using FastAPI for clarity and ease of use, you can use any framework or language that produces a valid OpenAPI (Swagger) specification. Some common choices include:
|
||||
|
||||
- FastAPI (Python)
|
||||
- Flask + Flask-RESTX (Python)
|
||||
- Express + Swagger UI (JavaScript/Node)
|
||||
- Spring Boot (Java)
|
||||
- Go with Swag or Echo
|
||||
|
||||
The key is to ensure your server exposes a valid OpenAPI schema, and that it communicates over HTTP(S).
|
||||
It is important to set a custom operationId for all endpoints.
|
||||
|
||||
---
|
||||
|
||||
#### 🚀 Q: Why choose OpenAPI over MCP?
|
||||
|
||||
**A:** OpenAPI wins over MCP in most real-world scenarios due to its simplicity, tooling ecosystem, stability, and developer-friendliness. Here's why:
|
||||
|
||||
- ✅ **Reuse Your Existing Code**: If you’ve built REST APIs before, you're mostly done—you don’t need to rewrite your logic. Just define a compliant OpenAPI spec and expose your current code as a tool server.
|
||||
|
||||
With MCP, you had to reimplement your tool logic inside a custom protocol layer, duplicating work and increasing the surface area to maintain.
|
||||
|
||||
- 💼 **Less to Maintain & Debug**: OpenAPI fits naturally into modern dev workflows. You can test endpoints with Postman, inspect logs with built-in APIs, troubleshoot easily with mature ecosystem tools—and often without modifying your core app at all.
|
||||
|
||||
MCP introduced new layers of transport, schema parsing, and runtime quirks, all of which had to be debugged manually.
|
||||
|
||||
- 🌍 **Standards-Based**: OpenAPI is widely adopted across the tech industry. Its well-defined structure means tools, agents, and servers can interoperate immediately, without needing special bridges or translations.
|
||||
|
||||
- 🧰 **Better Tooling**: There’s an entire universe of tools that support OpenAPI—automatic client/server generation, documentation, validation, mocking, testing, and even security audit tools.
|
||||
|
||||
- 🔐 **First-Class Security Support**: OpenAPI includes native support for things like OAuth2, JWTs, API Keys, and HTTPS—making it easier to build secure endpoints with common libraries and standards.
|
||||
|
||||
- 🧠 **More Devs Already Know It**: Using OpenAPI means you're speaking a language already familiar to backend teams, frontend developers, DevOps, and product engineers. There’s no learning curve or costly onboarding required.
|
||||
|
||||
- 🔄 **Future-Proof & Extensible**: OpenAPI evolves with API standards and remains forward-compatible. MCP, by contrast, was bespoke and experimental—often requiring changes as the surrounding ecosystem changed.
|
||||
|
||||
🧵 Bottom line: OpenAPI lets you do more with less effort, less code duplication, and fewer surprises. It’s a production-ready, developer-friendly route to powering LLM tools—without rebuilding everything from scratch.
|
||||
|
||||
---
|
||||
|
||||
#### 🔐 Q: How do I secure my OpenAPI tool server?
|
||||
|
||||
**A:** OpenAPI supports industry-standard security mechanisms like:
|
||||
|
||||
- OAuth 2.0
|
||||
- API Key headers
|
||||
- JWT (JSON Web Token)
|
||||
- Basic Auth
|
||||
|
||||
Use HTTPS in production to encrypt data in transit, and restrict endpoints with proper auth/authz methods as appropriate. You can incorporate these directly in your OpenAPI schema using the securitySchemes field.
|
||||
|
||||
---
|
||||
|
||||
#### ❓ Q: What kind of tools can I build using OpenAPI tool servers?
|
||||
|
||||
**A:** If it can be exposed via a REST API, you can build it. Common tool types include:
|
||||
|
||||
- Filesystem operations (read/write files, list directories)
|
||||
- Git and document repository access
|
||||
- Database querying or schema exploration
|
||||
- Web scrapers or summarizers
|
||||
- External SaaS integrations (e.g., Salesforce, Jira, Slack)
|
||||
- LLM-attached memory stores / RAG components
|
||||
- Secure internal microservices exposed to your agent
|
||||
|
||||
---
|
||||
|
||||
#### 🔌 Q: Can I run more than one tool server at the same time?
|
||||
|
||||
**A:** Absolutely. Each tool server runs independently and exposes its own OpenAPI schema. Your agent configuration can point to multiple tool servers, allowing you to mix and match based on need.
|
||||
|
||||
There's no limit—just ensure each server runs on its own port or address and is reachable by the agent host.
|
||||
|
||||
---
|
||||
|
||||
#### 🧪 Q: How do I test a tool server before linking it to an LLM agent?
|
||||
|
||||
**A:** You can test your OpenAPI tool servers using:
|
||||
|
||||
- Swagger UI or ReDoc (built into FastAPI by default)
|
||||
- Postman or Insomnia
|
||||
- curl or httpie from the command line
|
||||
- Python’s requests module
|
||||
- OpenAPI validators and mockers
|
||||
|
||||
Once validated, you can register the tool server with an LLM agent or through Open WebUI.
|
||||
|
||||
---
|
||||
|
||||
#### 🛠️ Q: Can I extend or customize the reference servers?
|
||||
|
||||
**A:** Yes! All servers in the servers/ directory are built to be simple templates. Fork and modify them to:
|
||||
|
||||
- Add new endpoints and business logic
|
||||
- Integrate authentication
|
||||
- Change response formats
|
||||
- Connect to new services or internal APIs
|
||||
- Deploy via Docker, Kubernetes, or any cloud host
|
||||
|
||||
---
|
||||
|
||||
#### 🌍 Q: Can I run OpenAPI tool servers on cloud platforms like AWS or GCP?
|
||||
|
||||
**A:** Yes. These servers are plain HTTP services. You can deploy them as:
|
||||
|
||||
- AWS Lambda with API Gateway (serverless)
|
||||
- EC2 or GCP Compute Engine instances
|
||||
- Kubernetes services in GKE/EKS/AKS
|
||||
- Cloud Run or App Engine
|
||||
- Render, Railway, Heroku, etc.
|
||||
|
||||
Just make sure they’re securely configured and publicly reachable (or VPN'd) if needed by the agent or user.
|
||||
|
||||
---
|
||||
|
||||
#### 🧪 Q: What if I have an existing MCP server?
|
||||
|
||||
**A:** Great news! You can use our MCP-to-OpenAPI Bridge: [mcpo](https://github.com/open-webui/mcpo), exposing your existing MCP-based tools as OpenAPI-compatible APIs is now easier than ever. No rewrites, no headaches — just plug and go! 🚀
|
||||
|
||||
If you've already built tools using the MCP protocol, `mcpo` helps you instantly unlock compatibility with Open WebUI and any OpenAPI-based agent — ensuring your hard work remains fully accessible and future-ready.
|
||||
|
||||
[Check out the optional Bridge to MCP section in the docs for setup instructions.](https://github.com/open-webui/openapi-servers?tab=readme-ov-file#-bridge-to-mcp-optional)
|
||||
|
||||
**Quick Start:**
|
||||
```bash
|
||||
uvx mcpo --port 8000 -- uvx mcp-server-time --local-timezone=America/New_York
|
||||
```
|
||||
|
||||
✨ That’s it — your MCP server is now OpenAPI-ready!
|
||||
|
||||
---
|
||||
|
||||
#### 🗂️ Q: Can one OpenAPI server implement multiple tools?
|
||||
|
||||
**A:** Yes. A single OpenAPI server can offer multiple related capabilities grouped under different endpoints. For example, a document server may provide search, upload, OCR, and summarization—all within one schema.
|
||||
|
||||
You can also modularize completely by creating one OpenAPI server per tool if you prefer isolation and flexibility.
|
||||
|
||||
---
|
||||
|
||||
🙋 Have more questions? Visit the GitHub discussions for help and feedback from the community:
|
||||
👉 [Community Discussions](https://github.com/open-webui/openapi-servers/discussions)
|
||||
70
docs/features/plugin/tools/openapi-servers/index.mdx
Normal file
70
docs/features/plugin/tools/openapi-servers/index.mdx
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
sidebar_position: 400
|
||||
title: "OpenAPI Tool Servers"
|
||||
---
|
||||
|
||||
import { TopBanners } from "@site/src/components/TopBanners";
|
||||
|
||||
<TopBanners />
|
||||
|
||||
# 🌟 OpenAPI Tool Servers
|
||||
|
||||
This repository provides reference OpenAPI Tool Server implementations making it easy and secure for developers to integrate external tooling and data sources into LLM agents and workflows. Designed for maximum ease of use and minimal learning curve, these implementations utilize the widely adopted and battle-tested [OpenAPI specification](https://www.openapis.org/) as the standard protocol.
|
||||
|
||||
By leveraging OpenAPI, we eliminate the need for a proprietary or unfamiliar communication protocol, ensuring you can quickly and confidently build or integrate servers. This means less time spent figuring out custom interfaces and more time building powerful tools that enhance your AI applications.
|
||||
|
||||
## ☝️ Why OpenAPI?
|
||||
|
||||
- **Established Standard**: OpenAPI is a widely used, production-proven API standard backed by thousands of tools, companies, and communities.
|
||||
|
||||
- **No Reinventing the Wheel**: No additional documentation or proprietary spec confusion. If you build REST APIs or use OpenAPI today, you're already set.
|
||||
|
||||
- **Easy Integration & Hosting**: Deploy your tool servers externally or locally without vendor lock-in or complex configurations.
|
||||
|
||||
- **Strong Security Focus**: Built around HTTP/REST APIs, OpenAPI inherently supports widely used, secure communication methods including HTTPS and well-proven authentication standards (OAuth, JWT, API Keys).
|
||||
|
||||
- **Future-Friendly & Stable**: Unlike less mature or experimental protocols, OpenAPI promises reliability, stability, and long-term community support.
|
||||
|
||||
## 🚀 Quickstart
|
||||
|
||||
Get started quickly with our reference FastAPI-based implementations provided in the `servers/` directory. (You can adapt these examples into your preferred stack as needed, such as using [FastAPI](https://fastapi.tiangolo.com/), [FastOpenAPI](https://github.com/mr-fatalyst/fastopenapi) or any other OpenAPI-compatible library):
|
||||
|
||||
```bash
|
||||
git clone https://github.com/open-webui/openapi-servers
|
||||
cd openapi-servers
|
||||
```
|
||||
|
||||
### With Bash
|
||||
|
||||
```bash
|
||||
|
||||
# Example: Installing dependencies for a specific server 'filesystem'
|
||||
cd servers/filesystem
|
||||
pip install -r requirements.txt
|
||||
uvicorn main:app --host 0.0.0.0 --reload
|
||||
```
|
||||
|
||||
The filesystem server should be reachable from: [http://localhost:8000](http://localhost:8000)
|
||||
|
||||
The documentation path will be: [http://localhost:8000](http://localhost:8000)
|
||||
|
||||
### With Docker
|
||||
|
||||
If you have docker compose installed, bring the servers up with:
|
||||
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
||||
The services will be reachable from:
|
||||
|
||||
* [Filesystem localhost:8081](http://localhost:8081)
|
||||
* [memory server localhost:8082](http://localhost:8082)
|
||||
* [time-server localhost:8083](http://localhost:8083)
|
||||
|
||||
Now, simply point your OpenAPI-compatible clients or AI agents to your local or publicly deployed URL—no configuration headaches, no complicated transports.
|
||||
|
||||
## 🌱 Open WebUI Community
|
||||
|
||||
- For general discussions, technical exchange, and announcements, visit our [Community Discussions](https://github.com/open-webui/openapi-servers/discussions) page.
|
||||
- Have ideas or feedback? Please open an issue!
|
||||
199
docs/features/plugin/tools/openapi-servers/mcp.mdx
Normal file
199
docs/features/plugin/tools/openapi-servers/mcp.mdx
Normal file
@@ -0,0 +1,199 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
title: "MCP Support"
|
||||
---
|
||||
|
||||
This documentation explains how to easily set up and deploy the [**MCP (Model Context Protocol)-to-OpenAPI proxy server** (mcpo)](https://github.com/open-webui/mcpo) provided by Open WebUI. Learn how you can effortlessly expose MCP-based tool servers using standard, familiar OpenAPI endpoints suitable for end-users and developers.
|
||||
|
||||
### 📌 What is the MCP Proxy Server?
|
||||
|
||||
The MCP-to-OpenAPI proxy server lets you use tool servers implemented with MCP (Model Context Protocol) directly via standard REST/OpenAPI APIs—no need to manage unfamiliar or complicated custom protocols. If you're an end-user or application developer, this means you can interact easily with powerful MCP-based tooling directly through familiar REST-like endpoints.
|
||||
|
||||
### 💡 Why Use mcpo?
|
||||
|
||||
While MCP tool servers are powerful and flexible, they commonly communicate via standard input/output (stdio)—often running on your local machine where they can easily access your filesystem, environment, and other native system capabilities.
|
||||
|
||||
That’s a strength—but also a limitation.
|
||||
|
||||
If you want to deploy your main interface (like Open WebUI) on the cloud, you quickly run into a problem: your cloud instance can’t speak directly to an MCP server running locally on your machine via stdio.
|
||||
|
||||
[That’s where mcpo comes in with a game-changing solution.](https://github.com/open-webui/mcpo)
|
||||
|
||||
MCP servers typically rely on raw stdio communication, which is:
|
||||
|
||||
- 🔓 Inherently insecure across environments
|
||||
- ❌ Incompatible with most modern tools, UIs, or platforms
|
||||
- 🧩 Lacking critical features like authentication, documentation, and error handling
|
||||
|
||||
The mcpo proxy eliminates those issues—automatically:
|
||||
|
||||
- ✅ Instantly compatible with existing OpenAPI tools, SDKs, and clients
|
||||
- 🛡 Wraps your tools with secure, scalable, and standards-based HTTP endpoints
|
||||
- 🧠 Auto-generates interactive OpenAPI documentation for every tool, entirely config-free
|
||||
- 🔌 Uses plain HTTP—no socket setup, daemon juggling, or platform-specific glue code
|
||||
|
||||
So even though adding mcpo might at first seem like "just one more layer"—in reality, it simplifies everything while giving you:
|
||||
|
||||
- Better integration ✅
|
||||
- Better security ✅
|
||||
- Better scalability ✅
|
||||
- Happier developers & users ✅
|
||||
|
||||
✨ With mcpo, your local-only AI tools become cloud-ready, UI-friendly, and instantly interoperable—without changing a single line of tool server code.
|
||||
|
||||
### ✅ Quickstart: Running the Proxy Locally
|
||||
|
||||
Here's how simple it is to launch the MCP-to-OpenAPI proxy server using the lightweight, easy-to-use tool **mcpo** ([GitHub Repository](https://github.com/open-webui/mcpo)):
|
||||
|
||||
1. **Prerequisites**
|
||||
- **Python 3.8+** with `pip` installed.
|
||||
- MCP-compatible application (for example: `mcp-server-time`)
|
||||
- (Optional but recommended) `uv` installed for faster startup and zero-config convenience.
|
||||
|
||||
2. **Install mcpo**
|
||||
|
||||
Using **uv** (recommended):
|
||||
|
||||
```bash
|
||||
uvx mcpo --port 8000 -- your_mcp_server_command
|
||||
```
|
||||
|
||||
Or using `pip`:
|
||||
|
||||
```bash
|
||||
pip install mcpo
|
||||
mcpo --port 8000 -- your_mcp_server_command
|
||||
```
|
||||
|
||||
3. 🚀 **Run the Proxy Server**
|
||||
|
||||
To start your MCP-to-OpenAPI proxy server, you need an MCP-compatible tool server. If you don't have one yet, the MCP community provides various ready-to-use MCP server implementations.
|
||||
|
||||
✨ **Where to find MCP Servers?**
|
||||
|
||||
You can discover officially supported MCP servers at the following repository example:
|
||||
|
||||
- [modelcontextprotocol/servers on GitHub](https://github.com/modelcontextprotocol/servers)
|
||||
|
||||
For instance, the popular **Time MCP Server** is documented [here](https://github.com/modelcontextprotocol/servers/blob/main/src/time/README.md), and is typically referenced clearly in the README, inside the provided MCP configuration. Specifically, the README states:
|
||||
|
||||
> Add to your Claude settings:
|
||||
>
|
||||
> ```json
|
||||
> "mcpServers": {
|
||||
> "time": {
|
||||
> "command": "uvx",
|
||||
> "args": ["mcp-server-time", "--local-timezone=America/New_York"]
|
||||
> }
|
||||
> }
|
||||
> ```
|
||||
|
||||
🔑 **Translating this MCP setup to a quick local proxy command**:
|
||||
|
||||
You can easily run the recommended MCP server (`mcp-server-time`) directly through the **MCP-to-OpenAPI proxy** (`mcpo`) like this:
|
||||
|
||||
```bash
|
||||
uvx mcpo --port 8000 -- uvx mcp-server-time --local-timezone=America/New_York
|
||||
```
|
||||
|
||||
That's it! You're now running the MCP-to-OpenAPI Proxy locally and exposing the powerful **MCP Time Server** through standard OpenAPI endpoints accessible at:
|
||||
|
||||
- 📖 **Interactive OpenAPI Documentation:** [`http://localhost:8000/docs`](http://localhost:8000/docs)
|
||||
|
||||
Feel free to replace `uvx mcp-server-time --local-timezone=America/New_York` with your preferred MCP Server command from other available MCP implementations found in the official repository.
|
||||
|
||||
🤝 **To integrate with Open WebUI after launching the server, check our [docs](https://docs.openwebui.com/openapi-servers/open-webui/).**
|
||||
|
||||
### 🚀 Accessing the Generated APIs
|
||||
|
||||
As soon as it starts, the MCP Proxy (`mcpo`) automatically:
|
||||
|
||||
- Discovers MCP tools dynamically and generates REST endpoints.
|
||||
- Creates interactive, human-readable OpenAPI documentation accessible at:
|
||||
- `http://localhost:8000/docs`
|
||||
|
||||
Simply call the auto-generated API endpoints directly via HTTP clients, AI agents, or other OpenAPI tools of your preference.
|
||||
|
||||
### 📖 Example Workflow for End-Users
|
||||
|
||||
Assuming you started the above server command (`uvx mcp-server-time`):
|
||||
|
||||
- Visit your local API documentation at `http://localhost:8000/docs`.
|
||||
- Select a generated endpoint (e.g., `/get_current_time`) and use the provided interactive form.
|
||||
- Click "**Execute**" and instantly receive your response.
|
||||
|
||||
No setup complexity—just instant REST APIs.
|
||||
|
||||
## 🚀 Deploying in Production (Example)
|
||||
|
||||
Deploying your MCP-to-OpenAPI proxy (powered by mcpo) is straightforward. Here's how to easily Dockerize and deploy it to cloud or VPS solutions:
|
||||
|
||||
### 🐳 Dockerize your Proxy Server using mcpo
|
||||
|
||||
1. **Dockerfile Example**
|
||||
|
||||
Create the following `Dockerfile` inside your deployment directory:
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
WORKDIR /app
|
||||
RUN pip install mcpo uv
|
||||
|
||||
# Replace with your MCP server command; example: uvx mcp-server-time
|
||||
CMD ["uvx", "mcpo", "--host", "0.0.0.0", "--port", "8000", "--", "uvx", "mcp-server-time", "--local-timezone=America/New_York"]
|
||||
```
|
||||
|
||||
2. **Build & Run the Container Locally**
|
||||
|
||||
```bash
|
||||
docker build -t mcp-proxy-server .
|
||||
docker run -d -p 8000:8000 mcp-proxy-server
|
||||
```
|
||||
|
||||
3. **Deploying Your Container**
|
||||
|
||||
Push to DockerHub or another registry:
|
||||
|
||||
```bash
|
||||
docker tag mcp-proxy-server yourdockerusername/mcp-proxy-server:latest
|
||||
docker push yourdockerusername/mcp-proxy-server:latest
|
||||
```
|
||||
|
||||
Deploy using Docker Compose, Kubernetes YAML manifests, or your favorite cloud container services (AWS ECS, Azure Container Instances, Render.com, or Heroku).
|
||||
|
||||
✔️ Your production MCP servers are now effortlessly available via REST APIs!
|
||||
|
||||
## 🧑💻 Technical Details and Background
|
||||
|
||||
### 🍃 How It Works (Technical Summary)
|
||||
|
||||
- **Dynamic Schema Discovery & Endpoints:** At server startup, the proxy connects to the MCP server to query available tools. It automatically builds FastAPI endpoints based on the MCP tool schemas, creating concise and clear REST endpoints.
|
||||
|
||||
- **OpenAPI Auto-documentation:** Endpoints generated are seamlessly documented and available via FastAPI's built-in Swagger UI (`/docs`). No extra doc writing required.
|
||||
|
||||
- **Asynchronous & Performant**: Built on robust asynchronous libraries, ensuring speed and reliability for concurrent users.
|
||||
|
||||
### 📚 Under the Hood:
|
||||
|
||||
- FastAPI (Automatic routing & docs generation)
|
||||
- MCP Client (Standard MCP integration & schema discovery)
|
||||
- Standard JSON over HTTP (Easy integration)
|
||||
|
||||
## ⚡️ Why is the MCP-to-OpenAPI Proxy Superior?
|
||||
|
||||
Here's why leveraging MCP servers through OpenAPI via the proxy approach is significantly better and why Open WebUI enthusiastically supports it:
|
||||
|
||||
- **User-friendly & Familiar Interface**: No custom clients; just HTTP REST endpoints you already know.
|
||||
- **Instant Integration**: Immediately compatible with thousands of existing REST/OpenAPI tools, SDKs, and services.
|
||||
- **Powerful & Automatic Docs**: Built-in Swagger UI documentation is automatically generated, always accurate, and maintained.
|
||||
- **No New Protocol overhead**: Eliminates the necessity to directly handle MCP-specific protocol complexities and socket communication issues.
|
||||
- **Battle-Tested Security & Stability**: Inherits well-established HTTPS transport, standard auth methods (JWT, API keys), solid async libraries, and FastAPI’s proven robustness.
|
||||
- **Future-Proof**: MCP proxy uses existing, stable, standard REST/OpenAPI formats guaranteed long-term community support and evolution.
|
||||
|
||||
🌟 **Bottom line:** MCP-to-OpenAPI makes your powerful MCP-based AI tools broadly accessible through intuitive, reliable, and scalable REST endpoints. Open WebUI proudly supports and recommends this best-in-class approach.
|
||||
|
||||
## 📢 Community & Support
|
||||
|
||||
- For questions, suggestions, or feature requests, please use our [GitHub Issue tracker](https://github.com/open-webui/openapi-servers/issues) or join our [Community Discussions](https://github.com/open-webui/openapi-servers/discussions).
|
||||
|
||||
Happy integrations! 🌟🚀
|
||||
211
docs/features/plugin/tools/openapi-servers/open-webui.mdx
Normal file
211
docs/features/plugin/tools/openapi-servers/open-webui.mdx
Normal file
@@ -0,0 +1,211 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
title: "Open WebUI Integration"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Open WebUI v0.6+ supports seamless integration with external tools via the OpenAPI servers — meaning you can easily extend your LLM workflows using custom or community-powered tool servers 🧰.
|
||||
|
||||
In this guide, you'll learn how to launch an OpenAPI-compatible tool server and connect it to Open WebUI through the intuitive user interface. Let’s get started! 🚀
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Launch an OpenAPI Tool Server
|
||||
|
||||
To begin, you'll need to start one of the reference tool servers available in the [openapi-servers repo](https://github.com/open-webui/openapi-servers). For quick testing, we’ll use the time tool server as an example.
|
||||
|
||||
🛠️ Example: Starting the `time` server locally
|
||||
|
||||
```bash
|
||||
git clone https://github.com/open-webui/openapi-servers
|
||||
cd openapi-servers
|
||||
|
||||
# Navigate to the time server
|
||||
cd servers/time
|
||||
|
||||
# Install required dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Start the server
|
||||
uvicorn main:app --host 0.0.0.0 --reload
|
||||
```
|
||||
|
||||
Once running, this will host a local OpenAPI server at http://localhost:8000, which you can point Open WebUI to.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Step 2: Connect Tool Server in Open WebUI
|
||||
|
||||
Next, connect your running tool server to Open WebUI:
|
||||
|
||||
1. Open WebUI in your browser.
|
||||
2. Open ⚙️ **Settings**.
|
||||
3. Click on ➕ **Tools** to add a new tool server.
|
||||
4. Enter the URL where your OpenAPI tool server is running (e.g., http://localhost:8000).
|
||||
5. Click "Save".
|
||||
|
||||

|
||||
|
||||
### 🧑💻 User Tool Servers vs. 🛠️ Global Tool Servers
|
||||
|
||||
There are two ways to register tool servers in Open WebUI:
|
||||
|
||||
#### 1. User Tool Servers (added via regular Settings)
|
||||
|
||||
- Only accessible to the user who registered the tool server.
|
||||
- The connection is made directly from the browser (client-side) by the user.
|
||||
- Perfect for personal workflows or when testing custom/local tools.
|
||||
|
||||
#### 2. Global Tool Servers (added via Admin Settings)
|
||||
|
||||
Admins can manage shared tool servers available to all or selected users across the entire deployment:
|
||||
|
||||
- Go to 🛠️ **Admin Settings > Tools**.
|
||||
- Add the tool server URL just as you would in user settings.
|
||||
- These tools are treated similarly to Open WebUI’s built-in tools.
|
||||
|
||||
#### Main Difference: Where Are Requests Made From?
|
||||
|
||||
The primary distinction between **User Tool Servers** and **Global Tool Servers** is where the API connection and requests are actually made:
|
||||
|
||||
- **User Tool Servers**
|
||||
- Requests to the tool server are performed **directly from your browser** (the client).
|
||||
- This means you can safely connect to localhost URLs (like `http://localhost:8000`)—even exposing private or development-only endpoints such as your local filesystem or dev tools—without risking exposure to the wider internet or other users.
|
||||
- Your connection is isolated; only your browser can access that tool server.
|
||||
|
||||
- **Global Tool Servers**
|
||||
- Requests are sent **from the Open WebUI backend/server** (not your browser).
|
||||
- The backend must be able to reach the tool server URL you specify—so `localhost` means the backend server's localhost, *not* your computer's.
|
||||
- Use this for sharing tools with other users across the deployment, but be mindful: since the backend makes the requests, you cannot access your personal local resources (like your own filesystem) through this method.
|
||||
- Think security! Only expose remote/global endpoints that are safe and meant to be accessed by multiple users.
|
||||
|
||||
**Summary Table:**
|
||||
|
||||
| Tool Server Type | Request Origin | Use Localhost? | Use Case Example |
|
||||
| ------------------ | -------------------- | ------------------ | ---------------------------------------- |
|
||||
| User Tool Server | User's Browser (Client-side) | Yes (private to you) | Personal tools, local dev/testing |
|
||||
| Global Tool Server | Open WebUI Backend (Server-side) | No (unless running on the backend itself) | Team/shared tools, enterprise integrations |
|
||||
|
||||
:::tip
|
||||
|
||||
User Tool Servers are best for personal or experimental tools, especially those running on your own machine, while Global Tool Servers are ideal for production or shared environments where everyone needs access to the same tools.
|
||||
|
||||
:::
|
||||
|
||||
### 👉 Optional: Using a Config File with mcpo
|
||||
|
||||
If you're running multiple tools through mcpo using a config file, take note:
|
||||
|
||||
🧩 Each tool is mounted under its own unique path!
|
||||
|
||||
For example, if you’re using memory and time tools simultaneously through mcpo, they’ll each be available at a distinct route:
|
||||
|
||||
- http://localhost:8000/time
|
||||
- http://localhost:8000/memory
|
||||
|
||||
This means:
|
||||
|
||||
- When connecting a tool in Open WebUI, you must enter the full route to that specific tool — do NOT enter just the root URL (http://localhost:8000).
|
||||
- Add each tool individually in Open WebUI Settings using their respective subpath URLs.
|
||||
|
||||

|
||||
|
||||
✅ Good:
|
||||
|
||||
http://localhost:8000/time
|
||||
http://localhost:8000/memory
|
||||
|
||||
🚫 Not valid:
|
||||
|
||||
http://localhost:8000
|
||||
|
||||
This ensures Open WebUI recognizes and communicates with each tool server correctly.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Confirm Your Tool Server Is Connected ✅
|
||||
|
||||
Once your tool server is successfully connected, Open WebUI will display a 👇 tool server indicator directly in the message input area:
|
||||
|
||||
📍 You'll now see this icon below the input box:
|
||||
|
||||

|
||||
|
||||
Clicking this icon opens a popup where you can:
|
||||
|
||||
- View connected tool server information
|
||||
- See which tools are available and which server they're provided by
|
||||
- Debug or disconnect any tool if needed
|
||||
|
||||
🔍 Here’s what the tool information modal looks like:
|
||||
|
||||

|
||||
|
||||
### 🛠️ Global Tool Servers Look Different — And Are Hidden by Default!
|
||||
|
||||
If you've connected a Global Tool Server (i.e., one that’s admin-configured), it will not appear automatically in the input area like user tool servers do.
|
||||
|
||||
Instead:
|
||||
|
||||
- Global tools are hidden by default and must be explicitly activated per user.
|
||||
- To enable them, you'll need to click on the ➕ button in the message input area (bottom left of the chat box), and manually toggle on the specific global tool(s) you want to use.
|
||||
|
||||
Here’s what that looks like:
|
||||
|
||||

|
||||
|
||||
⚠️ Important Notes for Global Tool Servers:
|
||||
|
||||
- They will not show up in the tool indicator popup until enabled from the ➕ menu.
|
||||
- Each global tool must be individually toggled on to become active inside your current chat.
|
||||
- Once toggled on, they function the same way as user tools.
|
||||
- Admins can control access to global tools via role-based permissions.
|
||||
|
||||
This is ideal for team setups or shared environments, where commonly-used tools (e.g., document search, memory, or web lookup) should be centrally accessible by multiple users.
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 4: Use "Native" Function Calling (ReACT-style) Tool Use 🧠
|
||||
|
||||
:::info
|
||||
|
||||
For this to work effectively, **your selected model must support native tool calling**. Some local models claim support but often produce poor results. We strongly recommend using GPT-4o or another OpenAI model that supports function calling natively for the best experience.
|
||||
|
||||
:::
|
||||
|
||||
Want to enable ReACT-style (Reasoning + Acting) native function calls directly inside your conversations? You can switch Open WebUI to use native function calling.
|
||||
|
||||
✳️ How to enable native function calling:
|
||||
|
||||
1. Open the chat window.
|
||||
2. Go to ⚙️ **Chat Controls > Advanced Params**.
|
||||
3. Change the **Function Calling** parameter from `Default` to `Native`.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Need More Tools? Explore & Expand! 🧱
|
||||
|
||||
The [openapi-servers repo](https://github.com/open-webui/openapi-servers) includes a variety of useful reference servers:
|
||||
|
||||
- 📂 Filesystem access
|
||||
- 🧠 Memory & knowledge graphs
|
||||
- 🗃️ Git repo browsing
|
||||
- 🌎 Web search (WIP)
|
||||
- 🛢️ Database querying (WIP)
|
||||
|
||||
You can run any of these in the same way and connect them to Open WebUI by repeating the steps above.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting & Tips 🧩
|
||||
|
||||
- ❌ Not connecting? Make sure the URL is correct and accessible from the browser used to run Open WebUI.
|
||||
- 🔒 If you're using remote servers, check firewalls and HTTPS configs!
|
||||
- 📝 To make servers persist, consider deploying them in Docker or with system services.
|
||||
|
||||
Need help? Visit the 👉 [Discussions page](https://github.com/open-webui/openapi-servers/discussions) or [open an issue](https://github.com/open-webui/openapi-servers/issues).
|
||||
2562
docs/zh/future_plugin_development_roadmap_cn.md
Normal file
2562
docs/zh/future_plugin_development_roadmap_cn.md
Normal file
File diff suppressed because it is too large
Load Diff
234
docs/zh/plugin_development_guide.md
Normal file
234
docs/zh/plugin_development_guide.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# OpenWebUI 插件开发权威指南
|
||||
|
||||
> 本指南整合了官方文档、SDK 详解及最佳实践,旨在为开发者提供一份从入门到精通的系统化教程。
|
||||
|
||||
## 📚 目录
|
||||
|
||||
1. [插件开发快速入门](#1-插件开发快速入门)
|
||||
2. [核心概念与 SDK 详解](#2-核心概念与-sdk-详解)
|
||||
3. [插件类型深度解析](#3-插件类型深度解析)
|
||||
* [Action (动作)](#31-action-动作)
|
||||
* [Filter (过滤器)](#32-filter-过滤器)
|
||||
* [Pipe (管道)](#33-pipe-管道)
|
||||
4. [高级开发模式](#4-高级开发模式)
|
||||
5. [最佳实践与设计原则](#5-最佳实践与设计原则)
|
||||
6. [故障排查](#6-故障排查)
|
||||
|
||||
---
|
||||
|
||||
## 1. 插件开发快速入门
|
||||
|
||||
### 1.1 什么是 OpenWebUI 插件?
|
||||
|
||||
OpenWebUI 插件(官方称为 "Functions")是扩展平台功能的主要方式。它们运行在后端 Python 环境中,允许你:
|
||||
* 🔌 **集成新模型**:通过 Pipe 接入 Claude、Gemini 或自定义 RAG。
|
||||
* 🎨 **增强交互**:通过 Action 在消息旁添加按钮(如"导出"、"生成图表")。
|
||||
* 🔧 **干预流程**:通过 Filter 在请求前后修改数据(如注入上下文、敏感词过滤)。
|
||||
|
||||
### 1.2 你的第一个插件 (Hello World)
|
||||
|
||||
保存以下代码为 `hello.py` 并上传到 OpenWebUI 的 **Functions** 面板:
|
||||
|
||||
```python
|
||||
"""
|
||||
title: Hello World Action
|
||||
author: Demo
|
||||
version: 1.0.0
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
class Action:
|
||||
class Valves(BaseModel):
|
||||
greeting: str = Field(default="你好", description="问候语")
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
async def action(
|
||||
self,
|
||||
body: dict,
|
||||
__event_emitter__=None,
|
||||
__user__=None
|
||||
) -> Optional[dict]:
|
||||
user_name = __user__.get("name", "朋友") if __user__ else "朋友"
|
||||
|
||||
if __event_emitter__:
|
||||
await __event_emitter__({
|
||||
"type": "notification",
|
||||
"data": {"type": "success", "content": f"{self.valves.greeting}, {user_name}!"}
|
||||
})
|
||||
return body
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心概念与 SDK 详解
|
||||
|
||||
### 2.1 ⚠️ 重要:同步与异步
|
||||
|
||||
OpenWebUI 插件运行在 `asyncio` 事件循环中。
|
||||
* **原则**:所有 I/O 操作(数据库、文件、网络)必须非阻塞。
|
||||
* **陷阱**:直接调用同步方法(如 `time.sleep`, `requests.get`)会卡死整个服务器。
|
||||
* **解决**:使用 `await asyncio.to_thread(sync_func, ...)` 包装同步调用。
|
||||
|
||||
### 2.2 核心参数详解
|
||||
|
||||
所有插件方法(`inlet`, `outlet`, `pipe`, `action`)都支持注入以下特殊参数:
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| `body` | `dict` | **核心数据**。包含 `messages`, `model`, `stream` 等请求信息。 |
|
||||
| `__user__` | `dict` | **当前用户**。包含 `id`, `name`, `role`, `valves` (用户配置) 等。 |
|
||||
| `__metadata__` | `dict` | **元数据**。包含 `chat_id`, `message_id`。其中 `variables` 字段包含 `{{USER_NAME}}`, `{{CURRENT_TIME}}` 等预置变量。 |
|
||||
| `__request__` | `Request` | **FastAPI 请求对象**。可访问 `app.state` 进行跨插件通信。 |
|
||||
| `__event_emitter__` | `func` | **单向通知**。用于发送 Toast 通知或状态条更新。 |
|
||||
| `__event_call__` | `func` | **双向交互**。用于在前端执行 JS 代码、弹出确认框或输入框。 |
|
||||
|
||||
### 2.3 配置系统 (Valves)
|
||||
|
||||
* **`Valves`**: 管理员全局配置。
|
||||
* **`UserValves`**: 用户级配置(优先级更高,可覆盖全局)。
|
||||
|
||||
```python
|
||||
class Filter:
|
||||
class Valves(BaseModel):
|
||||
API_KEY: str = Field(default="", description="全局 API Key")
|
||||
|
||||
class UserValves(BaseModel):
|
||||
API_KEY: str = Field(default="", description="用户私有 API Key")
|
||||
|
||||
def inlet(self, body, __user__):
|
||||
# 优先使用用户的 Key
|
||||
user_valves = __user__.get("valves", self.UserValves())
|
||||
api_key = user_valves.API_KEY or self.valves.API_KEY
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 插件类型深度解析
|
||||
|
||||
### 3.1 Action (动作)
|
||||
|
||||
**定位**:在消息下方添加按钮,用户点击触发。
|
||||
|
||||
**高级用法:前端执行 JavaScript (文件下载示例)**
|
||||
|
||||
```python
|
||||
import base64
|
||||
|
||||
async def action(self, body, __event_call__):
|
||||
# 1. 后端生成内容
|
||||
content = "Hello OpenWebUI".encode()
|
||||
b64 = base64.b64encode(content).decode()
|
||||
|
||||
# 2. 发送 JS 到前端执行
|
||||
js = f"""
|
||||
const blob = new Blob([atob('{b64}')], {{type: 'text/plain'}});
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = 'hello.txt';
|
||||
a.click();
|
||||
"""
|
||||
await __event_call__({"type": "execute", "data": {"code": js}})
|
||||
```
|
||||
|
||||
### 3.2 Filter (过滤器)
|
||||
|
||||
**定位**:中间件,拦截并修改请求/响应。
|
||||
|
||||
* **`inlet`**: 请求前。用于注入上下文、修改模型参数。
|
||||
* **`outlet`**: 响应后。用于格式化输出、保存日志。
|
||||
* **`stream`**: 流式处理中。用于实时敏感词过滤。
|
||||
|
||||
**示例:注入环境变量**
|
||||
|
||||
```python
|
||||
async def inlet(self, body, __metadata__):
|
||||
vars = __metadata__.get("variables", {})
|
||||
context = f"当前时间: {vars.get('{{CURRENT_DATETIME}}')}"
|
||||
|
||||
# 注入到 System Prompt 或第一条消息
|
||||
if body.get("messages"):
|
||||
body["messages"][0]["content"] += f"\n\n{context}"
|
||||
return body
|
||||
```
|
||||
|
||||
### 3.3 Pipe (管道)
|
||||
|
||||
**定位**:自定义模型/代理。
|
||||
|
||||
**示例:简单的 OpenAI 代理**
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
class Pipe:
|
||||
def pipes(self):
|
||||
return [{"id": "my-gpt", "name": "My GPT Wrapper"}]
|
||||
|
||||
def pipe(self, body):
|
||||
# 可以在这里修改 body,例如强制添加 prompt
|
||||
headers = {"Authorization": f"Bearer {self.valves.API_KEY}"}
|
||||
r = requests.post("https://api.openai.com/v1/chat/completions", json=body, headers=headers, stream=True)
|
||||
return r.iter_lines()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 高级开发模式
|
||||
|
||||
### 4.1 Pipe 与 Filter 协同
|
||||
利用 `__request__.app.state` 在不同插件间共享数据。
|
||||
* **Pipe**: `__request__.app.state.search_results = [...]`
|
||||
* **Filter (Outlet)**: 读取 `search_results` 并将其格式化为引用链接附加到回复末尾。
|
||||
|
||||
### 4.2 异步后台任务
|
||||
不阻塞用户响应,在后台执行耗时操作(如生成总结、存库)。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
async def outlet(self, body, __metadata__):
|
||||
asyncio.create_task(self.background_job(__metadata__["chat_id"]))
|
||||
return body
|
||||
|
||||
async def background_job(self, chat_id):
|
||||
# 执行耗时操作...
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 最佳实践与设计原则
|
||||
|
||||
### 5.1 命名与定位
|
||||
* **简短有力**:如 "闪记卡", "精读"。避免 "文本分析助手" 这种泛词。
|
||||
* **功能互补**:不要重复造轮子,明确你的插件解决了什么特定问题。
|
||||
|
||||
### 5.2 用户体验 (UX)
|
||||
* **反馈及时**:耗时操作前先发送 `notification` ("正在生成...")。
|
||||
* **视觉美观**:Action 输出 HTML 时,使用现代化的 CSS(圆角、阴影、渐变)。
|
||||
* **智能引导**:检测到文本过短时,提示用户"建议输入更多内容以获得更好结果"。
|
||||
|
||||
### 5.3 错误处理
|
||||
永远不要让插件静默失败。捕获异常并通过 `__event_emitter__` 告知用户。
|
||||
|
||||
```python
|
||||
try:
|
||||
# 业务逻辑
|
||||
except Exception as e:
|
||||
await __event_emitter__({
|
||||
"type": "notification",
|
||||
"data": {"type": "error", "content": f"处理失败: {str(e)}"}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 故障排查
|
||||
|
||||
* **HTML 不显示?** 确保包裹在 ` ```html ... ``` ` 代码块中。
|
||||
* **数据库报错?** 检查是否在 `async` 函数中直接调用了同步的 DB 方法,请使用 `asyncio.to_thread`。
|
||||
* **参数未生效?** 检查 `Valves` 定义是否正确,以及是否被 `UserValves` 覆盖。
|
||||
2236
docs/zh/从问一个AI到运营一支AI团队.md
Normal file
2236
docs/zh/从问一个AI到运营一支AI团队.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user