aniketqxp's picture
feat: add processing service runtime
f1ef7e2
Raw
History Blame Contribute Delete
5.67 kB
"""
Consistency harness for the graph evaluation pipeline.
A scoring system that produces different results on the same input across
runs is not dependable. This harness runs the SAME call through the graph N
times and measures agreement:
- compliance : per-check agreement rate (fraction of runs matching the
modal pass/fail/null verdict)
- quality : per-dimension score min / max / range / mean / stdev
- escalation : risk_level + emotion agreement rate
- anchoring : evidence anchor rate per run
Usage:
python consistency.py --call_id en_CA_Banking_1586889 --runs 5
"""
import os
import json
import time
import argparse
import statistics
from collections import Counter
from graph import build_graph
COMPLIANCE_KEYS = [
"name_announced", "company_announced", "recording_disclosure",
"identity_verified", "resolution_provided", "transfer_next_steps",
]
QUALITY_KEYS = [
"efficiency", "problem_resolution", "clarity",
"professionalism", "empathy", "customer_satisfaction",
]
def agreement(values):
"""Fraction of values matching the mode. 1.0 = perfect agreement."""
if not values:
return None
most_common = Counter(values).most_common(1)[0][1]
return most_common / len(values)
def run_sweep(call_id, runs, results_dir):
graph = build_graph()
evaluations = []
for i in range(1, runs + 1):
print(f"\n--- run {i}/{runs} " + "-" * 50)
t0 = time.time()
final = graph.invoke({"call_id": call_id, "results_dir": results_dir})
dt = time.time() - t0
ev = final["evaluation"]
evaluations.append(ev)
stats = ev.get("_anchor_stats", {})
print(f"--- run {i} done in {dt:.1f}s "
f"(anchored {stats.get('anchored')}/{stats.get('total')})")
return evaluations
def analyze(evaluations):
report = {"n_runs": len(evaluations), "compliance": {}, "quality": {},
"escalation": {}, "anchoring": {}}
# compliance agreement per check
for key in COMPLIANCE_KEYS:
verdicts = [str(ev["compliance"][key]["passed"]) for ev in evaluations]
report["compliance"][key] = {
"verdicts": verdicts,
"agreement": agreement(verdicts),
}
# quality score variance per dimension
for key in QUALITY_KEYS:
scores = [ev["quality"][key]["score"] for ev in evaluations
if ev["quality"].get(key)]
if not scores:
continue
report["quality"][key] = {
"scores": scores,
"min": min(scores),
"max": max(scores),
"range": max(scores) - min(scores),
"mean": round(statistics.mean(scores), 2),
"stdev": round(statistics.stdev(scores), 3) if len(scores) > 1 else 0.0,
}
# escalation agreement
risks = [ev["escalation"]["risk_level"] for ev in evaluations]
emotions = [ev["escalation"]["customer_emotion_text"] for ev in evaluations]
report["escalation"] = {
"risk_levels": risks,
"risk_agreement": agreement(risks),
"emotions": emotions,
"emotion_agreement": agreement(emotions),
}
# anchor rates
rates = []
for ev in evaluations:
s = ev.get("_anchor_stats", {})
if s.get("total"):
rates.append(round(s["anchored"] / s["total"], 3))
report["anchoring"] = {"rates": rates}
return report
def print_report(report, call_id):
n = report["n_runs"]
print("\n" + "=" * 68)
print(f" CONSISTENCY REPORT {call_id} ({n} runs)")
print("=" * 68)
print("\n COMPLIANCE (agreement with modal verdict)")
worst_c = 1.0
for key, r in report["compliance"].items():
a = r["agreement"]
worst_c = min(worst_c, a)
flag = "" if a == 1.0 else f" <-- UNSTABLE {r['verdicts']}"
print(f" {a * 100:5.1f}% {key}{flag}")
print("\n QUALITY (score spread across runs)")
worst_range = 0
for key, r in report["quality"].items():
worst_range = max(worst_range, r["range"])
flag = "" if r["range"] <= 1 else " <-- UNSTABLE"
print(f" {key:<24} scores={r['scores']} "
f"range={r['range']} stdev={r['stdev']}{flag}")
e = report["escalation"]
print(f"\n ESCALATION risk agreement: {e['risk_agreement'] * 100:.1f}% "
f"{e['risk_levels']}")
print(f" emotion agreement: {e['emotion_agreement'] * 100:.1f}% "
f"{e['emotions']}")
rates = report["anchoring"]["rates"]
if rates:
print(f"\n ANCHOR RATES per run: {rates}")
print("\n VERDICT: ", end="")
if worst_c == 1.0 and worst_range <= 1 and e["risk_agreement"] == 1.0:
print("stable (all compliance unanimous, quality range <= 1, "
"risk unanimous)")
else:
print("instability detected -- see UNSTABLE flags above")
print("=" * 68)
def main():
ap = argparse.ArgumentParser(
description="Run the graph N times on one call, measure agreement")
ap.add_argument("--call_id", required=True)
ap.add_argument("--runs", type=int, default=5)
ap.add_argument("--results_dir", default="results_channels")
args = ap.parse_args()
evaluations = run_sweep(args.call_id, args.runs, args.results_dir)
report = analyze(evaluations)
print_report(report, args.call_id)
here = os.path.dirname(os.path.abspath(__file__))
out = os.path.join(here, "results", f"{args.call_id}_consistency.json")
with open(out, "w", encoding="utf-8") as f:
json.dump(report, f, indent=2)
print(f"\nSaved -> {out}")
if __name__ == "__main__":
main()