Compare commits

..

59 Commits

Author SHA1 Message Date
fujie
c4d36c32a0 docs(pipes): fix copilot sdk tools link
Point tools usage link to GitHub source
2026-01-28 11:15:23 +08:00
fujie
6adbcd8d42 fix(scripts): resolve plugin scan NameError
Define metadata per file before use
2026-01-28 11:11:38 +08:00
fujie
89c039fe33 fix(actions): bump smart mind map to 0.9.2
Align mind map language rule with input text

Update plugin docs and README versions
2026-01-28 11:09:18 +08:00
github-actions[bot]
3a73ccfaa7 chore: update community stats - points increased (166 -> 167) 2026-01-28 01:37:45 +00:00
github-actions[bot]
7eff265e1c chore: update community stats - points increased (161 -> 166) 2026-01-27 21:07:14 +00:00
github-actions[bot]
989b45fc16 chore: update community stats - points increased (157 -> 161) 2026-01-27 20:09:28 +00:00
fujie
163d8ce8bd fix(scripts): exclude debug directory from release scanning 2026-01-28 02:30:38 +08:00
fujie
4e32e1a1da fix(scripts): normalize plugin paths in version extraction to prevent false positives in release diffs 2026-01-28 02:28:20 +08:00
github-actions[bot]
070e9f2456 chore: update community stats - plugin version updated 2026-01-27 18:15:04 +00:00
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
66 changed files with 10260 additions and 334 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)
@@ -841,8 +923,8 @@ Filter 实例是**单例 (Singleton)**。
### 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-20 19:10
> 🕐 Auto-updated: 2026-01-28 09:37
| 👤 Author | 👥 Followers | ⭐ Points | 🏆 Contributions |
|:---:|:---:|:---:|:---:|
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **137** | **134** | **25** |
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **165** | **167** | **35** |
| 📝 Posts | ⬇️ Downloads | 👁️ Views | 👍 Upvotes | 💾 Saves |
|:---:|:---:|:---:|:---:|:---:|
| **16** | **1887** | **22101** | **120** | **147** |
| **19** | **2553** | **29974** | **150** | **198** |
### 🔥 Top 6 Popular Plugins
> 🕐 Auto-updated: 2026-01-20 19:10
> 🕐 Auto-updated: 2026-01-28 09:37
| Rank | Plugin | Version | Downloads | Views | Updated |
|:---:|------|:---:|:---:|:---:|:---:|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 550 | 4939 | 2026-01-17 |
| 🥈 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 282 | 2667 | 2026-01-18 |
| 🥉 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 215 | 844 | 2026-01-07 |
| 4⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.1 | 189 | 2051 | 2026-01-20 |
| 5⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 170 | 1457 | 2026-01-17 |
| 6⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 144 | 2395 | 2026-01-17 |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 670 | 5919 | 2026-01-17 |
| 🥈 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.5.0 | 441 | 4010 | 2026-01-27 |
| 🥉 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 265 | 1099 | 2026-01-07 |
| 4⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 240 | 2593 | 2026-01-21 |
| 5⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 240 | 1951 | 2026-01-17 |
| 6⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 179 | 2805 | 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,12 +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
@@ -101,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-20 19:10
> 🕐 自动更新于 2026-01-28 09:37
| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🏆 贡献 |
|:---:|:---:|:---:|:---:|
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **137** | **134** | **25** |
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **165** | **167** | **35** |
| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 |
|:---:|:---:|:---:|:---:|:---:|
| **16** | **1887** | **22101** | **120** | **147** |
| **19** | **2553** | **29974** | **150** | **198** |
### 🔥 热门插件 Top 6
> 🕐 自动更新于 2026-01-20 19:10
> 🕐 自动更新于 2026-01-28 09:37
| 排名 | 插件 | 版本 | 下载 | 浏览 | 更新日期 |
|:---:|------|:---:|:---:|:---:|:---:|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 550 | 4939 | 2026-01-17 |
| 🥈 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 282 | 2667 | 2026-01-18 |
| 🥉 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 215 | 844 | 2026-01-07 |
| 4⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.1 | 189 | 2051 | 2026-01-20 |
| 5⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 170 | 1457 | 2026-01-17 |
| 6⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 144 | 2395 | 2026-01-17 |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 670 | 5919 | 2026-01-17 |
| 🥈 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.5.0 | 441 | 4010 | 2026-01-27 |
| 🥉 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 265 | 1099 | 2026-01-07 |
| 4⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 240 | 2593 | 2026-01-21 |
| 5⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 240 | 1951 | 2026-01-17 |
| 6⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 179 | 2805 | 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,6 +49,7 @@ 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`): 自动从对话中提取项目规则并注入到文件夹系统提示词中。
@@ -57,9 +59,12 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
- **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)
@@ -107,6 +112,7 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
### 贡献代码
如果你有优质的提示词或插件想要分享:
1. Fork 本仓库。
2. 将你的文件添加到对应的 `prompts/``plugins/` 目录。
3. 提交 Pull Request。

View File

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

View File

@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"label": "followers",
"message": "137",
"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": "134",
"message": "167",
"color": "orange"
}

View File

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

View File

@@ -1,14 +1,16 @@
{
"total_posts": 16,
"total_downloads": 1887,
"total_views": 22101,
"total_upvotes": 120,
"total_posts": 19,
"total_downloads": 2553,
"total_views": 29974,
"total_upvotes": 150,
"total_downvotes": 2,
"total_saves": 147,
"total_comments": 24,
"total_saves": 198,
"total_comments": 40,
"by_type": {
"action": 14,
"unknown": 2
"pipe": 1,
"unknown": 3,
"filter": 1
},
"posts": [
{
@@ -18,10 +20,10 @@
"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": 550,
"views": 4939,
"upvotes": 15,
"saves": 30,
"downloads": 670,
"views": 5919,
"upvotes": 17,
"saves": 38,
"comments": 11,
"created_at": "2025-12-30",
"updated_at": "2026-01-17",
@@ -31,16 +33,16 @@
"title": "📊 Smart Infographic (AntV)",
"slug": "smart_infographic_ad6f0c7f",
"type": "action",
"version": "1.4.9",
"version": "1.5.0",
"author": "Fu-Jie",
"description": "AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.",
"downloads": 282,
"views": 2667,
"upvotes": 14,
"saves": 21,
"comments": 3,
"downloads": 441,
"views": 4010,
"upvotes": 19,
"saves": 28,
"comments": 10,
"created_at": "2025-12-28",
"updated_at": "2026-01-18",
"updated_at": "2026-01-27",
"url": "https://openwebui.com/posts/smart_infographic_ad6f0c7f"
},
{
@@ -50,8 +52,8 @@
"version": "0.3.7",
"author": "Fu-Jie",
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
"downloads": 215,
"views": 844,
"downloads": 265,
"views": 1099,
"upvotes": 4,
"saves": 6,
"comments": 0,
@@ -63,16 +65,16 @@
"title": "Async Context Compression",
"slug": "async_context_compression_b1655bc8",
"type": "action",
"version": "1.2.1",
"version": "1.2.2",
"author": "Fu-Jie",
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
"downloads": 189,
"views": 2051,
"downloads": 240,
"views": 2593,
"upvotes": 9,
"saves": 22,
"saves": 27,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-20",
"updated_at": "2026-01-21",
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
},
{
@@ -82,10 +84,10 @@
"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": 170,
"views": 1457,
"downloads": 240,
"views": 1951,
"upvotes": 8,
"saves": 17,
"saves": 21,
"comments": 0,
"created_at": "2026-01-03",
"updated_at": "2026-01-17",
@@ -98,10 +100,10 @@
"version": "0.2.4",
"author": "Fu-Jie",
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
"downloads": 144,
"views": 2395,
"upvotes": 10,
"saves": 12,
"downloads": 179,
"views": 2805,
"upvotes": 11,
"saves": 13,
"comments": 2,
"created_at": "2025-12-30",
"updated_at": "2026-01-17",
@@ -114,10 +116,10 @@
"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": 96,
"views": 2234,
"downloads": 164,
"views": 2910,
"upvotes": 10,
"saves": 17,
"saves": 22,
"comments": 5,
"created_at": "2026-01-12",
"updated_at": "2026-01-19",
@@ -130,10 +132,10 @@
"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": 73,
"views": 707,
"downloads": 96,
"views": 880,
"upvotes": 4,
"saves": 7,
"saves": 8,
"comments": 0,
"created_at": "2026-01-08",
"updated_at": "2026-01-08",
@@ -146,11 +148,11 @@
"version": "0.4.3",
"author": "Fu-Jie",
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
"downloads": 65,
"views": 1335,
"downloads": 90,
"views": 1710,
"upvotes": 11,
"saves": 3,
"comments": 1,
"saves": 4,
"comments": 4,
"created_at": "2026-01-04",
"updated_at": "2026-01-17",
"url": "https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0"
@@ -159,18 +161,34 @@
"title": "📊 智能信息图 (AntV Infographic)",
"slug": "智能信息图_e04a48ff",
"type": "action",
"version": "1.4.9",
"version": "1.5.0",
"author": "Fu-Jie",
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
"downloads": 43,
"views": 704,
"upvotes": 6,
"downloads": 48,
"views": 811,
"upvotes": 7,
"saves": 0,
"comments": 0,
"created_at": "2025-12-28",
"updated_at": "2026-01-17",
"updated_at": "2026-01-27",
"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": 29,
"views": 834,
"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": 24,
"views": 407,
"upvotes": 3,
"downloads": 28,
"views": 468,
"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": 23,
"views": 518,
"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,9 +228,9 @@
"version": "0.2.4",
"author": "Fu-Jie",
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
"downloads": 16,
"views": 453,
"upvotes": 5,
"downloads": 20,
"views": 531,
"upvotes": 6,
"saves": 1,
"comments": 0,
"created_at": "2025-12-30",
@@ -204,20 +238,20 @@
"url": "https://openwebui.com/posts/闪记卡生成插件_4a31eac3"
},
{
"title": "异步上下文压缩",
"slug": "异步上下文压缩_5c0617cb",
"type": "action",
"version": "1.2.1",
"title": "GitHub Copilot Official SDK Pipe",
"slug": "github_copilot_official_sdk_pipe_ce96f7b4",
"type": "pipe",
"version": "0.2.3",
"author": "Fu-Jie",
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
"downloads": 14,
"views": 377,
"upvotes": 5,
"saves": 1,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-20",
"url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb"
"description": "Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, infinite sessions, and frontend debug logging.",
"downloads": 10,
"views": 517,
"upvotes": 8,
"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": "精读",
@@ -226,8 +260,8 @@
"version": "1.0.0",
"author": "Fu-Jie",
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
"downloads": 6,
"views": 261,
"downloads": 10,
"views": 321,
"upvotes": 3,
"saves": 1,
"comments": 0,
@@ -235,6 +269,22 @@
"updated_at": "2026-01-08",
"url": "https://openwebui.com/posts/精读_99830b0f"
},
{
"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": 705,
"upvotes": 7,
"saves": 9,
"comments": 5,
"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,7 +293,7 @@
"author": "",
"description": "",
"downloads": 0,
"views": 62,
"views": 101,
"upvotes": 1,
"saves": 0,
"comments": 0,
@@ -259,7 +309,7 @@
"author": "",
"description": "",
"downloads": 0,
"views": 1208,
"views": 1291,
"upvotes": 12,
"saves": 8,
"comments": 2,
@@ -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": 137,
"following": 2,
"total_points": 134,
"post_points": 118,
"comment_points": 16,
"contributions": 25
"followers": 165,
"following": 3,
"total_points": 167,
"post_points": 148,
"comment_points": 19,
"contributions": 35
}
}

View File

@@ -1,40 +1,45 @@
# 📊 OpenWebUI Community Stats Report
> 📅 Updated: 2026-01-20 19:10
> 📅 Updated: 2026-01-28 09:37
## 📈 Overview
| Metric | Value |
|------|------|
| 📝 Total Posts | 16 |
| ⬇️ Total Downloads | 1887 |
| 👁️ Total Views | 22101 |
| 👍 Total Upvotes | 120 |
| 💾 Total Saves | 147 |
| 💬 Total Comments | 24 |
| 📝 Total Posts | 19 |
| ⬇️ Total Downloads | 2553 |
| 👁️ Total Views | 29974 |
| 👍 Total Upvotes | 150 |
| 💾 Total Saves | 198 |
| 💬 Total Comments | 40 |
## 📂 By Type
- **action**: 14
- **unknown**: 2
- **pipe**: 1
- **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 | 550 | 4939 | 15 | 30 | 2026-01-17 |
| 2 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 282 | 2667 | 14 | 21 | 2026-01-18 |
| 3 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 215 | 844 | 4 | 6 | 2026-01-07 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.1 | 189 | 2051 | 9 | 22 | 2026-01-20 |
| 5 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 170 | 1457 | 8 | 17 | 2026-01-17 |
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 144 | 2395 | 10 | 12 | 2026-01-17 |
| 7 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 96 | 2234 | 10 | 17 | 2026-01-19 |
| 8 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 73 | 707 | 4 | 7 | 2026-01-08 |
| 9 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 65 | 1335 | 11 | 3 | 2026-01-17 |
| 10 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 43 | 704 | 6 | 0 | 2026-01-17 |
| 11 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 24 | 407 | 3 | 1 | 2026-01-17 |
| 12 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 16 | 453 | 5 | 1 | 2026-01-17 |
| 13 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.1 | 14 | 377 | 5 | 1 | 2026-01-20 |
| 14 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 6 | 261 | 3 | 1 | 2026-01-08 |
| 15 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 62 | 1 | 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 | 1208 | 12 | 8 | 2026-01-10 |
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 670 | 5919 | 17 | 38 | 2026-01-17 |
| 2 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.5.0 | 441 | 4010 | 19 | 28 | 2026-01-27 |
| 3 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 265 | 1099 | 4 | 6 | 2026-01-07 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 240 | 2593 | 9 | 27 | 2026-01-21 |
| 5 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 240 | 1951 | 8 | 21 | 2026-01-17 |
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 179 | 2805 | 11 | 13 | 2026-01-17 |
| 7 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 164 | 2910 | 10 | 22 | 2026-01-19 |
| 8 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 96 | 880 | 4 | 8 | 2026-01-08 |
| 9 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 90 | 1710 | 11 | 4 | 2026-01-17 |
| 10 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.5.0 | 48 | 811 | 7 | 0 | 2026-01-27 |
| 11 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 29 | 834 | 4 | 7 | 2026-01-20 |
| 12 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 28 | 468 | 4 | 1 | 2026-01-17 |
| 13 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 23 | 518 | 5 | 2 | 2026-01-21 |
| 14 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 20 | 531 | 6 | 1 | 2026-01-17 |
| 15 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | 0.2.3 | 10 | 517 | 8 | 2 | 2026-01-26 |
| 16 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 10 | 321 | 3 | 1 | 2026-01-08 |
| 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 | 705 | 7 | 9 | 2026-01-25 |
| 18 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 101 | 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-20 19:10
> 📅 更新时间: 2026-01-28 09:37
## 📈 总览
| 指标 | 数值 |
|------|------|
| 📝 发布数量 | 16 |
| ⬇️ 总下载量 | 1887 |
| 👁️ 总浏览量 | 22101 |
| 👍 总点赞数 | 120 |
| 💾 总收藏数 | 147 |
| 💬 总评论数 | 24 |
| 📝 发布数量 | 19 |
| ⬇️ 总下载量 | 2553 |
| 👁️ 总浏览量 | 29974 |
| 👍 总点赞数 | 150 |
| 💾 总收藏数 | 198 |
| 💬 总评论数 | 40 |
## 📂 按类型分类
- **action**: 14
- **unknown**: 2
- **pipe**: 1
- **unknown**: 3
- **filter**: 1
## 📋 发布列表
| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 550 | 4939 | 15 | 30 | 2026-01-17 |
| 2 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 282 | 2667 | 14 | 21 | 2026-01-18 |
| 3 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 215 | 844 | 4 | 6 | 2026-01-07 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.1 | 189 | 2051 | 9 | 22 | 2026-01-20 |
| 5 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 170 | 1457 | 8 | 17 | 2026-01-17 |
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 144 | 2395 | 10 | 12 | 2026-01-17 |
| 7 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 96 | 2234 | 10 | 17 | 2026-01-19 |
| 8 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 73 | 707 | 4 | 7 | 2026-01-08 |
| 9 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 65 | 1335 | 11 | 3 | 2026-01-17 |
| 10 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 43 | 704 | 6 | 0 | 2026-01-17 |
| 11 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 24 | 407 | 3 | 1 | 2026-01-17 |
| 12 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 16 | 453 | 5 | 1 | 2026-01-17 |
| 13 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.1 | 14 | 377 | 5 | 1 | 2026-01-20 |
| 14 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 6 | 261 | 3 | 1 | 2026-01-08 |
| 15 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 62 | 1 | 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 | 1208 | 12 | 8 | 2026-01-10 |
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 670 | 5919 | 17 | 38 | 2026-01-17 |
| 2 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.5.0 | 441 | 4010 | 19 | 28 | 2026-01-27 |
| 3 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 265 | 1099 | 4 | 6 | 2026-01-07 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 240 | 2593 | 9 | 27 | 2026-01-21 |
| 5 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 240 | 1951 | 8 | 21 | 2026-01-17 |
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 179 | 2805 | 11 | 13 | 2026-01-17 |
| 7 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 164 | 2910 | 10 | 22 | 2026-01-19 |
| 8 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 96 | 880 | 4 | 8 | 2026-01-08 |
| 9 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 90 | 1710 | 11 | 4 | 2026-01-17 |
| 10 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.5.0 | 48 | 811 | 7 | 0 | 2026-01-27 |
| 11 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 29 | 834 | 4 | 7 | 2026-01-20 |
| 12 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 28 | 468 | 4 | 1 | 2026-01-17 |
| 13 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 23 | 518 | 5 | 2 | 2026-01-21 |
| 14 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 20 | 531 | 6 | 1 | 2026-01-17 |
| 15 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | 0.2.3 | 10 | 517 | 8 | 2 | 2026-01-26 |
| 16 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 10 | 321 | 3 | 1 | 2026-01-08 |
| 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 | 705 | 7 | 9 | 2026-01-25 |
| 18 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 101 | 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 @@
# Smart Mind Map
<span class="category-badge action">Action</span>
<span class="version-badge">v0.9.1</span>
<span class="version-badge">v0.9.2</span>
Intelligently analyzes text content and generates interactive mind maps for better visualization and understanding.
@@ -17,7 +17,7 @@ The Smart Mind Map plugin transforms text content into beautiful, interactive mi
- :material-gesture-swipe: **Rich Controls**: Zoom, reset view, expand level selector (All/2/3) and fullscreen
- :material-palette: **Theme Aware**: Auto-detects OpenWebUI light/dark theme with manual toggle
- :material-download: **One-Click Export**: Download high-res PNG, copy SVG, or copy Markdown source
- :material-translate: **Multi-language**: Adapts output language to the user context
- :material-translate: **Multi-language**: Matches output language to the input text
---

View File

@@ -1,7 +1,7 @@
# Smart Mind Map智能思维导图
<span class="category-badge action">Action</span>
<span class="version-badge">v0.9.1</span>
<span class="version-badge">v0.9.2</span>
智能分析文本内容,生成交互式思维导图,帮助你更直观地理解信息结构。
@@ -17,7 +17,7 @@ Smart Mind Map 会将文本转换成漂亮的交互式思维导图。插件会
- :material-gesture-swipe: **丰富控制**:缩放/重置、展开层级(全部/2/3 级)与全屏
- :material-palette: **主题感知**:自动检测 OpenWebUI 亮/暗色主题并支持手动切换
- :material-download: **一键导出**:下载高分辨率 PNG、复制 SVG 或 Markdown
- :material-translate: **多语言**根据用户语言自动输出
- :material-translate: **多语言**:输出语言与输入文本一致
---

View File

@@ -1,7 +1,7 @@
# Async Context Compression
<span class="category-badge filter">Filter</span>
<span class="version-badge">v1.2.1</span>
<span class="version-badge">v1.2.2</span>
Reduces token consumption in long conversations through intelligent summarization while maintaining conversational coherence.

View File

@@ -1,7 +1,7 @@
# Async Context Compression异步上下文压缩
<span class="category-badge filter">Filter</span>
<span class="version-badge">v1.2.1</span>
<span class="version-badge">v1.2.2</span>
通过智能摘要减少长对话的 token 消耗,同时保持对话连贯。

View File

@@ -1,4 +1,15 @@
# 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.
@@ -11,6 +22,10 @@ This ensures that all future conversations within that folder share the same evo
- **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).

View File

@@ -1,4 +1,15 @@
# 文件夹记忆 (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 的智能上下文过滤器插件。它能自动从文件夹内的对话中提取一致性的“项目规则”,并将其回写到文件夹的系统提示词中。
@@ -11,6 +22,10 @@
- **异步处理**:在后台运行,不阻塞用户的聊天体验。
- **ORM 集成**:直接使用 OpenWebUI 的内部模型更新文件夹数据,确保可靠性。
## 前置条件
- **对话必须在文件夹内进行。** 此插件仅在聊天属于某个文件夹时触发(即您需要先在 OpenWebUI 中创建一个文件夹,并在其内部开始对话)。
## 安装指南
1.`folder_memory.py` (或中文版 `folder_memory_cn.py`) 复制到 OpenWebUI 的 `plugins/filters/` 目录(或通过管理员 UI 上传)。

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.2.1
**Version:** 1.2.2
[:octicons-arrow-right-24: Documentation](async-context-compression.md)

View File

@@ -22,7 +22,7 @@ Filter 充当消息管线中的中间件:
通过智能总结减少长对话的 token 消耗,同时保持连贯性。
**版本:** 1.2.1
**版本:** 1.2.2
[:octicons-arrow-right-24: 查看文档](async-context-compression.md)

View File

@@ -48,7 +48,7 @@ OpenWebUI supports four types of plugins, each serving a different purpose:
| Plugin | Type | Description | Version |
|--------|------|-------------|---------|
| [Smart Mind Map](actions/smart-mind-map.md) | Action | Generate interactive mind maps from text | 0.9.1 |
| [Smart Mind Map](actions/smart-mind-map.md) | Action | Generate interactive mind maps from text | 0.9.2 |
| [Smart Infographic](actions/smart-infographic.md) | Action | Transform text into professional infographics | 1.4.9 |
| [Flash Card](actions/flash-card.md) | Action | Create beautiful learning flashcards | 0.2.4 |
| [Export to Excel](actions/export-to-excel.md) | Action | Export chat history to Excel files | 0.3.7 |

View File

@@ -48,7 +48,7 @@ OpenWebUI 支持四种类型的插件,每种都有不同的用途:
| 插件 | 类型 | 描述 | 版本 |
|--------|------|-------------|---------|
| [Smart Mind Map智能思维导图](actions/smart-mind-map.md) | Action | 从文本生成交互式思维导图 | 0.9.1 |
| [Smart Mind Map智能思维导图](actions/smart-mind-map.md) | Action | 从文本生成交互式思维导图 | 0.9.2 |
| [Smart Infographic智能信息图](actions/smart-infographic.md) | Action | 将文本转成专业信息图 | 1.4.9 |
| [Flash Card闪记卡](actions/flash-card.md) | Action | 生成精美学习卡片 | 0.2.4 |
| [Export to Excel导出到 Excel](actions/export-to-excel.md) | Action | 导出聊天记录为 Excel | 0.3.7 |

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 [TOOLS_USAGE.md](https://github.com/Fu-Jie/awesome-openwebui/blob/main/plugins/debug/github-copilot-sdk/guides/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. 尝试问:“给我一个随机数”
**📚 详细使用说明和创建自定义工具,请参阅 [TOOLS_USAGE.md](https://github.com/Fu-Jie/awesome-openwebui/blob/main/plugins/debug/github-copilot-sdk/guides/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 渲染:

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

@@ -2,17 +2,14 @@
Smart Mind Map is a powerful OpenWebUI action plugin that intelligently analyzes long-form text content and automatically generates interactive mind maps, helping users structure and visualize knowledge.
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.9.1 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.9.2 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
## What's New in v0.9.1
## What's New in v0.9.2
**New Feature: Image Output Mode**
**Language Rule Alignment**
- **Static Image Support**: Added `OUTPUT_MODE` configuration parameter.
- `html` (default): Interactive HTML mind map.
- `image`: Static SVG image embedded directly in Markdown (**No HTML code output**, cleaner chat history).
- **Efficient Storage**: Image mode uploads SVG to `/api/v1/files`, avoiding huge base64 strings in chat history.
- **Smart Features**: Auto-responsive width and automatic theme detection (light/dark) for generated images.
- **Input Language First**: Mind map output now strictly matches the input text language.
- **Consistent Behavior**: Matches the infographic language rule for predictable multilingual output.
## Key Features 🔑

View File

@@ -2,17 +2,14 @@
思维导图是一个强大的 OpenWebUI 动作插件,能够智能分析长篇文本内容,自动生成交互式思维导图,帮助用户结构化和可视化知识。
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.9.1 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.9.2 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
## v0.9.1 更新亮点
## v0.9.2 更新亮点
**新功能:图片输出模式**
**语言规则对齐**
- **静态图片支持**:新增 `OUTPUT_MODE` 配置参数
- `html`(默认):交互式 HTML 思维导图
- `image`:静态 SVG 图片直接嵌入 Markdown**不输出 HTML 代码**,聊天记录更简洁)。
- **高效存储**:图片模式将 SVG 上传至 `/api/v1/files`,避免聊天记录中出现超长 Base64 字符串。
- **智能特性**:生成的图片支持自动响应式宽度和自动主题检测(亮色/暗色)。
- **输入语言优先**:导图输出严格与输入文本语言一致
- **一致性提升**:与信息图语言规则保持一致,多语言输出更可预期
## 核心特性 🔑

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
funding_url: https://github.com/Fu-Jie/awesome-openwebui
version: 0.9.1
version: 0.9.2
openwebui_id: 3094c59a-b4dd-4e0c-9449-15e2dd547dc4
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4=
description: Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.
@@ -33,7 +33,8 @@ SYSTEM_PROMPT_MINDMAP_ASSISTANT = """
You are a professional mind map generation assistant, capable of efficiently analyzing long-form text provided by users and structuring its core themes, key concepts, branches, and sub-branches into standard Markdown list syntax for rendering by Markmap.js.
Please strictly follow these guidelines:
- **Language**: All output must be in the language specified by the user.
- **Language**: All output must be in the exact same language as the input text (the text you are analyzing).
- **Format Consistency**: Even if this system prompt is in English, if the user input is in Chinese, the mind map content must be in Chinese. If input is Japanese, output Japanese.
- **Format**: Your output must strictly be in Markdown list format, wrapped with ```markdown and ```.
- Use `#` to define the central theme (root node).
- Use `-` with two-space indentation to represent branches and sub-branches.
@@ -811,7 +812,11 @@ class Action:
"Sunday": "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]:
"""Extract basic user context with safe fallbacks."""
if isinstance(__user__, (list, tuple)):
user_data = __user__[0] if __user__ else {}
@@ -820,10 +825,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(
@@ -1369,8 +1396,8 @@ class Action:
__metadata__: Optional[dict] = None,
__request__: Optional[Request] = None,
) -> Optional[dict]:
logger.info("Action: Smart Mind Map (v0.9.1) started")
user_ctx = self._get_user_context(__user__)
logger.info("Action: Smart Mind Map (v0.9.2) started")
user_ctx = await self._get_user_context(__user__, __event_call__)
user_language = user_ctx["user_language"]
user_name = user_ctx["user_name"]
user_id = user_ctx["user_id"]

View File

@@ -3,7 +3,7 @@ title: 思维导图
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 0.9.1
version: 0.9.2
openwebui_id: 8d4b097b-219b-4dd2-b509-05fbe6388335
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4=
description: 智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。
@@ -32,7 +32,8 @@ SYSTEM_PROMPT_MINDMAP_ASSISTANT = """
你是一个专业的思维导图生成助手,能够高效地分析用户提供的长篇文本,并将其核心主题、关键概念、分支和子分支结构化为标准的Markdown列表语法,以便Markmap.js进行渲染。
请严格遵循以下指导原则:
- **语言**: 所有输出必须使用用户指定的语言。
- **语言**: 所有输出必须与输入文本(正在分析的文本)保持完全一致的语言。
- **格式一致性**: 即使系统提示词是中文,只要用户输入是英文,导图内容必须是英文;若输入为日文,则输出日文。
- **格式**: 你的输出必须严格为Markdown列表格式,并用```markdown 和 ``` 包裹。
- 使用 `#` 定义中心主题(根节点)。
- 使用 `-` 和两个空格的缩进表示分支和子分支。
@@ -809,7 +810,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]:
"""Extract basic user context with safe fallbacks."""
if isinstance(__user__, (list, tuple)):
user_data = __user__[0] if __user__ else {}
@@ -818,10 +823,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_language": user_data.get("language", "zh-CN"),
"user_id": user_id,
"user_name": user_name,
"user_language": user_language,
}
def _get_chat_context(
@@ -1348,8 +1375,8 @@ class Action:
__metadata__: Optional[dict] = None,
__request__: Optional[Request] = None,
) -> Optional[dict]:
logger.info("Action: 思维导图 (v0.9.1) started")
user_ctx = self._get_user_context(__user__)
logger.info("Action: 思维导图 (v0.9.2) started")
user_ctx = await self._get_user_context(__user__, __event_call__)
user_language = user_ctx["user_language"]
user_name = user_ctx["user_name"]
user_id = user_ctx["user_id"]

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,9 +1,13 @@
# Async Context Compression Filter
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.2.1 | **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.

View File

@@ -1,11 +1,15 @@
# 异步上下文压缩过滤器
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.2.1 | **项目:** [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` 参数以独立控制摘要模型的上下文限制。

View File

@@ -5,7 +5,7 @@ author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
description: Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.
version: 1.2.1
version: 1.2.2
openwebui_id: b1655bc8-6de9-4cad-8cb5-a6f7829a02ce
license: MIT
@@ -839,7 +839,7 @@ class Filter:
except Exception as e:
logger.error(f"Error emitting debug log: {e}")
async def _log(self, message: str, type: str = "info", event_call=None):
async def _log(self, message: str, log_type: str = "info", event_call=None):
"""Unified logging to both backend (print) and frontend (console.log)"""
# Backend logging
if self.valves.debug_mode:
@@ -849,11 +849,11 @@ class Filter:
if self.valves.show_debug_log and event_call:
try:
css = "color: #3b82f6;" # Blue default
if type == "error":
if log_type == "error":
css = "color: #ef4444; font-weight: bold;" # Red
elif type == "warning":
elif log_type == "warning":
css = "color: #f59e0b;" # Orange
elif type == "success":
elif log_type == "success":
css = "color: #10b981; font-weight: bold;" # Green
# Clean message for frontend: remove separators and extra newlines
@@ -999,6 +999,7 @@ class Filter:
# 2. For base models: check messages for role='system'
system_prompt_content = None
# Try to get from DB (custom model)
# Try to get from DB (custom model)
try:
model_id = body.get("model")
@@ -1026,12 +1027,17 @@ class Filter:
# Handle case where params is a JSON string
if isinstance(params, str):
params = json.loads(params)
# Convert Pydantic model to dict if needed
elif hasattr(params, "model_dump"):
params = params.model_dump()
elif hasattr(params, "dict"):
params = params.dict()
# Handle dict or Pydantic object
# Now params should be a dict
if isinstance(params, dict):
system_prompt_content = params.get("system")
else:
# Assume Pydantic model or object
# Fallback: try getattr
system_prompt_content = getattr(params, "system", None)
if system_prompt_content:
@@ -1050,7 +1056,7 @@ class Filter:
if self.valves.show_debug_log and __event_call__:
await self._log(
f"[Inlet] ❌ Failed to parse model params: {e}",
type="error",
log_type="error",
event_call=__event_call__,
)
@@ -1071,7 +1077,7 @@ class Filter:
if self.valves.show_debug_log and __event_call__:
await self._log(
f"[Inlet] ❌ Error fetching system prompt from DB: {e}",
type="error",
log_type="error",
event_call=__event_call__,
)
if self.valves.debug_mode:
@@ -1125,7 +1131,7 @@ class Filter:
if not chat_id:
await self._log(
"[Inlet] ❌ Missing chat_id in metadata, skipping compression",
type="error",
log_type="error",
event_call=__event_call__,
)
return body
@@ -1154,7 +1160,7 @@ class Filter:
else:
await self._log(
f"[Inlet] ⚠️ Invalid Model Configs (Raw: '{raw_config}'): No valid configs parsed. Expected format: 'model_id:threshold:max_context'",
type="warning",
log_type="warning",
event_call=__event_call__,
)
else:
@@ -1258,7 +1264,7 @@ class Filter:
if total_tokens > max_context_tokens:
await self._log(
f"[Inlet] ⚠️ Candidate prompt ({total_tokens} Tokens) exceeds limit ({max_context_tokens}). Reducing history...",
type="warning",
log_type="warning",
event_call=__event_call__,
)
@@ -1395,7 +1401,7 @@ class Filter:
await self._log(
f"[Inlet] Applied summary: {system_info} + Head({len(head_messages)} msg, {head_tokens}t) + Summary({summary_tokens}t) + Tail({len(tail_messages)} msg, {tail_tokens}t) = Total({total_section_tokens}t)",
type="success",
log_type="success",
event_call=__event_call__,
)
@@ -1455,7 +1461,7 @@ class Filter:
if total_tokens > max_context_tokens:
await self._log(
f"[Inlet] ⚠️ Original messages ({total_tokens} Tokens) exceed limit ({max_context_tokens}). Reducing history...",
type="warning",
log_type="warning",
event_call=__event_call__,
)
@@ -1523,7 +1529,7 @@ class Filter:
if not chat_id:
await self._log(
"[Outlet] ❌ Missing chat_id in metadata, skipping compression",
type="error",
log_type="error",
event_call=__event_call__,
)
return body
@@ -1625,7 +1631,7 @@ class Filter:
if current_tokens >= compression_threshold_tokens:
await self._log(
f"[🔍 Background Calculation] ⚡ Compression threshold triggered (Token: {current_tokens} >= {compression_threshold_tokens})",
type="warning",
log_type="warning",
event_call=__event_call__,
)
@@ -1648,7 +1654,7 @@ class Filter:
except Exception as e:
await self._log(
f"[🔍 Background Calculation] ❌ Error: {str(e)}",
type="error",
log_type="error",
event_call=__event_call__,
)
@@ -1687,7 +1693,7 @@ class Filter:
target_compressed_count = max(0, len(messages) - self.valves.keep_last)
await self._log(
f"[🤖 Async Summary Task] ⚠️ target_compressed_count is None, estimating: {target_compressed_count}",
type="warning",
log_type="warning",
event_call=__event_call__,
)
@@ -1734,7 +1740,7 @@ class Filter:
if not summary_model_id:
await self._log(
"[🤖 Async Summary Task] ⚠️ Summary model does not exist, skipping compression",
type="warning",
log_type="warning",
event_call=__event_call__,
)
return
@@ -1765,7 +1771,7 @@ class Filter:
excess_tokens = estimated_input_tokens - max_context_tokens
await self._log(
f"[🤖 Async Summary Task] ⚠️ Middle messages ({middle_tokens} Tokens) + Buffer exceed summary model limit ({max_context_tokens}), need to remove approx {excess_tokens} Tokens",
type="warning",
log_type="warning",
event_call=__event_call__,
)
@@ -1822,7 +1828,7 @@ class Filter:
if not new_summary:
await self._log(
"[🤖 Async Summary Task] ⚠️ Summary generation returned empty result, skipping save",
type="warning",
log_type="warning",
event_call=__event_call__,
)
return
@@ -1851,7 +1857,7 @@ class Filter:
await self._log(
f"[🤖 Async Summary Task] ✅ Complete! New summary length: {len(new_summary)} characters",
type="success",
log_type="success",
event_call=__event_call__,
)
await self._log(
@@ -1957,14 +1963,14 @@ class Filter:
except Exception as e:
await self._log(
f"[Status] Error calculating tokens: {e}",
type="error",
log_type="error",
event_call=__event_call__,
)
except Exception as e:
await self._log(
f"[🤖 Async Summary Task] ❌ Error: {str(e)}",
type="error",
log_type="error",
event_call=__event_call__,
)
@@ -2066,7 +2072,7 @@ Based on the content above, generate the summary:
if not model:
await self._log(
"[🤖 LLM Call] ⚠️ Summary model does not exist, skipping summary generation",
type="warning",
log_type="warning",
event_call=__event_call__,
)
return ""
@@ -2133,7 +2139,7 @@ Based on the content above, generate the summary:
await self._log(
f"[🤖 LLM Call] ✅ Successfully received summary",
type="success",
log_type="success",
event_call=__event_call__,
)
@@ -2154,7 +2160,7 @@ Based on the content above, generate the summary:
await self._log(
f"[🤖 LLM Call] ❌ {error_message}",
type="error",
log_type="error",
event_call=__event_call__,
)

View File

@@ -5,7 +5,7 @@ author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
description: 通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。
version: 1.2.1
version: 1.2.2
openwebui_id: 5c0617cb-a9e4-4bd6-a440-d276534ebd18
license: MIT
@@ -787,7 +787,7 @@ class Filter:
except Exception as e:
print(f"Error emitting debug log: {e}")
async def _log(self, message: str, type: str = "info", event_call=None):
async def _log(self, message: str, log_type: str = "info", event_call=None):
"""统一日志输出到后端 (print) 和前端 (console.log)"""
# 后端日志
if self.valves.debug_mode:
@@ -797,11 +797,11 @@ class Filter:
if self.valves.show_debug_log and event_call:
try:
css = "color: #3b82f6;" # 默认蓝色
if type == "error":
if log_type == "error":
css = "color: #ef4444; font-weight: bold;" # 红色
elif type == "warning":
elif log_type == "warning":
css = "color: #f59e0b;" # 橙色
elif type == "success":
elif log_type == "success":
css = "color: #10b981; font-weight: bold;" # 绿色
# 清理前端消息:移除分隔符和多余换行
@@ -948,12 +948,17 @@ class Filter:
# 处理 params 是 JSON 字符串的情况
if isinstance(params, str):
params = json.loads(params)
# 转换 Pydantic 模型为字典
elif hasattr(params, "model_dump"):
params = params.model_dump()
elif hasattr(params, "dict"):
params = params.dict()
# 处理字典或 Pydantic 对象
# 处理字典
if isinstance(params, dict):
system_prompt_content = params.get("system")
else:
# 假设是 Pydantic 模型或对象
# 回退:尝试 getattr
system_prompt_content = getattr(params, "system", None)
if system_prompt_content:
@@ -972,7 +977,7 @@ class Filter:
if self.valves.show_debug_log and __event_call__:
await self._log(
f"[Inlet] ❌ 解析模型参数失败: {e}",
type="error",
log_type="error",
event_call=__event_call__,
)
@@ -986,7 +991,7 @@ class Filter:
if self.valves.show_debug_log and __event_call__:
await self._log(
f"[Inlet] ❌ 数据库中未找到模型",
type="warning",
log_type="warning",
event_call=__event_call__,
)
@@ -994,7 +999,7 @@ class Filter:
if self.valves.show_debug_log and __event_call__:
await self._log(
f"[Inlet] ❌ 从数据库获取系统提示词错误: {e}",
type="error",
log_type="error",
event_call=__event_call__,
)
if self.valves.debug_mode:
@@ -1048,7 +1053,7 @@ class Filter:
if not chat_id:
await self._log(
"[Inlet] ❌ metadata 中缺少 chat_id跳过压缩",
type="error",
log_type="error",
event_call=__event_call__,
)
return body
@@ -1154,7 +1159,7 @@ class Filter:
if total_tokens > max_context_tokens:
await self._log(
f"[Inlet] ⚠️ 候选提示词 ({total_tokens} Tokens) 超过上限 ({max_context_tokens})。正在缩减历史记录...",
type="warning",
log_type="warning",
event_call=__event_call__,
)
@@ -1290,7 +1295,7 @@ class Filter:
await self._log(
f"[Inlet] 应用摘要: {system_info} + Head({len(head_messages)} 条, {head_tokens}t) + Summary({summary_tokens}t) + Tail({len(tail_messages)} 条, {tail_tokens}t) = Total({total_section_tokens}t)",
type="success",
log_type="success",
event_call=__event_call__,
)
@@ -1350,7 +1355,7 @@ class Filter:
if total_tokens > max_context_tokens:
await self._log(
f"[Inlet] ⚠️ 原始消息 ({total_tokens} Tokens) 超过上限 ({max_context_tokens})。正在缩减历史记录...",
type="warning",
log_type="warning",
event_call=__event_call__,
)
@@ -1420,7 +1425,7 @@ class Filter:
if not chat_id:
await self._log(
"[Outlet] ❌ metadata 中缺少 chat_id跳过压缩",
type="error",
log_type="error",
event_call=__event_call__,
)
return body
@@ -1486,7 +1491,7 @@ class Filter:
if current_tokens >= compression_threshold_tokens:
await self._log(
f"[🔍 后台计算] ⚡ 触发压缩阈值 (Token: {current_tokens} >= {compression_threshold_tokens})",
type="warning",
log_type="warning",
event_call=__event_call__,
)
@@ -1509,7 +1514,7 @@ class Filter:
except Exception as e:
await self._log(
f"[🔍 后台计算] ❌ 错误: {str(e)}",
type="error",
log_type="error",
event_call=__event_call__,
)
@@ -1546,7 +1551,7 @@ class Filter:
target_compressed_count = max(0, len(messages) - self.valves.keep_last)
await self._log(
f"[🤖 异步摘要任务] ⚠️ target_compressed_count 为 None进行估算: {target_compressed_count}",
type="warning",
log_type="warning",
event_call=__event_call__,
)
@@ -1593,7 +1598,7 @@ class Filter:
if not summary_model_id:
await self._log(
"[🤖 异步摘要任务] ⚠️ 摘要模型不存在,跳过压缩",
type="warning",
log_type="warning",
event_call=__event_call__,
)
return
@@ -1624,7 +1629,7 @@ class Filter:
excess_tokens = estimated_input_tokens - max_context_tokens
await self._log(
f"[🤖 异步摘要任务] ⚠️ 中间消息 ({middle_tokens} Tokens) + 缓冲超过摘要模型上限 ({max_context_tokens}),需要移除约 {excess_tokens} Token",
type="warning",
log_type="warning",
event_call=__event_call__,
)
@@ -1681,7 +1686,7 @@ class Filter:
if not new_summary:
await self._log(
"[🤖 异步摘要任务] ⚠️ 摘要生成返回空结果,跳过保存",
type="warning",
log_type="warning",
event_call=__event_call__,
)
return
@@ -1710,7 +1715,7 @@ class Filter:
await self._log(
f"[🤖 异步摘要任务] ✅ 完成!新摘要长度: {len(new_summary)} 字符",
type="success",
log_type="success",
event_call=__event_call__,
)
await self._log(
@@ -1821,14 +1826,14 @@ class Filter:
except Exception as e:
await self._log(
f"[Status] 计算 Token 错误: {e}",
type="error",
log_type="error",
event_call=__event_call__,
)
except Exception as e:
await self._log(
f"[🤖 异步摘要任务] ❌ 错误: {str(e)}",
type="error",
log_type="error",
event_call=__event_call__,
)
@@ -1928,7 +1933,7 @@ class Filter:
if not model:
await self._log(
"[🤖 LLM 调用] ⚠️ 摘要模型不存在,跳过摘要生成",
type="warning",
log_type="warning",
event_call=__event_call__,
)
return ""
@@ -1995,7 +2000,7 @@ class Filter:
await self._log(
f"[🤖 LLM 调用] ✅ 成功接收摘要",
type="success",
log_type="success",
event_call=__event_call__,
)
@@ -2016,7 +2021,7 @@ class Filter:
await self._log(
f"[🤖 LLM 调用] ❌ {error_message}",
type="error",
log_type="error",
event_call=__event_call__,
)

View File

@@ -1,10 +1,17 @@
# Folder Memory
English | [中文](./README_CN.md)
**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
**Folder Memory** (formerly Folder Rule Collector) 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.
### 📌 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
@@ -13,6 +20,10 @@ This ensures that all future conversations within that folder share the same evo
- **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).

View File

@@ -1,8 +1,17 @@
# 文件夹记忆 (Folder Memory)
[English](./README.md) | 中文
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.1.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
**文件夹记忆 (Folder Memory)** (原名 Folder Rule Collector) 是一个 OpenWebUI 的智能上下文过滤器插件。它能自动从文件夹内的对话中提取一致性的“项目规则”,并将其回写到文件夹的系统提示词中。
---
### 📌 0.1.0 版本特性
- **首个版本发布**:专注于自动化的“项目规则”管理。
- **文件夹级持久化**:自动将提取的规则回写到文件夹系统提示词中。
- **性能优化**:采用异步处理机制,并支持 `PRIORITY` 配置,确保与其他过滤器(如上下文压缩)完美协作。
---
**文件夹记忆 (Folder Memory)** 是一个 OpenWebUI 的智能上下文过滤器插件。它能自动从文件夹内的对话中提取一致性的“项目规则”,并将其回写到文件夹的系统提示词中。
这确保了该文件夹内的所有未来对话都能共享相同的进化上下文和规则,无需手动更新。
@@ -13,6 +22,10 @@
- **异步处理**:在后台运行,不阻塞用户的聊天体验。
- **ORM 集成**:直接使用 OpenWebUI 的内部模型更新文件夹数据,确保可靠性。
## ⚠️ 前置条件
- **对话必须在文件夹内进行。** 此插件仅在聊天属于某个文件夹时触发(即您需要先在 OpenWebUI 中创建一个文件夹,并在其内部开始对话)。
## 📦 安装指南
1.`folder_memory.py` (或中文版 `folder_memory_cn.py`) 复制到 OpenWebUI 的 `plugins/filters/` 目录(或通过管理员 UI 上传)。

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

@@ -93,7 +93,11 @@ def scan_plugins_directory(plugins_dir: str) -> list[dict[str, Any]]:
return plugins
# Walk through all subdirectories
for root, _dirs, files in os.walk(plugins_path):
for root, dirs, files in os.walk(plugins_path):
# Exclude debug directory from scan
if "debug" in dirs:
dirs.remove("debug")
for file in files:
if file.endswith(".py") and not file.startswith("__"):
# Skip specific files that should not trigger release
@@ -110,6 +114,11 @@ def scan_plugins_directory(plugins_dir: str) -> list[dict[str, Any]]:
if metadata:
# Determine plugin type from directory structure
rel_path = os.path.relpath(file_path, plugins_dir)
# Normalize file_path to always start with "plugins/" for consistent ID comparison
# regardless of where we scan from (/tmp/old_repo or ./plugins)
metadata["file_path"] = os.path.join("plugins", rel_path)
parts = rel_path.split(os.sep)
if len(parts) > 0:
metadata["type"] = parts[0] # actions, filters, pipes, etc.
@@ -139,42 +148,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 +197,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 +229,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 +258,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 +290,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 = (

View File

@@ -23,7 +23,11 @@ from openwebui_community_client import get_client
def find_existing_plugins(plugins_dir: str) -> list:
"""查找所有已发布的插件文件(有 openwebui_id 的)"""
plugins = []
for root, _, files in os.walk(plugins_dir):
for root, dirs, files in os.walk(plugins_dir):
# Exclude debug directory
if "debug" in dirs:
dirs.remove("debug")
for file in files:
if file.endswith(".py") and not file.startswith("__"):
file_path = os.path.join(root, file)