feat: Implement configurable OpenWebUI base URL for deployment scripts and update documentation.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -142,3 +142,4 @@ logs/
|
|||||||
# OpenWebUI specific
|
# OpenWebUI specific
|
||||||
# Add any specific ignores for OpenWebUI plugins if needed
|
# Add any specific ignores for OpenWebUI plugins if needed
|
||||||
.git-worktrees/
|
.git-worktrees/
|
||||||
|
plugins/filters/auth_model_info/
|
||||||
|
|||||||
@@ -1886,19 +1886,9 @@ class Filter:
|
|||||||
"""
|
"""
|
||||||
Check if compression should be skipped.
|
Check if compression should be skipped.
|
||||||
Returns True if:
|
Returns True if:
|
||||||
1. The base model includes 'copilot_sdk'
|
|
||||||
"""
|
"""
|
||||||
# Check if base model includes copilot_sdk
|
if body.get("is_copilot_model", False):
|
||||||
if __model__:
|
|
||||||
base_model_id = __model__.get("base_model_id", "")
|
|
||||||
if "copilot_sdk" in base_model_id.lower():
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Also check model in body
|
|
||||||
model_id = body.get("model", "")
|
|
||||||
if "copilot_sdk" in model_id.lower():
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def inlet(
|
async def inlet(
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ class Filter:
|
|||||||
|
|
||||||
# Check if it's a Copilot model
|
# Check if it's a Copilot model
|
||||||
is_copilot_model = self._is_copilot_model(current_model)
|
is_copilot_model = self._is_copilot_model(current_model)
|
||||||
|
body["is_copilot_model"] = is_copilot_model
|
||||||
|
|
||||||
await self._emit_debug_log(
|
await self._emit_debug_log(
|
||||||
__event_emitter__,
|
__event_emitter__,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ This directory contains automated scripts for deploying plugins in development t
|
|||||||
1. **OpenWebUI Running**: Make sure OpenWebUI is running locally (default `http://localhost:3000`)
|
1. **OpenWebUI Running**: Make sure OpenWebUI is running locally (default `http://localhost:3000`)
|
||||||
2. **API Key**: You need a valid OpenWebUI API key
|
2. **API Key**: You need a valid OpenWebUI API key
|
||||||
3. **Environment File**: Create a `.env` file in this directory containing your API key:
|
3. **Environment File**: Create a `.env` file in this directory containing your API key:
|
||||||
|
|
||||||
```
|
```
|
||||||
api_key=sk-xxxxxxxxxxxxx
|
api_key=sk-xxxxxxxxxxxxx
|
||||||
```
|
```
|
||||||
@@ -42,12 +43,14 @@ python deploy_filter.py --list
|
|||||||
Used to deploy Filter-type plugins (such as message filtering, context compression, etc.).
|
Used to deploy Filter-type plugins (such as message filtering, context compression, etc.).
|
||||||
|
|
||||||
**Key Features**:
|
**Key Features**:
|
||||||
|
|
||||||
- ✅ Auto-extracts metadata from Python files (version, author, description, etc.)
|
- ✅ Auto-extracts metadata from Python files (version, author, description, etc.)
|
||||||
- ✅ Attempts to update existing plugins, creates if not found
|
- ✅ Attempts to update existing plugins, creates if not found
|
||||||
- ✅ Supports multiple Filter plugin management
|
- ✅ Supports multiple Filter plugin management
|
||||||
- ✅ Detailed error messages and connection diagnostics
|
- ✅ Detailed error messages and connection diagnostics
|
||||||
|
|
||||||
**Usage**:
|
**Usage**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Deploy async_context_compression (default)
|
# Deploy async_context_compression (default)
|
||||||
python deploy_filter.py
|
python deploy_filter.py
|
||||||
@@ -62,6 +65,7 @@ python deploy_filter.py -l
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Workflow**:
|
**Workflow**:
|
||||||
|
|
||||||
1. Load API key from `.env`
|
1. Load API key from `.env`
|
||||||
2. Find target Filter plugin directory
|
2. Find target Filter plugin directory
|
||||||
3. Read Python source file
|
3. Read Python source file
|
||||||
@@ -76,6 +80,7 @@ python deploy_filter.py -l
|
|||||||
Used to deploy Pipe-type plugins (such as GitHub Copilot SDK).
|
Used to deploy Pipe-type plugins (such as GitHub Copilot SDK).
|
||||||
|
|
||||||
**Usage**:
|
**Usage**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python deploy_pipe.py
|
python deploy_pipe.py
|
||||||
```
|
```
|
||||||
@@ -101,6 +106,7 @@ Create a dedicated long-term API key in OpenWebUI Settings for deployment purpos
|
|||||||
**Cause**: OpenWebUI is not running or port is different
|
**Cause**: OpenWebUI is not running or port is different
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
|
|
||||||
- Make sure OpenWebUI is running
|
- Make sure OpenWebUI is running
|
||||||
- Check which port OpenWebUI is actually listening on (usually 3000)
|
- Check which port OpenWebUI is actually listening on (usually 3000)
|
||||||
- Edit the URL in the script if needed
|
- Edit the URL in the script if needed
|
||||||
@@ -110,6 +116,7 @@ Create a dedicated long-term API key in OpenWebUI Settings for deployment purpos
|
|||||||
**Cause**: `.env` file was not created
|
**Cause**: `.env` file was not created
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo "api_key=sk-your-api-key-here" > .env
|
echo "api_key=sk-your-api-key-here" > .env
|
||||||
```
|
```
|
||||||
@@ -119,6 +126,7 @@ echo "api_key=sk-your-api-key-here" > .env
|
|||||||
**Cause**: Filter directory name is incorrect
|
**Cause**: Filter directory name is incorrect
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List all available Filters
|
# List all available Filters
|
||||||
python deploy_filter.py --list
|
python deploy_filter.py --list
|
||||||
@@ -129,6 +137,7 @@ python deploy_filter.py --list
|
|||||||
**Cause**: API key is invalid or expired
|
**Cause**: API key is invalid or expired
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
|
|
||||||
1. Verify your API key is valid
|
1. Verify your API key is valid
|
||||||
2. Generate a new API key
|
2. Generate a new API key
|
||||||
3. Update the `.env` file
|
3. Update the `.env` file
|
||||||
@@ -178,6 +187,7 @@ python deploy_filter.py async-context-compression
|
|||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
⚠️ **Important**:
|
⚠️ **Important**:
|
||||||
|
|
||||||
- ✅ Add `.env` file to `.gitignore` (avoid committing sensitive info)
|
- ✅ Add `.env` file to `.gitignore` (avoid committing sensitive info)
|
||||||
- ✅ Never commit API keys to version control
|
- ✅ Never commit API keys to version control
|
||||||
- ✅ Use only on trusted networks
|
- ✅ Use only on trusted networks
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Added a complete local deployment toolchain for the `async_context_compression`
|
|||||||
## 📋 New Files
|
## 📋 New Files
|
||||||
|
|
||||||
### 1. **deploy_filter.py** — Filter Plugin Deployment Script
|
### 1. **deploy_filter.py** — Filter Plugin Deployment Script
|
||||||
|
|
||||||
- **Location**: `scripts/deploy_filter.py`
|
- **Location**: `scripts/deploy_filter.py`
|
||||||
- **Function**: Auto-deploy Filter-type plugins to local OpenWebUI instance
|
- **Function**: Auto-deploy Filter-type plugins to local OpenWebUI instance
|
||||||
- **Features**:
|
- **Features**:
|
||||||
@@ -19,6 +20,7 @@ Added a complete local deployment toolchain for the `async_context_compression`
|
|||||||
- **Code Lines**: ~300
|
- **Code Lines**: ~300
|
||||||
|
|
||||||
### 2. **DEPLOYMENT_GUIDE.md** — Complete Deployment Guide
|
### 2. **DEPLOYMENT_GUIDE.md** — Complete Deployment Guide
|
||||||
|
|
||||||
- **Location**: `scripts/DEPLOYMENT_GUIDE.md`
|
- **Location**: `scripts/DEPLOYMENT_GUIDE.md`
|
||||||
- **Contents**:
|
- **Contents**:
|
||||||
- Prerequisites and quick start
|
- Prerequisites and quick start
|
||||||
@@ -28,6 +30,7 @@ Added a complete local deployment toolchain for the `async_context_compression`
|
|||||||
- Step-by-step workflow examples
|
- Step-by-step workflow examples
|
||||||
|
|
||||||
### 3. **QUICK_START.md** — Quick Reference Card
|
### 3. **QUICK_START.md** — Quick Reference Card
|
||||||
|
|
||||||
- **Location**: `scripts/QUICK_START.md`
|
- **Location**: `scripts/QUICK_START.md`
|
||||||
- **Contents**:
|
- **Contents**:
|
||||||
- One-line deployment command
|
- One-line deployment command
|
||||||
@@ -37,6 +40,7 @@ Added a complete local deployment toolchain for the `async_context_compression`
|
|||||||
- CI/CD integration examples
|
- CI/CD integration examples
|
||||||
|
|
||||||
### 4. **test_deploy_filter.py** — Unit Test Suite
|
### 4. **test_deploy_filter.py** — Unit Test Suite
|
||||||
|
|
||||||
- **Location**: `tests/scripts/test_deploy_filter.py`
|
- **Location**: `tests/scripts/test_deploy_filter.py`
|
||||||
- **Test Coverage**:
|
- **Test Coverage**:
|
||||||
- ✅ Filter file discovery (3 tests)
|
- ✅ Filter file discovery (3 tests)
|
||||||
@@ -138,6 +142,7 @@ openwebui_id: b1655bc8-6de9-4cad-8cb5-a6f7829a02ce
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Supported Metadata Fields**:
|
**Supported Metadata Fields**:
|
||||||
|
|
||||||
- `title` — Filter display name ✅
|
- `title` — Filter display name ✅
|
||||||
- `id` — Unique identifier ✅
|
- `id` — Unique identifier ✅
|
||||||
- `author` — Author name ✅
|
- `author` — Author name ✅
|
||||||
@@ -335,17 +340,20 @@ Metadata Extraction and Delivery
|
|||||||
### Debugging Tips
|
### Debugging Tips
|
||||||
|
|
||||||
1. **Enable Verbose Logging**:
|
1. **Enable Verbose Logging**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python deploy_filter.py 2>&1 | tee deploy.log
|
python deploy_filter.py 2>&1 | tee deploy.log
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Test API Connection**:
|
2. **Test API Connection**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X GET http://localhost:3000/api/v1/functions \
|
curl -X GET http://localhost:3000/api/v1/functions \
|
||||||
-H "Authorization: Bearer $API_KEY"
|
-H "Authorization: Bearer $API_KEY"
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Verify .env File**:
|
3. **Verify .env File**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
grep "api_key=" scripts/.env
|
grep "api_key=" scripts/.env
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -73,12 +73,14 @@ python deploy_async_context_compression.py
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Features**:
|
**Features**:
|
||||||
|
|
||||||
- ✅ Optimized specifically for async_context_compression
|
- ✅ Optimized specifically for async_context_compression
|
||||||
- ✅ Clear deployment steps and confirmation
|
- ✅ Clear deployment steps and confirmation
|
||||||
- ✅ Friendly error messages
|
- ✅ Friendly error messages
|
||||||
- ✅ Shows next steps after successful deployment
|
- ✅ Shows next steps after successful deployment
|
||||||
|
|
||||||
**Sample Output**:
|
**Sample Output**:
|
||||||
|
|
||||||
```
|
```
|
||||||
======================================================================
|
======================================================================
|
||||||
🚀 Deploying Async Context Compression Filter Plugin
|
🚀 Deploying Async Context Compression Filter Plugin
|
||||||
@@ -117,6 +119,7 @@ python deploy_filter.py --list
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Features**:
|
**Features**:
|
||||||
|
|
||||||
- ✅ Generic Filter deployment tool
|
- ✅ Generic Filter deployment tool
|
||||||
- ✅ Supports multiple plugins
|
- ✅ Supports multiple plugins
|
||||||
- ✅ Auto metadata extraction
|
- ✅ Auto metadata extraction
|
||||||
@@ -142,6 +145,7 @@ python deploy_tool.py openwebui-skills-manager
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Features**:
|
**Features**:
|
||||||
|
|
||||||
- ✅ Supports Tools plugin deployment
|
- ✅ Supports Tools plugin deployment
|
||||||
- ✅ Auto-detects `Tools` class definition
|
- ✅ Auto-detects `Tools` class definition
|
||||||
- ✅ Smart update/create logic
|
- ✅ Smart update/create logic
|
||||||
@@ -290,6 +294,7 @@ git status # should not show .env
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Check if OpenWebUI is running
|
# 1. Check if OpenWebUI is running
|
||||||
curl http://localhost:3000
|
curl http://localhost:3000
|
||||||
@@ -309,6 +314,7 @@ curl http://localhost:3000
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo "api_key=sk-your-api-key" > .env
|
echo "api_key=sk-your-api-key" > .env
|
||||||
cat .env # verify file created
|
cat .env # verify file created
|
||||||
@@ -321,6 +327,7 @@ cat .env # verify file created
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List all available Filters
|
# List all available Filters
|
||||||
python deploy_filter.py --list
|
python deploy_filter.py --list
|
||||||
@@ -337,6 +344,7 @@ python deploy_filter.py async-context-compression
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Verify API key is correct
|
# 1. Verify API key is correct
|
||||||
grep "api_key=" .env
|
grep "api_key=" .env
|
||||||
@@ -370,7 +378,7 @@ python deploy_async_context_compression.py
|
|||||||
|
|
||||||
### Method 2: Verify in OpenWebUI
|
### Method 2: Verify in OpenWebUI
|
||||||
|
|
||||||
1. Open OpenWebUI: http://localhost:3000
|
1. Open OpenWebUI: <http://localhost:3000>
|
||||||
2. Go to Settings → Filters
|
2. Go to Settings → Filters
|
||||||
3. Check if 'Async Context Compression' is listed
|
3. Check if 'Async Context Compression' is listed
|
||||||
4. Verify version number is correct (should be latest)
|
4. Verify version number is correct (should be latest)
|
||||||
@@ -380,6 +388,7 @@ python deploy_async_context_compression.py
|
|||||||
1. Open a new conversation
|
1. Open a new conversation
|
||||||
2. Enable 'Async Context Compression' Filter
|
2. Enable 'Async Context Compression' Filter
|
||||||
3. Have multiple-turn conversation and verify compression/summarization works
|
3. Have multiple-turn conversation and verify compression/summarization works
|
||||||
|
|
||||||
## 💡 Advanced Usage
|
## 💡 Advanced Usage
|
||||||
|
|
||||||
### Automated Deploy & Test
|
### Automated Deploy & Test
|
||||||
@@ -473,4 +482,3 @@ Newly created deployment-related files:
|
|||||||
**Last Updated**: 2026-03-09
|
**Last Updated**: 2026-03-09
|
||||||
**Script Status**: ✅ Ready for production
|
**Script Status**: ✅ Ready for production
|
||||||
**Test Coverage**: 10/10 passed ✅
|
**Test Coverage**: 10/10 passed ✅
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
✅ **Yes, re-deploying automatically updates the plugin!**
|
✅ **Yes, re-deploying automatically updates the plugin!**
|
||||||
|
|
||||||
The deployment script uses a **smart two-stage strategy**:
|
The deployment script uses a **smart two-stage strategy**:
|
||||||
|
|
||||||
1. 🔄 **Try UPDATE First** (if plugin exists)
|
1. 🔄 **Try UPDATE First** (if plugin exists)
|
||||||
2. 📝 **Auto CREATE** (if update fails — plugin doesn't exist)
|
2. 📝 **Auto CREATE** (if update fails — plugin doesn't exist)
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ if response.status_code == 200:
|
|||||||
```
|
```
|
||||||
|
|
||||||
**What Happens**:
|
**What Happens**:
|
||||||
|
|
||||||
- Send **POST** to `/api/v1/functions/id/{filter_id}/update`
|
- Send **POST** to `/api/v1/functions/id/{filter_id}/update`
|
||||||
- If returns **HTTP 200**, plugin exists and update succeeded
|
- If returns **HTTP 200**, plugin exists and update succeeded
|
||||||
- Includes:
|
- Includes:
|
||||||
@@ -84,6 +86,7 @@ if response.status_code != 200:
|
|||||||
```
|
```
|
||||||
|
|
||||||
**What Happens**:
|
**What Happens**:
|
||||||
|
|
||||||
- If update fails (HTTP ≠ 200), auto-attempt create
|
- If update fails (HTTP ≠ 200), auto-attempt create
|
||||||
- Send **POST** to `/api/v1/functions/create`
|
- Send **POST** to `/api/v1/functions/create`
|
||||||
- Uses **same payload** (code, metadata identical)
|
- Uses **same payload** (code, metadata identical)
|
||||||
@@ -103,6 +106,7 @@ $ python deploy_async_context_compression.py
|
|||||||
```
|
```
|
||||||
|
|
||||||
**What Happens**:
|
**What Happens**:
|
||||||
|
|
||||||
1. Try UPDATE → fails (HTTP 404 — plugin doesn't exist)
|
1. Try UPDATE → fails (HTTP 404 — plugin doesn't exist)
|
||||||
2. Auto-try CREATE → succeeds (HTTP 200)
|
2. Auto-try CREATE → succeeds (HTTP 200)
|
||||||
3. Plugin created in OpenWebUI
|
3. Plugin created in OpenWebUI
|
||||||
@@ -121,6 +125,7 @@ $ python deploy_async_context_compression.py
|
|||||||
```
|
```
|
||||||
|
|
||||||
**What Happens**:
|
**What Happens**:
|
||||||
|
|
||||||
1. Read modified code
|
1. Read modified code
|
||||||
2. Try UPDATE → succeeds (HTTP 200 — plugin exists)
|
2. Try UPDATE → succeeds (HTTP 200 — plugin exists)
|
||||||
3. Plugin in OpenWebUI updated to latest code
|
3. Plugin in OpenWebUI updated to latest code
|
||||||
@@ -147,6 +152,7 @@ $ python deploy_async_context_compression.py
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Characteristics**:
|
**Characteristics**:
|
||||||
|
|
||||||
- 🚀 Each update takes only 5 seconds
|
- 🚀 Each update takes only 5 seconds
|
||||||
- 📝 Each is an incremental update
|
- 📝 Each is an incremental update
|
||||||
- ✅ No need to restart OpenWebUI
|
- ✅ No need to restart OpenWebUI
|
||||||
@@ -181,11 +187,13 @@ version: 1.3.0
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Each deployment**:
|
**Each deployment**:
|
||||||
|
|
||||||
1. Script reads version from docstring
|
1. Script reads version from docstring
|
||||||
2. Sends this version in manifest to OpenWebUI
|
2. Sends this version in manifest to OpenWebUI
|
||||||
3. If you change version in code, deployment updates to new version
|
3. If you change version in code, deployment updates to new version
|
||||||
|
|
||||||
**Best Practice**:
|
**Best Practice**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Modify code
|
# 1. Modify code
|
||||||
vim async_context_compression.py
|
vim async_context_compression.py
|
||||||
@@ -300,6 +308,7 @@ Usually **not needed** because:
|
|||||||
4. ✅ Failures auto-rollback
|
4. ✅ Failures auto-rollback
|
||||||
|
|
||||||
但如果真的需要控制,可以:
|
但如果真的需要控制,可以:
|
||||||
|
|
||||||
- 手动修改脚本 (修改 `deploy_filter.py`)
|
- 手动修改脚本 (修改 `deploy_filter.py`)
|
||||||
- 或分别使用 UPDATE/CREATE 的具体 API 端点
|
- 或分别使用 UPDATE/CREATE 的具体 API 端点
|
||||||
|
|
||||||
@@ -323,6 +332,7 @@ Usually **not needed** because:
|
|||||||
### Q: 可以同时部署多个插件吗?
|
### Q: 可以同时部署多个插件吗?
|
||||||
|
|
||||||
✅ **可以!**
|
✅ **可以!**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python deploy_filter.py async-context-compression
|
python deploy_filter.py async-context-compression
|
||||||
python deploy_filter.py folder-memory
|
python deploy_filter.py folder-memory
|
||||||
@@ -337,6 +347,7 @@ python deploy_filter.py context_enhancement_filter
|
|||||||
---
|
---
|
||||||
|
|
||||||
**总结**: 部署脚本的更新机制完全自动化,开发者只需修改代码,每次运行 `deploy_async_context_compression.py` 就会自动:
|
**总结**: 部署脚本的更新机制完全自动化,开发者只需修改代码,每次运行 `deploy_async_context_compression.py` 就会自动:
|
||||||
|
|
||||||
1. ✅ 创建(第一次)或更新(后续)插件
|
1. ✅ 创建(第一次)或更新(后续)插件
|
||||||
2. ✅ 从代码提取最新的元数据和版本号
|
2. ✅ 从代码提取最新的元数据和版本号
|
||||||
3. ✅ 立即生效,无需重启 OpenWebUI
|
3. ✅ 立即生效,无需重启 OpenWebUI
|
||||||
|
|||||||
@@ -49,6 +49,31 @@ def _load_api_key() -> str:
|
|||||||
raise ValueError("api_key not found in .env file.")
|
raise ValueError("api_key not found in .env file.")
|
||||||
|
|
||||||
|
|
||||||
|
def _load_openwebui_base_url() -> str:
|
||||||
|
"""Load OpenWebUI base URL from .env file or environment.
|
||||||
|
|
||||||
|
Checks in order:
|
||||||
|
1. OPENWEBUI_BASE_URL in .env
|
||||||
|
2. OPENWEBUI_BASE_URL environment variable
|
||||||
|
3. Default to http://localhost:3000
|
||||||
|
"""
|
||||||
|
if ENV_FILE.exists():
|
||||||
|
for line in ENV_FILE.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("OPENWEBUI_BASE_URL="):
|
||||||
|
url = line.split("=", 1)[1].strip()
|
||||||
|
if url:
|
||||||
|
return url
|
||||||
|
|
||||||
|
# Try environment variable
|
||||||
|
url = os.environ.get("OPENWEBUI_BASE_URL")
|
||||||
|
if url:
|
||||||
|
return url
|
||||||
|
|
||||||
|
# Default
|
||||||
|
return "http://localhost:3000"
|
||||||
|
|
||||||
|
|
||||||
def _find_filter_file(filter_name: str) -> Optional[Path]:
|
def _find_filter_file(filter_name: str) -> Optional[Path]:
|
||||||
"""Find the main Python file for a filter.
|
"""Find the main Python file for a filter.
|
||||||
|
|
||||||
@@ -126,7 +151,9 @@ def _build_filter_payload(
|
|||||||
filter_id = metadata.get("id", filter_name).replace("-", "_")
|
filter_id = metadata.get("id", filter_name).replace("-", "_")
|
||||||
title = metadata.get("title", filter_name)
|
title = metadata.get("title", filter_name)
|
||||||
author = metadata.get("author", "Fu-Jie")
|
author = metadata.get("author", "Fu-Jie")
|
||||||
author_url = metadata.get("author_url", "https://github.com/Fu-Jie/openwebui-extensions")
|
author_url = metadata.get(
|
||||||
|
"author_url", "https://github.com/Fu-Jie/openwebui-extensions"
|
||||||
|
)
|
||||||
funding_url = metadata.get("funding_url", "https://github.com/open-webui")
|
funding_url = metadata.get("funding_url", "https://github.com/open-webui")
|
||||||
description = metadata.get("description", f"Filter plugin: {title}")
|
description = metadata.get("description", f"Filter plugin: {title}")
|
||||||
version = metadata.get("version", "1.0.0")
|
version = metadata.get("version", "1.0.0")
|
||||||
@@ -211,11 +238,13 @@ def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# 6. Send update request
|
# 6. Send update request
|
||||||
update_url = "http://localhost:3000/api/v1/functions/id/{}/update".format(filter_id)
|
base_url = _load_openwebui_base_url()
|
||||||
create_url = "http://localhost:3000/api/v1/functions/create"
|
update_url = "{}/api/v1/functions/id/{}/update".format(base_url, filter_id)
|
||||||
|
create_url = "{}/api/v1/functions/create".format(base_url)
|
||||||
|
|
||||||
print(f"📦 Deploying filter '{title}' (version {version})...")
|
print(f"📦 Deploying filter '{title}' (version {version})...")
|
||||||
print(f" File: {file_path}")
|
print(f" File: {file_path}")
|
||||||
|
print(f" Target: {base_url}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Try update first
|
# Try update first
|
||||||
@@ -247,7 +276,9 @@ def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool:
|
|||||||
print(f"✅ Successfully created '{title}' filter!")
|
print(f"✅ Successfully created '{title}' filter!")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print(f"❌ Failed to update or create. Status: {res_create.status_code}")
|
print(
|
||||||
|
f"❌ Failed to update or create. Status: {res_create.status_code}"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
error_msg = res_create.json()
|
error_msg = res_create.json()
|
||||||
print(f" Error: {error_msg}")
|
print(f" Error: {error_msg}")
|
||||||
@@ -256,9 +287,8 @@ def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
print(
|
base_url = _load_openwebui_base_url()
|
||||||
"❌ Connection error: Could not reach OpenWebUI at localhost:3000"
|
print(f"❌ Connection error: Could not reach OpenWebUI at {base_url}")
|
||||||
)
|
|
||||||
print(" Make sure OpenWebUI is running and accessible.")
|
print(" Make sure OpenWebUI is running and accessible.")
|
||||||
return False
|
return False
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
@@ -272,7 +302,11 @@ def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool:
|
|||||||
def list_filters() -> None:
|
def list_filters() -> None:
|
||||||
"""List all available filters."""
|
"""List all available filters."""
|
||||||
print("📋 Available filters:")
|
print("📋 Available filters:")
|
||||||
filters = [d.name for d in FILTERS_DIR.iterdir() if d.is_dir() and not d.name.startswith("_")]
|
filters = [
|
||||||
|
d.name
|
||||||
|
for d in FILTERS_DIR.iterdir()
|
||||||
|
if d.is_dir() and not d.name.startswith("_")
|
||||||
|
]
|
||||||
|
|
||||||
if not filters:
|
if not filters:
|
||||||
print(" (No filters found)")
|
print(" (No filters found)")
|
||||||
|
|||||||
@@ -76,8 +76,7 @@ def _get_base_url() -> str:
|
|||||||
|
|
||||||
if not base_url:
|
if not base_url:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Missing url. Please create {ENV_FILE} with: "
|
f"Missing url. Please create {ENV_FILE} with: " "url=http://localhost:3000"
|
||||||
"url=http://localhost:3000"
|
|
||||||
)
|
)
|
||||||
return base_url.rstrip("/")
|
return base_url.rstrip("/")
|
||||||
|
|
||||||
@@ -141,7 +140,9 @@ def _build_tool_payload(
|
|||||||
tool_id = metadata.get("id", tool_name).replace("-", "_")
|
tool_id = metadata.get("id", tool_name).replace("-", "_")
|
||||||
title = metadata.get("title", tool_name)
|
title = metadata.get("title", tool_name)
|
||||||
author = metadata.get("author", "Fu-Jie")
|
author = metadata.get("author", "Fu-Jie")
|
||||||
author_url = metadata.get("author_url", "https://github.com/Fu-Jie/openwebui-extensions")
|
author_url = metadata.get(
|
||||||
|
"author_url", "https://github.com/Fu-Jie/openwebui-extensions"
|
||||||
|
)
|
||||||
funding_url = metadata.get("funding_url", "https://github.com/open-webui")
|
funding_url = metadata.get("funding_url", "https://github.com/open-webui")
|
||||||
description = metadata.get("description", f"Tool plugin: {title}")
|
description = metadata.get("description", f"Tool plugin: {title}")
|
||||||
version = metadata.get("version", "1.0.0")
|
version = metadata.get("version", "1.0.0")
|
||||||
@@ -263,7 +264,9 @@ def deploy_tool(tool_name: str = DEFAULT_TOOL) -> bool:
|
|||||||
print(f"✅ Successfully created '{title}' tool!")
|
print(f"✅ Successfully created '{title}' tool!")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print(f"❌ Failed to update or create. Status: {res_create.status_code}")
|
print(
|
||||||
|
f"❌ Failed to update or create. Status: {res_create.status_code}"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
error_msg = res_create.json()
|
error_msg = res_create.json()
|
||||||
print(f" Error: {error_msg}")
|
print(f" Error: {error_msg}")
|
||||||
@@ -272,9 +275,7 @@ def deploy_tool(tool_name: str = DEFAULT_TOOL) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
print(
|
print("❌ Connection error: Could not reach OpenWebUI at {base_url}")
|
||||||
"❌ Connection error: Could not reach OpenWebUI at {base_url}"
|
|
||||||
)
|
|
||||||
print(" Make sure OpenWebUI is running and accessible.")
|
print(" Make sure OpenWebUI is running and accessible.")
|
||||||
return False
|
return False
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
@@ -288,7 +289,9 @@ def deploy_tool(tool_name: str = DEFAULT_TOOL) -> bool:
|
|||||||
def list_tools() -> None:
|
def list_tools() -> None:
|
||||||
"""List all available tools."""
|
"""List all available tools."""
|
||||||
print("📋 Available tools:")
|
print("📋 Available tools:")
|
||||||
tools = [d.name for d in TOOLS_DIR.iterdir() if d.is_dir() and not d.name.startswith("_")]
|
tools = [
|
||||||
|
d.name for d in TOOLS_DIR.iterdir() if d.is_dir() and not d.name.startswith("_")
|
||||||
|
]
|
||||||
|
|
||||||
if not tools:
|
if not tools:
|
||||||
print(" (No tools found)")
|
print(" (No tools found)")
|
||||||
|
|||||||
@@ -187,9 +187,7 @@ def build_payload(candidate: PluginCandidate) -> Dict[str, object]:
|
|||||||
manifest = dict(candidate.metadata)
|
manifest = dict(candidate.metadata)
|
||||||
manifest.setdefault("title", candidate.title)
|
manifest.setdefault("title", candidate.title)
|
||||||
manifest.setdefault("author", "Fu-Jie")
|
manifest.setdefault("author", "Fu-Jie")
|
||||||
manifest.setdefault(
|
manifest.setdefault("author_url", "https://github.com/Fu-Jie/openwebui-extensions")
|
||||||
"author_url", "https://github.com/Fu-Jie/openwebui-extensions"
|
|
||||||
)
|
|
||||||
manifest.setdefault("funding_url", "https://github.com/open-webui")
|
manifest.setdefault("funding_url", "https://github.com/open-webui")
|
||||||
manifest.setdefault(
|
manifest.setdefault(
|
||||||
"description", f"{candidate.plugin_type.title()} plugin: {candidate.title}"
|
"description", f"{candidate.plugin_type.title()} plugin: {candidate.title}"
|
||||||
@@ -233,7 +231,9 @@ def build_api_urls(base_url: str, candidate: PluginCandidate) -> Tuple[str, str]
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def discover_plugins(plugin_types: Sequence[str]) -> Tuple[List[PluginCandidate], List[Tuple[Path, str]]]:
|
def discover_plugins(
|
||||||
|
plugin_types: Sequence[str],
|
||||||
|
) -> Tuple[List[PluginCandidate], List[Tuple[Path, str]]]:
|
||||||
candidates: List[PluginCandidate] = []
|
candidates: List[PluginCandidate] = []
|
||||||
skipped: List[Tuple[Path, str]] = []
|
skipped: List[Tuple[Path, str]] = []
|
||||||
|
|
||||||
@@ -344,7 +344,9 @@ def print_skipped_summary(skipped: Sequence[Tuple[Path, str]]) -> None:
|
|||||||
for _, reason in skipped:
|
for _, reason in skipped:
|
||||||
counts[reason] = counts.get(reason, 0) + 1
|
counts[reason] = counts.get(reason, 0) + 1
|
||||||
|
|
||||||
summary = ", ".join(f"{reason}: {count}" for reason, count in sorted(counts.items()))
|
summary = ", ".join(
|
||||||
|
f"{reason}: {count}" for reason, count in sorted(counts.items())
|
||||||
|
)
|
||||||
print(f"Skipped {len(skipped)} files ({summary}).")
|
print(f"Skipped {len(skipped)} files ({summary}).")
|
||||||
|
|
||||||
|
|
||||||
@@ -421,7 +423,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|||||||
failed_candidates.append(candidate)
|
failed_candidates.append(candidate)
|
||||||
print(f" [FAILED] {message}")
|
print(f" [FAILED] {message}")
|
||||||
|
|
||||||
print(f"\n" + "="*80)
|
print(f"\n" + "=" * 80)
|
||||||
print(
|
print(
|
||||||
f"Finished: {success_count}/{len(candidates)} plugins installed successfully."
|
f"Finished: {success_count}/{len(candidates)} plugins installed successfully."
|
||||||
)
|
)
|
||||||
@@ -433,7 +435,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|||||||
print(f" → Check the error message above")
|
print(f" → Check the error message above")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
print("="*80)
|
print("=" * 80)
|
||||||
return 0 if success_count == len(candidates) else 1
|
return 0 if success_count == len(candidates) else 1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ local deployment are present and functional.
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Check all deployment tools are ready."""
|
"""Check all deployment tools are ready."""
|
||||||
base_dir = Path(__file__).parent.parent
|
base_dir = Path(__file__).parent.parent
|
||||||
|
|
||||||
print("\n" + "="*80)
|
print("\n" + "=" * 80)
|
||||||
print("✨ Async Context Compression Local Deployment Tools — Verification Status")
|
print("✨ Async Context Compression Local Deployment Tools — Verification Status")
|
||||||
print("="*80 + "\n")
|
print("=" * 80 + "\n")
|
||||||
|
|
||||||
files_to_check = {
|
files_to_check = {
|
||||||
"🐍 Python Scripts": [
|
"🐍 Python Scripts": [
|
||||||
@@ -50,17 +51,17 @@ def main():
|
|||||||
|
|
||||||
if exists and file_path.endswith(".py"):
|
if exists and file_path.endswith(".py"):
|
||||||
size = full_path.stat().st_size
|
size = full_path.stat().st_size
|
||||||
lines = len(full_path.read_text().split('\n'))
|
lines = len(full_path.read_text().split("\n"))
|
||||||
print(f" └─ [{size} bytes, ~{lines} lines]")
|
print(f" └─ [{size} bytes, ~{lines} lines]")
|
||||||
|
|
||||||
if not exists:
|
if not exists:
|
||||||
all_exist = False
|
all_exist = False
|
||||||
|
|
||||||
print("\n" + "="*80)
|
print("\n" + "=" * 80)
|
||||||
|
|
||||||
if all_exist:
|
if all_exist:
|
||||||
print("✅ All deployment tool files are ready!")
|
print("✅ All deployment tool files are ready!")
|
||||||
print("="*80 + "\n")
|
print("=" * 80 + "\n")
|
||||||
|
|
||||||
print("🚀 Quick Start (3 ways):\n")
|
print("🚀 Quick Start (3 ways):\n")
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ def main():
|
|||||||
print(" python deploy_filter.py folder-memory")
|
print(" python deploy_filter.py folder-memory")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
print("="*80 + "\n")
|
print("=" * 80 + "\n")
|
||||||
print("📚 Documentation References:\n")
|
print("📚 Documentation References:\n")
|
||||||
print(" • Quick Start: scripts/QUICK_START.md")
|
print(" • Quick Start: scripts/QUICK_START.md")
|
||||||
print(" • Complete Guide: scripts/DEPLOYMENT_GUIDE.md")
|
print(" • Complete Guide: scripts/DEPLOYMENT_GUIDE.md")
|
||||||
@@ -92,11 +93,11 @@ def main():
|
|||||||
print(" • Test Coverage: pytest tests/scripts/test_deploy_filter.py -v")
|
print(" • Test Coverage: pytest tests/scripts/test_deploy_filter.py -v")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
print("="*80 + "\n")
|
print("=" * 80 + "\n")
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
print("❌ Some files are missing!")
|
print("❌ Some files are missing!")
|
||||||
print("="*80 + "\n")
|
print("=" * 80 + "\n")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ def test_build_payload_uses_native_tool_shape_for_tools():
|
|||||||
"description": "Demo tool description",
|
"description": "Demo tool description",
|
||||||
"openwebui_id": "12345678-1234-1234-1234-123456789abc",
|
"openwebui_id": "12345678-1234-1234-1234-123456789abc",
|
||||||
},
|
},
|
||||||
content='class Tools:\n pass\n',
|
content="class Tools:\n pass\n",
|
||||||
function_id="demo_tool",
|
function_id="demo_tool",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ def test_build_payload_uses_native_tool_shape_for_tools():
|
|||||||
"description": "Demo tool description",
|
"description": "Demo tool description",
|
||||||
"manifest": {},
|
"manifest": {},
|
||||||
},
|
},
|
||||||
"content": 'class Tools:\n pass\n',
|
"content": "class Tools:\n pass\n",
|
||||||
"access_grants": [],
|
"access_grants": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ def test_build_api_urls_uses_tool_endpoints_for_tools():
|
|||||||
plugin_type="tool",
|
plugin_type="tool",
|
||||||
file_path=Path("plugins/tools/demo/demo_tool.py"),
|
file_path=Path("plugins/tools/demo/demo_tool.py"),
|
||||||
metadata={"title": "Demo Tool"},
|
metadata={"title": "Demo Tool"},
|
||||||
content='class Tools:\n pass\n',
|
content="class Tools:\n pass\n",
|
||||||
function_id="demo_tool",
|
function_id="demo_tool",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -101,7 +101,9 @@ def test_build_api_urls_uses_tool_endpoints_for_tools():
|
|||||||
assert create_url == "http://localhost:3000/api/v1/tools/create"
|
assert create_url == "http://localhost:3000/api/v1/tools/create"
|
||||||
|
|
||||||
|
|
||||||
def test_discover_plugins_only_returns_supported_openwebui_plugins(tmp_path, monkeypatch):
|
def test_discover_plugins_only_returns_supported_openwebui_plugins(
|
||||||
|
tmp_path, monkeypatch
|
||||||
|
):
|
||||||
actions_dir = tmp_path / "plugins" / "actions"
|
actions_dir = tmp_path / "plugins" / "actions"
|
||||||
filters_dir = tmp_path / "plugins" / "filters"
|
filters_dir = tmp_path / "plugins" / "filters"
|
||||||
pipes_dir = tmp_path / "plugins" / "pipes"
|
pipes_dir = tmp_path / "plugins" / "pipes"
|
||||||
@@ -110,7 +112,9 @@ def test_discover_plugins_only_returns_supported_openwebui_plugins(tmp_path, mon
|
|||||||
write_plugin(actions_dir / "flash-card" / "flash_card.py", PLUGIN_HEADER)
|
write_plugin(actions_dir / "flash-card" / "flash_card.py", PLUGIN_HEADER)
|
||||||
write_plugin(actions_dir / "flash-card" / "flash_card_cn.py", PLUGIN_HEADER)
|
write_plugin(actions_dir / "flash-card" / "flash_card_cn.py", PLUGIN_HEADER)
|
||||||
write_plugin(actions_dir / "infographic" / "verify_generation.py", PLUGIN_HEADER)
|
write_plugin(actions_dir / "infographic" / "verify_generation.py", PLUGIN_HEADER)
|
||||||
write_plugin(filters_dir / "missing-id" / "missing_id.py", '"""\ntitle: Missing ID\n"""\n')
|
write_plugin(
|
||||||
|
filters_dir / "missing-id" / "missing_id.py", '"""\ntitle: Missing ID\n"""\n'
|
||||||
|
)
|
||||||
write_plugin(pipes_dir / "sdk" / "github_copilot_sdk.py", PLUGIN_HEADER)
|
write_plugin(pipes_dir / "sdk" / "github_copilot_sdk.py", PLUGIN_HEADER)
|
||||||
write_plugin(tools_dir / "skills" / "openwebui_skills_manager.py", PLUGIN_HEADER)
|
write_plugin(tools_dir / "skills" / "openwebui_skills_manager.py", PLUGIN_HEADER)
|
||||||
|
|
||||||
@@ -150,7 +154,9 @@ def test_discover_plugins_only_returns_supported_openwebui_plugins(tmp_path, mon
|
|||||||
("class Action:\n pass\n", "missing plugin header"),
|
("class Action:\n pass\n", "missing plugin header"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_discover_plugins_reports_missing_metadata(tmp_path, monkeypatch, header, expected_reason):
|
def test_discover_plugins_reports_missing_metadata(
|
||||||
|
tmp_path, monkeypatch, header, expected_reason
|
||||||
|
):
|
||||||
action_dir = tmp_path / "plugins" / "actions"
|
action_dir = tmp_path / "plugins" / "actions"
|
||||||
plugin_file = action_dir / "demo" / "demo.py"
|
plugin_file = action_dir / "demo" / "demo.py"
|
||||||
write_plugin(plugin_file, header)
|
write_plugin(plugin_file, header)
|
||||||
|
|||||||
Reference in New Issue
Block a user