Coverage for graphqler / compiler / resolvers / llm / comparison.py: 24%
41 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-20 10:09 -0400
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-20 10:09 -0400
1"""ResolverComparison — aggregates and persists the side-by-side comparison of
2LLM resolver output vs classic resolver output.
4The saved JSON file (`eval/resolver_comparison.json`) is structured to make
5systematic analysis easy:
7{
8 "summary": {
9 "total_mutations": int,
10 "mutations_that_differ": int,
11 "total_queries": int,
12 "queries_that_differ": int
13 },
14 "mutations": {
15 "<name>": {
16 "classic": {"mutationType": "...", "hardDependsOn": {...}, "softDependsOn": {...}},
17 "llm": {"mutationType": "...", "hardDependsOn": {...}, "softDependsOn": {...}},
18 "differs": bool,
19 "diff": { "<field>": {"classic": ..., "llm": ...} }
20 }
21 },
22 "queries": { ... same shape without mutationType ... }
23}
24"""
26import json
27import logging
28from pathlib import Path
30logger = logging.getLogger(__name__)
32COMPARISON_FILE_NAME = "eval/resolver_comparison.json"
35class ResolverComparison:
36 """Holds and saves the LLM-vs-classic comparison for mutations and queries."""
38 def __init__(self, mutation_comparison: dict, query_comparison: dict):
39 """
40 Args:
41 mutation_comparison (dict): Output of LLMMutationObjectResolver.comparison.
42 query_comparison (dict): Output of LLMQueryObjectResolver.comparison.
43 """
44 self.mutation_comparison = mutation_comparison
45 self.query_comparison = query_comparison
47 def build(self) -> dict:
48 """Build the full comparison document.
50 Returns:
51 dict: The complete comparison document with summary + per-endpoint diffs.
52 """
53 mutations_that_differ = sum(1 for v in self.mutation_comparison.values() if v.get("differs"))
54 queries_that_differ = sum(1 for v in self.query_comparison.values() if v.get("differs"))
56 return {
57 "summary": {
58 "total_mutations": len(self.mutation_comparison),
59 "mutations_that_differ": mutations_that_differ,
60 "total_queries": len(self.query_comparison),
61 "queries_that_differ": queries_that_differ,
62 },
63 "mutations": self.mutation_comparison,
64 "queries": self.query_comparison,
65 }
67 def save(self, output_dir: str) -> str:
68 """Serialize and write the comparison document to disk.
70 Args:
71 output_dir (str): The run output directory (same as the compiled/ parent).
73 Returns:
74 str: Absolute path of the written file.
75 """
76 dest = Path(output_dir) / COMPARISON_FILE_NAME
77 dest.parent.mkdir(parents=True, exist_ok=True)
78 document = self.build()
79 with open(dest, "w") as f:
80 json.dump(document, f, indent=2)
81 logger.info(f"Resolver comparison saved to {dest}")
82 summary = document["summary"]
83 print(
84 f"(C) LLM resolver comparison: "
85 f"{summary['mutations_that_differ']}/{summary['total_mutations']} mutations differ, "
86 f"{summary['queries_that_differ']}/{summary['total_queries']} queries differ → {dest}"
87 )
88 return str(dest)
90 def print_diff_summary(self):
91 """Print a human-readable diff summary to stdout."""
92 doc = self.build()
93 summary = doc["summary"]
94 print(f"\n{'='*60}")
95 print(" Resolver comparison: LLM vs Classic")
96 print(f"{'='*60}")
97 print(f" Mutations : {summary['mutations_that_differ']} / {summary['total_mutations']} differ")
98 print(f" Queries : {summary['queries_that_differ']} / {summary['total_queries']} differ")
100 for section, label in (("mutations", "MUTATION"), ("queries", "QUERY")):
101 for name, entry in doc[section].items():
102 if not entry.get("differs"):
103 continue
104 print(f"\n [{label}] {name}")
105 for field, values in entry.get("diff", {}).items():
106 print(f" {field}:")
107 print(f" classic: {values['classic']}")
108 print(f" llm : {values['llm']}")
109 print(f"{'='*60}\n")