faris-abuali's picture
Upload 227 files
399944f verified
Raw
History Blame Contribute Delete
4.49 kB
from kbdebugger.types import EdgeProperties, ExtractionResult, GraphRelation
from kbdebugger.compat.langchain import Document
from typing import List, Iterable, Mapping, Any, Optional
from datetime import datetime
def normalize_text(text: str) -> str:
"""
Normalize a free-text label into a safe identifier:
- lowercase
- strip punctuation at edges
"""
clean = " ".join(text.strip().split()).lower()
# clean = clean.replace(" ", "_")
return clean
def map_doc_extracted_triplets_to_graph_relations(
extraction: ExtractionResult,
source_doc: Document,
# *,
# include_sentence: bool = True,
) -> List[GraphRelation]:
"""
Map an ExtractionResult to graph-ready relation dicts.
- extraction: {"sentence": str, "triplets": [(subj,obj,rel), ...]}
- source_doc: LangChain Document (for provenance: page_content + metadata)
"""
# defensive: accept partially-typed dicts
sentence_text = extraction.get("sentence")
triplets = extraction.get("triplets", [])
rels: List[GraphRelation] = []
for subj, obj, rel in triplets:
props: EdgeProperties = {
# for provenance
'sentence': sentence_text,
'original_sentence': getattr(source_doc, "page_content", ""),
**getattr(source_doc, "metadata", {}) # type: ignore[arg-type]
}
# if include_sentence:
# # human-readable extracted sentence (from the extractor)
# props["sentence"] = sentence_text
rels.append({
"source": { "label": normalize_text(subj) },
"target": { "label": normalize_text(obj) },
"edge": { "label": normalize_text(rel), "properties": props },
}) # type: ignore
return rels
def map_extracted_triplets_to_graph_relations(
extraction: ExtractionResult,
source: Optional[str] = None,
) -> List[GraphRelation]:
"""
Map an ExtractionResult to graph-ready relation dicts.
- extraction: {"sentence": str, "triplets": [(subj,obj,rel), ...]}
- source_doc: LangChain Document (for provenance: page_content + metadata)
"""
# defensive: accept partially-typed dicts
sentence_text = extraction.get("sentence")
triplets = extraction.get("triplets", [])
rels: List[GraphRelation] = []
for subj, obj, rel in triplets:
props: EdgeProperties = {
# for provenance
'sentence': sentence_text,
**({'source': source} if source else {}) # only include if source is provided
# 'original_sentence': getattr(source_doc, "page_content", ""),
# **getattr(source_doc, "metadata", {}) # type: ignore[arg-type]
}
rels.append({
"source": { "label": normalize_text(subj) },
"target": { "label": normalize_text(obj) },
"edge": { "label": normalize_text(rel), "properties": props },
}) # type: ignore
return rels
def rows_to_graph_relations(
rows: Iterable[Mapping[str, Any]],
*,
source_key: str = "source",
target_key: str = "target",
predicate_key: str = "predicate",
props_key: str = "props",
# if we want to enforce required props fields, will do it here
) -> List[GraphRelation]:
rels: List[GraphRelation] = []
for row in rows:
source = row[source_key]
target = row[target_key]
predicate = row[predicate_key]
props_raw = row.get(props_key, {}) or {}
source_id = str(row.get("source_id", ""))
target_id = str(row.get("target_id", ""))
if not isinstance(props_raw, dict):
raise TypeError(f"Expected '{props_key}' to be a dict, got {type(props_raw)}: {props_raw!r}")
props: EdgeProperties = {**props_raw} # type: ignore[misc]
# Optional: keep predicate redundantly in properties for provenance/compat
# (only if you want this invariant)
props.setdefault("label", predicate)
now = datetime.now().isoformat()
rels.append(
{
"source": {
"label": str(source),
"id": source_id,
},
"target": {
"label": str(target),
"id": target_id,
},
"edge": {
"label": str(predicate),
"properties": props
}
} # type: ignore
)
return rels