Merge pull request #11 from Fu-Jie/copilot/reuse-db-connection
Refactor async-context-compression to re-use OpenWebUI's internal DB connection
This commit is contained in:
113
.github/copilot-instructions.md
vendored
113
.github/copilot-instructions.md
vendored
@@ -349,6 +349,119 @@ requirements: python-docx==1.1.2, openpyxl==3.1.2
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🗄️ 数据库连接规范 (Database Connection)
|
||||||
|
|
||||||
|
### 复用 OpenWebUI 内部连接 (Re-use OpenWebUI's Internal Connection)
|
||||||
|
|
||||||
|
当插件需要持久化存储时,**必须**复用 Open WebUI 的内部数据库连接,而不是创建新的数据库引擎。这确保了:
|
||||||
|
|
||||||
|
- 插件与数据库类型无关(自动支持 PostgreSQL、SQLite 等)
|
||||||
|
- 自动继承 Open WebUI 的数据库配置
|
||||||
|
- 避免连接池资源浪费
|
||||||
|
- 保持与 Open WebUI 核心的兼容性
|
||||||
|
|
||||||
|
When a plugin requires persistent storage, it **MUST** re-use Open WebUI's internal database connection instead of creating a new database engine. This ensures:
|
||||||
|
|
||||||
|
- The plugin is database-agnostic (automatically supports PostgreSQL, SQLite, etc.)
|
||||||
|
- Automatic inheritance of Open WebUI's database configuration
|
||||||
|
- No wasted connection pool resources
|
||||||
|
- Compatibility with Open WebUI's core
|
||||||
|
|
||||||
|
### 实现示例 (Implementation Example)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Open WebUI internal database (re-use shared connection)
|
||||||
|
from open_webui.internal.db import engine as owui_engine
|
||||||
|
from open_webui.internal.db import Session as owui_Session
|
||||||
|
from open_webui.internal.db import Base as owui_Base
|
||||||
|
|
||||||
|
from sqlalchemy import Column, String, Text, DateTime, Integer, inspect
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class PluginTable(owui_Base):
|
||||||
|
"""Plugin storage table - inherits from OpenWebUI's Base"""
|
||||||
|
|
||||||
|
__tablename__ = "plugin_table_name"
|
||||||
|
__table_args__ = {"extend_existing": True} # Required to avoid conflicts on plugin reload
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
unique_id = Column(String(255), unique=True, nullable=False, index=True)
|
||||||
|
data = Column(Text, nullable=False)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
|
||||||
|
class Filter: # or Pipe, Action, etc.
|
||||||
|
def __init__(self):
|
||||||
|
self.valves = self.Valves()
|
||||||
|
self._db_engine = owui_engine
|
||||||
|
self._SessionLocal = owui_Session
|
||||||
|
self._init_database()
|
||||||
|
|
||||||
|
def _init_database(self):
|
||||||
|
"""Initialize the database table using OpenWebUI's shared connection."""
|
||||||
|
try:
|
||||||
|
inspector = inspect(self._db_engine)
|
||||||
|
if not inspector.has_table("plugin_table_name"):
|
||||||
|
PluginTable.__table__.create(bind=self._db_engine, checkfirst=True)
|
||||||
|
print("[Database] ✅ Created plugin table using OpenWebUI's shared connection.")
|
||||||
|
else:
|
||||||
|
print("[Database] ✅ Using OpenWebUI's shared connection. Table already exists.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Database] ❌ Initialization failed: {str(e)}")
|
||||||
|
|
||||||
|
def _save_data(self, unique_id: str, data: str):
|
||||||
|
"""Save data using context manager pattern."""
|
||||||
|
try:
|
||||||
|
with self._SessionLocal() as session:
|
||||||
|
# Your database operations here
|
||||||
|
session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Storage] ❌ Database save failed: {str(e)}")
|
||||||
|
|
||||||
|
def _load_data(self, unique_id: str):
|
||||||
|
"""Load data using context manager pattern."""
|
||||||
|
try:
|
||||||
|
with self._SessionLocal() as session:
|
||||||
|
record = session.query(PluginTable).filter_by(unique_id=unique_id).first()
|
||||||
|
if record:
|
||||||
|
session.expunge(record) # Detach from session for use after close
|
||||||
|
return record
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Load] ❌ Database read failed: {str(e)}")
|
||||||
|
return None
|
||||||
|
```
|
||||||
|
|
||||||
|
### 禁止的做法 (Prohibited Practices)
|
||||||
|
|
||||||
|
以下做法**已被弃用**,不应在新插件中使用:
|
||||||
|
|
||||||
|
The following practices are **deprecated** and should NOT be used in new plugins:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# ❌ 禁止: 读取 DATABASE_URL 环境变量
|
||||||
|
# ❌ Prohibited: Reading DATABASE_URL environment variable
|
||||||
|
database_url = os.getenv("DATABASE_URL")
|
||||||
|
|
||||||
|
# ❌ 禁止: 创建独立的数据库引擎
|
||||||
|
# ❌ Prohibited: Creating a separate database engine
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
self._db_engine = create_engine(database_url, **engine_args)
|
||||||
|
|
||||||
|
# ❌ 禁止: 创建独立的会话工厂
|
||||||
|
# ❌ Prohibited: Creating a separate session factory
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
self._SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=self._db_engine)
|
||||||
|
|
||||||
|
# ❌ 禁止: 使用独立的 Base
|
||||||
|
# ❌ Prohibited: Using a separate Base
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
Base = declarative_base()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🔧 代码规范 (Code Style)
|
## 🔧 代码规范 (Code Style)
|
||||||
|
|
||||||
### Python 规范
|
### Python 规范
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Async Context Compression Filter
|
# Async Context Compression Filter
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.0.0 | **License:** MIT
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.1.0 | **License:** MIT
|
||||||
|
|
||||||
> **Important Note**: To ensure the maintainability and usability of all filters, each filter should be accompanied by clear and complete documentation to fully explain its functionality, configuration, and usage.
|
> **Important Note**: To ensure the maintainability and usability of all filters, each filter should be accompanied by clear and complete documentation to fully explain its functionality, configuration, and usage.
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ This filter significantly reduces token consumption in long conversations by usi
|
|||||||
|
|
||||||
- ✅ **Automatic Compression**: Triggers context compression automatically based on a message count threshold.
|
- ✅ **Automatic Compression**: Triggers context compression automatically based on a message count threshold.
|
||||||
- ✅ **Asynchronous Summarization**: Generates summaries in the background without blocking the current chat response.
|
- ✅ **Asynchronous Summarization**: Generates summaries in the background without blocking the current chat response.
|
||||||
- ✅ **Persistent Storage**: Supports both PostgreSQL and SQLite databases to ensure summaries are not lost after a service restart.
|
- ✅ **Persistent Storage**: Uses Open WebUI's shared database connection - automatically supports any database backend (PostgreSQL, SQLite, etc.).
|
||||||
- ✅ **Flexible Retention Policy**: Freely configure the number of initial and final messages to keep, ensuring critical information and context continuity.
|
- ✅ **Flexible Retention Policy**: Freely configure the number of initial and final messages to keep, ensuring critical information and context continuity.
|
||||||
- ✅ **Smart Injection**: Intelligently injects the generated historical summary into the new context.
|
- ✅ **Smart Injection**: Intelligently injects the generated historical summary into the new context.
|
||||||
|
|
||||||
@@ -20,18 +20,11 @@ This filter significantly reduces token consumption in long conversations by usi
|
|||||||
|
|
||||||
## Installation & Configuration
|
## Installation & Configuration
|
||||||
|
|
||||||
### 1. Environment Variable
|
### 1. Database (Automatic)
|
||||||
|
|
||||||
This plugin requires a database connection. You **must** configure the `DATABASE_URL` in your Open WebUI environment variables.
|
This plugin automatically uses Open WebUI's shared database connection. **No additional database configuration is required.**
|
||||||
|
|
||||||
- **PostgreSQL Example**:
|
The `chat_summary` table will be created automatically on first run.
|
||||||
```
|
|
||||||
DATABASE_URL=postgresql://user:password@host:5432/openwebui
|
|
||||||
```
|
|
||||||
- **SQLite Example**:
|
|
||||||
```
|
|
||||||
DATABASE_URL=sqlite:///path/to/your/data/webui.db
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Filter Order
|
### 2. Filter Order
|
||||||
|
|
||||||
@@ -64,8 +57,8 @@ You can adjust the following parameters in the filter's settings:
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
- **Problem: Database connection failed.**
|
- **Problem: Database table not created.**
|
||||||
- **Solution**: Please ensure the `DATABASE_URL` environment variable is set correctly and that the database service is running.
|
- **Solution**: Ensure Open WebUI is properly configured with a database and check Open WebUI's logs for detailed error messages.
|
||||||
|
|
||||||
- **Problem: Summary not generated.**
|
- **Problem: Summary not generated.**
|
||||||
- **Solution**: Check if the `compression_threshold` has been met and verify that `summary_model` is configured correctly. Check the logs for detailed errors.
|
- **Solution**: Check if the `compression_threshold` has been met and verify that `summary_model` is configured correctly. Check the logs for detailed errors.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 异步上下文压缩过滤器
|
# 异步上下文压缩过滤器
|
||||||
|
|
||||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.0.0 | **许可证:** MIT
|
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.1.0 | **许可证:** MIT
|
||||||
|
|
||||||
> **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
|
> **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
- ✅ **自动压缩**: 基于消息数量阈值自动触发上下文压缩。
|
- ✅ **自动压缩**: 基于消息数量阈值自动触发上下文压缩。
|
||||||
- ✅ **异步摘要**: 在后台生成摘要,不阻塞当前对话的响应。
|
- ✅ **异步摘要**: 在后台生成摘要,不阻塞当前对话的响应。
|
||||||
- ✅ **持久化存储**: 支持 PostgreSQL 和 SQLite 数据库,确保摘要在服务重启后不丢失。
|
- ✅ **持久化存储**: 使用 Open WebUI 的共享数据库连接 - 自动支持任何数据库后端(PostgreSQL、SQLite 等)。
|
||||||
- ✅ **灵活保留策略**: 可自由配置保留对话头部和尾部的消息数量,确保关键信息和上下文的连贯性。
|
- ✅ **灵活保留策略**: 可自由配置保留对话头部和尾部的消息数量,确保关键信息和上下文的连贯性。
|
||||||
- ✅ **智能注入**: 将生成的历史摘要智能地注入到新的上下文中。
|
- ✅ **智能注入**: 将生成的历史摘要智能地注入到新的上下文中。
|
||||||
|
|
||||||
@@ -22,18 +22,11 @@
|
|||||||
|
|
||||||
## 安装与配置
|
## 安装与配置
|
||||||
|
|
||||||
### 1. 环境变量
|
### 1. 数据库(自动)
|
||||||
|
|
||||||
本插件的运行依赖于数据库,您**必须**在 Open WebUI 的环境变量中配置 `DATABASE_URL`。
|
本插件自动使用 Open WebUI 的共享数据库连接。**无需额外的数据库配置。**
|
||||||
|
|
||||||
- **PostgreSQL 示例**:
|
`chat_summary` 表将在首次运行时自动创建。
|
||||||
```
|
|
||||||
DATABASE_URL=postgresql://user:password@host:5432/openwebui
|
|
||||||
```
|
|
||||||
- **SQLite 示例**:
|
|
||||||
```
|
|
||||||
DATABASE_URL=sqlite:///path/to/your/data/webui.db
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 过滤器顺序
|
### 2. 过滤器顺序
|
||||||
|
|
||||||
@@ -101,8 +94,8 @@
|
|||||||
|
|
||||||
## 故障排除
|
## 故障排除
|
||||||
|
|
||||||
- **问题:数据库连接失败**
|
- **问题:数据库表未创建**
|
||||||
- **解决**:请确认 `DATABASE_URL` 环境变量已正确设置,并且数据库服务运行正常。
|
- **解决**:确保 Open WebUI 已正确配置数据库,并查看 Open WebUI 的日志以获取详细的错误信息。
|
||||||
|
|
||||||
- **问题:摘要未生成**
|
- **问题:摘要未生成**
|
||||||
- **解决**:检查 `compression_threshold_tokens` 是否已达到,并确认 `summary_model` 配置正确。查看日志以获取详细错误。
|
- **解决**:检查 `compression_threshold_tokens` 是否已达到,并确认 `summary_model` 配置正确。查看日志以获取详细错误。
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ author: Fu-Jie
|
|||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
description: Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.
|
description: Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.
|
||||||
version: 1.0.1
|
version: 1.1.0
|
||||||
license: MIT
|
license: MIT
|
||||||
|
|
||||||
═══════════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -49,14 +49,13 @@ Phase 2: Outlet (Post-response processing)
|
|||||||
💾 Storage
|
💾 Storage
|
||||||
═══════════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
This filter uses a database for persistent storage, configured via the `DATABASE_URL` environment variable. It supports both PostgreSQL and SQLite.
|
This filter uses Open WebUI's shared database connection for persistent storage.
|
||||||
|
It automatically reuses Open WebUI's internal SQLAlchemy engine and SessionLocal,
|
||||||
|
making the plugin database-agnostic and ensuring compatibility with any database
|
||||||
|
backend that Open WebUI supports (PostgreSQL, SQLite, etc.).
|
||||||
|
|
||||||
Configuration:
|
No additional database configuration is required - the plugin inherits
|
||||||
- The `DATABASE_URL` environment variable must be set.
|
Open WebUI's database settings automatically.
|
||||||
- PostgreSQL Example: `postgresql://user:password@host:5432/openwebui`
|
|
||||||
- SQLite Example: `sqlite:///path/to/your/database.db`
|
|
||||||
|
|
||||||
The filter automatically selects the appropriate database driver based on the `DATABASE_URL` prefix (`postgres` or `sqlite`).
|
|
||||||
|
|
||||||
Table Structure (`chat_summary`):
|
Table Structure (`chat_summary`):
|
||||||
- id: Primary Key (auto-increment)
|
- id: Primary Key (auto-increment)
|
||||||
@@ -142,21 +141,8 @@ debug_mode
|
|||||||
🔧 Deployment
|
🔧 Deployment
|
||||||
═══════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════
|
||||||
|
|
||||||
Docker Compose Example:
|
The plugin automatically uses Open WebUI's shared database connection.
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
No additional database configuration is required.
|
||||||
services:
|
|
||||||
openwebui:
|
|
||||||
environment:
|
|
||||||
DATABASE_URL: postgresql://user:password@postgres:5432/openwebui
|
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
|
|
||||||
postgres:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: user
|
|
||||||
POSTGRES_PASSWORD: password
|
|
||||||
POSTGRES_DB: openwebui
|
|
||||||
|
|
||||||
Suggested Filter Installation Order:
|
Suggested Filter Installation Order:
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
@@ -201,9 +187,10 @@ Statistics:
|
|||||||
⚠️ Important Notes
|
⚠️ Important Notes
|
||||||
═══════════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
1. Database Permissions
|
1. Database Connection
|
||||||
⚠ Ensure the user specified in `DATABASE_URL` has permissions to create tables.
|
✓ The plugin uses Open WebUI's shared database connection automatically.
|
||||||
⚠ The `chat_summary` table will be created automatically on first run.
|
✓ No additional configuration is required.
|
||||||
|
✓ The `chat_summary` table will be created automatically on first run.
|
||||||
|
|
||||||
2. Retention Policy
|
2. Retention Policy
|
||||||
⚠ The `keep_first` setting is crucial for preserving initial messages that contain system prompts. Configure it as needed.
|
⚠ The `keep_first` setting is crucial for preserving initial messages that contain system prompts. Configure it as needed.
|
||||||
@@ -226,13 +213,11 @@ Statistics:
|
|||||||
🐛 Troubleshooting
|
🐛 Troubleshooting
|
||||||
═══════════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
Problem: Database connection failed
|
Problem: Database table not created
|
||||||
Solution:
|
Solution:
|
||||||
1. Verify that the `DATABASE_URL` environment variable is set correctly.
|
1. Ensure Open WebUI is properly configured with a database.
|
||||||
2. Confirm that `DATABASE_URL` starts with either `sqlite` or `postgres`.
|
2. Check the Open WebUI container logs for detailed error messages.
|
||||||
3. Ensure the database service is running and network connectivity is normal.
|
3. Verify that Open WebUI's database connection is working correctly.
|
||||||
4. Validate the username, password, host, and port in the connection URL.
|
|
||||||
5. Check the Open WebUI container logs for detailed error messages.
|
|
||||||
|
|
||||||
Problem: Summary not generated
|
Problem: Summary not generated
|
||||||
Solution:
|
Solution:
|
||||||
@@ -258,7 +243,6 @@ from typing import Optional, Dict, Any, List, Union, Callable, Awaitable
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
# Open WebUI built-in imports
|
# Open WebUI built-in imports
|
||||||
@@ -267,6 +251,11 @@ from open_webui.models.users import Users
|
|||||||
from fastapi.requests import Request
|
from fastapi.requests import Request
|
||||||
from open_webui.main import app as webui_app
|
from open_webui.main import app as webui_app
|
||||||
|
|
||||||
|
# Open WebUI internal database (re-use shared connection)
|
||||||
|
from open_webui.internal.db import engine as owui_engine
|
||||||
|
from open_webui.internal.db import Session as owui_Session
|
||||||
|
from open_webui.internal.db import Base as owui_Base
|
||||||
|
|
||||||
# Try to import tiktoken
|
# Try to import tiktoken
|
||||||
try:
|
try:
|
||||||
import tiktoken
|
import tiktoken
|
||||||
@@ -274,18 +263,15 @@ except ImportError:
|
|||||||
tiktoken = None
|
tiktoken = None
|
||||||
|
|
||||||
# Database imports
|
# Database imports
|
||||||
from sqlalchemy import create_engine, Column, String, Text, DateTime, Integer
|
from sqlalchemy import Column, String, Text, DateTime, Integer, inspect
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
Base = declarative_base()
|
|
||||||
|
|
||||||
|
class ChatSummary(owui_Base):
|
||||||
class ChatSummary(Base):
|
|
||||||
"""Chat Summary Storage Table"""
|
"""Chat Summary Storage Table"""
|
||||||
|
|
||||||
__tablename__ = "chat_summary"
|
__tablename__ = "chat_summary"
|
||||||
|
__table_args__ = {"extend_existing": True}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
chat_id = Column(String(255), unique=True, nullable=False, index=True)
|
chat_id = Column(String(255), unique=True, nullable=False, index=True)
|
||||||
@@ -298,74 +284,29 @@ class ChatSummary(Base):
|
|||||||
class Filter:
|
class Filter:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.valves = self.Valves()
|
self.valves = self.Valves()
|
||||||
self._db_engine = None
|
self._db_engine = owui_engine
|
||||||
self._SessionLocal = None
|
self._SessionLocal = owui_Session
|
||||||
self.temp_state = {} # Used to pass temporary data between inlet and outlet
|
self.temp_state = {} # Used to pass temporary data between inlet and outlet
|
||||||
self._init_database()
|
self._init_database()
|
||||||
|
|
||||||
def _init_database(self):
|
def _init_database(self):
|
||||||
"""Initializes the database connection and table."""
|
"""Initializes the database table using Open WebUI's shared connection."""
|
||||||
try:
|
try:
|
||||||
database_url = os.getenv("DATABASE_URL")
|
# Check if table exists using SQLAlchemy inspect
|
||||||
|
inspector = inspect(self._db_engine)
|
||||||
if not database_url:
|
if not inspector.has_table("chat_summary"):
|
||||||
|
# Create the chat_summary table if it doesn't exist
|
||||||
|
ChatSummary.__table__.create(bind=self._db_engine, checkfirst=True)
|
||||||
print(
|
print(
|
||||||
"[Database] ❌ Error: DATABASE_URL environment variable is not set. Please set this variable."
|
"[Database] ✅ Successfully created chat_summary table using Open WebUI's shared database connection."
|
||||||
)
|
)
|
||||||
self._db_engine = None
|
|
||||||
self._SessionLocal = None
|
|
||||||
return
|
|
||||||
|
|
||||||
db_type = None
|
|
||||||
engine_args = {}
|
|
||||||
|
|
||||||
if database_url.startswith("sqlite"):
|
|
||||||
db_type = "SQLite"
|
|
||||||
engine_args = {
|
|
||||||
"connect_args": {"check_same_thread": False},
|
|
||||||
"echo": False,
|
|
||||||
}
|
|
||||||
elif database_url.startswith("postgres"):
|
|
||||||
db_type = "PostgreSQL"
|
|
||||||
if database_url.startswith("postgres://"):
|
|
||||||
database_url = database_url.replace(
|
|
||||||
"postgres://", "postgresql://", 1
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
"[Database] ℹ️ Automatically converted postgres:// to postgresql://"
|
|
||||||
)
|
|
||||||
engine_args = {
|
|
||||||
"pool_pre_ping": True,
|
|
||||||
"pool_recycle": 3600,
|
|
||||||
"echo": False,
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"[Database] ❌ Error: Unsupported database type. DATABASE_URL must start with 'sqlite' or 'postgres'. Current value: {database_url}"
|
"[Database] ✅ Using Open WebUI's shared database connection. chat_summary table already exists."
|
||||||
)
|
|
||||||
self._db_engine = None
|
|
||||||
self._SessionLocal = None
|
|
||||||
return
|
|
||||||
|
|
||||||
# Create database engine
|
|
||||||
self._db_engine = create_engine(database_url, **engine_args)
|
|
||||||
|
|
||||||
# Create session factory
|
|
||||||
self._SessionLocal = sessionmaker(
|
|
||||||
autocommit=False, autoflush=False, bind=self._db_engine
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create table if it doesn't exist
|
|
||||||
Base.metadata.create_all(bind=self._db_engine)
|
|
||||||
|
|
||||||
print(
|
|
||||||
f"[Database] ✅ Successfully connected to {db_type} and initialized the chat_summary table."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Database] ❌ Initialization failed: {str(e)}")
|
print(f"[Database] ❌ Initialization failed: {str(e)}")
|
||||||
self._db_engine = None
|
|
||||||
self._SessionLocal = None
|
|
||||||
|
|
||||||
class Valves(BaseModel):
|
class Valves(BaseModel):
|
||||||
priority: int = Field(
|
priority: int = Field(
|
||||||
@@ -416,14 +357,8 @@ class Filter:
|
|||||||
|
|
||||||
def _save_summary(self, chat_id: str, summary: str, compressed_count: int):
|
def _save_summary(self, chat_id: str, summary: str, compressed_count: int):
|
||||||
"""Saves the summary to the database."""
|
"""Saves the summary to the database."""
|
||||||
if not self._SessionLocal:
|
|
||||||
if self.valves.debug_mode:
|
|
||||||
print("[Storage] Database not initialized, skipping summary save.")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
session = self._SessionLocal()
|
|
||||||
try:
|
try:
|
||||||
|
with self._SessionLocal() as session:
|
||||||
# Find existing record
|
# Find existing record
|
||||||
existing = session.query(ChatSummary).filter_by(chat_id=chat_id).first()
|
existing = session.query(ChatSummary).filter_by(chat_id=chat_id).first()
|
||||||
|
|
||||||
@@ -457,27 +392,18 @@ class Filter:
|
|||||||
f"[Storage] Summary has been {action.lower()} in the database (Chat ID: {chat_id})"
|
f"[Storage] Summary has been {action.lower()} in the database (Chat ID: {chat_id})"
|
||||||
)
|
)
|
||||||
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Storage] ❌ Database save failed: {str(e)}")
|
print(f"[Storage] ❌ Database save failed: {str(e)}")
|
||||||
|
|
||||||
def _load_summary_record(self, chat_id: str) -> Optional[ChatSummary]:
|
def _load_summary_record(self, chat_id: str) -> Optional[ChatSummary]:
|
||||||
"""Loads the summary record object from the database."""
|
"""Loads the summary record object from the database."""
|
||||||
if not self._SessionLocal:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
session = self._SessionLocal()
|
|
||||||
try:
|
try:
|
||||||
|
with self._SessionLocal() as session:
|
||||||
record = session.query(ChatSummary).filter_by(chat_id=chat_id).first()
|
record = session.query(ChatSummary).filter_by(chat_id=chat_id).first()
|
||||||
if record:
|
if record:
|
||||||
# Detach the object from the session so it can be used after session close
|
# Detach the object from the session so it can be used after session close
|
||||||
session.expunge(record)
|
session.expunge(record)
|
||||||
return record
|
return record
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Load] ❌ Database read failed: {str(e)}")
|
print(f"[Load] ❌ Database read failed: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ author: Fu-Jie
|
|||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
description: 通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。
|
description: 通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。
|
||||||
version: 1.0.0
|
version: 1.1.0
|
||||||
license: MIT
|
license: MIT
|
||||||
|
|
||||||
═══════════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -49,14 +49,12 @@ license: MIT
|
|||||||
💾 存储方案
|
💾 存储方案
|
||||||
═══════════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
本过滤器使用数据库进行持久化存储,通过 `DATABASE_URL` 环境变量进行配置,支持 PostgreSQL 和 SQLite。
|
本过滤器使用 Open WebUI 的共享数据库连接进行持久化存储。
|
||||||
|
它自动复用 Open WebUI 内部的 SQLAlchemy 引擎和 SessionLocal,
|
||||||
|
使插件与数据库类型无关,并确保与 Open WebUI 支持的任何数据库后端
|
||||||
|
(PostgreSQL、SQLite 等)兼容。
|
||||||
|
|
||||||
配置方式:
|
无需额外的数据库配置 - 插件自动继承 Open WebUI 的数据库设置。
|
||||||
- 必须设置 `DATABASE_URL` 环境变量。
|
|
||||||
- PostgreSQL 示例: `postgresql://user:password@host:5432/openwebui`
|
|
||||||
- SQLite 示例: `sqlite:///path/to/your/database.db`
|
|
||||||
|
|
||||||
过滤器会根据 `DATABASE_URL` 的前缀(`postgres` 或 `sqlite`)自动选择合适的数据库驱动。
|
|
||||||
|
|
||||||
表结构:
|
表结构:
|
||||||
- id: 主键(自增)
|
- id: 主键(自增)
|
||||||
@@ -142,21 +140,8 @@ debug_mode (调试模式)
|
|||||||
🔧 部署配置
|
🔧 部署配置
|
||||||
═══════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════
|
||||||
|
|
||||||
Docker Compose 示例:
|
插件自动使用 Open WebUI 的共享数据库连接。
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
无需额外的数据库配置。
|
||||||
services:
|
|
||||||
openwebui:
|
|
||||||
environment:
|
|
||||||
DATABASE_URL: postgresql://user:password@postgres:5432/openwebui
|
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
|
|
||||||
postgres:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: user
|
|
||||||
POSTGRES_PASSWORD: password
|
|
||||||
POSTGRES_DB: openwebui
|
|
||||||
|
|
||||||
过滤器安装顺序建议:
|
过滤器安装顺序建议:
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
@@ -201,9 +186,10 @@ Docker Compose 示例:
|
|||||||
⚠️ 注意事项
|
⚠️ 注意事项
|
||||||
═══════════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
1. 数据库权限
|
1. 数据库连接
|
||||||
⚠ 确保 `DATABASE_URL` 指向的用户有创建表的权限。
|
✓ 插件自动使用 Open WebUI 的共享数据库连接。
|
||||||
⚠ 首次运行会自动创建 `chat_summary` 表。
|
✓ 无需额外配置。
|
||||||
|
✓ 首次运行会自动创建 `chat_summary` 表。
|
||||||
|
|
||||||
2. 保留策略
|
2. 保留策略
|
||||||
⚠ `keep_first` 配置对于保留包含提示或环境变量的初始消息非常重要。请根据需要进行配置。
|
⚠ `keep_first` 配置对于保留包含提示或环境变量的初始消息非常重要。请根据需要进行配置。
|
||||||
@@ -226,13 +212,11 @@ Docker Compose 示例:
|
|||||||
🐛 故障排除
|
🐛 故障排除
|
||||||
═══════════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
问题:数据库连接失败
|
问题:数据库表未创建
|
||||||
解决:
|
解决:
|
||||||
1. 确认 `DATABASE_URL` 环境变量已正确设置。
|
1. 确保 Open WebUI 已正确配置数据库。
|
||||||
2. 确认 `DATABASE_URL` 以 `sqlite` 或 `postgres` 开头。
|
2. 查看 Open WebUI 的容器日志以获取详细的错误信息。
|
||||||
3. 确认数据库服务正在运行,并且网络连接正常。
|
3. 验证 Open WebUI 的数据库连接是否正常工作。
|
||||||
4. 验证连接 URL 中的用户名、密码、主机和端口是否正确。
|
|
||||||
5. 查看 Open WebUI 的容器日志以获取详细的错误信息。
|
|
||||||
|
|
||||||
问题:摘要未生成
|
问题:摘要未生成
|
||||||
解决:
|
解决:
|
||||||
@@ -258,7 +242,6 @@ from typing import Optional, Dict, Any, List, Union, Callable, Awaitable
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
# Open WebUI 内置导入
|
# Open WebUI 内置导入
|
||||||
@@ -267,6 +250,11 @@ from open_webui.models.users import Users
|
|||||||
from fastapi.requests import Request
|
from fastapi.requests import Request
|
||||||
from open_webui.main import app as webui_app
|
from open_webui.main import app as webui_app
|
||||||
|
|
||||||
|
# Open WebUI 内部数据库 (复用共享连接)
|
||||||
|
from open_webui.internal.db import engine as owui_engine
|
||||||
|
from open_webui.internal.db import Session as owui_Session
|
||||||
|
from open_webui.internal.db import Base as owui_Base
|
||||||
|
|
||||||
# 尝试导入 tiktoken
|
# 尝试导入 tiktoken
|
||||||
try:
|
try:
|
||||||
import tiktoken
|
import tiktoken
|
||||||
@@ -274,18 +262,15 @@ except ImportError:
|
|||||||
tiktoken = None
|
tiktoken = None
|
||||||
|
|
||||||
# 数据库导入
|
# 数据库导入
|
||||||
from sqlalchemy import create_engine, Column, String, Text, DateTime, Integer
|
from sqlalchemy import Column, String, Text, DateTime, Integer, inspect
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
Base = declarative_base()
|
|
||||||
|
|
||||||
|
class ChatSummary(owui_Base):
|
||||||
class ChatSummary(Base):
|
|
||||||
"""对话摘要存储表"""
|
"""对话摘要存储表"""
|
||||||
|
|
||||||
__tablename__ = "chat_summary"
|
__tablename__ = "chat_summary"
|
||||||
|
__table_args__ = {"extend_existing": True}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
chat_id = Column(String(255), unique=True, nullable=False, index=True)
|
chat_id = Column(String(255), unique=True, nullable=False, index=True)
|
||||||
@@ -298,68 +283,29 @@ class ChatSummary(Base):
|
|||||||
class Filter:
|
class Filter:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.valves = self.Valves()
|
self.valves = self.Valves()
|
||||||
self._db_engine = None
|
self._db_engine = owui_engine
|
||||||
self._SessionLocal = None
|
self._SessionLocal = owui_Session
|
||||||
self.temp_state = {} # 用于在 inlet 和 outlet 之间传递临时数据
|
self.temp_state = {} # 用于在 inlet 和 outlet 之间传递临时数据
|
||||||
self._init_database()
|
self._init_database()
|
||||||
|
|
||||||
def _init_database(self):
|
def _init_database(self):
|
||||||
"""初始化数据库连接和表"""
|
"""使用 Open WebUI 的共享连接初始化数据库表"""
|
||||||
try:
|
try:
|
||||||
database_url = os.getenv("DATABASE_URL")
|
# 使用 SQLAlchemy inspect 检查表是否存在
|
||||||
|
inspector = inspect(self._db_engine)
|
||||||
if not database_url:
|
if not inspector.has_table("chat_summary"):
|
||||||
print("[数据库] ❌ 错误: DATABASE_URL 环境变量未设置。请设置该变量。")
|
# 如果表不存在则创建
|
||||||
self._db_engine = None
|
ChatSummary.__table__.create(bind=self._db_engine, checkfirst=True)
|
||||||
self._SessionLocal = None
|
print(
|
||||||
return
|
"[数据库] ✅ 使用 Open WebUI 的共享数据库连接成功创建 chat_summary 表。"
|
||||||
|
|
||||||
db_type = None
|
|
||||||
engine_args = {}
|
|
||||||
|
|
||||||
if database_url.startswith("sqlite"):
|
|
||||||
db_type = "SQLite"
|
|
||||||
engine_args = {
|
|
||||||
"connect_args": {"check_same_thread": False},
|
|
||||||
"echo": False,
|
|
||||||
}
|
|
||||||
elif database_url.startswith("postgres"):
|
|
||||||
db_type = "PostgreSQL"
|
|
||||||
if database_url.startswith("postgres://"):
|
|
||||||
database_url = database_url.replace(
|
|
||||||
"postgres://", "postgresql://", 1
|
|
||||||
)
|
)
|
||||||
print("[数据库] ℹ️ 已自动将 postgres:// 转换为 postgresql://")
|
|
||||||
engine_args = {
|
|
||||||
"pool_pre_ping": True,
|
|
||||||
"pool_recycle": 3600,
|
|
||||||
"echo": False,
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"[数据库] ❌ 错误: 不支持的数据库类型。DATABASE_URL 必须以 'sqlite' 或 'postgres' 开头。当前值: {database_url}"
|
"[数据库] ✅ 使用 Open WebUI 的共享数据库连接。chat_summary 表已存在。"
|
||||||
)
|
)
|
||||||
self._db_engine = None
|
|
||||||
self._SessionLocal = None
|
|
||||||
return
|
|
||||||
|
|
||||||
# 创建数据库引擎
|
|
||||||
self._db_engine = create_engine(database_url, **engine_args)
|
|
||||||
|
|
||||||
# 创建会话工厂
|
|
||||||
self._SessionLocal = sessionmaker(
|
|
||||||
autocommit=False, autoflush=False, bind=self._db_engine
|
|
||||||
)
|
|
||||||
|
|
||||||
# 创建表(如果不存在)
|
|
||||||
Base.metadata.create_all(bind=self._db_engine)
|
|
||||||
|
|
||||||
print(f"[数据库] ✅ 成功连接到 {db_type} 并初始化 chat_summary 表")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[数据库] ❌ 初始化失败: {str(e)}")
|
print(f"[数据库] ❌ 初始化失败: {str(e)}")
|
||||||
self._db_engine = None
|
|
||||||
self._SessionLocal = None
|
|
||||||
|
|
||||||
class Valves(BaseModel):
|
class Valves(BaseModel):
|
||||||
priority: int = Field(
|
priority: int = Field(
|
||||||
@@ -401,14 +347,8 @@ class Filter:
|
|||||||
|
|
||||||
def _save_summary(self, chat_id: str, summary: str, compressed_count: int):
|
def _save_summary(self, chat_id: str, summary: str, compressed_count: int):
|
||||||
"""保存摘要到数据库"""
|
"""保存摘要到数据库"""
|
||||||
if not self._SessionLocal:
|
|
||||||
if self.valves.debug_mode:
|
|
||||||
print("[存储] 数据库未初始化,跳过保存摘要")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
session = self._SessionLocal()
|
|
||||||
try:
|
try:
|
||||||
|
with self._SessionLocal() as session:
|
||||||
# 查找现有记录
|
# 查找现有记录
|
||||||
existing = session.query(ChatSummary).filter_by(chat_id=chat_id).first()
|
existing = session.query(ChatSummary).filter_by(chat_id=chat_id).first()
|
||||||
|
|
||||||
@@ -440,27 +380,18 @@ class Filter:
|
|||||||
action = "更新" if existing else "创建"
|
action = "更新" if existing else "创建"
|
||||||
print(f"[存储] 摘要已{action}到数据库 (Chat ID: {chat_id})")
|
print(f"[存储] 摘要已{action}到数据库 (Chat ID: {chat_id})")
|
||||||
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[存储] ❌ 数据库保存失败: {str(e)}")
|
print(f"[存储] ❌ 数据库保存失败: {str(e)}")
|
||||||
|
|
||||||
def _load_summary_record(self, chat_id: str) -> Optional[ChatSummary]:
|
def _load_summary_record(self, chat_id: str) -> Optional[ChatSummary]:
|
||||||
"""从数据库加载摘要记录对象"""
|
"""从数据库加载摘要记录对象"""
|
||||||
if not self._SessionLocal:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
session = self._SessionLocal()
|
|
||||||
try:
|
try:
|
||||||
|
with self._SessionLocal() as session:
|
||||||
record = session.query(ChatSummary).filter_by(chat_id=chat_id).first()
|
record = session.query(ChatSummary).filter_by(chat_id=chat_id).first()
|
||||||
if record:
|
if record:
|
||||||
# Detach the object from the session so it can be used after session close
|
# Detach the object from the session so it can be used after session close
|
||||||
session.expunge(record)
|
session.expunge(record)
|
||||||
return record
|
return record
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[加载] ❌ 数据库读取失败: {str(e)}")
|
print(f"[加载] ❌ 数据库读取失败: {str(e)}")
|
||||||
return None
|
return None
|
||||||
@@ -815,7 +746,7 @@ class Filter:
|
|||||||
|
|
||||||
# 计算当前总 Token (使用摘要模型进行计数)
|
# 计算当前总 Token (使用摘要模型进行计数)
|
||||||
total_tokens = await asyncio.to_thread(
|
total_tokens = await asyncio.to_thread(
|
||||||
self._calculate_messages_tokens, messages, summary_model_id
|
self._calculate_messages_tokens, messages
|
||||||
)
|
)
|
||||||
|
|
||||||
if total_tokens > max_context_tokens:
|
if total_tokens > max_context_tokens:
|
||||||
|
|||||||
Reference in New Issue
Block a user