novapay-support-agent / evaluation /bertscore_eval.py
Harshith69's picture
NovaPay multi-agent fintech customer support system
0d3cc2c
Raw
History Blame Contribute Delete
3.59 kB
"""BERTScore evaluation against reference resolutions.
For 50 test queries (balanced across categories) we get the live agent response
from the orchestrator, find the closest reference answer in the specialist
conversation dataset (keyword-overlap match), and compute BERTScore. Reports
mean precision/recall/F1.
Run: ``python evaluation/bertscore_eval.py``
Writes: ``evaluation/bertscore_results.json``
"""
from __future__ import annotations
import json
import re
from pathlib import Path
from common.config import PROCESSED_DIR, EVALUATION_DIR, TRIAGE_TEST_PATH, settings
from common.logging_utils import get_logger
from agents.orchestrator import handle_query
logger = get_logger("bertscore_eval")
_WORD_RE = re.compile(r"[a-z0-9]+")
def _tokens(text: str) -> set[str]:
return set(_WORD_RE.findall(text.lower()))
def _load_references() -> list[tuple[str, str]]:
"""Return (category, reference_text) from specialist conversations."""
refs = []
for category in settings.categories:
path = PROCESSED_DIR / f"specialist_conversations_{category}.jsonl"
if not path.exists():
continue
for line in path.read_text(encoding="utf-8").splitlines():
if not line.strip():
continue
convo = json.loads(line)
agent_turns = [t["text"] for t in convo.get("turns", []) if t.get("role") == "agent"]
ref = " ".join(agent_turns) or convo.get("resolution", "")
if ref:
refs.append((category, ref))
return refs
def _best_reference(query: str, category: str, refs: list[tuple[str, str]]) -> str:
q = _tokens(query)
best, best_overlap = "", -1
for cat, ref in refs:
if cat != category:
continue
overlap = len(q & _tokens(ref))
if overlap > best_overlap:
best, best_overlap = ref, overlap
return best
def _load_test_queries(n_per_category: int = 5) -> list[tuple[str, str]]:
if not TRIAGE_TEST_PATH.exists():
return []
buckets: dict[str, list[str]] = {c: [] for c in settings.categories}
for line in TRIAGE_TEST_PATH.read_text(encoding="utf-8").splitlines():
if not line.strip():
continue
rec = json.loads(line)
cat = json.loads(rec["output"])["category"]
if len(buckets[cat]) < n_per_category:
buckets[cat].append(rec["input"])
out = []
for cat, qs in buckets.items():
out.extend((q, cat) for q in qs)
return out[:50]
def main() -> None:
refs = _load_references()
queries = _load_test_queries()
if not refs or not queries:
logger.warning("Missing specialist references or test queries — generate Phase 1 data first.")
return
candidates, references = [], []
for query, category in queries:
response = handle_query(query, run_hallucination_check=False).get("response", "")
ref = _best_reference(query, category, refs)
if response and ref:
candidates.append(response)
references.append(ref)
from bert_score import score
P, R, F1 = score(candidates, references, model_type="distilbert-base-uncased", verbose=False)
results = {
"n": len(candidates),
"precision": round(P.mean().item(), 4),
"recall": round(R.mean().item(), 4),
"f1": round(F1.mean().item(), 4),
}
(EVALUATION_DIR / "bertscore_results.json").write_text(
json.dumps(results, indent=2), encoding="utf-8"
)
print("BERTScore:", results)
if __name__ == "__main__":
main()