Files
fn_registry/dev/issues/completed/0040-hybrid-extraction-pipeline.md
T
egutierrez e6451b4912 docs(issues): cerrar 0040 — hybrid extraction pipeline
Mueve el issue a completed/ y actualiza el indice.
2026-04-30 16:52:56 +02:00

4.0 KiB

0040 — Pipeline hibrido extraccion entidades+relaciones (regex + GLiNER/GLiREL + LLM fallback)

Metadata

Campo Valor
ID 0040
Estado pendiente
Prioridad media
Tipo feature — Python pipeline (python/functions/pipelines/)

Dependencias

Bloquea-por: 0037 (IoC regex), 0038 (GLiNER), 0039 (GLiREL).

Desbloquea: flujo OSINT/grafo de produccion con coste predecible. Reusable desde apps en apps/*/ y desde notebooks en analysis/*/.


Objetivo

Pipeline que combina los tres extractores en cascada para obtener triplets (entidad, relacion, entidad) masivamente con buen coste/calidad:

  1. Capa regex (0037) — extrae IoCs tecnicos con precision 100%. Coste 0.
  2. Capa GLiNER (0038) — extrae entidades semanticas (person, organization, location, event...) zero-shot. Coste bajo.
  3. Capa GLiREL (0039) — relaciones zero-shot entre las entidades de capa 1+2.
  4. Capa LLM fallback (existente: extract_entities_llm + extract_relations_llm) — solo se invoca cuando confidence < threshold o sobre los chunks marcados como "complejos".

Output: (list[EntityCandidate], list[RelationCandidate]) listos para deduplicate_entitiesdeduplicate_relationsops_to_sigma_json / ops_to_rdf_triples.

Funcion a crear

Function ID Rol
extract_graph_hybrid_py_pipelines Pipeline orquestador. kind: pipeline, purity: impure, uses_functions: [...] con todos los anteriores

Contrato

def extract_graph_hybrid(
    chunks: list[str],
    entity_schema: list[dict],
    relation_types: list[str],
    gliner_model,                           # inyectado
    glirel_model,                           # inyectado
    llm_chat_json: Callable | None = None,  # opcional; si None, sin fallback LLM
    ioc_types: list[str] | None = None,     # None = todos
    confidence_threshold: float = 0.6,      # bajo este umbral se llama LLM
    languages: str = "Respond in Spanish.",
) -> tuple[list[EntityCandidate], list[RelationCandidate]]

Logica interna por chunk:

  1. extract_iocs(chunk, ioc_types)EntityCandidate con type_ref tecnico (ip/email/hash/...).
  2. extract_entities_gliner(chunk, entity_schema, gliner_model) → entidades semanticas.
  3. Si hay chunks con < N entidades o confidence baja y hay llm_chat_jsonextract_entities_llm sobre esos chunks; mergear.
  4. extract_relations_glirel(chunk, entities_del_chunk, relation_types, glirel_model).
  5. Si baja cobertura de relaciones y hay LLM → extract_relations_llm sobre el chunk.
  6. Devolver listas concatenadas (sin deduplicar — eso lo hace el caller con deduplicate_*).

Pureza

kind: pipelinepurity: impure (regla del registry). uses_functions: lista los 5+ extractores invocados. error_type: error_go_core.

Deliverables

  • python/functions/pipelines/extract_graph_hybrid.py + .md
  • Test de integracion en python/functions/pipelines/tests/test_extract_graph_hybrid.py con un corpus pequeño (2-3 textos OSINT realistas, mock del LLM).
  • .md documenta: cuando usar fallback LLM, latencia esperada por chunk, recomendacion de batch size.

Validacion

./fn run extract_graph_hybrid_py_pipelines

Bench end-to-end sobre 100 KB de texto:

  • Solo LLM (linea base actual): registrar tiempo y coste estimado.
  • Pipeline hibrido: registrar tiempo, coste (solo chunks con fallback) y delta de calidad vs solo-LLM.

Registrar resultados en el .md para tener referencia historica.

Notas

  • La deduplicacion fuzzy (Levenshtein + Union-Find) ya esta hecha en deduplicate_entities — NO replicar aqui.
  • IoCs y entidades semanticas pueden solapar (ej: GLiNER detecta apple.com como organization, regex como domain). Resolver dejando ambas con type_ref distinto y que deduplicate_entities con same_type_only=True no las mezcle. Documentar esta decision.
  • Pensar en un app apps/osint_extractor/ que use este pipeline + sigma viz como demo. Fuera de scope de este issue — proponer en proposals despues.