ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
"""Helpers for direct execution of tests moved under tests/."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
def bootstrap_experiment_root() -> None:
|
||||
experiment_root = Path(__file__).resolve().parents[1]
|
||||
if str(experiment_root) not in sys.path:
|
||||
sys.path.insert(0, str(experiment_root))
|
||||
@@ -0,0 +1,9 @@
|
||||
"""Test import bootstrap for the elo-leaderboard experiment."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
EXPERIMENT_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(EXPERIMENT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(EXPERIMENT_ROOT))
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Empty rating history must not crash analyze_rating_changes / get_rating_history."""
|
||||
import pandas as pd
|
||||
|
||||
from animation import prepare_animation_data
|
||||
from leaderboard import (
|
||||
analyze_rating_changes,
|
||||
build_historical_leaderboards,
|
||||
get_rating_history,
|
||||
)
|
||||
|
||||
|
||||
def test_get_rating_history_empty_keeps_columns():
|
||||
hist = build_historical_leaderboards(
|
||||
pd.DataFrame(columns=["model_a", "model_b", "winner"]),
|
||||
[(pd.Timestamp("2020-01-01"), pd.DataFrame(columns=["model_a", "model_b", "winner"]))],
|
||||
)
|
||||
rh = get_rating_history(hist)
|
||||
assert list(rh.columns) == ["date", "model", "rating", "rank", "matches", "wins"]
|
||||
assert len(rh) == 0
|
||||
|
||||
|
||||
def test_analyze_empty_history_returns_empty_frame():
|
||||
empty = pd.DataFrame(columns=["date", "model", "rating", "rank", "matches", "wins"])
|
||||
stats = analyze_rating_changes(empty)
|
||||
assert len(stats) == 0
|
||||
assert "model" in stats.columns
|
||||
|
||||
|
||||
def test_analyze_after_empty_historical_leaderboards():
|
||||
hist = build_historical_leaderboards(
|
||||
pd.DataFrame(columns=["model_a", "model_b", "winner"]),
|
||||
[(pd.Timestamp("2020-01-01"), pd.DataFrame(columns=["model_a", "model_b", "winner"]))],
|
||||
)
|
||||
rh = get_rating_history(hist)
|
||||
stats = analyze_rating_changes(rh)
|
||||
assert len(stats) == 0
|
||||
anim = prepare_animation_data(rh)
|
||||
assert anim["frames"] == []
|
||||
assert anim["total_frames"] == 0
|
||||
@@ -0,0 +1,11 @@
|
||||
"""Regression: prepare_animation_data must tolerate empty history."""
|
||||
import pandas as pd
|
||||
from animation import prepare_animation_data
|
||||
|
||||
|
||||
def test_empty_history_returns_empty_frames():
|
||||
df = pd.DataFrame(columns=["date", "model", "rating", "rank", "matches", "wins"])
|
||||
data = prepare_animation_data(df)
|
||||
assert data["frames"] == []
|
||||
assert data["total_frames"] == 0
|
||||
assert data["start_date"] is None
|
||||
@@ -0,0 +1,35 @@
|
||||
"""prepare_animation_data must keep fractional wins from Elo ties."""
|
||||
import pandas as pd
|
||||
from animation import prepare_animation_data
|
||||
|
||||
|
||||
def test_tie_half_wins_are_not_truncated():
|
||||
history = pd.DataFrame(
|
||||
{
|
||||
"date": pd.to_datetime(["2024-01-07", "2024-01-07"]),
|
||||
"model": ["A", "B"],
|
||||
"rating": [1000.0, 1000.0],
|
||||
"rank": [1, 2],
|
||||
"matches": [1, 1],
|
||||
"wins": [0.5, 0.5],
|
||||
}
|
||||
)
|
||||
data = prepare_animation_data(history, top_n=2)
|
||||
wins = {m["name"]: m["wins"] for m in data["frames"][0]["models"]}
|
||||
assert wins["A"] == 0.5
|
||||
assert wins["B"] == 0.5
|
||||
|
||||
|
||||
def test_whole_wins_still_serialize():
|
||||
history = pd.DataFrame(
|
||||
{
|
||||
"date": pd.to_datetime(["2024-01-07"]),
|
||||
"model": ["A"],
|
||||
"rating": [1010.0],
|
||||
"rank": [1],
|
||||
"matches": [2],
|
||||
"wins": [2.0],
|
||||
}
|
||||
)
|
||||
data = prepare_animation_data(history, top_n=1)
|
||||
assert data["frames"][0]["models"][0]["wins"] == 2.0
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Test suite locking out ZeroDivisionError in benchmark summary print logic
|
||||
when time_basic is 0.0 or df_sample is empty.
|
||||
"""
|
||||
|
||||
def test_benchmark_pct_reduction_zero_division():
|
||||
"""
|
||||
Ensure zero time_basic does not raise ZeroDivisionError during benchmark calculation.
|
||||
"""
|
||||
time_basic = 0.0
|
||||
time_optimized = 0.0
|
||||
pct_reduction = (1 - time_optimized / time_basic) * 100 if time_basic > 0 else 0.0
|
||||
assert pct_reduction == 0.0
|
||||
|
||||
|
||||
def test_benchmark_extrapolation_zero_sample():
|
||||
"""
|
||||
Ensure empty df_sample does not raise ZeroDivisionError during extrapolation check.
|
||||
"""
|
||||
df_sample = []
|
||||
df_filtered = [1, 2, 3]
|
||||
should_extrapolate = len(df_sample) > 0 and len(df_sample) < len(df_filtered)
|
||||
assert not should_extrapolate
|
||||
@@ -0,0 +1,16 @@
|
||||
import pandas as pd
|
||||
from bradley_terry import compute_mle_elo, get_bootstrap_result
|
||||
|
||||
|
||||
def test_bootstrap_is_reproducible():
|
||||
battles = pd.DataFrame(
|
||||
[
|
||||
{"model_a": "a", "model_b": "b", "winner": "model_a"},
|
||||
{"model_a": "a", "model_b": "b", "winner": "model_b"},
|
||||
{"model_a": "a", "model_b": "b", "winner": "tie"},
|
||||
{"model_a": "b", "model_b": "a", "winner": "model_a"},
|
||||
]
|
||||
)
|
||||
first = get_bootstrap_result(battles, compute_mle_elo, num_round=3)
|
||||
second = get_bootstrap_result(battles, compute_mle_elo, num_round=3)
|
||||
pd.testing.assert_frame_equal(first, second)
|
||||
@@ -0,0 +1,11 @@
|
||||
"""Regression: compute_mle_elo must work on small Arena-shaped battle sets."""
|
||||
import pandas as pd
|
||||
from battle_simulator import simulate_battles
|
||||
from bradley_terry import compute_mle_elo
|
||||
|
||||
|
||||
def test_small_two_model_sample():
|
||||
df = pd.DataFrame(simulate_battles({"gpt-4": 1200.0, "llama-3": 1000.0}, 10, seed=1))
|
||||
ratings = compute_mle_elo(df)
|
||||
assert len(ratings) == 2
|
||||
assert set(ratings.index) == {"gpt-4", "llama-3"}
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Ties must contribute to Bradley-Terry weights (not be zeroed by pivot+T)."""
|
||||
import pandas as pd
|
||||
|
||||
from bradley_terry import compute_mle_elo
|
||||
|
||||
|
||||
def test_all_ties_rates_models_instead_of_sample_weight_error():
|
||||
df = pd.DataFrame(
|
||||
[
|
||||
{"model_a": "A", "model_b": "B", "winner": "tie"},
|
||||
{"model_a": "A", "model_b": "C", "winner": "tie (bothbad)"},
|
||||
{"model_a": "B", "model_b": "C", "winner": "tie"},
|
||||
]
|
||||
)
|
||||
ratings = compute_mle_elo(df)
|
||||
assert set(ratings.index) == {"A", "B", "C"}
|
||||
# Pure ties -> equal latent skills under BT.
|
||||
assert abs(float(ratings["A"]) - float(ratings["B"])) < 1e-6
|
||||
assert abs(float(ratings["A"]) - float(ratings["C"])) < 1e-6
|
||||
|
||||
|
||||
def test_ties_change_ratings_versus_wins_only():
|
||||
wins_only = pd.DataFrame(
|
||||
[
|
||||
{"model_a": "A", "model_b": "B", "winner": "model_a"},
|
||||
{"model_a": "B", "model_b": "C", "winner": "model_a"},
|
||||
]
|
||||
)
|
||||
with_ties = pd.concat(
|
||||
[
|
||||
wins_only,
|
||||
pd.DataFrame(
|
||||
[{"model_a": "A", "model_b": "C", "winner": "tie"}] * 8
|
||||
),
|
||||
],
|
||||
ignore_index=True,
|
||||
)
|
||||
r1 = compute_mle_elo(wins_only)
|
||||
r2 = compute_mle_elo(with_ties)
|
||||
# Extra A–C ties pull A and C together relative to the wins-only fit.
|
||||
assert abs(float(r2["A"]) - float(r2["C"])) < abs(float(r1["A"]) - float(r1["C"]))
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Regression test for prepare_animation_data with string or date objects in history_df."""
|
||||
import pandas as pd
|
||||
from animation import prepare_animation_data
|
||||
|
||||
|
||||
def test_prepare_animation_data_string_date():
|
||||
"""prepare_animation_data must handle string dates without raising AttributeError."""
|
||||
history = pd.DataFrame([
|
||||
{
|
||||
"date": "2024-08-01",
|
||||
"model": "model_a",
|
||||
"rating": 1050.0,
|
||||
"rank": 1,
|
||||
"matches": 10,
|
||||
"wins": 7.0,
|
||||
},
|
||||
{
|
||||
"date": "2024-08-01",
|
||||
"model": "model_b",
|
||||
"rating": 950.0,
|
||||
"rank": 2,
|
||||
"matches": 10,
|
||||
"wins": 3.0,
|
||||
},
|
||||
])
|
||||
data = prepare_animation_data(history, top_n=2)
|
||||
assert data["total_frames"] == 1
|
||||
assert data["start_date"] == "2024-08-01"
|
||||
assert data["end_date"] == "2024-08-01"
|
||||
assert len(data["frames"]) == 1
|
||||
assert data["frames"][0]["date"] == "2024-08-01"
|
||||
assert data["frames"][0]["timestamp"] == 1722470400
|
||||
@@ -0,0 +1,17 @@
|
||||
"""Regression test for compare_win_rates when comparisons list is empty."""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from elo_rating import EloRatingSystem
|
||||
from leaderboard import compare_win_rates
|
||||
|
||||
|
||||
def test_compare_win_rates_empty_has_required_columns():
|
||||
"""compare_win_rates must return a DataFrame with required columns when no valid comparisons exist."""
|
||||
elo = EloRatingSystem()
|
||||
empirical_df = pd.DataFrame(np.nan, index=["model_a", "model_b"], columns=["model_a", "model_b"])
|
||||
df_comp = compare_win_rates(elo, empirical_df)
|
||||
assert list(df_comp.columns) == ["model_a", "model_b", "empirical", "predicted", "error"]
|
||||
assert len(df_comp) == 0
|
||||
# Accessing columns on empty result must not raise KeyError
|
||||
assert "error" in df_comp
|
||||
assert df_comp["error"].empty
|
||||
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
Unit tests for Elo rating system
|
||||
"""
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from _bootstrap import bootstrap_experiment_root
|
||||
|
||||
bootstrap_experiment_root()
|
||||
|
||||
from elo_rating import EloRatingSystem
|
||||
|
||||
|
||||
def test_initial_rating():
|
||||
"""Test that models start with initial rating."""
|
||||
elo = EloRatingSystem(initial_rating=1000.0)
|
||||
assert elo.get_rating("model_a") == 1000.0
|
||||
assert elo.get_rating("model_b") == 1000.0
|
||||
|
||||
|
||||
def test_expected_score():
|
||||
"""Test expected score calculation."""
|
||||
elo = EloRatingSystem()
|
||||
|
||||
# Equal ratings should give 50% probability
|
||||
assert elo.expected_score(1000, 1000) == 0.5
|
||||
|
||||
# Higher rated player should have > 50% probability
|
||||
assert elo.expected_score(1200, 1000) > 0.5
|
||||
assert elo.expected_score(1000, 1200) < 0.5
|
||||
|
||||
# 400 point difference should give ~91% probability
|
||||
prob = elo.expected_score(1400, 1000)
|
||||
assert 0.90 < prob < 0.92
|
||||
|
||||
|
||||
def test_rating_update_win():
|
||||
"""Test rating update when model_a wins."""
|
||||
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
||||
|
||||
new_a, new_b = elo.update_ratings("model_a", "model_b", "model_a")
|
||||
|
||||
# Winner should gain rating, loser should lose rating
|
||||
assert new_a > 1000.0
|
||||
assert new_b < 1000.0
|
||||
|
||||
# Total rating should be conserved (zero-sum)
|
||||
assert abs((new_a + new_b) - 2000.0) < 0.01
|
||||
|
||||
|
||||
def test_rating_update_tie():
|
||||
"""Test rating update for a tie."""
|
||||
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
||||
|
||||
new_a, new_b = elo.update_ratings("model_a", "model_b", "tie")
|
||||
|
||||
# With equal ratings, tie should not change ratings much
|
||||
assert abs(new_a - 1000.0) < 0.01
|
||||
assert abs(new_b - 1000.0) < 0.01
|
||||
|
||||
|
||||
def test_upset_gives_larger_change():
|
||||
"""Test that unexpected results cause larger rating changes."""
|
||||
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
||||
|
||||
# Give model_a higher rating
|
||||
elo.ratings["model_a"] = 1200.0
|
||||
elo.ratings["model_b"] = 1000.0
|
||||
|
||||
# If weaker model wins (upset), changes should be larger
|
||||
new_a_upset, new_b_upset = elo.update_ratings("model_a", "model_b", "model_b")
|
||||
|
||||
# Reset
|
||||
elo.ratings["model_a"] = 1200.0
|
||||
elo.ratings["model_b"] = 1000.0
|
||||
|
||||
# If stronger model wins (expected), changes should be smaller
|
||||
new_a_expected, new_b_expected = elo.update_ratings("model_a", "model_b", "model_a")
|
||||
|
||||
# Upset should cause larger change
|
||||
change_upset = abs(new_a_upset - 1200.0)
|
||||
change_expected = abs(new_a_expected - 1200.0)
|
||||
|
||||
assert change_upset > change_expected
|
||||
|
||||
|
||||
def test_leaderboard_sorting():
|
||||
"""Test that leaderboard is sorted by rating."""
|
||||
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
||||
|
||||
# Create some matches to differentiate ratings
|
||||
elo.update_ratings("model_a", "model_b", "model_a")
|
||||
elo.update_ratings("model_a", "model_c", "model_a")
|
||||
elo.update_ratings("model_b", "model_c", "model_b")
|
||||
|
||||
leaderboard = elo.get_leaderboard()
|
||||
|
||||
# Check descending order
|
||||
for i in range(len(leaderboard) - 1):
|
||||
assert leaderboard[i][1] >= leaderboard[i+1][1]
|
||||
|
||||
# model_a should be first (won all matches)
|
||||
assert leaderboard[0][0] == "model_a"
|
||||
|
||||
|
||||
def test_win_probability_symmetry():
|
||||
"""Test that win probabilities sum to 1."""
|
||||
elo = EloRatingSystem()
|
||||
elo.ratings["model_a"] = 1200.0
|
||||
elo.ratings["model_b"] = 1000.0
|
||||
|
||||
prob_a = elo.calculate_win_probability("model_a", "model_b")
|
||||
prob_b = elo.calculate_win_probability("model_b", "model_a")
|
||||
|
||||
# Should sum to 1
|
||||
assert abs(prob_a + prob_b - 1.0) < 0.001
|
||||
|
||||
|
||||
def test_match_counting():
|
||||
"""Test that match and win counts are tracked correctly."""
|
||||
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
||||
|
||||
elo.update_ratings("model_a", "model_b", "model_a") # model_a wins
|
||||
elo.update_ratings("model_a", "model_c", "model_b") # model_a loses (2nd slot wins)
|
||||
elo.update_ratings("model_a", "model_b", "tie") # tie -> 0.5 each
|
||||
|
||||
# model_a played 3 matches
|
||||
assert elo.match_counts["model_a"] == 3
|
||||
|
||||
# model_a won 1 match and tied 1 (1.5 total)
|
||||
assert elo.win_counts["model_a"] == 1.5
|
||||
|
||||
# model_b played 2 matches
|
||||
assert elo.match_counts["model_b"] == 2
|
||||
|
||||
|
||||
def test_copy():
|
||||
"""Test that copy creates independent instance."""
|
||||
elo1 = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
||||
elo1.update_ratings("model_a", "model_b", "model_a")
|
||||
|
||||
elo2 = elo1.copy()
|
||||
|
||||
# Modify elo2
|
||||
elo2.update_ratings("model_a", "model_b", "model_b")
|
||||
|
||||
# elo1 should be unchanged
|
||||
assert elo1.ratings["model_a"] != elo2.ratings["model_a"]
|
||||
|
||||
|
||||
def test_reset():
|
||||
"""Test that reset clears all data."""
|
||||
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
||||
|
||||
elo.update_ratings("model_a", "model_b", "model_a")
|
||||
elo.update_ratings("model_a", "model_c", "model_a")
|
||||
|
||||
assert len(elo.ratings) > 0
|
||||
|
||||
elo.reset()
|
||||
|
||||
assert len(elo.ratings) == 0
|
||||
assert len(elo.match_counts) == 0
|
||||
assert len(elo.win_counts) == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run tests
|
||||
pytest.main([__file__, "-v"])
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Regression: filter_data_parallel must tolerate n_jobs > len(df)."""
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from parallel_processing import filter_data_parallel
|
||||
|
||||
|
||||
def test_n_jobs_larger_than_rows():
|
||||
df = pd.DataFrame({"anony": [True, False, True], "turn": [1, 2, 1]})
|
||||
|
||||
def map_inline(fn, chunks):
|
||||
return [fn(c) for c in chunks]
|
||||
|
||||
pool = MagicMock()
|
||||
pool.__enter__.return_value.map.side_effect = map_inline
|
||||
pool.__exit__.return_value = False
|
||||
|
||||
with patch("parallel_processing.Pool", return_value=pool):
|
||||
out = filter_data_parallel(df, {"anony_only": True}, n_jobs=8)
|
||||
assert len(out) == 2
|
||||
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
Regression test for filter_data on empty input (实验 7-7 排行榜).
|
||||
|
||||
An empty arena data file (e.g. a failed/truncated download saved as `[]`) used to
|
||||
crash with ZeroDivisionError at the "After filtering" percentage print.
|
||||
"""
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from _bootstrap import bootstrap_experiment_root
|
||||
|
||||
bootstrap_experiment_root()
|
||||
|
||||
from data_loader import filter_data
|
||||
|
||||
|
||||
def test_filter_data_tolerates_empty_dataframe():
|
||||
"""Empty input no longer raises ZeroDivisionError; returns an empty DataFrame."""
|
||||
empty = pd.DataFrame({"model_a": [], "model_b": [], "winner": []})
|
||||
result = filter_data(empty)
|
||||
assert len(result) == 0
|
||||
|
||||
|
||||
def test_filter_data_normal_case_unchanged():
|
||||
"""Non-empty input still filters and reports normally."""
|
||||
df = pd.DataFrame({
|
||||
"model_a": ["a", "b", "a"],
|
||||
"model_b": ["b", "a", "c"],
|
||||
"winner": ["model_a", "model_b", "tie"],
|
||||
"anony": [True, True, False],
|
||||
})
|
||||
result = filter_data(df, anony_only=True, use_dedup=False)
|
||||
assert len(result) == 2 # 非匿名的一条被过滤
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Empty battles JSON array [] must load as an empty battle frame."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import cli
|
||||
|
||||
|
||||
def test_load_battles_empty_json_array(tmp_path):
|
||||
path = tmp_path / "battles.json"
|
||||
path.write_text("[]", encoding="utf-8")
|
||||
df = cli._load_battles(str(path))
|
||||
assert list(df.columns) == ["model_a", "model_b", "winner"]
|
||||
assert len(df) == 0
|
||||
|
||||
|
||||
def test_load_battles_nonempty_still_requires_columns(tmp_path):
|
||||
path = tmp_path / "bad.json"
|
||||
path.write_text(json.dumps([{"x": 1}]), encoding="utf-8")
|
||||
try:
|
||||
cli._load_battles(str(path))
|
||||
assert False, "expected ValueError"
|
||||
except ValueError as e:
|
||||
assert "model_a" in str(e)
|
||||
|
||||
|
||||
def test_load_battles_normal(tmp_path):
|
||||
path = tmp_path / "ok.json"
|
||||
path.write_text(
|
||||
json.dumps([{"model_a": "A", "model_b": "B", "winner": "model_a"}]),
|
||||
encoding="utf-8",
|
||||
)
|
||||
df = cli._load_battles(str(path))
|
||||
assert len(df) == 1
|
||||
assert df.iloc[0]["winner"] == "model_a"
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Regression: optimize_dataframe must tolerate empty object columns."""
|
||||
import pandas as pd
|
||||
from parallel_processing import optimize_dataframe
|
||||
|
||||
|
||||
def test_optimize_empty_object_columns():
|
||||
df = pd.DataFrame({
|
||||
"model_a": pd.Series([], dtype=object),
|
||||
"model_b": pd.Series([], dtype=object),
|
||||
"winner": pd.Series([], dtype=object),
|
||||
})
|
||||
out = optimize_dataframe(df)
|
||||
assert len(out) == 0
|
||||
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
Regression test for 'tie (bothbad)' handling in optimized_elo (实验 7-7 排行榜).
|
||||
|
||||
Chatbot Arena battle data has four outcomes; 'tie (bothbad)' was missing from
|
||||
the outcome map, so Series.map produced NaN. NaN then propagated through the
|
||||
rating updates and spread to every model that later faced an affected one,
|
||||
leaving the whole leaderboard NaN.
|
||||
"""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from optimized_elo import NumpyEloRatingSystem
|
||||
|
||||
|
||||
def test_tie_bothbad_does_not_produce_nan_outcomes():
|
||||
"""'tie (bothbad)' maps to a tie instead of NaN."""
|
||||
df = pd.DataFrame({
|
||||
"model_a": ["a", "a"],
|
||||
"model_b": ["b", "b"],
|
||||
"winner": ["model_a", "tie (bothbad)"],
|
||||
})
|
||||
_, _, outcomes = NumpyEloRatingSystem()._prepare_data(df)
|
||||
assert not np.isnan(outcomes).any()
|
||||
assert outcomes[1] == 0.5
|
||||
|
||||
|
||||
def test_tie_bothbad_does_not_poison_the_leaderboard():
|
||||
"""One 'tie (bothbad)' battle used to NaN every rating, including model c."""
|
||||
df = pd.DataFrame({
|
||||
"model_a": ["a", "a", "b"],
|
||||
"model_b": ["b", "b", "c"],
|
||||
"winner": ["model_a", "tie (bothbad)", "model_a"],
|
||||
})
|
||||
system = NumpyEloRatingSystem()
|
||||
system.process_matches_vectorized(df, show_progress=False)
|
||||
ratings = [rating for _, rating, _, _ in system.get_leaderboard()]
|
||||
assert len(ratings) == 3
|
||||
assert not any(np.isnan(r) for r in ratings)
|
||||
|
||||
|
||||
def test_unknown_outcome_falls_back_to_tie():
|
||||
"""An unrecognized label degrades to a tie rather than NaN."""
|
||||
df = pd.DataFrame({
|
||||
"model_a": ["a"],
|
||||
"model_b": ["b"],
|
||||
"winner": ["something_new"],
|
||||
})
|
||||
_, _, outcomes = NumpyEloRatingSystem()._prepare_data(df)
|
||||
assert outcomes[0] == 0.5
|
||||
@@ -0,0 +1,9 @@
|
||||
"""Regression: documented interval='M' must work on modern pandas."""
|
||||
import pandas as pd
|
||||
from data_loader import get_time_slices
|
||||
|
||||
|
||||
def test_monthly_interval_alias():
|
||||
df = pd.DataFrame({"tstamp": [1_700_000_000, 1_710_000_000]})
|
||||
slices = get_time_slices(df, interval="M")
|
||||
assert len(slices) >= 1
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Regression: get_time_slices must not IndexError when the tstamp span is
|
||||
shorter than the requested interval (default weekly).
|
||||
|
||||
Chatbot Arena samples, same-second dumps, and single-row demos all produce an
|
||||
empty pd.date_range for freq='W'; the old code then crashed on date_ranges[-1].
|
||||
"""
|
||||
import pandas as pd
|
||||
|
||||
from _bootstrap import bootstrap_experiment_root
|
||||
|
||||
bootstrap_experiment_root()
|
||||
|
||||
from data_loader import get_time_slices
|
||||
|
||||
|
||||
def test_identical_timestamps_return_one_slice():
|
||||
"""Two battles at the same unix second (weekly interval) -> one slice."""
|
||||
ts = 1_700_000_000
|
||||
df = pd.DataFrame({
|
||||
"tstamp": [ts, ts],
|
||||
"model_a": ["a", "c"],
|
||||
"model_b": ["b", "d"],
|
||||
"winner": ["model_a", "model_b"],
|
||||
})
|
||||
slices = get_time_slices(df, interval="W")
|
||||
assert len(slices) == 1
|
||||
end_date, slice_df = slices[0]
|
||||
assert len(slice_df) == 2
|
||||
assert end_date == pd.to_datetime(ts, unit="s")
|
||||
|
||||
|
||||
def test_empty_dataframe_returns_empty_list():
|
||||
"""Empty input returns [] instead of NaT ValueError."""
|
||||
df = pd.DataFrame({"tstamp": pd.Series(dtype="float64")})
|
||||
assert get_time_slices(df, interval="W") == []
|
||||
|
||||
|
||||
def test_multi_week_span_still_produces_buckets():
|
||||
"""A span covering multiple weeks still yields intermediate buckets."""
|
||||
# ~3 weeks apart
|
||||
df = pd.DataFrame({
|
||||
"tstamp": [1_700_000_000, 1_700_000_000 + 21 * 86400],
|
||||
"model_a": ["a", "c"],
|
||||
"model_b": ["b", "d"],
|
||||
"winner": ["model_a", "model_b"],
|
||||
})
|
||||
slices = get_time_slices(df, interval="W")
|
||||
assert len(slices) >= 2
|
||||
assert all(len(s[1]) > 0 for s in slices)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pytest
|
||||
pytest.main([__file__, "-v"])
|
||||
@@ -0,0 +1,20 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_canonical_manifest_is_hash_complete():
|
||||
run_dir = Path(__file__).resolve().parents[1] / "validation" / "runs" / "exp7-7-arena-20260731-v1"
|
||||
manifest_path = run_dir / "manifest.json"
|
||||
assert manifest_path.exists()
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
assert manifest["experiment"] == "7-7"
|
||||
assert manifest["official_complete"] is True
|
||||
assert all(manifest["gates"].values())
|
||||
assert set(manifest["artifacts"]) >= {
|
||||
"summary.json",
|
||||
"online_elo.json",
|
||||
"bradley_terry.json",
|
||||
"win_rate_matrix.json",
|
||||
"rating_history.json",
|
||||
"leaderboard_animation.html",
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Empty battle DataFrame must not crash Bradley-Terry LogisticRegression."""
|
||||
import pandas as pd
|
||||
|
||||
from bradley_terry import compute_bradley_terry_leaderboard, compute_mle_elo
|
||||
|
||||
|
||||
def test_compute_mle_elo_empty_battles():
|
||||
df = pd.DataFrame(columns=["model_a", "model_b", "winner"])
|
||||
ratings = compute_mle_elo(df)
|
||||
assert isinstance(ratings, pd.Series)
|
||||
assert len(ratings) == 0
|
||||
|
||||
|
||||
def test_compute_bradley_terry_leaderboard_empty():
|
||||
df = pd.DataFrame(columns=["model_a", "model_b", "winner"])
|
||||
board = compute_bradley_terry_leaderboard(df)
|
||||
assert isinstance(board, pd.DataFrame)
|
||||
assert len(board) == 0
|
||||
|
||||
|
||||
def test_nonempty_still_rates():
|
||||
df = pd.DataFrame(
|
||||
[
|
||||
{"model_a": "A", "model_b": "B", "winner": "model_a"},
|
||||
{"model_a": "A", "model_b": "B", "winner": "model_a"},
|
||||
{"model_a": "B", "model_b": "C", "winner": "model_b"},
|
||||
{"model_a": "A", "model_b": "C", "winner": "model_a"},
|
||||
]
|
||||
)
|
||||
ratings = compute_mle_elo(df)
|
||||
assert set(ratings.index) >= {"A", "B", "C"}
|
||||
assert ratings["A"] > ratings["C"]
|
||||
Reference in New Issue
Block a user