class TestQueryTrace:
"""Test QueryTrace construction, properties, and serialization."""
def _make_trace(self) -> QueryTrace:
turns = [
TurnTrace(
turn_index=0,
input_tokens=100,
output_tokens=50,
tools_called=["calc"],
wall_clock_s=1.0,
gpu_energy_joules=5.0,
cost_usd=0.002,
),
TurnTrace(
turn_index=1,
input_tokens=80,
output_tokens=30,
tools_called=["search", "think"],
wall_clock_s=2.0,
gpu_energy_joules=8.0,
cost_usd=0.003,
),
]
return QueryTrace(
query_id="q0001",
workload_type="agentic",
query_text="What is 2+2?",
response_text="4",
turns=turns,
total_wall_clock_s=3.0,
completed=True,
)
def test_num_turns(self) -> None:
trace = self._make_trace()
assert trace.num_turns == 2
def test_total_input_tokens(self) -> None:
trace = self._make_trace()
assert trace.total_input_tokens == 180
def test_total_output_tokens(self) -> None:
trace = self._make_trace()
assert trace.total_output_tokens == 80
def test_total_tool_calls(self) -> None:
trace = self._make_trace()
assert trace.total_tool_calls == 3
def test_total_gpu_energy_joules(self) -> None:
trace = self._make_trace()
assert trace.total_gpu_energy_joules == 13.0
def test_total_gpu_energy_joules_none_when_no_values(self) -> None:
trace = QueryTrace(
query_id="q0",
workload_type="test",
turns=[TurnTrace(turn_index=0)],
)
assert trace.total_gpu_energy_joules is None
def test_total_cost_usd(self) -> None:
trace = self._make_trace()
assert trace.total_cost_usd == pytest.approx(0.005)
def test_total_cost_usd_none_when_no_values(self) -> None:
trace = QueryTrace(
query_id="q0",
workload_type="test",
turns=[TurnTrace(turn_index=0)],
)
assert trace.total_cost_usd is None
def test_to_dict_from_dict_roundtrip(self) -> None:
trace = self._make_trace()
d = trace.to_dict()
restored = QueryTrace.from_dict(d)
assert restored.query_id == trace.query_id
assert restored.workload_type == trace.workload_type
assert restored.num_turns == trace.num_turns
assert restored.total_input_tokens == trace.total_input_tokens
assert restored.completed == trace.completed
def test_jsonl_save_load_roundtrip(self, tmp_path: Path) -> None:
trace = self._make_trace()
jsonl_path = tmp_path / "traces.jsonl"
trace.save_jsonl(jsonl_path)
loaded = QueryTrace.load_jsonl(jsonl_path)
assert len(loaded) == 1
assert loaded[0].query_id == "q0001"
assert loaded[0].num_turns == 2
assert loaded[0].total_input_tokens == 180
def test_jsonl_multiple_traces(self, tmp_path: Path) -> None:
trace1 = self._make_trace()
trace2 = QueryTrace(
query_id="q0002",
workload_type="agentic",
query_text="Another question",
response_text="Another answer",
turns=[TurnTrace(turn_index=0, input_tokens=10, output_tokens=5)],
total_wall_clock_s=0.5,
completed=True,
)
jsonl_path = tmp_path / "traces.jsonl"
trace1.save_jsonl(jsonl_path)
trace2.save_jsonl(jsonl_path)
loaded = QueryTrace.load_jsonl(jsonl_path)
assert len(loaded) == 2
assert loaded[0].query_id == "q0001"
assert loaded[1].query_id == "q0002"
def test_to_hf_dataset(self) -> None:
try:
from datasets import Dataset
except ImportError:
pytest.skip("datasets package not available")
trace = self._make_trace()
ds = QueryTrace.to_hf_dataset([trace])
assert len(ds) == 1
row = ds[0]
assert row["query_id"] == "q0001"
assert row["num_turns"] == 2
assert row["total_input_tokens"] == 180
assert row["total_output_tokens"] == 80
assert row["total_tool_calls"] == 3
assert row["completed"] is True
# trace_json should be valid JSON
parsed = json.loads(row["trace_json"])
assert parsed["query_id"] == "q0001"