decision-debate-agent / evaluate.py
saadumar26's picture
Deploy decision debate agent backend
e59ae36
Raw History Blame Contribute Delete
5.3 kB
"""
Evaluation runner.
Runs both the baseline (single prompt) and the full agent (multi-persona
debate graph) on every scenario in eval_data/scenarios.json, and saves
raw outputs + basic run metadata (timing, retry counts) to a results
file. This does NOT auto-score quality -- per the hackathon brief, a
"good final result" is user-defined, and forcing an LLM to grade its own
sibling's outputs would be circular evidence. Instead this script produces
the paired baseline/agent transcripts that a human (you) scores against
the rubric in eval_data/rubric.md, and that go into the submission as
raw evidence.
Usage:
python evaluate.py # run all scenarios
python evaluate.py --limit 2 # quick smoke test on first 2
"""
import json
import time
import shutil
import argparse
import tempfile
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
from baseline import run_baseline
from graph import run_debate
from memory.vector_store import DecisionMemory
SCENARIOS_PATH = Path("eval_data/scenarios.json")
RESULTS_PATH = Path("eval_data/results.json")
def load_scenarios():
with open(SCENARIOS_PATH, "r", encoding="utf-8") as f:
return json.load(f)
def run_one_scenario(scenario: dict) -> dict:
"""Each scenario gets a FRESH, isolated memory store.
Discovered issue: running all benchmark scenarios against one shared
persistent memory (as this originally did) caused cross-scenario
contamination -- e.g. the 'used_vs_new_car' scenario's agent output
referenced "your upcoming housing goal", which the user never
mentioned in that query. It leaked in because the unrelated
'rent_vs_buy' scenario, run earlier in the same batch, had been
stored in memory and got retrieved as a "similar past decision" by
TF-IDF's loose similarity matching.
This is a real failure mode, not just a benchmark artifact: in
production, an unrelated prior decision (even the user's own) being
injected as unstated context could distort advice on a fresh
question. The fix here is narrow and honest about its limits: for
evaluation, we isolate memory per scenario so the debate quality is
judged on its own merits, uncontaminated. Memory's actual value
(recalling a user's own genuinely related past decisions) would need
a separate, deliberately-related test case to demonstrate -- it is
not validated by this benchmark run.
"""
query = scenario["query"]
print(f"\n=== Scenario: {scenario['id']} ===")
print(f"Query: {query}")
temp_dir = tempfile.mkdtemp(prefix="eval_memory_")
memory = DecisionMemory(persist_dir=temp_dir)
try:
print("Running baseline...")
t0 = time.time()
baseline_output = run_baseline(query)
baseline_time = time.time() - t0
print(f" baseline done in {baseline_time:.1f}s")
print("Running agent (multi-persona debate)...")
t0 = time.time()
agent_result = run_debate(query, memory)
agent_time = time.time() - t0
print(f" agent done in {agent_time:.1f}s (verification retries: {agent_result.get('retry_count', 0) - 1})")
return {
"id": scenario["id"],
"category": scenario.get("category"),
"query": query,
"note": scenario.get("note", ""),
"baseline": {
"output": baseline_output,
"time_seconds": round(baseline_time, 2),
},
"agent": {
"optimist_view": agent_result["optimist_view"],
"skeptic_view": agent_result["skeptic_view"],
"analyst_view": agent_result["analyst_view"],
"moderator_output": agent_result["moderator_output"],
"verification_retries": agent_result.get("retry_count", 1) - 1,
"time_seconds": round(agent_time, 2),
},
}
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--limit", type=int, default=None, help="Only run the first N scenarios")
args = parser.parse_args()
scenarios = load_scenarios()
if args.limit:
scenarios = scenarios[: args.limit]
results = []
for scenario in scenarios:
try:
result = run_one_scenario(scenario)
except RuntimeError as e:
if "DAILY quota exhausted" in str(e):
print(f"\n STOPPING: {e}")
print(f" Saving {len(results)} completed results so far before exiting.")
break
print(f" ERROR on {scenario['id']}: {e}")
result = {"id": scenario["id"], "query": scenario["query"], "error": str(e)}
results.append(result)
continue
except Exception as e:
print(f" ERROR on {scenario['id']}: {e}")
result = {"id": scenario["id"], "query": scenario["query"], "error": str(e)}
results.append(result)
RESULTS_PATH.parent.mkdir(exist_ok=True)
with open(RESULTS_PATH, "w", encoding="utf-8") as f:
json.dump(results, f, indent=2, ensure_ascii=False)
print(f"\nSaved {len(results)} results to {RESULTS_PATH}")
if __name__ == "__main__":
main()