""" Reference extraction from PDF and text documents using Google Gemini AI. """ from google import genai from google.genai import types import pathlib import json import logging from typing import Dict, List, Any logger = logging.getLogger(__name__) class ReferenceExtractor: """ Extracts academic references from PDF files and text using AI. """ # Shared extraction prompt template EXTRACTION_PROMPT_TEMPLATE = """ Identify the title, first author, year, and journal of each reference. The references may be in various formats, including APA, MLA, and Chicago. The references may also include DOIs, URLs, and other identifiers. IMPORTANT: For each reference, analyze its content to determine the optimal API order: - If the reference mentions "arXiv", "preprint", or contains "arxiv.org", put 'arxiv' first in the API order. - If it mentions a specific journal, contains a DOI, or is clearly a published journal article, put 'crossref' first. - For newer publications, open access articles, or general academic works, put 'openalex' first. Format your response as JSON with these fields: - title: The title of the academic work in the reference - first_author: The first author's name - year: Publication year as string - journal: Journal or publication venue name (optional) - reference: The full reference text without the number of the reference - check_api_order: Array of API names to check in order ['crossref', 'arxiv', 'openalex'] based on the reference type Output only json """ def __init__(self, client: genai.Client, model: str = "gemini-2.5-flash-lite"): """ Initialize the reference extractor. Args: client: Google GenAI client instance model: Model name to use for extraction """ self.client = client self.model = model def extract_from_pdf(self, file_path: str) -> List[Dict[str, Any]]: """ Extracts references from a PDF file using GenAI. Args: file_path: The path to the PDF file to extract references from. Returns: A list of extracted references. Raises: FileNotFoundError: If PDF file doesn't exist ValueError: If extraction fails """ file_path_obj = pathlib.Path(file_path) if not file_path_obj.exists(): logger.error(f"PDF file not found: {file_path}") raise FileNotFoundError(f"PDF file not found: {file_path}") prompt = f"From the PDF file, extract the references.\n{self.EXTRACTION_PROMPT_TEMPLATE}" try: logger.info(f"Extracting references from PDF: {file_path_obj.name}") response = self.client.models.generate_content( model=self.model, contents=[ types.Part.from_bytes( data=file_path_obj.read_bytes(), mime_type='application/pdf', ), prompt ], config=types.GenerateContentConfig( response_mime_type="application/json", temperature=0 ) ) references = json.loads(response.candidates[0].content.parts[0].text) logger.info(f"Extracted {len(references) if isinstance(references, list) else 1} references") return references except json.JSONDecodeError as e: logger.error(f"Failed to parse GenAI response as JSON: {e}") raise ValueError(f"Invalid JSON response from GenAI: {e}") except Exception as e: logger.exception(f"Failed to extract references from PDF: {e}") raise def extract_from_text(self, text: str) -> List[Dict[str, Any]]: """ Extracts references from a text string using GenAI. Args: text: The text to extract references from. Returns: A list of extracted references. Raises: ValueError: If text is empty or extraction fails """ if not text or not text.strip(): logger.error("Empty text provided for reference extraction") raise ValueError("Text cannot be empty") prompt = f""" From the text, extract the references. Text: {text} {self.EXTRACTION_PROMPT_TEMPLATE} """ try: logger.info(f"Extracting references from text ({len(text)} characters)") response = self.client.models.generate_content( model=self.model, contents=[prompt], config=types.GenerateContentConfig( response_mime_type="application/json", temperature=0 ) ) references = json.loads(response.candidates[0].content.parts[0].text) logger.info(f"Extracted {len(references) if isinstance(references, list) else 1} references") return references except json.JSONDecodeError as e: logger.error(f"Failed to parse GenAI response as JSON: {e}") raise ValueError(f"Invalid JSON response from GenAI: {e}") except Exception as e: logger.exception(f"Failed to extract references from text: {e}") raise