Spaces:
Sleeping
Sleeping
| import logging | |
| from typing import List | |
| from pydantic import BaseModel, Field | |
| from langchain_core.prompts import ChatPromptTemplate | |
| from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type | |
| from .llm_client import get_llm | |
| logger = logging.getLogger(__name__) | |
| class Entity(BaseModel): | |
| """ | |
| Represents a single extracted entity from the document text. | |
| """ | |
| name: str = Field( | |
| description="The canonical name of the entity, correctly capitalized (e.g., 'Google', 'John Smith'). Do not use pronouns or generic words." | |
| ) | |
| type: str = Field( | |
| description="The category of the entity. Must be exactly one of: PERSON, ORGANIZATION, PRODUCT, TECHNOLOGY, LOCATION, EVENT, DATE, CONCEPT, DOCUMENT" | |
| ) | |
| description: str = Field( | |
| description="A brief 1-2 sentence description explaining who or what this entity is, based strictly on the text chunk." | |
| ) | |
| class ExtractedEntities(BaseModel): | |
| """ | |
| Container list of all extracted entities. | |
| """ | |
| entities: List[Entity] | |
| class EntityExtractor: | |
| def __init__(self): | |
| logger.info("Initializing EntityExtractor service.") | |
| self.llm = get_llm(temperature=0.0) | |
| # Binds the LLM to output structured JSON matching our Pydantic schema | |
| self.structured_llm = self.llm.with_structured_output(ExtractedEntities) | |
| # Build prompt instructions | |
| self.prompt = ChatPromptTemplate.from_messages([ | |
| ("system", ( | |
| "You are an expert knowledge extraction agent. Your job is to read the provided text chunk " | |
| "and extract all key entities.\n\n" | |
| "Strict rules:\n" | |
| "1. Entity Type: Each entity must belong to one of these types: PERSON, ORGANIZATION, PRODUCT, " | |
| "TECHNOLOGY, LOCATION, EVENT, DATE, CONCEPT, DOCUMENT.\n" | |
| "2. Name Canonicalization: Extract names in their canonical, capitalized form. Avoid pronouns ('he', 'she', 'it') " | |
| "and generic descriptors ('the company', 'the engineer').\n" | |
| "3. Grounding: Descriptions must be factual and derived strictly from the text provided." | |
| )), | |
| ("human", "Extract all key entities from this text chunk:\n\n{text_content}") | |
| ]) | |
| # Chain prompt with structured model execution | |
| self.chain = self.prompt | self.structured_llm | |
| def extract_entities(self, text_content: str) -> List[dict]: | |
| """ | |
| Processes a string chunk of text and returns a list of serialized entity dicts. | |
| Retries on transient connection errors with exponential backoff. | |
| Raises on LLM failures to let graph_builder.py mark document as FAILED. | |
| """ | |
| if not text_content or not text_content.strip(): | |
| logger.warning("Empty text chunk provided to extract_entities.") | |
| return [] | |
| word_count = len(text_content.split()) | |
| logger.info("Running entity extraction on text chunk (Words: %d).", word_count) | |
| result: ExtractedEntities = self.chain.invoke({"text_content": text_content}) | |
| extracted = [entity.model_dump() for entity in result.entities] | |
| logger.info("Successfully extracted %d entities from text chunk.", len(extracted)) | |
| return extracted | |