citation-validator / components /reference_extractor.py
iluksic's picture
Added initial app version
f473a91
Raw
History Blame Contribute Delete
5.32 kB
"""
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