prompt-compiler-api / src /benchmarks /semantic_quality_audit.py
JairoDanielMT's picture
Upload folder using huggingface_hub
4ef6c2b verified
Raw
History Blame Contribute Delete
7.99 kB
import json
import os
from typing import List, Dict, Any, Tuple
from collections import Counter
from src.parser.parser import Parser
from src.ontology.matcher import ConceptMatcher
from src.embeddings.engine import EmbeddingEngine
from src.enrichment.enricher import Enricher
class SemanticQualityAudit:
def __init__(self):
# Ground truth for semantic quality
self.ground_truth = [
# Colors
{"query": "maroon", "expected": "red", "acceptable": ["maroon"], "category": "hair_color", "type": "color"},
{"query": "burgundy", "expected": "red", "acceptable": ["burgundy"], "category": "hair_color", "type": "color"},
{"query": "cerulean", "expected": "blue", "acceptable": ["ceruledge"], "category": "hair_color", "type": "color"},
{"query": "azure", "expected": "blue", "acceptable": ["azure_fang"], "category": "hair_color", "type": "color"},
# Scenes
{"query": "dance floor", "expected": "gala", "acceptable": ["ballroom", "concert stage"], "category": "scene", "type": "synonym"},
{"query": "royal palace", "expected": "castle", "acceptable": ["temple", "shrine"], "category": "scene", "type": "synonym"},
# Clothing
{"query": "royal robes", "expected": "evening gown", "acceptable": ["tuxedo", "suit", "cape", "cloak", "crown and royal robes"], "category": "clothing", "type": "related"},
{"query": "combat gear", "expected": "armor", "acceptable": ["military uniform", "plate armor"], "category": "clothing", "type": "related"},
# Identity
{"query": "speedy hedgehog", "expected": "Sonic", "category": "character", "type": "identity"},
{"query": "pink hedgehog", "expected": "Amy Rose", "category": "character", "type": "identity"},
{"query": "dark hedgehog", "expected": "Shadow", "category": "character", "type": "identity"},
# Typos
{"query": "crimsn", "expected": "crimson", "acceptable": ["red"], "category": "hair_color", "type": "typo"},
{"query": "skool uniform", "expected": "school uniform", "category": "clothing", "type": "typo"}
]
self.expanded_gt = []
for item in self.ground_truth:
self.expanded_gt.append(item)
if item["type"] == "color":
self.expanded_gt.append({**item, "query": f"deep {item['query']}"})
self.expanded_gt.append({**item, "query": f"bright {item['query']}"})
elif item["type"] == "related":
self.expanded_gt.append({**item, "query": f"a {item['query']}"})
self.expanded_gt.append({**item, "query": f"wearing {item['query']}"})
self.adversarial = [
{"query": "ballroom", "forbidden": "cocktail dress", "category": "scene"},
{"query": "Amy", "forbidden": "Rouge", "category": "character"},
{"query": "Shadow", "forbidden": "Sonic", "category": "character"}
]
def evaluate(self, parser: Parser):
results = {
"correct": 0,
"acceptable": 0,
"incorrect": 0,
"rejected": 0,
"total": 0
}
failures = []
confusion_matrix = Counter()
for item in self.expanded_gt:
query = item["query"]
search_results = parser.embedding_engine.search(query, category=item.get("category"), top_k=1)
results["total"] += 1
if not search_results:
results["rejected"] += 1
continue
record, conf = search_results[0]
if conf < 0.5:
results["rejected"] += 1
continue
actual = record.canonical
actual_cat = record.category
actual_lower = actual.lower().strip()
expected_lower = item["expected"].lower().strip()
is_correct = (actual_lower == expected_lower) or (expected_lower in actual_lower) or (actual_lower in expected_lower)
is_acceptable = False
if not is_correct:
if "acceptable" in item:
is_acceptable = any(acc.lower().strip() in actual_lower for acc in item["acceptable"]) or \
any(actual_lower in acc.lower().strip() for acc in item["acceptable"])
if not is_acceptable and query.lower().strip() in actual_lower:
is_acceptable = True
if item["category"] == "character" and actual_lower != expected_lower:
is_correct = False
is_acceptable = False
is_incorrect = True
else:
is_incorrect = not (is_correct or is_acceptable)
if is_correct:
results["correct"] += 1
elif is_acceptable:
results["acceptable"] += 1
else:
results["incorrect"] += 1
failures.append({
"query": query,
"actual": actual,
"actual_category": actual_cat,
"expected": item["expected"],
"expected_category": item["category"],
"confidence": conf
})
confusion_matrix[(item["category"], actual_cat)] += 1
for item in self.adversarial:
res = parser.embedding_engine.search(item["query"], top_k=5)
for rec, conf in res:
if rec.canonical.lower() == item["forbidden"].lower():
results["total"] += 1
results["incorrect"] += 1
failures.append({
"query": item["query"],
"actual": rec.canonical,
"reason": "Identity/Category Swap",
"severity": "Critical"
})
break
return results, failures, confusion_matrix
if __name__ == "__main__":
audit = SemanticQualityAudit()
matcher = ConceptMatcher("data/ontology")
from src.embeddings.engine import EmbeddingEngine
engine = EmbeddingEngine(index_dir="data/faiss_indices")
engine.load_index()
parser = Parser(matcher, engine)
print("Running semantic quality audit...")
stats, failures, confusion = audit.evaluate(parser)
total = stats["total"]
report = {
"metrics": {
"correct_retrieval_pct": round(stats["correct"] / total * 100, 2),
"acceptable_retrieval_pct": round(stats["acceptable"] / total * 100, 2),
"incorrect_retrieval_pct": round(stats["incorrect"] / total * 100, 2),
"trustworthy_pct": round((stats["correct"] + stats["acceptable"]) / total * 100, 2)
},
"counts": stats
}
os.makedirs("reports", exist_ok=True)
with open("reports/semantic_quality.json", "w", encoding="utf-8") as f:
json.dump(report, f, indent=2)
with open("reports/semantic_failures.json", "w", encoding="utf-8") as f:
json.dump(failures, f, indent=2)
cm_report = []
for (expected_cat, actual_cat), count in confusion.items():
cm_report.append({"expected_category": expected_cat, "actual_category": actual_cat, "count": count})
with open("reports/semantic_confusion_matrix.json", "w", encoding="utf-8") as f:
json.dump(cm_report, f, indent=2)
print(f"Audit complete.")
print(f"Correct + Acceptable: {report['metrics']['trustworthy_pct']}%")
print(f"Incorrect: {report['metrics']['incorrect_retrieval_pct']}%")