Arag / app /services /summarizer.py
AuthorBot
Restructure project for HF Spaces deployment
772f852
Raw
History Blame Contribute Delete
2.58 kB
"""Author RAG Chatbot SaaS β€” Book Summarizer.
Generates a concise summary for each book using Facebook's BART model.
This runs ONCE after embedding β€” result stored on the Book record as ai_summary.
RULE: Summarizer runs async after embedding completes β€” never blocks ingestion.
"""
import structlog
logger = structlog.get_logger(__name__)
_summarizer_pipeline = None
async def get_summarizer():
"""Lazily load and cache the BART summarization pipeline.
Returns:
HuggingFace pipeline for summarization.
"""
global _summarizer_pipeline
if _summarizer_pipeline is None:
from transformers import pipeline
logger.info("Loading BART summarizer model (first load may take a moment)...")
_summarizer_pipeline = pipeline(
"summarization",
model="facebook/bart-large-cnn",
device=-1, # CPU (-1), use 0 for GPU
)
logger.info("BART summarizer loaded successfully")
return _summarizer_pipeline
async def summarize_book(text: str, max_length: int = 300) -> str:
"""Generate a concise summary of a book's content using BART.
Uses the first 3000 characters as representative input (BART has input limits).
Falls back gracefully if model fails.
Args:
text: Full extracted book text.
max_length: Maximum summary length in tokens.
Returns:
Summary string (or empty string on failure).
"""
if not text.strip():
return ""
# BART works best with ~1024 tokens input β€” use beginning of book
input_text = text[:4000].strip()
try:
summarizer = await get_summarizer()
result = summarizer(
input_text,
max_length=max_length,
min_length=60,
do_sample=False,
truncation=True,
)
summary = result[0]["summary_text"].strip()
logger.info("Book summary generated", length=len(summary))
return summary
except Exception as e:
logger.error("BART summarization failed", error=str(e))
return _extract_first_paragraph(text)
def _extract_first_paragraph(text: str) -> str:
"""Fallback: extract the first meaningful paragraph as a summary.
Args:
text: Full document text.
Returns:
First non-empty paragraph, truncated to 500 chars.
"""
for paragraph in text.split("\n\n"):
stripped = paragraph.strip()
if len(stripped) > 100:
return stripped[:500] + ("..." if len(stripped) > 500 else "")
return text[:300].strip()