arxplorer / src /core /summary_engine /summary.py
Subhadeep Mandal
Added summary fix
347865f
Raw
History Blame Contribute Delete
5.18 kB
import tiktoken
from typing import Optional
from fastapi import Request
from qdrant_client import models
from ..vectorstore import VectorStoreFactory
from ..embedding import EmbeddingFactory
from ..llm import LLMFactory
from ...core.logger import SingletonLogger
from .pdf_parser import load_pdf_content
from ...config.prompts import PAPER_SUMMARY_SYSTEM_PROMPT
logger = SingletonLogger().get_logger()
class SummaryEngine:
"""SummaryEngine class for generating summaries of papers."""
DEFAULT_MODEL = "groq/qwen3-32b"
@staticmethod
async def generate_paper_summary(
arxiv_id: str = None, pdf_url: str = None, request: Optional[Request] = None,
model_name: Optional[str] = None,
) -> str:
try:
embedding = EmbeddingFactory.build_embedding_model(request=request)
vector_store = VectorStoreFactory.build_vector_store(
embedding_model=embedding
)
full_content = ""
if arxiv_id:
filter_condition = models.Filter(
must=[
models.FieldCondition(
key="metadata.paper_id",
match=models.MatchValue(value=arxiv_id),
)
]
)
retriever = vector_store.as_retriever(
search_kwargs={"filter": filter_condition, "k": 200}
)
docs = await retriever._aget_relevant_documents(
query="*", run_manager=None
)
sorted_docs = await SummaryEngine._sort_docs(docs)
full_content = "\n\n".join([doc.page_content for doc in sorted_docs])
if not full_content.strip() and pdf_url:
logger.info(
"No indexed content found for paper %s. Falling back to PDF URL.",
arxiv_id,
)
full_content = await load_pdf_content(pdf_url)
elif pdf_url:
full_content = await load_pdf_content(pdf_url)
if not full_content.strip():
raise ValueError("No paper content available for summary generation")
encoding = tiktoken.get_encoding("cl100k_base")
tokens = encoding.encode(full_content)
token_limit = 32768
cumulative_summary = ""
if len(tokens) > token_limit:
logger.warning(
f"Content for paper {arxiv_id} exceeds token limit ({len(tokens)}/{token_limit}). Generating summary in chunks."
)
for i in range(0, len(tokens), token_limit):
chunk_tokens = tokens[i : i + token_limit]
chunk_content = encoding.decode(chunk_tokens)
summary = await SummaryEngine.__generate_summary(
chunk_content, request, model_name
)
cumulative_summary += summary + "\n\n"
final_summary = await SummaryEngine.__generate_summary(
cumulative_summary, request, model_name
)
else:
final_summary = await SummaryEngine.__generate_summary(
full_content, request, model_name
)
return final_summary
except Exception as e:
logger.error(f"Error generating summary for paper {arxiv_id}: {str(e)}")
raise e
@classmethod
async def __generate_summary(
cls, content: str, request: Optional[Request] = None,
model_name: Optional[str] = None,
) -> str:
"""Generate a summary of the given content."""
try:
llm = LLMFactory.build_llm(
model_name=model_name or cls.DEFAULT_MODEL,
max_tokens=4096,
reasoning="hidden",
request=request,
)
messages = [
{
"role": "system",
"content": PAPER_SUMMARY_SYSTEM_PROMPT,
},
{"role": "user", "content": content},
]
response = await llm.ainvoke(messages)
if isinstance(response.content, str):
return response.content
else:
if isinstance(response.content[0], str):
return str(response.content[0])
else:
return str(response.content[0]["text"])
except Exception as e:
logger.error(f"Error in __generate_summary: {str(e)}")
raise e
@classmethod
async def _sort_docs(cls, docs):
"""Sort documents by page number."""
def extract_page_number(doc):
try:
return int(doc.metadata.get("page_number"))
except (ValueError, TypeError):
return 0
return sorted(docs, key=extract_page_number)