Files
fn_registry/docs/capabilities/nlp.md
T
egutierrez a03675113a chore: auto-commit (286 archivos)
- .claude/agents/fn-orquestador/SKILL.md
- .claude/commands/fn_claude.md
- .claude/rules/INDEX.md
- .claude/rules/cpp_apps.md
- .claude/rules/ids_naming.md
- CHANGELOG.md
- apps/dag_engine/README.md
- apps/dag_engine/api.go
- apps/dag_engine/dags_migrated/example.yaml
- apps/dag_engine/dags_migrated/example_lineage_tracking.yaml
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:33:22 +02:00

15 KiB

Capability: nlp

Pipeline NLP para extraccion de entities/relations sobre documentos en castellano (proyecto OSINT principalmente). Cubre: lectura de PDFs (extract_pdf_text, clean_pdf_text), OCR fallback, chunking (chunk_with_overlap), inferencia GLiNER (NER) + GLiREL (relation extraction), dedup (dedup_entities, dedup_relations), agregacion (aggregate_extraction_results), extraccion de elementos especificos (URLs, crypto wallets, IPs, dominios).

Funciones

ID Firma Que hace
aggregate_extraction_results_py_core def aggregate_extraction_results(extract_results: list[dict]) -> dict Agrega entidades y relaciones de N resultados de extraccion por chunk. Deduplica entidades por (type, name_lowercased) acumulando counts. Deduplica relaciones por (head, rel_type, tail) con Counter.
align_relations_to_entities_py_datascience def align_relations_to_entities(triplets: list[dict], entity_names: list[str]) -> list[dict] Filtra y alinea triplets REBEL/mREBEL a nombres canonicos de entidades. Para cada triplet, resuelve head y tail contra entity_names con match exacto case-insensitive o substring (gana el nombre mas largo). Descarta triplets donde algun lado no resuelve o head==tail.
chunk_with_overlap_py_core def chunk_with_overlap(text: str, max_chars: int = 1500, overlap_sentences: int = 2) -> list[dict] Divide texto en chunks por sentence boundaries con sliding window overlap. Garantiza avance forzado si una frase supera max_chars (evita bucle infinito). Cada chunk retorna dict con 'text' y 'sentences'.
clean_pdf_text_py_core def clean_pdf_text(text: str) -> str Limpieza de artefactos PyPDF2/pdfplumber: elimina marcas de pagina (1/20), tabs, guiones de dehyphenation, saltos de linea en medio de oraciones y espacios duplicados.
deduplicate_entities_py_datascience def deduplicate_entities(candidates: list[EntityCandidate], name_threshold: float = 0.85, same_type_only: bool = True) -> DeduplicationResult Agrupa entidades candidatas que refieren a la misma entidad real usando fuzzy matching de nombres (Levenshtein + Jaccard) y Union-Find para clusters transitivos. Retorna entidades mergeadas con mapas de resolucion de IDs y log de merges.
deduplicate_relations_py_datascience def deduplicate_relations(relations: list[RelationCandidate], entity_id_map: dict[str, str]) -> list[RelationCandidate] Deduplica relaciones candidatas resolviendo from_name/to_name a entity IDs finales via entity_id_map. Descarta self-loops y relaciones sin match. Mergea duplicados (mismo from_id, to_id, relation_type) concatenando descripciones unicas y tomando max confidence.
estimate_token_count_py_core def estimate_token_count(content: str) -> int Estimacion rapida de tokens sin tokenizer. CJK chars cuentan ~0.7 token/char, otros non-whitespace ~0.3 token/char.
extract_entities_gliner_py_datascience def extract_entities_gliner(text: str, entity_schema: list[dict], model: Any, threshold: float = 0.5, flat_ner: bool = True) -> list[EntityCandidate] Extrae entidades zero-shot con GLiNER. Drop-in del contrato de extract_entities_llm pero 50-200x mas rapido y sin coste por token. El caller inyecta el modelo cargado con gliner_load_model. Anota offsets start/end en attributes para reconciliar con extract_iocs.
extract_entities_llm_py_datascience def extract_entities_llm(text: str, entity_schema: list[dict], llm_chat_json: Callable[[list[dict]], dict], language_instruction: str = 'Respond in English.') -> list[EntityCandidate] Extrae entidades de un chunk de texto usando un LLM inyectado. Construye el system prompt con el schema, llama al LLM y valida la respuesta retornando EntityCandidate. JSON invalido o type_ref fuera del schema se descartan con warning.
extract_graph_from_text_py_pipelines def extract_graph_from_text(text: str, entity_labels: list[str], relation_labels: list | dict, allowed: dict, model: Any, threshold: float = 0.3, max_chars_per_chunk: int = 1500, overlap_sentences: int = 2) -> dict Pipeline E2E: texto -> grafo de entidades y relaciones. Orquesta chunking, extraccion con GLiNER2 por chunk, agregacion, filtrado tipado y resolucion de alias. Refactorizacion del playground del analisis gliner_glirel_tuning.
extract_graph_gliner2_py_datascience def extract_graph_gliner2(text: str, entity_labels: list[str], relation_labels: list | dict, model: Any, threshold: float = 0.3, include_confidence: bool = False) -> dict Extrae entidades + relaciones en una sola pasada con GLiNER2. Wrapper de alto nivel: construye schema, ejecuta extraccion, normaliza a dict plano. No aplica post-filtrado ni coreference.
extract_graph_hybrid_py_pipelines def extract_graph_hybrid(chunks: list[str], entity_schema: list[dict], relation_types: list[str], gliner_model: Any, glirel_model: Any, llm_chat_json: Callable[[list[dict]], dict] | None = None, ioc_types: list[str] | None = None, confidence_threshold: float = 0.6, languages: str = 'Respond in Spanish.', min_entities_per_chunk: int = 2) -> tuple[list[EntityCandidate], list[RelationCandidate]] Pipeline hibrido en cascada que combina extract_iocs (regex, coste 0), GLiNER (zero-shot NER, coste bajo), GLiREL (zero-shot RE) y un LLM fallback opcional para chunks complejos o de baja confianza. Devuelve listas concatenadas listas para deduplicate_entities/deduplicate_relations.
extract_relations_glirel_py_datascience def extract_relations_glirel(text: str, entities: list[EntityCandidate], relation_types: list[str], model: Any, threshold: float = 0.5, max_pairs: int = 0) -> list[RelationCandidate] Extrae relaciones zero-shot con GLiREL. Drop-in del contrato de extract_relations_llm pero sin coste por token y mas rapido para corpus grandes. Tokeniza por whitespace, mapea spans de entidades (de attributes['start'/'end'] o fallback text.find) a indices de tokens, y devuelve RelationCandidate cuyos from_name/to_name siempre coinciden con entidades del input.
extract_relations_llm_py_datascience def extract_relations_llm(text: str, entities: list, relation_types: list[str], llm_chat_json: Callable[[list[dict]], dict], language_instruction: str = 'Respond in English.') -> list Extrae relaciones entre entidades de un chunk de texto usando un LLM inyectado. Valida que from_name y to_name correspondan a entidades existentes, y usa 'related_to' como fallback para tipos de relacion no permitidos.
extract_relations_mrebel_py_datascience def extract_relations_mrebel(text: str, entities: list[EntityCandidate], tokenizer: Any, model: Any, src_lang: str = 'es_XX', sentence_split_re: str = r'(?<=[.!?])\s+', min_sentence_chars: int = 20, num_beams: int = 4, max_length: int = 256) -> list[RelationCandidate] Extrae relaciones entre entidades usando mREBEL (seq2seq multilingue). Divide el texto por oraciones, genera triplets con mREBEL, parsea con parse_rebel_output y alinea a entidades conocidas con align_relations_to_entities. Drop-in con extract_relations_glirel para benchmarks.
extract_triples_spacy_es_py_datascience def extract_triples_spacy_es(text: str, nlp: Any) -> dict Extraccion OpenIE schema-less en castellano via reglas de dependencia spaCy. Detecta patrones sujeto-verbo-objeto con el lemma del verbo como relacion (sin vocabulario fijo). Tambien extrae entidades NER (PER, ORG, LOC, MISC).
extraction_pipeline_py_pipelines def extraction_pipeline(file_path: str, entity_presets: list[dict], relation_types: list[str], llm_chat_json: Callable[[list[dict]], dict], chunk_size: int = 500, chunk_overlap: int = 50, confidence_threshold: float = 0.5, dedup_threshold: float = 0.85, on_progress: Callable[[str, float], None] | None = None) -> ExtractionResult Pipeline completa de extraccion de entidades y relaciones desde un documento. Orquesta extract_text_from_file -> preprocess_text -> split_text_into_chunks -> extract_entities_llm por chunk -> deduplicate_entities -> extract_relations_llm por chunk -> deduplicate_relations.
filter_relations_by_entity_types_py_core def filter_relations_by_entity_types(relations: dict, name_to_type: dict, allowed: dict) -> tuple[list, list] Post-filtrado tipado de relaciones NER+RE: descarta pares donde los tipos de entidad (head_type, tail_type) no coinciden con los permitidos por relation kind. Ej: descarta 'Madrid president_of Persona' porque Madrid es location no person.
gliner2_load_model_py_datascience def gliner2_load_model(model_name: str = 'fastino/gliner2-large-v1', device: str = 'auto') -> Any Carga (y cachea por (model_name, device)) un modelo GLiNER2 (NER+RE joint). GLiNER2 extrae entidades y relaciones en una sola pasada con schema unificado. ~2x mas rapido que GLiNER + GLiREL separados. LICENSE: Apache 2.0.
gliner_load_model_py_datascience def gliner_load_model(model_name: str = 'urchade/gliner_multi-v2.1', device: str = 'auto') -> Any Carga (y cachea por (model_name, device)) un modelo GLiNER zero-shot NER. La primera llamada descarga ~200 MB desde HuggingFace; sucesivas devuelven la instancia cacheada. device='auto' usa CUDA si esta disponible, o CPU.
glirel_load_model_py_datascience def glirel_load_model(model_name: str = 'jackboyla/glirel-large-v0', device: str = 'auto') -> Any Carga (y cachea por (model_name, device)) un modelo GLiREL zero-shot relation extraction. La primera llamada descarga ~500 MB desde HuggingFace; sucesivas devuelven la instancia cacheada. device='auto' usa CUDA si esta disponible, o CPU.
marianmt_es_en_load_model_py_datascience def marianmt_es_en_load_model(model_name: str = 'Helsinki-NLP/opus-mt-es-en') -> tuple[Any, Any] Carga (y cachea) el tokenizer y modelo MarianMT para traduccion ES->EN (Helsinki-NLP, ~300 MB). Licencia Apache 2.0. Cache por model_name.
merge_entity_aliases_py_core def merge_entity_aliases(entity_names: list[str]) -> dict[str, str] Coreference simple por normalizacion + substring: mapea cada nombre de entidad a su forma canonica. 'BBVA' y 'bbva' -> mismo canonical. Nombres cortos absorbidos por nombres largos que los contienen como palabra completa (min 4 chars normalizados).
mrebel_base_load_model_py_datascience def mrebel_base_load_model(model_name: str = 'Babelscape/mrebel-base', src_lang: str = 'es_XX', tgt_lang: str = 'tp_XX') -> tuple[Any, Any] Variante rapida de mrebel_load_model con checkpoint base (250M params, ~900 MB). Delega completamente en mrebel_load_model. Misma licencia CC BY-NC-SA 4.0 — solo uso no comercial.
mrebel_load_model_py_datascience def mrebel_load_model(model_name: str = 'Babelscape/mrebel-large', src_lang: str = 'es_XX', tgt_lang: str = 'tp_XX') -> tuple[Any, Any] Carga (y cachea) el tokenizer y modelo mREBEL (mBART-based, ~600M params, ~2.4 GB). Multilingue 30+ idiomas. Cache por (model_name, src_lang). Primera llamada descarga de HuggingFace. LICENCIA CC BY-NC-SA 4.0 — solo uso no comercial.
normalize_entity_name_py_core def normalize_entity_name(name: str, entity_type: str = "") -> str Normaliza el nombre de una entidad para comparacion y deduplicacion. Aplica reglas distintas segun el tipo: ip/email/domain/crypto_wallet/phone usan normalizacion tecnica; person remueve titulos y convierte formato Apellido-Nombre; organization elimina sufijos legales; default aplica lower+strip+colapso de espacios.
parse_rebel_output_py_datascience def parse_rebel_output(decoded_text: str) -> list[dict] Parser puro del wire format de REBEL / mREBEL. Convierte la cadena decoded por el tokenizer (con skip_special_tokens=False) a una lista de triplets tipados {head, head_type, type, tail, tail_type}. Nunca lanza excepcion.
rebel_load_model_py_datascience def rebel_load_model(model_name: str = 'Babelscape/rebel-large') -> tuple[Any, Any] Carga (y cachea) el tokenizer y modelo REBEL (BART-based, ~1.5 GB). Solo ingles. Licencia Apache 2.0 — uso comercial permitido. Cache por model_name.
remove_words_from_column_py_datascience def remove_words_from_column(values: Iterable[str | None], words: list[str]) -> list[str] Elimina palabras especificas de un iterable de strings usando regex de palabra completa (\b). Case-insensitive. Colapsa espacios multiples y hace strip. None se convierte en cadena vacia. Sin pandas.
spacy_es_load_model_py_datascience def spacy_es_load_model(model_name: str = 'es_core_news_md') -> Any Carga (y cachea) un modelo spaCy en castellano. Provee POS, dependencias y NER (PER, ORG, LOC, MISC). Usado por extract_triples_spacy_es para OpenIE schema-less. LICENSE: spaCy MIT + es_core_news_md CC BY-SA 4.0.
split_text_into_chunks_py_core def split_text_into_chunks(text: str, chunk_size: int = 500, overlap: int = 50) -> list[str] Divide texto en chunks de tamaño fijo con overlap, intentando cortar en límites de oración para no romper frases a mitad. Soporta separadores CJK (。!?) y occidentales (. ! ?).
translate_es_to_en_py_datascience def translate_es_to_en(text: str, tokenizer: Any, model: Any, max_length: int = 512, num_beams: int = 4) -> str Traduce texto espanol a ingles frase a frase usando MarianMT. Divide por boundaries de oracion, traduce cada una independientemente y une con espacio. Preserva nombres propios mejor que pasar el parrafo entero.
words_to_dataset_py_datascience def words_to_dataset(texts: Iterable[str | None], min_ocurrencias: int = 1, eliminar_stopwords: bool = False) -> list[dict] Extrae palabras y sus ocurrencias de un iterable de textos. Tokeniza con \b\w+\b, convierte a mayusculas, cuenta con Counter, filtra por minimo de ocurrencias y opcionalmente elimina stopwords en espanol. Sin pandas.

Ejemplo canonico

Pipeline completo PDF -> entities + relations

import os, sys
sys.path.insert(0, os.path.join(os.environ["FN_REGISTRY_ROOT"], "python", "functions"))

from core import extract_pdf_text, clean_pdf_text, chunk_with_overlap
from datascience import (
    gliner_extract_entities, glirel_extract_relations,
    dedup_entities, dedup_relations, aggregate_extraction_results,
)

raw = extract_pdf_text("/path/to/doc.pdf")
text = clean_pdf_text(raw)
chunks = chunk_with_overlap(text, size=512, overlap=64)

entities = []
relations = []
for ch in chunks:
    entities.extend(gliner_extract_entities(ch, labels=["PERSON","ORG","ACCOUNT"]))
    relations.extend(glirel_extract_relations(ch, entity_pairs=entities))

result = aggregate_extraction_results(
    entities=dedup_entities(entities),
    relations=dedup_relations(relations),
)

Fronteras

  • NO entrena modelos. Solo inferencia con GLiNER/GLiREL pre-entrenados (HuggingFace).
  • NO maneja embeddings densos (sentence-transformers / e5). Para vectores, usa funciones del grupo ml.
  • NO hace traduccion ni summarization LLM. Solo NER + RE. Para LLM, ver tag llm.
  • NO escribe a BD automaticamente. La persistencia (vault, sqlite, parquet) la maneja el caller via funciones de infra/datascience.