Convert the ebook-search web app to async end to end and add concurrency
to the protected-phrase extraction and judging pipeline so large books no
longer block the event loop or the UI.
ORM / infra:
- Add get_async_postgres_engine and factor shared URL/connect_args building
into build_postgres_url (reused by the sync and async engine builders)
- Add async FastAPI session helpers (get_async_db, AsyncDbSession) with
expire_on_commit=False to avoid implicit IO under asyncio
App:
- Use AsyncEngine/AsyncSession throughout routes, search, ingest, embeddings,
answer, rerank and LLM calls; convert handlers to async
- Share a single httpx.AsyncClient in app state for LLM requests; size the
connection pool for concurrent phrase-judging workers
- Add judge_tasks: run per-book judging as tracked background tasks so a
book already being judged isn't double-queued
Protected phrases:
- Add a process pool (pool.py) and worker-count config
(extraction/judge book/phrase workers) to parallelize candidate generation
and judging
- Split admin actions into all/missing variants for generation and judging
Config:
- Add protected_phrase_extraction_workers, phrase_judge_book_workers,
phrase_judge_phrase_workers
Refactor protected phrase handling from a single module into a
python/ebook_search/protected_phrases package covering extraction,
storage, and runtime matching. Phrase filtering is now data-driven via
bundled TOML files: ignored_phrases, bad_starts, bad_ends, and
most_common_words.
Add phrase-tuning settings to EbookSearchConfig so candidate generation,
scoring, LLM judging, and matching are configurable rather than hardcoded:
token bounds, entity token limit, raw n-gram min count, frequency and
chapter-spread score thresholds, candidate/LLM/target caps, confidence
threshold, nesting defaults, and the phrase hit boost.