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,223 @@
|
||||
"""
|
||||
Data loading and preprocessing for Chatbot Arena voting data
|
||||
"""
|
||||
import pandas as pd
|
||||
import requests
|
||||
import os
|
||||
from typing import Optional
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
def download_arena_data(output_path: str = "arena_data.json", force_download: bool = False) -> str:
|
||||
"""
|
||||
Download Chatbot Arena voting data via HTTPS.
|
||||
|
||||
Args:
|
||||
output_path: Path to save downloaded file
|
||||
force_download: If True, re-download even if file exists
|
||||
|
||||
Returns:
|
||||
Path to downloaded file
|
||||
"""
|
||||
if os.path.exists(output_path) and not force_download:
|
||||
print(f"Data file already exists at {output_path}")
|
||||
file_size = os.path.getsize(output_path) / (1024 * 1024)
|
||||
print(f"File size: {file_size:.2f} MB")
|
||||
return output_path
|
||||
|
||||
print("Downloading Chatbot Arena voting data...")
|
||||
url = "https://storage.googleapis.com/arena_external_data/public/clean_battle_20240814_public.json"
|
||||
|
||||
try:
|
||||
# Stream download with progress bar
|
||||
response = requests.get(url, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
# Get total file size
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
|
||||
# Download with progress bar
|
||||
with open(output_path, 'wb') as f, tqdm(
|
||||
desc=output_path,
|
||||
total=total_size,
|
||||
unit='B',
|
||||
unit_scale=True,
|
||||
unit_divisor=1024,
|
||||
) as pbar:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
pbar.update(len(chunk))
|
||||
|
||||
file_size = os.path.getsize(output_path) / (1024 * 1024)
|
||||
print(f"\nDownloaded data to {output_path} ({file_size:.2f} MB)")
|
||||
return output_path
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error downloading data: {e}")
|
||||
print("Please ensure you have internet connection and the URL is accessible.")
|
||||
raise
|
||||
|
||||
|
||||
def load_arena_data(filepath: str) -> pd.DataFrame:
|
||||
"""
|
||||
Load and preprocess Chatbot Arena voting data.
|
||||
|
||||
Expected columns:
|
||||
- model_a: Identifier for first model
|
||||
- model_b: Identifier for second model
|
||||
- winner: Which model won ('model_a', 'model_b', or 'tie')
|
||||
- tstamp: Unix timestamp of the vote
|
||||
- judge: User who made the vote
|
||||
- turn: Conversation turn
|
||||
- anony: Whether vote was anonymous (blind)
|
||||
- language: Language of the conversation
|
||||
|
||||
Args:
|
||||
filepath: Path to data file
|
||||
|
||||
Returns:
|
||||
Preprocessed DataFrame sorted by timestamp
|
||||
"""
|
||||
print(f"Loading data from {filepath}...")
|
||||
print("Note: This is a large file (~2GB), loading may take 1-2 minutes...")
|
||||
|
||||
# Try different file formats
|
||||
if filepath.endswith('.json'):
|
||||
df = pd.read_json(filepath)
|
||||
elif filepath.endswith('.jsonl'):
|
||||
df = pd.read_json(filepath, lines=True)
|
||||
elif filepath.endswith('.csv'):
|
||||
df = pd.read_csv(filepath)
|
||||
else:
|
||||
# Try JSON by default
|
||||
try:
|
||||
df = pd.read_json(filepath)
|
||||
except (ValueError, KeyError):
|
||||
df = pd.read_json(filepath, lines=True)
|
||||
|
||||
print(f"Loaded {len(df)} records")
|
||||
print(f"Columns: {df.columns.tolist()}")
|
||||
|
||||
# Sort by timestamp
|
||||
if 'tstamp' in df.columns:
|
||||
df = df.sort_values('tstamp', ascending=True).reset_index(drop=True)
|
||||
print(f"Data spans from {pd.to_datetime(df['tstamp'].min(), unit='s')} to {pd.to_datetime(df['tstamp'].max(), unit='s')}")
|
||||
|
||||
# Basic statistics
|
||||
if 'winner' in df.columns:
|
||||
print(f"\nOutcome distribution:")
|
||||
print(df['winner'].value_counts())
|
||||
|
||||
if 'model_a' in df.columns and 'model_b' in df.columns:
|
||||
all_models = set(df['model_a'].unique()) | set(df['model_b'].unique())
|
||||
print(f"\nTotal unique models: {len(all_models)}")
|
||||
print(f"Top 10 models by appearance:")
|
||||
model_counts = pd.concat([df['model_a'], df['model_b']]).value_counts().head(10)
|
||||
print(model_counts)
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def filter_data(df: pd.DataFrame,
|
||||
min_date: Optional[str] = None,
|
||||
max_date: Optional[str] = None,
|
||||
anony_only: bool = True,
|
||||
language: Optional[str] = None,
|
||||
min_turn: int = 1,
|
||||
use_dedup: bool = True) -> pd.DataFrame:
|
||||
"""
|
||||
Filter voting data based on various criteria (following official Chatbot Arena method).
|
||||
|
||||
Args:
|
||||
df: Input DataFrame
|
||||
min_date: Minimum date (YYYY-MM-DD format)
|
||||
max_date: Maximum date (YYYY-MM-DD format)
|
||||
anony_only: If True, only include anonymous (blind) votes
|
||||
language: If specified, filter by language
|
||||
min_turn: Minimum conversation turn
|
||||
use_dedup: If True, apply deduplication filter (official Arena method)
|
||||
|
||||
Returns:
|
||||
Filtered DataFrame
|
||||
"""
|
||||
filtered = df.copy()
|
||||
|
||||
print(f"Before filtering: {len(filtered)} records")
|
||||
|
||||
# Filter by anonymous votes only (official method)
|
||||
if anony_only and 'anony' in filtered.columns:
|
||||
filtered = filtered[filtered['anony'] == True]
|
||||
print(f" After anony filter: {len(filtered)} records")
|
||||
|
||||
# Apply deduplication (official method removes top 0.1% redundant prompts)
|
||||
if use_dedup and 'dedup_tag' in filtered.columns:
|
||||
try:
|
||||
filtered = filtered[filtered["dedup_tag"].apply(lambda x: x.get("sampled", False) if isinstance(x, dict) else False)]
|
||||
print(f" After dedup filter: {len(filtered)} records")
|
||||
except Exception as e:
|
||||
print(f" Warning: Could not apply dedup filter: {e}")
|
||||
|
||||
# Filter by date
|
||||
if 'tstamp' in filtered.columns:
|
||||
if min_date:
|
||||
min_timestamp = pd.to_datetime(min_date).timestamp()
|
||||
filtered = filtered[filtered['tstamp'] >= min_timestamp]
|
||||
if max_date:
|
||||
max_timestamp = pd.to_datetime(max_date).timestamp()
|
||||
filtered = filtered[filtered['tstamp'] <= max_timestamp]
|
||||
|
||||
# Filter by language
|
||||
if language and 'language' in filtered.columns:
|
||||
filtered = filtered[filtered['language'] == language]
|
||||
|
||||
# Filter by turn
|
||||
if 'turn' in filtered.columns:
|
||||
filtered = filtered[filtered['turn'] >= min_turn]
|
||||
|
||||
pct = (len(filtered) / len(df) * 100) if len(df) else 0.0
|
||||
print(f"After filtering: {len(filtered)} records ({pct:.1f}% of original)")
|
||||
return filtered.reset_index(drop=True)
|
||||
|
||||
|
||||
def get_time_slices(df: pd.DataFrame, interval: str = 'W') -> list:
|
||||
"""
|
||||
Split data into time slices for historical analysis.
|
||||
|
||||
Args:
|
||||
df: Input DataFrame with 'tstamp' column
|
||||
interval: Pandas frequency string ('D' for daily, 'W' for weekly, 'M' for monthly)
|
||||
|
||||
Returns:
|
||||
List of (end_date, dataframe_slice) tuples
|
||||
"""
|
||||
if 'tstamp' not in df.columns:
|
||||
raise ValueError("DataFrame must have 'tstamp' column")
|
||||
|
||||
if len(df) == 0:
|
||||
print(f"Created 0 time slices with interval '{interval}'")
|
||||
return []
|
||||
|
||||
df['datetime'] = pd.to_datetime(df['tstamp'], unit='s')
|
||||
min_date = df['datetime'].min()
|
||||
max_date = df['datetime'].max()
|
||||
|
||||
# Pandas 2.2+ removed 'M' (month-end); keep the documented monthly alias.
|
||||
freq = "ME" if interval == "M" else interval
|
||||
|
||||
# Generate date ranges
|
||||
date_ranges = pd.date_range(start=min_date, end=max_date, freq=freq)
|
||||
|
||||
slices = []
|
||||
for end_date in date_ranges:
|
||||
slice_df = df[df['datetime'] <= end_date].copy()
|
||||
if len(slice_df) > 0:
|
||||
slices.append((end_date, slice_df))
|
||||
|
||||
# Empty date_ranges when span < interval; also cover trailing gap to max_date.
|
||||
if len(date_ranges) == 0 or date_ranges[-1] < max_date:
|
||||
slices.append((max_date, df.copy()))
|
||||
|
||||
print(f"Created {len(slices)} time slices with interval '{interval}'")
|
||||
return slices
|
||||
|
||||
Reference in New Issue
Block a user