GraphResearcher / app /generation /evidence_extractor.py
yugbirla's picture
Sync GraphRAG fusion quality cleanup and evaluation files
b7d0804
Raw
History Blame Contribute Delete
5.06 kB
import re
from typing import List, Dict, Any
from app.generation.context_cleaner import clean_sentence_text
STOPWORDS = {
"what", "is", "are", "the", "a", "an", "of", "to", "and", "or", "in",
"for", "with", "on", "by", "from", "this", "that", "it", "does",
"do", "why", "how", "explain", "define", "meaning"
}
def split_into_sentences(text: str) -> List[str]:
if not text:
return []
sentence_candidates = re.split(r"(?<=[.!?])\s+", text)
sentences = []
for sentence in sentence_candidates:
sentence = clean_sentence_text(sentence)
if len(sentence) < 25:
continue
if is_noise_sentence(sentence):
continue
sentences.append(sentence)
return sentences
def is_noise_sentence(sentence: str) -> bool:
sentence_lower = sentence.lower().strip()
noise_starts = [
"chapter ",
"page ",
"this chapter prepares",
"practice saying",
"component what it does",
]
for start in noise_starts:
if sentence_lower.startswith(start):
return True
return False
def extract_query_terms(query: str) -> List[str]:
words = re.findall(r"[a-zA-Z0-9_]+", query.lower())
terms = [
word for word in words
if word not in STOPWORDS and len(word) > 1
]
return terms
def score_sentence(sentence: str, query_terms: List[str]) -> float:
sentence_lower = sentence.lower()
score = 0.0
for term in query_terms:
if term in sentence_lower:
score += 2.0
important_markers = [
"stands for",
"means",
"refers to",
"retrieval-augmented generation",
"retrieval augmented generation",
"adds a retrieval step",
"adding a retrieval step",
"before generation",
"before generating",
"search your document corpus",
"search a document corpus",
"provide the relevant passages",
"relevant passages as context",
"frozen knowledge",
"reduces hallucination",
"grounds the answer",
"private or recent data"
]
for marker in important_markers:
if marker in sentence_lower:
score += 1.5
if 60 <= len(sentence) <= 350:
score += 0.5
return score
def normalize_for_dedup(text: str) -> str:
text = text.lower()
text = re.sub(r"[^a-z0-9\s]", " ", text)
text = re.sub(r"\s+", " ", text).strip()
return text
def token_set(text: str) -> set:
return set(normalize_for_dedup(text).split())
def is_similar_to_existing(sentence: str, existing_sentences: List[str]) -> bool:
current_tokens = token_set(sentence)
if not current_tokens:
return True
for existing in existing_sentences:
existing_tokens = token_set(existing)
if not existing_tokens:
continue
overlap = len(current_tokens.intersection(existing_tokens))
union = len(current_tokens.union(existing_tokens))
if union == 0:
continue
similarity = overlap / union
if similarity >= 0.72:
return True
return False
def extract_evidence_sentences(
query: str,
results: List[Dict[str, Any]],
max_evidence: int = 8
) -> List[Dict[str, Any]]:
query_terms = extract_query_terms(query)
evidence_items = []
for result in results:
content = result.get("content", "")
sentences = split_into_sentences(content)
for sentence in sentences:
score = score_sentence(sentence, query_terms)
if score <= 0:
continue
evidence_items.append(
{
"sentence": sentence,
"score": score,
"source_id": result.get("source_id"),
"citation": result.get("citation"),
"chunk_id": result.get("chunk_id"),
"document_id": result.get("document_id"),
"source_file_name": result.get("source_file_name"),
"page_number": result.get("page_number")
}
)
evidence_items.sort(
key=lambda item: item["score"],
reverse=True
)
deduplicated = []
existing_sentences = []
for item in evidence_items:
sentence = item["sentence"]
if is_similar_to_existing(sentence, existing_sentences):
continue
deduplicated.append(item)
existing_sentences.append(sentence)
if len(deduplicated) >= max_evidence:
break
return deduplicated
def build_evidence_context(evidence_items: List[Dict[str, Any]]) -> str:
context_lines = []
for item in evidence_items:
source_id = item.get("source_id", "S?")
citation = item.get("citation", "")
sentence = item.get("sentence", "")
context_lines.append(
f"{source_id}: {sentence} {citation}"
)
return "\n".join(context_lines)