fix: NullPool for Celery workers + skip pgvector on first message

- Celery workers use NullPool to avoid "Future attached to a different
  loop" errors from stale pooled async connections across asyncio.run()
  calls. FastAPI keeps regular pool (single event loop, safe to reuse).
- Skip pgvector similarity search when no conversation history exists
  (first message) — saves ~3s embedding + query overhead.
- Wrap pgvector retrieval in try/except to prevent DB errors from
  blocking the LLM response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 18:21:19 -06:00
parent 5e4d9ce144
commit 2116059157
2 changed files with 34 additions and 16 deletions

View File

@@ -14,19 +14,32 @@ from __future__ import annotations
from collections.abc import AsyncGenerator
import os
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import NullPool
from shared.config import settings
# ---------------------------------------------------------------------------
# Engine — one per process; shared across all requests
#
# Celery workers use asyncio.run() per task, creating a new event loop each
# time. Connection pools hold connections bound to the previous (closed) loop,
# causing "Future attached to a different loop" errors. NullPool avoids this
# by never reusing connections. FastAPI (single event loop) can safely use a
# regular pool, but NullPool works fine there too with minimal overhead.
# ---------------------------------------------------------------------------
_is_celery_worker = "celery" in os.environ.get("_", "") or "celery" in " ".join(os.sys.argv)
engine: AsyncEngine = create_async_engine(
settings.database_url,
echo=settings.debug,
pool_pre_ping=True,
pool_size=10,
max_overflow=20,
**({"poolclass": NullPool} if _is_celery_worker else {
"pool_pre_ping": True,
"pool_size": 10,
"max_overflow": 20,
}),
)
# ---------------------------------------------------------------------------