telltale / tests /test_model_eval_harness.py
smpdl
Deploy ZeroGPU Space snapshot
60daaa6
Raw
History Blame Contribute Delete
10.3 kB
import json
from pathlib import Path
from telltale.game.holdem import ActionType
from telltale.models.eval_prompts import NEMOTRON_3_NANO_4B_Q4_K_M, SMOKE_EVAL_CASES
from telltale.models.eval_prompts import load_candidates_from_json
from telltale.models.eval_runner import (
EvalRunConfig,
evaluate_case,
summarize_candidate_results,
summarize_results,
write_eval_bundle,
write_eval_results,
)
class StaticRuntime:
def __init__(self, output: str):
self.output = output
self.prompts = []
def generate(self, prompt: str, *, max_tokens=None, temperature=None, seed=None):
self.prompts.append(
{
"prompt": prompt,
"max_tokens": max_tokens,
"temperature": temperature,
"seed": seed,
}
)
return self.output
class DiagnosticRuntime(StaticRuntime):
last_generation_metadata = {"finish_reason": "stop", "raw_text_length": 0}
def test_smoke_eval_cases_build_real_agent_prompts():
prompts = [case.build_prompt() for case in SMOKE_EVAL_CASES]
assert len(prompts) == 3
assert "worm" in prompts[0]
assert "molly" in prompts[1].lower()
assert "teddy" in prompts[2].lower()
assert "required_output" in prompts[0]
assert "max 28 words" in prompts[0]
def test_eval_case_can_build_richer_dialogue_prompt():
prompt = SMOKE_EVAL_CASES[0].build_prompt(speech_max_words=36, rationale_max_words=44)
assert "max 36 words" in prompt
assert "max 44 words" in prompt
def test_valid_model_eval_records_success():
case = SMOKE_EVAL_CASES[0]
legal_action = case.legal_actions[0].value
runtime = StaticRuntime(
json.dumps(
{
"action": legal_action,
"amount": 0,
"speech": "I hear you. Still, this is my price, and I am not donating another chip.",
"honest_rationale": "The legal action fits the solver pressure.",
"emotional_state": "needled",
"memory_delta": {"summary": "Player is applying verbal pressure."},
}
)
)
result = evaluate_case(
NEMOTRON_3_NANO_4B_Q4_K_M,
runtime,
case,
EvalRunConfig(max_tokens=111, temperature=0.6, seed=9),
)
assert result.json_valid is True
assert result.legal_action_valid is True
assert result.error is None
assert result.final_decision["action"] == legal_action
assert runtime.prompts[0]["max_tokens"] == 111
assert runtime.prompts[0]["temperature"] == 0.6
assert runtime.prompts[0]["seed"] == 9
def test_speech_length_uses_configured_limit():
case = SMOKE_EVAL_CASES[0]
runtime = StaticRuntime(
json.dumps(
{
"action": "fold",
"amount": 0,
"speech": "This line has exactly seven calm words.",
"honest_rationale": "The legal action follows the solver.",
"emotional_state": "calm",
"memory_delta": {},
}
)
)
strict = evaluate_case(NEMOTRON_3_NANO_4B_Q4_K_M, runtime, case, EvalRunConfig(speech_max_words=5))
generous = evaluate_case(NEMOTRON_3_NANO_4B_Q4_K_M, runtime, case, EvalRunConfig(speech_max_words=12))
assert strict.speech_length_valid is False
assert generous.speech_length_valid is True
def test_illegal_model_action_is_recorded_as_repaired():
case = SMOKE_EVAL_CASES[1]
runtime = StaticRuntime(
json.dumps(
{
"action": "check",
"amount": 500,
"speech": "I will not buy the act.",
"honest_rationale": "The price and line are worth contesting.",
"emotional_state": "composed",
"memory_delta": {},
}
)
)
result = evaluate_case(NEMOTRON_3_NANO_4B_Q4_K_M, runtime, case, EvalRunConfig())
assert result.json_valid is True
assert result.legal_action_valid is False
assert result.repair_applied is True
assert result.normalization_applied is False
assert result.repair_reason is not None
assert result.final_decision["action"] == ActionType.CALL.value
assert result.final_decision["amount"] == 0
def test_invalid_json_model_output_is_a_visible_eval_error():
runtime = StaticRuntime("I call. Trust me.")
result = evaluate_case(NEMOTRON_3_NANO_4B_Q4_K_M, runtime, SMOKE_EVAL_CASES[0], EvalRunConfig())
assert result.json_valid is False
assert result.final_decision is None
assert result.error is not None
def test_eval_result_includes_runtime_generation_diagnostics():
runtime = DiagnosticRuntime("")
result = evaluate_case(NEMOTRON_3_NANO_4B_Q4_K_M, runtime, SMOKE_EVAL_CASES[0], EvalRunConfig())
assert result.json_valid is False
assert result.runtime_metadata["generation"] == {"finish_reason": "stop", "raw_text_length": 0}
def test_call_amount_canonicalization_is_not_scored_as_repair():
case = SMOKE_EVAL_CASES[0]
runtime = StaticRuntime(
json.dumps(
{
"action": "call",
"amount": 45,
"speech": "Nice try, but I am not your charity.",
"honest_rationale": "The table talk got under my skin, but call is legal.",
"emotional_state": "annoyed",
"memory_delta": {"summary": "Player needled Worm."},
}
)
)
result = evaluate_case(NEMOTRON_3_NANO_4B_Q4_K_M, runtime, case, EvalRunConfig())
assert result.json_valid is True
assert result.legal_action_valid is True
assert result.normalization_applied is True
assert result.repair_applied is False
assert result.final_decision["amount"] == 0
def test_extra_model_keys_are_tracked_without_breaking_parse():
case = SMOKE_EVAL_CASES[0]
runtime = StaticRuntime(
json.dumps(
{
"action": "fold",
"amount": 0,
"speech": "Not paying for that speech.",
"honest_rationale": "The solver pressure says fold and the board is poor.",
"emotional_state": "annoyed",
"memory_delta": {},
"player_utterance": "echoed input",
}
)
)
result = evaluate_case(NEMOTRON_3_NANO_4B_Q4_K_M, runtime, case, EvalRunConfig())
assert result.json_valid is True
assert result.schema_keys_valid is False
assert result.unexpected_keys == ["player_utterance"]
def test_eval_results_write_jsonl_and_summarize(tmp_path):
runtime = StaticRuntime(
json.dumps(
{
"action": "bet",
"amount": 90,
"speech": "The trap has teeth.",
"honest_rationale": "Strong equity should charge draws.",
"emotional_state": "amused",
"memory_delta": {},
}
)
)
config = EvalRunConfig(output_dir=str(tmp_path))
result = evaluate_case(NEMOTRON_3_NANO_4B_Q4_K_M, runtime, SMOKE_EVAL_CASES[2], config)
path = write_eval_results([result], NEMOTRON_3_NANO_4B_Q4_K_M, config)
lines = path.read_text(encoding="utf-8").splitlines()
summary = summarize_results([result])
assert len(lines) == 1
assert json.loads(lines[0])["case_id"] == "teddy_boss_value_raise"
assert summary["total"] == 1
assert summary["json_valid"] == 1
def test_candidate_json_can_extend_harness_without_code_changes(tmp_path):
path = tmp_path / "candidates.json"
path.write_text(
json.dumps(
{
"candidates": [
{
"label": "example_qwen",
"hf_repo_id": "example/qwen-gguf",
"gguf_filename": "qwen-q4_k_m.gguf",
"quantization": "Q4_K_M",
}
]
}
),
encoding="utf-8",
)
candidates = load_candidates_from_json(str(path))
assert candidates[0].label == "example_qwen"
assert candidates[0].hf_repo_id == "example/qwen-gguf"
assert candidates[0].quantization == "Q4_K_M"
def test_candidate_summary_scores_mechanical_viability():
good_runtime = StaticRuntime(
json.dumps(
{
"action": "bet",
"amount": 90,
"speech": "The trap has teeth.",
"honest_rationale": "Strong equity should charge draws.",
"emotional_state": "amused",
"memory_delta": {},
}
)
)
bad_runtime = StaticRuntime("plain text, no JSON")
config = EvalRunConfig()
good = evaluate_case(NEMOTRON_3_NANO_4B_Q4_K_M, good_runtime, SMOKE_EVAL_CASES[2], config)
bad = evaluate_case(NEMOTRON_3_NANO_4B_Q4_K_M, bad_runtime, SMOKE_EVAL_CASES[2], config)
good_summary = summarize_candidate_results("good", [good])
bad_summary = summarize_candidate_results("bad", [bad])
assert good_summary.automatic_score > bad_summary.automatic_score
assert good_summary.speech_length_valid == 1
assert bad_summary.errors == 1
def test_eval_bundle_writes_per_candidate_jsonl_and_summary(tmp_path):
runtime = StaticRuntime(
json.dumps(
{
"action": "fold",
"amount": 0,
"speech": "Not buying this one.",
"honest_rationale": "The price trails my equity.",
"emotional_state": "annoyed",
"memory_delta": {},
}
)
)
config = EvalRunConfig(output_dir=str(tmp_path))
result = evaluate_case(NEMOTRON_3_NANO_4B_Q4_K_M, runtime, SMOKE_EVAL_CASES[0], config)
bundle = write_eval_bundle({NEMOTRON_3_NANO_4B_Q4_K_M.label: [result]}, output_dir=str(tmp_path))
summary = json.loads(Path(bundle["summary_path"]).read_text(encoding="utf-8"))
result_path = Path(bundle["result_paths"][NEMOTRON_3_NANO_4B_Q4_K_M.label])
assert summary["ranking"][0]["candidate_label"] == NEMOTRON_3_NANO_4B_Q4_K_M.label
assert json.loads(result_path.read_text(encoding="utf-8").splitlines()[0])["case_id"] == "worm_needle_facing_call"