Compare commits

...

76 Commits

Author SHA1 Message Date
fujie
219ba83df3 feat(infographic): release v1.5.0 with smart language detection & organize debug tools 2026-01-28 02:14:30 +08:00
github-actions[bot]
e412aeb93d chore: update community stats - followers increased (164 -> 165) 2026-01-27 17:12:55 +00:00
github-actions[bot]
38102ca0c4 chore: update community stats - followers increased (163 -> 164) 2026-01-27 13:23:17 +00:00
github-actions[bot]
6ab69fba1c chore: update community stats - plugin version updated, followers increased (162 -> 163) 2026-01-26 21:09:49 +00:00
fujie
e0c0f69dc8 fix: update Git operations rules to allow direct pushes to main branch 2026-01-27 04:39:37 +08:00
fujie
7921b14dae fix: remove obsolete openwebui_id from SDK metadata 2026-01-27 04:32:22 +08:00
Jeff
30cde9e871 Merge pull request #36 from Fu-Jie/feature/copilot-sdk-fix
fix(pipes): sync copilot sdk thinking
2026-01-27 04:26:08 +08:00
fujie
ac50cd249a fix(pipes): sync copilot sdk thinking
- Fix thinking visibility by passing user overrides into streaming

- Harden UserValves handling for mapping/instance inputs

- Update bilingual README with per-user valves and troubleshooting
2026-01-27 04:22:36 +08:00
github-actions[bot]
927db6dbaa chore: update community stats - points increased (155 -> 157) 2026-01-26 18:13:33 +00:00
github-actions[bot]
376c398ac7 chore: update community stats - followers increased (161 -> 162) 2026-01-26 12:15:53 +00:00
github-actions[bot]
a167a3cf83 chore: update community stats - followers increased (160 -> 161) 2026-01-26 11:09:11 +00:00
github-actions[bot]
c51e7dfdf7 chore: update community stats - followers increased (159 -> 160) 2026-01-26 10:10:58 +00:00
github-actions[bot]
1d4d13b34b chore: update community stats - points increased (154 -> 155), followers increased (158 -> 159) 2026-01-26 09:15:49 +00:00
github-actions[bot]
18e8775f38 chore: update community stats - points increased (152 -> 154) 2026-01-26 08:12:51 +00:00
fujie
813b019653 release: GitHub Copilot SDK Pipe v0.1.1 2026-01-26 15:29:26 +08:00
github-actions[bot]
b0b1542939 chore: update community stats - new plugin added (18 -> 19), plugin version updated, points increased (148 -> 152), followers increased (157 -> 158) 2026-01-26 07:14:37 +00:00
github-actions[bot]
15f19d8b8d chore: update community stats - points increased (147 -> 148) 2026-01-26 00:38:32 +00:00
fujie
82253b114c feat(copilot-sdk): release v0.1.1 - remove db dependency, add timeout, fix streaming
- Remove database dependency for session management, use chat_id directly
- Add TIMEOUT valve (default 300s)
- Fix streaming issues by handling full message events
- Improve chat_id extraction and tool detection
- Update docs and bump version to 0.1.1
2026-01-26 07:25:01 +08:00
github-actions[bot]
e0bfbf6dd4 chore: update community stats - points increased (146 -> 147) 2026-01-25 19:07:08 +00:00
github-actions[bot]
4689e80e7a chore: update community stats - points increased (144 -> 146) 2026-01-25 11:07:02 +00:00
github-actions[bot]
556e6c1c67 chore: update community stats - new plugin added (17 -> 18), plugin version updated, points increased (143 -> 144) 2026-01-25 10:08:13 +00:00
github-actions[bot]
3ab84a526d chore: update community stats - followers increased (156 -> 157) 2026-01-25 02:55:55 +00:00
github-actions[bot]
bdce96f912 chore: update community stats - followers increased (155 -> 156) 2026-01-24 17:06:50 +00:00
github-actions[bot]
4811b99a4b chore: update community stats - followers increased (154 -> 155) 2026-01-24 05:08:58 +00:00
github-actions[bot]
fb2a64c07a chore: update community stats - followers increased (153 -> 154) 2026-01-23 20:09:48 +00:00
github-actions[bot]
e023e4f2e2 chore: update community stats - followers increased (152 -> 153) 2026-01-23 07:12:10 +00:00
github-actions[bot]
0b16b1e0f4 chore: update community stats - followers increased (151 -> 152) 2026-01-22 21:09:33 +00:00
github-actions[bot]
59073ad7ac chore: update community stats - points increased (141 -> 143) 2026-01-22 20:10:29 +00:00
github-actions[bot]
8248644c45 chore: update community stats - points increased (140 -> 141) 2026-01-22 16:13:08 +00:00
github-actions[bot]
f38e6394c9 chore: update community stats - points increased (136 -> 140) 2026-01-22 15:13:08 +00:00
github-actions[bot]
0aaa529c6b chore: update community stats - followers increased (150 -> 151) 2026-01-22 13:23:00 +00:00
github-actions[bot]
b81a6562a1 chore: update community stats - points increased (135 -> 136) 2026-01-22 11:10:17 +00:00
github-actions[bot]
c5b10db23a chore: update community stats - followers increased (149 -> 150) 2026-01-22 09:14:48 +00:00
github-actions[bot]
d16e444643 chore: update community stats - followers increased (148 -> 149) 2026-01-22 07:13:25 +00:00
github-actions[bot]
8202468099 chore: update community stats - followers increased (147 -> 148) 2026-01-22 06:13:25 +00:00
github-actions[bot]
766e8bd20f chore: update community stats - followers increased (146 -> 147) 2026-01-22 02:51:30 +00:00
github-actions[bot]
1214ab5a8c chore: update community stats - followers increased (145 -> 146) 2026-01-21 21:13:00 +00:00
github-actions[bot]
ebddbb25f8 chore: update community stats - followers increased (144 -> 145) 2026-01-21 15:13:27 +00:00
github-actions[bot]
59545e1110 chore: update community stats - plugin version updated, followers increased (143 -> 144) 2026-01-21 14:14:42 +00:00
fujie
500e090b11 fix: resolve TypeError and improve Pydantic compatibility in async-context-compression v1.2.2 2026-01-21 21:51:58 +08:00
github-actions[bot]
a75ee555fa chore: update community stats - followers increased (142 -> 143) 2026-01-21 13:22:53 +00:00
github-actions[bot]
6a8c2164cd chore: update community stats - followers increased (141 -> 142) 2026-01-21 12:15:46 +00:00
github-actions[bot]
7f7efa325a chore: update community stats - followers increased (140 -> 141) 2026-01-21 04:25:49 +00:00
github-actions[bot]
9ba6cb08fc chore: update community stats - followers increased (139 -> 140) 2026-01-20 20:27:29 +00:00
github-actions[bot]
1872271a2d chore: update community stats - new plugin added (16 -> 17), plugin version updated, points increased (134 -> 135) 2026-01-20 13:23:26 +00:00
fujie
813b50864a docs(folder-memory): add prerequisites section and enhance release workflow with README links
- Add 'Prerequisites' section to folder-memory README files clarifying that conversations must occur inside a folder
- Update docs/plugins/filters/folder-memory.md and folder-memory.zh.md with same prerequisites
- Enhance extract_plugin_versions.py to auto-generate GitHub README URLs in release notes
- Update plugin-development workflow to document README link requirements for publishing
2026-01-20 20:35:06 +08:00
github-actions[bot]
b18cefe320 chore: update community stats - followers increased (137 -> 139) 2026-01-20 12:15:40 +00:00
fujie
a54c359fcf docs(filters): remove language switchers and legacy references from folder-memory docs 2026-01-20 20:11:00 +08:00
fujie
8d83221a4a docs(filters): add author and project info to folder-memory READMEs and docs 2026-01-20 20:08:52 +08:00
fujie
1879000720 docs(filters): add 'What's New' section to folder-memory READMEs and docs
- Add prominent 'What's New' section to README.md, README_CN.md, and global docs.
- Ensure compliance with plugin development standards.
2026-01-20 20:07:46 +08:00
fujie
ba92649a98 feat(filters): refactor folder-rule-collector to folder-memory
- Rename plugin from `folder-rule-collector` to `folder-memory` for better clarity.
- Refactor code to focus on "Project Rules" collection, removing "Knowledge" collection for V1.
- Add `PRIORITY` valve (default: 20) to ensure execution after context compression.
- Update all parameter names to uppercase for consistency.
- Update documentation (README, global docs) with GitHub raw URL for demo image.
- Remove `STATUS` valve as it's redundant with OpenWebUI's built-in function toggle.
- Add `ROADMAP.md` to track future "Project Knowledge" features.
- Update `.github/copilot-instructions.md` with detailed commit message guidelines.
2026-01-20 20:02:50 +08:00
github-actions[bot]
d2276dcaae chore: update community stats - plugin version updated 2026-01-20 11:10:30 +00:00
fujie
25c9d20f3d feat(async-context-compression): release v1.2.1 with smart config & optimizations
This release introduces significant improvements to configuration flexibility, performance, and stability.

**Key Changes:**

*   **Smart Configuration:**
    *   Added `summary_model_max_context` to allow independent context limits for the summary model (e.g., using `gemini-flash` with 1M context to summarize `gpt-4` history).
    *   Implemented auto-detection of base model settings for custom models, ensuring correct threshold application.
*   **Performance & Refactoring:**
    *   Optimized `model_thresholds` parsing with caching to reduce overhead.
    *   Refactored `inlet` and `outlet` logic to remove redundant code and improve maintainability.
    *   Replaced all `print` statements with proper `logging` calls for better production monitoring.
*   **Bug Fixes & Modernization:**
    *   Fixed `datetime.utcnow()` deprecation warnings by switching to timezone-aware `datetime.now(timezone.utc)`.
    *   Corrected type annotations and improved error handling for `JSONResponse` objects from LLM backends.
    *   Removed hard truncation in summary generation to allow full context usage.

**Files Updated:**
*   Plugin source code (English & Chinese)
*   Documentation and READMEs
*   Version bumped to 1.2.1
2026-01-20 19:09:25 +08:00
github-actions[bot]
0d853577df chore: update community stats - followers increased (136 -> 137) 2026-01-20 09:15:24 +00:00
github-actions[bot]
f91f3d8692 chore: update community stats - followers increased (135 -> 136) 2026-01-20 07:14:01 +00:00
github-actions[bot]
0f7cad8dfa chore: update community stats - followers increased (134 -> 135) 2026-01-19 23:08:06 +00:00
fujie
db1a1e7ef0 fix(async-context-compression): sync CN version with EN version logic
- Add missing imports (contextlib, sessionmaker, Engine)
- Add database engine discovery functions (_discover_owui_engine, _discover_owui_schema)
- Fix ChatSummary table to support schema configuration
- Fix duplicate code in __init__ method
- Add _db_session context manager for robust session handling
- Fix inlet method signature (add __request__, __model__ parameters)
- Fix tool output trimming to check native function calling
- Add chat_id empty check in outlet method
2026-01-19 20:37:37 +08:00
github-actions[bot]
e7de80a059 chore: update community stats - plugin version updated, followers increased (133 -> 134) 2026-01-19 12:15:44 +00:00
fujie
0d8c4e048e release: async-context-compression v1.2.0 and markdown-normalizer v1.2.4 2026-01-19 20:11:55 +08:00
github-actions[bot]
014a5a9d1f chore: update community stats - followers increased (132 -> 133) 2026-01-19 10:11:26 +00:00
github-actions[bot]
a6dd970859 chore: update community stats - followers increased (131 -> 132) 2026-01-19 09:16:08 +00:00
github-actions[bot]
aac730f5b1 chore: update community stats - points increased (133 -> 134), followers increased (130 -> 131) 2026-01-19 07:15:13 +00:00
github-actions[bot]
ff95d9328e chore: update community stats - followers increased (129 -> 130) 2026-01-19 06:15:55 +00:00
github-actions[bot]
afe1d8cf52 chore: update community stats - points increased (118 -> 133) 2026-01-18 19:06:16 +00:00
github-actions[bot]
67b819f3de chore: update community stats - followers increased (128 -> 129) 2026-01-18 15:07:24 +00:00
github-actions[bot]
9b6acb6b95 chore: update community stats - points increased (117 -> 118), followers increased (127 -> 128) 2026-01-18 14:07:18 +00:00
github-actions[bot]
a9a59e1e34 chore: update community stats - followers increased (126 -> 127) 2026-01-18 13:14:54 +00:00
github-actions[bot]
5b05397356 chore: update community stats - followers increased (124 -> 126) 2026-01-18 12:13:26 +00:00
github-actions[bot]
7a7dbc0cfa chore: update community stats - points increased (116 -> 117) 2026-01-18 09:08:33 +00:00
github-actions[bot]
6ac0ba6efe chore: update community stats - followers increased (123 -> 124) 2026-01-18 08:10:15 +00:00
github-actions[bot]
d3d008efb4 chore: update community stats - followers increased (122 -> 123) 2026-01-18 07:08:42 +00:00
github-actions[bot]
4f1528128a chore: update community stats - followers increased (121 -> 122) 2026-01-18 05:10:36 +00:00
github-actions[bot]
93c4326206 chore: update community stats - followers increased (120 -> 121) 2026-01-18 01:37:36 +00:00
github-actions[bot]
0fca7fe524 chore: update community stats - points increased (113 -> 116) 2026-01-18 00:38:45 +00:00
github-actions[bot]
afdcab10c6 chore: update community stats - followers increased (119 -> 120) 2026-01-17 21:06:42 +00:00
github-actions[bot]
f8cc5eabe6 chore: update community stats - plugin version updated 2026-01-17 18:10:56 +00:00
68 changed files with 13290 additions and 539 deletions

View File

@@ -90,6 +90,9 @@ Reference: `.github/workflows/release.yml`
- Action: Automatically updates the plugin code and metadata on OpenWebUI.com using `scripts/publish_plugin.py`.
- **Auto-Sync**: If a local plugin has no ID but matches an existing published plugin by **Title**, the script will automatically fetch the ID, update the local file, and proceed with the update.
- Requirement: `OPENWEBUI_API_KEY` secret must be set.
- **README Link**: When announcing a release, always include the GitHub README URL for the plugin:
- Format: `https://github.com/Fu-Jie/awesome-openwebui/blob/main/plugins/{type}/{name}/README.md`
- Example: `https://github.com/Fu-Jie/awesome-openwebui/blob/main/plugins/filters/folder-memory/README.md`
### Pull Request Check
- Workflow: `.github/workflows/plugin-version-check.yml`

View File

@@ -62,18 +62,41 @@ plugins/
│ ├── ACTION_PLUGIN_TEMPLATE_CN.py # Chinese template
│ └── README.md
├── filters/ # Filter 插件 (输入处理)
── my_filter/
│ │ ├── my_filter.py
│ │ ├── 我的过滤器.py
│ │ ├── README.md
│ │ └── README_CN.md
│ └── README.md
── ...
├── pipes/ # Pipe 插件 (输出处理)
│ └── ...
── pipelines/ # Pipeline 插件
── pipelines/ # Pipeline 插件
└── ...
├── debug/ # 调试与开发工具 (Debug & Development Tools)
│ ├── my_debug_tool/
│ │ ├── debug_script.py
│ │ └── notes.md
│ └── ...
```
#### 调试目录规范 (Debug Directory Standards)
`plugins/debug/` 目录用于存放调试用的脚本、临时验证代码或开发笔记。
**目录结构 (Directory Structure)**:
应根据调试工具所属的插件或功能模块进行子目录分类,而非将文件散落在根目录。
```
plugins/debug/
├── my_plugin_name/ # 特定插件的调试文件 (Debug files for specific plugin)
│ ├── debug_script.py
│ └── guides/
├── common_tools/ # 通用调试工具 (General debug tools)
│ └── ...
└── ...
```
**规范说明 (Guidelines)**:
- **不强制要求 README**: 该目录下的子项目不需要包含 `README.md`
- **发布豁免**: 该目录下的内容**绝不会**被发布脚本处理。
- **内容灵活性**: 可以包含 Python 脚本、Markdown 文档、JSON 数据等。
- **分类存放**: 任何调试产物(如 `test_*.py`, `inspect_*.py`)都不应直接存放在项目根目录,必须移动到此目录下相应的子文件夹中。
### 3. 文档字符串规范 (Docstring Standard)
每个插件文件必须以标准化的文档字符串开头:
@@ -100,13 +123,14 @@ description: 插件功能的简短描述。Brief description of plugin functiona
| `author_url` | 作者主页链接 | `https://github.com/Fu-Jie/awesome-openwebui` |
| `funding_url` | 赞助/项目链接 | `https://github.com/open-webui` |
| `version` | 语义化版本号 | `0.1.0`, `1.2.3` |
| `icon_url` | 图标 (Base64 编码的 SVG) | 见下方图标规范 |
| `icon_url` | 图标 (Base64 编码的 SVG) | 仅 Action 插件**必须**提供。其他类型可选。 |
| `requirements` | 额外依赖 (仅 OpenWebUI 环境未安装的) | `python-docx==1.1.2` |
| `description` | 功能描述 | `将对话导出为 Word 文档` |
#### 图标规范 (Icon Guidelines)
- 图标来源:从 [Lucide Icons](https://lucide.dev/icons/) 获取符合插件功能的图标
- 适用范围Action 插件**必须**提供,其他插件可选
- 格式Base64 编码的 SVG
- 获取方法:从 Lucide 下载 SVG然后使用 Base64 编码
- 示例格式:
@@ -408,6 +432,51 @@ async def long_running_task_with_notification(self, event_emitter, ...):
return task_future.result()
```
### 7. 前端数据获取与交互规范 (Frontend Data Access & Interaction)
#### 获取前端信息 (Retrieving Frontend Info)
当需要获取用户浏览器的上下文信息如语言、时区、LocalStorage**必须**使用 `__event_call__``execute` 类型,而不是通过文件上传或复杂的 API 请求。
```python
async def _get_frontend_value(self, js_code: str) -> str:
"""Helper to execute JS and get return value."""
try:
response = await __event_call__(
{
"type": "execute",
"data": {
"code": js_code,
},
}
)
return str(response)
except Exception as e:
logger.error(f"Failed to execute JS: {e}")
return ""
# 示例:获取界面语言 (Get UI Language)
async def get_user_language(self):
js_code = """
return (
localStorage.getItem('locale') ||
localStorage.getItem('language') ||
navigator.language ||
'en-US'
);
"""
return await self._get_frontend_value(js_code)
```
#### 适用场景与引导 (Usage Guidelines)
- **语言适配**: 动态获取界面语言 (`ru-RU`, `zh-CN`) 自动切换输出语言。
- **时区处理**: 获取 `Intl.DateTimeFormat().resolvedOptions().timeZone` 处理时间。
- **客户端存储**: 读取 `localStorage` 中的用户偏好设置。
- **硬件能力**: 获取 `navigator.clipboard``navigator.geolocation` (需授权)。
**注意**: 即使插件有 `Valves` 配置,也应优先尝试自动探测,提升用户体验。
---
## ⚡ Action 插件规范 (Action Plugin Standards)
@@ -788,6 +857,19 @@ Filter 实例是**单例 (Singleton)**。
---
## 🧪 测试规范 (Testing Standards)
### 1. Copilot SDK 测试模型 (Copilot SDK Test Models)
在编写 Copilot SDK 相关的测试脚本时 (如 `test_injection.py`, `test_capabilities.py` 等)**必须**优先使用以下免费/低成本模型之一,严禁使用高昂费用的模型进行常规测试,除非用户明确要求:
- `gpt-5-mini` (首选 / Preferred)
- `gpt-4.1`
此规则适用于所有自动化测试脚本和临时验证脚本。
---
## 🔄 工作流与流程 (Workflow & Process)
### 1. ✅ 开发检查清单 (Development Checklist)
@@ -822,11 +904,27 @@ Filter 实例是**单例 (Singleton)**。
#### Commit Message 规范
使用 Conventional Commits 格式 (`feat`, `fix`, `docs`, etc.)。
**必须**在提交标题与正文中清晰描述变更内容,确保在 Release 页面可读且可追踪。
要求:
- 标题必须包含“做了什么”与影响范围(避免含糊词)。
- 正文必须列出关键变更点1-3 条),与实际改动一一对应。
- 若影响用户或插件行为,必须在正文标明影响与迁移说明。
推荐格式:
- `feat(actions): add export settings panel`
- `fix(filters): handle empty metadata to avoid crash`
- `docs(plugins): update bilingual README structure`
正文示例:
- Add valves for export format selection
- Update README/README_CN to include What's New section
- Migration: default TITLE_SOURCE changed to chat_title
### 4. 🤖 Git Operations (Agent Rules)
- **允许**: 直接推送到 `main` 分支并发布。
- **允许**: 创建功能分支 (`feature/xxx`),推送到功能分支。
- **禁止**: 直接推送到 `main`,自动合并到 `main`
### 5. 🤝 贡献者认可规范 (Contributor Recognition)

View File

@@ -6,6 +6,7 @@ on:
- main
paths:
- 'plugins/**/*.py'
- '!plugins/debug/**'
release:
types: [published]
workflow_dispatch:

View File

@@ -246,6 +246,52 @@ jobs:
echo "=== Collected Files ==="
find release_plugins -name "*.py" -type f | head -20
- name: Update plugin icon URLs
run: |
echo "Updating icon_url in plugins to use absolute GitHub URLs..."
# Base URL for raw content using the release tag
REPO_URL="https://raw.githubusercontent.com/${{ github.repository }}/${{ steps.version.outputs.version }}"
find release_plugins -name "*.py" | while read -r file; do
# $file is like release_plugins/plugins/actions/infographic/infographic.py
# Remove release_plugins/ prefix to get the path in the repo
src_file="${file#release_plugins/}"
src_dir=$(dirname "$src_file")
base_name=$(basename "$src_file" .py)
# Check if a corresponding png exists in the source repository
png_file="${src_dir}/${base_name}.png"
if [ -f "$png_file" ]; then
echo "Found icon for $src_file: $png_file"
TARGET_ICON_URL="${REPO_URL}/${png_file}"
# Use python for safe replacement
python3 -c "
import sys
import re
file_path = '$file'
icon_url = '$TARGET_ICON_URL'
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Replace icon_url: ... with new url
# Matches 'icon_url: ...' and replaces it
new_content = re.sub(r'^icon_url:.*$', f'icon_url: {icon_url}', content, flags=re.MULTILINE)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f'Successfully updated icon_url in {file_path}')
except Exception as e:
print(f'Error updating {file_path}: {e}', file=sys.stderr)
sys.exit(1)
"
fi
done
- name: Debug Filenames
run: |
python3 -c "import sys; print(f'Filesystem encoding: {sys.getfilesystemencoding()}')"

View File

@@ -10,28 +10,28 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
<!-- STATS_START -->
## 📊 Community Stats
> 🕐 Auto-updated: 2026-01-18 00:08
> 🕐 Auto-updated: 2026-01-28 01:12
| 👤 Author | 👥 Followers | ⭐ Points | 🏆 Contributions |
|:---:|:---:|:---:|:---:|
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **119** | **113** | **25** |
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **165** | **157** | **34** |
| 📝 Posts | ⬇️ Downloads | 👁️ Views | 👍 Upvotes | 💾 Saves |
|:---:|:---:|:---:|:---:|:---:|
| **16** | **1659** | **19990** | **99** | **126** |
| **19** | **2501** | **29277** | **141** | **197** |
### 🔥 Top 6 Popular Plugins
> 🕐 Auto-updated: 2026-01-18 00:08
> 🕐 Auto-updated: 2026-01-28 01:12
| Rank | Plugin | Version | Downloads | Views | Updated |
|:---:|------|:---:|:---:|:---:|:---:|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 507 | 4641 | 2026-01-17 |
| 🥈 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 228 | 2285 | 2026-01-17 |
| 🥉 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 202 | 751 | 2026-01-07 |
| 4⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.1.3 | 174 | 1906 | 2026-01-17 |
| 5⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 134 | 1255 | 2026-01-17 |
| 6⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 132 | 2269 | 2026-01-17 |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 652 | 5828 | 2026-01-17 |
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 434 | 3928 | 2026-01-25 |
| 🥉 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 263 | 1082 | 2026-01-07 |
| 4⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 238 | 1916 | 2026-01-17 |
| 5⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 236 | 2557 | 2026-01-21 |
| 6⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 176 | 2769 | 2026-01-17 |
*See full stats in [Community Stats Report](./docs/community-stats.md)*
<!-- STATS_END -->
@@ -43,6 +43,7 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
Located in the `plugins/` directory, containing Python-based enhancements:
#### Actions
- **Smart Mind Map** (`smart-mind-map`): Generates interactive mind maps from text.
- **Smart Infographic** (`infographic`): Transforms text into professional infographics using AntV.
- **Flash Card** (`flash-card`): Quickly generates beautiful flashcards for learning.
@@ -51,11 +52,18 @@ Located in the `plugins/` directory, containing Python-based enhancements:
- **Export to Word** (`export_to_docx`): Exports chat history to Word documents.
#### Filters
- **Async Context Compression** (`async-context-compression`): Optimizes token usage via context compression.
- **Context Enhancement** (`context_enhancement_filter`): Enhances chat context.
- **Folder Memory** (`folder-memory`): Automatically extracts project rules from conversations and injects them into the folder's system prompt.
- **Markdown Normalizer** (`markdown_normalizer`): Fixes common Markdown formatting issues in LLM outputs.
#### Pipes
- **GitHub Copilot SDK** (`github-copilot-sdk`): Official GitHub Copilot SDK integration. Supports dynamic models, multi-turn conversation, streaming, multimodal input, and infinite sessions.
#### Pipelines
- **MoE Prompt Refiner** (`moe_prompt_refiner`): Refines prompts for Mixture of Experts (MoE) summary requests to generate high-quality comprehensive reports.
### 🎯 Prompts
@@ -100,6 +108,7 @@ This project is a collection of resources and does not require a Python environm
### Contributing
If you have great prompts or plugins to share:
1. Fork this repository.
2. Add your files to the appropriate `prompts/` or `plugins/` directory.
3. Submit a Pull Request.

View File

@@ -7,28 +7,28 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
<!-- STATS_START -->
## 📊 社区统计
> 🕐 自动更新于 2026-01-18 00:08
> 🕐 自动更新于 2026-01-28 01:12
| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🏆 贡献 |
|:---:|:---:|:---:|:---:|
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **119** | **113** | **25** |
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **165** | **157** | **34** |
| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 |
|:---:|:---:|:---:|:---:|:---:|
| **16** | **1659** | **19990** | **99** | **126** |
| **19** | **2501** | **29277** | **141** | **197** |
### 🔥 热门插件 Top 6
> 🕐 自动更新于 2026-01-18 00:08
> 🕐 自动更新于 2026-01-28 01:12
| 排名 | 插件 | 版本 | 下载 | 浏览 | 更新日期 |
|:---:|------|:---:|:---:|:---:|:---:|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 507 | 4641 | 2026-01-17 |
| 🥈 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 228 | 2285 | 2026-01-17 |
| 🥉 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 202 | 751 | 2026-01-07 |
| 4⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.1.3 | 174 | 1906 | 2026-01-17 |
| 5⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 134 | 1255 | 2026-01-17 |
| 6⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 132 | 2269 | 2026-01-17 |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 652 | 5828 | 2026-01-17 |
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 434 | 3928 | 2026-01-25 |
| 🥉 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 263 | 1082 | 2026-01-07 |
| 4⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 238 | 1916 | 2026-01-17 |
| 5⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 236 | 2557 | 2026-01-21 |
| 6⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 176 | 2769 | 2026-01-17 |
*完整统计请查看 [社区统计报告](./docs/community-stats.zh.md)*
<!-- STATS_END -->
@@ -40,6 +40,7 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
位于 `plugins/` 目录,包含各类 Python 编写的功能增强插件:
#### Actions (交互增强)
- **Smart Mind Map** (`smart-mind-map`): 智能分析文本并生成交互式思维导图。
- **Smart Infographic** (`infographic`): 基于 AntV 的智能信息图生成工具。
- **Flash Card** (`flash-card`): 快速生成精美的学习记忆卡片。
@@ -48,17 +49,22 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
- **Export to Word** (`export_to_docx`): 将对话内容导出为 Word 文档。
#### Filters (消息处理)
- **Async Context Compression** (`async-context-compression`): 异步上下文压缩,优化 Token 使用。
- **Context Enhancement** (`context_enhancement_filter`): 上下文增强过滤器。
- **Folder Memory** (`folder-memory`): 自动从对话中提取项目规则并注入到文件夹系统提示词中。
- **Gemini Manifold Companion** (`gemini_manifold_companion`): Gemini Manifold 配套增强。
- **Gemini Multimodal Filter** (`web_gemini_multimodel_filter`): 为任意模型提供多模态能力PDF、Office、视频等支持智能路由和字幕精修。
- **Markdown Normalizer** (`markdown_normalizer`): 修复 LLM 输出中常见的 Markdown 格式问题。
- **Multi-Model Context Merger** (`multi_model_context_merger`): 自动合并并注入多模型回答的上下文。
#### Pipes (模型管道)
- **GitHub Copilot SDK** (`github-copilot-sdk`): GitHub Copilot SDK 官方集成。支持动态模型、多轮对话、流式输出、图片输入及无限会话。
- **Gemini Manifold** (`gemini_mainfold`): 集成 Gemini 模型的管道。
#### Pipelines (工作流管道)
- **MoE Prompt Refiner** (`moe_prompt_refiner`): 优化多模型 (MoE) 汇总请求的提示词,生成高质量的综合报告。
### 🎯 提示词 (Prompts)
@@ -106,6 +112,7 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
### 贡献代码
如果你有优质的提示词或插件想要分享:
1. Fork 本仓库。
2. 将你的文件添加到对应的 `prompts/``plugins/` 目录。
3. 提交 Pull Request。

View File

@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"label": "downloads",
"message": "1.7k",
"message": "2.5k",
"color": "blue",
"namedLogo": "openwebui"
}

View File

@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"label": "followers",
"message": "119",
"message": "165",
"color": "blue"
}

View File

@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"label": "plugins",
"message": "16",
"message": "19",
"color": "green"
}

View File

@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"label": "points",
"message": "113",
"message": "157",
"color": "orange"
}

View File

@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"label": "upvotes",
"message": "99",
"message": "141",
"color": "brightgreen"
}

View File

@@ -1,14 +1,16 @@
{
"total_posts": 16,
"total_downloads": 1659,
"total_views": 19990,
"total_upvotes": 99,
"total_posts": 19,
"total_downloads": 2501,
"total_views": 29277,
"total_upvotes": 141,
"total_downvotes": 2,
"total_saves": 126,
"total_comments": 23,
"total_saves": 197,
"total_comments": 38,
"by_type": {
"pipe": 1,
"action": 14,
"unknown": 2
"unknown": 3,
"filter": 1
},
"posts": [
{
@@ -18,29 +20,29 @@
"version": "0.9.1",
"author": "Fu-Jie",
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
"downloads": 507,
"views": 4641,
"upvotes": 14,
"saves": 28,
"downloads": 652,
"views": 5828,
"upvotes": 17,
"saves": 38,
"comments": 11,
"created_at": "2025-12-30",
"updated_at": "2026-01-17",
"url": "https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a"
},
{
"title": "📊 Smart Infographic (AntV)",
"title": "Smart Infographic",
"slug": "smart_infographic_ad6f0c7f",
"type": "action",
"version": "1.4.9",
"author": "Fu-Jie",
"description": "AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.",
"downloads": 228,
"views": 2285,
"upvotes": 11,
"saves": 16,
"comments": 2,
"downloads": 434,
"views": 3928,
"upvotes": 18,
"saves": 28,
"comments": 10,
"created_at": "2025-12-28",
"updated_at": "2026-01-17",
"updated_at": "2026-01-25",
"url": "https://openwebui.com/posts/smart_infographic_ad6f0c7f"
},
{
@@ -50,31 +52,15 @@
"version": "0.3.7",
"author": "Fu-Jie",
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
"downloads": 202,
"views": 751,
"upvotes": 3,
"saves": 5,
"downloads": 263,
"views": 1082,
"upvotes": 4,
"saves": 6,
"comments": 0,
"created_at": "2025-05-30",
"updated_at": "2026-01-07",
"url": "https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d"
},
{
"title": "Async Context Compression",
"slug": "async_context_compression_b1655bc8",
"type": "action",
"version": "1.1.3",
"author": "Fu-Jie",
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
"downloads": 174,
"views": 1906,
"upvotes": 7,
"saves": 18,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-17",
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
},
{
"title": "Export to Word (Enhanced)",
"slug": "export_to_word_enhanced_formatting_fca6a315",
@@ -82,15 +68,31 @@
"version": "0.4.3",
"author": "Fu-Jie",
"description": "Export current conversation from Markdown to Word (.docx) with Mermaid diagrams rendered client-side (Mermaid.js, SVG+PNG), LaTeX math, real hyperlinks, improved tables, syntax highlighting, and blockquote support.",
"downloads": 134,
"views": 1255,
"upvotes": 6,
"saves": 15,
"downloads": 238,
"views": 1916,
"upvotes": 8,
"saves": 21,
"comments": 0,
"created_at": "2026-01-03",
"updated_at": "2026-01-17",
"url": "https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315"
},
{
"title": "Async Context Compression",
"slug": "async_context_compression_b1655bc8",
"type": "action",
"version": "1.2.2",
"author": "Fu-Jie",
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
"downloads": 236,
"views": 2557,
"upvotes": 9,
"saves": 27,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-21",
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
},
{
"title": "Flash Card",
"slug": "flash_card_65a2ea8f",
@@ -98,10 +100,10 @@
"version": "0.2.4",
"author": "Fu-Jie",
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
"downloads": 132,
"views": 2269,
"upvotes": 8,
"saves": 10,
"downloads": 176,
"views": 2769,
"upvotes": 11,
"saves": 13,
"comments": 2,
"created_at": "2025-12-30",
"updated_at": "2026-01-17",
@@ -111,34 +113,18 @@
"title": "Markdown Normalizer",
"slug": "markdown_normalizer_baaa8732",
"type": "action",
"version": "1.2.2",
"version": "1.2.4",
"author": "Fu-Jie",
"description": "A content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting.",
"downloads": 63,
"views": 1746,
"upvotes": 9,
"saves": 15,
"downloads": 160,
"views": 2884,
"upvotes": 10,
"saves": 22,
"comments": 5,
"created_at": "2026-01-12",
"updated_at": "2026-01-17",
"updated_at": "2026-01-19",
"url": "https://openwebui.com/posts/markdown_normalizer_baaa8732"
},
{
"title": "导出为 Word (增强版)",
"slug": "导出为_word_支持公式流程图表格和代码块_8a6306c0",
"type": "action",
"version": "0.4.3",
"author": "Fu-Jie",
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
"downloads": 62,
"views": 1262,
"upvotes": 10,
"saves": 3,
"comments": 1,
"created_at": "2026-01-04",
"updated_at": "2026-01-17",
"url": "https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0"
},
{
"title": "Deep Dive",
"slug": "deep_dive_c0b846e4",
@@ -146,15 +132,31 @@
"version": "1.0.0",
"author": "Fu-Jie",
"description": "A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.",
"downloads": 58,
"views": 605,
"upvotes": 3,
"saves": 5,
"downloads": 94,
"views": 863,
"upvotes": 4,
"saves": 8,
"comments": 0,
"created_at": "2026-01-08",
"updated_at": "2026-01-08",
"url": "https://openwebui.com/posts/deep_dive_c0b846e4"
},
{
"title": "导出为 Word (增强版)",
"slug": "导出为_word_支持公式流程图表格和代码块_8a6306c0",
"type": "action",
"version": "0.4.3",
"author": "Fu-Jie",
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
"downloads": 89,
"views": 1689,
"upvotes": 11,
"saves": 4,
"comments": 4,
"created_at": "2026-01-04",
"updated_at": "2026-01-17",
"url": "https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0"
},
{
"title": "📊 智能信息图 (AntV Infographic)",
"slug": "智能信息图_e04a48ff",
@@ -162,15 +164,31 @@
"version": "1.4.9",
"author": "Fu-Jie",
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
"downloads": 41,
"views": 654,
"upvotes": 5,
"downloads": 47,
"views": 799,
"upvotes": 6,
"saves": 0,
"comments": 0,
"created_at": "2025-12-28",
"updated_at": "2026-01-17",
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
},
{
"title": "📂 Folder Memory Auto-Evolving Project Context",
"slug": "folder_memory_auto_evolving_project_context_4a9875b2",
"type": "filter",
"version": "0.1.0",
"author": "Fu-Jie",
"description": "Automatically extracts project rules from conversations and injects them into the folder's system prompt.",
"downloads": 28,
"views": 810,
"upvotes": 4,
"saves": 7,
"comments": 0,
"created_at": "2026-01-20",
"updated_at": "2026-01-20",
"url": "https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2"
},
{
"title": "思维导图",
"slug": "智能生成交互式思维导图帮助用户可视化知识_8d4b097b",
@@ -178,15 +196,31 @@
"version": "0.9.1",
"author": "Fu-Jie",
"description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。",
"downloads": 22,
"views": 392,
"upvotes": 2,
"downloads": 27,
"views": 463,
"upvotes": 4,
"saves": 1,
"comments": 0,
"created_at": "2025-12-31",
"updated_at": "2026-01-17",
"url": "https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b"
},
{
"title": "异步上下文压缩",
"slug": "异步上下文压缩_5c0617cb",
"type": "action",
"version": "1.2.2",
"author": "Fu-Jie",
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
"downloads": 22,
"views": 511,
"upvotes": 5,
"saves": 2,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-21",
"url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb"
},
{
"title": "闪记卡 (Flash Card)",
"slug": "闪记卡生成插件_4a31eac3",
@@ -194,31 +228,15 @@
"version": "0.2.4",
"author": "Fu-Jie",
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
"downloads": 16,
"views": 430,
"upvotes": 4,
"downloads": 19,
"views": 524,
"upvotes": 6,
"saves": 1,
"comments": 0,
"created_at": "2025-12-30",
"updated_at": "2026-01-17",
"url": "https://openwebui.com/posts/闪记卡生成插件_4a31eac3"
},
{
"title": "异步上下文压缩",
"slug": "异步上下文压缩_5c0617cb",
"type": "action",
"version": "1.1.3",
"author": "Fu-Jie",
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
"downloads": 14,
"views": 340,
"upvotes": 4,
"saves": 1,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-17",
"url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb"
},
{
"title": "精读",
"slug": "精读_99830b0f",
@@ -226,15 +244,47 @@
"version": "1.0.0",
"author": "Fu-Jie",
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
"downloads": 6,
"views": 254,
"upvotes": 2,
"downloads": 9,
"views": 316,
"upvotes": 3,
"saves": 1,
"comments": 0,
"created_at": "2026-01-08",
"updated_at": "2026-01-08",
"url": "https://openwebui.com/posts/精读_99830b0f"
},
{
"title": "GitHub Copilot Official SDK Pipe",
"slug": "github_copilot_official_sdk_pipe_ce96f7b4",
"type": "pipe",
"version": "0.2.3",
"author": "Fu-Jie",
"description": "Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, infinite sessions, and frontend debug logging.",
"downloads": 7,
"views": 295,
"upvotes": 2,
"saves": 2,
"comments": 1,
"created_at": "2026-01-26",
"updated_at": "2026-01-26",
"url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4"
},
{
"title": "🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager",
"slug": "open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e",
"type": "unknown",
"version": "",
"author": "",
"description": "",
"downloads": 0,
"views": 653,
"upvotes": 6,
"saves": 8,
"comments": 3,
"created_at": "2026-01-25",
"updated_at": "2026-01-25",
"url": "https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e"
},
{
"title": "Review of Claude Haiku 4.5",
"slug": "review_of_claude_haiku_45_41b0db39",
@@ -243,8 +293,8 @@
"author": "",
"description": "",
"downloads": 0,
"views": 46,
"upvotes": 0,
"views": 99,
"upvotes": 1,
"saves": 0,
"comments": 0,
"created_at": "2026-01-14",
@@ -259,9 +309,9 @@
"author": "",
"description": "",
"downloads": 0,
"views": 1154,
"upvotes": 11,
"saves": 7,
"views": 1291,
"upvotes": 12,
"saves": 8,
"comments": 2,
"created_at": "2026-01-10",
"updated_at": "2026-01-10",
@@ -273,11 +323,11 @@
"name": "Fu-Jie",
"profile_url": "https://openwebui.com/u/Fu-Jie",
"profile_image": "https://community.s3.openwebui.com/uploads/users/b15d1348-4347-42b4-b815-e053342d6cb0/profile_d9510745-4bd4-4f8f-a997-4a21847d9300.webp",
"followers": 119,
"following": 2,
"total_points": 113,
"post_points": 97,
"comment_points": 16,
"contributions": 25
"followers": 165,
"following": 3,
"total_points": 157,
"post_points": 139,
"comment_points": 18,
"contributions": 34
}
}

View File

@@ -1,40 +1,45 @@
# 📊 OpenWebUI Community Stats Report
> 📅 Updated: 2026-01-18 00:08
> 📅 Updated: 2026-01-28 01:12
## 📈 Overview
| Metric | Value |
|------|------|
| 📝 Total Posts | 16 |
| ⬇️ Total Downloads | 1659 |
| 👁️ Total Views | 19990 |
| 👍 Total Upvotes | 99 |
| 💾 Total Saves | 126 |
| 💬 Total Comments | 23 |
| 📝 Total Posts | 19 |
| ⬇️ Total Downloads | 2501 |
| 👁️ Total Views | 29277 |
| 👍 Total Upvotes | 141 |
| 💾 Total Saves | 197 |
| 💬 Total Comments | 38 |
## 📂 By Type
- **pipe**: 1
- **action**: 14
- **unknown**: 2
- **unknown**: 3
- **filter**: 1
## 📋 Posts List
| Rank | Title | Type | Version | Downloads | Views | Upvotes | Saves | Updated |
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 507 | 4641 | 14 | 28 | 2026-01-17 |
| 2 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 228 | 2285 | 11 | 16 | 2026-01-17 |
| 3 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 202 | 751 | 3 | 5 | 2026-01-07 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.1.3 | 174 | 1906 | 7 | 18 | 2026-01-17 |
| 5 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 134 | 1255 | 6 | 15 | 2026-01-17 |
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 132 | 2269 | 8 | 10 | 2026-01-17 |
| 7 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.2 | 63 | 1746 | 9 | 15 | 2026-01-17 |
| 8 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 62 | 1262 | 10 | 3 | 2026-01-17 |
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 58 | 605 | 3 | 5 | 2026-01-08 |
| 10 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 41 | 654 | 5 | 0 | 2026-01-17 |
| 11 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 22 | 392 | 2 | 1 | 2026-01-17 |
| 12 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 16 | 430 | 4 | 1 | 2026-01-17 |
| 13 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.1.3 | 14 | 340 | 4 | 1 | 2026-01-17 |
| 14 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 6 | 254 | 2 | 1 | 2026-01-08 |
| 15 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 46 | 0 | 0 | 2026-01-14 |
| 16 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1154 | 11 | 7 | 2026-01-10 |
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 652 | 5828 | 17 | 38 | 2026-01-17 |
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 434 | 3928 | 18 | 28 | 2026-01-25 |
| 3 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 263 | 1082 | 4 | 6 | 2026-01-07 |
| 4 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 238 | 1916 | 8 | 21 | 2026-01-17 |
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 236 | 2557 | 9 | 27 | 2026-01-21 |
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 176 | 2769 | 11 | 13 | 2026-01-17 |
| 7 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 160 | 2884 | 10 | 22 | 2026-01-19 |
| 8 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 94 | 863 | 4 | 8 | 2026-01-08 |
| 9 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 89 | 1689 | 11 | 4 | 2026-01-17 |
| 10 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 47 | 799 | 6 | 0 | 2026-01-17 |
| 11 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 28 | 810 | 4 | 7 | 2026-01-20 |
| 12 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 27 | 463 | 4 | 1 | 2026-01-17 |
| 13 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 22 | 511 | 5 | 2 | 2026-01-21 |
| 14 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 19 | 524 | 6 | 1 | 2026-01-17 |
| 15 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 9 | 316 | 3 | 1 | 2026-01-08 |
| 16 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | 0.2.3 | 7 | 295 | 2 | 2 | 2026-01-26 |
| 17 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 653 | 6 | 8 | 2026-01-25 |
| 18 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 99 | 1 | 0 | 2026-01-14 |
| 19 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1291 | 12 | 8 | 2026-01-10 |

View File

@@ -1,40 +1,45 @@
# 📊 OpenWebUI 社区统计报告
> 📅 更新时间: 2026-01-18 00:08
> 📅 更新时间: 2026-01-28 01:12
## 📈 总览
| 指标 | 数值 |
|------|------|
| 📝 发布数量 | 16 |
| ⬇️ 总下载量 | 1659 |
| 👁️ 总浏览量 | 19990 |
| 👍 总点赞数 | 99 |
| 💾 总收藏数 | 126 |
| 💬 总评论数 | 23 |
| 📝 发布数量 | 19 |
| ⬇️ 总下载量 | 2501 |
| 👁️ 总浏览量 | 29277 |
| 👍 总点赞数 | 141 |
| 💾 总收藏数 | 197 |
| 💬 总评论数 | 38 |
## 📂 按类型分类
- **pipe**: 1
- **action**: 14
- **unknown**: 2
- **unknown**: 3
- **filter**: 1
## 📋 发布列表
| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 507 | 4641 | 14 | 28 | 2026-01-17 |
| 2 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 228 | 2285 | 11 | 16 | 2026-01-17 |
| 3 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 202 | 751 | 3 | 5 | 2026-01-07 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.1.3 | 174 | 1906 | 7 | 18 | 2026-01-17 |
| 5 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 134 | 1255 | 6 | 15 | 2026-01-17 |
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 132 | 2269 | 8 | 10 | 2026-01-17 |
| 7 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.2 | 63 | 1746 | 9 | 15 | 2026-01-17 |
| 8 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 62 | 1262 | 10 | 3 | 2026-01-17 |
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 58 | 605 | 3 | 5 | 2026-01-08 |
| 10 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 41 | 654 | 5 | 0 | 2026-01-17 |
| 11 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 22 | 392 | 2 | 1 | 2026-01-17 |
| 12 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 16 | 430 | 4 | 1 | 2026-01-17 |
| 13 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.1.3 | 14 | 340 | 4 | 1 | 2026-01-17 |
| 14 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 6 | 254 | 2 | 1 | 2026-01-08 |
| 15 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 46 | 0 | 0 | 2026-01-14 |
| 16 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1154 | 11 | 7 | 2026-01-10 |
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 652 | 5828 | 17 | 38 | 2026-01-17 |
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 434 | 3928 | 18 | 28 | 2026-01-25 |
| 3 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 263 | 1082 | 4 | 6 | 2026-01-07 |
| 4 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 238 | 1916 | 8 | 21 | 2026-01-17 |
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 236 | 2557 | 9 | 27 | 2026-01-21 |
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 176 | 2769 | 11 | 13 | 2026-01-17 |
| 7 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 160 | 2884 | 10 | 22 | 2026-01-19 |
| 8 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 94 | 863 | 4 | 8 | 2026-01-08 |
| 9 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 89 | 1689 | 11 | 4 | 2026-01-17 |
| 10 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 47 | 799 | 6 | 0 | 2026-01-17 |
| 11 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 28 | 810 | 4 | 7 | 2026-01-20 |
| 12 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 27 | 463 | 4 | 1 | 2026-01-17 |
| 13 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 22 | 511 | 5 | 2 | 2026-01-21 |
| 14 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 19 | 524 | 6 | 1 | 2026-01-17 |
| 15 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 9 | 316 | 3 | 1 | 2026-01-08 |
| 16 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | 0.2.3 | 7 | 295 | 2 | 2 | 2026-01-26 |
| 17 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 653 | 6 | 8 | 2026-01-25 |
| 18 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 99 | 1 | 0 | 2026-01-14 |
| 19 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1291 | 12 | 8 | 2026-01-10 |

View File

@@ -349,6 +349,53 @@ await __event_emitter__(
)
```
#### Advanced Use Case: Retrieving Frontend Data
One of the most powerful capabilities of the `execute` event type is the ability to fetch data from the browser environment (JavaScript) and return it to your Python backend. This allows plugins to access information like:
- `localStorage` items (user preferences, tokens)
- `navigator` properties (language, geolocation, platform)
- `document` properties (cookies, URL parameters)
**How it works:**
The JavaScript code you provide in the `"code"` field is executed in the browser. If your JS code includes a `return` statement, that value is sent back to Python as the result of `await __event_call__`.
**Example: Getting the User's UI Language**
```python
try:
# Execute JS on the frontend to get language settings
response = await __event_call__(
{
"type": "execute",
"data": {
# This JS code runs in the browser.
# The 'return' value is sent back to Python.
"code": """
return (
localStorage.getItem('locale') ||
localStorage.getItem('language') ||
navigator.language ||
'en-US'
);
""",
},
}
)
# 'response' will contain the string returned by JS (e.g., "en-US", "zh-CN")
# Note: Wrap in try-except to handle potential timeouts or JS errors
logger.info(f"Frontend Language: {response}")
except Exception as e:
logger.error(f"Failed to get frontend data: {e}")
```
**Key capabilities unlocked:**
- **Context Awareness:** Adapt responses based on user time zone or language.
- **Client-Side Storage:** Use `localStorage` to persist simple plugin settings without a database.
- **Hardware Access:** Request geolocation or clipboard access (requires user permission).
---
## 🏗️ When & Where to Use Events
@@ -421,4 +468,4 @@ Refer to this document for common event types and structures, and explore Open W
---
**Happy event-driven coding in Open WebUI! 🚀**
**Happy event-driven coding in Open WebUI! 🚀**

View File

@@ -1,7 +1,7 @@
# Async Context Compression
<span class="category-badge filter">Filter</span>
<span class="version-badge">v1.1.3</span>
<span class="version-badge">v1.2.2</span>
Reduces token consumption in long conversations through intelligent summarization while maintaining conversational coherence.
@@ -34,6 +34,12 @@ This is especially useful for:
- :material-check-all: **Open WebUI v0.7.x Compatibility**: Dynamic DB session handling
- :material-account-convert: **Improved Compatibility**: Summary role changed to `assistant`
- :material-shield-check: **Enhanced Stability**: Resolved race conditions in state management
- :material-ruler: **Preflight Context Check**: Validates context fit before sending
- :material-format-align-justify: **Structure-Aware Trimming**: Preserves document structure
- :material-content-cut: **Native Tool Output Trimming**: Trims verbose tool outputs (Note: Non-native tool outputs are not fully injected into context)
- :material-chart-bar: **Detailed Token Logging**: Granular token breakdown
- :material-account-search: **Smart Model Matching**: Inherit config from base models
- :material-image-off: **Multimodal Support**: Images are preserved but tokens are **NOT** calculated
---
@@ -64,10 +70,14 @@ graph TD
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `token_threshold` | integer | `4000` | Trigger compression above this token count |
| `preserve_recent` | integer | `5` | Number of recent messages to keep uncompressed |
| `summary_model` | string | `"auto"` | Model to use for summarization |
| `compression_ratio` | float | `0.3` | Target compression ratio |
| `compression_threshold_tokens` | integer | `64000` | Trigger compression above this token count |
| `max_context_tokens` | integer | `128000` | Hard limit for context |
| `keep_first` | integer | `1` | Always keep the first N messages |
| `keep_last` | integer | `6` | Always keep the last N messages |
| `summary_model` | string | `None` | Model to use for summarization |
| `summary_model_max_context` | integer | `0` | Max context tokens for summary model |
| `max_summary_tokens` | integer | `16384` | Maximum tokens for the summary |
| `enable_tool_output_trimming` | boolean | `false` | Enable trimming of large tool outputs |
---

View File

@@ -1,7 +1,7 @@
# Async Context Compression异步上下文压缩
<span class="category-badge filter">Filter</span>
<span class="version-badge">v1.1.3</span>
<span class="version-badge">v1.2.2</span>
通过智能摘要减少长对话的 token 消耗,同时保持对话连贯。
@@ -34,6 +34,12 @@ Async Context Compression 过滤器通过以下方式帮助管理长对话的 to
- :material-check-all: **Open WebUI v0.7.x 兼容性**:动态数据库会话处理
- :material-account-convert: **兼容性提升**:摘要角色改为 `assistant`
- :material-shield-check: **稳定性增强**:解决状态管理竞态条件
- :material-ruler: **预检上下文检查**:发送前验证上下文是否超限
- :material-format-align-justify: **结构感知裁剪**:保留文档结构的智能裁剪
- :material-content-cut: **原生工具输出裁剪**:自动裁剪冗长的工具输出(注意:非原生工具调用输出不会完整注入上下文)
- :material-chart-bar: **详细 Token 日志**:提供细粒度的 Token 统计
- :material-account-search: **智能模型匹配**:自定义模型自动继承基础模型配置
- :material-image-off: **多模态支持**:图片内容保留但 Token **不参与计算**
---
@@ -64,10 +70,14 @@ graph TD
| 选项 | 类型 | 默认值 | 说明 |
|--------|------|---------|-------------|
| `token_threshold` | integer | `4000` | 超过该 token 数触发压缩 |
| `preserve_recent` | integer | `5` | 保留不压缩的最近消息数量 |
| `summary_model` | string | `"auto"` | 用于摘要的模型 |
| `compression_ratio` | float | `0.3` | 目标压缩比例 |
| `compression_threshold_tokens` | integer | `64000` | 超过该 token 数触发压缩 |
| `max_context_tokens` | integer | `128000` | 上下文硬性上限 |
| `keep_first` | integer | `1` | 始终保留的前 N 条消息 |
| `keep_last` | integer | `6` | 始终保留的后 N 条消息 |
| `summary_model` | string | `None` | 用于摘要的模型 |
| `summary_model_max_context` | integer | `0` | 摘要模型的最大上下文 Token 数 |
| `max_summary_tokens` | integer | `16384` | 摘要的最大 token 数 |
| `enable_tool_output_trimming` | boolean | `false` | 启用长工具输出裁剪 |
---

View File

@@ -0,0 +1,57 @@
# Folder Memory
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.1.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
---
### 📌 What's new in 0.1.0
- **Initial Release**: Automated "Project Rules" management for OpenWebUI folders.
- **Folder-Level Persistence**: Automatically updates folder system prompts with extracted rules.
- **Optimized Performance**: Runs asynchronously and supports `PRIORITY` configuration for seamless integration with other filters.
---
**Folder Memory** is an intelligent context filter plugin for OpenWebUI. It automatically extracts consistent "Project Rules" from ongoing conversations within a folder and injects them back into the folder's system prompt.
This ensures that all future conversations within that folder share the same evolved context and rules, without manual updates.
## Features
- **Automatic Extraction**: Analyzes chat history every N messages to extract project rules.
- **Non-destructive Injection**: Updates only the specific "Project Rules" block in the system prompt, preserving other instructions.
- **Async Processing**: Runs in the background without blocking the user's chat experience.
- **ORM Integration**: Directly updates folder data using OpenWebUI's internal models for reliability.
## Prerequisites
- **Conversations must occur inside a folder.** This plugin only triggers when a chat belongs to a folder (i.e., you need to create a folder in OpenWebUI and start a conversation within it).
## Installation
1. Copy `folder_memory.py` to your OpenWebUI `plugins/filters/` directory (or upload via Admin UI).
2. Enable the filter in your **Settings** -> **Filters**.
3. (Optional) Configure the triggering threshold (default: every 10 messages).
## Configuration (Valves)
| Valve | Default | Description |
| :--- | :--- | :--- |
| `PRIORITY` | `20` | Priority level for the filter operations. |
| `MESSAGE_TRIGGER_COUNT` | `10` | The number of messages required to trigger a rule analysis. |
| `MODEL_ID` | `""` | The model used to generate rules. If empty, uses the current chat model. |
| `RULES_BLOCK_TITLE` | `## 📂 Project Rules` | The title displayed above the injected rules block. |
| `SHOW_DEBUG_LOG` | `False` | Show detailed debug logs in the browser console. |
| `UPDATE_ROOT_FOLDER` | `False` | If enabled, finds and updates the root folder rules instead of the current subfolder. |
## How It Works
![Folder Memory Demo](https://raw.githubusercontent.com/Fu-Jie/awesome-openwebui/main/plugins/filters/folder-memory/folder-memory-demo.png)
1. **Trigger**: When a conversation reaches `MESSAGE_TRIGGER_COUNT` (e.g., 10, 20 messages).
2. **Analysis**: The plugin sends the recent conversation + existing rules to the LLM.
3. **Synthesis**: The LLM merges new insights with old rules, removing obsolete ones.
4. **Update**: The new rule set replaces the `<!-- OWUI_PROJECT_RULES_START -->` block in the folder's system prompt.
## Roadmap
See [ROADMAP](https://github.com/Fu-Jie/awesome-openwebui/blob/main/plugins/filters/folder-memory/ROADMAP.md) for future plans, including "Project Knowledge" collection.

View File

@@ -0,0 +1,57 @@
# 文件夹记忆 (Folder Memory)
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.1.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
---
### 📌 0.1.0 版本特性
- **首个版本发布**:专注于自动化的“项目规则”管理。
- **文件夹级持久化**:自动将提取的规则回写到文件夹系统提示词中。
- **性能优化**:采用异步处理机制,并支持 `PRIORITY` 配置,确保与其他过滤器(如上下文压缩)完美协作。
---
**文件夹记忆 (Folder Memory)** 是一个 OpenWebUI 的智能上下文过滤器插件。它能自动从文件夹内的对话中提取一致性的“项目规则”,并将其回写到文件夹的系统提示词中。
这确保了该文件夹内的所有未来对话都能共享相同的进化上下文和规则,无需手动更新。
## 功能特性
- **自动提取**:每隔 N 条消息分析一次聊天记录,提取项目规则。
- **无损注入**:仅更新系统提示词中的特定“项目规则”块,保留其他指令。
- **异步处理**:在后台运行,不阻塞用户的聊天体验。
- **ORM 集成**:直接使用 OpenWebUI 的内部模型更新文件夹数据,确保可靠性。
## 前置条件
- **对话必须在文件夹内进行。** 此插件仅在聊天属于某个文件夹时触发(即您需要先在 OpenWebUI 中创建一个文件夹,并在其内部开始对话)。
## 安装指南
1.`folder_memory.py` (或中文版 `folder_memory_cn.py`) 复制到 OpenWebUI 的 `plugins/filters/` 目录(或通过管理员 UI 上传)。
2.**设置** -> **过滤器** 中启用该插件。
3. (可选)配置触发阈值(默认:每 10 条消息)。
## 配置 (Valves)
| 参数 | 默认值 | 说明 |
| :--- | :--- | :--- |
| `PRIORITY` | `20` | 过滤器操作的优先级。 |
| `MESSAGE_TRIGGER_COUNT` | `10` | 触发规则分析的消息数量阈值。 |
| `MODEL_ID` | `""` | 用于生成规则的模型 ID。若为空则使用当前对话模型。 |
| `RULES_BLOCK_TITLE` | `## 📂 项目规则` | 显示在注入规则块上方的标题。 |
| `SHOW_DEBUG_LOG` | `False` | 在浏览器控制台显示详细调试日志。 |
| `UPDATE_ROOT_FOLDER` | `False` | 如果启用,将向上查找并更新根文件夹的规则,而不是当前子文件夹。 |
## 工作原理
![Folder Memory Demo](https://raw.githubusercontent.com/Fu-Jie/awesome-openwebui/main/plugins/filters/folder-memory/folder-memory-demo.png)
1. **触发**:当对话达到 `MESSAGE_TRIGGER_COUNT`(例如 10、20 条消息)时。
2. **分析**:插件将最近的对话 + 现有规则发送给 LLM。
3. **综合**LLM 将新见解与旧规则合并,移除过时的规则。
4. **更新**:新的规则集替换文件夹系统提示词中的 `<!-- OWUI_PROJECT_RULES_START -->` 块。
## 路线图
查看 [ROADMAP](https://github.com/Fu-Jie/awesome-openwebui/blob/main/plugins/filters/folder-memory/ROADMAP.md) 了解未来计划,包括“项目知识”收集功能。

View File

@@ -22,7 +22,7 @@ Filters act as middleware in the message pipeline:
Reduces token consumption in long conversations through intelligent summarization while maintaining coherence.
**Version:** 1.1.3
**Version:** 1.2.2
[:octicons-arrow-right-24: Documentation](async-context-compression.md)
@@ -36,7 +36,15 @@ Filters act as middleware in the message pipeline:
[:octicons-arrow-right-24: Documentation](context-enhancement.md)
- :material-folder-refresh:{ .lg .middle } **Folder Memory**
---
Automatically extracts consistent "Project Rules" from ongoing conversations within a folder and injects them back into the folder's system prompt.
**Version:** 0.1.0
[:octicons-arrow-right-24: Documentation](folder-memory.md)
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
@@ -44,7 +52,7 @@ Filters act as middleware in the message pipeline:
Fixes common Markdown formatting issues in LLM outputs, including Mermaid syntax, code blocks, and LaTeX formulas.
**Version:** 1.2.3
**Version:** 1.2.4
[:octicons-arrow-right-24: Documentation](markdown_normalizer.md)

View File

@@ -22,7 +22,7 @@ Filter 充当消息管线中的中间件:
通过智能总结减少长对话的 token 消耗,同时保持连贯性。
**版本:** 1.1.3
**版本:** 1.2.2
[:octicons-arrow-right-24: 查看文档](async-context-compression.md)
@@ -36,7 +36,15 @@ Filter 充当消息管线中的中间件:
[:octicons-arrow-right-24: 查看文档](context-enhancement.md)
- :material-folder-refresh:{ .lg .middle } **Folder Memory**
---
自动从文件夹内的对话中提取一致性的“项目规则”,并将其回写到文件夹的系统提示词中。
**版本:** 0.1.0
[:octicons-arrow-right-24: 查看文档](folder-memory.zh.md)
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
@@ -44,7 +52,7 @@ Filter 充当消息管线中的中间件:
修复 LLM 输出中常见的 Markdown 格式问题,包括 Mermaid 语法、代码块和 LaTeX 公式。
**版本:** 1.2.3
**版本:** 1.2.4
[:octicons-arrow-right-24: 查看文档](markdown_normalizer.zh.md)

View File

@@ -51,6 +51,10 @@ A content normalizer filter for Open WebUI that fixes common Markdown formatting
## Changelog
### v1.2.4
* **Documentation Updates**: Synchronized version numbers across all documentation and code files.
### v1.2.3
* **List Marker Protection Enhancement**: Fixed a bug where list markers (`*`) followed by plain text and emphasis were having their spaces incorrectly stripped (e.g., `* U16 forward` became `*U16 forward`).

View File

@@ -51,6 +51,10 @@
## 更新日志
### v1.2.4
* **文档更新**: 同步了所有文档和代码文件的版本号。
### v1.2.3
* **列表标记保护增强**: 修复了列表标记 (`*`) 后跟普通文本和强调标记时,空格被错误剥离的问题(例如 `* U16 前锋` 变成 `*U16 前锋`)。

View File

@@ -0,0 +1,120 @@
# GitHub Copilot SDK Pipe for OpenWebUI
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.2.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/open-webui) that allows you to use GitHub Copilot models (such as `gpt-5`, `gpt-5-mini`, `claude-sonnet-4.5`) directly within OpenWebUI. It is built upon the official [GitHub Copilot SDK for Python](https://github.com/github/copilot-sdk), providing a native integration experience.
## 🚀 What's New (v0.2.3)
* **🧩 Per-user Overrides**: Added user-level overrides for `REASONING_EFFORT`, `CLI_PATH`, `DEBUG`, `SHOW_THINKING`, and `MODEL_ID`.
* **🧠 Thinking Output Reliability**: Thinking visibility now respects the user setting and is correctly passed into streaming.
* **📝 Formatting Enforcement**: Added automatic formatting hints to ensure outputs are well-structured (paragraphs, lists).
## ✨ Core Features
* **🚀 Official SDK Integration**: Built on the official SDK for stability and reliability.
* **🛠️ Custom Tools Support**: Example tools included (random number). Easy to extend with your own tools.
* **💬 Multi-turn Conversation**: Automatically concatenates history context so Copilot understands your previous messages.
* **🌊 Streaming Output**: Supports typewriter effect for fast responses.
* **🖼️ Multimodal Support**: Supports image uploads, automatically converting them to attachments for Copilot (requires model support).
* **🛠️ Zero-config Installation**: Automatically detects and downloads the GitHub Copilot CLI, ready to use out of the box.
* **🔑 Secure Authentication**: Supports Fine-grained Personal Access Tokens for minimized permissions.
* **🐛 Debug Mode**: Built-in detailed log output (browser console) for easy troubleshooting.
* **⚠️ Single Node Only**: Due to local session storage, this plugin currently supports single-node OpenWebUI deployment or multi-node with sticky sessions enabled.
## 📦 Installation & Usage
### 1. Import Function
1. Open OpenWebUI.
2. Go to **Workspace** -> **Functions**.
3. Click **+** (Create Function).
4. Paste the content of `github_copilot_sdk.py` (or `github_copilot_sdk_cn.py` for Chinese) completely.
5. Save.
### 2. Configure Valves (Settings)
Find "GitHub Copilot" in the function list and click the **⚙️ (Valves)** icon to configure:
| Parameter | Description | Default |
| :--- | :--- | :--- |
| **GH_TOKEN** | **(Required)** Your GitHub Token. | - |
| **MODEL_ID** | The model name to use. Recommended `gpt-5-mini` or `gpt-5`. | `gpt-5-mini` |
| **CLI_PATH** | Path to the Copilot CLI. Will download automatically if not found. | `/usr/local/bin/copilot` |
| **DEBUG** | Whether to enable debug logs (output to browser console). | `False` |
| **LOG_LEVEL** | Copilot CLI log level: none, error, warning, info, debug, all. | `error` |
| **SHOW_THINKING** | Show model reasoning/thinking process (requires streaming + model support). | `True` |
| **SHOW_WORKSPACE_INFO** | Show session workspace path and summary in debug mode. | `True` |
| **EXCLUDE_KEYWORDS** | Exclude models containing these keywords (comma separated). | - |
| **WORKSPACE_DIR** | Restricted workspace directory for file operations. | - |
| **INFINITE_SESSION** | Enable Infinite Sessions (automatic context compaction). | `True` |
| **COMPACTION_THRESHOLD** | Background compaction threshold (0.0-1.0). | `0.8` |
| **BUFFER_THRESHOLD** | Buffer exhaustion threshold (0.0-1.0). | `0.95` |
| **TIMEOUT** | Timeout for each stream chunk (seconds). | `300` |
| **CUSTOM_ENV_VARS** | Custom environment variables (JSON format). | - |
| **REASONING_EFFORT** | Reasoning effort level: low, medium, high. `xhigh` is supported for gpt-5.2-codex. | `medium` |
| **ENFORCE_FORMATTING** | Add formatting instructions to system prompt for better readability. | `True` |
| **ENABLE_TOOLS** | Enable custom tools (example: random number). | `False` |
| **AVAILABLE_TOOLS** | Available tools: 'all' or comma-separated list. | `all` |
#### User Valves (per-user overrides)
These optional settings can be set per user (overrides global Valves):
| Parameter | Description | Default |
| :--- | :--- | :--- |
| **REASONING_EFFORT** | Reasoning effort level (low/medium/high/xhigh). | - |
| **CLI_PATH** | Custom path to Copilot CLI. | - |
| **DEBUG** | Enable technical debug logs. | `False` |
| **SHOW_THINKING** | Show model reasoning/thinking process (requires streaming + model support). | `True` |
| **MODEL_ID** | Custom model ID. | - |
### 3. Using Custom Tools (🆕 Optional)
This pipe includes **1 example tool** to demonstrate tool calling:
* **🎲 generate_random_number**: Generate random integers
**To enable:**
1. Set `ENABLE_TOOLS: true` in Valves
2. Try: "Give me a random number"
**📚 For detailed usage and creating your own tools, see [plugins/pipes/github-copilot-sdk/TOOLS_USAGE.md](plugins/pipes/github-copilot-sdk/TOOLS_USAGE.md)**
### 4. Get GH_TOKEN
For security, it is recommended to use a **Fine-grained Personal Access Token**:
1. Visit [GitHub Token Settings](https://github.com/settings/tokens?type=beta).
2. Click **Generate new token**.
3. **Repository access**: Select **Public repositories** (Required to access Copilot permissions).
4. **Permissions**:
* Click **Account permissions**.
* Find **Copilot Requests** (It defaults to **Read-only**, no selection needed).
5. Generate and copy the Token.
## 📋 Dependencies
This Pipe will automatically attempt to install the following dependencies:
* `github-copilot-sdk` (Python package)
* `github-copilot-cli` (Binary file, installed via official script)
## ⚠️ FAQ
* **Stuck on "Waiting..."**:
* Check if `GH_TOKEN` is correct and has `Copilot Requests` permission.
* **Images not recognized**:
* Ensure `MODEL_ID` is a model that supports multimodal input.
* **Thinking not shown**:
* Ensure **streaming is enabled** and the selected model supports reasoning output.
* **CLI Installation Failed**:
* Ensure the OpenWebUI container has internet access.
* You can manually download the CLI and specify `CLI_PATH` in Valves.
## 📄 License
MIT

View File

@@ -0,0 +1,120 @@
# GitHub Copilot SDK 官方管道
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.2.3 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
这是一个用于 [OpenWebUI](https://github.com/open-webui/open-webui) 的高级 Pipe 函数,允许你直接在 OpenWebUI 中使用 GitHub Copilot 模型(如 `gpt-5`, `gpt-5-mini`, `claude-sonnet-4.5`)。它基于官方 [GitHub Copilot SDK for Python](https://github.com/github/copilot-sdk) 构建,提供了原生级的集成体验。
## 🚀 最新特性 (v0.2.3)
* **🧩 用户级覆盖**:新增 `REASONING_EFFORT``CLI_PATH``DEBUG``SHOW_THINKING``MODEL_ID` 的用户级覆盖。
* **🧠 思考输出可靠性**:思考显示会遵循用户设置,并正确传递到流式输出中。
* **📝 格式化输出增强**:自动优化输出格式(段落、列表),并解决了在某些界面下显示过于紧凑的问题。
## ✨ 核心特性
* **🚀 官方 SDK 集成**:基于官方 SDK稳定可靠。
* **🛠️ 自定义工具支持**:内置示例工具(随机数)。易于扩展自定义工具。
* **💬 多轮对话支持**自动拼接历史上下文Copilot 能理解你的前文。
* **🌊 流式输出 (Streaming)**:支持打字机效果,响应迅速。
* **🖼️ 多模态支持**:支持上传图片,自动转换为附件发送给 Copilot需模型支持
* **🛠️ 零配置安装**:自动检测并下载 GitHub Copilot CLI开箱即用。
* **🔑 安全认证**:支持 Fine-grained Personal Access Tokens权限最小化。
* **🐛 调试模式**:内置详细的日志输出(浏览器控制台),方便排查问题。
* **⚠️ 仅支持单节点**:由于会话状态存储在本地,本插件目前仅支持 OpenWebUI 单节点部署,或开启了会话粘性 (Sticky Session) 的多节点集群。
## 📦 安装与使用
### 1. 导入函数
1. 打开 OpenWebUI。
2. 进入 **Workspace** -> **Functions**
3. 点击 **+** (创建函数)。
4.`github_copilot_sdk_cn.py` 的内容完整粘贴进去。
5. 保存。
### 2. 配置 Valves (设置)
在函数列表中找到 "GitHub Copilot",点击 **⚙️ (Valves)** 图标进行配置:
| 参数 | 说明 | 默认值 |
| :--- | :--- | :--- |
| **GH_TOKEN** | **(必填)** 你的 GitHub Token。 | - |
| **MODEL_ID** | 使用的模型名称。推荐 `gpt-5-mini``gpt-5`。 | `gpt-5-mini` |
| **CLI_PATH** | Copilot CLI 的路径。如果未找到会自动下载。 | `/usr/local/bin/copilot` |
| **DEBUG** | 是否开启调试日志(输出到浏览器控制台)。 | `False` |
| **LOG_LEVEL** | Copilot CLI 日志级别: none, error, warning, info, debug, all。 | `error` |
| **SHOW_THINKING** | 是否显示模型推理/思考过程(需开启流式 + 模型支持)。 | `True` |
| **SHOW_WORKSPACE_INFO** | 在调试模式下显示会话工作空间路径和摘要。 | `True` |
| **EXCLUDE_KEYWORDS** | 排除包含这些关键词的模型 (逗号分隔)。 | - |
| **WORKSPACE_DIR** | 文件操作的受限工作目录。 | - |
| **INFINITE_SESSION** | 启用无限会话 (自动上下文压缩)。 | `True` |
| **COMPACTION_THRESHOLD** | 后台压缩阈值 (0.0-1.0)。 | `0.8` |
| **BUFFER_THRESHOLD** | 缓冲耗尽阈值 (0.0-1.0)。 | `0.95` |
| **TIMEOUT** | 流式数据块超时时间 (秒)。 | `300` |
| **CUSTOM_ENV_VARS** | 自定义环境变量 (JSON 格式)。 | - |
| **ENABLE_TOOLS** | 启用自定义工具 (示例:随机数)。 | `False` |
| **AVAILABLE_TOOLS** | 可用工具: 'all' 或逗号分隔列表。 | `all` |
| **REASONING_EFFORT** | 推理强度级别low, medium, high。`gpt-5.2-codex`额外支持`xhigh`。 | `medium` |
| **ENFORCE_FORMATTING** | 是否强制添加格式化指导,以提高输出可读性。 | `True` |
#### 用户 Valves按用户覆盖
以下设置可按用户单独配置(覆盖全局 Valves
| 参数 | 说明 | 默认值 |
| :--- | :--- | :--- |
| **REASONING_EFFORT** | 推理强度级别low/medium/high/xhigh。 | - |
| **CLI_PATH** | 自定义 Copilot CLI 路径。 | - |
| **DEBUG** | 是否启用技术调试日志。 | `False` |
| **SHOW_THINKING** | 是否显示思考过程(需开启流式 + 模型支持)。 | `True` |
| **MODEL_ID** | 自定义模型 ID。 | - |
### 3. 使用自定义工具 (🆕 可选)
本 Pipe 内置了 **1 个示例工具**来展示工具调用功能:
* **🎲 generate_random_number**:生成随机整数
**启用方法:**
1. 在 Valves 中设置 `ENABLE_TOOLS: true`
2. 尝试问:“给我一个随机数”
**📚 详细使用说明和创建自定义工具,请参阅 [plugins/pipes/github-copilot-sdk/TOOLS_USAGE.md](plugins/pipes/github-copilot-sdk/TOOLS_USAGE.md)**
### 4. 获取 GH_TOKEN
为了安全起见,推荐使用 **Fine-grained Personal Access Token**
1. 访问 [GitHub Token Settings](https://github.com/settings/tokens?type=beta)。
2. 点击 **Generate new token**
3. **Repository access**: 选择 **Public repositories** (必须选择此项才能看到 Copilot 权限)。
4. **Permissions**:
* 点击 **Account permissions**
* 找到 **Copilot Requests** (默认即为 **Read-only**,无需手动修改)。
5. 生成并复制 Token。
## 📋 依赖说明
该 Pipe 会自动尝试安装以下依赖(如果环境中缺失):
* `github-copilot-sdk` (Python 包)
* `github-copilot-cli` (二进制文件,通过官方脚本安装)
## ⚠️ 常见问题
* **一直显示 "Waiting..."**
* 检查 `GH_TOKEN` 是否正确且拥有 `Copilot Requests` 权限。
* **图片无法识别**
* 确保 `MODEL_ID` 是支持多模态的模型。
* **看不到思考过程**
* 确认已开启**流式输出**,且所选模型支持推理输出。
* **CLI 安装失败**
* 确保 OpenWebUI 容器有外网访问权限。
* 你可以手动下载 CLI 并挂载到容器中,然后在 Valves 中指定 `CLI_PATH`
## 📄 许可证
MIT

View File

@@ -15,7 +15,7 @@ Pipes allow you to:
## Available Pipe Plugins
- [GitHub Copilot SDK](github-copilot-sdk.md) (v0.1.1) - Official GitHub Copilot SDK integration. Supports dynamic models, multi-turn conversation, streaming, multimodal input, and infinite sessions.
---

View File

@@ -15,7 +15,7 @@ Pipes 可以用于:
## 可用的 Pipe 插件
- [GitHub Copilot SDK](github-copilot-sdk.zh.md) (v0.1.1) - GitHub Copilot SDK 官方集成。支持动态模型、多轮对话、流式输出、图片输入及无限会话。
---

View File

@@ -1,10 +1,16 @@
# 📊 Smart Infographic (AntV)
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.4.9 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.5.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
An Open WebUI plugin powered by the AntV Infographic engine. It transforms long text into professional, beautiful infographics with a single click.
## 🔥 What's New in v1.4.9
## 🔥 What's New in v1.5.0
- 🌐 **Smart Language Detection**: Automatically detects the accurate UI language from your browser.
- 🗣️ **Context-Aware Generation**: Generated infographics now strictly follow the language of your input content (e.g., input Japanese -> output Japanese infographic).
- 🐛 **Bug Fixes**: Fixed issues with language synchronization between the UI and generated content.
### Previous: v1.4.9
- 🎨 **70+ Official Templates**: Integrated comprehensive AntV infographic template library.
- 🖼️ **Iconify & unDraw Support**: Richer visuals with official icons and illustrations.
@@ -63,7 +69,6 @@ You can adjust the following parameters in the plugin settings to optimize the g
- **Error Messages**: If you see an error, please copy the full error message and report it.
- **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
## 📝 Syntax Example (For Advanced Users)
You can also input this syntax directly for AI to render:

View File

@@ -1,10 +1,16 @@
# 📊 智能信息图 (AntV Infographic)
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.4.9 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.5.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
基于 AntV Infographic 引擎的 Open WebUI 插件,能够将长文本内容一键转换为专业、美观的信息图表。
## 🔥 v1.4.9 更新日志
## 🔥 v1.5.0 更新日志
- 🌐 **智能语言检测**:自动从浏览器准确识别当前界面语言设置。
- 🗣️ **上下文感知生成**:生成的信息图内容现在严格跟随用户输入内容的语言(例如:输入日语 -> 生成日语信息图)。
- 🐛 **问题修复**:修复了界面语言与生成内容语言不同步的问题。
### 此前: v1.4.9
- 🎨 **70+ 官方模板**:全面集成 AntV 官方信息图模板库。
- 🖼️ **图标与插图支持**:支持 Iconify 图标库与 unDraw 插图库,视觉效果更丰富。
@@ -63,7 +69,6 @@
- **错误信息**: 如果看到错误,请复制完整的错误信息并报告。
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
## 📝 语法示例 (高级用户)
你也可以直接输入以下语法让 AI 渲染:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 234 KiB

View File

@@ -4,7 +4,7 @@ author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
version: 1.4.9
version: 1.5.0
openwebui_id: ad6f0c7f-c571-4dea-821d-8e71697274cf
description: AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.
"""
@@ -32,6 +32,10 @@ logger = logging.getLogger(__name__)
SYSTEM_PROMPT_INFOGRAPHIC_ASSISTANT = """
You are a professional infographic design expert who can analyze user-provided text content and convert it into AntV Infographic syntax format.
## Important Language Rule
- **GENERATE CONTENT IN INPUT LANGUAGE**: You must generate the text content of the infographic in the **exact same language** as the user's input content (the text you are analyzing).
- **Format Consistency**: Even if this system prompt is in English, if the user input is in Chinese, the infographic content must be in Chinese. If input is Japanese, output Japanese.
## Infographic Syntax Specification
Infographic syntax is a Mermaid-like declarative syntax for describing infographic templates, data, and themes.
@@ -958,7 +962,11 @@ class Action:
def __init__(self):
self.valves = self.Valves()
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
async def _get_user_context(
self,
__user__: Optional[Dict[str, Any]],
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
) -> Dict[str, str]:
"""Safely extracts user context information."""
if isinstance(__user__, (list, tuple)):
user_data = __user__[0] if __user__ else {}
@@ -967,10 +975,32 @@ class Action:
else:
user_data = {}
user_id = user_data.get("id", "unknown_user")
user_name = user_data.get("name", "User")
user_language = user_data.get("language", "en-US")
if __event_call__:
try:
js_code = """
return (
localStorage.getItem('locale') ||
localStorage.getItem('language') ||
navigator.language ||
'en-US'
);
"""
frontend_lang = await __event_call__(
{"type": "execute", "data": {"code": js_code}}
)
if frontend_lang and isinstance(frontend_lang, str):
user_language = frontend_lang
except Exception as e:
logger.warning(f"Failed to retrieve frontend language: {e}")
return {
"user_id": user_data.get("id", "unknown_user"),
"user_name": user_data.get("name", "User"),
"user_language": user_data.get("language", "en-US"),
"user_id": user_id,
"user_name": user_name,
"user_language": user_language,
}
def _get_chat_context(
@@ -1469,18 +1499,10 @@ class Action:
logger.info("Action: Infographic started (v1.4.0)")
# Get user information
if isinstance(__user__, (list, tuple)):
user_language = __user__[0].get("language", "en") if __user__ else "en"
user_name = __user__[0].get("name", "User") if __user__[0] else "User"
user_id = (
__user__[0]["id"]
if __user__ and "id" in __user__[0]
else "unknown_user"
)
elif isinstance(__user__, dict):
user_language = __user__.get("language", "en")
user_name = __user__.get("name", "User")
user_id = __user__.get("id", "unknown_user")
user_ctx = await self._get_user_context(__user__, __event_call__)
user_name = user_ctx["user_name"]
user_id = user_ctx["user_id"]
user_language = user_ctx["user_language"]
# Get current time
now = datetime.now()

View File

@@ -4,7 +4,7 @@ author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
version: 1.4.9
version: 1.5.0
openwebui_id: e04a48ff-23ee-4a41-8ea7-66c19524e7c8
description: 基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。
"""
@@ -32,6 +32,10 @@ logger = logging.getLogger(__name__)
SYSTEM_PROMPT_INFOGRAPHIC_ASSISTANT = """
You are a professional infographic design expert who can analyze user-provided text content and convert it into AntV Infographic syntax format.
## Important Language Rule (语言规则)
- **Priority Input Language (优先使用输入语言)**: You must generate the text content of the infographic in the **exact same language** as the user's input content.
- **Example**: If the user provides a summary in Chinese, the labels and descriptions in the infographic must be in Chinese.
## Infographic Syntax Specification
Infographic syntax is a Mermaid-like declarative syntax for describing infographic templates, data, and themes.
@@ -974,7 +978,11 @@ class Action:
"Sunday": "星期日",
}
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
async def _get_user_context(
self,
__user__: Optional[Dict[str, Any]],
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
) -> Dict[str, str]:
"""安全提取用户上下文信息。"""
if isinstance(__user__, (list, tuple)):
user_data = __user__[0] if __user__ else {}
@@ -983,10 +991,32 @@ class Action:
else:
user_data = {}
user_id = user_data.get("id", "unknown_user")
user_name = user_data.get("name", "用户")
user_language = user_data.get("language", "zh-CN")
if __event_call__:
try:
js_code = """
return (
localStorage.getItem('locale') ||
localStorage.getItem('language') ||
navigator.language ||
'zh-CN'
);
"""
frontend_lang = await __event_call__(
{"type": "execute", "data": {"code": js_code}}
)
if frontend_lang and isinstance(frontend_lang, str):
user_language = frontend_lang
except Exception as e:
pass
return {
"user_id": user_data.get("id", "unknown_user"),
"user_name": user_data.get("name", "用户"),
"user_language": user_data.get("language", "zh-CN"),
"user_id": user_id,
"user_name": user_name,
"user_language": user_language,
}
def _get_chat_context(
@@ -1509,20 +1539,10 @@ class Action:
logger.info("Action: 信息图启动 (v1.4.0)")
# 获取用户信息
if isinstance(__user__, (list, tuple)):
user_language = (
__user__[0].get("language", "zh-CN") if __user__ else "zh-CN"
)
user_name = __user__[0].get("name", "用户") if __user__[0] else "用户"
user_id = (
__user__[0]["id"]
if __user__ and "id" in __user__[0]
else "unknown_user"
)
elif isinstance(__user__, dict):
user_language = __user__.get("language", "zh-CN")
user_name = __user__.get("name", "用户")
user_id = __user__.get("id", "unknown_user")
user_ctx = await self._get_user_context(__user__, __event_call__)
user_name = user_ctx["user_name"]
user_id = user_ctx["user_id"]
user_language = user_ctx["user_language"]
# 获取当前时间
now = datetime.now()

View File

@@ -0,0 +1,568 @@
# GitHub Copilot SDK 自定义工具快速入门
## 🎯 目标
在 OpenWebUI Pipe 中直接使用 GitHub Copilot SDK 的自定义工具功能,无需集成 OpenWebUI Function 系统。
---
## 📖 基础概念
### Copilot SDK Tool 的三要素
```python
from copilot.types import Tool, ToolInvocation, ToolResult
# 1. Tool Definition工具定义
tool = Tool(
name="tool_name", # 工具名称
description="What it does", # 描述(给 AI 看的)
parameters={...}, # JSON Schema 参数定义
handler=handler_function # 处理函数
)
# 2. Tool Handler处理函数
async def handler_function(invocation: ToolInvocation) -> ToolResult:
# invocation 包含:
# - session_id: 会话 ID
# - tool_call_id: 调用 ID
# - tool_name: 工具名称
# - arguments: dict实际参数
result = do_something(invocation["arguments"])
return ToolResult(
textResultForLlm="结果文本",
resultType="success", # 或 "failure"
error=None,
toolTelemetry={}
)
# 3. Session Configuration会话配置
session_config = SessionConfig(
model="claude-sonnet-4.5",
tools=[tool1, tool2, tool3], # ✅ 传入工具列表
streaming=True
)
```
---
## 💻 完整实现示例
### 示例 1获取当前时间
```python
from datetime import datetime
from copilot.types import Tool, ToolInvocation, ToolResult
def create_time_tool():
"""创建获取时间的工具"""
async def get_time_handler(invocation: ToolInvocation) -> ToolResult:
"""工具处理函数"""
try:
# 获取参数
timezone = invocation["arguments"].get("timezone", "UTC")
format_str = invocation["arguments"].get("format", "%Y-%m-%d %H:%M:%S")
# 执行逻辑
current_time = datetime.now().strftime(format_str)
result_text = f"Current time: {current_time}"
# 返回结果
return ToolResult(
textResultForLlm=result_text,
resultType="success",
error=None,
toolTelemetry={"execution_time": "fast"}
)
except Exception as e:
return ToolResult(
textResultForLlm=f"Error getting time: {str(e)}",
resultType="failure",
error=str(e),
toolTelemetry={}
)
# 创建工具定义
return Tool(
name="get_current_time",
description="Get the current date and time. Useful when user asks 'what time is it' or needs to know the current date.",
parameters={
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "Timezone name (e.g., 'UTC', 'Asia/Shanghai')",
"default": "UTC"
},
"format": {
"type": "string",
"description": "Time format string",
"default": "%Y-%m-%d %H:%M:%S"
}
}
},
handler=get_time_handler
)
```
### 示例 2数学计算器
```python
def create_calculator_tool():
"""创建计算器工具"""
async def calculate_handler(invocation: ToolInvocation) -> ToolResult:
try:
expression = invocation["arguments"].get("expression", "")
# 安全检查
allowed_chars = set("0123456789+-*/()., ")
if not all(c in allowed_chars for c in expression):
raise ValueError("Expression contains invalid characters")
# 计算(安全的 eval
result = eval(expression, {"__builtins__": {}})
return ToolResult(
textResultForLlm=f"The result of {expression} is {result}",
resultType="success",
error=None,
toolTelemetry={}
)
except Exception as e:
return ToolResult(
textResultForLlm=f"Calculation error: {str(e)}",
resultType="failure",
error=str(e),
toolTelemetry={}
)
return Tool(
name="calculate",
description="Perform mathematical calculations. Supports basic arithmetic operations (+, -, *, /).",
parameters={
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate (e.g., '2 + 2 * 3')"
}
},
"required": ["expression"]
},
handler=calculate_handler
)
```
### 示例 3随机数生成器
```python
import random
def create_random_number_tool():
"""创建随机数生成工具"""
async def random_handler(invocation: ToolInvocation) -> ToolResult:
try:
min_val = invocation["arguments"].get("min", 1)
max_val = invocation["arguments"].get("max", 100)
if min_val >= max_val:
raise ValueError("min must be less than max")
number = random.randint(min_val, max_val)
return ToolResult(
textResultForLlm=f"Generated random number: {number}",
resultType="success",
error=None,
toolTelemetry={}
)
except Exception as e:
return ToolResult(
textResultForLlm=f"Error: {str(e)}",
resultType="failure",
error=str(e),
toolTelemetry={}
)
return Tool(
name="generate_random_number",
description="Generate a random integer within a specified range.",
parameters={
"type": "object",
"properties": {
"min": {
"type": "integer",
"description": "Minimum value (inclusive)",
"default": 1
},
"max": {
"type": "integer",
"description": "Maximum value (inclusive)",
"default": 100
}
}
},
handler=random_handler
)
```
---
## 🔧 集成到 Pipe
### 完整的 Pipe 实现
```python
class Pipe:
class Valves(BaseModel):
# ... 现有 Valves ...
ENABLE_TOOLS: bool = Field(
default=False,
description="Enable custom tools (time, calculator, random)"
)
AVAILABLE_TOOLS: str = Field(
default="all",
description="Available tools: 'all' or comma-separated list (e.g., 'get_current_time,calculate')"
)
def __init__(self):
# ... 现有初始化 ...
self._custom_tools = []
def _initialize_custom_tools(self):
"""初始化自定义工具"""
if not self.valves.ENABLE_TOOLS:
return []
# 定义所有可用工具
all_tools = {
"get_current_time": create_time_tool(),
"calculate": create_calculator_tool(),
"generate_random_number": create_random_number_tool(),
}
# 根据配置过滤工具
if self.valves.AVAILABLE_TOOLS == "all":
return list(all_tools.values())
# 只启用指定的工具
enabled = [t.strip() for t in self.valves.AVAILABLE_TOOLS.split(",")]
return [all_tools[name] for name in enabled if name in all_tools]
async def pipe(
self,
body: dict,
__metadata__: Optional[dict] = None,
__event_emitter__=None,
__event_call__=None,
) -> Union[str, AsyncGenerator]:
# ... 现有代码 ...
# ✅ 初始化工具
custom_tools = self._initialize_custom_tools()
if custom_tools:
await self._emit_debug_log(
f"Enabled {len(custom_tools)} custom tools: {[t.name for t in custom_tools]}",
__event_call__
)
# ✅ 创建会话配置(传入工具)
from copilot.types import SessionConfig, InfiniteSessionConfig
session_config = SessionConfig(
session_id=chat_id if chat_id else None,
model=real_model_id,
streaming=body.get("stream", False),
tools=custom_tools, # ✅✅✅ 关键:传入工具列表
infinite_sessions=infinite_session_config if self.valves.INFINITE_SESSION else None,
)
session = await client.create_session(config=session_config)
# ... 其余代码保持不变 ...
```
---
## 📊 处理工具调用事件
### 在 stream_response 中显示工具调用
```python
async def stream_response(
self, client, session, send_payload, init_message: str = "", __event_call__=None
) -> AsyncGenerator:
# ... 现有代码 ...
def handler(event):
event_type = str(getattr(event.type, "value", event.type))
# ✅ 工具调用开始
if "tool_invocation_started" in event_type or "tool_call_started" in event_type:
tool_name = get_event_data(event, "tool_name", "")
if tool_name:
queue.put_nowait(f"\n\n🔧 **Calling tool**: `{tool_name}`\n")
# ✅ 工具调用完成
elif "tool_invocation_completed" in event_type or "tool_call_completed" in event_type:
tool_name = get_event_data(event, "tool_name", "")
result = get_event_data(event, "result", "")
if tool_name:
queue.put_nowait(f"\n✅ **Tool `{tool_name}` completed**\n")
# ✅ 工具调用失败
elif "tool_invocation_failed" in event_type or "tool_call_failed" in event_type:
tool_name = get_event_data(event, "tool_name", "")
error = get_event_data(event, "error", "")
if tool_name:
queue.put_nowait(f"\n❌ **Tool `{tool_name}` failed**: {error}\n")
# ... 其他事件处理 ...
# ... 其余代码 ...
```
---
## 🧪 测试示例
### 测试 1询问时间
```
User: "What time is it now?"
Expected Flow:
1. Copilot 识别需要调用 get_current_time 工具
2. 调用工具(无参数或默认参数)
3. 工具返回: "Current time: 2026-01-26 15:30:00"
4. Copilot 回答: "The current time is 2026-01-26 15:30:00"
Pipe Output:
---
🔧 **Calling tool**: `get_current_time`
✅ **Tool `get_current_time` completed**
The current time is 2026-01-26 15:30:00
---
```
### 测试 2数学计算
```
User: "Calculate 123 * 456"
Expected Flow:
1. Copilot 调用 calculate 工具
2. 参数: {"expression": "123 * 456"}
3. 工具返回: "The result of 123 * 456 is 56088"
4. Copilot 回答: "123 multiplied by 456 equals 56,088"
Pipe Output:
---
🔧 **Calling tool**: `calculate`
✅ **Tool `calculate` completed**
123 multiplied by 456 equals 56,088
---
```
### 测试 3生成随机数
```
User: "Give me a random number between 1 and 10"
Expected Flow:
1. Copilot 调用 generate_random_number 工具
2. 参数: {"min": 1, "max": 10}
3. 工具返回: "Generated random number: 7"
4. Copilot 回答: "I generated a random number for you: 7"
```
---
## 🔍 调试技巧
### 1. 记录所有工具事件
```python
def handler(event):
event_type = str(getattr(event.type, "value", event.type))
# 记录所有包含 "tool" 的事件
if "tool" in event_type.lower():
event_data = {}
if hasattr(event, "data"):
try:
event_data = {
"type": event_type,
"data": str(event.data)[:200] # 截断长数据
}
except:
pass
self._emit_debug_log_sync(
f"Tool Event: {json.dumps(event_data)}",
__event_call__
)
```
### 2. 验证工具注册
```python
async def pipe(...):
# ...
custom_tools = self._initialize_custom_tools()
# 调试:打印工具信息
if self.valves.DEBUG:
tool_info = [
{
"name": t.name,
"description": t.description[:50],
"has_handler": t.handler is not None
}
for t in custom_tools
]
await self._emit_debug_log(
f"Registered tools: {json.dumps(tool_info, indent=2)}",
__event_call__
)
```
### 3. 测试工具处理函数
```python
# 单独测试工具
async def test_tool():
tool = create_time_tool()
# 模拟调用
invocation = {
"session_id": "test",
"tool_call_id": "test_call",
"tool_name": "get_current_time",
"arguments": {"format": "%H:%M:%S"}
}
result = await tool.handler(invocation)
print(f"Result: {result}")
```
---
## ⚠️ 注意事项
### 1. 工具描述的重要性
工具的 `description` 字段非常重要,它告诉 AI 何时应该使用这个工具:
```python
# ❌ 差的描述
description="Get time"
# ✅ 好的描述
description="Get the current date and time. Use this when the user asks 'what time is it', 'what's the date', or needs to know the current timestamp."
```
### 2. 参数定义
使用标准的 JSON Schema 定义参数:
```python
parameters={
"type": "object",
"properties": {
"param_name": {
"type": "string", # string, integer, boolean, array, object
"description": "Clear description",
"enum": ["option1", "option2"], # 可选:枚举值
"default": "default_value" # 可选:默认值
}
},
"required": ["param_name"] # 必需参数
}
```
### 3. 错误处理
总是捕获异常并返回有意义的错误:
```python
try:
result = do_something()
return ToolResult(
textResultForLlm=f"Success: {result}",
resultType="success",
error=None,
toolTelemetry={}
)
except Exception as e:
return ToolResult(
textResultForLlm=f"Error occurred: {str(e)}",
resultType="failure",
error=str(e), # 用于调试
toolTelemetry={}
)
```
### 4. 异步 vs 同步
工具处理函数可以是同步或异步:
```python
# 同步工具
def sync_handler(invocation):
result = calculate(invocation["arguments"])
return ToolResult(...)
# 异步工具(推荐)
async def async_handler(invocation):
result = await fetch_data(invocation["arguments"])
return ToolResult(...)
```
---
## 🚀 快速开始清单
- [ ] 1. 在 Valves 中添加 `ENABLE_TOOLS` 配置
- [ ] 2. 定义 2-3 个简单的工具函数
- [ ] 3. 实现 `_initialize_custom_tools()` 方法
- [ ] 4. 修改 `SessionConfig` 传入 `tools` 参数
- [ ] 5. 在 `stream_response` 中添加工具事件处理
- [ ] 6. 测试:询问时间、计算数学、生成随机数
- [ ] 7. 添加调试日志
- [ ] 8. 同步中文版本
---
## 📚 完整的工具事件列表
根据 SDK 源码,可能的工具相关事件:
- `tool_invocation_started` / `tool_call_started`
- `tool_invocation_completed` / `tool_call_completed`
- `tool_invocation_failed` / `tool_call_failed`
- `tool_parameter_validation_failed`
实际事件名称可能因 SDK 版本而异,建议先记录所有事件类型:
```python
def handler(event):
print(f"Event type: {event.type}")
```
---
**快速实现入口:** 从示例 1获取时间开始这是最简单的工具可以快速验证整个流程
**作者:** Fu-Jie
**日期:** 2026-01-26

View File

@@ -0,0 +1,480 @@
# OpenWebUI Native Tool Call Display Implementation Guide
**Date:** 2026-01-27
**Purpose:** Analyze and implement OpenWebUI's native tool call display mechanism
---
## 📸 Current vs Native Display
### Current Implementation
```markdown
> 🔧 **Running Tool**: `search_chats`
> ✅ **Tool Completed**: {...}
```
### OpenWebUI Native Display (from screenshot)
- ✅ Collapsible panel: "查看来自 search_chats 的结果"
- ✅ Formatted JSON display
- ✅ Syntax highlighting
- ✅ Expand/collapse functionality
- ✅ Clean visual separation
---
## 🔍 Understanding OpenWebUI's Tool Call Format
### Standard OpenAI Tool Call Message Format
OpenWebUI follows the OpenAI Chat Completion API format for tool calls:
#### 1. Assistant Message with Tool Calls
```python
{
"role": "assistant",
"content": None, # or explanatory text
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "search_chats",
"arguments": '{"query": ""}'
}
}
]
}
```
#### 2. Tool Response Message
```python
{
"role": "tool",
"tool_call_id": "call_abc123",
"name": "search_chats", # Optional but recommended
"content": '{"count": 5, "results": [...]}' # JSON string
}
```
---
## 🎯 Implementation Strategy for Native Display
### Option 1: Event Emitter Approach (Recommended)
Use OpenWebUI's event emitter to send structured tool call data:
```python
async def stream_response(self, ...):
# When tool execution starts
if event_type == "tool.execution_start":
await self._emit_tool_call_start(
emitter=__event_call__,
tool_call_id=tool_call_id,
tool_name=tool_name,
arguments=arguments
)
# When tool execution completes
elif event_type == "tool.execution_complete":
await self._emit_tool_call_result(
emitter=__event_call__,
tool_call_id=tool_call_id,
tool_name=tool_name,
result=result_content
)
```
#### Helper Methods
```python
async def _emit_tool_call_start(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
tool_call_id: str,
tool_name: str,
arguments: dict
):
"""Emit a tool call start event to OpenWebUI."""
if not emitter:
return
try:
# OpenWebUI expects tool_calls in assistant message format
await emitter({
"type": "message",
"data": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": tool_call_id,
"type": "function",
"function": {
"name": tool_name,
"arguments": json.dumps(arguments, ensure_ascii=False)
}
}
]
}
})
except Exception as e:
logger.error(f"Failed to emit tool call start: {e}")
async def _emit_tool_call_result(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
tool_call_id: str,
tool_name: str,
result: Any
):
"""Emit a tool call result to OpenWebUI."""
if not emitter:
return
try:
# Format result as JSON string
if isinstance(result, str):
result_content = result
else:
result_content = json.dumps(result, ensure_ascii=False, indent=2)
# OpenWebUI expects tool results in tool message format
await emitter({
"type": "message",
"data": {
"role": "tool",
"tool_call_id": tool_call_id,
"name": tool_name,
"content": result_content
}
})
except Exception as e:
logger.error(f"Failed to emit tool result: {e}")
```
### Option 2: Message History Injection
Modify the conversation history to include tool calls:
```python
# After tool execution, append to messages
messages.append({
"role": "assistant",
"content": None,
"tool_calls": [{
"id": tool_call_id,
"type": "function",
"function": {
"name": tool_name,
"arguments": json.dumps(arguments)
}
}]
})
messages.append({
"role": "tool",
"tool_call_id": tool_call_id,
"name": tool_name,
"content": json.dumps(result)
})
```
---
## ⚠️ Challenges with Current Architecture
### 1. Streaming Context
Our current implementation uses:
- **Queue-based streaming**: Events → Queue → Yield chunks
- **Text chunks only**: We yield plain text, not structured messages
OpenWebUI's native display requires:
- **Structured message events**: Not text chunks
- **Message-level control**: Need to emit complete messages
### 2. Event Emitter Compatibility
**Current usage:**
```python
# We use event_emitter for status/notifications
await event_emitter({
"type": "status",
"data": {"description": "Processing..."}
})
```
**Need for tool calls:**
```python
# Need to emit message-type events
await event_emitter({
"type": "message",
"data": {
"role": "tool",
"content": "..."
}
})
```
**Question:** Does `__event_emitter__` support `message` type events?
### 3. Session SDK Events vs OpenWebUI Messages
**Copilot SDK events:**
- `tool.execution_start` → We get tool name, arguments
- `tool.execution_complete` → We get tool result
- Designed for streaming text output
**OpenWebUI messages:**
- Expect structured message objects
- Not designed for mid-stream injection
---
## 🧪 Experimental Implementation
### Step 1: Add Valve for Native Display
```python
class Valves(BaseModel):
USE_NATIVE_TOOL_DISPLAY: bool = Field(
default=False,
description="Use OpenWebUI's native tool call display instead of markdown formatting"
)
```
### Step 2: Modify Tool Event Handling
```python
async def stream_response(self, ...):
# ...existing code...
def handler(event):
event_type = get_event_type(event)
if event_type == "tool.execution_start":
tool_name = safe_get_data_attr(event, "name")
# Get tool arguments
tool_input = safe_get_data_attr(event, "input") or {}
tool_call_id = safe_get_data_attr(event, "tool_call_id", f"call_{time.time()}")
if tool_call_id:
active_tools[tool_call_id] = {
"name": tool_name,
"arguments": tool_input
}
if self.valves.USE_NATIVE_TOOL_DISPLAY:
# Emit structured tool call
asyncio.create_task(
self._emit_tool_call_start(
__event_call__,
tool_call_id,
tool_name,
tool_input
)
)
else:
# Current markdown display
queue.put_nowait(f"\n\n> 🔧 **Running Tool**: `{tool_name}`\n\n")
elif event_type == "tool.execution_complete":
tool_call_id = safe_get_data_attr(event, "tool_call_id")
tool_info = active_tools.get(tool_call_id, {})
tool_name = tool_info.get("name", "Unknown")
# Extract result
result_obj = safe_get_data_attr(event, "result")
result_content = ""
if hasattr(result_obj, "content"):
result_content = result_obj.content
elif isinstance(result_obj, dict):
result_content = result_obj.get("content", "")
if self.valves.USE_NATIVE_TOOL_DISPLAY:
# Emit structured tool result
asyncio.create_task(
self._emit_tool_call_result(
__event_call__,
tool_call_id,
tool_name,
result_content
)
)
else:
# Current markdown display
queue.put_nowait(f"> ✅ **Tool Completed**: {result_content}\n\n")
```
---
## 🔬 Testing Plan
### Test 1: Event Emitter Message Type Support
```python
# In a test conversation, try:
await __event_emitter__({
"type": "message",
"data": {
"role": "assistant",
"content": "Test message"
}
})
```
**Expected:** Message appears in chat
**If fails:** Event emitter doesn't support message type
### Test 2: Tool Call Message Format
```python
# Send a tool call message
await __event_emitter__({
"type": "message",
"data": {
"role": "assistant",
"content": None,
"tool_calls": [{
"id": "test_123",
"type": "function",
"function": {
"name": "test_tool",
"arguments": '{"param": "value"}'
}
}]
}
})
# Send tool result
await __event_emitter__({
"type": "message",
"data": {
"role": "tool",
"tool_call_id": "test_123",
"name": "test_tool",
"content": '{"result": "success"}'
}
})
```
**Expected:** OpenWebUI displays collapsible tool panel
**If fails:** Event format doesn't match OpenWebUI expectations
### Test 3: Mid-Stream Tool Call Injection
Test if tool call messages can be injected during streaming:
```python
# Start streaming text
yield "Processing your request..."
# Mid-stream: emit tool call
await __event_emitter__({"type": "message", "data": {...}})
# Continue streaming
yield "Done!"
```
**Expected:** Tool panel appears mid-response
**Risk:** May break streaming flow
---
## 📋 Implementation Checklist
- [x] Add `REASONING_EFFORT` valve (completed)
- [ ] Add `USE_NATIVE_TOOL_DISPLAY` valve
- [ ] Implement `_emit_tool_call_start()` helper
- [ ] Implement `_emit_tool_call_result()` helper
- [ ] Modify tool event handling in `stream_response()`
- [ ] Test event emitter message type support
- [ ] Test tool call message format
- [ ] Test mid-stream injection
- [ ] Update documentation
- [ ] Add user configuration guide
---
## 🤔 Recommendation
### Hybrid Approach (Safest)
Keep both display modes:
1. **Default (Current):** Markdown-based display
- ✅ Works reliably with streaming
- ✅ No OpenWebUI API dependencies
- ✅ Consistent across versions
2. **Experimental (Native):** Structured tool messages
- ✅ Better visual integration
- ⚠️ Requires testing with OpenWebUI internals
- ⚠️ May not work in all scenarios
**Configuration:**
```python
USE_NATIVE_TOOL_DISPLAY: bool = Field(
default=False,
description="[EXPERIMENTAL] Use OpenWebUI's native tool call display"
)
```
### Why Markdown Display is Currently Better
1. **Reliability:** Always works with streaming
2. **Flexibility:** Can customize format easily
3. **Context:** Shows tools inline with reasoning
4. **Compatibility:** Works across OpenWebUI versions
### When to Use Native Display
- Non-streaming mode (easier to inject messages)
- After confirming event emitter supports message type
- For tools with large JSON results (better formatting)
---
## 📚 Next Steps
1. **Research OpenWebUI Source Code**
- Check `__event_emitter__` implementation
- Verify supported event types
- Test message injection patterns
2. **Create Proof of Concept**
- Simple test plugin
- Emit tool call messages
- Verify UI rendering
3. **Document Findings**
- Update this guide with test results
- Add code examples that work
- Create migration guide if successful
---
## 🔗 References
- [OpenAI Chat Completion API](https://platform.openai.com/docs/api-reference/chat/create)
- [OpenWebUI Plugin Development](https://docs.openwebui.com/)
- [Copilot SDK Events](https://github.com/github/copilot-sdk)
---
**Author:** Fu-Jie
**Status:** Analysis Complete - Implementation Pending Testing

View File

@@ -0,0 +1,480 @@
# OpenWebUI 原生工具调用展示实现指南
**日期:** 2026-01-27
**目的:** 分析并实现 OpenWebUI 的原生工具调用展示机制
---
## 📸 当前展示 vs 原生展示
### 当前实现
```markdown
> 🔧 **Running Tool**: `search_chats`
> ✅ **Tool Completed**: {...}
```
### OpenWebUI 原生展示(来自截图)
- ✅ 可折叠面板:"查看来自 search_chats 的结果"
- ✅ 格式化的 JSON 显示
- ✅ 语法高亮
- ✅ 展开/折叠功能
- ✅ 清晰的视觉分隔
---
## 🔍 理解 OpenWebUI 的工具调用格式
### 标准 OpenAI 工具调用消息格式
OpenWebUI 遵循 OpenAI Chat Completion API 的工具调用格式:
#### 1. 带工具调用的助手消息
```python
{
"role": "assistant",
"content": None, # 或解释性文本
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "search_chats",
"arguments": '{"query": ""}'
}
}
]
}
```
#### 2. 工具响应消息
```python
{
"role": "tool",
"tool_call_id": "call_abc123",
"name": "search_chats", # 可选但推荐
"content": '{"count": 5, "results": [...]}' # JSON 字符串
}
```
---
## 🎯 原生展示的实现策略
### 方案 1事件发射器方法推荐
使用 OpenWebUI 的事件发射器发送结构化工具调用数据:
```python
async def stream_response(self, ...):
# 工具执行开始时
if event_type == "tool.execution_start":
await self._emit_tool_call_start(
emitter=__event_call__,
tool_call_id=tool_call_id,
tool_name=tool_name,
arguments=arguments
)
# 工具执行完成时
elif event_type == "tool.execution_complete":
await self._emit_tool_call_result(
emitter=__event_call__,
tool_call_id=tool_call_id,
tool_name=tool_name,
result=result_content
)
```
#### 辅助方法
```python
async def _emit_tool_call_start(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
tool_call_id: str,
tool_name: str,
arguments: dict
):
"""向 OpenWebUI 发射工具调用开始事件。"""
if not emitter:
return
try:
# OpenWebUI 期望 assistant 消息格式的 tool_calls
await emitter({
"type": "message",
"data": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": tool_call_id,
"type": "function",
"function": {
"name": tool_name,
"arguments": json.dumps(arguments, ensure_ascii=False)
}
}
]
}
})
except Exception as e:
logger.error(f"发射工具调用开始事件失败: {e}")
async def _emit_tool_call_result(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
tool_call_id: str,
tool_name: str,
result: Any
):
"""向 OpenWebUI 发射工具调用结果。"""
if not emitter:
return
try:
# 将结果格式化为 JSON 字符串
if isinstance(result, str):
result_content = result
else:
result_content = json.dumps(result, ensure_ascii=False, indent=2)
# OpenWebUI 期望 tool 消息格式的工具结果
await emitter({
"type": "message",
"data": {
"role": "tool",
"tool_call_id": tool_call_id,
"name": tool_name,
"content": result_content
}
})
except Exception as e:
logger.error(f"发射工具结果失败: {e}")
```
### 方案 2消息历史注入
修改对话历史以包含工具调用:
```python
# 工具执行后,追加到消息中
messages.append({
"role": "assistant",
"content": None,
"tool_calls": [{
"id": tool_call_id,
"type": "function",
"function": {
"name": tool_name,
"arguments": json.dumps(arguments)
}
}]
})
messages.append({
"role": "tool",
"tool_call_id": tool_call_id,
"name": tool_name,
"content": json.dumps(result)
})
```
---
## ⚠️ 当前架构的挑战
### 1. 流式上下文
我们当前的实现使用:
- **基于队列的流式传输**:事件 → 队列 → 产出块
- **仅文本块**:我们产出纯文本,而非结构化消息
OpenWebUI 的原生展示需要:
- **结构化消息事件**:不是文本块
- **消息级别控制**:需要发射完整消息
### 2. 事件发射器兼容性
**当前用法:**
```python
# 我们使用 event_emitter 发送状态/通知
await event_emitter({
"type": "status",
"data": {"description": "处理中..."}
})
```
**工具调用所需:**
```python
# 需要发射 message 类型事件
await event_emitter({
"type": "message",
"data": {
"role": "tool",
"content": "..."
}
})
```
**问题:** `__event_emitter__` 是否支持 `message` 类型事件?
### 3. Session SDK 事件 vs OpenWebUI 消息
**Copilot SDK 事件:**
- `tool.execution_start` → 获取工具名称、参数
- `tool.execution_complete` → 获取工具结果
- 为流式文本输出设计
**OpenWebUI 消息:**
- 期望结构化消息对象
- 不为中间流注入设计
---
## 🧪 实验性实现
### 步骤 1添加原生展示 Valve
```python
class Valves(BaseModel):
USE_NATIVE_TOOL_DISPLAY: bool = Field(
default=False,
description="使用 OpenWebUI 的原生工具调用展示,而非 Markdown 格式"
)
```
### 步骤 2修改工具事件处理
```python
async def stream_response(self, ...):
# ...现有代码...
def handler(event):
event_type = get_event_type(event)
if event_type == "tool.execution_start":
tool_name = safe_get_data_attr(event, "name")
# 获取工具参数
tool_input = safe_get_data_attr(event, "input") or {}
tool_call_id = safe_get_data_attr(event, "tool_call_id", f"call_{time.time()}")
if tool_call_id:
active_tools[tool_call_id] = {
"name": tool_name,
"arguments": tool_input
}
if self.valves.USE_NATIVE_TOOL_DISPLAY:
# 发射结构化工具调用
asyncio.create_task(
self._emit_tool_call_start(
__event_call__,
tool_call_id,
tool_name,
tool_input
)
)
else:
# 当前 Markdown 展示
queue.put_nowait(f"\n\n> 🔧 **运行工具**: `{tool_name}`\n\n")
elif event_type == "tool.execution_complete":
tool_call_id = safe_get_data_attr(event, "tool_call_id")
tool_info = active_tools.get(tool_call_id, {})
tool_name = tool_info.get("name", "未知")
# 提取结果
result_obj = safe_get_data_attr(event, "result")
result_content = ""
if hasattr(result_obj, "content"):
result_content = result_obj.content
elif isinstance(result_obj, dict):
result_content = result_obj.get("content", "")
if self.valves.USE_NATIVE_TOOL_DISPLAY:
# 发射结构化工具结果
asyncio.create_task(
self._emit_tool_call_result(
__event_call__,
tool_call_id,
tool_name,
result_content
)
)
else:
# 当前 Markdown 展示
queue.put_nowait(f"> ✅ **工具完成**: {result_content}\n\n")
```
---
## 🔬 测试计划
### 测试 1事件发射器消息类型支持
```python
# 在测试对话中尝试:
await __event_emitter__({
"type": "message",
"data": {
"role": "assistant",
"content": "测试消息"
}
})
```
**预期:** 消息出现在聊天中
**如果失败:** 事件发射器不支持 message 类型
### 测试 2工具调用消息格式
```python
# 发送工具调用消息
await __event_emitter__({
"type": "message",
"data": {
"role": "assistant",
"content": None,
"tool_calls": [{
"id": "test_123",
"type": "function",
"function": {
"name": "test_tool",
"arguments": '{"param": "value"}'
}
}]
}
})
# 发送工具结果
await __event_emitter__({
"type": "message",
"data": {
"role": "tool",
"tool_call_id": "test_123",
"name": "test_tool",
"content": '{"result": "success"}'
}
})
```
**预期:** OpenWebUI 显示可折叠工具面板
**如果失败:** 事件格式与 OpenWebUI 期望不符
### 测试 3中间流工具调用注入
测试是否可以在流式传输期间注入工具调用消息:
```python
# 开始流式文本
yield "正在处理您的请求..."
# 中间流:发射工具调用
await __event_emitter__({"type": "message", "data": {...}})
# 继续流式传输
yield "完成!"
```
**预期:** 工具面板出现在响应中间
**风险:** 可能破坏流式传输流程
---
## 📋 实施检查清单
- [x] 添加 `REASONING_EFFORT` valve已完成
- [ ] 添加 `USE_NATIVE_TOOL_DISPLAY` valve
- [ ] 实现 `_emit_tool_call_start()` 辅助方法
- [ ] 实现 `_emit_tool_call_result()` 辅助方法
- [ ] 修改 `stream_response()` 中的工具事件处理
- [ ] 测试事件发射器消息类型支持
- [ ] 测试工具调用消息格式
- [ ] 测试中间流注入
- [ ] 更新文档
- [ ] 添加用户配置指南
---
## 🤔 建议
### 混合方法(最安全)
保留两种展示模式:
1. **默认(当前):** 基于 Markdown 的展示
- ✅ 与流式传输可靠工作
- ✅ 无 OpenWebUI API 依赖
- ✅ 跨版本一致
2. **实验性(原生):** 结构化工具消息
- ✅ 更好的视觉集成
- ⚠️ 需要测试 OpenWebUI 内部
- ⚠️ 可能不适用于所有场景
**配置:**
```python
USE_NATIVE_TOOL_DISPLAY: bool = Field(
default=False,
description="[实验性] 使用 OpenWebUI 的原生工具调用展示"
)
```
### 为什么 Markdown 展示目前更好
1. **可靠性:** 始终与流式传输兼容
2. **灵活性:** 可以轻松自定义格式
3. **上下文:** 与推理内联显示工具
4. **兼容性:** 跨 OpenWebUI 版本工作
### 何时使用原生展示
- 非流式模式(更容易注入消息)
- 确认事件发射器支持 message 类型后
- 对于具有大型 JSON 结果的工具(更好的格式化)
---
## 📚 后续步骤
1. **研究 OpenWebUI 源代码**
- 检查 `__event_emitter__` 实现
- 验证支持的事件类型
- 测试消息注入模式
2. **创建概念验证**
- 简单测试插件
- 发射工具调用消息
- 验证 UI 渲染
3. **记录发现**
- 使用测试结果更新本指南
- 添加有效的代码示例
- 如果成功,创建迁移指南
---
## 🔗 参考资料
- [OpenAI Chat Completion API](https://platform.openai.com/docs/api-reference/chat/create)
- [OpenWebUI 插件开发](https://docs.openwebui.com/)
- [Copilot SDK 事件](https://github.com/github/copilot-sdk)
---
**作者:** Fu-Jie
**状态:** 分析完成 - 实施等待测试

View File

@@ -0,0 +1,182 @@
# Native Tool Display Usage Guide
## 🎨 What is Native Tool Display?
Native Tool Display is an experimental feature that integrates with OpenWebUI's built-in tool call visualization system. When enabled, tool calls and their results are displayed in **collapsible JSON panels** instead of plain markdown text.
### Visual Comparison
**Traditional Display (markdown):**
```
> 🔧 Running Tool: `get_current_time`
> ✅ Tool Completed: 2026-01-27 10:30:00
```
**Native Display (collapsible panels):**
- Tool call appears in a collapsible `assistant.tool_calls` panel
- Tool result appears in a separate collapsible `tool.content` panel
- JSON syntax highlighting for better readability
- Compact by default, expandable on click
## 🚀 How to Enable
1. Open the GitHub Copilot SDK Pipe configuration (Valves)
2. Set `USE_NATIVE_TOOL_DISPLAY` to `true`
3. Save the configuration
4. Start a new conversation with tool calls
## 📋 Requirements
- OpenWebUI with native tool display support
- `__event_emitter__` must support `message` type events
- Tool-enabled models (e.g., GPT-4, Claude Sonnet)
## ⚙️ How It Works
### OpenAI Standard Format
The native display uses the OpenAI standard message format:
**Tool Call (Assistant Message):**
```json
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_current_time",
"arguments": "{\"timezone\":\"UTC\"}"
}
}
]
}
```
**Tool Result (Tool Message):**
```json
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "2026-01-27 10:30:00 UTC"
}
```
### Message Flow
1. **Tool Execution Start**:
- SDK emits `tool.execution_start` event
- Plugin sends `assistant` message with `tool_calls` array
- OpenWebUI displays collapsible tool call panel
2. **Tool Execution Complete**:
- SDK emits `tool.execution_complete` event
- Plugin sends `tool` message with `tool_call_id` and `content`
- OpenWebUI displays collapsible result panel
## 🔧 Troubleshooting
### Panel Not Showing
**Symptoms:** Tool calls still appear as markdown text
**Possible Causes:**
1. `__event_emitter__` doesn't support `message` type events
2. OpenWebUI version too old
3. Feature not enabled (`USE_NATIVE_TOOL_DISPLAY = false`)
**Solution:**
- Enable DEBUG mode to see error messages in browser console
- Check browser console for "Native message emission failed" warnings
- Update OpenWebUI to latest version
- Keep `USE_NATIVE_TOOL_DISPLAY = false` to use traditional markdown display
### Duplicate Tool Information
**Symptoms:** Tool calls appear in both native panels and markdown
**Cause:** Mixed display modes
**Solution:**
- Ensure `USE_NATIVE_TOOL_DISPLAY` is either `true` (native only) or `false` (markdown only)
- Restart the conversation after changing this setting
## 🧪 Experimental Status
This feature is marked as **EXPERIMENTAL** because:
1. **Event Emitter API**: The `__event_emitter__` support for `message` type events is not fully documented
2. **OpenWebUI Version Dependency**: Requires recent versions of OpenWebUI with native tool display support
3. **Streaming Architecture**: May have compatibility issues with streaming responses
### Fallback Behavior
If native message emission fails:
- Plugin automatically falls back to markdown display
- Error logged to browser console (when DEBUG is enabled)
- No interruption to conversation flow
## 📊 Performance Considerations
Native display has slightly better performance characteristics:
| Aspect | Native Display | Markdown Display |
|--------|----------------|------------------|
| **Rendering** | Native UI components | Markdown parser |
| **Interactivity** | Collapsible panels | Static text |
| **JSON Parsing** | Handled by UI | Not formatted |
| **Token Usage** | Minimal overhead | Formatting tokens |
## 🔮 Future Enhancements
Planned improvements for native tool display:
- [ ] Automatic fallback detection
- [ ] Tool call history persistence
- [ ] Rich metadata display (execution time, arguments preview)
- [ ] Copy tool call JSON button
- [ ] Tool call replay functionality
## 💡 Best Practices
1. **Enable DEBUG First**: Test with DEBUG mode before using in production
2. **Monitor Browser Console**: Check for warning messages during tool calls
3. **Test with Simple Tools**: Verify with built-in tools before custom implementations
4. **Keep Fallback Option**: Don't rely solely on native display until it exits experimental status
## 📖 Related Documentation
- [TOOLS_USAGE.md](TOOLS_USAGE.md) - How to create and use custom tools
- [NATIVE_TOOL_DISPLAY_GUIDE.md](NATIVE_TOOL_DISPLAY_GUIDE.md) - Technical implementation details
- [WORKFLOW.md](WORKFLOW.md) - Complete integration workflow
## 🐛 Reporting Issues
If you encounter issues with native tool display:
1. Enable `DEBUG` and `USE_NATIVE_TOOL_DISPLAY`
2. Open browser console (F12)
3. Trigger a tool call
4. Copy any error messages
5. Report to [GitHub Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
Include:
- OpenWebUI version
- Browser and version
- Error messages from console
- Steps to reproduce
---
**Author:** Fu-Jie | **Version:** 0.2.0 | **License:** MIT

View File

@@ -0,0 +1,182 @@
# 原生工具显示使用指南
## 🎨 什么是原生工具显示?
原生工具显示是一项实验性功能,与 OpenWebUI 的内置工具调用可视化系统集成。启用后,工具调用及其结果将以**可折叠的 JSON 面板**显示,而不是纯文本 markdown。
### 视觉对比
**传统显示 (markdown):**
```
> 🔧 正在运行工具: `get_current_time`
> ✅ 工具已完成: 2026-01-27 10:30:00
```
**原生显示 (可折叠面板):**
- 工具调用显示在可折叠的 `assistant.tool_calls` 面板中
- 工具结果显示在单独的可折叠 `tool.content` 面板中
- JSON 语法高亮,提高可读性
- 默认折叠,点击即可展开
## 🚀 如何启用
1. 打开 GitHub Copilot SDK Pipe 配置 (Valves)
2.`USE_NATIVE_TOOL_DISPLAY` 设置为 `true`
3. 保存配置
4. 开始新的对话并使用工具调用
## 📋 要求
- 支持原生工具显示的 OpenWebUI
- `__event_emitter__` 必须支持 `message` 类型事件
- 支持工具的模型(例如 GPT-4、Claude Sonnet
## ⚙️ 工作原理
### OpenAI 标准格式
原生显示使用 OpenAI 标准消息格式:
**工具调用(助手消息):**
```json
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_current_time",
"arguments": "{\"timezone\":\"UTC\"}"
}
}
]
}
```
**工具结果(工具消息):**
```json
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "2026-01-27 10:30:00 UTC"
}
```
### 消息流程
1. **工具执行开始**
- SDK 发出 `tool.execution_start` 事件
- 插件发送带有 `tool_calls` 数组的 `assistant` 消息
- OpenWebUI 显示可折叠的工具调用面板
2. **工具执行完成**
- SDK 发出 `tool.execution_complete` 事件
- 插件发送带有 `tool_call_id``content``tool` 消息
- OpenWebUI 显示可折叠的结果面板
## 🔧 故障排除
### 面板未显示
**症状:** 工具调用仍以 markdown 文本形式显示
**可能原因:**
1. `__event_emitter__` 不支持 `message` 类型事件
2. OpenWebUI 版本过旧
3. 功能未启用(`USE_NATIVE_TOOL_DISPLAY = false`
**解决方案:**
- 启用 DEBUG 模式查看浏览器控制台中的错误消息
- 检查浏览器控制台的 "Native message emission failed" 警告
- 更新 OpenWebUI 到最新版本
- 保持 `USE_NATIVE_TOOL_DISPLAY = false` 使用传统 markdown 显示
### 重复的工具信息
**症状:** 工具调用同时出现在原生面板和 markdown 中
**原因:** 混合显示模式
**解决方案:**
- 确保 `USE_NATIVE_TOOL_DISPLAY``true`(仅原生)或 `false`(仅 markdown
- 更改设置后重启对话
## 🧪 实验性状态
此功能标记为**实验性**,因为:
1. **事件发射器 API**`__event_emitter__``message` 类型事件的支持未完全文档化
2. **OpenWebUI 版本依赖**:需要支持原生工具显示的较新 OpenWebUI 版本
3. **流式架构**:可能与流式响应存在兼容性问题
### 回退行为
如果原生消息发送失败:
- 插件自动回退到 markdown 显示
- 错误记录到浏览器控制台(启用 DEBUG 时)
- 不会中断对话流程
## 📊 性能考虑
原生显示具有略好的性能特征:
| 方面 | 原生显示 | Markdown 显示 |
|------|----------|---------------|
| **渲染** | 原生 UI 组件 | Markdown 解析器 |
| **交互性** | 可折叠面板 | 静态文本 |
| **JSON 解析** | 由 UI 处理 | 未格式化 |
| **Token 使用** | 最小开销 | 格式化 token |
## 🔮 未来增强
原生工具显示的计划改进:
- [ ] 自动回退检测
- [ ] 工具调用历史持久化
- [ ] 丰富的元数据显示(执行时间、参数预览)
- [ ] 复制工具调用 JSON 按钮
- [ ] 工具调用重放功能
## 💡 最佳实践
1. **先启用 DEBUG**:在生产环境使用前先在 DEBUG 模式下测试
2. **监控浏览器控制台**:在工具调用期间检查警告消息
3. **使用简单工具测试**:在自定义实现前先用内置工具验证
4. **保留回退选项**:在退出实验性状态前不要完全依赖原生显示
## 📖 相关文档
- [TOOLS_USAGE.md](TOOLS_USAGE.md) - 如何创建和使用自定义工具
- [NATIVE_TOOL_DISPLAY_GUIDE.md](NATIVE_TOOL_DISPLAY_GUIDE.md) - 技术实现细节
- [WORKFLOW.md](WORKFLOW.md) - 完整集成工作流程
## 🐛 报告问题
如果您在使用原生工具显示时遇到问题:
1. 启用 `DEBUG``USE_NATIVE_TOOL_DISPLAY`
2. 打开浏览器控制台F12
3. 触发工具调用
4. 复制任何错误消息
5. 报告到 [GitHub Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
包含:
- OpenWebUI 版本
- 浏览器和版本
- 控制台的错误消息
- 复现步骤
---
**作者:** Fu-Jie | **版本:** 0.2.0 | **许可证:** MIT

View File

@@ -0,0 +1,509 @@
# OpenWebUI Function 集成方案
## 🎯 核心挑战
在 Copilot Tool Handler 中调用 OpenWebUI Functions 的关键问题:
**问题:** Copilot SDK 的 Tool Handler 是一个独立的回调函数,如何在这个上下文中访问和执行 OpenWebUI 的 Function
---
## 🔍 OpenWebUI Function 系统分析
### 1. Function 数据结构
OpenWebUI 的 Function/Tool 传递格式:
```python
body = {
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}
}
}
]
}
```
### 2. Function 执行机制
OpenWebUI Functions 的执行方式有几种可能:
#### 选项 A: 通过 Function ID 调用内部 API
```python
# 假设 OpenWebUI 提供内部 API
from open_webui.apps.webui.models.functions import Functions
function_id = "function_uuid" # 需要从配置中获取
result = await Functions.execute_function(
function_id=function_id,
arguments={"location": "Beijing"}
)
```
#### 选项 B: 通过 **event_emitter** 触发
```python
# 通过事件系统触发 function 执行
if __event_emitter__:
await __event_emitter__({
"type": "function_call",
"data": {
"name": "get_weather",
"arguments": {"location": "Beijing"}
}
})
```
#### 选项 C: 自己实现 Function 逻辑
```python
# 在 Pipe 内部实现常用功能
class Pipe:
def _builtin_get_weather(self, location: str) -> dict:
# 实现天气查询
pass
def _builtin_search_web(self, query: str) -> dict:
# 实现网页搜索
pass
```
---
## 💡 推荐方案:混合架构
### 架构设计
```
User Message
OpenWebUI UI (Functions 已配置)
Pipe.pipe(body) - body 包含 tools[]
转换为 Copilot Tools + 存储 Function Registry
Copilot 决定调用 Tool
Tool Handler 查询 Registry → 执行对应逻辑
返回结果给 Copilot
继续生成回答
```
### 核心实现
#### 1. Function Registry函数注册表
```python
class Pipe:
def __init__(self):
# ...
self._function_registry = {} # {function_name: callable}
self._function_metadata = {} # {function_name: metadata}
```
#### 2. 注册 Functions
```python
def _register_openwebui_functions(
self,
owui_functions: List[dict],
__event_emitter__=None,
__event_call__=None
):
"""
注册 OpenWebUI Functions 到内部 registry
关键:将 function 定义和执行逻辑关联起来
"""
for func_def in owui_functions:
if func_def.get("type") != "function":
continue
func_info = func_def.get("function", {})
func_name = func_info.get("name")
if not func_name:
continue
# 存储元数据
self._function_metadata[func_name] = {
"description": func_info.get("description", ""),
"parameters": func_info.get("parameters", {}),
"original_def": func_def
}
# 创建执行器(关键)
executor = self._create_function_executor(
func_name,
func_def,
__event_emitter__,
__event_call__
)
self._function_registry[func_name] = executor
```
#### 3. Function Executor 工厂
```python
def _create_function_executor(
self,
func_name: str,
func_def: dict,
__event_emitter__=None,
__event_call__=None
):
"""
为每个 function 创建执行器
策略:
1. 优先使用内置实现
2. 尝试调用 OpenWebUI API
3. 返回错误
"""
async def executor(arguments: dict) -> dict:
# 策略 1: 检查是否有内置实现
builtin_method = getattr(self, f"_builtin_{func_name}", None)
if builtin_method:
self._emit_debug_log_sync(
f"Using builtin implementation for {func_name}",
__event_call__
)
try:
result = builtin_method(arguments)
if inspect.iscoroutine(result):
result = await result
return {"success": True, "result": result}
except Exception as e:
return {"success": False, "error": str(e)}
# 策略 2: 尝试通过 Event Emitter 调用
if __event_emitter__:
try:
# 尝试触发 function_call 事件
response_queue = asyncio.Queue()
await __event_emitter__({
"type": "function_call",
"data": {
"name": func_name,
"arguments": arguments,
"response_queue": response_queue # 回调队列
}
})
# 等待结果(带超时)
result = await asyncio.wait_for(
response_queue.get(),
timeout=self.valves.TOOL_TIMEOUT
)
return {"success": True, "result": result}
except asyncio.TimeoutError:
return {"success": False, "error": "Function execution timeout"}
except Exception as e:
self._emit_debug_log_sync(
f"Event emitter call failed: {e}",
__event_call__
)
# 继续尝试其他方法
# 策略 3: 尝试调用 OpenWebUI internal API
try:
# 这需要研究 OpenWebUI 源码确定正确的调用方式
from open_webui.apps.webui.models.functions import Functions
# 需要获取 function_id这是关键问题
function_id = self._get_function_id_by_name(func_name)
if function_id:
result = await Functions.execute(
function_id=function_id,
params=arguments
)
return {"success": True, "result": result}
except ImportError:
pass
except Exception as e:
self._emit_debug_log_sync(
f"OpenWebUI API call failed: {e}",
__event_call__
)
# 策略 4: 返回"未实现"错误
return {
"success": False,
"error": f"Function '{func_name}' is not implemented. "
"Please implement it as a builtin method or ensure OpenWebUI API is available."
}
return executor
```
#### 4. Tool Handler 实现
```python
def _create_tool_handler(self, tool_name: str, __event_call__=None):
"""为 Copilot SDK 创建 Tool Handler"""
async def handler(invocation: dict) -> dict:
"""
Copilot Tool Handler
invocation: {
"session_id": str,
"tool_call_id": str,
"tool_name": str,
"arguments": dict
}
"""
try:
# 从 registry 获取 executor
executor = self._function_registry.get(invocation["tool_name"])
if not executor:
return {
"textResultForLlm": f"Function '{invocation['tool_name']}' not found.",
"resultType": "failure",
"error": "function_not_found",
"toolTelemetry": {}
}
# 执行 function
self._emit_debug_log_sync(
f"Executing function: {invocation['tool_name']}({invocation['arguments']})",
__event_call__
)
exec_result = await executor(invocation["arguments"])
# 处理结果
if exec_result.get("success"):
result_text = str(exec_result.get("result", ""))
return {
"textResultForLlm": result_text,
"resultType": "success",
"error": None,
"toolTelemetry": {}
}
else:
error_msg = exec_result.get("error", "Unknown error")
return {
"textResultForLlm": f"Function execution failed: {error_msg}",
"resultType": "failure",
"error": error_msg,
"toolTelemetry": {}
}
except Exception as e:
self._emit_debug_log_sync(
f"Tool handler error: {e}",
__event_call__
)
return {
"textResultForLlm": "An unexpected error occurred during function execution.",
"resultType": "failure",
"error": str(e),
"toolTelemetry": {}
}
return handler
```
---
## 🔌 内置 Functions 实现示例
### 示例 1: 获取当前时间
```python
def _builtin_get_current_time(self, arguments: dict) -> str:
"""内置实现:获取当前时间"""
from datetime import datetime
timezone = arguments.get("timezone", "UTC")
format_str = arguments.get("format", "%Y-%m-%d %H:%M:%S")
now = datetime.now()
return now.strftime(format_str)
```
### 示例 2: 简单计算器
```python
def _builtin_calculate(self, arguments: dict) -> str:
"""内置实现:数学计算"""
expression = arguments.get("expression", "")
try:
# 安全的数学计算(仅允许基本运算)
allowed_chars = set("0123456789+-*/()., ")
if not all(c in allowed_chars for c in expression):
raise ValueError("Invalid characters in expression")
result = eval(expression, {"__builtins__": {}})
return str(result)
except Exception as e:
raise ValueError(f"Calculation error: {e}")
```
### 示例 3: 网页搜索(需要外部 API
```python
async def _builtin_search_web(self, arguments: dict) -> str:
"""内置实现:网页搜索(使用 DuckDuckGo"""
query = arguments.get("query", "")
max_results = arguments.get("max_results", 5)
try:
# 使用 duckduckgo_search 库
from duckduckgo_search import DDGS
results = []
with DDGS() as ddgs:
for r in ddgs.text(query, max_results=max_results):
results.append({
"title": r.get("title", ""),
"url": r.get("href", ""),
"snippet": r.get("body", "")
})
# 格式化结果
formatted = "\n\n".join([
f"**{r['title']}**\n{r['url']}\n{r['snippet']}"
for r in results
])
return formatted
except Exception as e:
raise ValueError(f"Search failed: {e}")
```
---
## 🚀 完整集成流程
### pipe() 方法中的集成
```python
async def pipe(
self,
body: dict,
__metadata__: Optional[dict] = None,
__event_emitter__=None,
__event_call__=None,
) -> Union[str, AsyncGenerator]:
# ... 现有代码 ...
# ✅ Step 1: 提取 OpenWebUI Functions
owui_functions = body.get("tools", [])
# ✅ Step 2: 注册 Functions
if self.valves.ENABLE_TOOLS and owui_functions:
self._register_openwebui_functions(
owui_functions,
__event_emitter__,
__event_call__
)
# ✅ Step 3: 转换为 Copilot Tools
copilot_tools = []
for func_name in self._function_registry.keys():
metadata = self._function_metadata[func_name]
copilot_tools.append({
"name": func_name,
"description": metadata["description"],
"parameters": metadata["parameters"],
"handler": self._create_tool_handler(func_name, __event_call__)
})
# ✅ Step 4: 创建 Session 并传递 Tools
session_config = SessionConfig(
model=real_model_id,
tools=copilot_tools, # ✅ 关键
...
)
session = await client.create_session(config=session_config)
# ... 后续代码 ...
```
---
## ⚠️ 待解决问题
### 1. Function ID 映射
**问题:** OpenWebUI Functions 通常通过 UUID 标识,但 body 中只有 name
**解决思路:**
- 在 OpenWebUI 启动时建立 name → id 映射表
- 或者修改 OpenWebUI 在 body 中同时传递 id
### 2. Event Emitter 回调机制
**问题:** 不确定 **event_emitter** 是否支持 function_call 事件
**验证方法:**
```python
# 测试代码
await __event_emitter__({
"type": "function_call",
"data": {"name": "test_func", "arguments": {}}
})
```
### 3. 异步执行超时
**问题:** 某些 Functions 可能执行很慢
**解决方案:**
- 实现 timeout 机制(已在 executor 中实现)
- 对于长时间运行的任务,考虑返回"processing"状态
---
## 📝 实现清单
- [ ] 实现 _function_registry 和 _function_metadata
- [ ] 实现 _register_openwebui_functions()
- [ ] 实现 _create_function_executor()
- [ ] 实现 _create_tool_handler()
- [ ] 实现 3-5 个常用内置 Functions
- [ ] 测试 Function 注册和调用流程
- [ ] 验证 **event_emitter** 机制
- [ ] 研究 OpenWebUI Functions API
- [ ] 添加错误处理和超时机制
- [ ] 更新文档
---
**下一步行动:**
1. 实现基础的 Function Registry
2. 添加 2-3 个简单的内置 Functions如 get_time, calculate
3. 测试基本的 Tool Calling 流程
4. 根据测试结果调整架构
**作者:** Fu-Jie
**日期:** 2026-01-26

View File

@@ -0,0 +1,708 @@
# SessionConfig 完整功能集成指南
## 📋 概述
本文档详细说明如何将 GitHub Copilot SDK 的 `SessionConfig` 所有功能集成到 OpenWebUI Pipe 中。
---
## 🎯 功能清单与集成状态
| 功能 | 状态 | 优先级 | 说明 |
|------|------|--------|------|
| `session_id` | ✅ 已实现 | 高 | 使用 OpenWebUI chat_id |
| `model` | ✅ 已实现 | 高 | 从 body 动态获取 |
| `tools` | ✅ 已实现 | 高 | v0.2.0 新增示例工具 |
| `streaming` | ✅ 已实现 | 高 | 支持流式输出 |
| `infinite_sessions` | ✅ 已实现 | 高 | 自动上下文压缩 |
| `system_message` | ⚠️ 部分支持 | 中 | 可通过 Valves 添加 |
| `available_tools` | ⚠️ 部分支持 | 中 | 已有 AVAILABLE_TOOLS |
| `excluded_tools` | 🔲 未实现 | 低 | 可添加到 Valves |
| `on_permission_request` | 🔲 未实现 | 中 | 需要 UI 交互支持 |
| `provider` (BYOK) | 🔲 未实现 | 低 | 高级功能 |
| `mcp_servers` | 🔲 未实现 | 低 | MCP 协议支持 |
| `custom_agents` | 🔲 未实现 | 低 | 自定义代理 |
| `config_dir` | 🔲 未实现 | 低 | 可通过 WORKSPACE_DIR |
| `skill_directories` | 🔲 未实现 | 低 | 技能系统 |
| `disabled_skills` | 🔲 未实现 | 低 | 技能过滤 |
---
## 📖 详细集成方案
### 1. ✅ session_id已实现
**功能:** 持久化会话 ID
**当前实现:**
```python
session_config = SessionConfig(
session_id=chat_id if chat_id else None, # 使用 OpenWebUI 的 chat_id
...
)
```
**工作原理:**
- OpenWebUI 的 `chat_id` 直接映射为 Copilot 的 `session_id`
- 会话状态持久化到磁盘
- 支持跨重启恢复对话
---
### 2. ✅ model已实现
**功能:** 选择 Copilot 模型
**当前实现:**
```python
# 从用户选择的模型中提取
request_model = body.get("model", "")
if request_model.startswith(f"{self.id}-"):
real_model_id = request_model[len(f"{self.id}-"):]
```
**Valves 配置:**
```python
MODEL_ID: str = Field(
default="claude-sonnet-4.5",
description="默认模型(动态获取失败时使用)"
)
```
---
### 3. ✅ tools已实现 - v0.2.0
**功能:** 自定义工具/函数调用
**当前实现:**
```python
custom_tools = self._initialize_custom_tools()
session_config = SessionConfig(
tools=custom_tools,
...
)
```
**Valves 配置:**
```python
ENABLE_TOOLS: bool = Field(default=False)
AVAILABLE_TOOLS: str = Field(default="all")
```
**内置示例工具:**
- `get_current_time` - 获取当前时间
- `calculate` - 数学计算
- `generate_random_number` - 随机数生成
**扩展方法:** 参考 [TOOLS_USAGE.md](TOOLS_USAGE.md)
---
### 4. ⚠️ system_message部分支持
**功能:** 自定义系统提示词
**集成方案:**
#### 方案 A通过 Valves 添加(推荐)
```python
class Valves(BaseModel):
SYSTEM_MESSAGE: str = Field(
default="",
description="Custom system message (append mode)"
)
SYSTEM_MESSAGE_MODE: str = Field(
default="append",
description="System message mode: 'append' or 'replace'"
)
```
**实现:**
```python
async def pipe(self, body, ...):
system_message_config = None
if self.valves.SYSTEM_MESSAGE:
if self.valves.SYSTEM_MESSAGE_MODE == "replace":
system_message_config = {
"mode": "replace",
"content": self.valves.SYSTEM_MESSAGE
}
else:
system_message_config = {
"mode": "append",
"content": self.valves.SYSTEM_MESSAGE
}
session_config = SessionConfig(
system_message=system_message_config,
...
)
```
#### 方案 B从 OpenWebUI 系统提示词读取
```python
# 从 body 中获取系统提示词
system_prompt = body.get("system", "")
if system_prompt:
system_message_config = {
"mode": "append",
"content": system_prompt
}
```
**注意事项:**
- `append` 模式:在默认系统提示词后追加
- `replace` 模式:完全替换(移除 SDK 安全保护)
---
### 5. ⚠️ available_tools / excluded_tools
**功能:** 工具白名单/黑名单
**当前部分支持:**
```python
AVAILABLE_TOOLS: str = Field(
default="all",
description="'all' or comma-separated list"
)
```
**增强实现:**
```python
class Valves(BaseModel):
AVAILABLE_TOOLS: str = Field(
default="all",
description="Available tools (comma-separated or 'all')"
)
EXCLUDED_TOOLS: str = Field(
default="",
description="Excluded tools (comma-separated)"
)
```
**应用到 SessionConfig**
```python
session_config = SessionConfig(
tools=custom_tools,
available_tools=self._parse_tool_list(self.valves.AVAILABLE_TOOLS),
excluded_tools=self._parse_tool_list(self.valves.EXCLUDED_TOOLS),
...
)
def _parse_tool_list(self, value: str) -> list[str]:
"""解析工具列表"""
if not value or value == "all":
return []
return [t.strip() for t in value.split(",") if t.strip()]
```
---
### 6. 🔲 on_permission_request未实现
**功能:** 处理权限请求shell 命令、文件写入等)
**使用场景:**
- Copilot 需要执行 shell 命令
- 需要写入文件
- 需要访问 URL
**集成挑战:**
- 需要 OpenWebUI 前端支持实时权限弹窗
- 需要异步处理用户确认
**推荐方案:**
#### 方案 A自动批准开发/测试环境)
```python
async def auto_approve_permission_handler(
request: dict,
context: dict
) -> dict:
"""自动批准所有权限请求(危险!)"""
return {
"kind": "approved",
"rules": []
}
session_config = SessionConfig(
on_permission_request=auto_approve_permission_handler,
...
)
```
#### 方案 B基于规则的批准
```python
class Valves(BaseModel):
ALLOW_SHELL_COMMANDS: bool = Field(default=False)
ALLOW_FILE_WRITE: bool = Field(default=False)
ALLOW_URL_ACCESS: bool = Field(default=True)
async def rule_based_permission_handler(
request: dict,
context: dict
) -> dict:
kind = request.get("kind")
if kind == "shell" and not self.valves.ALLOW_SHELL_COMMANDS:
return {"kind": "denied-by-rules"}
if kind == "write" and not self.valves.ALLOW_FILE_WRITE:
return {"kind": "denied-by-rules"}
if kind == "url" and not self.valves.ALLOW_URL_ACCESS:
return {"kind": "denied-by-rules"}
return {"kind": "approved", "rules": []}
```
#### 方案 C通过 Event Emitter 请求用户确认(理想)
```python
async def interactive_permission_handler(
request: dict,
context: dict
) -> dict:
"""通过前端请求用户确认"""
if not __event_emitter__:
return {"kind": "denied-no-approval-rule-and-could-not-request-from-user"}
# 发送权限请求到前端
response_queue = asyncio.Queue()
await __event_emitter__({
"type": "permission_request",
"data": {
"kind": request.get("kind"),
"description": request.get("description"),
"response_queue": response_queue
}
})
# 等待用户响应(带超时)
try:
user_response = await asyncio.wait_for(
response_queue.get(),
timeout=30.0
)
if user_response.get("approved"):
return {"kind": "approved", "rules": []}
else:
return {"kind": "denied-interactively-by-user"}
except asyncio.TimeoutError:
return {"kind": "denied-no-approval-rule-and-could-not-request-from-user"}
```
---
### 7. 🔲 providerBYOK - Bring Your Own Key
**功能:** 使用自己的 API 密钥连接 OpenAI/Azure/Anthropic
**使用场景:**
- 不使用 GitHub Copilot 配额
- 直接连接云服务提供商
- 使用 Azure OpenAI 部署
**集成方案:**
```python
class Valves(BaseModel):
USE_CUSTOM_PROVIDER: bool = Field(default=False)
PROVIDER_TYPE: str = Field(
default="openai",
description="Provider type: openai, azure, anthropic"
)
PROVIDER_BASE_URL: str = Field(default="")
PROVIDER_API_KEY: str = Field(default="")
PROVIDER_BEARER_TOKEN: str = Field(default="")
AZURE_API_VERSION: str = Field(default="2024-10-21")
def _build_provider_config(self) -> dict | None:
"""构建 Provider 配置"""
if not self.valves.USE_CUSTOM_PROVIDER:
return None
config = {
"type": self.valves.PROVIDER_TYPE,
"base_url": self.valves.PROVIDER_BASE_URL,
}
if self.valves.PROVIDER_API_KEY:
config["api_key"] = self.valves.PROVIDER_API_KEY
if self.valves.PROVIDER_BEARER_TOKEN:
config["bearer_token"] = self.valves.PROVIDER_BEARER_TOKEN
if self.valves.PROVIDER_TYPE == "azure":
config["azure"] = {
"api_version": self.valves.AZURE_API_VERSION
}
# 自动推断 wire_api
if self.valves.PROVIDER_TYPE == "anthropic":
config["wire_api"] = "responses"
else:
config["wire_api"] = "completions"
return config
```
**应用:**
```python
session_config = SessionConfig(
provider=self._build_provider_config(),
...
)
```
---
### 8. ✅ streaming已实现
**功能:** 流式输出
**当前实现:**
```python
session_config = SessionConfig(
streaming=body.get("stream", False),
...
)
```
---
### 9. 🔲 mcp_serversMCP 协议)
**功能:** Model Context Protocol 服务器集成
**使用场景:**
- 连接外部数据源数据库、API
- 集成第三方服务
**集成方案:**
```python
class Valves(BaseModel):
MCP_SERVERS_CONFIG: str = Field(
default="{}",
description="MCP servers configuration (JSON format)"
)
def _parse_mcp_servers(self) -> dict | None:
"""解析 MCP 服务器配置"""
if not self.valves.MCP_SERVERS_CONFIG:
return None
try:
return json.loads(self.valves.MCP_SERVERS_CONFIG)
except:
return None
```
**配置示例:**
```json
{
"database": {
"type": "local",
"command": "mcp-server-sqlite",
"args": ["--db", "/path/to/db.sqlite"],
"tools": ["*"]
},
"weather": {
"type": "http",
"url": "https://weather-api.example.com/mcp",
"tools": ["get_weather", "get_forecast"]
}
}
```
---
### 10. 🔲 custom_agents
**功能:** 自定义 AI 代理
**使用场景:**
- 专门化的子代理(如代码审查、文档编写)
- 不同的提示词策略
**集成方案:**
```python
class Valves(BaseModel):
CUSTOM_AGENTS_CONFIG: str = Field(
default="[]",
description="Custom agents configuration (JSON array)"
)
def _parse_custom_agents(self) -> list | None:
"""解析自定义代理配置"""
if not self.valves.CUSTOM_AGENTS_CONFIG:
return None
try:
return json.loads(self.valves.CUSTOM_AGENTS_CONFIG)
except:
return None
```
**配置示例:**
```json
[
{
"name": "code_reviewer",
"display_name": "Code Reviewer",
"description": "Reviews code for best practices",
"prompt": "You are an expert code reviewer. Focus on security, performance, and maintainability.",
"tools": ["read_file", "write_file"],
"infer": true
}
]
```
---
### 11. 🔲 config_dir
**功能:** 自定义配置目录
**当前支持:**
- 已有 `WORKSPACE_DIR` 控制工作目录
**增强方案:**
```python
class Valves(BaseModel):
CONFIG_DIR: str = Field(
default="",
description="Custom config directory for session state"
)
session_config = SessionConfig(
config_dir=self.valves.CONFIG_DIR if self.valves.CONFIG_DIR else None,
...
)
```
---
### 12. 🔲 skill_directories / disabled_skills
**功能:** Copilot Skills 系统
**使用场景:**
- 加载自定义技能包
- 禁用特定技能
**集成方案:**
```python
class Valves(BaseModel):
SKILL_DIRECTORIES: str = Field(
default="",
description="Comma-separated skill directories"
)
DISABLED_SKILLS: str = Field(
default="",
description="Comma-separated disabled skills"
)
def _parse_skills_config(self):
"""解析技能配置"""
skill_dirs = []
if self.valves.SKILL_DIRECTORIES:
skill_dirs = [
d.strip()
for d in self.valves.SKILL_DIRECTORIES.split(",")
if d.strip()
]
disabled = []
if self.valves.DISABLED_SKILLS:
disabled = [
s.strip()
for s in self.valves.DISABLED_SKILLS.split(",")
if s.strip()
]
return skill_dirs, disabled
# 应用
skill_dirs, disabled_skills = self._parse_skills_config()
session_config = SessionConfig(
skill_directories=skill_dirs if skill_dirs else None,
disabled_skills=disabled_skills if disabled_skills else None,
...
)
```
---
### 13. ✅ infinite_sessions已实现
**功能:** 无限会话与自动上下文压缩
**当前实现:**
```python
class Valves(BaseModel):
INFINITE_SESSION: bool = Field(default=True)
COMPACTION_THRESHOLD: float = Field(default=0.8)
BUFFER_THRESHOLD: float = Field(default=0.95)
infinite_session_config = None
if self.valves.INFINITE_SESSION:
infinite_session_config = {
"enabled": True,
"background_compaction_threshold": self.valves.COMPACTION_THRESHOLD,
"buffer_exhaustion_threshold": self.valves.BUFFER_THRESHOLD,
}
session_config = SessionConfig(
infinite_sessions=infinite_session_config,
...
)
```
---
## 🎯 实施优先级建议
### 🔥 高优先级(立即实现)
1. **system_message** - 用户最常需要的功能
2. **on_permission_request (基于规则)** - 安全性需求
### 📌 中优先级(下一阶段)
3. **excluded_tools** - 完善工具管理
4. **provider (BYOK)** - 高级用户需求
5. **config_dir** - 增强会话管理
### 📋 低优先级(可选)
6. **mcp_servers** - 高级集成
7. **custom_agents** - 专业化功能
8. **skill_directories** - 生态系统功能
---
## 🚀 快速实施计划
### Phase 1: 基础增强1-2小时
```python
# 添加到 Valves
SYSTEM_MESSAGE: str = Field(default="")
SYSTEM_MESSAGE_MODE: str = Field(default="append")
EXCLUDED_TOOLS: str = Field(default="")
# 添加到 pipe() 方法
system_message_config = self._build_system_message_config()
excluded_tools = self._parse_tool_list(self.valves.EXCLUDED_TOOLS)
session_config = SessionConfig(
system_message=system_message_config,
excluded_tools=excluded_tools,
...
)
```
### Phase 2: 权限管理2-3小时
```python
# 添加权限控制 Valves
ALLOW_SHELL_COMMANDS: bool = Field(default=False)
ALLOW_FILE_WRITE: bool = Field(default=False)
ALLOW_URL_ACCESS: bool = Field(default=True)
# 实现权限处理器
session_config = SessionConfig(
on_permission_request=self._create_permission_handler(),
...
)
```
### Phase 3: BYOK 支持3-4小时
```python
# 添加 Provider Valves
USE_CUSTOM_PROVIDER: bool = Field(default=False)
PROVIDER_TYPE: str = Field(default="openai")
PROVIDER_BASE_URL: str = Field(default="")
PROVIDER_API_KEY: str = Field(default="")
# 实现 Provider 配置
session_config = SessionConfig(
provider=self._build_provider_config(),
...
)
```
---
## 📚 参考资源
- **SDK 类型定义**: `/opt/homebrew/.../copilot/types.py`
- **工具系统**: [TOOLS_USAGE.md](TOOLS_USAGE.md)
- **SDK 文档**: <https://github.com/github/copilot-sdk>
---
## ✅ 实施检查清单
使用此清单跟踪实施进度:
- [x] session_id
- [x] model
- [x] tools
- [x] streaming
- [x] infinite_sessions
- [ ] system_message
- [ ] available_tools (完善)
- [ ] excluded_tools
- [ ] on_permission_request
- [ ] provider (BYOK)
- [ ] mcp_servers
- [ ] custom_agents
- [ ] config_dir
- [ ] skill_directories
- [ ] disabled_skills
---
**作者:** Fu-Jie
**版本:** v1.0
**日期:** 2026-01-26
**更新:** 随功能实施持续更新

View File

@@ -0,0 +1,191 @@
# 🛠️ Custom Tools Usage / 自定义工具使用指南
## Overview / 概览
This pipe includes **1 example custom tool** that demonstrates how to use GitHub Copilot SDK's tool calling feature.
本 Pipe 包含 **1 个示例自定义工具**,展示如何使用 GitHub Copilot SDK 的工具调用功能。
---
## 🚀 Quick Start / 快速开始
### 1. Enable Tools / 启用工具
In Valves configuration:
在 Valves 配置中:
```
ENABLE_TOOLS: true
AVAILABLE_TOOLS: all
```
### 2. Test with Conversations / 测试对话
Try these examples:
尝试这些示例:
**English:**
- "Give me a random number between 1 and 100"
**中文:**
- "给我一个 1 到 100 之间的随机数"
---
## 📦 Included Tools / 内置工具
### 1. `generate_random_number` / 生成随机数
**Description:** Generate a random integer
**描述:** 生成随机整数
**Parameters / 参数:**
- `min` (optional): Minimum value (default: 1)
- `max` (optional): Maximum value (default: 100)
- `min` (可选): 最小值 (默认: 1)
- `max` (可选): 最大值 (默认: 100)
**Example / 示例:**
```
User: "Give me a random number between 1 and 10"
Copilot: [calls generate_random_number with min=1, max=10] "Generated random number: 7"
用户: "给我一个 1 到 10 之间的随机数"
Copilot: [调用 generate_random_number参数 min=1, max=10] "生成的随机数: 7"
```
---
## ⚙️ Advanced Configuration / 高级配置
### Select Specific Tools / 选择特定工具
Instead of enabling all tools, specify which ones to use:
不启用所有工具,而是指定要使用的工具:
```
ENABLE_TOOLS: true
AVAILABLE_TOOLS: generate_random_number
```
---
## 🔧 How Tool Calling Works / 工具调用的工作原理
```
1. User asks a question / 用户提问
2. Copilot decides if it needs a tool / Copilot 决定是否需要工具
3. If yes, Copilot calls the appropriate tool / 如果需要,调用相应工具
4. Tool executes and returns result / 工具执行并返回结果
5. Copilot uses the result to answer / Copilot 使用结果回答
```
### Visual Feedback / 可视化反馈
When tools are called, you'll see:
当工具被调用时,你会看到:
```
🔧 **Calling tool**: `generate_random_number`
✅ **Tool `generate_random_number` completed**
Generated random number: 7
```
---
## 📚 Creating Your Own Tools / 创建自定义工具
Want to add your own tools? Follow this pattern (module-level tools):
想要添加自己的工具?遵循这个模式(模块级工具):
```python
from pydantic import BaseModel, Field
from copilot import define_tool
class MyToolParams(BaseModel):
param_name: str = Field(description="Parameter description")
@define_tool(description="Clear description of what the tool does and when to use it")
async def my_tool(params: MyToolParams) -> str:
# Do something
result = do_something(params.param_name)
return f"Result: {result}"
```
Then register it in `_initialize_custom_tools()`:
然后将它添加到 `_initialize_custom_tools()`:
```python
def _initialize_custom_tools(self):
if not self.valves.ENABLE_TOOLS:
return []
all_tools = {
"generate_random_number": generate_random_number,
"my_tool": my_tool, # ✅ Add here
}
if self.valves.AVAILABLE_TOOLS == "all":
return list(all_tools.values())
enabled = [t.strip() for t in self.valves.AVAILABLE_TOOLS.split(",")]
return [all_tools[name] for name in enabled if name in all_tools]
```
---
## ⚠️ Important Notes / 重要说明
### Security / 安全性
- Tools run in the same process as the pipe
- Be careful with tools that execute code or access files
- Always validate input parameters
- 工具在与 Pipe 相同的进程中运行
- 谨慎处理执行代码或访问文件的工具
- 始终验证输入参数
### Performance / 性能
- Tool execution is synchronous during streaming
- Long-running tools may cause delays
- Consider adding timeouts for external API calls
- 工具执行在流式传输期间是同步的
- 长时间运行的工具可能导致延迟
- 考虑为外部 API 调用添加超时
### Debugging / 调试
- Enable `DEBUG: true` to see tool events in the browser console
- Check tool calls in `🔧 Calling tool` messages
- Tool errors are displayed in the response
- 启用 `DEBUG: true` 在浏览器控制台查看工具事件
-`🔧 Calling tool` 消息中检查工具调用
- 工具错误会显示在响应中
---
## 📖 References / 参考资料
- [Copilot SDK Documentation](https://github.com/github/copilot-sdk)
- [COPILOT_TOOLS_QUICKSTART.md](COPILOT_TOOLS_QUICKSTART.md) - Detailed implementation guide
- [JSON Schema](https://json-schema.org/) - For parameter definitions
---
**Version:** 0.2.3
**Last Updated:** 2026-01-27

View File

@@ -0,0 +1,431 @@
# GitHub Copilot SDK - Tool 功能实现指南
## 📋 概述
本指南介绍如何在 GitHub Copilot SDK Pipe 中实现 Function/Tool Calling 功能。
---
## 🏗️ 架构设计
### 工作流程
```
OpenWebUI Tools/Functions
↓ (转换)
Copilot SDK Tool Definition
↓ (注册)
Session Tool Handlers
↓ (调用)
Tool Execution → Result
↓ (返回)
Continue Conversation
```
### 核心接口
#### 1. Tool Definition工具定义
```python
from copilot.types import Tool
tool = Tool(
name="get_weather",
description="Get current weather for a location",
parameters={
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, e.g., 'San Francisco'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
},
handler=weather_handler # 处理函数
)
```
#### 2. Tool Handler处理函数
```python
from copilot.types import ToolInvocation, ToolResult
async def weather_handler(invocation: ToolInvocation) -> ToolResult:
"""
invocation 包含:
- session_id: str
- tool_call_id: str
- tool_name: str
- arguments: dict # {"location": "San Francisco", "unit": "celsius"}
"""
location = invocation["arguments"]["location"]
# 执行实际逻辑
weather_data = await fetch_weather(location)
# 返回结果
return ToolResult(
textResultForLlm=f"Weather in {location}: {weather_data['temp']}°C, {weather_data['condition']}",
resultType="success", # or "failure"
error=None,
toolTelemetry={"execution_time_ms": 150}
)
```
#### 3. Session Configuration会话配置
```python
from copilot.types import SessionConfig
session_config = SessionConfig(
model="claude-sonnet-4.5",
tools=[tool1, tool2, tool3], # ✅ 传递工具列表
available_tools=["get_weather", "search_web"], # 可选:过滤可用工具
excluded_tools=["dangerous_tool"], # 可选:排除工具
)
session = await client.create_session(config=session_config)
```
---
## 💻 实现方案
### 方案 A桥接 OpenWebUI Tools推荐
#### 1. 添加 Valves 配置
```python
class Valves(BaseModel):
ENABLE_TOOLS: bool = Field(
default=True,
description="Enable OpenWebUI tool integration"
)
TOOL_TIMEOUT: int = Field(
default=30,
description="Tool execution timeout (seconds)"
)
AVAILABLE_TOOLS: str = Field(
default="",
description="Filter specific tools (comma separated, empty = all)"
)
```
#### 2. 实现 Tool 转换器
```python
def _convert_openwebui_tools_to_copilot(
self,
owui_tools: List[dict],
__event_call__=None
) -> List[dict]:
"""
将 OpenWebUI tools 转换为 Copilot SDK 格式
OpenWebUI Tool 格式:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather info",
"parameters": {...} # JSON Schema
}
}
"""
copilot_tools = []
for tool in owui_tools:
if tool.get("type") != "function":
continue
func = tool.get("function", {})
tool_name = func.get("name")
if not tool_name:
continue
# 应用过滤器
if self.valves.AVAILABLE_TOOLS:
allowed = [t.strip() for t in self.valves.AVAILABLE_TOOLS.split(",")]
if tool_name not in allowed:
continue
copilot_tools.append({
"name": tool_name,
"description": func.get("description", ""),
"parameters": func.get("parameters", {}),
"handler": self._create_tool_handler(tool_name, __event_call__)
})
self._emit_debug_log_sync(
f"Registered tool: {tool_name}",
__event_call__
)
return copilot_tools
```
#### 3. 实现动态 Tool Handler
```python
def _create_tool_handler(self, tool_name: str, __event_call__=None):
"""为每个 tool 创建 handler 函数"""
async def handler(invocation: dict) -> dict:
"""
Tool handler 实现
invocation 结构:
{
"session_id": "...",
"tool_call_id": "...",
"tool_name": "get_weather",
"arguments": {"location": "Beijing"}
}
"""
try:
self._emit_debug_log_sync(
f"Tool called: {invocation['tool_name']} with {invocation['arguments']}",
__event_call__
)
# 方法 1: 调用 OpenWebUI 内部 Function API
result = await self._execute_openwebui_function(
function_name=invocation["tool_name"],
arguments=invocation["arguments"]
)
# 方法 2: 通过 __event_emitter__ 触发(需要测试)
# 方法 3: 直接实现工具逻辑
return {
"textResultForLlm": str(result),
"resultType": "success",
"error": None,
"toolTelemetry": {}
}
except asyncio.TimeoutError:
return {
"textResultForLlm": "Tool execution timed out.",
"resultType": "failure",
"error": "timeout",
"toolTelemetry": {}
}
except Exception as e:
self._emit_debug_log_sync(
f"Tool error: {e}",
__event_call__
)
return {
"textResultForLlm": f"Tool execution failed: {str(e)}",
"resultType": "failure",
"error": str(e),
"toolTelemetry": {}
}
return handler
```
#### 4. 集成到 pipe() 方法
```python
async def pipe(
self,
body: dict,
__metadata__: Optional[dict] = None,
__event_emitter__=None,
__event_call__=None,
) -> Union[str, AsyncGenerator]:
# ... 现有代码 ...
# ✅ 提取并转换 tools
copilot_tools = []
if self.valves.ENABLE_TOOLS and body.get("tools"):
copilot_tools = self._convert_openwebui_tools_to_copilot(
body["tools"],
__event_call__
)
await self._emit_debug_log(
f"Enabled {len(copilot_tools)} tools",
__event_call__
)
# ✅ 传递给 SessionConfig
session_config = SessionConfig(
session_id=chat_id if chat_id else None,
model=real_model_id,
streaming=body.get("stream", False),
tools=copilot_tools, # ✅ 关键
infinite_sessions=infinite_session_config,
)
session = await client.create_session(config=session_config)
# ...
```
#### 5. 处理 Tool 调用事件
```python
def stream_response(...):
def handler(event):
event_type = str(event.type)
# ✅ Tool 调用开始
if "tool_invocation_started" in event_type:
tool_name = get_event_data(event, "tool_name", "")
yield f"\n🔧 **Calling tool**: `{tool_name}`\n"
# ✅ Tool 调用完成
elif "tool_invocation_completed" in event_type:
tool_name = get_event_data(event, "tool_name", "")
result = get_event_data(event, "result", "")
yield f"\n✅ **Tool result**: {result}\n"
# ✅ Tool 调用失败
elif "tool_invocation_failed" in event_type:
tool_name = get_event_data(event, "tool_name", "")
error = get_event_data(event, "error", "")
yield f"\n❌ **Tool failed**: `{tool_name}` - {error}\n"
```
---
### 方案 B自定义 Tool 实现
#### Valves 配置
```python
class Valves(BaseModel):
CUSTOM_TOOLS: str = Field(
default="[]",
description="Custom tools JSON: [{name, description, parameters, implementation}]"
)
```
#### 工具定义示例
```json
[
{
"name": "calculate",
"description": "Perform mathematical calculations",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Math expression, e.g., '2 + 2 * 3'"
}
},
"required": ["expression"]
},
"implementation": "eval" // 或指定 Python 函数名
}
]
```
---
## 🧪 测试方案
### 1. 测试 Tool 定义
```python
# 在 OpenWebUI 中创建一个简单的 Function:
# Name: get_time
# Description: Get current time
# Parameters: {"type": "object", "properties": {}}
# 测试对话:
# User: "What time is it?"
# Expected: Copilot 调用 get_time tool返回当前时间
```
### 2. 测试 Tool 调用链
```python
# User: "Search for Python tutorials and summarize the top 3 results"
# Expected Flow:
# 1. Copilot calls search_web(query="Python tutorials")
# 2. Copilot receives search results
# 3. Copilot summarizes top 3
# 4. Returns final answer
```
### 3. 测试错误处理
```python
# User: "Call a non-existent tool"
# Expected: 返回 "Tool not supported" error
```
---
## 📊 事件监听
Tool 相关事件类型:
- `tool_invocation_started` - Tool 调用开始
- `tool_invocation_completed` - Tool 完成
- `tool_invocation_failed` - Tool 失败
- `tool_parameter_validation_failed` - 参数验证失败
---
## ⚠️ 注意事项
### 1. 安全性
- ✅ 验证 tool parameters
- ✅ 限制执行超时
- ✅ 不暴露详细错误信息给 LLM
- ❌ 禁止执行危险命令(如 `rm -rf`
### 2. 性能
- ⏱️ 设置合理的 timeout
- 🔄 考虑异步执行长时间运行的 tool
- 📊 记录 tool 执行时间toolTelemetry
### 3. 调试
- 🐛 在 DEBUG 模式下记录所有 tool 调用
- 📝 记录 arguments 和 results
- 🔍 使用前端 console 显示 tool 流程
---
## 🔗 参考资源
- [GitHub Copilot SDK 官方文档](https://github.com/github/copilot-sdk)
- [OpenWebUI Function API](https://docs.openwebui.com/features/plugin-system)
- [JSON Schema 规范](https://json-schema.org/)
---
## 📝 实现清单
- [ ] 添加 ENABLE_TOOLS Valve
- [ ] 实现 _convert_openwebui_tools_to_copilot()
- [ ] 实现 _create_tool_handler()
- [ ] 修改 SessionConfig 传递 tools
- [ ] 处理 tool 事件流
- [ ] 添加调试日志
- [ ] 测试基础 tool 调用
- [ ] 测试错误处理
- [ ] 更新文档和 README
- [ ] 同步中文版本
---
**作者:** Fu-Jie
**版本:** v1.0
**日期:** 2026-01-26

View File

@@ -0,0 +1,835 @@
# GitHub Copilot SDK Integration Workflow
**Author:** Fu-Jie
**Version:** 0.2.3
**Last Updated:** 2026-01-27
---
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Request Processing Flow](#request-processing-flow)
3. [Session Management](#session-management)
4. [Streaming Response Handling](#streaming-response-handling)
5. [Event Processing Mechanism](#event-processing-mechanism)
6. [Tool Execution Flow](#tool-execution-flow)
7. [System Prompt Extraction](#system-prompt-extraction)
8. [Configuration Parameters](#configuration-parameters)
9. [Key Functions Reference](#key-functions-reference)
---
## Architecture Overview
### Component Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ OpenWebUI │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Pipe Interface (Entry Point) │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ _pipe_impl (Main Logic) │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ 1. Environment Setup (_setup_env) │ │ │
│ │ │ 2. Model Selection (request_model parsing) │ │ │
│ │ │ 3. Chat Context Extraction │ │ │
│ │ │ 4. System Prompt Extraction │ │ │
│ │ │ 5. Session Management (create/resume) │ │ │
│ │ │ 6. Streaming/Non-streaming Response │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ GitHub Copilot Client │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ • CopilotClient (SDK instance) │ │ │
│ │ │ • Session (conversation context) │ │ │
│ │ │ • Event Stream (async events) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
└────────────────────────┼─────────────────────────────────────┘
┌──────────────────────┐
│ Copilot CLI Process │
│ (Backend Agent) │
└──────────────────────┘
```
### Key Components
1. **Pipe Interface**: OpenWebUI's standard entry point
2. **Environment Manager**: CLI setup, token validation, environment variables
3. **Session Manager**: Persistent conversation state with automatic compaction
4. **Event Processor**: Asynchronous streaming event handler
5. **Tool System**: Custom tool registration and execution
6. **Debug Logger**: Frontend console logging for troubleshooting
---
## Request Processing Flow
### Complete Request Lifecycle
```mermaid
graph TD
A[OpenWebUI Request] --> B[pipe Entry Point]
B --> C[_pipe_impl]
C --> D{Setup Environment}
D --> E[Parse Model ID]
E --> F[Extract Chat Context]
F --> G[Extract System Prompt]
G --> H{Session Exists?}
H -->|Yes| I[Resume Session]
H -->|No| J[Create New Session]
I --> K[Initialize Tools]
J --> K
K --> L[Process Images]
L --> M{Streaming Mode?}
M -->|Yes| N[stream_response]
M -->|No| O[send_and_wait]
N --> P[Async Event Stream]
O --> Q[Direct Response]
P --> R[Return to OpenWebUI]
Q --> R
```
### Step-by-Step Breakdown
#### 1. Environment Setup (`_setup_env`)
```python
def _setup_env(self, __event_call__=None):
"""
Priority:
1. Check VALVES.CLI_PATH
2. Search system PATH
3. Auto-install via curl (if not found)
4. Set GH_TOKEN environment variables
"""
```
**Actions:**
- Locate Copilot CLI binary
- Set `COPILOT_CLI_PATH` environment variable
- Configure `GH_TOKEN` for authentication
- Apply custom environment variables
#### 2. Model Selection
```python
# Input: body["model"] = "copilotsdk-claude-sonnet-4.5"
request_model = body.get("model", "")
if request_model.startswith(f"{self.id}-"):
real_model_id = request_model[len(f"{self.id}-"):] # "claude-sonnet-4.5"
```
#### 3. Chat Context Extraction (`_get_chat_context`)
```python
# Priority order for chat_id:
# 1. __metadata__ (most reliable)
# 2. body["chat_id"]
# 3. body["metadata"]["chat_id"]
chat_ctx = self._get_chat_context(body, __metadata__, __event_call__)
chat_id = chat_ctx.get("chat_id")
```
#### 4. System Prompt Extraction (`_extract_system_prompt`)
Multi-source fallback strategy:
1. `metadata.model.params.system`
2. Model database lookup (by model_id)
3. `body.params.system`
4. Messages with `role="system"`
#### 5. Session Creation/Resumption
**New Session:**
```python
session_config = SessionConfig(
session_id=chat_id,
model=real_model_id,
streaming=is_streaming,
tools=custom_tools,
system_message={"mode": "append", "content": system_prompt_content},
infinite_sessions=InfiniteSessionConfig(
enabled=True,
background_compaction_threshold=0.8,
buffer_exhaustion_threshold=0.95
)
)
session = await client.create_session(config=session_config)
```
**Resume Session:**
```python
try:
session = await client.resume_session(chat_id)
# Session state preserved: history, tools, workspace
except Exception:
# Fallback to creating new session
```
---
## Session Management
### Infinite Sessions Architecture
```
┌─────────────────────────────────────────────────────────┐
│ Session Lifecycle │
│ │
│ ┌──────────┐ create ┌──────────┐ resume ┌───────┴───┐
│ │ Chat ID │─────────▶ │ Session │ ◀────────│ OpenWebUI │
│ └──────────┘ │ State │ └───────────┘
│ └─────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Context Window Management │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Messages [user, assistant, tool_results...] │ │ │
│ │ │ Token Usage: ████████████░░░░ (80%) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Threshold Reached (0.8) │ │ │
│ │ │ → Background Compaction Triggered │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Compacted Summary + Recent Messages │ │ │
│ │ │ Token Usage: ██████░░░░░░░░░░░ (40%) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### Configuration Parameters
```python
InfiniteSessionConfig(
enabled=True, # Enable infinite sessions
background_compaction_threshold=0.8, # Start compaction at 80% token usage
buffer_exhaustion_threshold=0.95 # Emergency threshold at 95%
)
```
**Behavior:**
- **< 80%**: Normal operation, no compaction
- **80-95%**: Background compaction (summarize older messages)
- **> 95%**: Force compaction before next request
---
## Streaming Response Handling
### Event-Driven Architecture
```python
async def stream_response(
self, client, session, send_payload, init_message: str = "", __event_call__=None
) -> AsyncGenerator:
"""
Asynchronous event processing with queue-based buffering.
Flow:
1. Start async send task
2. Register event handler
3. Process events via queue
4. Yield chunks to OpenWebUI
5. Clean up resources
"""
```
### Event Processing Pipeline
```
┌────────────────────────────────────────────────────────────┐
│ Copilot SDK Event Stream │
└────────────────────┬───────────────────────────────────────┘
┌────────────────────────┐
│ Event Handler │
│ (Sync Callback) │
└────────┬───────────────┘
┌────────────────────────┐
│ Async Queue │
│ (Thread-safe) │
└────────┬───────────────┘
┌────────────────────────┐
│ Consumer Loop │
│ (async for) │
└────────┬───────────────┘
┌────────────────────────┐
│ yield to OpenWebUI │
└────────────────────────┘
```
### State Management During Streaming
```python
state = {
"thinking_started": False, # <think> tags opened
"content_sent": False # Main content has started
}
active_tools = {} # Track concurrent tool executions
```
**State Transitions:**
1. `reasoning_delta` arrives → `thinking_started = True` → Output: `<think>\n{reasoning}`
2. `message_delta` arrives → Close `</think>` if open → `content_sent = True` → Output: `{content}`
3. `tool.execution_start` → Output tool indicator (inside/outside `<think>`)
4. `session.complete` → Finalize stream
---
## Event Processing Mechanism
### Event Type Reference
Following official SDK patterns (from `copilot.SessionEventType`):
| Event Type | Description | Key Data Fields | Handler Action |
|-----------|-------------|-----------------|----------------|
| `assistant.message_delta` | Main content streaming | `delta_content` | Yield text chunk |
| `assistant.reasoning_delta` | Chain-of-thought | `delta_content` | Wrap in `<think>` tags |
| `tool.execution_start` | Tool call initiated | `name`, `tool_call_id` | Display tool indicator |
| `tool.execution_complete` | Tool finished | `result.content` | Show completion status |
| `session.compaction_start` | Context compaction begins | - | Log debug info |
| `session.compaction_complete` | Compaction done | - | Log debug info |
| `session.error` | Error occurred | `error`, `message` | Emit error notification |
### Event Handler Implementation
```python
def handler(event):
"""Process streaming events following official SDK patterns."""
event_type = get_event_type(event) # Handle enum/string types
# Extract data using safe_get_data_attr (handles dict/object)
if event_type == "assistant.message_delta":
delta = safe_get_data_attr(event, "delta_content")
if delta:
queue.put_nowait(delta) # Thread-safe enqueue
```
### Official SDK Pattern Compliance
```python
def safe_get_data_attr(event, attr: str, default=None):
"""
Official pattern: event.data.delta_content
Handles both dict and object access patterns.
"""
if not hasattr(event, "data") or event.data is None:
return default
data = event.data
# Dict access (JSON-like)
if isinstance(data, dict):
return data.get(attr, default)
# Object attribute (Python SDK)
return getattr(data, attr, default)
```
---
## Tool Execution Flow
### Tool Registration
```python
# 1. Define tool at module level
@define_tool(description="Generate a random integer within a specified range.")
async def generate_random_number(params: RandomNumberParams) -> str:
number = random.randint(params.min, params.max)
return f"Generated random number: {number}"
# 2. Register in _initialize_custom_tools
def _initialize_custom_tools(self):
if not self.valves.ENABLE_TOOLS:
return []
all_tools = {
"generate_random_number": generate_random_number,
}
# Filter based on AVAILABLE_TOOLS valve
if self.valves.AVAILABLE_TOOLS == "all":
return list(all_tools.values())
enabled = [t.strip() for t in self.valves.AVAILABLE_TOOLS.split(",")]
return [all_tools[name] for name in enabled if name in all_tools]
```
### Tool Execution Timeline
```
User Message: "Generate a random number between 1 and 100"
Model Decision: Use tool `generate_random_number`
Event: tool.execution_start
│ → Display: "🔧 Running Tool: generate_random_number"
Tool Function Execution (async)
Event: tool.execution_complete
│ → Result: "Generated random number: 42"
│ → Display: "✅ Tool Completed: 42"
Model generates response using tool result
Event: assistant.message_delta
│ → "I generated the number 42 for you."
Stream Complete
```
### Visual Indicators
**Before Content:**
```markdown
<think>
Running Tool: generate_random_number...
Tool `generate_random_number` Completed. Result: 42
</think>
I generated the number 42 for you.
```
**After Content Started:**
```markdown
The number is
> 🔧 **Running Tool**: `generate_random_number`
> ✅ **Tool Completed**: 42
actually 42.
```
---
## System Prompt Extraction
### Multi-Source Priority System
```python
async def _extract_system_prompt(self, body, messages, request_model, real_model_id):
"""
Priority order:
1. metadata.model.params.system (highest)
2. Model database lookup
3. body.params.system
4. messages[role="system"] (fallback)
"""
```
### Source 1: Metadata Model Params
```python
# OpenWebUI injects model configuration
metadata = body.get("metadata", {})
meta_model = metadata.get("model", {})
meta_params = meta_model.get("params", {})
system_prompt = meta_params.get("system") # Priority 1
```
### Source 2: Model Database
```python
from open_webui.models.models import Models
# Try multiple model ID variations
model_ids_to_try = [
request_model, # "copilotsdk-claude-sonnet-4.5"
request_model.removeprefix(...), # "claude-sonnet-4.5"
real_model_id, # From valves
]
for mid in model_ids_to_try:
model_record = Models.get_model_by_id(mid)
if model_record and hasattr(model_record, "params"):
system_prompt = model_record.params.get("system")
if system_prompt:
break
```
### Source 3: Body Params
```python
body_params = body.get("params", {})
system_prompt = body_params.get("system")
```
### Source 4: System Message
```python
for msg in messages:
if msg.get("role") == "system":
system_prompt = self._extract_text_from_content(msg.get("content"))
break
```
### Configuration in SessionConfig
```python
system_message_config = {
"mode": "append", # Append to conversation context
"content": system_prompt_content
}
session_config = SessionConfig(
system_message=system_message_config,
# ... other params
)
```
---
## Configuration Parameters
### Valve Definitions
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `GH_TOKEN` | str | `""` | GitHub Fine-grained Token (requires 'Copilot Requests' permission) |
| `MODEL_ID` | str | `"claude-sonnet-4.5"` | Default model when dynamic fetching fails |
| `CLI_PATH` | str | `"/usr/local/bin/copilot"` | Path to Copilot CLI binary |
| `DEBUG` | bool | `False` | Enable frontend console debug logging |
| `LOG_LEVEL` | str | `"error"` | CLI log level: none, error, warning, info, debug, all |
| `SHOW_THINKING` | bool | `True` | Display model reasoning in `<think>` tags |
| `SHOW_WORKSPACE_INFO` | bool | `True` | Show session workspace path in debug mode |
| `EXCLUDE_KEYWORDS` | str | `""` | Comma-separated keywords to exclude models |
| `WORKSPACE_DIR` | str | `""` | Restricted workspace directory (empty = process cwd) |
| `INFINITE_SESSION` | bool | `True` | Enable automatic context compaction |
| `COMPACTION_THRESHOLD` | float | `0.8` | Background compaction at 80% token usage |
| `BUFFER_THRESHOLD` | float | `0.95` | Emergency threshold at 95% |
| `TIMEOUT` | int | `300` | Stream chunk timeout (seconds) |
| `CUSTOM_ENV_VARS` | str | `""` | JSON string of custom environment variables |
| `ENABLE_TOOLS` | bool | `False` | Enable custom tool system |
| `AVAILABLE_TOOLS` | str | `"all"` | Available tools: "all" or comma-separated list |
### Environment Variables
```bash
# Set by _setup_env
export COPILOT_CLI_PATH="/usr/local/bin/copilot"
export GH_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
export GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
# Custom variables (from CUSTOM_ENV_VARS valve)
export CUSTOM_VAR_1="value1"
export CUSTOM_VAR_2="value2"
```
---
## Key Functions Reference
### Entry Points
#### `pipe(body, __metadata__, __event_emitter__, __event_call__)`
- **Purpose**: OpenWebUI stable entry point
- **Returns**: Delegates to `_pipe_impl`
#### `_pipe_impl(body, __metadata__, __event_emitter__, __event_call__)`
- **Purpose**: Main request processing logic
- **Flow**: Setup → Extract → Session → Response
- **Returns**: `str` (non-streaming) or `AsyncGenerator` (streaming)
#### `pipes()`
- **Purpose**: Dynamic model list fetching
- **Returns**: List of available models with multiplier info
- **Caching**: Uses `_model_cache` to avoid repeated API calls
### Session Management
#### `_build_session_config(chat_id, real_model_id, custom_tools, system_prompt_content, is_streaming)`
- **Purpose**: Construct SessionConfig object
- **Returns**: `SessionConfig` with infinite sessions and tools
#### `_get_chat_context(body, __metadata__, __event_call__)`
- **Purpose**: Extract chat_id with priority fallback
- **Returns**: `{"chat_id": str}`
### Streaming
#### `stream_response(client, session, send_payload, init_message, __event_call__)`
- **Purpose**: Async streaming event processor
- **Yields**: Text chunks to OpenWebUI
- **Resources**: Auto-cleanup client and session
#### `handler(event)`
- **Purpose**: Sync event callback (inside `stream_response`)
- **Action**: Parse event → Enqueue chunks → Update state
### Helpers
#### `_emit_debug_log(message, __event_call__)`
- **Purpose**: Send debug logs to frontend console
- **Condition**: Only when `DEBUG=True`
#### `_setup_env(__event_call__)`
- **Purpose**: Locate CLI, set environment variables
- **Side Effects**: Modifies `os.environ`
#### `_extract_system_prompt(body, messages, request_model, real_model_id, __event_call__)`
- **Purpose**: Multi-source system prompt extraction
- **Returns**: `(system_prompt_content, source_name)`
#### `_process_images(messages, __event_call__)`
- **Purpose**: Extract text and images from multimodal messages
- **Returns**: `(text_content, attachments_list)`
#### `_initialize_custom_tools()`
- **Purpose**: Register and filter custom tools
- **Returns**: List of tool functions
### Utility Functions
#### `get_event_type(event) -> str`
- **Purpose**: Extract event type string from enum/string
- **Handles**: `SessionEventType` enum → `.value` extraction
#### `safe_get_data_attr(event, attr: str, default=None)`
- **Purpose**: Safe attribute extraction from event.data
- **Handles**: Both dict access and object attribute access
---
## Troubleshooting Guide
### Enable Debug Mode
```python
# In OpenWebUI Valves UI:
DEBUG = True
SHOW_WORKSPACE_INFO = True
LOG_LEVEL = "debug"
```
### Debug Output Location
**Frontend Console:**
```javascript
// Open browser DevTools (F12)
// Look for logs with prefix: [Copilot Pipe]
console.debug("[Copilot Pipe] Extracted ChatID: abc123 (Source: __metadata__)")
```
**Backend Logs:**
```python
# Python logging output
logger.debug(f"[Copilot Pipe] Session resumed: {chat_id}")
```
### Common Issues
#### 1. Session Not Resuming
**Symptom:** New session created every request
**Causes:**
- `chat_id` not extracted correctly
- Session expired on Copilot side
- `INFINITE_SESSION=False` (sessions not persistent)
**Solution:**
```python
# Check debug logs for:
"Extracted ChatID: <id> (Source: ...)"
"Session <id> not found (...), creating new."
```
#### 2. System Prompt Not Applied
**Symptom:** Model ignores configured system prompt
**Causes:**
- Not found in any of 4 sources
- Session resumed (system prompt only set on creation)
**Solution:**
```python
# Check debug logs for:
"Extracted system prompt from <source> (length: X)"
"Configured system message (mode: append)"
```
#### 3. Tools Not Available
**Symptom:** Model can't use custom tools
**Causes:**
- `ENABLE_TOOLS=False`
- Tool not registered in `_initialize_custom_tools`
- Wrong `AVAILABLE_TOOLS` filter
**Solution:**
```python
# Check debug logs for:
"Enabled X custom tools: ['tool1', 'tool2']"
```
---
## Performance Optimization
### Model List Caching
```python
# First request: Fetch from API
models = await client.list_models()
self._model_cache = [...] # Cache result
# Subsequent requests: Use cache
if self._model_cache:
return self._model_cache
```
### Session Persistence
**Impact:** Eliminates redundant model initialization on every request
```python
# Without session:
# Each request: Initialize model → Load context → Generate → Discard
# With session (chat_id):
# First request: Initialize model → Load context → Generate → Save
# Later: Resume → Generate (instant)
```
### Streaming vs Non-streaming
**Streaming:**
- Lower perceived latency (first token faster)
- Better UX for long responses
- Resource cleanup via generator exit
**Non-streaming:**
- Simpler error handling
- Atomic response (no partial output)
- Use for short responses
---
## Security Considerations
### Token Protection
```python
# ❌ Never log tokens
logger.debug(f"Token: {self.valves.GH_TOKEN}") # DON'T DO THIS
# ✅ Mask sensitive data
logger.debug(f"Token configured: {'*' * 10}")
```
### Workspace Isolation
```python
# Set WORKSPACE_DIR to restrict file access
WORKSPACE_DIR = "/safe/sandbox/path"
# Copilot CLI respects this directory
client_config["cwd"] = WORKSPACE_DIR
```
### Input Validation
```python
# Validate chat_id format
if chat_id and not re.match(r'^[a-zA-Z0-9_-]+$', chat_id):
logger.warning(f"Invalid chat_id format: {chat_id}")
chat_id = None
```
---
## Future Enhancements
### Planned Features
1. **Multi-Session Management**: Support multiple parallel sessions per user
2. **Session Analytics**: Track token usage, compaction frequency
3. **Tool Result Caching**: Avoid redundant tool calls
4. **Custom Event Filters**: User-configurable event handling
5. **Workspace Templates**: Pre-configured workspace environments
6. **Streaming Abort**: Graceful cancellation of long-running requests
### API Evolution
Monitoring Copilot SDK updates for:
- New event types (e.g., `assistant.function_call`)
- Enhanced tool capabilities
- Improved session serialization
---
## References
- [GitHub Copilot SDK Documentation](https://github.com/github/copilot-sdk)
- [OpenWebUI Pipe Development](https://docs.openwebui.com/)
- [Awesome OpenWebUI Project](https://github.com/Fu-Jie/awesome-openwebui)
---
**License:** MIT
**Maintainer:** Fu-Jie ([@Fu-Jie](https://github.com/Fu-Jie))

View File

@@ -0,0 +1,835 @@
# GitHub Copilot SDK 集成工作流程
**作者:** Fu-Jie
**版本:** 0.2.3
**最后更新:** 2026-01-27
---
## 目录
1. [架构概览](#架构概览)
2. [请求处理流程](#请求处理流程)
3. [会话管理](#会话管理)
4. [流式响应处理](#流式响应处理)
5. [事件处理机制](#事件处理机制)
6. [工具执行流程](#工具执行流程)
7. [系统提示词提取](#系统提示词提取)
8. [配置参数](#配置参数)
9. [核心函数参考](#核心函数参考)
---
## 架构概览
### 组件图
```
┌─────────────────────────────────────────────────────────────┐
│ OpenWebUI │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Pipe 接口 (入口点) │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ _pipe_impl (主逻辑) │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ 1. 环境设置 (_setup_env) │ │ │
│ │ │ 2. 模型选择 (request_model 解析) │ │ │
│ │ │ 3. 聊天上下文提取 │ │ │
│ │ │ 4. 系统提示词提取 │ │ │
│ │ │ 5. 会话管理 (创建/恢复) │ │ │
│ │ │ 6. 流式/非流式响应 │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ GitHub Copilot 客户端 │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ • CopilotClient (SDK 实例) │ │ │
│ │ │ • Session (对话上下文) │ │ │
│ │ │ • Event Stream (异步事件流) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
└────────────────────────┼─────────────────────────────────────┘
┌──────────────────────┐
│ Copilot CLI 进程 │
│ (后端代理) │
└──────────────────────┘
```
### 核心组件
1. **Pipe 接口**OpenWebUI 的标准入口点
2. **环境管理器**CLI 设置、令牌验证、环境变量
3. **会话管理器**:持久化对话状态,自动压缩
4. **事件处理器**:异步流式事件处理器
5. **工具系统**:自定义工具注册和执行
6. **调试日志器**:前端控制台日志,用于故障排除
---
## 请求处理流程
### 完整请求生命周期
```mermaid
graph TD
A[OpenWebUI 请求] --> B[pipe 入口点]
B --> C[_pipe_impl]
C --> D{设置环境}
D --> E[解析模型 ID]
E --> F[提取聊天上下文]
F --> G[提取系统提示词]
G --> H{会话存在?}
H -->|是| I[恢复会话]
H -->|否| J[创建新会话]
I --> K[初始化工具]
J --> K
K --> L[处理图片]
L --> M{流式模式?}
M -->|是| N[stream_response]
M -->|否| O[send_and_wait]
N --> P[异步事件流]
O --> Q[直接响应]
P --> R[返回到 OpenWebUI]
Q --> R
```
### 逐步分解
#### 1. 环境设置 (`_setup_env`)
```python
def _setup_env(self, __event_call__=None):
"""
优先级:
1. 检查 VALVES.CLI_PATH
2. 搜索系统 PATH
3. 自动通过 curl 安装(如果未找到)
4. 设置 GH_TOKEN 环境变量
"""
```
**操作:**
- 定位 Copilot CLI 二进制文件
- 设置 `COPILOT_CLI_PATH` 环境变量
- 配置 `GH_TOKEN` 进行身份验证
- 应用自定义环境变量
#### 2. 模型选择
```python
# 输入body["model"] = "copilotsdk-claude-sonnet-4.5"
request_model = body.get("model", "")
if request_model.startswith(f"{self.id}-"):
real_model_id = request_model[len(f"{self.id}-"):] # "claude-sonnet-4.5"
```
#### 3. 聊天上下文提取 (`_get_chat_context`)
```python
# chat_id 的优先级顺序:
# 1. __metadata__最可靠
# 2. body["chat_id"]
# 3. body["metadata"]["chat_id"]
chat_ctx = self._get_chat_context(body, __metadata__, __event_call__)
chat_id = chat_ctx.get("chat_id")
```
#### 4. 系统提示词提取 (`_extract_system_prompt`)
多源回退策略:
1. `metadata.model.params.system`
2. 模型数据库查询(按 model_id
3. `body.params.system`
4. 包含 `role="system"` 的消息
#### 5. 会话创建/恢复
**新会话:**
```python
session_config = SessionConfig(
session_id=chat_id,
model=real_model_id,
streaming=is_streaming,
tools=custom_tools,
system_message={"mode": "append", "content": system_prompt_content},
infinite_sessions=InfiniteSessionConfig(
enabled=True,
background_compaction_threshold=0.8,
buffer_exhaustion_threshold=0.95
)
)
session = await client.create_session(config=session_config)
```
**恢复会话:**
```python
try:
session = await client.resume_session(chat_id)
# 会话状态保留:历史、工具、工作区
except Exception:
# 回退到创建新会话
```
---
## 会话管理
### 无限会话架构
```
┌─────────────────────────────────────────────────────────┐
│ 会话生命周期 │
│ │
│ ┌──────────┐ 创建 ┌──────────┐ 恢复 ┌───────────┐ │
│ │ Chat ID │─────▶ │ Session │ ◀────────│ OpenWebUI │ │
│ └──────────┘ │ State │ └───────────┘ │
│ └─────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 上下文窗口管理 │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ 消息 [user, assistant, tool_results...] │ │ │
│ │ │ Token 使用率: ████████████░░░░ (80%) │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ 达到阈值 (0.8) │ │ │
│ │ │ → 后台压缩触发 │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ 压缩摘要 + 最近消息 │ │ │
│ │ │ Token 使用率: ██████░░░░░░░░░░░ (40%) │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 配置参数
```python
InfiniteSessionConfig(
enabled=True, # 启用无限会话
background_compaction_threshold=0.8, # 在 80% token 使用率时开始压缩
buffer_exhaustion_threshold=0.95 # 95% 紧急阈值
)
```
**行为:**
- **< 80%**:正常操作,无压缩
- **80-95%**:后台压缩(总结旧消息)
- **> 95%**:在下一个请求前强制压缩
---
## 流式响应处理
### 事件驱动架构
```python
async def stream_response(
self, client, session, send_payload, init_message: str = "", __event_call__=None
) -> AsyncGenerator:
"""
使用基于队列的缓冲进行异步事件处理。
流程:
1. 启动异步发送任务
2. 注册事件处理器
3. 通过队列处理事件
4. 向 OpenWebUI 产出块
5. 清理资源
"""
```
### 事件处理管道
```
┌────────────────────────────────────────────────────────────┐
│ Copilot SDK 事件流 │
└────────────────────┬───────────────────────────────────────┘
┌────────────────────────┐
│ 事件处理器 │
│ (同步回调) │
└────────┬───────────────┘
┌────────────────────────┐
│ 异步队列 │
│ (线程安全) │
└────────┬───────────────┘
┌────────────────────────┐
│ 消费者循环 │
│ (async for) │
└────────┬───────────────┘
┌────────────────────────┐
│ yield 到 OpenWebUI │
└────────────────────────┘
```
### 流式传输期间的状态管理
```python
state = {
"thinking_started": False, # <think> 标签已打开
"content_sent": False # 主内容已开始
}
active_tools = {} # 跟踪并发工具执行
```
**状态转换:**
1. `reasoning_delta` 到达 → `thinking_started = True` → 输出:`<think>\n{reasoning}`
2. `message_delta` 到达 → 如果打开则关闭 `</think>``content_sent = True` → 输出:`{content}`
3. `tool.execution_start` → 输出工具指示器(在 `<think>` 内部/外部)
4. `session.complete` → 完成流
---
## 事件处理机制
### 事件类型参考
遵循官方 SDK 模式(来自 `copilot.SessionEventType`
| 事件类型 | 描述 | 关键数据字段 | 处理器操作 |
|---------|------|-------------|-----------|
| `assistant.message_delta` | 主内容流式传输 | `delta_content` | 产出文本块 |
| `assistant.reasoning_delta` | 思维链 | `delta_content` | 用 `<think>` 标签包装 |
| `tool.execution_start` | 工具调用启动 | `name`, `tool_call_id` | 显示工具指示器 |
| `tool.execution_complete` | 工具完成 | `result.content` | 显示完成状态 |
| `session.compaction_start` | 上下文压缩开始 | - | 记录调试信息 |
| `session.compaction_complete` | 压缩完成 | - | 记录调试信息 |
| `session.error` | 发生错误 | `error`, `message` | 发出错误通知 |
### 事件处理器实现
```python
def handler(event):
"""遵循官方 SDK 模式处理流式事件。"""
event_type = get_event_type(event) # 处理枚举/字符串类型
# 使用 safe_get_data_attr 提取数据(处理 dict/object
if event_type == "assistant.message_delta":
delta = safe_get_data_attr(event, "delta_content")
if delta:
queue.put_nowait(delta) # 线程安全入队
```
### 官方 SDK 模式合规性
```python
def safe_get_data_attr(event, attr: str, default=None):
"""
官方模式event.data.delta_content
处理 dict 和对象访问模式。
"""
if not hasattr(event, "data") or event.data is None:
return default
data = event.data
# Dict 访问(类似 JSON
if isinstance(data, dict):
return data.get(attr, default)
# 对象属性Python SDK
return getattr(data, attr, default)
```
---
## 工具执行流程
### 工具注册
```python
# 1. 在模块级别定义工具
@define_tool(description="在指定范围内生成随机整数。")
async def generate_random_number(params: RandomNumberParams) -> str:
number = random.randint(params.min, params.max)
return f"生成的随机数: {number}"
# 2. 在 _initialize_custom_tools 中注册
def _initialize_custom_tools(self):
if not self.valves.ENABLE_TOOLS:
return []
all_tools = {
"generate_random_number": generate_random_number,
}
# 根据 AVAILABLE_TOOLS valve 过滤
if self.valves.AVAILABLE_TOOLS == "all":
return list(all_tools.values())
enabled = [t.strip() for t in self.valves.AVAILABLE_TOOLS.split(",")]
return [all_tools[name] for name in enabled if name in all_tools]
```
### 工具执行时间线
```
用户消息:生成一个 1 到 100 之间的随机数
模型决策:使用工具 `generate_random_number`
事件tool.execution_start
│ → 显示:"🔧 运行工具generate_random_number"
工具函数执行(异步)
事件tool.execution_complete
│ → 结果:"生成的随机数42"
│ → 显示:"✅ 工具完成42"
模型使用工具结果生成响应
事件assistant.message_delta
│ → "我为你生成了数字 42。"
流完成
```
### 视觉指示器
**内容前:**
```markdown
<think>
运行工具generate_random_number...
工具 `generate_random_number` 完成。结果42
</think>
我为你生成了数字 42。
```
**内容开始后:**
```markdown
数字是
> 🔧 **运行工具**`generate_random_number`
> ✅ **工具完成**42
实际上是 42。
```
---
## 系统提示词提取
### 多源优先级系统
```python
async def _extract_system_prompt(self, body, messages, request_model, real_model_id):
"""
优先级顺序:
1. metadata.model.params.system最高
2. 模型数据库查询
3. body.params.system
4. messages[role="system"](回退)
"""
```
### 来源 1元数据模型参数
```python
# OpenWebUI 注入模型配置
metadata = body.get("metadata", {})
meta_model = metadata.get("model", {})
meta_params = meta_model.get("params", {})
system_prompt = meta_params.get("system") # 优先级 1
```
### 来源 2模型数据库
```python
from open_webui.models.models import Models
# 尝试多个模型 ID 变体
model_ids_to_try = [
request_model, # "copilotsdk-claude-sonnet-4.5"
request_model.removeprefix(...), # "claude-sonnet-4.5"
real_model_id, # 来自 valves
]
for mid in model_ids_to_try:
model_record = Models.get_model_by_id(mid)
if model_record and hasattr(model_record, "params"):
system_prompt = model_record.params.get("system")
if system_prompt:
break
```
### 来源 3Body 参数
```python
body_params = body.get("params", {})
system_prompt = body_params.get("system")
```
### 来源 4系统消息
```python
for msg in messages:
if msg.get("role") == "system":
system_prompt = self._extract_text_from_content(msg.get("content"))
break
```
### SessionConfig 中的配置
```python
system_message_config = {
"mode": "append", # 追加到对话上下文
"content": system_prompt_content
}
session_config = SessionConfig(
system_message=system_message_config,
# ... 其他参数
)
```
---
## 配置参数
### Valve 定义
| 参数 | 类型 | 默认值 | 描述 |
|-----|------|--------|------|
| `GH_TOKEN` | str | `""` | GitHub 精细化令牌(需要 'Copilot Requests' 权限) |
| `MODEL_ID` | str | `"claude-sonnet-4.5"` | 动态获取失败时的默认模型 |
| `CLI_PATH` | str | `"/usr/local/bin/copilot"` | Copilot CLI 二进制文件路径 |
| `DEBUG` | bool | `False` | 启用前端控制台调试日志 |
| `LOG_LEVEL` | str | `"error"` | CLI 日志级别none、error、warning、info、debug、all |
| `SHOW_THINKING` | bool | `True` | 在 `<think>` 标签中显示模型推理 |
| `SHOW_WORKSPACE_INFO` | bool | `True` | 在调试模式下显示会话工作区路径 |
| `EXCLUDE_KEYWORDS` | str | `""` | 逗号分隔的关键字,用于排除模型 |
| `WORKSPACE_DIR` | str | `""` | 限制的工作区目录(空 = 进程 cwd |
| `INFINITE_SESSION` | bool | `True` | 启用自动上下文压缩 |
| `COMPACTION_THRESHOLD` | float | `0.8` | 80% token 使用率时后台压缩 |
| `BUFFER_THRESHOLD` | float | `0.95` | 95% 紧急阈值 |
| `TIMEOUT` | int | `300` | 流块超时(秒) |
| `CUSTOM_ENV_VARS` | str | `""` | 自定义环境变量的 JSON 字符串 |
| `ENABLE_TOOLS` | bool | `False` | 启用自定义工具系统 |
| `AVAILABLE_TOOLS` | str | `"all"` | 可用工具:"all" 或逗号分隔列表 |
### 环境变量
```bash
# 由 _setup_env 设置
export COPILOT_CLI_PATH="/usr/local/bin/copilot"
export GH_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
export GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
# 自定义变量(来自 CUSTOM_ENV_VARS valve
export CUSTOM_VAR_1="value1"
export CUSTOM_VAR_2="value2"
```
---
## 核心函数参考
### 入口点
#### `pipe(body, __metadata__, __event_emitter__, __event_call__)`
- **目的**OpenWebUI 稳定入口点
- **返回**:委托给 `_pipe_impl`
#### `_pipe_impl(body, __metadata__, __event_emitter__, __event_call__)`
- **目的**:主请求处理逻辑
- **流程**:设置 → 提取 → 会话 → 响应
- **返回**`str`(非流式)或 `AsyncGenerator`(流式)
#### `pipes()`
- **目的**:动态模型列表获取
- **返回**:带有倍数信息的可用模型列表
- **缓存**:使用 `_model_cache` 避免重复 API 调用
### 会话管理
#### `_build_session_config(chat_id, real_model_id, custom_tools, system_prompt_content, is_streaming)`
- **目的**:构建 SessionConfig 对象
- **返回**:带有无限会话和工具的 `SessionConfig`
#### `_get_chat_context(body, __metadata__, __event_call__)`
- **目的**:使用优先级回退提取 chat_id
- **返回**`{"chat_id": str}`
### 流式传输
#### `stream_response(client, session, send_payload, init_message, __event_call__)`
- **目的**:异步流式事件处理器
- **产出**:文本块到 OpenWebUI
- **资源**:自动清理客户端和会话
#### `handler(event)`
- **目的**:同步事件回调(在 `stream_response` 内)
- **操作**:解析事件 → 入队块 → 更新状态
### 辅助函数
#### `_emit_debug_log(message, __event_call__)`
- **目的**:将调试日志发送到前端控制台
- **条件**:仅当 `DEBUG=True`
#### `_setup_env(__event_call__)`
- **目的**:定位 CLI设置环境变量
- **副作用**:修改 `os.environ`
#### `_extract_system_prompt(body, messages, request_model, real_model_id, __event_call__)`
- **目的**:多源系统提示词提取
- **返回**`(system_prompt_content, source_name)`
#### `_process_images(messages, __event_call__)`
- **目的**:从多模态消息中提取文本和图片
- **返回**`(text_content, attachments_list)`
#### `_initialize_custom_tools()`
- **目的**:注册和过滤自定义工具
- **返回**:工具函数列表
### 实用函数
#### `get_event_type(event) -> str`
- **目的**:从枚举/字符串提取事件类型字符串
- **处理**`SessionEventType` 枚举 → `.value` 提取
#### `safe_get_data_attr(event, attr: str, default=None)`
- **目的**:从 event.data 安全提取属性
- **处理**dict 访问和对象属性访问
---
## 故障排除指南
### 启用调试模式
```python
# 在 OpenWebUI Valves UI 中:
DEBUG = True
SHOW_WORKSPACE_INFO = True
LOG_LEVEL = "debug"
```
### 调试输出位置
**前端控制台:**
```javascript
// 打开浏览器开发工具 (F12)
// 查找前缀为 [Copilot Pipe] 的日志
console.debug("[Copilot Pipe] 提取的 ChatIDabc123来源__metadata__")
```
**后端日志:**
```python
# Python 日志输出
logger.debug(f"[Copilot Pipe] 会话已恢复:{chat_id}")
```
### 常见问题
#### 1. 会话未恢复
**症状**:每次请求都创建新会话
**原因**
- `chat_id` 提取不正确
- Copilot 端会话过期
- `INFINITE_SESSION=False`(会话不持久)
**解决方案**
```python
# 检查调试日志中的:
"提取的 ChatID<id>(来源:..."
"会话 <id> 未找到(...),正在创建新会话。"
```
#### 2. 系统提示词未应用
**症状**:模型忽略配置的系统提示词
**原因**
- 在 4 个来源中均未找到
- 会话已恢复(系统提示词仅在创建时设置)
**解决方案**
```python
# 检查调试日志中的:
"从 <source> 提取系统提示词长度X"
"配置系统消息模式append"
```
#### 3. 工具不可用
**症状**:模型无法使用自定义工具
**原因**
- `ENABLE_TOOLS=False`
- 工具未在 `_initialize_custom_tools` 中注册
- 错误的 `AVAILABLE_TOOLS` 过滤器
**解决方案**
```python
# 检查调试日志中的:
"已启用 X 个自定义工具:['tool1', 'tool2']"
```
---
## 性能优化
### 模型列表缓存
```python
# 第一次请求:从 API 获取
models = await client.list_models()
self._model_cache = [...] # 缓存结果
# 后续请求:使用缓存
if self._model_cache:
return self._model_cache
```
### 会话持久化
**影响**:消除每次请求的冗余模型初始化
```python
# 没有会话:
# 每次请求:初始化模型 → 加载上下文 → 生成 → 丢弃
# 有会话chat_id
# 第一次请求:初始化模型 → 加载上下文 → 生成 → 保存
# 后续:恢复 → 生成(即时)
```
### 流式 vs 非流式
**流式:**
- 降低感知延迟(首个 token 更快)
- 长响应的更好用户体验
- 通过生成器退出进行资源清理
**非流式:**
- 更简单的错误处理
- 原子响应(无部分输出)
- 用于短响应
---
## 安全考虑
### 令牌保护
```python
# ❌ 永远不要记录令牌
logger.debug(f"令牌:{self.valves.GH_TOKEN}") # 不要这样做
# ✅ 屏蔽敏感数据
logger.debug(f"令牌已配置:{'*' * 10}")
```
### 工作区隔离
```python
# 设置 WORKSPACE_DIR 以限制文件访问
WORKSPACE_DIR = "/safe/sandbox/path"
# Copilot CLI 遵守此目录
client_config["cwd"] = WORKSPACE_DIR
```
### 输入验证
```python
# 验证 chat_id 格式
if chat_id and not re.match(r'^[a-zA-Z0-9_-]+$', chat_id):
logger.warning(f"无效的 chat_id 格式:{chat_id}")
chat_id = None
```
---
## 未来增强
### 计划功能
1. **多会话管理**:支持每个用户的多个并行会话
2. **会话分析**:跟踪 token 使用率、压缩频率
3. **工具结果缓存**:避免冗余工具调用
4. **自定义事件过滤器**:用户可配置的事件处理
5. **工作区模板**:预配置的工作区环境
6. **流式中止**:优雅取消长时间运行的请求
### API 演进
监控 Copilot SDK 更新:
- 新事件类型(例如 `assistant.function_call`
- 增强的工具功能
- 改进的会话序列化
---
## 参考资料
- [GitHub Copilot SDK 文档](https://github.com/github/copilot-sdk)
- [OpenWebUI Pipe 开发](https://docs.openwebui.com/)
- [Awesome OpenWebUI 项目](https://github.com/Fu-Jie/awesome-openwebui)
---
**许可证**MIT
**维护者**Fu-Jie ([@Fu-Jie](https://github.com/Fu-Jie))

View File

@@ -0,0 +1,124 @@
import asyncio
import os
import json
import sys
from copilot import CopilotClient, define_tool
from copilot.types import SessionConfig
from pydantic import BaseModel, Field
# Define a simple tool for testing
class RandomNumberParams(BaseModel):
min: int = Field(description="Minimum value")
max: int = Field(description="Maximum value")
@define_tool(description="Generate a random integer within a range.")
async def generate_random_number(params: RandomNumberParams) -> str:
import random
return f"Result: {random.randint(params.min, params.max)}"
async def main():
print(f"Running tests with Python: {sys.executable}")
# 1. Setup Client
client = CopilotClient({"log_level": "error"})
await client.start()
try:
print("\n=== Test 1: Session Creation & Formatting Injection ===")
# Use gpt-4o or similar capable model
model_id = "gpt-5-mini"
system_message_config = {
"mode": "append",
"content": "You are a test assistant. Always start your response with 'TEST_PREFIX: '.",
}
session_config = SessionConfig(
model=model_id,
system_message=system_message_config,
tools=[generate_random_number],
)
session = await client.create_session(config=session_config)
session_id = session.session_id
print(f"Session Created: {session_id}")
# Test 1.1: Check system prompt effect
resp = await session.send_and_wait(
{"prompt": "Say hello.", "mode": "immediate"}
)
content = resp.data.content
print(f"Response 1: {content}")
if "TEST_PREFIX:" in content:
print("✅ System prompt injection active.")
else:
print("⚠️ System prompt injection NOT detected.")
print("\n=== Test 2: Tool Execution ===")
# Test Tool Usage
prompt_with_tool = (
"Generate a random number between 100 and 200 using the tool."
)
print(f"Sending: {prompt_with_tool}")
# We need to listen to events to verify tool execution,
# but send_and_wait handles it internally and returns the final answer.
# We check if the final answer mentions the result.
resp_tool = await session.send_and_wait(
{"prompt": prompt_with_tool, "mode": "immediate"}
)
tool_content = resp_tool.data.content
print(f"Response 2: {tool_content}")
if "Result:" in tool_content or any(char.isdigit() for char in tool_content):
print("✅ Tool likely executed (numbers found).")
else:
print("⚠️ Tool execution uncertain.")
print("\n=== Test 3: Context Retention (Memory) ===")
# Store a fact
await session.send_and_wait(
{"prompt": "My secret code is 'BLUE-42'. Remember it.", "mode": "immediate"}
)
print("Fact sent.")
# Retrieve it
resp_mem = await session.send_and_wait(
{"prompt": "What is my secret code?", "mode": "immediate"}
)
mem_content = resp_mem.data.content
print(f"Response 3: {mem_content}")
if "BLUE-42" in mem_content:
print("✅ Context retention successful.")
else:
print("⚠️ Context retention failed.")
# Cleanup
await session.destroy()
print("\n=== Test 4: Resume Session (Simulation) ===")
# Note: Actual resuming depends on backend persistence.
# The SDK's client.resume_session(id) tries to find it.
# Since we destroyed it above, we expect failure or new session logic in real app.
# But let's create a new one to persist, close client, and try to resume if process was same?
# Actually persistence usually requires the Copilot Agent/Extension host to keep state or file backed.
# The Python SDK defaults to file-based workspace in standard generic usage?
# Let's just skip complex resume testing for this simple script as it depends on environment (vscode-chat-session vs file).
print("Skipping complex resume test in script.")
except Exception as e:
print(f"Test Failed: {e}")
finally:
await client.stop()
print("\nTests Completed.")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,94 @@
import asyncio
import os
import sys
import json
from copilot import CopilotClient
from copilot.types import SessionConfig
# Define the formatting instruction exactly as in the plugin
FORMATTING_INSTRUCTION = (
"\n\n[Formatting Guidelines]\n"
"When providing explanations or descriptions:\n"
"- Use clear paragraph breaks (double line breaks)\n"
"- Break long sentences into multiple shorter ones\n"
"- Use bullet points or numbered lists for multiple items\n"
"- Add headings (##, ###) for major sections\n"
"- Ensure proper spacing between different topics"
)
async def main():
print(f"Python executable: {sys.executable}")
# Check for GH_TOKEN
token = os.environ.get("GH_TOKEN")
if token:
print("GH_TOKEN is set.")
else:
print(
"Warning: GH_TOKEN not found in environment variables. Relying on CLI auth."
)
client_config = {"log_level": "debug"}
client = CopilotClient(client_config)
try:
print("Starting client...")
await client.start()
# Test 1: Check available models
try:
models = await client.list_models()
print(f"Connection successful. Found {len(models)} models.")
model_id = "gpt-5-mini" # User requested model
except Exception as e:
print(f"Failed to list models: {e}")
return
print(f"\nCreating session with model {model_id} and system injection...")
system_message_config = {
"mode": "append",
"content": "You are a helpful assistant." + FORMATTING_INSTRUCTION,
}
session_config = SessionConfig(
model=model_id, system_message=system_message_config
)
session = await client.create_session(config=session_config)
print(f"Session created: {session.session_id}")
# Test 2: Ask the model to summarize its instructions
prompt = "Please summarize the [Formatting Guidelines] you have been given in a list."
print(f"\nSending prompt: '{prompt}'")
response = await session.send_and_wait({"prompt": prompt, "mode": "immediate"})
print("\n--- Model Response ---")
content = response.data.content if response and response.data else "No content"
print(content)
print("----------------------")
required_keywords = ["paragraph", "break", "heading", "spacing", "bullet"]
found_keywords = [kw for kw in required_keywords if kw in content.lower()]
if len(found_keywords) >= 3:
print(
f"\n✅ SUCCESS: Model summarized the guidelines correctly. Found match for: {found_keywords}"
)
else:
print(
f"\n⚠️ UNCERTAIN: Summary might be generic. Found keywords: {found_keywords}"
)
except Exception as e:
print(f"\nError: {e}")
finally:
await client.stop()
print("\nClient stopped.")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,359 @@
"""
title: UI Language Debugger
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 0.1.0
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxwYXRoIGQ9Im01IDggNiA2Ii8+CiAgPHBhdGggZD0ibTQgMTQgNi02IDItMiIvPgogIDxwYXRoIGQ9Ik0yIDVoMTIiLz4KICA8cGF0aCBkPSJNNyAyaDEiLz4KICA8cGF0aCBkPSJtMjIgMjItNS0xMC01IDEwIi8+CiAgPHBhdGggZD0iTTE0IDE4aDYiLz4KPC9zdmc+Cg==
description: Debug UI language detection in the browser console and on-page panel.
"""
import json
import logging
from typing import Optional, Dict, Any, Callable, Awaitable
from pydantic import BaseModel, Field
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)
HTML_WRAPPER_TEMPLATE = """
<!-- OPENWEBUI_PLUGIN_OUTPUT -->
<!DOCTYPE html>
<html lang="{user_language}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 10px;
background-color: transparent;
}
#main-container {
display: flex;
flex-direction: column;
gap: 16px;
width: 100%;
max-width: 100%;
}
/* STYLES_INSERTION_POINT */
</style>
</head>
<body>
<div id="main-container">
<!-- CONTENT_INSERTION_POINT -->
</div>
<!-- SCRIPTS_INSERTION_POINT -->
</body>
</html>
"""
CONTENT_TEMPLATE = """
<div class="lang-debug-card" id="lang-debug-card-{unique_id}">
<div class="lang-debug-header">
🧭 UI Language Debugger
</div>
<div class="lang-debug-body">
<div class="lang-debug-row"><span>python.ui_language</span><code id="lang-py-{unique_id}">{python_language}</code></div>
<div class="lang-debug-row"><span>document.documentElement.lang</span><code id="lang-html-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>document.documentElement.getAttribute('lang')</span><code id="lang-attr-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>document.documentElement.dir</span><code id="lang-dir-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>document.body.lang</span><code id="lang-body-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>navigator.language</span><code id="lang-nav-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>navigator.languages</span><code id="lang-navs-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>localStorage.language</span><code id="lang-store-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>localStorage.locale</span><code id="lang-locale-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>localStorage.i18n</span><code id="lang-i18n-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>localStorage.settings</span><code id="lang-settings-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>document.documentElement.dataset</span><code id="lang-dataset-{unique_id}">-</code></div>
</div>
</div>
"""
STYLE_TEMPLATE = """
.lang-debug-card {
border: 1px solid #e2e8f0;
border-radius: 12px;
background: #ffffff;
overflow: hidden;
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.06);
}
.lang-debug-header {
padding: 12px 16px;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
color: #fff;
font-weight: 600;
}
.lang-debug-body {
padding: 12px 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
.lang-debug-row {
display: flex;
justify-content: space-between;
gap: 12px;
font-size: 0.9em;
color: #1f2937;
}
.lang-debug-row code {
background: #f8fafc;
border: 1px solid #e2e8f0;
padding: 2px 6px;
border-radius: 6px;
color: #0f172a;
}
"""
SCRIPT_TEMPLATE = """
<script>
(function() {{
const uniqueId = "{unique_id}";
const get = (id) => document.getElementById(id + '-' + uniqueId);
const safe = (value) => {
if (value === undefined || value === null || value === "") return "-";
if (Array.isArray(value)) return value.join(", ");
if (typeof value === "object") return JSON.stringify(value);
return String(value);
};
const safeJson = (value) => {
try {
return value ? JSON.stringify(JSON.parse(value)) : "-";
} catch (e) {
return value ? String(value) : "-";
}
};
const settingsRaw = localStorage.getItem('settings');
const i18nRaw = localStorage.getItem('i18n');
const localeRaw = localStorage.getItem('locale');
const payload = {{
htmlLang: document.documentElement.lang,
htmlAttr: document.documentElement.getAttribute('lang'),
htmlDir: document.documentElement.dir,
bodyLang: document.body ? document.body.lang : "",
navigatorLanguage: navigator.language,
navigatorLanguages: navigator.languages,
localStorageLanguage: localStorage.getItem('language'),
localStorageLocale: localeRaw,
localStorageI18n: i18nRaw,
localStorageSettings: settingsRaw,
htmlDataset: document.documentElement.dataset,
}};
get('lang-html').textContent = safe(payload.htmlLang);
get('lang-attr').textContent = safe(payload.htmlAttr);
get('lang-dir').textContent = safe(payload.htmlDir);
get('lang-body').textContent = safe(payload.bodyLang);
get('lang-nav').textContent = safe(payload.navigatorLanguage);
get('lang-navs').textContent = safe(payload.navigatorLanguages);
get('lang-store').textContent = safe(payload.localStorageLanguage);
get('lang-locale').textContent = safe(payload.localStorageLocale);
get('lang-i18n').textContent = safeJson(payload.localStorageI18n);
get('lang-settings').textContent = safeJson(payload.localStorageSettings);
get('lang-dataset').textContent = safe(payload.htmlDataset);
console.group('🧭 UI Language Debugger');
console.log(payload);
console.groupEnd();
}})();
</script>
"""
class Action:
class Valves(BaseModel):
SHOW_STATUS: bool = Field(
default=True,
description="Whether to show operation status updates.",
)
SHOW_DEBUG_LOG: bool = Field(
default=True,
description="Whether to print debug logs in the browser console.",
)
def __init__(self):
self.valves = self.Valves()
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
if isinstance(__user__, (list, tuple)):
user_data = __user__[0] if __user__ else {}
elif isinstance(__user__, dict):
user_data = __user__
else:
user_data = {}
return {
"user_id": user_data.get("id", "unknown_user"),
"user_name": user_data.get("name", "User"),
"user_language": user_data.get("language", ""),
}
def _get_chat_context(
self, body: dict, __metadata__: Optional[dict] = None
) -> Dict[str, str]:
chat_id = ""
message_id = ""
if isinstance(body, dict):
chat_id = body.get("chat_id", "")
message_id = body.get("id", "")
if not chat_id or not message_id:
body_metadata = body.get("metadata", {})
if isinstance(body_metadata, dict):
if not chat_id:
chat_id = body_metadata.get("chat_id", "")
if not message_id:
message_id = body_metadata.get("message_id", "")
if __metadata__ and isinstance(__metadata__, dict):
if not chat_id:
chat_id = __metadata__.get("chat_id", "")
if not message_id:
message_id = __metadata__.get("message_id", "")
return {
"chat_id": str(chat_id).strip(),
"message_id": str(message_id).strip(),
}
async def _emit_status(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
description: str,
done: bool = False,
):
if self.valves.SHOW_STATUS and emitter:
await emitter(
{"type": "status", "data": {"description": description, "done": done}}
)
async def _emit_debug_log(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
title: str,
data: dict,
):
if not self.valves.SHOW_DEBUG_LOG or not emitter:
return
try:
js_code = f"""
(async function() {{
console.group("🛠️ {title}");
console.log({json.dumps(data, ensure_ascii=False)});
console.groupEnd();
}})();
"""
await emitter({"type": "execute", "data": {"code": js_code}})
except Exception as e:
logger.error("Error emitting debug log: %s", e, exc_info=True)
def _merge_html(
self,
existing_html: str,
new_content: str,
new_styles: str = "",
new_scripts: str = "",
user_language: str = "en-US",
) -> str:
if not existing_html:
base_html = HTML_WRAPPER_TEMPLATE.replace("{user_language}", user_language)
else:
base_html = existing_html
if "<!-- CONTENT_INSERTION_POINT -->" in base_html:
base_html = base_html.replace(
"<!-- CONTENT_INSERTION_POINT -->",
f"{new_content}\n <!-- CONTENT_INSERTION_POINT -->",
)
if new_styles and "/* STYLES_INSERTION_POINT */" in base_html:
base_html = base_html.replace(
"/* STYLES_INSERTION_POINT */",
f"{new_styles}\n /* STYLES_INSERTION_POINT */",
)
if new_scripts and "<!-- SCRIPTS_INSERTION_POINT -->" in base_html:
base_html = base_html.replace(
"<!-- SCRIPTS_INSERTION_POINT -->",
f"{new_scripts}\n <!-- SCRIPTS_INSERTION_POINT -->",
)
return base_html
async def action(
self,
body: dict,
__user__: Optional[Dict[str, Any]] = None,
__event_emitter__: Optional[Any] = None,
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
__metadata__: Optional[dict] = None,
__request__: Optional[Any] = None,
) -> Optional[dict]:
await self._emit_status(__event_emitter__, "Detecting UI language...", False)
user_ctx = self._get_user_context(__user__)
await self._emit_debug_log(
__event_emitter__,
"Language Debugger: user context",
user_ctx,
)
ui_language = ""
if __event_call__:
try:
response = await __event_call__(
{
"type": "execute",
"data": {
"code": "return (localStorage.getItem('locale') || localStorage.getItem('language') || (navigator.languages && navigator.languages[0]) || navigator.language || document.documentElement.lang || '')",
},
}
)
await self._emit_debug_log(
__event_emitter__,
"Language Debugger: execute response",
{"response": response},
)
if isinstance(response, dict) and "value" in response:
ui_language = response.get("value", "") or ""
elif isinstance(response, str):
ui_language = response
except Exception as e:
logger.error(
"Failed to read UI language from frontend: %s", e, exc_info=True
)
unique_id = f"lang_{int(__import__('time').time() * 1000)}"
content_html = CONTENT_TEMPLATE.replace("{unique_id}", unique_id).replace(
"{python_language}", ui_language or "-"
)
script_html = SCRIPT_TEMPLATE.replace("{unique_id}", unique_id)
script_html = script_html.replace("{{", "{").replace("}}", "}")
final_html = self._merge_html(
"",
content_html,
STYLE_TEMPLATE,
script_html,
"en",
)
html_embed_tag = f"```html\n{final_html}\n```"
body["messages"][-1]["content"] = (
body["messages"][-1].get("content", "") + "\n\n" + html_embed_tag
)
await self._emit_status(__event_emitter__, "UI language captured.", True)
return body

View File

@@ -1,26 +1,34 @@
# Async Context Compression Filter
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.1.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.2.2 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent.
## What's new in 1.2.2
- **Critical Fix**: Resolved `TypeError: 'str' object is not callable` caused by variable name conflict in logging function.
- **Compatibility**: Enhanced `params` handling to support Pydantic objects, improving compatibility with different OpenWebUI versions.
## What's new in 1.2.1
- **Smart Configuration**: Automatically detects base model settings for custom models and adds `summary_model_max_context` for independent summary limits.
- **Performance & Refactoring**: Optimized threshold parsing with caching, removed redundant code, and improved LLM response handling (JSONResponse support).
- **Bug Fixes & Modernization**: Fixed `datetime` deprecation warnings, corrected type annotations, and replaced print statements with proper logging.
## What's new in 1.2.0
- **Preflight Context Check**: Before sending to the model, validates that total tokens fit within the context window. Automatically trims or drops oldest messages if exceeded.
- **Structure-Aware Assistant Trimming**: When context exceeds the limit, long AI responses are intelligently collapsed while preserving their structure (headers H1-H6, first line, last line).
- **Native Tool Output Trimming**: Detects and trims native tool outputs (`function_calling: "native"`), extracting only the final answer. Enable via `enable_tool_output_trimming`. **Note**: Non-native tool outputs are not fully injected into context.
- **Consolidated Status Notifications**: Unified "Context Usage" and "Context Summary Updated" notifications with appended warnings (e.g., `| ⚠️ High Usage`) for clearer feedback.
- **Context Usage Warning**: Emits a warning notification when context usage exceeds 90%.
- **Enhanced Header Detection**: Optimized regex (`^#{1,6}\s+`) to avoid false positives like `#hashtag`.
- **Detailed Token Logging**: Logs now show token breakdown for System, Head, Summary, and Tail sections with total.
## What's new in 1.1.3
- **Improved Compatibility**: Changed summary injection role from `user` to `assistant` for better compatibility across different LLMs.
- **Enhanced Stability**: Fixed a race condition in state management that could cause "inlet state not found" warnings in high-concurrency scenarios.
- **Bug Fixes**: Corrected default model handling to prevent misleading logs when no model is specified.
## What's new in 1.1.2
- **Open WebUI v0.7.x Compatibility**: Resolved a critical database session binding error affecting Open WebUI v0.7.x users. The plugin now dynamically discovers the database engine and session context, ensuring compatibility across versions.
- **Enhanced Error Reporting**: Errors during background summary generation are now reported via both the status bar and browser console.
- **Robust Model Handling**: Improved handling of missing or invalid model IDs to prevent crashes.
## What's new in 1.1.1
- **Frontend Debugging**: Added `show_debug_log` option to print debug info to the browser console (F12).
- **Optimized Compression**: Improved token calculation logic to prevent aggressive truncation of history, ensuring more context is retained.
---
@@ -31,6 +39,12 @@ This filter reduces token consumption in long conversations through intelligent
- ✅ Persistent storage via Open WebUI's shared database connection (PostgreSQL, SQLite, etc.).
- ✅ Flexible retention policy to keep the first and last N messages.
- ✅ Smart injection of historical summaries back into the context.
- ✅ Structure-aware trimming that preserves document structure (headers, intro, conclusion).
- ✅ Native tool output trimming for cleaner context when using function calling.
- ✅ Real-time context usage monitoring with warning notifications (>90%).
- ✅ Detailed token logging for precise debugging and optimization.
-**Smart Model Matching**: Automatically inherits configuration from base models for custom presets.
-**Multimodal Support**: Images are preserved but their tokens are **NOT** calculated. Please adjust thresholds accordingly.
---
@@ -61,9 +75,11 @@ It is recommended to keep this filter early in the chain so it runs before filte
| `keep_first` | `1` | Always keep the first N messages (protects system prompts). |
| `keep_last` | `6` | Always keep the last N messages to preserve recent context. |
| `summary_model` | `None` | Model for summaries. Strongly recommended to set a fast, economical model (e.g., `gemini-2.5-flash`, `deepseek-v3`). Falls back to the current chat model when empty. |
| `max_summary_tokens` | `4000` | Maximum tokens for the generated summary. |
| `summary_model_max_context` | `0` | Max context tokens for the summary model. If 0, falls back to `model_thresholds` or global `max_context_tokens`. |
| `max_summary_tokens` | `16384` | Maximum tokens for the generated summary. |
| `summary_temperature` | `0.3` | Randomness for summary generation. Lower is more deterministic. |
| `model_thresholds` | `{}` | Per-model overrides for `compression_threshold_tokens` and `max_context_tokens` (useful for mixed models). |
| `enable_tool_output_trimming` | `false` | When enabled and `function_calling: "native"` is active, trims verbose tool outputs to extract only the final answer. |
| `debug_mode` | `true` | Log verbose debug info. Set to `false` in production. |
| `show_debug_log` | `false` | Print debug logs to browser console (F12). Useful for frontend debugging. |

View File

@@ -1,28 +1,36 @@
# 异步上下文压缩过滤器
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.1.3 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.2.2 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
> **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
本过滤器通过智能摘要和消息压缩技术,在保持对话连贯性的同时,显著降低长对话的 Token 消耗。
## 1.2.2 版本更新
- **严重错误修复**: 解决了因日志函数变量名冲突导致的 `TypeError: 'str' object is not callable` 错误。
- **兼容性增强**: 改进了 `params` 处理逻辑以支持 Pydantic 对象,提高了对不同 OpenWebUI 版本的兼容性。
## 1.2.1 版本更新
- **智能配置增强**: 自动检测自定义模型的基础模型配置,并新增 `summary_model_max_context` 参数以独立控制摘要模型的上下文限制。
- **性能优化与重构**: 重构了阈值解析逻辑并增加缓存,移除了冗余的处理代码,并增强了 LLM 响应处理(支持 JSONResponse
- **稳定性改进**: 修复了 `datetime` 弃用警告,修正了类型注解,并将 print 语句替换为标准日志记录。
## 1.2.0 版本更新
- **预检上下文检查 (Preflight Context Check)**: 在发送给模型之前,验证总 Token 是否符合上下文窗口。如果超出,自动裁剪或丢弃最旧的消息。
- **结构感知助手裁剪 (Structure-Aware Assistant Trimming)**: 当上下文超出限制时,智能折叠过长的 AI 回复,同时保留其结构(标题 H1-H6、首行、尾行
- **原生工具输出裁剪 (Native Tool Output Trimming)**: 检测并裁剪原生工具输出 (`function_calling: "native"`),仅提取最终答案。通过 `enable_tool_output_trimming` 启用。**注意**:非原生工具调用输出不会完整注入上下文。
- **统一状态通知**: 统一了“上下文使用情况”和“上下文摘要更新”的通知,并附加警告(例如 `| ⚠️ 高负载`),反馈更清晰。
- **上下文使用警告**: 当上下文使用率超过 90% 时发出警告通知。
- **增强的标题检测**: 优化了正则表达式 (`^#{1,6}\s+`) 以避免误判(如 `#hashtag`)。
- **详细 Token 日志**: 日志现在显示 System、Head、Summary 和 Tail 部分的 Token 细分及总计。
## 1.1.3 版本更新
- **兼容性提升**: 将摘要注入角色从 `user` 改为 `assistant`,以提高在不同 LLM 之间的兼容性。
- **稳定性增强**: 修复了状态管理中的竞态条件,解决了高并发场景下可能出现的“无法获取 inlet 状态”警告。
- **Bug 修复**: 修正了默认模型处理逻辑,防止在未指定模型时产生误导性日志。
## 1.1.2 版本更新
- **Open WebUI v0.7.x 兼容性**: 修复了影响 Open WebUI v0.7.x 用户的严重数据库会话绑定错误。插件现在动态发现数据库引擎和会话上下文,确保跨版本兼容性。
- **增强错误报告**: 后台摘要生成过程中的错误现在会通过状态栏和浏览器控制台同时报告。
- **健壮的模型处理**: 改进了对缺失或无效模型 ID 的处理,防止程序崩溃。
## 1.1.1 版本更新
- **前端调试**: 新增 `show_debug_log` 选项,支持在浏览器控制台 (F12) 打印调试信息。
- **压缩优化**: 优化 Token 计算逻辑,防止历史记录被过度截断,保留更多上下文。
---
@@ -33,6 +41,12 @@
-**持久化存储**: 复用 Open WebUI 共享数据库连接,自动支持 PostgreSQL/SQLite 等。
-**灵活保留策略**: 可配置保留对话头部和尾部消息,确保关键信息连贯。
-**智能注入**: 将历史摘要智能注入到新上下文中。
-**结构感知裁剪**: 智能折叠过长消息,保留文档骨架(标题、首尾)。
-**原生工具输出裁剪**: 支持裁剪冗长的工具调用输出。
-**实时监控**: 实时监控上下文使用情况,超过 90% 发出警告。
-**详细日志**: 提供精确的 Token 统计日志,便于调试。
-**智能模型匹配**: 自定义模型自动继承基础模型的阈值配置。
-**多模态支持**: 图片内容会被保留,但其 Token **不参与计算**。请相应调整阈值。
详细的工作原理和流程请参考 [工作流程指南](WORKFLOW_GUIDE_CN.md)。
@@ -74,6 +88,7 @@
| 参数 | 默认值 | 描述 |
| :-------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ |
| `summary_model` | `None` | 用于生成摘要的模型 ID。**强烈建议**配置快速、经济、上下文窗口大的模型(如 `gemini-2.5-flash``deepseek-v3`)。留空则尝试复用当前对话模型。 |
| `summary_model_max_context` | `0` | 摘要模型的最大上下文 Token 数。如果为 0则回退到 `model_thresholds` 或全局 `max_context_tokens`。 |
| `max_summary_tokens` | `16384` | 生成摘要时允许的最大 Token 数。 |
| `summary_temperature` | `0.1` | 控制摘要生成的随机性,较低的值结果更稳定。 |
@@ -100,15 +115,12 @@
}
```
#### `debug_mode`
- **默认值**: `true`
- **描述**: 是否在 Open WebUI 的控制台日志中打印详细的调试信息(如 Token 计数、压缩进度、数据库操作等)。生产环境建议设为 `false`
#### `show_debug_log`
- **默认值**: `false`
- **描述**: 是否在浏览器控制台 (F12) 打印调试日志。便于前端调试。
| 参数 | 默认值 | 描述 |
| :----------------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
| `enable_tool_output_trimming` | `false` | 启用时,若 `function_calling: "native"` 激活,将裁剪冗长的工具输出以仅提取最终答案。 |
| `debug_mode` | `true` | 是否在 Open WebUI 的控制台日志中打印详细的调试信息(如 Token 计数、压缩进度、数据库操作等)。生产环境建议设为 `false` |
| `show_debug_log` | `false` | 是否在浏览器控制台 (F12) 打印调试日志。便于前端调试。 |
| `show_token_usage_status` | `true` | 是否在对话结束时显示 Token 使用情况的状态通知。 |
---

View File

@@ -0,0 +1,60 @@
# Folder Memory
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.1.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
---
### 📌 What's new in 0.1.0
- **Initial Release**: Automated "Project Rules" management for OpenWebUI folders.
- **Folder-Level Persistence**: Automatically updates folder system prompts with extracted rules.
- **Optimized Performance**: Runs asynchronously and supports `PRIORITY` configuration for seamless integration with other filters.
---
**Folder Memory** is an intelligent context filter plugin for OpenWebUI. It automatically extracts consistent "Project Rules" from ongoing conversations within a folder and injects them back into the folder's system prompt.
## ✨ Features
- **Automatic Extraction**: Analyzes chat history every N messages to extract project rules.
- **Non-destructive Injection**: Updates only the specific "Project Rules" block in the system prompt, preserving other instructions.
- **Async Processing**: Runs in the background without blocking the user's chat experience.
- **ORM Integration**: Directly updates folder data using OpenWebUI's internal models for reliability.
## ⚠️ Prerequisites
- **Conversations must occur inside a folder.** This plugin only triggers when a chat belongs to a folder (i.e., you need to create a folder in OpenWebUI and start a conversation within it).
## 📦 Installation
1. Copy `folder_memory.py` to your OpenWebUI `plugins/filters/` directory (or upload via Admin UI).
2. Enable the filter in your **Settings** -> **Filters**.
3. (Optional) Configure the triggering threshold (default: every 10 messages).
## ⚙️ Configuration (Valves)
| Valve | Default | Description |
| :--- | :--- | :--- |
| `PRIORITY` | `20` | Priority level for the filter operations. |
| `MESSAGE_TRIGGER_COUNT` | `10` | The number of messages required to trigger a rule analysis. |
| `MODEL_ID` | `""` | The model used to generate rules. If empty, uses the current chat model. |
| `RULES_BLOCK_TITLE` | `## 📂 Project Rules` | The title displayed above the injected rules block. |
| `SHOW_DEBUG_LOG` | `False` | Show detailed debug logs in the browser console. |
| `UPDATE_ROOT_FOLDER` | `False` | If enabled, finds and updates the root folder rules instead of the current subfolder. |
## 🛠️ How It Works
![Folder Memory Demo](https://raw.githubusercontent.com/Fu-Jie/awesome-openwebui/main/plugins/filters/folder-memory/folder-memory-demo.png)
1. **Trigger**: When a conversation reaches `MESSAGE_TRIGGER_COUNT` (e.g., 10, 20 messages).
2. **Analysis**: The plugin sends the recent conversation + existing rules to the LLM.
3. **Synthesis**: The LLM merges new insights with old rules, removing obsolete ones.
4. **Update**: The new rule set replaces the `<!-- OWUI_PROJECT_RULES_START -->` block in the folder's system prompt.
## ⚠️ Notes
- This plugin modifies the `system_prompt` of your folders.
- It uses a specific marker `<!-- OWUI_PROJECT_RULES_START -->` to locate its content. Do not manually remove these markers if you want the plugin to continue managing that section.
## 🗺️ Roadmap
See [ROADMAP.md](./ROADMAP.md) for future plans, including "Project Knowledge" collection.

View File

@@ -0,0 +1,62 @@
# 文件夹记忆 (Folder Memory)
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.1.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
---
### 📌 0.1.0 版本特性
- **首个版本发布**:专注于自动化的“项目规则”管理。
- **文件夹级持久化**:自动将提取的规则回写到文件夹系统提示词中。
- **性能优化**:采用异步处理机制,并支持 `PRIORITY` 配置,确保与其他过滤器(如上下文压缩)完美协作。
---
**文件夹记忆 (Folder Memory)** 是一个 OpenWebUI 的智能上下文过滤器插件。它能自动从文件夹内的对话中提取一致性的“项目规则”,并将其回写到文件夹的系统提示词中。
这确保了该文件夹内的所有未来对话都能共享相同的进化上下文和规则,无需手动更新。
## ✨ 功能特性
- **自动提取**:每隔 N 条消息分析一次聊天记录,提取项目规则。
- **无损注入**:仅更新系统提示词中的特定“项目规则”块,保留其他指令。
- **异步处理**:在后台运行,不阻塞用户的聊天体验。
- **ORM 集成**:直接使用 OpenWebUI 的内部模型更新文件夹数据,确保可靠性。
## ⚠️ 前置条件
- **对话必须在文件夹内进行。** 此插件仅在聊天属于某个文件夹时触发(即您需要先在 OpenWebUI 中创建一个文件夹,并在其内部开始对话)。
## 📦 安装指南
1.`folder_memory.py` (或中文版 `folder_memory_cn.py`) 复制到 OpenWebUI 的 `plugins/filters/` 目录(或通过管理员 UI 上传)。
2.**设置** -> **过滤器** 中启用该插件。
3. (可选)配置触发阈值(默认:每 10 条消息)。
## ⚙️ 配置 (Valves)
| 参数 | 默认值 | 说明 |
| :--- | :--- | :--- |
| `PRIORITY` | `20` | 过滤器操作的优先级。 |
| `MESSAGE_TRIGGER_COUNT` | `10` | 触发规则分析的消息数量阈值。 |
| `MODEL_ID` | `""` | 用于生成规则的模型 ID。若为空则使用当前对话模型。 |
| `RULES_BLOCK_TITLE` | `## 📂 项目规则` | 显示在注入规则块上方的标题。 |
| `SHOW_DEBUG_LOG` | `False` | 在浏览器控制台显示详细调试日志。 |
| `UPDATE_ROOT_FOLDER` | `False` | 如果启用,将向上查找并更新根文件夹的规则,而不是当前子文件夹。 |
## 🛠️ 工作原理
![Folder Memory Demo](https://raw.githubusercontent.com/Fu-Jie/awesome-openwebui/main/plugins/filters/folder-memory/folder-memory-demo.png)
1. **触发**:当对话达到 `MESSAGE_TRIGGER_COUNT`(例如 10、20 条消息)时。
2. **分析**:插件将最近的对话 + 现有规则发送给 LLM。
3. **综合**LLM 将新见解与旧规则合并,移除过时的规则。
4. **更新**:新的规则集替换文件夹系统提示词中的 `<!-- OWUI_PROJECT_RULES_START -->` 块。
## ⚠️ 注意事项
- 此插件会修改文件夹的 `system_prompt`
- 它使用特定标记 `<!-- OWUI_PROJECT_RULES_START -->` 来定位内容。如果您希望插件继续管理该部分,请勿手动删除这些标记。
## 🗺️ 路线图
查看 [ROADMAP.md](./ROADMAP.md) 了解未来计划,包括“项目知识”收集功能。

View File

@@ -0,0 +1,10 @@
# Roadmap
## Future Features
### 🧠 Project Knowledge (Planned)
In future versions, we plan to introduce "Project Knowledge" collection. Unlike "Rules" which are strict instructions, "Knowledge" will capture reusable information, consensus, and context that helps the LLM understand the project better.
- **Knowledge Extraction**: Automatically extract reusable knowledge (terminology, style guides, business logic) from conversations.
- **Long-term Memory**: Use the entire folder's chat history as a corpus for knowledge generation.
- **Context Injection**: Inject summarized knowledge into the system prompt alongside rules.

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

View File

@@ -0,0 +1,483 @@
"""
title: 📂 Folder Memory
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 0.1.0
description: Automatically extracts project rules from conversations and injects them into the folder's system prompt.
requirements:
"""
from pydantic import BaseModel, Field
from typing import Optional, Dict, List
from fastapi import Request
import logging
import json
import re
import asyncio
from datetime import datetime
from open_webui.utils.chat import generate_chat_completion
from open_webui.models.users import Users
from open_webui.models.folders import Folders, FolderUpdateForm
from open_webui.models.chats import Chats
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# Markers for rule injection
RULES_BLOCK_START = "<!-- OWUI_PROJECT_RULES_START -->"
RULES_BLOCK_END = "<!-- OWUI_PROJECT_RULES_END -->"
# System Prompt for Rule Generation
SYSTEM_PROMPT_RULE_GENERATOR = """
You are a project rule extractor. Your task is to extract "Project Rules" from the conversation and merge them with existing rules.
### Input
1. **Existing Rules**: Current rules in the folder system prompt.
2. **Conversation**: Recent chat history.
### Goal
Synthesize a concise list of rules that apply to this project/folder.
- **Remove** rules that are no longer relevant or were one-off instructions.
- **Add** new consistent requirements found in the conversation.
- **Merge** similar rules.
- **Format**: Concise bullet points (Markdown).
### Output Format
ONLY output the rules list as Markdown bullet points. Do not include any intro/outro text.
Example:
- Always use Python 3.11 for type hinting.
- Docstrings must follow Google style.
- Commit messages should be in English.
"""
class Filter:
class Valves(BaseModel):
PRIORITY: int = Field(
default=20, description="Priority level for the filter operations."
)
SHOW_DEBUG_LOG: bool = Field(
default=False, description="Show debug logs in console."
)
MESSAGE_TRIGGER_COUNT: int = Field(
default=10, description="Analyze rules after every N messages in a chat."
)
MODEL_ID: str = Field(
default="",
description="Model used for rule extraction. If empty, uses the current chat model.",
)
RULES_BLOCK_TITLE: str = Field(
default="## 📂 Project Rules",
description="Title displayed above the rules block.",
)
UPDATE_ROOT_FOLDER: bool = Field(
default=False,
description="If enabled, finds and updates the root folder rules instead of the current subfolder.",
)
def __init__(self):
self.valves = self.Valves()
# ==================== Helper Methods ====================
def _get_user_context(self, __user__: Optional[dict]) -> Dict[str, str]:
"""Safely extracts user context information."""
if isinstance(__user__, (list, tuple)):
user_data = __user__[0] if __user__ else {}
elif isinstance(__user__, dict):
user_data = __user__
else:
user_data = {}
return {
"user_id": user_data.get("id", ""),
"user_name": user_data.get("name", "User"),
"user_language": user_data.get("language", "en-US"),
}
def _get_chat_context(
self, body: dict, __metadata__: Optional[dict] = None
) -> Dict[str, str]:
"""Unified extraction of chat context information (chat_id, message_id)."""
chat_id = ""
message_id = ""
if isinstance(body, dict):
chat_id = body.get("chat_id", "")
message_id = body.get("id", "")
if not chat_id or not message_id:
body_metadata = body.get("metadata", {})
if isinstance(body_metadata, dict):
if not chat_id:
chat_id = body_metadata.get("chat_id", "")
if not message_id:
message_id = body_metadata.get("message_id", "")
if __metadata__ and isinstance(__metadata__, dict):
if not chat_id:
chat_id = __metadata__.get("chat_id", "")
if not message_id:
message_id = __metadata__.get("message_id", "")
return {
"chat_id": str(chat_id).strip(),
"message_id": str(message_id).strip(),
}
async def _emit_debug_log(self, __event_emitter__, title: str, data: dict):
if self.valves.SHOW_DEBUG_LOG and __event_emitter__:
try:
# Flat log format as requested
js_code = f"""
console.log("[Folder Memory] {title}", {json.dumps(data, ensure_ascii=False)});
"""
await __event_emitter__({"type": "execute", "data": {"code": js_code}})
except Exception as e:
logger.error(f"Error emitting log: {e}")
async def _emit_status(
self, __event_emitter__, description: str, done: bool = False
):
if __event_emitter__:
await __event_emitter__(
{"type": "status", "data": {"description": description, "done": done}}
)
def _get_folder_id(self, body: dict) -> Optional[str]:
# 1. Try retrieving folder_id specifically from metadata
if "metadata" in body and isinstance(body["metadata"], dict):
if "folder_id" in body["metadata"]:
return body["metadata"]["folder_id"]
# 2. Check regular body chat object if available
if "chat" in body and isinstance(body["chat"], dict):
if "folder_id" in body["chat"]:
return body["chat"]["folder_id"]
# 3. Try fallback via Chat ID (Most reliable)
chat_id = body.get("chat_id")
if not chat_id:
if "metadata" in body and isinstance(body["metadata"], dict):
chat_id = body["metadata"].get("chat_id")
if chat_id:
try:
chat = Chats.get_chat_by_id(chat_id)
if chat and chat.folder_id:
return chat.folder_id
except Exception as e:
logger.error(f"Failed to fetch chat {chat_id}: {e}")
return None
def _extract_existing_rules(self, system_prompt: str) -> str:
pattern = re.compile(
re.escape(RULES_BLOCK_START) + r"([\s\S]*?)" + re.escape(RULES_BLOCK_END)
)
match = pattern.search(system_prompt)
if match:
# Remove title if it's inside the block
content = match.group(1).strip()
# Simple cleanup of the title if user formatted it inside
title_pat = re.compile(r"^#+\s+.*$", re.MULTILINE)
return title_pat.sub("", content).strip()
return ""
def _inject_rules(self, system_prompt: str, new_rules: str, title: str) -> str:
new_block_content = f"\n{title}\n\n{new_rules}\n"
new_block = f"{RULES_BLOCK_START}{new_block_content}{RULES_BLOCK_END}"
system_prompt = system_prompt or ""
pattern = re.compile(
re.escape(RULES_BLOCK_START) + r"[\s\S]*?" + re.escape(RULES_BLOCK_END)
)
if pattern.search(system_prompt):
return pattern.sub(new_block, system_prompt).strip()
else:
# Append if not found
if system_prompt:
return f"{system_prompt}\n\n{new_block}"
else:
return new_block
async def _generate_new_rules(
self,
current_rules: str,
messages: List[Dict],
user_id: str,
__request__: Request,
) -> str:
# Prepare context
conversation_text = "\n".join(
[
f"{msg['role'].upper()}: {msg['content']}"
for msg in messages[-20:] # Analyze last 20 messages context
]
)
prompt = f"""
Existing Rules:
{current_rules if current_rules else "None"}
Conversation Excerpt:
{conversation_text}
Please output the updated Project Rules:
"""
payload = {
"model": self.valves.MODEL_ID,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT_RULE_GENERATOR},
{"role": "user", "content": prompt},
],
"stream": False,
}
try:
# We need a user object for permission checks in generate_chat_completion
user = Users.get_user_by_id(user_id)
if not user:
return current_rules
completion = await generate_chat_completion(__request__, payload, user)
if "choices" in completion and len(completion["choices"]) > 0:
content = completion["choices"][0]["message"]["content"].strip()
# Basic validation: ensure it looks like a list
if (
content.startswith("-")
or content.startswith("*")
or content.startswith("1.")
):
return content
except Exception as e:
logger.error(f"Rule generation failed: {e}")
return current_rules
async def _process_rules_update(
self,
folder_id: str,
body: dict,
user_id: str,
__request__: Request,
__event_emitter__,
):
try:
await self._emit_debug_log(
__event_emitter__,
"Start Processing",
{"step": "start", "initial_folder_id": folder_id, "user_id": user_id},
)
# 1. Fetch Folder Data (ORM)
initial_folder = Folders.get_folder_by_id_and_user_id(folder_id, user_id)
if not initial_folder:
await self._emit_debug_log(
__event_emitter__,
"Error: Initial folder not found",
{
"step": "fetch_initial_folder",
"initial_folder_id": folder_id,
"user_id": user_id,
},
)
return
# Subfolder handling logic
target_folder = initial_folder
if self.valves.UPDATE_ROOT_FOLDER:
# Traverse up until a folder with no parent_id is found
while target_folder and getattr(target_folder, "parent_id", None):
try:
parent = Folders.get_folder_by_id_and_user_id(
target_folder.parent_id, user_id
)
if parent:
target_folder = parent
else:
break
except Exception as e:
await self._emit_debug_log(
__event_emitter__,
"Warning: Failed to traverse parent folder",
{"step": "traverse_root", "error": str(e)},
)
break
target_folder_id = target_folder.id
await self._emit_debug_log(
__event_emitter__,
"Target Folder Resolved",
{
"step": "target_resolved",
"target_folder_id": target_folder_id,
"target_folder_name": target_folder.name,
"is_root_update": target_folder_id != folder_id,
},
)
existing_data = target_folder.data if target_folder.data else {}
existing_sys_prompt = existing_data.get("system_prompt", "")
# 2. Extract Existing Rules
current_rules_content = self._extract_existing_rules(existing_sys_prompt)
# 3. Generate New Rules
await self._emit_status(
__event_emitter__, "Analyzing project rules...", done=False
)
messages = body.get("messages", [])
new_rules_content = await self._generate_new_rules(
current_rules_content, messages, user_id, __request__
)
rules_changed = new_rules_content != current_rules_content
# 4. If no change, skip
if not rules_changed:
await self._emit_debug_log(
__event_emitter__,
"No Changes",
{
"step": "check_changes",
"reason": "content_identical_or_generation_failed",
},
)
await self._emit_status(
__event_emitter__,
"Rule analysis complete: No new content.",
done=True,
)
return
# 5. Inject Rules into System Prompt
updated_sys_prompt = existing_sys_prompt
if rules_changed:
updated_sys_prompt = self._inject_rules(
updated_sys_prompt,
new_rules_content,
self.valves.RULES_BLOCK_TITLE,
)
await self._emit_debug_log(
__event_emitter__,
"Ready to Update DB",
{"step": "pre_db_update", "target_folder_id": target_folder_id},
)
# 6. Update Folder (ORM) - Only update 'data' field
existing_data["system_prompt"] = updated_sys_prompt
updated_folder = Folders.update_folder_by_id_and_user_id(
target_folder_id,
user_id,
FolderUpdateForm(data=existing_data),
)
if not updated_folder:
raise Exception("Update folder failed (ORM returned None)")
await self._emit_status(
__event_emitter__, "Rule analysis complete: Rules updated.", done=True
)
await self._emit_debug_log(
__event_emitter__,
"Rule Generation Process & Change Details",
{
"step": "success",
"folder_id": target_folder_id,
"target_is_root": target_folder_id != folder_id,
"model_used": self.valves.MODEL_ID,
"analyzed_messages_count": len(messages),
"old_rules_length": len(current_rules_content),
"new_rules_length": len(new_rules_content),
"changes_digest": {
"old_rules_preview": (
current_rules_content[:100] + "..."
if current_rules_content
else "None"
),
"new_rules_preview": (
new_rules_content[:100] + "..."
if new_rules_content
else "None"
),
},
"timestamp": datetime.now().isoformat(),
},
)
except Exception as e:
logger.error(f"Async rule processing error: {e}")
await self._emit_status(
__event_emitter__, "Failed to update rules.", done=True
)
# Emit error to console for debugging
await self._emit_debug_log(
__event_emitter__,
"Execution Error",
{"error": str(e), "folder_id": folder_id},
)
# ==================== Filter Hooks ====================
async def inlet(
self, body: dict, __user__: Optional[dict] = None, __event_emitter__=None
) -> dict:
return body
async def outlet(
self,
body: dict,
__user__: Optional[dict] = None,
__event_emitter__=None,
__request__: Optional[Request] = None,
) -> dict:
user_ctx = self._get_user_context(__user__)
chat_ctx = self._get_chat_context(body)
messages = body.get("messages", [])
if not messages:
return body
# Trigger logic: Message Count threshold
if len(messages) % self.valves.MESSAGE_TRIGGER_COUNT != 0:
return body
folder_id = self._get_folder_id(body)
if not folder_id:
await self._emit_debug_log(
__event_emitter__,
"Skipping Analysis",
{
"reason": "Chat does not belong to any folder",
"chat_id": chat_ctx.get("chat_id"),
},
)
return body
# User Info
user_id = user_ctx.get("user_id")
if not user_id:
return body
# Async Task
if self.valves.MODEL_ID == "":
self.valves.MODEL_ID = body.get("model", "")
asyncio.create_task(
self._process_rules_update(
folder_id, body, user_id, __request__, __event_emitter__
)
)
return body

View File

@@ -0,0 +1,470 @@
"""
title: 📂 文件夹记忆 (Folder Memory)
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 0.1.0
description: 自动从对话中提取项目规则,并将其注入到文件夹的系统提示词中。
requirements:
"""
from pydantic import BaseModel, Field
from typing import Optional, Dict, List
from fastapi import Request
import logging
import json
import re
import asyncio
from datetime import datetime
from open_webui.utils.chat import generate_chat_completion
from open_webui.models.users import Users
from open_webui.models.folders import Folders, FolderUpdateForm
from open_webui.models.chats import Chats
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# 规则注入标记
RULES_BLOCK_START = "<!-- OWUI_PROJECT_RULES_START -->"
RULES_BLOCK_END = "<!-- OWUI_PROJECT_RULES_END -->"
# 规则生成系统提示词
SYSTEM_PROMPT_RULE_GENERATOR = """
你是一个项目规则提取器。你的任务是从对话中提取“项目规则”,并与现有规则合并。
### 输入
1. **现有规则 (Existing Rules)**:当前文件夹系统提示词中的规则。
2. **对话片段 (Conversation)**:最近的聊天记录。
### 目标
综合生成一份适用于当前项目/文件夹的简洁规则列表。
- **移除** 不再相关或仅是一次性指令的规则。
- **添加** 对话中发现的新的、一致性的要求。
- **合并** 相似的规则。
- **格式**:简洁的 Markdown 项目符号列表。
### 输出格式
仅输出 Markdown 项目符号列表形式的规则。不要包含任何开头或结尾的说明文字。
示例:
- 始终使用 Python 3.11 进行类型提示。
- 文档字符串必须遵循 Google 风格。
- 提交信息必须使用英文。
"""
class Filter:
class Valves(BaseModel):
PRIORITY: int = Field(default=20, description="过滤器操作的优先级。")
SHOW_DEBUG_LOG: bool = Field(
default=False, description="在控制台显示调试日志。"
)
MESSAGE_TRIGGER_COUNT: int = Field(
default=10, description="每隔 N 条消息分析一次规则。"
)
MODEL_ID: str = Field(
default="", description="用于提取规则的模型 ID。为空则使用当前对话模型。"
)
RULES_BLOCK_TITLE: str = Field(
default="## 📂 项目规则", description="显示在规则块上方的标题。"
)
UPDATE_ROOT_FOLDER: bool = Field(
default=False,
description="如果启用,将向上查找并更新根文件夹的规则,而不是当前子文件夹。",
)
def __init__(self):
self.valves = self.Valves()
# ==================== 辅助方法 ====================
def _get_user_context(self, __user__: Optional[dict]) -> Dict[str, str]:
"""安全提取用户上下文信息。"""
if isinstance(__user__, (list, tuple)):
user_data = __user__[0] if __user__ else {}
elif isinstance(__user__, dict):
user_data = __user__
else:
user_data = {}
return {
"user_id": user_data.get("id", ""),
"user_name": user_data.get("name", "User"),
"user_language": user_data.get("language", "zh-CN"),
}
def _get_chat_context(
self, body: dict, __metadata__: Optional[dict] = None
) -> Dict[str, str]:
"""统一提取聊天上下文信息 (chat_id, message_id)。"""
chat_id = ""
message_id = ""
if isinstance(body, dict):
chat_id = body.get("chat_id", "")
message_id = body.get("id", "")
if not chat_id or not message_id:
body_metadata = body.get("metadata", {})
if isinstance(body_metadata, dict):
if not chat_id:
chat_id = body_metadata.get("chat_id", "")
if not message_id:
message_id = body_metadata.get("message_id", "")
if __metadata__ and isinstance(__metadata__, dict):
if not chat_id:
chat_id = __metadata__.get("chat_id", "")
if not message_id:
message_id = __metadata__.get("message_id", "")
return {
"chat_id": str(chat_id).strip(),
"message_id": str(message_id).strip(),
}
async def _emit_debug_log(self, __event_emitter__, title: str, data: dict):
if self.valves.SHOW_DEBUG_LOG and __event_emitter__:
try:
# 按照用户要求的格式输出展平的日志
js_code = f"""
console.log("[Folder Memory] {title}", {json.dumps(data, ensure_ascii=False)});
"""
await __event_emitter__({"type": "execute", "data": {"code": js_code}})
except Exception as e:
logger.error(f"发出日志错误: {e}")
async def _emit_status(
self, __event_emitter__, description: str, done: bool = False
):
if __event_emitter__:
await __event_emitter__(
{"type": "status", "data": {"description": description, "done": done}}
)
def _get_folder_id(self, body: dict) -> Optional[str]:
# 1. 尝试从 metadata 获取 folder_id
if "metadata" in body and isinstance(body["metadata"], dict):
if "folder_id" in body["metadata"]:
return body["metadata"]["folder_id"]
# 2. 检查 chat 对象
if "chat" in body and isinstance(body["chat"], dict):
if "folder_id" in body["chat"]:
return body["chat"]["folder_id"]
# 3. 尝试通过 Chat ID 查找 (最可靠的方法)
chat_id = body.get("chat_id")
if not chat_id:
if "metadata" in body and isinstance(body["metadata"], dict):
chat_id = body["metadata"].get("chat_id")
if chat_id:
try:
chat = Chats.get_chat_by_id(chat_id)
if chat and chat.folder_id:
return chat.folder_id
except Exception as e:
logger.error(f"获取聊天信息失败 chat_id={chat_id}: {e}")
return None
def _extract_existing_rules(self, system_prompt: str) -> str:
pattern = re.compile(
re.escape(RULES_BLOCK_START) + r"([\s\S]*?)" + re.escape(RULES_BLOCK_END)
)
match = pattern.search(system_prompt)
if match:
# 如果标题在块内,将其移除以便纯净合并
content = match.group(1).strip()
title_pat = re.compile(r"^#+\s+.*$", re.MULTILINE)
return title_pat.sub("", content).strip()
return ""
def _inject_rules(self, system_prompt: str, new_rules: str, title: str) -> str:
new_block_content = f"\n{title}\n\n{new_rules}\n"
new_block = f"{RULES_BLOCK_START}{new_block_content}{RULES_BLOCK_END}"
system_prompt = system_prompt or ""
pattern = re.compile(
re.escape(RULES_BLOCK_START) + r"[\s\S]*?" + re.escape(RULES_BLOCK_END)
)
if pattern.search(system_prompt):
# 替换现有块
return pattern.sub(new_block, system_prompt).strip()
else:
# 追加到末尾
if system_prompt:
return f"{system_prompt}\n\n{new_block}"
else:
return new_block
async def _generate_new_rules(
self,
current_rules: str,
messages: List[Dict],
user_id: str,
__request__: Request,
) -> str:
# 准备上下文
conversation_text = "\n".join(
[
f"{msg['role'].upper()}: {msg['content']}"
for msg in messages[-20:] # 分析最近 20 条消息上下文
]
)
prompt = f"""
Existing Rules (现有规则):
{current_rules if current_rules else ""}
Conversation Excerpt (对话片段):
{conversation_text}
Please output the updated Project Rules (请输出更新后的项目规则):
"""
payload = {
"model": self.valves.MODEL_ID,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT_RULE_GENERATOR},
{"role": "user", "content": prompt},
],
"stream": False,
}
try:
# 需要用户对象进行权限检查
user = Users.get_user_by_id(user_id)
if not user:
return current_rules
completion = await generate_chat_completion(__request__, payload, user)
if "choices" in completion and len(completion["choices"]) > 0:
content = completion["choices"][0]["message"]["content"].strip()
# 简单验证:确保看起来像个列表
if (
content.startswith("-")
or content.startswith("*")
or content.startswith("1.")
):
return content
except Exception as e:
logger.error(f"规则生成失败: {e}")
return current_rules
async def _process_rules_update(
self,
folder_id: str,
body: dict,
user_id: str,
__request__: Request,
__event_emitter__,
):
try:
await self._emit_debug_log(
__event_emitter__,
"开始处理",
{"step": "start", "initial_folder_id": folder_id, "user_id": user_id},
)
# 1. 获取文件夹数据 (ORM)
initial_folder = Folders.get_folder_by_id_and_user_id(folder_id, user_id)
if not initial_folder:
await self._emit_debug_log(
__event_emitter__,
"错误:未找到初始文件夹",
{
"step": "fetch_initial_folder",
"initial_folder_id": folder_id,
"user_id": user_id,
},
)
return
# 处理子文件夹逻辑:决定是更新当前文件夹还是根文件夹
target_folder = initial_folder
if self.valves.UPDATE_ROOT_FOLDER:
# 向上遍历直到找到没有 parent_id 的根文件夹
while target_folder and getattr(target_folder, "parent_id", None):
try:
parent = Folders.get_folder_by_id_and_user_id(
target_folder.parent_id, user_id
)
if parent:
target_folder = parent
else:
break
except Exception as e:
await self._emit_debug_log(
__event_emitter__,
"警告:向上查找父文件夹失败",
{"step": "traverse_root", "error": str(e)},
)
break
target_folder_id = target_folder.id
await self._emit_debug_log(
__event_emitter__,
"定目标文件夹",
{
"step": "target_resolved",
"target_folder_id": target_folder_id,
"target_folder_name": target_folder.name,
"is_root_update": target_folder_id != folder_id,
},
)
existing_data = target_folder.data if target_folder.data else {}
existing_sys_prompt = existing_data.get("system_prompt", "")
# 2. 提取现有规则
current_rules_content = self._extract_existing_rules(existing_sys_prompt)
# 3. 生成新规则
await self._emit_status(
__event_emitter__, "正在分析项目规则...", done=False
)
messages = body.get("messages", [])
new_rules_content = await self._generate_new_rules(
current_rules_content, messages, user_id, __request__
)
rules_changed = new_rules_content != current_rules_content
# 如果生成结果无变更
if not rules_changed:
await self._emit_debug_log(
__event_emitter__,
"无变更",
{
"step": "check_changes",
"reason": "content_identical_or_generation_failed",
},
)
await self._emit_status(
__event_emitter__, "规则分析完成:无新增内容。", done=True
)
return
# 5. 注入规则到 System Prompt
updated_sys_prompt = existing_sys_prompt
if rules_changed:
updated_sys_prompt = self._inject_rules(
updated_sys_prompt,
new_rules_content,
self.valves.RULES_BLOCK_TITLE,
)
await self._emit_debug_log(
__event_emitter__,
"准备更新数据库",
{"step": "pre_db_update", "target_folder_id": target_folder_id},
)
# 6. 更新文件夹 (ORM) - 仅更新 'data' 字段
existing_data["system_prompt"] = updated_sys_prompt
updated_folder = Folders.update_folder_by_id_and_user_id(
target_folder_id,
user_id,
FolderUpdateForm(data=existing_data),
)
if not updated_folder:
raise Exception("Update folder failed (ORM returned None)")
await self._emit_status(
__event_emitter__, "规则分析完成:规则已更新。", done=True
)
await self._emit_debug_log(
__event_emitter__,
"规则生成过程和变更详情",
{
"step": "success",
"folder_id": target_folder_id,
"target_is_root": target_folder_id != folder_id,
"model_used": self.valves.MODEL_ID,
"analyzed_messages_count": len(messages),
"old_rules_length": len(current_rules_content),
"new_rules_length": len(new_rules_content),
"changes_digest": {
"old_rules_preview": (
current_rules_content[:100] + "..."
if current_rules_content
else "None"
),
"new_rules_preview": (
new_rules_content[:100] + "..."
if new_rules_content
else "None"
),
},
"timestamp": datetime.now().isoformat(),
},
)
except Exception as e:
logger.error(f"异步规则处理错误: {e}")
await self._emit_status(__event_emitter__, "更新规则失败。", done=True)
# 在控制台也输出错误信息,方便调试
await self._emit_debug_log(
__event_emitter__, "执行出错", {"error": str(e), "folder_id": folder_id}
)
# ==================== Filter Hooks ====================
async def inlet(
self, body: dict, __user__: Optional[dict] = None, __event_emitter__=None
) -> dict:
return body
async def outlet(
self,
body: dict,
__user__: Optional[dict] = None,
__event_emitter__=None,
__request__: Optional[Request] = None,
) -> dict:
user_ctx = self._get_user_context(__user__)
chat_ctx = self._get_chat_context(body)
messages = body.get("messages", [])
if not messages:
return body
# 触发逻辑:消息计数阈值
if len(messages) % self.valves.MESSAGE_TRIGGER_COUNT != 0:
return body
folder_id = self._get_folder_id(body)
if not folder_id:
await self._emit_debug_log(
__event_emitter__,
"跳过分析",
{"reason": "对话不属于任何文件夹", "chat_id": chat_ctx.get("chat_id")},
)
return body
# 用户信息
user_id = user_ctx.get("user_id")
if not user_id:
return body
# 异步任务
if self.valves.MODEL_ID == "":
self.valves.MODEL_ID = body.get("model", "")
asyncio.create_task(
self._process_rules_update(
folder_id, body, user_id, __request__, __event_emitter__
)
)
return body

View File

@@ -1,6 +1,6 @@
# Markdown Normalizer Filter
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.2.2 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.2.4 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
A content normalizer filter for Open WebUI that fixes common Markdown formatting issues in LLM outputs. It ensures that code blocks, LaTeX formulas, Mermaid diagrams, and other Markdown elements are rendered correctly.
@@ -43,7 +43,7 @@ A content normalizer filter for Open WebUI that fixes common Markdown formatting
* `enable_heading_fix`: Fix missing space in headings.
* `enable_table_fix`: Fix missing closing pipe in tables.
* `enable_xml_tag_cleanup`: Cleanup leftover XML tags.
* `enable_emphasis_spacing_fix`: Fix extra spaces in emphasis (default: True).
* `enable_emphasis_spacing_fix`: Fix extra spaces in emphasis (default: False).
* `show_status`: Show status notification when fixes are applied.
* `show_debug_log`: Print debug logs to browser console.
@@ -53,6 +53,10 @@ A content normalizer filter for Open WebUI that fixes common Markdown formatting
## Changelog
### v1.2.4
* **Documentation Updates**: Synchronized version numbers across all documentation and code files.
### v1.2.3
* **List Marker Protection Enhancement**: Fixed a bug where list markers (`*`) followed by plain text and emphasis were having their spaces incorrectly stripped (e.g., `* U16 forward` became `*U16 forward`).

View File

@@ -1,6 +1,6 @@
# Markdown 格式化过滤器 (Markdown Normalizer)
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.2.2 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.2.4 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
这是一个用于 Open WebUI 的内容格式化过滤器,旨在修复 LLM 输出中常见的 Markdown 格式问题。它能确保代码块、LaTeX 公式、Mermaid 图表和其他 Markdown 元素被正确渲染。
@@ -53,6 +53,10 @@
## 更新日志
### v1.2.4
* **文档更新**: 同步了所有文档和代码文件的版本号。
### v1.2.3
* **列表标记保护增强**: 修复了列表标记 (`*`) 后跟普通文本和强调标记时,空格被错误剥离的问题(例如 `* U16 前锋` 变成 `*U16 前锋`)。

View File

@@ -3,7 +3,7 @@ title: Markdown Normalizer
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 1.2.3
version: 1.2.4
openwebui_id: baaa8732-9348-40b7-8359-7e009660e23c
description: A content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting.
"""
@@ -43,7 +43,7 @@ class NormalizerConfig:
)
enable_table_fix: bool = True # Fix missing closing pipe in tables
enable_xml_tag_cleanup: bool = True # Cleanup leftover XML tags
enable_emphasis_spacing_fix: bool = True # Fix spaces inside **emphasis**
enable_emphasis_spacing_fix: bool = False # Fix spaces inside **emphasis**
# Custom cleaner functions (for advanced extension)
custom_cleaners: List[Callable[[str], str]] = field(default_factory=list)
@@ -564,7 +564,7 @@ class Filter:
default=True, description="Cleanup leftover XML tags"
)
enable_emphasis_spacing_fix: bool = Field(
default=True,
default=False,
description="Fix spaces inside **emphasis** (e.g. ** text ** -> **text**)",
)
show_status: bool = Field(
@@ -695,6 +695,15 @@ class Filter:
if self._contains_html(content):
return body
# Skip if content contains tool output markers (native function calling)
# Pattern: ""&quot;...&quot;"" or tool_call_id or <details type="tool_calls"...>
if (
'""&quot;' in content
or "tool_call_id" in content
or '<details type="tool_calls"' in content
):
return body
# Configure normalizer based on valves
config = NormalizerConfig(
enable_escape_fix=self.valves.enable_escape_fix,

View File

@@ -3,7 +3,7 @@ title: Markdown 格式修复器 (Markdown Normalizer)
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 1.2.3
version: 1.2.4
description: 内容规范化过滤器,修复 LLM 输出中常见的 Markdown 格式问题如损坏的代码块、LaTeX 公式、Mermaid 图表和列表格式。
"""
@@ -24,6 +24,9 @@ class NormalizerConfig:
"""配置类,用于启用/禁用特定的规范化规则"""
enable_escape_fix: bool = True # 修复过度的转义字符
enable_escape_fix_in_code_blocks: bool = (
False # 在代码块内部应用转义修复 (默认:关闭,以确保安全)
)
enable_thought_tag_fix: bool = True # 规范化思维链标签
enable_details_tag_fix: bool = True # 规范化 <details> 标签(类似思维链标签)
enable_code_block_fix: bool = True # 修复代码块格式
@@ -35,7 +38,7 @@ class NormalizerConfig:
enable_heading_fix: bool = True # 修复标题中缺失的空格 (#Header -> # Header)
enable_table_fix: bool = True # 修复表格中缺失的闭合管道符
enable_xml_tag_cleanup: bool = True # 清理残留的 XML 标签
enable_emphasis_spacing_fix: bool = True # 修复 **强调内容** 中的多余空格
enable_emphasis_spacing_fix: bool = False # 修复 **强调内容** 中的多余空格
# 自定义清理函数 (用于高级扩展)
custom_cleaners: List[Callable[[str], str]] = field(default_factory=list)
@@ -239,12 +242,27 @@ class ContentNormalizer:
return content
def _fix_escape_characters(self, content: str) -> str:
"""Fix excessive escape characters"""
content = content.replace("\\r\\n", "\n")
content = content.replace("\\n", "\n")
content = content.replace("\\t", "\t")
content = content.replace("\\\\", "\\")
return content
"""修复过度的转义字符
如果 enable_escape_fix_in_code_blocks 为 False (默认),此方法将仅修复代码块外部的转义字符,
以避免破坏有效的代码示例 (例如,带有 \\n 的 JSON 字符串、正则表达式模式等)。
"""
if self.config.enable_escape_fix_in_code_blocks:
# 全局应用 (原始行为)
content = content.replace("\\r\\n", "\n")
content = content.replace("\\n", "\n")
content = content.replace("\\t", "\t")
content = content.replace("\\\\", "\\")
return content
else:
# 仅在代码块外部应用 (安全模式)
parts = content.split("```")
for i in range(0, len(parts), 2): # 偶数索引是 Markdown 文本 (非代码)
parts[i] = parts[i].replace("\\r\\n", "\n")
parts[i] = parts[i].replace("\\n", "\n")
parts[i] = parts[i].replace("\\t", "\t")
parts[i] = parts[i].replace("\\\\", "\\")
return "```".join(parts)
def _fix_thought_tags(self, content: str) -> str:
"""Normalize thought tags: unify naming and fix spacing"""
@@ -501,6 +519,10 @@ class Filter:
enable_escape_fix: bool = Field(
default=True, description="修复过度的转义字符 (\\n, \\t 等)"
)
enable_escape_fix_in_code_blocks: bool = Field(
default=False,
description="在代码块内部应用转义修复 (⚠️ 警告:可能会破坏有效的代码,如 JSON 字符串或正则模式。默认:关闭,以确保安全)",
)
enable_thought_tag_fix: bool = Field(
default=True, description="规范化思维链标签 (<think> -> <thought>)"
)
@@ -539,7 +561,7 @@ class Filter:
default=True, description="清理残留的 XML 标签"
)
enable_emphasis_spacing_fix: bool = Field(
default=True,
default=False,
description="修复强调语法中的多余空格 (例如 ** 文本 ** -> **文本**)",
)
show_status: bool = Field(default=True, description="应用修复时显示状态通知")
@@ -682,13 +704,23 @@ class Filter:
content = last.get("content", "") or ""
if last.get("role") == "assistant" and isinstance(content, str):
# Skip if content looks like HTML to avoid breaking it
# 如果内容看起来像 HTML则跳过以避免破坏它
if self._contains_html(content):
return body
# Configure normalizer based on valves
# 如果内容包含工具输出标记 (原生函数调用),则跳过
# 模式:""&quot;...&quot;"" 或 tool_call_id 或 <details type="tool_calls"...>
if (
'""&quot;' in content
or "tool_call_id" in content
or '<details type="tool_calls"' in content
):
return body
# 根据 Valves 配置 Normalizer
config = NormalizerConfig(
enable_escape_fix=self.valves.enable_escape_fix,
enable_escape_fix_in_code_blocks=self.valves.enable_escape_fix_in_code_blocks,
enable_thought_tag_fix=self.valves.enable_thought_tag_fix,
enable_details_tag_fix=self.valves.enable_details_tag_fix,
enable_code_block_fix=self.valves.enable_code_block_fix,

View File

@@ -0,0 +1,114 @@
# GitHub Copilot SDK Pipe for OpenWebUI
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.2.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/open-webui) that allows you to use GitHub Copilot models (such as `gpt-5`, `gpt-5-mini`, `claude-sonnet-4.5`) directly within OpenWebUI. It is built upon the official [GitHub Copilot SDK for Python](https://github.com/github/copilot-sdk), providing a native integration experience.
## 🚀 What's New (v0.2.3)
* **🧩 Per-user Overrides**: Added user-level overrides for `REASONING_EFFORT`, `CLI_PATH`, `DEBUG`, `SHOW_THINKING`, and `MODEL_ID`.
* **🧠 Thinking Output Reliability**: Thinking visibility now respects the user setting and is correctly passed into streaming.
* **📝 Formatting Enforcement**: Added automatic formatting hints to ensure outputs are well-structured (paragraphs, lists) and addressed "tight output" issues.
## ✨ Core Features
* **🚀 Official SDK Integration**: Built on the official SDK for stability and reliability.
* **🛠️ Custom Tools Support**: Example tools included (random number). Easy to extend with your own tools.
* **💬 Multi-turn Conversation**: Automatically concatenates history context so Copilot understands your previous messages.
* **🌊 Streaming Output**: Supports typewriter effect for fast responses.
* **🖼️ Multimodal Support**: Supports image uploads, automatically converting them to attachments for Copilot (requires model support).
* **🛠️ Zero-config Installation**: Automatically detects and downloads the GitHub Copilot CLI, ready to use out of the box.
* **🔑 Secure Authentication**: Supports Fine-grained Personal Access Tokens for minimized permissions.
* **🐛 Debug Mode**: Built-in detailed log output (browser console) for easy troubleshooting.
* **⚠️ Single Node Only**: Due to local session storage, this plugin currently supports single-node OpenWebUI deployment or multi-node with sticky sessions enabled.
## 📦 Installation & Usage
### 1. Import Function
1. Open OpenWebUI.
2. Go to **Workspace** -> **Functions**.
3. Click **+** (Create Function).
4. Paste the content of `github_copilot_sdk.py` (or `github_copilot_sdk_cn.py` for Chinese) completely.
5. Save.
### 2. Configure Valves (Settings)
Find "GitHub Copilot" in the function list and click the **⚙️ (Valves)** icon to configure:
| Parameter | Description | Default |
| :--- | :--- | :--- |
| **GH_TOKEN** | **(Required)** Your GitHub Token. | - |
| **MODEL_ID** | The model name to use. | `gpt-5-mini` |
| **CLI_PATH** | Path to the Copilot CLI. Will download automatically if not found. | `/usr/local/bin/copilot` |
| **DEBUG** | Whether to enable debug logs (output to browser console). | `False` |
| **LOG_LEVEL** | Copilot CLI log level: none, error, warning, info, debug, all. | `error` |
| **SHOW_THINKING** | Show model reasoning/thinking process (requires streaming + model support). | `True` |
| **SHOW_WORKSPACE_INFO** | Show session workspace path and summary in debug mode. | `True` |
| **EXCLUDE_KEYWORDS** | Exclude models containing these keywords (comma separated). | - |
| **WORKSPACE_DIR** | Restricted workspace directory for file operations. | - |
| **INFINITE_SESSION** | Enable Infinite Sessions (automatic context compaction). | `True` |
| **COMPACTION_THRESHOLD** | Background compaction threshold (0.0-1.0). | `0.8` |
| **BUFFER_THRESHOLD** | Buffer exhaustion threshold (0.0-1.0). | `0.95` |
| **TIMEOUT** | Timeout for each stream chunk (seconds). | `300` |
| **CUSTOM_ENV_VARS** | Custom environment variables (JSON format). | - |
| **REASONING_EFFORT** | Reasoning effort level: low, medium, high. `xhigh` is supported for gpt-5.2-codex. | `medium` |
| **ENFORCE_FORMATTING** | Add formatting instructions to system prompt for better readability. | `True` |
| **ENABLE_TOOLS** | Enable custom tools (example: random number). | `False` |
| **AVAILABLE_TOOLS** | Available tools: 'all' or comma-separated list. | `all` |
#### User Valves (per-user overrides)
These optional settings can be set per user (overrides global Valves):
| Parameter | Description | Default |
| :--- | :--- | :--- |
| **REASONING_EFFORT** | Reasoning effort level (low/medium/high/xhigh). | - |
| **CLI_PATH** | Custom path to Copilot CLI. | - |
| **DEBUG** | Enable technical debug logs. | `False` |
| **SHOW_THINKING** | Show model reasoning/thinking process (requires streaming + model support). | `True` |
| **MODEL_ID** | Custom model ID. | - |
### 3. Using Custom Tools (🆕 Optional)
This pipe includes **1 example tool** to demonstrate tool calling:
* **🎲 generate_random_number**: Generate random integers
**To enable:**
1. Set `ENABLE_TOOLS: true` in Valves
2. Try: "Give me a random number"
**📚 For detailed usage and creating your own tools, see [TOOLS_USAGE.md](TOOLS_USAGE.md)**
### 4. Get GH_TOKEN
For security, it is recommended to use a **Fine-grained Personal Access Token**:
1. Visit [GitHub Token Settings](https://github.com/settings/tokens?type=beta).
2. Click **Generate new token**.
3. **Repository access**: Select **Public repositories** (Required to access Copilot permissions).
4. **Permissions**:
* Click **Account permissions**.
* Find **Copilot Requests** (It defaults to **Read-only**, no selection needed).
5. Generate and copy the Token.
## 📋 Dependencies
This Pipe will automatically attempt to install the following dependencies:
* `github-copilot-sdk` (Python package)
* `github-copilot-cli` (Binary file, installed via official script)
## ⚠️ FAQ
* **Stuck on "Waiting..."**:
* Check if `GH_TOKEN` is correct and has `Copilot Requests` permission.
* **Images not recognized**:
* Ensure `MODEL_ID` is a model that supports multimodal input.
* **Thinking not shown**:
* Ensure **streaming is enabled** and the selected model supports reasoning output.
* **CLI Installation Failed**:
* Ensure the OpenWebUI container has internet access.
* You can manually download the CLI and specify `CLI_PATH` in Valves.

View File

@@ -0,0 +1,114 @@
# GitHub Copilot SDK 官方管道
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.2.3 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
这是一个用于 [OpenWebUI](https://github.com/open-webui/open-webui) 的高级 Pipe 函数,允许你直接在 OpenWebUI 中使用 GitHub Copilot 模型(如 `gpt-5`, `gpt-5-mini`, `claude-sonnet-4.5`)。它基于官方 [GitHub Copilot SDK for Python](https://github.com/github/copilot-sdk) 构建,提供了原生级的集成体验。
## 🚀 最新特性 (v0.2.3)
* **🧩 用户级覆盖**:新增 `REASONING_EFFORT``CLI_PATH``DEBUG``SHOW_THINKING``MODEL_ID` 的用户级覆盖。
* **🧠 思考输出可靠性**:思考显示会遵循用户设置,并正确传递到流式输出中。
* **📝 格式化输出增强**:自动优化输出格式(短句、段落、列表),并解决了在某些界面下显示过于紧凑的问题。
## ✨ 核心特性
* **🚀 官方 SDK 集成**:基于官方 SDK稳定可靠。
* **🛠️ 自定义工具支持**:内置示例工具(随机数)。易于扩展自定义工具。
* **💬 多轮对话支持**自动拼接历史上下文Copilot 能理解你的前文。
* **🌊 流式输出 (Streaming)**:支持打字机效果,响应迅速。
* **🖼️ 多模态支持**:支持上传图片,自动转换为附件发送给 Copilot需模型支持
* **🛠️ 零配置安装**:自动检测并下载 GitHub Copilot CLI开箱即用。
* **🔑 安全认证**:支持 Fine-grained Personal Access Tokens权限最小化。
* **🐛 调试模式**:内置详细的日志输出(浏览器控制台),方便排查问题。
* **⚠️ 仅支持单节点**:由于会话状态存储在本地,本插件目前仅支持 OpenWebUI 单节点部署,或开启了会话粘性 (Sticky Session) 的多节点集群。
## 📦 安装与使用
### 1. 导入函数
1. 打开 OpenWebUI。
2. 进入 **Workspace** -> **Functions**
3. 点击 **+** (创建函数)。
4.`github_copilot_sdk_cn.py` 的内容完整粘贴进去。
5. 保存。
### 2. 配置 Valves (设置)
在函数列表中找到 "GitHub Copilot",点击 **⚙️ (Valves)** 图标进行配置:
| 参数 | 说明 | 默认值 |
| :--- | :--- | :--- |
| **GH_TOKEN** | **(必填)** 你的 GitHub Token。 | - |
| **MODEL_ID** | 使用的模型名称。 | `gpt-5-mini` |
| **CLI_PATH** | Copilot CLI 的路径。如果未找到会自动下载。 | `/usr/local/bin/copilot` |
| **DEBUG** | 是否开启调试日志(输出到浏览器控制台)。 | `False` |
| **LOG_LEVEL** | Copilot CLI 日志级别: none, error, warning, info, debug, all。 | `error` |
| **SHOW_THINKING** | 是否显示模型推理/思考过程(需开启流式 + 模型支持)。 | `True` |
| **SHOW_WORKSPACE_INFO** | 在调试模式下显示会话工作空间路径和摘要。 | `True` |
| **EXCLUDE_KEYWORDS** | 排除包含这些关键词的模型 (逗号分隔)。 | - |
| **WORKSPACE_DIR** | 文件操作的受限工作目录。 | - |
| **INFINITE_SESSION** | 启用无限会话 (自动上下文压缩)。 | `True` |
| **COMPACTION_THRESHOLD** | 后台压缩阈值 (0.0-1.0)。 | `0.8` |
| **BUFFER_THRESHOLD** | 缓冲耗尽阈值 (0.0-1.0)。 | `0.95` |
| **TIMEOUT** | 流式数据块超时时间 (秒)。 | `300` |
| **CUSTOM_ENV_VARS** | 自定义环境变量 (JSON 格式)。 | - |
| **ENABLE_TOOLS** | 启用自定义工具 (示例:随机数)。 | `False` |
| **AVAILABLE_TOOLS** | 可用工具: 'all' 或逗号分隔列表。 | `all` |
| **REASONING_EFFORT** | 推理强度级别low, medium, high。`gpt-5.2-codex`额外支持`xhigh`。 | `medium` |
| **ENFORCE_FORMATTING** | 是否强制添加格式化指导,以提高输出可读性。 | `True` |
#### 用户 Valves按用户覆盖
以下设置可按用户单独配置(覆盖全局 Valves
| 参数 | 说明 | 默认值 |
| :--- | :--- | :--- |
| **REASONING_EFFORT** | 推理强度级别low/medium/high/xhigh。 | - |
| **CLI_PATH** | 自定义 Copilot CLI 路径。 | - |
| **DEBUG** | 是否启用技术调试日志。 | `False` |
| **SHOW_THINKING** | 是否显示思考过程(需开启流式 + 模型支持)。 | `True` |
| **MODEL_ID** | 自定义模型 ID。 | - |
### 3. 使用自定义工具 (🆕 可选)
本 Pipe 内置了 **1 个示例工具**来展示工具调用功能:
* **🎲 generate_random_number**:生成随机整数
**启用方法:**
1. 在 Valves 中设置 `ENABLE_TOOLS: true`
2. 尝试问:“给我一个随机数”
**📚 详细使用说明和创建自定义工具,请参阅 [TOOLS_USAGE.md](TOOLS_USAGE.md)**
### 4. 获取 GH_TOKEN
为了安全起见,推荐使用 **Fine-grained Personal Access Token**
1. 访问 [GitHub Token Settings](https://github.com/settings/tokens?type=beta)。
2. 点击 **Generate new token**
3. **Repository access**: 选择 **Public repositories** (必须选择此项才能看到 Copilot 权限)。
4. **Permissions**:
* 点击 **Account permissions**
* 找到 **Copilot Requests** (默认即为 **Read-only**,无需手动修改)。
5. 生成并复制 Token。
## 📋 依赖说明
该 Pipe 会自动尝试安装以下依赖(如果环境中缺失):
* `github-copilot-sdk` (Python 包)
* `github-copilot-cli` (二进制文件,通过官方脚本安装)
## ⚠️ 常见问题
* **一直显示 "Waiting..."**
* 检查 `GH_TOKEN` 是否正确且拥有 `Copilot Requests` 权限。
* **图片无法识别**
* 确保 `MODEL_ID` 是支持多模态的模型。
* **CLI 安装失败**
* 确保 OpenWebUI 容器有外网访问权限。
* 你可以手动下载 CLI 并挂载到容器中,然后在 Valves 中指定 `CLI_PATH`
* **看不到思考过程**
* 确认已开启**流式输出**,且所选模型支持推理输出。

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -139,42 +139,45 @@ def compare_versions(current: list[dict], previous_file: str) -> dict[str, list[
print(f"Error parsing {previous_file}", file=sys.stderr)
return {"added": current, "updated": [], "removed": []}
# Create lookup dictionaries by title
# Helper to extract title/version from either simple dict or raw post object
# Create lookup dictionaries by file_path (fallback to title)
# Helper to extract title/version/file_path from either simple dict or raw post object
def get_info(p):
if "data" in p and "function" in p["data"]:
# It's a raw post object
manifest = p["data"]["function"].get("meta", {}).get("manifest", {})
title = manifest.get("title") or p.get("title")
version = manifest.get("version", "0.0.0")
return title, version, p
file_path = p.get("file_path")
return title, version, file_path, p
else:
# It's a simple dict
return p.get("title"), p.get("version"), p
return p.get("title"), p.get("version"), p.get("file_path"), p
current_by_title = {}
current_by_key = {}
for p in current:
title, _, _ = get_info(p)
if title:
current_by_title[title] = p
title, _, file_path, _ = get_info(p)
key = file_path or title
if key:
current_by_key[key] = p
previous_by_title = {}
previous_by_key = {}
for p in previous:
title, _, _ = get_info(p)
if title:
previous_by_title[title] = p
title, _, file_path, _ = get_info(p)
key = file_path or title
if key:
previous_by_key[key] = p
result = {"added": [], "updated": [], "removed": []}
# Find added and updated plugins
for title, plugin in current_by_title.items():
curr_title, curr_ver, _ = get_info(plugin)
for key, plugin in current_by_key.items():
curr_title, curr_ver, _file_path, _ = get_info(plugin)
if title not in previous_by_title:
if key not in previous_by_key:
result["added"].append(plugin)
else:
prev_plugin = previous_by_title[title]
_, prev_ver, _ = get_info(prev_plugin)
prev_plugin = previous_by_key[key]
_, prev_ver, _prev_file_path, _ = get_info(prev_plugin)
if curr_ver != prev_ver:
result["updated"].append(
@@ -185,8 +188,8 @@ def compare_versions(current: list[dict], previous_file: str) -> dict[str, list[
)
# Find removed plugins
for title, plugin in previous_by_title.items():
if title not in current_by_title:
for key, plugin in previous_by_key.items():
if key not in current_by_key:
result["removed"].append(plugin)
return result
@@ -217,6 +220,23 @@ def format_markdown_table(plugins: list[dict]) -> str:
return "\n".join(lines)
def _get_readme_url(file_path: str) -> str:
"""
Generate GitHub README URL from plugin file path.
从插件文件路径生成 GitHub README 链接。
"""
if not file_path:
return ""
# Extract plugin directory (e.g., plugins/filters/folder-memory/folder_memory.py -> plugins/filters/folder-memory)
from pathlib import Path
plugin_dir = Path(file_path).parent
# Convert to GitHub URL
return (
f"https://github.com/Fu-Jie/awesome-openwebui/blob/main/{plugin_dir}/README.md"
)
def format_release_notes(
comparison: dict[str, list], ignore_removed: bool = False
) -> str:
@@ -229,9 +249,12 @@ def format_release_notes(
if comparison["added"]:
lines.append("### 新增插件 / New Plugins")
for plugin in comparison["added"]:
readme_url = _get_readme_url(plugin.get("file_path", ""))
lines.append(f"- **{plugin['title']}** v{plugin['version']}")
if plugin.get("description"):
lines.append(f" - {plugin['description']}")
if readme_url:
lines.append(f" - 📖 [README / 文档]({readme_url})")
lines.append("")
if comparison["updated"]:
@@ -258,7 +281,10 @@ def format_release_notes(
)
prev_ver = prev_manifest.get("version") or prev.get("version")
readme_url = _get_readme_url(curr.get("file_path", ""))
lines.append(f"- **{curr_title}**: v{prev_ver} → v{curr_ver}")
if readme_url:
lines.append(f" - 📖 [README / 文档]({readme_url})")
lines.append("")
if comparison["removed"] and not ignore_removed:

View File

@@ -473,10 +473,27 @@ class OpenWebUICommunityClient:
# 查找 README
readme_content = self._find_readme(file_path)
# 获取远程帖子信息(提前获取,用于判断是否需要上传图片)
remote_post = None
if post_id:
remote_post = self.get_post(post_id)
# 查找并上传图片
media_urls = None
image_path = self._find_image(file_path)
if image_path:
# 决定是否上传图片
should_upload_image = True
if remote_post:
remote_media = remote_post.get("media", [])
if remote_media and len(remote_media) > 0:
# 远程已有图片,跳过上传以避免覆盖(防止出现空白图片问题)
print(
f" Remote post already has images. Skipping auto-upload to preserve existing media."
)
should_upload_image = False
if image_path and should_upload_image:
print(f" Found image: {os.path.basename(image_path)}")
image_url = self.upload_image(image_path)
if image_url:
@@ -500,7 +517,8 @@ class OpenWebUICommunityClient:
post_id = existing_post.get("id")
print(f" Found existing post: {title} (ID: {post_id})")
self._inject_id_to_file(file_path, post_id)
# post_id 已设置,后续将进入更新流程
# post_id 已设置,重新获取 remote_post 以便后续版本检查
remote_post = self.get_post(post_id)
else:
# 2. 如果没找到,且允许自动创建,则创建
@@ -522,11 +540,6 @@ class OpenWebUICommunityClient:
return True, f"Created new post (ID: {new_post_id})"
return False, "Failed to create new post"
# 获取远程帖子信息(只需获取一次)
remote_post = None
if post_id:
remote_post = self.get_post(post_id)
# 版本检查(仅对更新有效)
if not force and local_version and remote_post:
remote_version = (