From e510b3b5806b47885b4ba7df1e45be841938e0a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 03:52:19 +0000 Subject: [PATCH] Refactor async-context-compression to use OpenWebUI's internal DB connection Co-authored-by: Fu-Jie <33599649+Fu-Jie@users.noreply.github.com> --- .github/copilot-instructions.md | 112 +++++++++++++ .../async-context-compression/README.md | 21 +-- .../async-context-compression/README_CN.md | 21 +-- .../async_context_compression.py | 147 +++++------------- .../异步上下文压缩.py | 144 +++++------------ 5 files changed, 199 insertions(+), 246 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 6c977e3..a83c9b4 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -349,6 +349,118 @@ 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" + + 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) ### Python 规范 diff --git a/plugins/filters/async-context-compression/README.md b/plugins/filters/async-context-compression/README.md index 0b94d63..c6305bd 100644 --- a/plugins/filters/async-context-compression/README.md +++ b/plugins/filters/async-context-compression/README.md @@ -1,6 +1,6 @@ # 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. @@ -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. - ✅ **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. - ✅ **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 -### 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**: - ``` - DATABASE_URL=postgresql://user:password@host:5432/openwebui - ``` -- **SQLite Example**: - ``` - DATABASE_URL=sqlite:///path/to/your/data/webui.db - ``` +The `chat_summary` table will be created automatically on first run. ### 2. Filter Order @@ -64,8 +57,8 @@ You can adjust the following parameters in the filter's settings: ## Troubleshooting -- **Problem: Database connection failed.** - - **Solution**: Please ensure the `DATABASE_URL` environment variable is set correctly and that the database service is running. +- **Problem: Database table not created.** + - **Solution**: Ensure Open WebUI is properly configured with a database and check Open WebUI's logs for detailed error messages. - **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. diff --git a/plugins/filters/async-context-compression/README_CN.md b/plugins/filters/async-context-compression/README_CN.md index 4983838..85f636c 100644 --- a/plugins/filters/async-context-compression/README_CN.md +++ b/plugins/filters/async-context-compression/README_CN.md @@ -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 示例**: - ``` - DATABASE_URL=postgresql://user:password@host:5432/openwebui - ``` -- **SQLite 示例**: - ``` - DATABASE_URL=sqlite:///path/to/your/data/webui.db - ``` +`chat_summary` 表将在首次运行时自动创建。 ### 2. 过滤器顺序 @@ -101,8 +94,8 @@ ## 故障排除 -- **问题:数据库连接失败** - - **解决**:请确认 `DATABASE_URL` 环境变量已正确设置,并且数据库服务运行正常。 +- **问题:数据库表未创建** + - **解决**:确保 Open WebUI 已正确配置数据库,并查看 Open WebUI 的日志以获取详细的错误信息。 - **问题:摘要未生成** - **解决**:检查 `compression_threshold_tokens` 是否已达到,并确认 `summary_model` 配置正确。查看日志以获取详细错误。 diff --git a/plugins/filters/async-context-compression/async_context_compression.py b/plugins/filters/async-context-compression/async_context_compression.py index 0678b74..7207ddb 100644 --- a/plugins/filters/async-context-compression/async_context_compression.py +++ b/plugins/filters/async-context-compression/async_context_compression.py @@ -5,7 +5,7 @@ author: Fu-Jie author_url: https://github.com/Fu-Jie 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. -version: 1.0.1 +version: 1.1.0 license: MIT ═══════════════════════════════════════════════════════════════════════════════ @@ -49,14 +49,13 @@ Phase 2: Outlet (Post-response processing) 💾 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: - - The `DATABASE_URL` environment variable must be set. - - 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`). +No additional database configuration is required - the plugin inherits +Open WebUI's database settings automatically. Table Structure (`chat_summary`): - id: Primary Key (auto-increment) @@ -142,21 +141,8 @@ debug_mode 🔧 Deployment ═══════════════════════════════════════════════════════ -Docker Compose Example: -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - 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 +The plugin automatically uses Open WebUI's shared database connection. +No additional database configuration is required. Suggested Filter Installation Order: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -201,9 +187,10 @@ Statistics: ⚠️ Important Notes ═══════════════════════════════════════════════════════════════════════════════ -1. Database Permissions - ⚠ Ensure the user specified in `DATABASE_URL` has permissions to create tables. - ⚠ The `chat_summary` table will be created automatically on first run. +1. Database Connection + ✓ The plugin uses Open WebUI's shared database connection automatically. + ✓ No additional configuration is required. + ✓ The `chat_summary` table will be created automatically on first run. 2. Retention Policy ⚠ The `keep_first` setting is crucial for preserving initial messages that contain system prompts. Configure it as needed. @@ -226,13 +213,11 @@ Statistics: 🐛 Troubleshooting ═══════════════════════════════════════════════════════════════════════════════ -Problem: Database connection failed +Problem: Database table not created Solution: - 1. Verify that the `DATABASE_URL` environment variable is set correctly. - 2. Confirm that `DATABASE_URL` starts with either `sqlite` or `postgres`. - 3. Ensure the database service is running and network connectivity is normal. - 4. Validate the username, password, host, and port in the connection URL. - 5. Check the Open WebUI container logs for detailed error messages. + 1. Ensure Open WebUI is properly configured with a database. + 2. Check the Open WebUI container logs for detailed error messages. + 3. Verify that Open WebUI's database connection is working correctly. Problem: Summary not generated Solution: @@ -258,7 +243,6 @@ from typing import Optional, Dict, Any, List, Union, Callable, Awaitable import asyncio import json import hashlib -import os import time # Open WebUI built-in imports @@ -267,6 +251,11 @@ from open_webui.models.users import Users from fastapi.requests import Request 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: import tiktoken @@ -274,15 +263,11 @@ except ImportError: tiktoken = None # Database imports -from sqlalchemy import create_engine, Column, String, Text, DateTime, Integer -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker +from sqlalchemy import Column, String, Text, DateTime, Integer, inspect from datetime import datetime -Base = declarative_base() - -class ChatSummary(Base): +class ChatSummary(owui_Base): """Chat Summary Storage Table""" __tablename__ = "chat_summary" @@ -298,74 +283,29 @@ class ChatSummary(Base): class Filter: def __init__(self): self.valves = self.Valves() - self._db_engine = None - self._SessionLocal = None + self._db_engine = owui_engine + self._SessionLocal = owui_Session self.temp_state = {} # Used to pass temporary data between inlet and outlet self._init_database() def _init_database(self): - """Initializes the database connection and table.""" + """Initializes the database table using Open WebUI's shared connection.""" try: - database_url = os.getenv("DATABASE_URL") - - if not database_url: + # Check if table exists using SQLAlchemy inspect + inspector = inspect(self._db_engine) + 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( - "[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: 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: print(f"[Database] ❌ Initialization failed: {str(e)}") - self._db_engine = None - self._SessionLocal = None class Valves(BaseModel): priority: int = Field( @@ -416,14 +356,8 @@ class Filter: def _save_summary(self, chat_id: str, summary: str, compressed_count: int): """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: + with self._SessionLocal() as session: # Find existing record existing = session.query(ChatSummary).filter_by(chat_id=chat_id).first() @@ -457,27 +391,18 @@ class Filter: f"[Storage] Summary has been {action.lower()} in the database (Chat ID: {chat_id})" ) - finally: - session.close() - except Exception as e: print(f"[Storage] ❌ Database save failed: {str(e)}") def _load_summary_record(self, chat_id: str) -> Optional[ChatSummary]: """Loads the summary record object from the database.""" - if not self._SessionLocal: - return None - try: - session = self._SessionLocal() - try: + with self._SessionLocal() as session: record = session.query(ChatSummary).filter_by(chat_id=chat_id).first() if record: # Detach the object from the session so it can be used after session close session.expunge(record) return record - finally: - session.close() except Exception as e: print(f"[Load] ❌ Database read failed: {str(e)}") return None diff --git a/plugins/filters/async-context-compression/异步上下文压缩.py b/plugins/filters/async-context-compression/异步上下文压缩.py index 4d30129..673edc5 100644 --- a/plugins/filters/async-context-compression/异步上下文压缩.py +++ b/plugins/filters/async-context-compression/异步上下文压缩.py @@ -5,7 +5,7 @@ author: Fu-Jie author_url: https://github.com/Fu-Jie funding_url: https://github.com/Fu-Jie/awesome-openwebui description: 通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。 -version: 1.0.0 +version: 1.1.0 license: MIT ═══════════════════════════════════════════════════════════════════════════════ @@ -49,14 +49,12 @@ license: MIT 💾 存储方案 ═══════════════════════════════════════════════════════════════════════════════ -本过滤器使用数据库进行持久化存储,通过 `DATABASE_URL` 环境变量进行配置,支持 PostgreSQL 和 SQLite。 +本过滤器使用 Open WebUI 的共享数据库连接进行持久化存储。 +它自动复用 Open WebUI 内部的 SQLAlchemy 引擎和 SessionLocal, +使插件与数据库类型无关,并确保与 Open WebUI 支持的任何数据库后端 +(PostgreSQL、SQLite 等)兼容。 -配置方式: - - 必须设置 `DATABASE_URL` 环境变量。 - - PostgreSQL 示例: `postgresql://user:password@host:5432/openwebui` - - SQLite 示例: `sqlite:///path/to/your/database.db` - -过滤器会根据 `DATABASE_URL` 的前缀(`postgres` 或 `sqlite`)自动选择合适的数据库驱动。 +无需额外的数据库配置 - 插件自动继承 Open WebUI 的数据库设置。 表结构: - id: 主键(自增) @@ -142,21 +140,8 @@ debug_mode (调试模式) 🔧 部署配置 ═══════════════════════════════════════════════════════ -Docker Compose 示例: -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - 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 +插件自动使用 Open WebUI 的共享数据库连接。 +无需额外的数据库配置。 过滤器安装顺序建议: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -201,9 +186,10 @@ Docker Compose 示例: ⚠️ 注意事项 ═══════════════════════════════════════════════════════════════════════════════ -1. 数据库权限 - ⚠ 确保 `DATABASE_URL` 指向的用户有创建表的权限。 - ⚠ 首次运行会自动创建 `chat_summary` 表。 +1. 数据库连接 + ✓ 插件自动使用 Open WebUI 的共享数据库连接。 + ✓ 无需额外配置。 + ✓ 首次运行会自动创建 `chat_summary` 表。 2. 保留策略 ⚠ `keep_first` 配置对于保留包含提示或环境变量的初始消息非常重要。请根据需要进行配置。 @@ -226,13 +212,11 @@ Docker Compose 示例: 🐛 故障排除 ═══════════════════════════════════════════════════════════════════════════════ -问题:数据库连接失败 +问题:数据库表未创建 解决: - 1. 确认 `DATABASE_URL` 环境变量已正确设置。 - 2. 确认 `DATABASE_URL` 以 `sqlite` 或 `postgres` 开头。 - 3. 确认数据库服务正在运行,并且网络连接正常。 - 4. 验证连接 URL 中的用户名、密码、主机和端口是否正确。 - 5. 查看 Open WebUI 的容器日志以获取详细的错误信息。 + 1. 确保 Open WebUI 已正确配置数据库。 + 2. 查看 Open WebUI 的容器日志以获取详细的错误信息。 + 3. 验证 Open WebUI 的数据库连接是否正常工作。 问题:摘要未生成 解决: @@ -258,7 +242,6 @@ from typing import Optional, Dict, Any, List, Union, Callable, Awaitable import asyncio import json import hashlib -import os import time # Open WebUI 内置导入 @@ -267,6 +250,11 @@ from open_webui.models.users import Users from fastapi.requests import Request 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 try: import tiktoken @@ -274,15 +262,11 @@ except ImportError: tiktoken = None # 数据库导入 -from sqlalchemy import create_engine, Column, String, Text, DateTime, Integer -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker +from sqlalchemy import Column, String, Text, DateTime, Integer, inspect from datetime import datetime -Base = declarative_base() - -class ChatSummary(Base): +class ChatSummary(owui_Base): """对话摘要存储表""" __tablename__ = "chat_summary" @@ -298,68 +282,29 @@ class ChatSummary(Base): class Filter: def __init__(self): self.valves = self.Valves() - self._db_engine = None - self._SessionLocal = None + self._db_engine = owui_engine + self._SessionLocal = owui_Session self.temp_state = {} # 用于在 inlet 和 outlet 之间传递临时数据 self._init_database() def _init_database(self): - """初始化数据库连接和表""" + """使用 Open WebUI 的共享连接初始化数据库表""" try: - database_url = os.getenv("DATABASE_URL") - - if not database_url: - print("[数据库] ❌ 错误: DATABASE_URL 环境变量未设置。请设置该变量。") - 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("[数据库] ℹ️ 已自动将 postgres:// 转换为 postgresql://") - engine_args = { - "pool_pre_ping": True, - "pool_recycle": 3600, - "echo": False, - } + # 使用 SQLAlchemy inspect 检查表是否存在 + inspector = inspect(self._db_engine) + if not inspector.has_table("chat_summary"): + # 如果表不存在则创建 + ChatSummary.__table__.create(bind=self._db_engine, checkfirst=True) + print( + "[数据库] ✅ 使用 Open WebUI 的共享数据库连接成功创建 chat_summary 表。" + ) else: 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: print(f"[数据库] ❌ 初始化失败: {str(e)}") - self._db_engine = None - self._SessionLocal = None class Valves(BaseModel): priority: int = Field( @@ -401,14 +346,8 @@ class Filter: 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: + with self._SessionLocal() as session: # 查找现有记录 existing = session.query(ChatSummary).filter_by(chat_id=chat_id).first() @@ -440,27 +379,18 @@ class Filter: action = "更新" if existing else "创建" print(f"[存储] 摘要已{action}到数据库 (Chat ID: {chat_id})") - finally: - session.close() - except Exception as e: print(f"[存储] ❌ 数据库保存失败: {str(e)}") def _load_summary_record(self, chat_id: str) -> Optional[ChatSummary]: """从数据库加载摘要记录对象""" - if not self._SessionLocal: - return None - try: - session = self._SessionLocal() - try: + with self._SessionLocal() as session: record = session.query(ChatSummary).filter_by(chat_id=chat_id).first() if record: # Detach the object from the session so it can be used after session close session.expunge(record) return record - finally: - session.close() except Exception as e: print(f"[加载] ❌ 数据库读取失败: {str(e)}") return None