prompt-compiler-api / src /benchmarks /external_benchmark.py
JairoDanielMT's picture
Upload folder using huggingface_hub
4ef6c2b verified
Raw
History Blame Contribute Delete
7.15 kB
import json
import os
from typing import List, Dict, Any
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 ExternalBenchmarkSuite:
def __init__(self):
# We manually define challenging benchmarks that do NOT come directly from sampling the ontology JSON.
self.benchmarks = [
# 1. Compound Entity Resolution
{
"input": "Classic Sonic running",
"expected": ["Sonic", "classic"]
},
{
"input": "Dark Shadow with red eyes",
"expected": ["Shadow", "dark", "red eyes"]
},
# 2. Adjective and Attribute Recovery
{
"input": "Amy looking elegant in a luxury event",
"expected": ["Amy Rose", "elegant"]
},
{
"input": "Rouge wearing futuristic combat gear",
"expected": ["Rouge", "futuristic", "combat gear"]
},
# 3. Misspellings and Semantic Fallback
{
"input": "amy in a crimsn dress",
"expected": ["Amy Rose", "red dress", "crimson"]
},
{
"input": "blaze the cat with fyre powers",
"expected": ["Blaze"]
},
# 4. Mixed Language / Complex syntax
{
"input": "rosa amy vestido rojo at night",
"expected": ["Amy Rose", "red dress", "night"]
},
{
"input": "a cute school uniform worn by a girl",
"expected": ["school uniform", "cute", "girl"]
}
] * 10 # Multiply to get a larger suite for processing
def evaluate(self, parser: Parser, enricher: Enricher) -> Dict[str, Any]:
results = []
total_precision = 0
total_recall = 0
exact_match_count = 0
for case in self.benchmarks:
ir = parser.parse(case["input"])
ir = enricher.enrich(ir)
# Extract found elements
found = set()
for char in ir.characters:
found.add(char.name.lower())
for app in char.appearance: found.add(app.lower())
for clo in char.clothing: found.add(clo.lower())
for acc in char.accessories: found.add(acc.lower())
for pos in char.pose: found.add(pos.lower())
for exp in char.expression: found.add(exp.lower())
for loc in ir.scene.locations: found.add(loc.lower())
# Additional trace check for recovered attributes that might not map strictly
for res in ir.trace.get("resolved", {}).values():
if "attributes_recovered" in res:
for attr in res["attributes_recovered"]:
found.add(attr.lower())
expected = set(e.lower() for e in case["expected"])
# Match
# To handle fuzzy matching/semantic overlaps in evaluation:
# For strict precision/recall:
intersection = set()
for f in found:
for e in expected:
if f in e or e in f:
intersection.add(e)
precision = len(intersection) / len(found) if found else 0
recall = len(intersection) / len(expected) if expected else 1.0
total_precision += precision
total_recall += recall
if recall == 1.0:
exact_match_count += 1
else:
# Log failure
missing = list(expected - intersection)
extra = list(found - expected)
failure_entry = {
"input": case["input"],
"expected": list(expected),
"actual": list(found),
"missing": missing,
"extra": extra,
"root_cause": "Semantic drift or dependency parsing drop" if extra else "Extraction failure",
"severity": "High" if len(missing) >= len(expected)/2 else "Medium"
}
os.makedirs("failures", exist_ok=True)
# Use a hash of the input to avoid huge number of files, or just append to a single JSON Lines
with open("failures/error_catalog.jsonl", "a", encoding="utf-8") as err_file:
err_file.write(json.dumps(failure_entry) + "\n")
results.append({
"input": case["input"],
"expected": list(expected),
"found": list(found),
"recall": recall
})
avg_precision = total_precision / len(self.benchmarks)
avg_recall = total_recall / len(self.benchmarks)
f1 = 2 * (avg_precision * avg_recall) / (avg_precision + avg_recall) if (avg_precision + avg_recall) else 0
return {
"metrics": {
"precision": round(avg_precision, 4),
"recall": round(avg_recall, 4),
"f1": round(f1, 4),
"exact_match_rate": round(exact_match_count / len(self.benchmarks), 4)
},
"total_cases": len(self.benchmarks),
"results": results
}
if __name__ == "__main__":
suite = ExternalBenchmarkSuite()
print("Running external evaluation...")
matcher = ConceptMatcher("data/ontology")
# We use ONNX engine if available, or fallback to PyTorch
try:
from src.runtime.onnx_runtime import ONNXEmbeddingEngine
engine = ONNXEmbeddingEngine(index_dir="data/faiss_indices_onnx")
engine.load_index()
except Exception:
engine = EmbeddingEngine(index_dir="data/faiss_indices")
engine.load_index()
parser = Parser(matcher, engine)
enricher = Enricher(matcher)
report = suite.evaluate(parser, enricher)
os.makedirs("reports", exist_ok=True)
with open("reports/external_benchmark_results.json", "w", encoding="utf-8") as f:
json.dump(report, f, indent=2)
with open("reports/external_benchmark_results.md", "w", encoding="utf-8") as f:
f.write("# External Benchmark Results (v2.0)\n\n")
m = report["metrics"]
f.write(f"- **Total Cases**: {report['total_cases']}\n")
f.write(f"- **Precision**: {m['precision']}\n")
f.write(f"- **Recall**: {m['recall']}\n")
f.write(f"- **F1 Score**: {m['f1']}\n")
f.write(f"- **Exact Match Rate**: {m['exact_match_rate']}\n")
print("Benchmark evaluation complete. Reports generated in reports/external_benchmark_results.md")