from __future__ import annotations from typing import List, Optional, Sequence from kbdebugger.types import GraphRelation, ExtractionResult from .retriever import KnowledgeGraphRetriever from .utils import map_extracted_triplets_to_graph_relations from .types import BatchUpsertSummary from . import get_graph from .cytoscape import graph_relations_to_cytoscape, CytoscapeGraphPayload def retrieve_keyword_subgraph( *, keyword: str, limit_per_pattern: int, ) -> List[GraphRelation]: """ Retrieve a keyword-guided KG subgraph from Neo4j and return its relations. This function is a **public stage API**: it performs the retrieval and enforces a clear contract for downstream stages. Parameters ---------- keyword: Keyword used to drive the subgraph retrieval patterns. limit_per_pattern: Maximum number of relations returned per retrieval pattern in the KnowledgeGraphRetriever. Returns ------- List[GraphRelation] The retrieved relations (GraphRelation dicts), ready to be used as the reference set for vector similarity filtering. Raises ------ ValueError If no relations were retrieved. This typically indicates that: - the keyword does not exist in the KG (or is too specific), - the KG is empty, - the retriever patterns are too restrictive, - or Neo4j connectivity/configuration is wrong. """ retriever = KnowledgeGraphRetriever(limit_per_pattern=limit_per_pattern) hits = retriever.retrieve(keyword) relations = [h["relation"] for h in hits] return relations def retrieve_keyword_subgraph_cytoscape( *, keyword: str, limit_per_pattern: int, ) -> CytoscapeGraphPayload: """ Retrieve a keyword-guided KG subgraph and return Cytoscape-ready elements. Why this exists --------------- - `retrieve_keyword_subgraph()` is a pipeline stage API and returns `List[GraphRelation]` for downstream algorithmic stages (similarity, novelty, etc.). - The UI needs Cytoscape.js elements: {"elements": {"nodes": [...], "edges": [...]}} This function is a pure adapter: - no new graph logic - no DB access beyond the underlying retrieval call - strictly typed UI contract Parameters ---------- keyword: Keyword used to drive the subgraph retrieval patterns. limit_per_pattern: Maximum number of relations returned per retrieval pattern. Returns ------- CytoscapeGraphPayload Cytoscape.js compatible graph payload. """ relations = retrieve_keyword_subgraph( keyword=keyword, limit_per_pattern=limit_per_pattern, ) return graph_relations_to_cytoscape(relations) def upsert_extracted_triplets( *, extractions: Sequence[ExtractionResult], source: Optional[str] = None, pretty_print: bool = True, ) -> BatchUpsertSummary: """ Convert triplet extraction outputs into graph relations, then upsert them. Why this exists --------------- The triplet extractor returns "ExtractionResult" objects: { "sentence": str, "triplets": [(subject, object, predicate), ...] } Neo4j upsert expects GraphRelation objects: { "source": {"label": ...}, "target": {"label": ...}, "edge": {"label": ..., "properties": {...}} } This function is the stage boundary that: - maps ExtractionResult -> list[GraphRelation] - batches all relations together - performs a single high-level upsert call Parameters ---------- graph: Connected GraphStore instance. extractions: Sequence of ExtractionResult items. Each item corresponds to one input sentence, containing zero or more extracted triplets. source: Optional provenance string (e.g., PDF filename). If provided, it is stored on relationship properties under key "source". pretty_print: If True, print an upsert summary. Returns ------- BatchUpsertSummary Summary across all relations produced by all extractions. """ graph = get_graph() all_relations: List[GraphRelation] = [] for extraction in extractions: # extractions is like a list of list of triplets. # Thus, we have to iterate through each extraction (corresponding to one quality sentence) # and map its triplets to graph relations. And we accumulate all triplets in one flat list to do a single upsert at the end. rels = map_extracted_triplets_to_graph_relations(extraction, source=source) all_relations.extend(rels) # Here we do a single batch upsert for all relations extracted from all sentences. # This is more efficient than upserting per `extraction`, and allows us to get a comprehensive summary of the batch operation. return graph.upsert_relations(all_relations, pretty_print=pretty_print)