Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions astrbot/core/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from dataclasses import dataclass

from deprecated import deprecated
from sqlalchemy.engine import make_url
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import NullPool

from astrbot.core.db.po import (
ApiKey,
Expand Down Expand Up @@ -40,13 +42,22 @@ def __init__(self) -> None:
# second write is attempted. Setting timeout=30 tells SQLite to
# wait up to 30 s for the lock, which is enough to ride out brief
# write bursts from concurrent agent/metrics/session operations.
is_sqlite = "sqlite" in self.DATABASE_URL
db_url = make_url(self.DATABASE_URL)
is_sqlite = db_url.get_backend_name() == "sqlite"
connect_args = {"timeout": 30} if is_sqlite else {}
engine_kwargs = {
"echo": False,
"future": True,
"connect_args": connect_args,
}
if is_sqlite:
# Keep SQLite async engines off SQLAlchemy's default async queue
# pool so packaged runtimes don't depend on dialect-specific pool
# event support.
engine_kwargs["poolclass"] = NullPool
self.engine = create_async_engine(
self.DATABASE_URL,
echo=False,
future=True,
connect_args=connect_args,
**engine_kwargs,
)
self.AsyncSessionLocal = async_sessionmaker(
self.engine,
Expand Down
31 changes: 28 additions & 3 deletions astrbot/core/db/vec_db/faiss_impl/document_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
from pathlib import Path

from sqlalchemy import Column, Text, bindparam
from sqlalchemy.dialects import sqlite
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import NullPool
from sqlalchemy.schema import CreateTable
from sqlmodel import Field, MetaData, SQLModel, col, func, select, text

from astrbot.core import logger
Expand Down Expand Up @@ -34,7 +37,7 @@ class Document(BaseDocModel, table=True):
primary_key=True,
sa_column_kwargs={"autoincrement": True},
)
doc_id: str = Field(nullable=False)
doc_id: str = Field(nullable=False, unique=True)
text: str = Field(nullable=False)
metadata_: str | None = Field(default=None, sa_column=Column("metadata", Text))
created_at: datetime | None = Field(default=None)
Expand All @@ -60,8 +63,7 @@ async def initialize(self) -> None:
"""Initialize the SQLite database and create the documents table if it doesn't exist."""
await self.connect()
async with self.engine.begin() as conn: # type: ignore
# Create tables using SQLModel
await conn.run_sync(BaseDocModel.metadata.create_all)
await self._ensure_documents_table(conn)

try:
await conn.execute(
Expand Down Expand Up @@ -94,6 +96,28 @@ async def initialize(self) -> None:
await self._initialize_fts5(conn)
await conn.commit()

async def _ensure_documents_table(self, executor) -> None:
"""Create the document table from the SQLModel definition."""
result = await executor.execute(
text(
"""
SELECT 1
FROM sqlite_master
WHERE type='table' AND name=:table_name
LIMIT 1
""",
),
{"table_name": Document.__tablename__},
)
if result.scalar_one_or_none() is not None:
return

create_table = CreateTable(Document.__table__, if_not_exists=True) # type: ignore[attr-defined]

await executor.execute(
text(str(create_table.compile(dialect=sqlite.dialect())))
)

async def _initialize_fts5(self, executor) -> None:
try:
await self._create_fts5_table(executor, if_not_exists=True)
Expand Down Expand Up @@ -197,6 +221,7 @@ async def connect(self) -> None:
self.DATABASE_URL,
echo=False,
future=True,
poolclass=NullPool,
)
self.async_session_maker = sessionmaker(
self.engine, # type: ignore
Expand Down
4 changes: 2 additions & 2 deletions astrbot/core/knowledge_base/kb_db_sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from sqlalchemy import delete, func, select, text, update
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import NullPool
from sqlmodel import col, desc

from astrbot.core import logger
Expand Down Expand Up @@ -40,8 +41,7 @@ def __init__(self, db_path: str | None = None) -> None:
self.engine = create_async_engine(
self.DATABASE_URL,
echo=False,
pool_pre_ping=True,
pool_recycle=3600,
poolclass=NullPool,
Comment thread
hibiki233i marked this conversation as resolved.
)
Comment thread
sourcery-ai[bot] marked this conversation as resolved.

# 创建会话工厂
Expand Down
6 changes: 5 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
download_dashboard,
get_dashboard_version,
)
from astrbot.core.utils.runtime_env import is_packaged_desktop_runtime # noqa: E402

# 将父目录添加到 sys.path
sys.path.append(Path(__file__).parent.as_posix())
Expand All @@ -50,7 +51,10 @@ def check_env() -> None:
sys.path.insert(0, astrbot_root)

site_packages_path = get_astrbot_site_packages_path()
if site_packages_path not in sys.path:
if not is_packaged_desktop_runtime() and site_packages_path not in sys.path:
# Packaged desktop runtime keeps shared plugin dependencies out of the
# global import path so bundled core libraries don't mix with user-
# installed wheels from ~/.astrbot/data/site-packages.
sys.path.append(site_packages_path)

os.makedirs(get_astrbot_config_path(), exist_ok=True)
Expand Down