Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Commit ·
13c9630
1
Parent(s): 0bdb50e
Remove eval/ from tracking and add to gitignore
Browse files- .gitignore +3 -0
- eval/README.md +0 -100
- eval/__init__.py +0 -3
- eval/check_completeness.py +0 -164
- eval/claude_batch_solve.py +0 -230
- eval/create_eval_dataset.py +0 -160
- eval/eval_set.ipynb +0 -0
- eval/generate_rubrics.py +0 -403
- eval/generated_tasks_with_difficulty.json +0 -255
- eval/hf_agent_connector.py +0 -89
- eval/hf_io.py +0 -215
- eval/leaderboard.py +0 -172
- eval/models.py +0 -63
- eval/rubric_eval.py +0 -142
- eval/run_eval_with_leaderboard.py +0 -215
- eval/scrape_discussions/discussions_scraper.py +0 -98
- eval/solvers.py +0 -165
- eval/task.py +0 -121
.gitignore
CHANGED
|
@@ -52,6 +52,9 @@ frontend/yarn-error.log*
|
|
| 52 |
# Docker
|
| 53 |
.docker/
|
| 54 |
|
|
|
|
|
|
|
|
|
|
| 55 |
# Project-specific
|
| 56 |
session_logs/
|
| 57 |
/logs
|
|
|
|
| 52 |
# Docker
|
| 53 |
.docker/
|
| 54 |
|
| 55 |
+
# Eval (stale)
|
| 56 |
+
eval/
|
| 57 |
+
|
| 58 |
# Project-specific
|
| 59 |
session_logs/
|
| 60 |
/logs
|
eval/README.md
DELETED
|
@@ -1,100 +0,0 @@
|
|
| 1 |
-
# HF-Agent Eval
|
| 2 |
-
|
| 3 |
-
Rubric-based evaluation pipeline implementing [Rubrics as Rewards](https://arxiv.org/abs/2507.17746) paper (RaR-Explicit formula).
|
| 4 |
-
|
| 5 |
-
## Components
|
| 6 |
-
|
| 7 |
-
| Component | Purpose | Long Term Goal |
|
| 8 |
-
|-----------|---------|----------------|
|
| 9 |
-
| **`generate_rubrics.py`** | Generates instance-specific evaluation criteria (7-20 weighted rubrics) from QA pairs using LLM, following the RaR paper methodology | Improve rubric quality with few-shot examples, domain-specific templates, and iterative refinement |
|
| 10 |
-
| **`rubric_eval.py`** | Scores responses using RaR-Explicit formula: checks each criterion independently via LLM judge, computes weighted normalized score | Support batch evaluation, caching, and alternative scoring formulas (RaR-Holistic) |
|
| 11 |
-
| **`task.py`** | Defines Inspect AI task `hf-benchmark-with-rubrics` that wires dataset, solver, and rubric scorer into a single evaluation pipeline | Add more task variants for different benchmarks (code generation, tool use, multi-turn) |
|
| 12 |
-
| **`solvers.py`** | Registry of solver implementations (`hf_agent`, `claude_code`, `claude_code+hf_mcp`) that can be swapped via CLI args | Expand solver library to benchmark more agents (OpenAI Codex, Gemini, open-source agents) |
|
| 13 |
-
| **`hf_agent_connector.py`** | Lightweight bridge that spins up the hf-agent stack (tools, MCP, LiteLLM loop) and returns the final assistant response | Enable streaming, intermediate step logging, and cost tracking per evaluation |
|
| 14 |
-
| **`leaderboard.py`** | Utilities to build records and append scores to a HuggingFace dataset for tracking performance over time | Add score breakdowns, visualizations, and automatic regression detection |
|
| 15 |
-
| **`run_eval_with_leaderboard.py`** | CLI wrapper that runs `inspect eval`, parses scores from logs, and pushes results to the leaderboard dataset | Support scheduled CI runs, PR-gated benchmarks, and multi-dataset aggregation |
|
| 16 |
-
| **`hf_io.py`** | Helper utilities for pushing DataFrames to HuggingFace Hub | Extend with dataset versioning and diff tracking |
|
| 17 |
-
| **`models.py`** | Shared Pydantic models for evaluation data structures | Centralize all eval schemas for consistency across components |
|
| 18 |
-
|
| 19 |
-
## Pipeline
|
| 20 |
-
|
| 21 |
-
```
|
| 22 |
-
QA pairs → generate_rubrics.py → run `inspect-ai eval eval/task.py@hf-benchmark-with-rubrics` → scores
|
| 23 |
-
```
|
| 24 |
-
|
| 25 |
-
### 1. Generate Rubrics (if not already generated)
|
| 26 |
-
|
| 27 |
-
Creates instance-specific evaluation criteria from question + reference answer.
|
| 28 |
-
|
| 29 |
-
```bash
|
| 30 |
-
python eval/generate_rubrics.py \
|
| 31 |
-
--infile qa_pairs.jsonl \
|
| 32 |
-
--outfile qa_rubrics.jsonl \
|
| 33 |
-
--model anthropic/claude-sonnet-4-5-20250929 \
|
| 34 |
-
--push-to-hub akseljoonas/hf-agent-benchmark@rubrics
|
| 35 |
-
```
|
| 36 |
-
|
| 37 |
-
**Input format:**
|
| 38 |
-
```json
|
| 39 |
-
{"question": "...", "solution": "...", "thread": [...]}
|
| 40 |
-
```
|
| 41 |
-
|
| 42 |
-
**Output:** 7-20 weighted criteria per question (Essential: +5, Important: +3-4, Optional: +1-2, Pitfall: -1 to -2)
|
| 43 |
-
|
| 44 |
-
### 2. Response evaluation
|
| 45 |
-
|
| 46 |
-
Files:
|
| 47 |
-
- `eval/hf_agent_connector.py` contains a lightweight bridge that spins up
|
| 48 |
-
the existing hf-agent stack in `agent/` (tools, MCP, LiteLLM loop) and returns the assistant reply.
|
| 49 |
-
- `eval/solvers.py` keeps the solver implementations (e.g. `hf_agent`,
|
| 50 |
-
`claude_code`). If additional solvers are needed, register them there and pass
|
| 51 |
-
`-T solver_name=<name>` to swap them in without touching the task.
|
| 52 |
-
- `eval/task.py` registers `hf-benchmark-with-rubrics`, which wires
|
| 53 |
-
the dataset, solver, and rubric scorer into a single Inspect task and does the eval.
|
| 54 |
-
|
| 55 |
-
### Running the hf-agent (implemented in `agent/`) (args are optional)
|
| 56 |
-
```bash
|
| 57 |
-
uv run inspect eval eval/task.py@hf-benchmark-with-rubrics \
|
| 58 |
-
-T dataset_name=akseljoonas/hf-agent-rubrics \
|
| 59 |
-
-T dataset_split=train \
|
| 60 |
-
-T limit=25 \
|
| 61 |
-
-T solver_name=hf_agent \
|
| 62 |
-
-T solver_kwargs='{"config_path":"agent/config_mcp_example.json","max_iterations":10}' \
|
| 63 |
-
--log-dir logs/inspect
|
| 64 |
-
```
|
| 65 |
-
|
| 66 |
-
Different benchmarks can be used by making/running a new task in `eval/task.py`.
|
| 67 |
-
|
| 68 |
-
### Running Claude Code headlessly
|
| 69 |
-
|
| 70 |
-
The `claude_code` solver shell-outs to the `claude` CLI (`claude -p ... --output-format json`)
|
| 71 |
-
so you can benchmark Claude Code without any interactive UI. Example:
|
| 72 |
-
|
| 73 |
-
Claude Code command example (kwargs are optional):
|
| 74 |
-
```bash
|
| 75 |
-
uv run inspect eval eval/task.py@hf-benchmark-with-rubrics \
|
| 76 |
-
-T solver_name=claude_code \
|
| 77 |
-
-T solver_kwargs='{"allowed_tools":"Bash,Read","output_format":"json"}'
|
| 78 |
-
```
|
| 79 |
-
|
| 80 |
-
### Leaderboard
|
| 81 |
-
|
| 82 |
-
Scores can be pushed to a Hugging Face dataset automatically by wrapping the run
|
| 83 |
-
with `eval/run_eval_with_leaderboard.py` (it executes `inspect eval ...` under the hood
|
| 84 |
-
and only appends results when the command succeeds):
|
| 85 |
-
|
| 86 |
-
```bash
|
| 87 |
-
uv run python eval/run_eval_with_leaderboard.py \
|
| 88 |
-
--hf-dataset akseljoonas/hf-agent-leaderboard \
|
| 89 |
-
--hf-token $HF_TOKEN \
|
| 90 |
-
--solver-name hf_agent \
|
| 91 |
-
--solver-kwargs '{"config_path":"agent/config_mcp_example.json","max_iterations":10}' \
|
| 92 |
-
--dataset akseljoonas/hf-agent-rubrics@train \
|
| 93 |
-
--limit 25
|
| 94 |
-
```
|
| 95 |
-
|
| 96 |
-
## Scoring (implemented in `eval/rubric_eval.py`)
|
| 97 |
-
|
| 98 |
-
The scoring is implemented in `eval/rubric_eval.py` and is based on the RaR-Explicit formula: `score = Σ(weight × satisfied) / Σ(positive_weights)`.
|
| 99 |
-
|
| 100 |
-
The score is normalized to [0, 1] and clipped if pitfalls make it negative.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/__init__.py
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
from eval.task import hf_benchmark_with_rubrics
|
| 2 |
-
|
| 3 |
-
__all__ = ["hf_benchmark_with_rubrics"]
|
|
|
|
|
|
|
|
|
|
|
|
eval/check_completeness.py
DELETED
|
@@ -1,164 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Minimal script to check if tasks in solved_tasks.jsonl were fully completed and verified.
|
| 4 |
-
Uses an LLM to assess completion status and adds the result to each row.
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import argparse
|
| 8 |
-
import json
|
| 9 |
-
import sys
|
| 10 |
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 11 |
-
|
| 12 |
-
import litellm
|
| 13 |
-
from dotenv import load_dotenv
|
| 14 |
-
from pydantic import BaseModel
|
| 15 |
-
|
| 16 |
-
load_dotenv()
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
class CompletionCheck(BaseModel):
|
| 20 |
-
reasoning: str
|
| 21 |
-
completed: bool
|
| 22 |
-
verified: bool
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
PROMPT = """You are evaluating whether an AI agent fully completed a task AND verified its completion.
|
| 26 |
-
|
| 27 |
-
Task: {question}
|
| 28 |
-
|
| 29 |
-
Agent's final answer: {solution}
|
| 30 |
-
|
| 31 |
-
Agent's trace (tool calls and responses):
|
| 32 |
-
{trace}
|
| 33 |
-
|
| 34 |
-
Evaluate:
|
| 35 |
-
1. **completed**: Did the agent actually complete the task? (not just explain what could be done, but actually do it)
|
| 36 |
-
2. **verified**: Did the agent verify/confirm that the task was completed correctly? (e.g., checked output, validated results, confirmed success)
|
| 37 |
-
|
| 38 |
-
Be strict:
|
| 39 |
-
- If the agent asked for more information or said "please provide...", it's NOT completed.
|
| 40 |
-
- If the agent only explained how to do something but didn't do it, it's NOT completed.
|
| 41 |
-
- If the agent just made a plan of how to complete it but didn't do it, it's NOT completed.
|
| 42 |
-
- If there's an error in the trace and no recovery, it's NOT completed.
|
| 43 |
-
- If the agent didn't check/confirm the code/command completed succesfully or the result is correct somehow, it's NOT verified.
|
| 44 |
-
|
| 45 |
-
Return JSON with: completed (bool), verified (bool), reasoning (brief explanation)."""
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
def format_trace(messages: list) -> str:
|
| 49 |
-
"""Format messages trace for the prompt."""
|
| 50 |
-
if not messages:
|
| 51 |
-
return "(No trace)"
|
| 52 |
-
|
| 53 |
-
parts = []
|
| 54 |
-
for msg in messages:
|
| 55 |
-
role = msg.get("role", "unknown")
|
| 56 |
-
if role == "system":
|
| 57 |
-
continue
|
| 58 |
-
|
| 59 |
-
content = msg.get("content", "")
|
| 60 |
-
tool_calls = msg.get("tool_calls", [])
|
| 61 |
-
|
| 62 |
-
if tool_calls:
|
| 63 |
-
for tc in tool_calls:
|
| 64 |
-
if isinstance(tc, dict) and "function" in tc:
|
| 65 |
-
name = tc["function"].get("name", "?")
|
| 66 |
-
parts.append(f"[TOOL CALL] {name}")
|
| 67 |
-
|
| 68 |
-
if content:
|
| 69 |
-
# Truncate long content
|
| 70 |
-
if len(content) > 5000:
|
| 71 |
-
content = content[:4000] + "..." + content[-1000:]
|
| 72 |
-
parts.append(f"[{role.upper()}] {content}")
|
| 73 |
-
|
| 74 |
-
return "\n".join(parts) if parts else "(Empty trace)"
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
def check_row(row: dict, model: str) -> CompletionCheck | None:
|
| 78 |
-
"""Check if a single task was completed and verified."""
|
| 79 |
-
prompt = PROMPT.format(
|
| 80 |
-
question=row["question"],
|
| 81 |
-
solution=row.get("solution", "(No solution)"),
|
| 82 |
-
trace=format_trace(row.get("messages", [])),
|
| 83 |
-
)
|
| 84 |
-
|
| 85 |
-
try:
|
| 86 |
-
response = litellm.completion(
|
| 87 |
-
model=model,
|
| 88 |
-
messages=[{"role": "user", "content": prompt}],
|
| 89 |
-
response_format=CompletionCheck,
|
| 90 |
-
timeout=60,
|
| 91 |
-
)
|
| 92 |
-
return CompletionCheck.model_validate_json(response.choices[0].message.content)
|
| 93 |
-
except Exception as e:
|
| 94 |
-
print(f"Error: {e}", file=sys.stderr)
|
| 95 |
-
return None
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
def main():
|
| 99 |
-
parser = argparse.ArgumentParser(description="Check task completion status")
|
| 100 |
-
parser.add_argument("--infile", type=str, default="eval/solved_tasks.jsonl")
|
| 101 |
-
parser.add_argument(
|
| 102 |
-
"--outfile", type=str, default="eval/solved_tasks_checked.jsonl"
|
| 103 |
-
)
|
| 104 |
-
parser.add_argument(
|
| 105 |
-
"--model", type=str, default="anthropic/claude-sonnet-4-5-20250929"
|
| 106 |
-
)
|
| 107 |
-
parser.add_argument("--max-concurrent", type=int, default=30)
|
| 108 |
-
args = parser.parse_args()
|
| 109 |
-
|
| 110 |
-
# Load data
|
| 111 |
-
print(f"Loading {args.infile}...")
|
| 112 |
-
rows = []
|
| 113 |
-
with open(args.infile) as f:
|
| 114 |
-
for line in f:
|
| 115 |
-
rows.append(json.loads(line))
|
| 116 |
-
print(f"Loaded {len(rows)} rows")
|
| 117 |
-
|
| 118 |
-
# Process in parallel
|
| 119 |
-
print(f"Checking completion with {args.model}...")
|
| 120 |
-
with ThreadPoolExecutor(max_workers=args.max_concurrent) as executor:
|
| 121 |
-
futures = {
|
| 122 |
-
executor.submit(check_row, row, args.model): i for i, row in enumerate(rows)
|
| 123 |
-
}
|
| 124 |
-
results = [None] * len(rows)
|
| 125 |
-
|
| 126 |
-
for future in as_completed(futures):
|
| 127 |
-
idx = futures[future]
|
| 128 |
-
results[idx] = future.result()
|
| 129 |
-
print(
|
| 130 |
-
f"Done: {sum(1 for r in results if r is not None)}/{len(rows)}",
|
| 131 |
-
end="\r",
|
| 132 |
-
)
|
| 133 |
-
|
| 134 |
-
print()
|
| 135 |
-
|
| 136 |
-
# Merge results
|
| 137 |
-
output_rows = []
|
| 138 |
-
for row, result in zip(rows, results):
|
| 139 |
-
if result:
|
| 140 |
-
row["task_completed"] = result.completed
|
| 141 |
-
row["task_verified"] = result.verified
|
| 142 |
-
row["completion_reasoning"] = result.reasoning
|
| 143 |
-
else:
|
| 144 |
-
row["task_completed"] = None
|
| 145 |
-
row["task_verified"] = None
|
| 146 |
-
row["completion_reasoning"] = "Error during check"
|
| 147 |
-
output_rows.append(row)
|
| 148 |
-
|
| 149 |
-
# Write output
|
| 150 |
-
print(f"Writing to {args.outfile}...")
|
| 151 |
-
with open(args.outfile, "w") as f:
|
| 152 |
-
for row in output_rows:
|
| 153 |
-
f.write(json.dumps(row, default=str) + "\n")
|
| 154 |
-
|
| 155 |
-
# Summary
|
| 156 |
-
completed = sum(1 for r in results if r and r.completed)
|
| 157 |
-
verified = sum(1 for r in results if r and r.verified)
|
| 158 |
-
print("\nSummary:")
|
| 159 |
-
print(f" Completed: {completed}/{len(rows)}")
|
| 160 |
-
print(f" Verified: {verified}/{len(rows)}")
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
if __name__ == "__main__":
|
| 164 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/claude_batch_solve.py
DELETED
|
@@ -1,230 +0,0 @@
|
|
| 1 |
-
import asyncio
|
| 2 |
-
import json
|
| 3 |
-
import os
|
| 4 |
-
import threading
|
| 5 |
-
from pathlib import Path
|
| 6 |
-
from typing import Any
|
| 7 |
-
|
| 8 |
-
from claude_agent_sdk import (
|
| 9 |
-
AssistantMessage,
|
| 10 |
-
ClaudeAgentOptions,
|
| 11 |
-
ResultMessage,
|
| 12 |
-
SystemMessage,
|
| 13 |
-
TextBlock,
|
| 14 |
-
ToolResultBlock,
|
| 15 |
-
ToolUseBlock,
|
| 16 |
-
UserMessage,
|
| 17 |
-
query,
|
| 18 |
-
)
|
| 19 |
-
from dotenv import load_dotenv
|
| 20 |
-
|
| 21 |
-
load_dotenv()
|
| 22 |
-
|
| 23 |
-
# Thread-safe file writing
|
| 24 |
-
file_lock = threading.Lock()
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
def convert_message_to_chat_format(message: Any) -> dict | None:
|
| 28 |
-
"""Convert SDK message to standard chat format with role/content/tool_calls."""
|
| 29 |
-
|
| 30 |
-
if isinstance(message, SystemMessage):
|
| 31 |
-
# Extract tools list from init data for system message
|
| 32 |
-
if message.subtype == "init":
|
| 33 |
-
tools = message.data.get("tools", [])
|
| 34 |
-
tools_desc = "\n".join(f"- {tool}" for tool in tools)
|
| 35 |
-
return {
|
| 36 |
-
"role": "system",
|
| 37 |
-
"content": f"You are a helpful assistant with access to the following tools:\n{tools_desc}",
|
| 38 |
-
}
|
| 39 |
-
return None
|
| 40 |
-
|
| 41 |
-
elif isinstance(message, AssistantMessage):
|
| 42 |
-
text_content = ""
|
| 43 |
-
tool_calls = []
|
| 44 |
-
|
| 45 |
-
for block in message.content:
|
| 46 |
-
if isinstance(block, TextBlock):
|
| 47 |
-
text_content += block.text
|
| 48 |
-
elif isinstance(block, ToolUseBlock):
|
| 49 |
-
tool_calls.append(
|
| 50 |
-
{
|
| 51 |
-
"id": block.id,
|
| 52 |
-
"function": {
|
| 53 |
-
"name": block.name,
|
| 54 |
-
"arguments": block.input,
|
| 55 |
-
},
|
| 56 |
-
}
|
| 57 |
-
)
|
| 58 |
-
|
| 59 |
-
result = {"role": "assistant", "content": text_content}
|
| 60 |
-
if tool_calls:
|
| 61 |
-
result["tool_calls"] = tool_calls
|
| 62 |
-
return result
|
| 63 |
-
|
| 64 |
-
elif isinstance(message, UserMessage):
|
| 65 |
-
# UserMessage can contain tool results or text
|
| 66 |
-
if isinstance(message.content, str):
|
| 67 |
-
return {"role": "user", "content": message.content}
|
| 68 |
-
elif isinstance(message.content, list):
|
| 69 |
-
# Check for tool results
|
| 70 |
-
tool_results = []
|
| 71 |
-
text_content = ""
|
| 72 |
-
for block in message.content:
|
| 73 |
-
if isinstance(block, ToolResultBlock):
|
| 74 |
-
# Format tool result content
|
| 75 |
-
if isinstance(block.content, str):
|
| 76 |
-
content = block.content
|
| 77 |
-
elif isinstance(block.content, list):
|
| 78 |
-
content = json.dumps(block.content)
|
| 79 |
-
else:
|
| 80 |
-
content = str(block.content) if block.content else ""
|
| 81 |
-
|
| 82 |
-
tool_results.append(
|
| 83 |
-
{
|
| 84 |
-
"tool_use_id": block.tool_use_id,
|
| 85 |
-
"content": content,
|
| 86 |
-
"is_error": block.is_error,
|
| 87 |
-
}
|
| 88 |
-
)
|
| 89 |
-
elif isinstance(block, TextBlock):
|
| 90 |
-
text_content += block.text
|
| 91 |
-
|
| 92 |
-
if tool_results:
|
| 93 |
-
return {
|
| 94 |
-
"role": "user",
|
| 95 |
-
"content": f"<tool_response>\n{json.dumps(tool_results, indent=2)}\n</tool_response>",
|
| 96 |
-
}
|
| 97 |
-
else:
|
| 98 |
-
return {"role": "user", "content": text_content}
|
| 99 |
-
return None
|
| 100 |
-
|
| 101 |
-
elif isinstance(message, ResultMessage):
|
| 102 |
-
# ResultMessage is metadata, not a conversation message
|
| 103 |
-
return None
|
| 104 |
-
|
| 105 |
-
return None
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
async def solve_task(
|
| 109 |
-
question: str,
|
| 110 |
-
difficulty: str,
|
| 111 |
-
task_idx: int,
|
| 112 |
-
total: int,
|
| 113 |
-
semaphore: asyncio.Semaphore,
|
| 114 |
-
) -> dict:
|
| 115 |
-
"""Solve a single task using Claude Agent SDK."""
|
| 116 |
-
async with semaphore:
|
| 117 |
-
print(f"[{task_idx}/{total}] Starting: {question[:60]}...")
|
| 118 |
-
|
| 119 |
-
messages = []
|
| 120 |
-
solution = None
|
| 121 |
-
|
| 122 |
-
try:
|
| 123 |
-
async for message in query(
|
| 124 |
-
prompt=question,
|
| 125 |
-
options=ClaudeAgentOptions(
|
| 126 |
-
cwd=os.getcwd(),
|
| 127 |
-
permission_mode="bypassPermissions",
|
| 128 |
-
disallowed_tools=["Write", "Edit", "Bash", "Glob", "Grep"],
|
| 129 |
-
mcp_servers={
|
| 130 |
-
"huggingface": {
|
| 131 |
-
"type": "http",
|
| 132 |
-
"url": "https://huggingface.co/mcp",
|
| 133 |
-
"headers": {
|
| 134 |
-
"Authorization": f"Bearer {os.environ['HF_TOKEN']}"
|
| 135 |
-
},
|
| 136 |
-
}
|
| 137 |
-
},
|
| 138 |
-
),
|
| 139 |
-
):
|
| 140 |
-
# Convert to chat format and append if valid
|
| 141 |
-
chat_msg = convert_message_to_chat_format(message)
|
| 142 |
-
if chat_msg:
|
| 143 |
-
messages.append(chat_msg)
|
| 144 |
-
|
| 145 |
-
# Extract text from assistant messages
|
| 146 |
-
if isinstance(message, AssistantMessage):
|
| 147 |
-
for block in message.content:
|
| 148 |
-
if isinstance(block, TextBlock):
|
| 149 |
-
solution = block.text
|
| 150 |
-
# Check for result messages
|
| 151 |
-
elif isinstance(message, ResultMessage):
|
| 152 |
-
if message.is_error:
|
| 153 |
-
print(f"[{task_idx}/{total}] ✗ Agent error: {message.subtype}")
|
| 154 |
-
return {
|
| 155 |
-
"question": question,
|
| 156 |
-
"difficulty": difficulty,
|
| 157 |
-
"solution": None,
|
| 158 |
-
"messages": messages,
|
| 159 |
-
"error": f"Agent error: {message.subtype}",
|
| 160 |
-
}
|
| 161 |
-
elif message.result:
|
| 162 |
-
solution = message.result
|
| 163 |
-
|
| 164 |
-
print(f"[{task_idx}/{total}] ✓ Done: {question[:60]}...")
|
| 165 |
-
return {
|
| 166 |
-
"question": question,
|
| 167 |
-
"difficulty": difficulty,
|
| 168 |
-
"solution": solution,
|
| 169 |
-
"messages": messages,
|
| 170 |
-
"error": None,
|
| 171 |
-
}
|
| 172 |
-
except Exception as e:
|
| 173 |
-
print(f"[{task_idx}/{total}] ✗ Error: {e}")
|
| 174 |
-
return {
|
| 175 |
-
"question": question,
|
| 176 |
-
"difficulty": difficulty,
|
| 177 |
-
"solution": None,
|
| 178 |
-
"messages": messages,
|
| 179 |
-
"error": str(e),
|
| 180 |
-
}
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
def write_result(output_path: Path, result: dict):
|
| 184 |
-
"""Thread-safe write to output file."""
|
| 185 |
-
with file_lock:
|
| 186 |
-
with open(output_path, "a") as f:
|
| 187 |
-
f.write(json.dumps(result) + "\n")
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
async def main():
|
| 191 |
-
# Load tasks from filled_tasks.jsonl
|
| 192 |
-
tasks_path = Path(__file__).parent / "filled_tasks.jsonl"
|
| 193 |
-
tasks = []
|
| 194 |
-
with open(tasks_path) as f:
|
| 195 |
-
for line in f:
|
| 196 |
-
tasks.append(json.loads(line))
|
| 197 |
-
|
| 198 |
-
# Output file - clear it first
|
| 199 |
-
output_path = Path(__file__).parent / "solved_tasks.jsonl"
|
| 200 |
-
output_path.write_text("")
|
| 201 |
-
|
| 202 |
-
# Semaphore to limit concurrency
|
| 203 |
-
max_concurrent = 5
|
| 204 |
-
semaphore = asyncio.Semaphore(max_concurrent)
|
| 205 |
-
|
| 206 |
-
total = len(tasks)
|
| 207 |
-
print(f"Processing {total} tasks with {max_concurrent} concurrent agents...")
|
| 208 |
-
|
| 209 |
-
async def process_and_save(task: dict, idx: int):
|
| 210 |
-
result = await solve_task(
|
| 211 |
-
task["question"], task["difficulty"], idx, total, semaphore
|
| 212 |
-
)
|
| 213 |
-
write_result(output_path, result)
|
| 214 |
-
return result
|
| 215 |
-
|
| 216 |
-
# Create all tasks
|
| 217 |
-
coroutines = [process_and_save(task, i + 1) for i, task in enumerate(tasks)]
|
| 218 |
-
|
| 219 |
-
# Run all concurrently (semaphore limits actual parallelism)
|
| 220 |
-
results = await asyncio.gather(*coroutines, return_exceptions=True)
|
| 221 |
-
|
| 222 |
-
successful = sum(
|
| 223 |
-
1 for r in results if isinstance(r, dict) and r.get("error") is None
|
| 224 |
-
)
|
| 225 |
-
print(f"\nCompleted: {successful}/{total} successful")
|
| 226 |
-
print(f"Results saved to {output_path}")
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
if __name__ == "__main__":
|
| 230 |
-
asyncio.run(main())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/create_eval_dataset.py
DELETED
|
@@ -1,160 +0,0 @@
|
|
| 1 |
-
from itertools import product
|
| 2 |
-
|
| 3 |
-
from datasets import Dataset
|
| 4 |
-
|
| 5 |
-
# Task templates (excluding Very hard difficulty)
|
| 6 |
-
tasks = [
|
| 7 |
-
{
|
| 8 |
-
"task": "Evaluate models {M} on benchmarks {B}",
|
| 9 |
-
"difficulty": "Easy",
|
| 10 |
-
"category": "Evaluation",
|
| 11 |
-
"params": ["M", "B"],
|
| 12 |
-
},
|
| 13 |
-
{
|
| 14 |
-
"task": "Train models {M} on datasets {D} evaluating them on benchmarks {B}",
|
| 15 |
-
"difficulty": "Medium",
|
| 16 |
-
"category": "Training",
|
| 17 |
-
"params": ["M", "D", "B"],
|
| 18 |
-
},
|
| 19 |
-
{
|
| 20 |
-
"task": "Run an ablation for hyperparameter {P} for model {M} on dataset {D}",
|
| 21 |
-
"difficulty": "Hard",
|
| 22 |
-
"category": "Ablation",
|
| 23 |
-
"params": ["P", "M", "D"],
|
| 24 |
-
},
|
| 25 |
-
{
|
| 26 |
-
"task": "Generate completions with model {M} on benchmarks {B} using engine {E}",
|
| 27 |
-
"difficulty": "Medium",
|
| 28 |
-
"category": "Generation",
|
| 29 |
-
"params": ["M", "B", "E"],
|
| 30 |
-
},
|
| 31 |
-
# {
|
| 32 |
-
# "task": "Merge models {M} using linear averaging to find the best result on benchmarks {B}",
|
| 33 |
-
# "difficulty": "Hard",
|
| 34 |
-
# "category": "Model Merging",
|
| 35 |
-
# "params": ["M", "B"],
|
| 36 |
-
# },
|
| 37 |
-
{
|
| 38 |
-
"task": "Decontaminate dataset {D} against benchmarks {B}",
|
| 39 |
-
"difficulty": "Hard",
|
| 40 |
-
"category": "Data Processing",
|
| 41 |
-
"params": ["D", "B"],
|
| 42 |
-
},
|
| 43 |
-
{
|
| 44 |
-
"task": "Format dataset {D} for compatibility with framework {F} on task {T}",
|
| 45 |
-
"difficulty": "Easy",
|
| 46 |
-
"category": "Data Formatting",
|
| 47 |
-
"params": ["D", "F", "T"],
|
| 48 |
-
},
|
| 49 |
-
]
|
| 50 |
-
|
| 51 |
-
# Parameter values
|
| 52 |
-
values = {
|
| 53 |
-
"M": [
|
| 54 |
-
"Qwen/Qwen3-4B-Instruct-2507",
|
| 55 |
-
"openai/gpt-oss-20b",
|
| 56 |
-
"gpt-4o-mini",
|
| 57 |
-
"deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B",
|
| 58 |
-
"anthropic's latest model",
|
| 59 |
-
],
|
| 60 |
-
"B": [
|
| 61 |
-
"Idavidrein/gpqa",
|
| 62 |
-
"HuggingFaceH4/MATH-500",
|
| 63 |
-
"lighteval/SimpleQA",
|
| 64 |
-
"TIGER-Lab/MMLU-Pro",
|
| 65 |
-
],
|
| 66 |
-
"D": [
|
| 67 |
-
"HuggingFaceH4/multi_turn_if",
|
| 68 |
-
"HuggingFaceH4/ultrachat_200k",
|
| 69 |
-
"HuggingFaceH4/AceReason-1.1-SFT config: math_no_think",
|
| 70 |
-
],
|
| 71 |
-
"E": [
|
| 72 |
-
"vllm",
|
| 73 |
-
"sglang",
|
| 74 |
-
],
|
| 75 |
-
"F": [
|
| 76 |
-
"trl",
|
| 77 |
-
"axolotl",
|
| 78 |
-
"verl",
|
| 79 |
-
],
|
| 80 |
-
"P": [
|
| 81 |
-
"learning_rate",
|
| 82 |
-
"batch_size",
|
| 83 |
-
"num_epochs",
|
| 84 |
-
],
|
| 85 |
-
"T": [
|
| 86 |
-
"SFT",
|
| 87 |
-
"GRPO",
|
| 88 |
-
],
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
# Task-specific instance limits
|
| 92 |
-
# For each task, specify which parameter(s) to pivot on and how many instances per pivot combination
|
| 93 |
-
# pivot can be a single parameter string or a list of parameters
|
| 94 |
-
task_limits = [
|
| 95 |
-
{"pivot": "B", "instances_per_pivot": 1}, # Task 0: 1 instance per
|
| 96 |
-
{"pivot": ["M", "B"], "instances_per_pivot": 3}, # Task 1: 3 instances per model
|
| 97 |
-
{"pivot": ["P", "D"], "instances_per_pivot": 3}, # Task 2:
|
| 98 |
-
{"pivot": "E", "instances_per_pivot": 2}, # Task 3: 2 instances per benchmark
|
| 99 |
-
# {"pivot": "M", "instances_per_pivot": 2}, # Task 4
|
| 100 |
-
{"pivot": "D", "instances_per_pivot": 2}, # Task 5: 2 instances per dataset
|
| 101 |
-
{"pivot": ["D", "F", "T"], "instances_per_pivot": 2}, # Task 6:
|
| 102 |
-
]
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
def main():
|
| 106 |
-
eval_data = []
|
| 107 |
-
|
| 108 |
-
for task_idx, task_dict in enumerate(tasks):
|
| 109 |
-
template = task_dict["task"]
|
| 110 |
-
params = task_dict["params"]
|
| 111 |
-
limit_config = task_limits[task_idx]
|
| 112 |
-
|
| 113 |
-
pivot_params = limit_config["pivot"]
|
| 114 |
-
instances_per_pivot = limit_config["instances_per_pivot"]
|
| 115 |
-
|
| 116 |
-
# Normalize pivot to list
|
| 117 |
-
if isinstance(pivot_params, str):
|
| 118 |
-
pivot_params = [pivot_params]
|
| 119 |
-
|
| 120 |
-
# Get all combinations of pivot values
|
| 121 |
-
pivot_param_values = [values[p] for p in pivot_params]
|
| 122 |
-
pivot_combinations = product(*pivot_param_values)
|
| 123 |
-
|
| 124 |
-
# For each pivot combination, generate limited instances
|
| 125 |
-
for pivot_combo in pivot_combinations:
|
| 126 |
-
# Get combinations of other (non-pivot) parameters
|
| 127 |
-
other_params = [p for p in params if p not in pivot_params]
|
| 128 |
-
other_param_values = [values[p] for p in other_params]
|
| 129 |
-
other_combinations = list(product(*other_param_values))
|
| 130 |
-
|
| 131 |
-
# Limit to specified number of instances per pivot combination
|
| 132 |
-
limited_combinations = other_combinations[:instances_per_pivot]
|
| 133 |
-
|
| 134 |
-
# Generate instances
|
| 135 |
-
for combo in limited_combinations:
|
| 136 |
-
# Build kwargs with pivot values and other values
|
| 137 |
-
kwargs = dict(zip(pivot_params, pivot_combo))
|
| 138 |
-
kwargs.update(dict(zip(other_params, combo)))
|
| 139 |
-
|
| 140 |
-
concrete_task = template.format(**kwargs)
|
| 141 |
-
eval_data.append(
|
| 142 |
-
{
|
| 143 |
-
"task": concrete_task,
|
| 144 |
-
"difficulty": task_dict["difficulty"],
|
| 145 |
-
"category": task_dict["category"],
|
| 146 |
-
}
|
| 147 |
-
)
|
| 148 |
-
|
| 149 |
-
print(f"Generated {len(eval_data)} instances from {len(tasks)} templates")
|
| 150 |
-
|
| 151 |
-
dataset = Dataset.from_list(eval_data)
|
| 152 |
-
print(f"\nDataset: {len(dataset)} rows")
|
| 153 |
-
print(f"Sample: {dataset[0]['task']}")
|
| 154 |
-
|
| 155 |
-
dataset.push_to_hub("akseljoonas/qyestions", private=False)
|
| 156 |
-
print("\n✓ Pushed to akseljoonas/qyestions")
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
if __name__ == "__main__":
|
| 160 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/eval_set.ipynb
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
eval/generate_rubrics.py
DELETED
|
@@ -1,403 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env env python3
|
| 2 |
-
"""
|
| 3 |
-
Rubric Generation Script for HF-Agent Benchmark
|
| 4 |
-
|
| 5 |
-
Generates instance-specific evaluation rubrics following the "Rubrics as Rewards" paper.
|
| 6 |
-
Uses LiteLLM to call LLM models for rubric synthesis with expert grounding via reference answers.
|
| 7 |
-
"""
|
| 8 |
-
|
| 9 |
-
import argparse
|
| 10 |
-
import json
|
| 11 |
-
import os
|
| 12 |
-
import sys
|
| 13 |
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 14 |
-
from pathlib import Path
|
| 15 |
-
from typing import Any, Dict, List
|
| 16 |
-
|
| 17 |
-
import litellm
|
| 18 |
-
import pandas as pd
|
| 19 |
-
from dotenv import load_dotenv
|
| 20 |
-
from pydantic import BaseModel
|
| 21 |
-
|
| 22 |
-
from eval.hf_io import df_to_hub
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
class Rubric(BaseModel):
|
| 26 |
-
title: str
|
| 27 |
-
description: str
|
| 28 |
-
weight: int
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
class RubricList(BaseModel):
|
| 32 |
-
rubrics: List[Rubric]
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
# Load environment variables
|
| 36 |
-
load_dotenv()
|
| 37 |
-
|
| 38 |
-
# Rubric generation prompt template based on RaR paper
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
PROMPT_TEMPLATE = """You are an expert rubric writer. Your job is to generate a self-contained set of evaluation criteria ("rubrics") for judging how good, helpful and complete an agent's trajectory is to a given user question/request.
|
| 42 |
-
|
| 43 |
-
Rubrics can cover aspects of a response such as, but not limited to, factual correctness, helpfulness, completeness, harmlessness, correctness of using Hugging Face best practices (based on HF documentation), depth of
|
| 44 |
-
reasoning, contextual relevance and usefulness. Each item must be self-contained – non expert readers should not need to
|
| 45 |
-
infer anything or consult external information. Begin each description with its category: "Essential Criteria: . . . ", "Important
|
| 46 |
-
Criteria: . . . ", "Optional Criteria: . . . ", or "Pitfall Criteria: Does not mention . . . ".
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
Inputs:
|
| 50 |
-
- question: <<<{question}>>>
|
| 51 |
-
- example_solution (NOT ground truth - just an okay attempt): <<<{example_solution}>>>
|
| 52 |
-
- example_trace (NOT ground truth - just an okay attempt showing what tool usage might look like): <<<{example_trace}>>>
|
| 53 |
-
|
| 54 |
-
IMPORTANT: The example_solution and example_trace provided are NOT ground truth or ideal solutions. They represent
|
| 55 |
-
an attempt at solving the task - they give you a general idea of the shape of the problem and what tool usage
|
| 56 |
-
might look like, but they contain mistakes and incomplete solutions, suboptimal approaches, or incomplete answers. Your rubrics MUST be designed to fairly grade a PERFECT solution. The perfect solution is complete in all aspects of solving the task and verifing it's correctness before giving the final answer. It tells the user what was done and why, and provides the final answer clearly answering the user's question.
|
| 57 |
-
|
| 58 |
-
Total items:
|
| 59 |
-
• Choose 7–20 rubric items based on the complexity of the question.
|
| 60 |
-
|
| 61 |
-
Each rubric item:
|
| 62 |
-
• title (2–4 words).
|
| 63 |
-
• description: One sentence starting with its category prefix that explicitly states exactly what to look for. For example:
|
| 64 |
-
– Essential Criteria: Writes a up-to-date, correct, complete and working training loop using the latest Hugging Face best practices. Launches the training with hf-jobs.
|
| 65 |
-
– Pitfall Criteria: Deprecated launcher usage. Uses python -m torch.distributed.launch instead of torchrun / accelerate.
|
| 66 |
-
– Important Criteria: Explains common DDP knobs. Mentions ddp_find_unused_parameters=False for models with conditional branches; optional ddp_timeout; brief note on when they matter and why.
|
| 67 |
-
– Optional Criteria: Briefly notes --deepspeed ds_config.json as an alternative scaler when models get big (but stays on DDP for this Q).
|
| 68 |
-
• weight: For Essential/Important/Optional, use 1–5 (5 = most important); for Pitfall, use –1 or –2.
|
| 69 |
-
|
| 70 |
-
Category guidance:
|
| 71 |
-
• Essential: Critical actions to answer/complete the user's question/request; if missing, the response is invalid and useless (weight 5).
|
| 72 |
-
• Important: Key reasoning, completeness, or clarity; strongly affects quality and usefulness (weight 3–4).
|
| 73 |
-
• Optional: Helpfulness in educating the user or providing extra depth; nice to have but not deal-breaking (weight 1–2).
|
| 74 |
-
• Pitfall: Common mistakes or omissions specific to this prompt—identify things a respondent often forgets or misstates.
|
| 75 |
-
Each Pitfall description must begin with "Pitfall Criteria: Does not mention . . . " or "Pitfall Criteria: Recommends . . . "
|
| 76 |
-
and use weight –1 or –2.
|
| 77 |
-
|
| 78 |
-
To ensure self-contained guidance:
|
| 79 |
-
• When referring to answer choices, explicitly say "Identifies (A)", "Identifies (B)", etc., rather than vague phrasing.
|
| 80 |
-
• If the format requires an action like calling a tool or launching a training run, include a rubric item such as:
|
| 81 |
-
– Essential Criteria: Includes a clear statement "Launches the training with hf-jobs.".
|
| 82 |
-
• If reasoning should precede the answer, include a rubric like:
|
| 83 |
-
– Important Criteria: Presents the explanation and reasoning before stating the final answer.
|
| 84 |
-
• If brevity is valued, include a rubric like:
|
| 85 |
-
– Optional Criteria: Remains concise and avoids unnecessary detail.
|
| 86 |
-
• If the question context demands mention of specific findings/best practices, include that explicitly (e.g., "Essential Criteria: Mentions
|
| 87 |
-
that training data must be in "messages" column for LLM training").
|
| 88 |
-
|
| 89 |
-
Output: Provide a JSON array of rubric objects. Each object must contain exactly three keys—title, description, and weight.
|
| 90 |
-
Do not copy large blocks of the question or example_solution into the text. Each description must begin with its category
|
| 91 |
-
prefix, and no extra keys are allowed.
|
| 92 |
-
|
| 93 |
-
Remember: The example_solution and example_trace are NOT ideal answers - they are just rough attempts to show the
|
| 94 |
-
general approach. Design rubrics that can fairly evaluate any solution, including ones that are better than the example."""
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
def build_prompt(
|
| 98 |
-
question: str,
|
| 99 |
-
example_solution: str,
|
| 100 |
-
example_trace: List[Dict[str, Any]],
|
| 101 |
-
) -> List[Dict[str, str]]:
|
| 102 |
-
"""
|
| 103 |
-
Build the messages list for LiteLLM completion.
|
| 104 |
-
|
| 105 |
-
Args:
|
| 106 |
-
question: The question/task to evaluate
|
| 107 |
-
difficulty: The difficulty level of the task
|
| 108 |
-
example_solution: An example solution attempt (not ground truth)
|
| 109 |
-
example_trace: The agent's message trace showing tool usage
|
| 110 |
-
|
| 111 |
-
Returns:
|
| 112 |
-
List of message dicts for LiteLLM
|
| 113 |
-
"""
|
| 114 |
-
# Format the trace for readability - only include key parts
|
| 115 |
-
formatted_trace = format_trace_for_prompt(example_trace)
|
| 116 |
-
|
| 117 |
-
prompt = PROMPT_TEMPLATE.format(
|
| 118 |
-
question=question,
|
| 119 |
-
example_solution=example_solution,
|
| 120 |
-
example_trace=formatted_trace,
|
| 121 |
-
)
|
| 122 |
-
|
| 123 |
-
return [{"role": "user", "content": prompt}]
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
def format_trace_for_prompt(messages: List[Dict[str, Any]]) -> str:
|
| 127 |
-
"""
|
| 128 |
-
Format the agent message trace for inclusion in the prompt.
|
| 129 |
-
Extracts key information while keeping it readable.
|
| 130 |
-
"""
|
| 131 |
-
if not messages:
|
| 132 |
-
return "(No trace available)"
|
| 133 |
-
|
| 134 |
-
formatted_parts = []
|
| 135 |
-
for msg in messages:
|
| 136 |
-
role = msg.get("role", "unknown")
|
| 137 |
-
content = msg.get("content", "")
|
| 138 |
-
|
| 139 |
-
# Skip system messages
|
| 140 |
-
if role == "system":
|
| 141 |
-
continue
|
| 142 |
-
|
| 143 |
-
# Handle tool calls
|
| 144 |
-
if "tool_calls" in msg and msg["tool_calls"]:
|
| 145 |
-
tool_info = []
|
| 146 |
-
for tc in msg["tool_calls"]:
|
| 147 |
-
if isinstance(tc, dict) and "function" in tc:
|
| 148 |
-
func = tc["function"]
|
| 149 |
-
tool_name = func.get("name", "unknown_tool")
|
| 150 |
-
tool_info.append(f" - Called: {tool_name}")
|
| 151 |
-
if tool_info:
|
| 152 |
-
formatted_parts.append(
|
| 153 |
-
"[Assistant Tool Calls]\n" + "\n".join(tool_info)
|
| 154 |
-
)
|
| 155 |
-
|
| 156 |
-
# Handle regular content
|
| 157 |
-
if content:
|
| 158 |
-
# Truncate very long content
|
| 159 |
-
if len(content) > 500:
|
| 160 |
-
content = content[:500] + "... (truncated)"
|
| 161 |
-
formatted_parts.append(f"[{role.title()}]\n{content}")
|
| 162 |
-
|
| 163 |
-
return "\n\n".join(formatted_parts) if formatted_parts else "(Empty trace)"
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
def validate_rubric(rubric_list: List[Dict[str, Any]]) -> bool:
|
| 167 |
-
"""
|
| 168 |
-
Validate that rubric meets basic requirements.
|
| 169 |
-
|
| 170 |
-
Args:
|
| 171 |
-
rubric_list: List of rubric items to validate
|
| 172 |
-
|
| 173 |
-
Returns:
|
| 174 |
-
True if valid, False otherwise
|
| 175 |
-
"""
|
| 176 |
-
# Check count
|
| 177 |
-
if not (7 <= len(rubric_list) <= 20):
|
| 178 |
-
return False
|
| 179 |
-
|
| 180 |
-
# Check each item
|
| 181 |
-
category_prefixes = [
|
| 182 |
-
"Essential Criteria:",
|
| 183 |
-
"Important Criteria:",
|
| 184 |
-
"Optional Criteria:",
|
| 185 |
-
"Pitfall Criteria:",
|
| 186 |
-
]
|
| 187 |
-
|
| 188 |
-
for item in rubric_list:
|
| 189 |
-
# Check keys
|
| 190 |
-
if set(item.keys()) != {"title", "description", "weight"}:
|
| 191 |
-
return False
|
| 192 |
-
|
| 193 |
-
# Check description starts with category prefix
|
| 194 |
-
if not any(
|
| 195 |
-
item["description"].startswith(prefix) for prefix in category_prefixes
|
| 196 |
-
):
|
| 197 |
-
return False
|
| 198 |
-
|
| 199 |
-
return True
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
def generate_rubric(row: pd.Series, model: str, timeout: int = 120) -> Dict[str, Any]:
|
| 203 |
-
"""
|
| 204 |
-
Generate rubric for a single question using LiteLLM.
|
| 205 |
-
|
| 206 |
-
Args:
|
| 207 |
-
row: DataFrame row containing question, difficulty, solution, and messages
|
| 208 |
-
model: Model name for LiteLLM
|
| 209 |
-
timeout: Request timeout in seconds
|
| 210 |
-
|
| 211 |
-
Returns:
|
| 212 |
-
Dict with rubric_list and rubric_count, or None on failure
|
| 213 |
-
"""
|
| 214 |
-
|
| 215 |
-
messages = build_prompt(
|
| 216 |
-
question=row["question"],
|
| 217 |
-
example_solution=row["solution"],
|
| 218 |
-
example_trace=row.get("messages", []),
|
| 219 |
-
)
|
| 220 |
-
|
| 221 |
-
try:
|
| 222 |
-
response = litellm.completion(
|
| 223 |
-
model=model,
|
| 224 |
-
messages=messages,
|
| 225 |
-
timeout=timeout,
|
| 226 |
-
response_format=RubricList,
|
| 227 |
-
)
|
| 228 |
-
|
| 229 |
-
# Parse structured output
|
| 230 |
-
rubric_list: RubricList = RubricList.model_validate_json(
|
| 231 |
-
response.choices[0].message.content
|
| 232 |
-
)
|
| 233 |
-
|
| 234 |
-
return rubric_list.model_dump_json()
|
| 235 |
-
except Exception as e:
|
| 236 |
-
print(f"Error generating rubric: {e}", file=sys.stderr)
|
| 237 |
-
return None
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
def load_input_data(infile: str) -> pd.DataFrame:
|
| 241 |
-
"""
|
| 242 |
-
Load input data from CSV or JSONL file.
|
| 243 |
-
|
| 244 |
-
Args:
|
| 245 |
-
infile: Path to input file
|
| 246 |
-
|
| 247 |
-
Returns:
|
| 248 |
-
DataFrame with loaded data
|
| 249 |
-
"""
|
| 250 |
-
path = Path(infile)
|
| 251 |
-
|
| 252 |
-
if not path.exists():
|
| 253 |
-
raise FileNotFoundError(f"Input file not found: {infile}")
|
| 254 |
-
|
| 255 |
-
if path.suffix == ".csv":
|
| 256 |
-
# Try to auto-detect delimiter (comma or semicolon)
|
| 257 |
-
df = pd.read_csv(infile, sep=None, engine="python")
|
| 258 |
-
elif path.suffix == ".jsonl":
|
| 259 |
-
df = pd.read_json(infile, lines=True)
|
| 260 |
-
else:
|
| 261 |
-
raise ValueError(f"Unsupported file format: {path.suffix}. Use .csv or .jsonl")
|
| 262 |
-
|
| 263 |
-
# Validate required columns
|
| 264 |
-
required_cols = [
|
| 265 |
-
"question",
|
| 266 |
-
"solution",
|
| 267 |
-
]
|
| 268 |
-
optional_cols = ["difficulty", "messages", "error"]
|
| 269 |
-
missing_cols = [col for col in required_cols if col not in df.columns]
|
| 270 |
-
|
| 271 |
-
if missing_cols:
|
| 272 |
-
raise ValueError(f"Missing required columns: {missing_cols}")
|
| 273 |
-
|
| 274 |
-
# Log available optional columns
|
| 275 |
-
available_optional = [col for col in optional_cols if col in df.columns]
|
| 276 |
-
print(f"Found optional columns: {available_optional}")
|
| 277 |
-
|
| 278 |
-
return df
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
def main():
|
| 282 |
-
parser = argparse.ArgumentParser(
|
| 283 |
-
description="Generate rubrics for HF-agent benchmark evaluation"
|
| 284 |
-
)
|
| 285 |
-
parser.add_argument(
|
| 286 |
-
"--infile", type=str, required=True, help="Input file path (.csv or .jsonl)"
|
| 287 |
-
)
|
| 288 |
-
parser.add_argument(
|
| 289 |
-
"--outfile", type=str, required=True, help="Output JSONL file path"
|
| 290 |
-
)
|
| 291 |
-
parser.add_argument(
|
| 292 |
-
"--model",
|
| 293 |
-
type=str,
|
| 294 |
-
default="anthropic/claude-sonnet-4-5-20250929",
|
| 295 |
-
help="LiteLLM model name (default: from LITELLM_MODEL env or gpt-4o-mini)",
|
| 296 |
-
)
|
| 297 |
-
parser.add_argument(
|
| 298 |
-
"--timeout",
|
| 299 |
-
type=int,
|
| 300 |
-
default=120,
|
| 301 |
-
help="Request timeout in seconds (default: 120)",
|
| 302 |
-
)
|
| 303 |
-
parser.add_argument(
|
| 304 |
-
"--max-concurrent",
|
| 305 |
-
type=int,
|
| 306 |
-
default=30,
|
| 307 |
-
help="Maximum number of concurrent workers (default: 30)",
|
| 308 |
-
)
|
| 309 |
-
parser.add_argument(
|
| 310 |
-
"--push-to-hub",
|
| 311 |
-
type=str,
|
| 312 |
-
default=None,
|
| 313 |
-
help="Push to HuggingFace dataset (e.g., username/dataset@rubrics)",
|
| 314 |
-
)
|
| 315 |
-
|
| 316 |
-
args = parser.parse_args()
|
| 317 |
-
|
| 318 |
-
# Determine model
|
| 319 |
-
model = args.model or os.getenv("LITELLM_MODEL", "gpt-4o-mini")
|
| 320 |
-
print(f"Using model: {model}")
|
| 321 |
-
|
| 322 |
-
# Load input data
|
| 323 |
-
print(f"Loading data from {args.infile}...")
|
| 324 |
-
df = load_input_data(args.infile)
|
| 325 |
-
print(f"Loaded {len(df)} examples")
|
| 326 |
-
|
| 327 |
-
# Run rubric generation in parallel using ThreadPoolExecutor
|
| 328 |
-
print(f"Running generation with {args.max_concurrent} parallel workers...")
|
| 329 |
-
|
| 330 |
-
with ThreadPoolExecutor(max_workers=args.max_concurrent) as executor:
|
| 331 |
-
# Submit all tasks
|
| 332 |
-
future_to_idx = {}
|
| 333 |
-
for idx, row in df.iterrows():
|
| 334 |
-
future = executor.submit(
|
| 335 |
-
generate_rubric,
|
| 336 |
-
row=row,
|
| 337 |
-
model=model,
|
| 338 |
-
timeout=args.timeout,
|
| 339 |
-
)
|
| 340 |
-
future_to_idx[future] = idx
|
| 341 |
-
|
| 342 |
-
# Collect results in order
|
| 343 |
-
results = [None] * len(df)
|
| 344 |
-
completed = 0
|
| 345 |
-
for future in as_completed(future_to_idx):
|
| 346 |
-
idx = future_to_idx[future]
|
| 347 |
-
results[idx] = future.result()
|
| 348 |
-
completed += 1
|
| 349 |
-
print(f"Completed: {completed}/{len(df)}", end="\r")
|
| 350 |
-
|
| 351 |
-
print() # New line after progress
|
| 352 |
-
|
| 353 |
-
# Prepare results DataFrame
|
| 354 |
-
print("Preparing results...")
|
| 355 |
-
output_rows = []
|
| 356 |
-
success_count = 0
|
| 357 |
-
failure_count = 0
|
| 358 |
-
|
| 359 |
-
for idx, (_, row) in enumerate(df.iterrows()):
|
| 360 |
-
rubric_result = results[idx]
|
| 361 |
-
|
| 362 |
-
if rubric_result is None:
|
| 363 |
-
failure_count += 1
|
| 364 |
-
continue
|
| 365 |
-
|
| 366 |
-
# Merge with original data
|
| 367 |
-
output_row = row.to_dict()
|
| 368 |
-
output_row["messages"] = json.dumps(output_row["messages"])
|
| 369 |
-
output_row["rubric"] = rubric_result
|
| 370 |
-
output_rows.append(output_row)
|
| 371 |
-
success_count += 1
|
| 372 |
-
|
| 373 |
-
# Create DataFrame with results
|
| 374 |
-
results_df = pd.DataFrame(output_rows)
|
| 375 |
-
|
| 376 |
-
# Upload to HuggingFace if specified (before saving JSONL)
|
| 377 |
-
if args.push_to_hub:
|
| 378 |
-
print(f"\nUploading to HuggingFace: {args.push_to_hub}")
|
| 379 |
-
upload_success = df_to_hub(
|
| 380 |
-
df=results_df,
|
| 381 |
-
dataset_spec=args.push_to_hub,
|
| 382 |
-
split="train",
|
| 383 |
-
private=False,
|
| 384 |
-
)
|
| 385 |
-
if not upload_success:
|
| 386 |
-
print("Warning: HuggingFace push failed, but continuing to save JSONL...")
|
| 387 |
-
|
| 388 |
-
# Write results to JSONL file
|
| 389 |
-
print(f"\nWriting results to {args.outfile}...")
|
| 390 |
-
with open(args.outfile, "w") as outf:
|
| 391 |
-
for output_row in output_rows:
|
| 392 |
-
outf.write(json.dumps(output_row, default=str) + "\n")
|
| 393 |
-
|
| 394 |
-
print("\nComplete!")
|
| 395 |
-
print(f"Success: {success_count}/{len(df)}")
|
| 396 |
-
print(f"Failures: {failure_count}/{len(df)}")
|
| 397 |
-
print(f"Output written to: {args.outfile}")
|
| 398 |
-
if args.push_to_hub and upload_success:
|
| 399 |
-
print(f"Pushed to: {args.push_to_hub}")
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
if __name__ == "__main__":
|
| 403 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/generated_tasks_with_difficulty.json
DELETED
|
@@ -1,255 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"Evaluate models {M_i} on benchmarks {B_i}": "Easy",
|
| 3 |
-
"Train models {M_i} on datasets {D_i} with benchmarks {B_i}": "Medium",
|
| 4 |
-
"Run an ablation for hyperparameter P for model M on dataset D": "Hard",
|
| 5 |
-
"Generate completions with model M on dataset D using engine E": "Medium",
|
| 6 |
-
"Merge models {M_i} using linear averaging to find the best result on benchmarks {B_i}": "Hard",
|
| 7 |
-
"Given datasets {D_i}, ablate the best SFT mixture for model M across benchmarks {B_i}": "Very hard",
|
| 8 |
-
"Decontaminate dataset D against benchmarks {B_i}": "Hard",
|
| 9 |
-
"Benchmark RL framework F for best throughput on G GPUs": "Very hard",
|
| 10 |
-
"Implement post-training algorithm A from paper P in framework F. Validate it runs end-to-end": "Very hard",
|
| 11 |
-
"Implement benchmark B in framework F. Validate it reproduces some published results": "Very hard",
|
| 12 |
-
"Format dataset D for compatibility with framework F on task T": "Easy",
|
| 13 |
-
"Remove the background from this image: [image path]": "Easy",
|
| 14 |
-
"Transcribe all of the audio files in this directory": "Easy",
|
| 15 |
-
"Transcribe all of the audio files in this directory, choose the model that'll be cheapest and also relatively accurate": "Medium (judgment call or interaction needed to figure out what accuracy levels are acceptable)",
|
| 16 |
-
"Remove the background music from this audio file": "Medium (needs to find Gradio Space and call its API0",
|
| 17 |
-
"Change this video track to be from English to Spanish": "Medium (needs to link several models together)",
|
| 18 |
-
"Translate this flyer from English to Spanish, keeping the layout and images the same": "Medium (needs to link several models together)",
|
| 19 |
-
"What's the best model for X?": "Easy",
|
| 20 |
-
"What datasets are available for X? (X={domain x task x modality})": "Easy",
|
| 21 |
-
"Is there a space to do Y?": "Easy",
|
| 22 |
-
"I have this script and this error - what's the issue?": "Medium",
|
| 23 |
-
"This space is broken, how can i fix it?": "Medium",
|
| 24 |
-
"I built a space but it is super slow. What can I do?": "Medium",
|
| 25 |
-
"How can I run modal X locally?": "Medium",
|
| 26 |
-
"I want to build a space with model Y to do X?": "Hard",
|
| 27 |
-
"How can I serve a model with multiple LoRAs?": "Hard",
|
| 28 |
-
"What's the best model for sentiment analysis on financial text?": "Easy",
|
| 29 |
-
"Are there any medical image segmentation datasets on HuggingFace for CT scans?": "Easy",
|
| 30 |
-
"Which text classification models support 4-bit quantization?": "Medium",
|
| 31 |
-
"Are there inference endpoints available for Whisper large-v3?": "Easy",
|
| 32 |
-
"What's the license for the SA-Med2D-20M dataset?": "Easy",
|
| 33 |
-
"Which vision models fit in 8GB VRAM for image segmentation?": "Medium",
|
| 34 |
-
"What datasets are available for 3D medical image segmentation?": "Medium",
|
| 35 |
-
"Is there a space to do text-to-speech with emotion control?": "Medium",
|
| 36 |
-
"I'm getting \"CUDA out of memory\" when loading Llama-2-7b even though nvidia-smi shows I have 6GB free - what's the issue?": "Medium",
|
| 37 |
-
"My Gradio space shows \"Connection errored out\" after working fine yesterday, no code changes - how can I fix it?": "Medium",
|
| 38 |
-
"I built a Gradio space for Stable Diffusion but inference takes 5+ minutes on a 4090 - what can I do?": "Medium",
|
| 39 |
-
"My Whisper model outputs different transcriptions after quantization to int8 - why?": "Medium",
|
| 40 |
-
"Getting \"RuntimeError: CUDA error: out of memory. Tried to allocate 70.00 MiB\" but only 2.87 GiB is allocated - what's happening?": "Medium",
|
| 41 |
-
"My HuggingFace space build fails with \"failed to create containerd task\" - how to fix?": "Medium",
|
| 42 |
-
"DistilBERT model gives \"you should probably train your model\" warning even though it's a pretrained model from the Hub": "Easy",
|
| 43 |
-
"Space was working fine but now receiving build errors - receiving this error even with a new space": "Medium",
|
| 44 |
-
"Inference is correct locally but wrong on deployed space": "Medium",
|
| 45 |
-
"Getting CUDA OOM despite having enough memory according to nvidia-smi": "Medium",
|
| 46 |
-
"How can I run Mistral-7B-v0.1 locally with multiple LoRA adapters?": "Hard",
|
| 47 |
-
"How can I serve Llama-2-7b with vLLM and dynamically load multiple LoRA adapters?": "Hard",
|
| 48 |
-
"How do I batch inference requests in my Gradio space for better throughput?": "Medium",
|
| 49 |
-
"Can I run Whisper large-v3 with faster-whisper for 4x speedup?": "Medium",
|
| 50 |
-
"How to run Llama 2 on CPU after fine-tuning with LoRA?": "Medium",
|
| 51 |
-
"Best way to handle 50+ concurrent requests in a Gradio space without OOM?": "Hard",
|
| 52 |
-
"How do I add custom stopping criteria for text generation with Transformers?": "Hard",
|
| 53 |
-
"Can I merge multiple LoRA adapters before inference to reduce latency?": "Hard",
|
| 54 |
-
"How can I optimize my LLM inference with one base LLM and multiple LoRA adapters?": "Hard",
|
| 55 |
-
"Compare tokenizers {T_i} for model M on tasks {classification, QA}; report accuracy and average sequence length per task": "Medium",
|
| 56 |
-
"Run a LoRA rank sweep (r in {4, 8, 16, 32}) for model M on dataset D; plot validation perplexity vs VRAM usage and select Pareto-optimal settings": "Hard",
|
| 57 |
-
"Build a streaming dataloader from Parquet on S3 with deterministic shuffling across N workers; validate epoch reproducibility": "Very hard",
|
| 58 |
-
"Find three open-source TTS models with emotion control and list their sample rates and licenses": "Easy",
|
| 59 |
-
"Create a retrieval-augmented QA pipeline: index corpus C with FAISS, connect to model M, and benchmark top-1 accuracy and p95 latency": "Hard",
|
| 60 |
-
"Diagnose a Space where memory grows per request; add no-grad guards, free caches, and demonstrate stable RSS over 10,000 calls": "Hard",
|
| 61 |
-
"Deduplicate dataset D using MinHash LSH at Jaccard >= 0.9 and publish a cleaned HF dataset with provenance columns": "Medium",
|
| 62 |
-
"Add special tokens to tokenizer T and resize model M embeddings; resume pretraining for 10k steps without loss spikes": "Hard",
|
| 63 |
-
"Create a HuggingFace Dataset from CSV file data.csv and push to repo username/my_dataset": "Easy",
|
| 64 |
-
"Build a real-time Whisper transcription Space with VAD and chunked decoding; keep end-to-end latency under 200 ms": "Hard",
|
| 65 |
-
"Quantize model M to 4-bit (bnb.int4) with bitsandbytes; compare perplexity and p95 latency to 8-bit on dataset D; select config with <1% perplexity increase": "Medium",
|
| 66 |
-
"Fuse LoRA adapter A into base model M and export a single safetensors checkpoint; verify logits parity (<1e-5 MSE) vs on-the-fly LoRA": "Hard",
|
| 67 |
-
"Redact PII from dataset D using a transformer NER pipeline; produce a cleaned HuggingFace Dataset with per-entity removal stats and provenance": "Medium",
|
| 68 |
-
"Train a SentencePiece tokenizer (vocab=64k, byte fallback) on corpus C; compare tokenization speed, unknown-token rate, and bytes/token vs tokenizer T": "Hard",
|
| 69 |
-
"Build a sharded FAISS IVF-PQ index for 100M embeddings stored on S3; integrate with HF datasets streaming and report recall@10 and QPS": "Very hard",
|
| 70 |
-
"Fine-tune model M with QLoRA using TRL PPO on dataset D; log KL, reward, and throughput; validate no divergence on a held-out eval": "Hard",
|
| 71 |
-
"Resolve HfHubHTTPError 401 when pushing dataset repo R: diagnose token scopes, git-lfs config, and large file thresholds; document the fix": "Medium",
|
| 72 |
-
"Implement a custom Transformers LogitsProcessor that bans repeated bigrams; add unit tests and benchmark generation quality (BLEU) on dataset D": "Hard",
|
| 73 |
-
"List and download all Hub models tagged 'text-classification' with Apache-2.0 license and size <500MB; save model ids and downloads to CSV": "Easy",
|
| 74 |
-
"Enable speculative decoding in vLLM with draft model D for base model M; benchmark tokens/sec speedup at batch sizes {1,4,16} and max_new_tokens {64,256}": "Very hard",
|
| 75 |
-
"Profile model M under torch.compile modes {reduce-overhead, max-autotune} on GPU G; report tokens/sec, peak VRAM, and compile overhead": "Medium",
|
| 76 |
-
"Detect and remove near-duplicate images in dataset D using CLIP ViT-L/14 embeddings at cosine >= 0.95; publish a cleaned dataset with duplicate_group ids": "Medium",
|
| 77 |
-
"Convert a TensorFlow SavedModel of T5-base to Transformers PyTorch format; verify logits parity (MSE < 1e-4) on 1,000 random prompts": "Hard",
|
| 78 |
-
"Enable FlashAttention-2 in a Transformers training loop for model M; benchmark step time and confirm loss parity over 2,000 steps vs baseline": "Hard",
|
| 79 |
-
"Deploy vLLM for model M with hot-swappable LoRA adapters {A_i}; provide an API to switch adapters and demonstrate <200 ms switch latency under load": "Very hard",
|
| 80 |
-
"Implement a custom Trainer callback to log gradient norms, activation histograms, and learning rate; diagnose periodic loss spikes and propose a fix": "Hard",
|
| 81 |
-
"Build a bilingual RAG pipeline indexing corpora {en, es} with FAISS HNSW; evaluate exact match@1 on dataset D and report p95 latency": "Hard",
|
| 82 |
-
"Run a mixed-precision sweep (fp16 vs bf16) for model M on A100 and RTX 3090; compare convergence, throughput, and numerical stability issues": "Medium",
|
| 83 |
-
"Create a Gradio Space that batches Whisper-large-v3 transcription via queue + chunked decoding; maintain real-time factor <= 0.5 on a T4": "Hard",
|
| 84 |
-
"List five OCR datasets on the Hub with line-level annotations; include licenses and approximate image counts": "Easy",
|
| 85 |
-
"List models on the Hub tagged 'summarization' that offer safetensors weights and 4-bit quantization; output model ids": "Easy",
|
| 86 |
-
"Evaluate safety filters of models {M_i} on red-team prompt set R; report jailbreak rate and false positive rate": "Medium",
|
| 87 |
-
"Run a prompt template ablation for chat model M on dataset D; compare {alpaca, chatml, llama2} formats and report exact match and average output length": "Hard",
|
| 88 |
-
"Implement tensor parallelism for model M in framework F and show linear scaling across 2\u20138 GPUs with <=10% gap from ideal": "Very hard",
|
| 89 |
-
"Convert and shard dataset D into WebDataset tar files (~500MB/shard); build a streaming loader with checksum validation": "Medium",
|
| 90 |
-
"Deploy a Spaces app serving Stable Diffusion XL with ControlNet; add output caching and keep p95 latency <1s for 20 concurrent users": "Hard",
|
| 91 |
-
"Diagnose and fix 'shape mismatch' when loading LoRA into model M after tokenizer resize; provide minimal repro and patch": "Medium",
|
| 92 |
-
"Add a detailed model card to repo username/model_M with training data, intended use, limitations, and evaluation results": "Easy",
|
| 93 |
-
"Enable KV cache quantization (int8) in Transformers for model M; compare tokens/sec and ROUGE-L on dataset D vs fp16 cache": "Hard",
|
| 94 |
-
"Detect and redact license-incompatible samples in dataset D by matching SPDX identifiers and source domains; publish a compliance report": "Medium",
|
| 95 |
-
"Profile vLLM serving of model M with paged attention; tune block_size to maximize tokens/sec and report p50/p95 latency and peak VRAM": "Medium",
|
| 96 |
-
"Filter dataset D for toxic content using classifier C; log per-label removal rates and recreate stratified train/valid/test splits": "Medium",
|
| 97 |
-
"Train a unigram tokenizer (vocab=80k) on corpora {en, fr}; fine-tune T5-small and compare BLEU vs a BPE baseline; report tokenization speed and OOV rate": "Hard",
|
| 98 |
-
"Run distributed evaluation of models {M_i} on benchmark B across 4 GPUs with DeepSpeed-Inference; ensure identical metrics across 3 seeds": "Hard",
|
| 99 |
-
"Find three open-source ASR models that provide word-level timestamps; record licenses and expected WER on LibriSpeech": "Easy",
|
| 100 |
-
"Diagnose intermittent 'Address already in use' crashes in a FastAPI Space; add graceful shutdown and port probing, verifying stability over 1,000 restart cycles": "Medium",
|
| 101 |
-
"Export a LoRA-finetuned Llama checkpoint to GGUF for llama.cpp; validate perplexity parity (<=1% drift) on WikiText-2": "Hard",
|
| 102 |
-
"Construct a streaming RAG pipeline over S3-stored corpus C with Chroma; index ~1B tokens, implement shard rebalancing, and benchmark recall@5 and QPS": "Very hard",
|
| 103 |
-
"List Hub datasets tagged 'speech-emotion-recognition' with CC-BY or CC-BY-SA licenses and >=10k utterances; write dataset ids and sizes to JSON": "Easy",
|
| 104 |
-
"Train a summarization reward model via pairwise ranking on dataset D; apply DPO to model M and report ROUGE-L and human win rate": "Hard",
|
| 105 |
-
"Find four open-source OCR models that output line- or paragraph-level text and provide ONNX or TensorRT exports; list their licenses and maximum input resolutions": "Easy",
|
| 106 |
-
"Verify tokenizer special tokens for model M are preserved after adding new tokens; write a unit test that asserts CLS/SEP/PAD ids are unchanged before and after resize": "Medium",
|
| 107 |
-
"Implement a constrained decoder for model M that enforces a JSON schema via a custom Transformers LogitsProcessor; add unit tests and benchmark latency on dataset D": "Hard",
|
| 108 |
-
"Build a multilingual RAG index for 50M documents using mDPR with sharded storage on S3; support hot index reloads and report recall@10 and p95 latency at 100 QPS": "Very hard",
|
| 109 |
-
"Quantize T5-base to 8-bit with bitsandbytes (LLM.int8) and compare ROUGE-L and tokens/sec to fp16 on CNN/DailyMail; keep ROUGE-L drop <=1%": "Medium",
|
| 110 |
-
"Diagnose VRAM growth in a vLLM server at batch size 32; add profiling, fix cache eviction behavior, and demonstrate flat memory over 10,000 requests": "Hard",
|
| 111 |
-
"Convert a HuggingFace TokenizerFast to a SentencePiece model; verify >=99.9% token-level agreement on 10,000 sentences and measure tokenization speed delta": "Medium",
|
| 112 |
-
"Train a multi-task adapter stack for {summarization, QA, NLI} on model M; implement routing by prompt prefix and report per-task metrics and cross-task interference": "Very hard",
|
| 113 |
-
"Assess license compatibility between model M (Apache-2.0) and dataset D (CC-BY-SA); produce a one-paragraph verdict with rationale and reference links": "Easy",
|
| 114 |
-
"Enable FSDP with activation checkpointing for a 13B model across 2\u00d7A100 GPUs; achieve <=10% throughput loss vs baseline and verify loss parity over 1,000 steps": "Hard",
|
| 115 |
-
"List three datasets for code summarization with permissive licenses; output their dataset ids and license names": "Easy",
|
| 116 |
-
"Set up nightly continuous evaluation of model M on benchmarks {B_i}; log metrics to Weights & Biases and alert on >2% regression vs last 7-day rolling mean": "Medium",
|
| 117 |
-
"Implement streaming text generation in a Gradio Space for model M using server-sent events; cap median token emission delay at <50 ms": "Hard",
|
| 118 |
-
"Scale out training of a 7B model with FSDP + ZeRO across 8 GPUs; demonstrate checkpoint save/restore and achieve throughput within 15% of ideal linear scaling": "Very hard",
|
| 119 |
-
"Export a mixture-of-experts PyTorch model to ONNX and run with TensorRT; verify top-1 accuracy within 0.5% of PyTorch on dataset D": "Medium",
|
| 120 |
-
"Identify whether model M supports FlashAttention-2 from its config or source; provide supporting repo links and a yes/no compatibility flag": "Easy",
|
| 121 |
-
"Build an audio deduplication pipeline for dataset D using embedding model E with cosine similarity >= 0.98; publish grouped duplicate ids and a cleaned manifest": "Hard",
|
| 122 |
-
"Diagnose slow tokenization in a Transformers pipeline; profile, switch to a fast tokenizer, and demonstrate 2\u00d7 end-to-end speedup on 1M lines": "Medium",
|
| 123 |
-
"Implement a contrastive preference learning loss in TRL; train model M on dataset D and compare KL, reward variance, and human win rate vs a PPO baseline": "Hard",
|
| 124 |
-
"Build an elastic RAG service with Ray that autoscales FAISS shards on S3, supports live corpus updates, and maintains p95 latency <500 ms at 200 QPS": "Very hard",
|
| 125 |
-
"List five chat-optimized LLMs on the Hub that include a tokenizer chat_template and safetensors weights; output model ids": "Easy",
|
| 126 |
-
"Find three biomedical NER datasets with Apache-2.0 or MIT licenses; return dataset ids and license names": "Easy",
|
| 127 |
-
"Create a dataset viewer Space that streams Parquet shards from the Hub using datasets streaming; implement server-side filtering and pagination": "Medium",
|
| 128 |
-
"Enable gradient checkpointing and optimizer state offloading for model M with Accelerate; report step time and peak VRAM vs baseline on a single A100": "Medium",
|
| 129 |
-
"Diagnose and fix 'size mismatch for position_embeddings' after increasing max_position_embeddings; provide a minimal repro and a migration script": "Medium",
|
| 130 |
-
"Implement a regex-constrained Transformers LogitsProcessor that enforces ISO-8601 timestamps; add unit tests and report generation latency overhead on dataset D": "Hard",
|
| 131 |
-
"Train language-specific LoRA adapters for {en, es, de} on model M; add an automatic language router and report per-language BLEU and cross-language interference": "Hard",
|
| 132 |
-
"Build a speaker diarization + ASR Gradio Space using pyannote and Whisper-large-v3; achieve DER <= 12% and real-time factor <= 0.75 on a T4": "Hard",
|
| 133 |
-
"Implement multi-draft speculative decoding with dynamic draft-model selection per prompt; integrate with vLLM and benchmark tokens/sec speedup at batch sizes {1,8,32}": "Very hard",
|
| 134 |
-
"Convert a TensorFlow DistilBERT SavedModel to ONNX (opset 17) and validate logits parity (MSE < 1e-4) on 1,000 random inputs; measure CPU inference speedup vs TensorFlow": "Medium",
|
| 135 |
-
"Evaluate alignment drift after SFT: compare model M vs base M0 on prompt set P; report win rate, refusal rate, and average output length": "Medium",
|
| 136 |
-
"Enable KV cache int4 quantization in vLLM for model M; benchmark tokens/sec and exact match on dataset D vs fp16 cache": "Hard",
|
| 137 |
-
"Implement variable-length packing in a HF Datasets + Transformers training loop; ensure epoch-level sample coverage matches baseline and no truncation beyond max_length": "Medium",
|
| 138 |
-
"Build a multi-tenant LoRA router over vLLM: on-demand load adapters from the Hub with LRU eviction; sustain 100 tenants and <300 ms adapter swap latency under load": "Very hard",
|
| 139 |
-
"Audit generations for PII leakage on prompt set P using detector C; compute precision, recall, and false positive rate; redact before logging and publish a compliance summary": "Medium",
|
| 140 |
-
"Merge a stack of PEFT adapters {A_i} into base model M to produce a single FP16 checkpoint; validate perplexity drift <=0.5% on dataset D and export safetensors": "Hard",
|
| 141 |
-
"Find three Spaces that demonstrate constrained JSON generation; return Space ids and URLs": "Easy",
|
| 142 |
-
"Deploy a cross-lingual vector search service with multilingual-e5-large; shard FAISS across 3 nodes and measure mAP@10 and p95 latency at 500 QPS": "Very hard",
|
| 143 |
-
"Quantize attention and MLP projections only with bitsandbytes (selective 8-bit); compare peak VRAM, tokens/sec, and ROUGE-L vs full-model 8-bit on dataset D": "Hard",
|
| 144 |
-
"Fix \"Token indices sequence length is longer than the specified maximum\" after tokenizer resize; add truncation with stride and update generation config; verify no validation metric regression": "Medium",
|
| 145 |
-
"Identify splits for dataset D and output split names with sample counts": "Easy",
|
| 146 |
-
"Find five multilingual sentence-embedding models on the Hub with Apache-2.0 license; return model ids": "Easy",
|
| 147 |
-
"Set up CI to run evaluation suite E for model M nightly; fail the job if any metric drops >1% vs 7-day rolling mean": "Medium",
|
| 148 |
-
"Add length normalization to beam search for model M; compare vs baseline on dataset D and report ROUGE-L and average output length": "Medium",
|
| 149 |
-
"Detect per-sample language for dataset D; add a 'lang' column and recreate train/valid/test splits preserving language proportions": "Medium",
|
| 150 |
-
"Benchmark vLLM KV-cache eviction strategies (e.g., LRU vs TTL) for model M at batch sizes {1,8,32}; report tokens/sec and peak VRAM": "Medium",
|
| 151 |
-
"Implement a custom DataCollator that packs multiple documents for summarization with separator tokens; add unit tests to prevent cross-sample leakage": "Hard",
|
| 152 |
-
"Build a PDF-to-dataset pipeline: OCR pages with model Donut, store word-level bboxes, and publish a HuggingFace Dataset with a viewer Space": "Hard",
|
| 153 |
-
"Train a ColBERT reranker on corpus C + pairs dataset D; integrate into a RAG search service and report recall@10 and p95 latency delta": "Hard",
|
| 154 |
-
"Deploy vLLM for model M with multi-GPU tensor-parallel inference across 2 nodes using NCCL; demonstrate near-linear throughput scaling and deterministic outputs across 3 seeds": "Very hard",
|
| 155 |
-
"List four Hub models tagged 'named-entity-recognition' that declare bitsandbytes 8-bit support in their README; output model ids": "Easy",
|
| 156 |
-
"Find three Spaces that provide real-time TTS streaming demos; return Space ids and reported sample rates": "Easy",
|
| 157 |
-
"Create a Spaces app that visualizes transformer attention maps for a ViT model using Captum; keep heatmap rendering under 200 ms for 224x224 images": "Medium",
|
| 158 |
-
"Set up datasets streaming with resumable downloads and exponential backoff for S3-hosted Parquet shards; verify checksum integrity after killing and resuming the job": "Medium",
|
| 159 |
-
"Build a tokenizer migration tool to convert a SentencePiece model to a HuggingFace tokenizers JSON with byte-fallback; assert >=99.95% token-level agreement on 20k sentences and report speed delta": "Medium",
|
| 160 |
-
"Implement a custom DataCollator for span masking with variable block sizes for byte-level BPE; add unit tests and demonstrate MLM loss parity over 10k steps on WikiText-103": "Hard",
|
| 161 |
-
"Add speculative decoding with a small draft model to a Transformers-based text-generation server; expose a per-request flag and benchmark tokens/sec speedup at batch sizes {1,8,32}": "Hard",
|
| 162 |
-
"Train an online knowledge-distillation SFT: teacher M0 -> student M on dataset D; log KL divergence, token agreement, and throughput; cap metric drop at <=2% vs teacher": "Hard",
|
| 163 |
-
"Deploy a multi-region vLLM service on Kubernetes with adaptive batching and hot LoRA adapter loading; sustain 200 QPS with p95 latency <300 ms and zero-downtime rollouts": "Very hard",
|
| 164 |
-
"Build a sharded cross-encoder reranking service with Ray: distribute ColBERT scoring across nodes, integrate with FAISS retrieval, and maintain recall@10 within 1% of single-node baseline at 500 QPS": "Very hard",
|
| 165 |
-
"List four Spaces that perform multilingual OCR with layout extraction; return Space ids and supported languages": "Easy",
|
| 166 |
-
"Find five Hub datasets for code generation evaluation with permissive licenses; output dataset ids and license names": "Easy",
|
| 167 |
-
"Add gradient accumulation and gradient clipping to a Transformers Trainer finetune of model M; report step time, peak VRAM, and validation metric vs baseline": "Medium",
|
| 168 |
-
"Implement document chunking with sliding windows and overlap in a Datasets map pipeline; add doc_id and span indices and verify no segment exceeds max_length": "Medium",
|
| 169 |
-
"Export a fine-tuned BERT model to TorchScript and ONNX; verify logits parity (MSE < 1e-4) on 1,000 samples and compare CPU throughput": "Medium",
|
| 170 |
-
"Diagnose 'pad_token_id is not set' warnings during generation; add a PAD token, resize embeddings, and write a unit test asserting identical logits pre/post fix on 200 prompts": "Medium",
|
| 171 |
-
"Implement diverse beam search (group_beam_search) for model M; evaluate on dataset D and report ROUGE-L, distinct-n, and average output length vs standard beam search": "Hard",
|
| 172 |
-
"Build a multi-modal RAG demo that indexes image captions with CLIP and uses LLM M to answer visual questions; report top-1 accuracy and p95 latency": "Hard",
|
| 173 |
-
"Profile activation and KV-cache memory during generation for model M; log per-layer footprints and reduce peak usage via attention slicing; show tokens/sec and VRAM deltas": "Hard",
|
| 174 |
-
"Construct a 200M-document FAISS hybrid (IVF-PQ + HNSW) index with memory-mapped shards on S3; support live add/delete and benchmark recall@10 and QPS at 300 QPS": "Very hard",
|
| 175 |
-
"List five Hub datasets tagged 'topic-modeling' with MIT or Apache-2.0 licenses; output dataset ids": "Easy",
|
| 176 |
-
"Find three Spaces that offer real-time grammar correction with streaming tokens; return Space ids and URLs": "Easy",
|
| 177 |
-
"Convert a spaCy en_core_web_trf NER model to ONNX and wrap it in a Transformers TokenClassification pipeline; verify entity text/label/span parity on 1,000 sentences": "Medium",
|
| 178 |
-
"Set up a GitHub Actions workflow that snapshots tokenizer T weekly and fails if vocab or special token ids drift vs the last snapshot; upload a diff artifact": "Medium",
|
| 179 |
-
"Profile a Datasets map pipeline on corpus C; refactor to use batched=True, num_proc>1, and caching; achieve >=2\u00d7 speedup while preserving deterministic ordering across runs": "Medium",
|
| 180 |
-
"Implement a custom Transformers StoppingCriteria that halts when JSON braces are balanced or max nesting depth is reached; add unit tests and benchmark latency overhead on dataset D": "Hard",
|
| 181 |
-
"Build a visual-and-tabular RAG pipeline: index images with CLIP and CSV tables with TAPAS; answer mixed queries using LLM M; report EM@1 and p95 latency at 50 QPS": "Hard",
|
| 182 |
-
"Enable KV-cache int4 quantization during generation in Transformers for model M; compare tokens/sec and exact match vs fp16 cache on dataset D; keep metric drop <=1%": "Hard",
|
| 183 |
-
"Implement a hot-reloadable sharded FAISS IVF-PQ index for multilingual-e5-base with live add/delete and background re-training; sustain 200 QPS with p95 latency <400 ms across 3 nodes": "Very hard",
|
| 184 |
-
"Deploy a geo-distributed vLLM + LoRA adapter gateway across two regions with consistent hashing and zero-downtime adapter updates; ensure identical outputs across 3 seeds and report cross-region p95 latency": "Very hard",
|
| 185 |
-
"List five Hub LLM repos that disclose training token counts in their model cards; output model ids and token totals": "Easy",
|
| 186 |
-
"Find two ready-to-use Spaces for speaker diarization compatible with Whisper; return Space ids and URLs": "Easy",
|
| 187 |
-
"Create a hashing-based dataset splitter using column 'doc_id' to produce reproducible train/valid/test; verify identical splits across two machines and Python versions": "Medium",
|
| 188 |
-
"Resolve HTTP 403 when creating an organization dataset via the Hub API; diagnose token scopes and org permissions; provide a minimal repro script and the fix": "Medium",
|
| 189 |
-
"Export a PEFT LoRA adapter from a fine-tuned Llama checkpoint as standalone safetensors with a correct adapter_config.json; push to the Hub and verify PEFT.from_pretrained loads it": "Medium",
|
| 190 |
-
"Enable multi-query attention in model M within Transformers; benchmark tokens/sec and peak VRAM vs multi-head attention and verify perplexity parity over 2,000 steps": "Hard",
|
| 191 |
-
"Audit code dataset D for contamination against {HumanEval, MBPP} using exact substring and 3-gram Jaccard >= 0.9; publish per-source contamination rates and a cleaned dataset": "Hard",
|
| 192 |
-
"Implement contrastive search decoding for model M with tunable alpha; compare ROUGE-L, distinct-n, and latency vs nucleus sampling on dataset D": "Hard",
|
| 193 |
-
"Implement pipeline parallelism for model M across 4 GPUs with Accelerate; achieve near-linear scaling (<=15% gap), support checkpoint save/restore, and ensure deterministic outputs across 3 seeds": "Very hard",
|
| 194 |
-
"Deploy a Spaces app that serves two ASR models with automatic language ID routing; maintain real-time factor <= 0.6 on a single T4 and log per-language latency": "Hard",
|
| 195 |
-
"Benchmark JSON-constrained decoding across models {M_i}; report JSON validity rate, exact match on dataset D, and p95 latency under streaming": "Hard",
|
| 196 |
-
"Filter a multilingual dataset D to non-English using fastText language ID; recreate stratified splits and report per-language retention and drop rates": "Medium",
|
| 197 |
-
"Enable paged attention in a custom Transformers generation loop for model M; verify token-level parity on 500 prompts and measure peak VRAM change": "Hard",
|
| 198 |
-
"Shard a 1B-token text corpus into deterministic HF Datasets processing across 16 workers; validate byte-for-byte identical outputs across two runs": "Very hard",
|
| 199 |
-
"Compare LoRA vs QLoRA fine-tunes of Mistral-7B on GSM8K; track loss, exact match, and throughput; select the lowest-VRAM config within 2% EM of best": "Hard",
|
| 200 |
-
"Deploy a quantized T5 encoder-decoder on Triton Inference Server via a Python backend; add token streaming and achieve >=1.5x throughput vs PyTorch baseline": "Hard",
|
| 201 |
-
"Find three Spaces that perform audio source separation (vocals/music); return Space ids and reported sample rates": "Easy",
|
| 202 |
-
"Merge a PEFT IA3 adapter stack into Llama-3-8B base weights; verify perplexity drift <=0.3% on WikiText-103 and export safetensors": "Hard",
|
| 203 |
-
"Resolve DeepSpeed ZeRO-3 stalls during S3 checkpointing; implement async multipart uploads and show stable 5-minute checkpoint cadence over 2 hours": "Very hard",
|
| 204 |
-
"Set up CI to run contamination checks on dataset R against {TruthfulQA, SQuAD} using 4-gram overlap; fail if rate >0.5% and attach offending ids as artifacts": "Medium",
|
| 205 |
-
"List four Hub datasets for sarcasm detection in English; return dataset ids and license tags": "Easy",
|
| 206 |
-
"Identify whether tokenizer T enables byte_fallback in tokenizer.json; output true/false and the file path": "Easy",
|
| 207 |
-
"Find three Spaces that showcase streaming chat with token-by-token updates; return Space ids and whether they use SSE or websockets": "Easy",
|
| 208 |
-
"Create a Datasets loader that parses Praat TextGrid files into word-level timestamps aligned with audio; publish a dataset with an 'audio' column and validate 100 sample alignments": "Medium",
|
| 209 |
-
"Set up a GitHub Actions workflow that lints model cards for repos {R_i} to require intended use, training data, and limitations; fail PRs and post a summary comment on violations": "Medium",
|
| 210 |
-
"Containerize a Gradio Space with optional FlashAttention build: detect GPU capability at startup, compile kernels if supported, and fall back gracefully on unsupported GPUs; test on T4 and A100": "Medium",
|
| 211 |
-
"Evaluate long-context retrieval via needle-in-a-haystack for models {M_i} at context lengths {8k, 32k, 64k}; report retrieval accuracy, tokens/sec, and the max stable context length": "Hard",
|
| 212 |
-
"Implement a curriculum sampler as a HuggingFace Trainer callback that schedules sample difficulty over epochs; compare convergence and final eval metrics vs random sampling": "Hard",
|
| 213 |
-
"Add on-the-fly near-duplicate filtering during training using SimHash over token ids; log per-epoch removal rates and verify no convergence regressions vs a deduplicated baseline": "Hard",
|
| 214 |
-
"Deploy a dual-backend inference router using vLLM and TensorRT-LLM that selects backend per prompt length to minimize latency; maintain deterministic outputs across 3 seeds and sustain 300 QPS with p95 latency SLOs": "Very hard",
|
| 215 |
-
"Identify max_position_embeddings and whether rope_scaling is enabled for model M from its config; output both values.": "Easy",
|
| 216 |
-
"List five Vision Transformer models on the Hub that provide safetensors and have a default image size >= 384; output model ids.": "Easy",
|
| 217 |
-
"Find three Spaces that stream machine-translation outputs token-by-token; return Space ids and whether they use SSE or websockets.": "Easy",
|
| 218 |
-
"Diagnose bursts of [UNK] after adding special tokens to tokenizer T; enable byte_fallback, retrain embeddings for 2k steps, and show unknown-token rate <= baseline+0.1% on corpus C.": "Medium",
|
| 219 |
-
"Create a dataset viewer Space for a dataset with a nested JSON column; convert to Arrow struct arrays, implement server-side filtering on nested keys, and verify row counts match the source.": "Medium",
|
| 220 |
-
"Set up a GitHub Action that hits /health and a no-op inference on Space S after each deploy; fail if cold-start median latency >10s and attach server logs as an artifact.": "Medium",
|
| 221 |
-
"Implement a SQL grammar-constrained Transformers LogitsProcessor using an LL(1) parser; evaluate on Spider dev and report exact match and p95 latency overhead vs nucleus sampling.": "Hard",
|
| 222 |
-
"Add CPU-tier KV-cache offloading with pinned memory for model M in a custom generation loop; compare tokens/sec and peak VRAM vs baseline at context lengths {4k, 16k, 32k}.": "Hard",
|
| 223 |
-
"Deploy a batched cross-encoder reranker microservice using bge-reranker-base; keep recall@10 within 1% of single-request baseline and achieve >=2\u00d7 QPS at 100 concurrent users.": "Hard",
|
| 224 |
-
"Build a heterogeneous inference gateway that routes requests to vLLM or llama.cpp based on prompt length and GPU load; ensure identical normalized outputs across 3 seeds and sustain 200 QPS with p95 latency <300 ms.": "Very hard",
|
| 225 |
-
"Determine whether tokenizer T strips accents (strip_accents); output true/false and the file path where the setting is defined.": "Easy",
|
| 226 |
-
"List four Hub datasets for hate-speech detection in English; return dataset ids and license tags.": "Easy",
|
| 227 |
-
"Write a Datasets loader for a paginated OAuth2 REST API; cache pages, support streaming, and provide deterministic sharding across 8 workers; verify identical row counts across two runs.": "Medium",
|
| 228 |
-
"Add request-level caching (ETag/If-None-Match) to a Gradio summarization Space; achieve >=1.8\u00d7 QPS at 50 concurrent users and report cache hit ratio and p95 latency.": "Medium",
|
| 229 |
-
"Enable HuggingFace tokenizers parallelism and batched encoding for corpus C; benchmark throughput and memory on 10M lines and ensure deterministic outputs across 3 runs.": "Medium",
|
| 230 |
-
"Set up CI to lint dataset cards in repos {R_i} for required fields {license, citation, dataset_summary}; fail PRs and post a summary comment with missing keys.": "Medium",
|
| 231 |
-
"Run a parameter-efficient finetuning sweep comparing LoRA, IA3, and prefix-tuning on RoBERTa-base for MNLI; report accuracy, training time, and peak VRAM; select a Pareto-optimal config.": "Hard",
|
| 232 |
-
"Implement a Transformers LogitsProcessor that enforces balanced parentheses and proper quoted-string escaping; add unit tests and benchmark latency overhead on dataset D.": "Hard",
|
| 233 |
-
"Export Whisper-medium to ONNX with dynamic axes and int8 weights; verify word-timestamp parity on 500 clips and measure CPU real-time factor improvement >=1.3\u00d7 vs PyTorch.": "Hard",
|
| 234 |
-
"Deploy a geo-replicated RAG service: shard FAISS HNSW across three regions with conflict-free index metadata sync; sustain 300 QPS with p95 latency <450 ms and recall@10 within 1% of single-region baseline.": "Very hard",
|
| 235 |
-
"Compare cased vs uncased tokenization for BERT on CoNLL-2003 NER; train both, and report F1, average tokens per sentence, and training time.": "Medium",
|
| 236 |
-
"Create a HuggingFace Datasets loader for EPUB files: extract chapter text and embedded images into Arrow columns, support streaming and deterministic sharding across 8 workers; verify identical row counts across two runs.": "Medium",
|
| 237 |
-
"Configure a Hub webhook to trigger CI when a model card (README.md) changes; fail the job if sections {intended use, limitations} are missing and post a checklist comment on the PR.": "Medium",
|
| 238 |
-
"Add a reranking cache to a RAG service keyed by (query, candidate_ids); achieve >=50% cache hit at 100 QPS and keep recall@10 within 0.5% of baseline.": "Hard",
|
| 239 |
-
"Fix torch.compile graph breaks in a Transformers training loop; patch non-compilable ops, re-enable compilation, and demonstrate >=1.4\u00d7 step-time speedup with matching loss over 2,000 steps.": "Hard",
|
| 240 |
-
"Compute 95% bootstrap confidence intervals for ROUGE-L on dataset D over 3 random seeds; flag regressions when the new CI lies entirely below last week's baseline CI.": "Medium",
|
| 241 |
-
"Build a batch image-captioning Space with ViT-GPT2: accept ZIP uploads, use queue-based batching, and keep p95 latency <2s for 32 images.": "Medium",
|
| 242 |
-
"Implement hybrid parallelism (tensor + pipeline) for a 13B encoder-decoder using Accelerate; scale across 8 GPUs with <=15% gap from linear, support elastic resize (8->6 GPUs) without losing determinism, and verify checkpoint save/restore.": "Very hard",
|
| 243 |
-
"Find five Spaces that stream live vision-language captioning (e.g., LLaVA or BLIP); return Space ids and reported FPS.": "Easy",
|
| 244 |
-
"Identify whether tokenizer T applies Unicode normalization (NFKC/NFC/NFD/NFKD) and where it is configured; output the mode and file path.": "Easy",
|
| 245 |
-
"Identify whether model repo M stores weights exclusively as safetensors; output true/false and list the .safetensors file paths.": "Easy",
|
| 246 |
-
"List three multilingual sentence-embedding models on the Hub that provide ONNX exports; return model ids.": "Easy",
|
| 247 |
-
"Determine if tokenizer T lowercases text (do_lower_case or lowercase flag); output true/false and the file path or JSON key where it is set.": "Easy",
|
| 248 |
-
"Set up a GitHub Action to run a smoke-test text generation for model M on each push; fail if median time to first token >2s and attach container logs as an artifact.": "Medium",
|
| 249 |
-
"Create a Datasets preprocessing pipeline that tokenizes to max_length=512 with stride=64 and retains an 'orig_text' column; verify row counts match input and no NaNs after caching.": "Medium",
|
| 250 |
-
"Resolve 'git-lfs: command not found' when pushing model repo R to the Hub; install and configure Git LFS, set an appropriate large file threshold, and provide a minimal repro plus the verified fix.": "Medium",
|
| 251 |
-
"Enable KV-cache CPU offloading in a custom Transformers generation loop for model M; benchmark tokens/sec and peak VRAM vs baseline at context lengths {4k, 8k}.": "Hard",
|
| 252 |
-
"Implement LoRA rank warmup (r: 4\u219232 over the first 1,000 steps) in a custom Trainer; fine-tune model M on dataset D and report validation perplexity and peak VRAM vs fixed r=32.": "Hard",
|
| 253 |
-
"Export Whisper-small to TensorRT via ONNX (opset 18) with dynamic axes; verify word-timestamp parity (median diff \u22640.05s) on 300 clips and measure \u22651.3\u00d7 GPU speedup vs PyTorch.": "Hard",
|
| 254 |
-
"Deploy a multi-tenant RAG service that hot-loads per-tenant FAISS indices from S3, shares a reranker, and sustains 200 QPS with p95 latency <350 ms across 1,000 tenants; maintain recall@10 within 1% of a single-tenant baseline.": "Very hard"
|
| 255 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/hf_agent_connector.py
DELETED
|
@@ -1,89 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import asyncio
|
| 4 |
-
import sys
|
| 5 |
-
from pathlib import Path
|
| 6 |
-
from typing import Any
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
from agent.config import Config, load_config
|
| 10 |
-
from agent.core.agent_loop import Handlers
|
| 11 |
-
from agent.core.session import Session
|
| 12 |
-
from agent.core.tools import ToolRouter
|
| 13 |
-
|
| 14 |
-
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 15 |
-
if str(PROJECT_ROOT) not in sys.path:
|
| 16 |
-
sys.path.insert(0, str(PROJECT_ROOT))
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
def _resolve_project_path(path: str | Path) -> Path:
|
| 20 |
-
candidate = Path(path)
|
| 21 |
-
if candidate.is_absolute():
|
| 22 |
-
return candidate
|
| 23 |
-
return (PROJECT_ROOT / candidate).resolve()
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
class AgentResponseGenerator:
|
| 27 |
-
"""
|
| 28 |
-
Thin async wrapper that executes the existing agent loop once and
|
| 29 |
-
returns the assistant's final message.
|
| 30 |
-
"""
|
| 31 |
-
|
| 32 |
-
def __init__(self, config_path: str | Path, max_iterations: int = 300) -> None:
|
| 33 |
-
self.config_path = _resolve_project_path(config_path)
|
| 34 |
-
self.config: Config = load_config(str(self.config_path))
|
| 35 |
-
self.max_iterations = max_iterations
|
| 36 |
-
|
| 37 |
-
@property
|
| 38 |
-
def model_name(self) -> str:
|
| 39 |
-
"""Expose the agent model name for downstream logging."""
|
| 40 |
-
return self.config.model_name
|
| 41 |
-
|
| 42 |
-
async def run(self, prompt: str) -> str:
|
| 43 |
-
"""
|
| 44 |
-
Execute the agent loop for a single prompt and return the assistant reply.
|
| 45 |
-
"""
|
| 46 |
-
tool_router = ToolRouter(self.config.mcpServers)
|
| 47 |
-
|
| 48 |
-
async with tool_router:
|
| 49 |
-
session = Session(asyncio.Queue(), config=self.config)
|
| 50 |
-
session.tool_router = tool_router
|
| 51 |
-
await Handlers.run_agent(
|
| 52 |
-
session,
|
| 53 |
-
prompt,
|
| 54 |
-
max_iterations=self.max_iterations,
|
| 55 |
-
)
|
| 56 |
-
return self._latest_assistant_response(session)
|
| 57 |
-
|
| 58 |
-
def _latest_assistant_response(self, session: Session) -> str:
|
| 59 |
-
"""
|
| 60 |
-
Extract the final assistant response from the session history.
|
| 61 |
-
"""
|
| 62 |
-
for message in reversed(session.context_manager.items):
|
| 63 |
-
if getattr(message, "role", None) == "assistant":
|
| 64 |
-
return _content_to_text(getattr(message, "content", ""))
|
| 65 |
-
|
| 66 |
-
raise RuntimeError("Agent did not produce an assistant message.")
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
def _content_to_text(content: Any) -> str:
|
| 70 |
-
"""
|
| 71 |
-
Convert LiteLLM content payloads (str or list[dict]) into plain text.
|
| 72 |
-
"""
|
| 73 |
-
if isinstance(content, str):
|
| 74 |
-
return content
|
| 75 |
-
|
| 76 |
-
if isinstance(content, list):
|
| 77 |
-
parts: list[str] = []
|
| 78 |
-
for block in content:
|
| 79 |
-
if isinstance(block, dict):
|
| 80 |
-
text = block.get("text")
|
| 81 |
-
if text:
|
| 82 |
-
parts.append(str(text))
|
| 83 |
-
else:
|
| 84 |
-
text = getattr(block, "text", None)
|
| 85 |
-
if text:
|
| 86 |
-
parts.append(str(text))
|
| 87 |
-
return "\n".join(parts)
|
| 88 |
-
|
| 89 |
-
return str(content)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/hf_io.py
DELETED
|
@@ -1,215 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
HuggingFace Dataset I/O Utilities
|
| 3 |
-
|
| 4 |
-
Reusable functions for uploading and downloading JSONL data to/from HuggingFace Hub.
|
| 5 |
-
Supports the dataset_name@config_name notation for managing multiple configurations.
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
from typing import List, Optional
|
| 9 |
-
|
| 10 |
-
import pandas as pd
|
| 11 |
-
from datasets import Dataset, load_dataset
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
def list_dataset_configs(dataset_name: str) -> Optional[List[str]]:
|
| 15 |
-
"""
|
| 16 |
-
List all available configs for a dataset on HuggingFace Hub.
|
| 17 |
-
|
| 18 |
-
Args:
|
| 19 |
-
dataset_name: Name of the dataset (e.g., "username/my-dataset")
|
| 20 |
-
|
| 21 |
-
Returns:
|
| 22 |
-
List of config names, or None if unable to retrieve
|
| 23 |
-
|
| 24 |
-
Example:
|
| 25 |
-
>>> configs = list_dataset_configs("username/hf-agent-benchmark")
|
| 26 |
-
>>> print(configs)
|
| 27 |
-
['default', 'rubrics', 'evaluations']
|
| 28 |
-
"""
|
| 29 |
-
try:
|
| 30 |
-
from datasets import get_dataset_config_names
|
| 31 |
-
|
| 32 |
-
configs = get_dataset_config_names(dataset_name)
|
| 33 |
-
return configs
|
| 34 |
-
except Exception as e:
|
| 35 |
-
print(f"✗ Failed to list configs: {type(e).__name__}: {str(e)}")
|
| 36 |
-
return None
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
def df_to_hub(
|
| 40 |
-
df: pd.DataFrame,
|
| 41 |
-
dataset_spec: str,
|
| 42 |
-
split: str = "train",
|
| 43 |
-
private: bool = False,
|
| 44 |
-
) -> bool:
|
| 45 |
-
"""
|
| 46 |
-
Upload a pandas DataFrame directly to HuggingFace Hub as a dataset.
|
| 47 |
-
|
| 48 |
-
This function converts a pandas DataFrame to a HuggingFace Dataset and uploads
|
| 49 |
-
it to the Hub. This is useful for uploading data directly without creating an
|
| 50 |
-
intermediate JSONL file.
|
| 51 |
-
|
| 52 |
-
Args:
|
| 53 |
-
df: pandas DataFrame to upload. All column types should be serializable.
|
| 54 |
-
Example DataFrame:
|
| 55 |
-
```
|
| 56 |
-
| question | solution | rubric |
|
| 57 |
-
|----------|----------|--------|
|
| 58 |
-
| "How..." | "You..." | {...} |
|
| 59 |
-
```
|
| 60 |
-
|
| 61 |
-
dataset_spec: Dataset specification in the format "dataset_name" or
|
| 62 |
-
"dataset_name@config_name". Examples:
|
| 63 |
-
- "username/my-dataset" (uses "default" config)
|
| 64 |
-
- "username/my-dataset@rubrics" (uses "rubrics" config)
|
| 65 |
-
- "username/my-dataset@evaluations" (uses "evaluations" config)
|
| 66 |
-
|
| 67 |
-
split: The dataset split name. Defaults to "train". Common values:
|
| 68 |
-
- "train": Training or main data
|
| 69 |
-
- "validation": Validation data
|
| 70 |
-
- "test": Test data
|
| 71 |
-
|
| 72 |
-
private: Whether to create a private dataset. Defaults to False (public).
|
| 73 |
-
|
| 74 |
-
Returns:
|
| 75 |
-
bool: True if upload succeeded, False otherwise
|
| 76 |
-
|
| 77 |
-
Raises:
|
| 78 |
-
ValueError: If DataFrame is empty
|
| 79 |
-
Exception: For HuggingFace Hub upload errors
|
| 80 |
-
|
| 81 |
-
Example:
|
| 82 |
-
>>> import pandas as pd
|
| 83 |
-
>>> df = pd.DataFrame({
|
| 84 |
-
... "question": ["How to train?", "What is fine-tuning?"],
|
| 85 |
-
... "solution": ["Use trainer...", "Fine-tuning is..."],
|
| 86 |
-
... "rubric": ['[{"title": "...", ...}]', '[{"title": "...", ...}]']
|
| 87 |
-
... })
|
| 88 |
-
>>> upload_dataframe_to_hf(df, "username/dataset@rubrics")
|
| 89 |
-
|
| 90 |
-
Notes:
|
| 91 |
-
- Requires authentication via `huggingface-cli login` or HF_TOKEN env var
|
| 92 |
-
- DataFrame columns with complex objects should be serialized first (e.g., to JSON strings)
|
| 93 |
-
- If the dataset doesn't exist, it will be created automatically
|
| 94 |
-
- Empty DataFrames will raise ValueError to prevent uploading invalid data
|
| 95 |
-
"""
|
| 96 |
-
# Validate DataFrame
|
| 97 |
-
if df.empty:
|
| 98 |
-
raise ValueError("DataFrame is empty")
|
| 99 |
-
|
| 100 |
-
# Parse dataset specification
|
| 101 |
-
if "@" in dataset_spec:
|
| 102 |
-
dataset_name, config_name = dataset_spec.split("@", 1)
|
| 103 |
-
else:
|
| 104 |
-
dataset_name = dataset_spec
|
| 105 |
-
config_name = "default"
|
| 106 |
-
|
| 107 |
-
try:
|
| 108 |
-
print("\nUploading DataFrame to HuggingFace Hub...")
|
| 109 |
-
print(f" Dataset: {dataset_name}")
|
| 110 |
-
print(f" Config: {config_name}")
|
| 111 |
-
print(f" Split: {split}")
|
| 112 |
-
print(f" Rows: {len(df)}")
|
| 113 |
-
print(f" Columns: {list(df.columns)}")
|
| 114 |
-
|
| 115 |
-
# Convert DataFrame to HuggingFace Dataset
|
| 116 |
-
dataset = Dataset.from_pandas(df)
|
| 117 |
-
|
| 118 |
-
# Upload to HuggingFace Hub
|
| 119 |
-
dataset.push_to_hub(
|
| 120 |
-
dataset_name,
|
| 121 |
-
config_name=config_name,
|
| 122 |
-
split=split,
|
| 123 |
-
private=private,
|
| 124 |
-
)
|
| 125 |
-
|
| 126 |
-
print(
|
| 127 |
-
f"✓ Successfully uploaded to {dataset_name}@{config_name} (split: {split})"
|
| 128 |
-
)
|
| 129 |
-
return True
|
| 130 |
-
|
| 131 |
-
except Exception as e:
|
| 132 |
-
print(f"✗ Failed to upload to HuggingFace: {type(e).__name__}: {str(e)}")
|
| 133 |
-
return False
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
def hub_to_df(
|
| 137 |
-
dataset_spec: str,
|
| 138 |
-
split: str = "train",
|
| 139 |
-
) -> Optional[pd.DataFrame]:
|
| 140 |
-
"""
|
| 141 |
-
Download a dataset from HuggingFace Hub as a pandas DataFrame.
|
| 142 |
-
|
| 143 |
-
This function downloads a dataset from the HuggingFace Hub and returns it as a
|
| 144 |
-
pandas DataFrame for immediate use in Python.
|
| 145 |
-
|
| 146 |
-
Args:
|
| 147 |
-
dataset_spec: Dataset specification in the format "dataset_name" or
|
| 148 |
-
"dataset_name@config_name". Examples:
|
| 149 |
-
- "username/my-dataset" (uses "default" config)
|
| 150 |
-
- "username/my-dataset@rubrics" (uses "rubrics" config)
|
| 151 |
-
- "username/my-dataset@evaluations" (uses "evaluations" config)
|
| 152 |
-
|
| 153 |
-
split: The dataset split to download. Defaults to "train". Common values:
|
| 154 |
-
- "train": Training or main data
|
| 155 |
-
- "validation": Validation data
|
| 156 |
-
- "test": Test data
|
| 157 |
-
|
| 158 |
-
Returns:
|
| 159 |
-
pd.DataFrame: Downloaded data as pandas DataFrame, or None if failed
|
| 160 |
-
|
| 161 |
-
Raises:
|
| 162 |
-
ValueError: If the dataset/config/split doesn't exist
|
| 163 |
-
Exception: For HuggingFace Hub download errors
|
| 164 |
-
|
| 165 |
-
Example:
|
| 166 |
-
>>> # Download rubrics from specific config
|
| 167 |
-
>>> df = hub_to_df("username/hf-agent-benchmark@rubrics")
|
| 168 |
-
>>> print(df.head())
|
| 169 |
-
>>> print(f"Shape: {df.shape}")
|
| 170 |
-
|
| 171 |
-
>>> # Download evaluation results
|
| 172 |
-
>>> results_df = download_hf_to_dataframe(
|
| 173 |
-
... "username/hf-agent-benchmark@evaluations",
|
| 174 |
-
... split="test"
|
| 175 |
-
... )
|
| 176 |
-
|
| 177 |
-
Notes:
|
| 178 |
-
- Requires authentication for private datasets via `huggingface-cli login`
|
| 179 |
-
- Downloaded data will be in the same format as uploaded (preserves structure)
|
| 180 |
-
- Large datasets may take time to download and consume significant memory
|
| 181 |
-
- For very large datasets, consider using streaming or download_hf_to_jsonl
|
| 182 |
-
"""
|
| 183 |
-
# Parse dataset specification
|
| 184 |
-
if "@" in dataset_spec:
|
| 185 |
-
dataset_name, config_name = dataset_spec.split("@", 1)
|
| 186 |
-
else:
|
| 187 |
-
dataset_name = dataset_spec
|
| 188 |
-
config_name = "default"
|
| 189 |
-
|
| 190 |
-
try:
|
| 191 |
-
print("\nDownloading from HuggingFace Hub...")
|
| 192 |
-
print(f" Dataset: {dataset_name}")
|
| 193 |
-
print(f" Config: {config_name}")
|
| 194 |
-
print(f" Split: {split}")
|
| 195 |
-
|
| 196 |
-
# Download dataset from HuggingFace Hub
|
| 197 |
-
dataset = load_dataset(
|
| 198 |
-
dataset_name,
|
| 199 |
-
name=config_name,
|
| 200 |
-
split=split,
|
| 201 |
-
)
|
| 202 |
-
|
| 203 |
-
print(f" Downloaded {len(dataset)} records")
|
| 204 |
-
|
| 205 |
-
# Convert to pandas DataFrame
|
| 206 |
-
df = dataset.to_pandas()
|
| 207 |
-
|
| 208 |
-
print("✓ Successfully loaded as DataFrame")
|
| 209 |
-
print(f" Shape: {df.shape}")
|
| 210 |
-
print(f" Columns: {list(df.columns)}")
|
| 211 |
-
return df
|
| 212 |
-
|
| 213 |
-
except Exception as e:
|
| 214 |
-
print(f"✗ Failed to download from HuggingFace: {type(e).__name__}: {str(e)}")
|
| 215 |
-
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/leaderboard.py
DELETED
|
@@ -1,172 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Utilities for logging solver scores to a Hugging Face dataset.
|
| 3 |
-
"""
|
| 4 |
-
|
| 5 |
-
from __future__ import annotations
|
| 6 |
-
|
| 7 |
-
import json
|
| 8 |
-
import re
|
| 9 |
-
import shutil
|
| 10 |
-
import subprocess
|
| 11 |
-
import tempfile
|
| 12 |
-
from dataclasses import dataclass
|
| 13 |
-
from datetime import datetime, timezone
|
| 14 |
-
from pathlib import Path
|
| 15 |
-
from typing import Any
|
| 16 |
-
|
| 17 |
-
from huggingface_hub import HfApi, hf_hub_download
|
| 18 |
-
|
| 19 |
-
AVERAGE_RE = re.compile(r"Average normalized score:\s*([0-9.]+)")
|
| 20 |
-
DEFAULT_FILENAME = "records.jsonl"
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def _hydra_join(*parts: str | None) -> str:
|
| 24 |
-
tokens = [str(part).strip().replace(" ", "_") for part in parts if part]
|
| 25 |
-
return "/".join(tokens) if tokens else "default"
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
def detect_agent_version(config_path: str = "agent/config_mcp_example.json") -> str:
|
| 29 |
-
"""
|
| 30 |
-
Returns a short string identifying the current agent version:
|
| 31 |
-
<git short sha>-<config hash>.
|
| 32 |
-
"""
|
| 33 |
-
|
| 34 |
-
try:
|
| 35 |
-
commit = (
|
| 36 |
-
subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
|
| 37 |
-
.decode()
|
| 38 |
-
.strip()
|
| 39 |
-
)
|
| 40 |
-
except Exception:
|
| 41 |
-
commit = "unknown"
|
| 42 |
-
|
| 43 |
-
config_file = Path(config_path)
|
| 44 |
-
config_stem = config_file.stem or "config"
|
| 45 |
-
parent_name = config_file.parent.name if config_file.parent.name else None
|
| 46 |
-
return _hydra_join(parent_name, config_stem, commit)
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
def parse_average_score(text: str) -> float | None:
|
| 50 |
-
"""Extracts the 'Average normalized score' value from Inspect logs."""
|
| 51 |
-
|
| 52 |
-
match = AVERAGE_RE.search(text)
|
| 53 |
-
if match:
|
| 54 |
-
try:
|
| 55 |
-
return float(match.group(1))
|
| 56 |
-
except ValueError:
|
| 57 |
-
return None
|
| 58 |
-
return None
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
def latest_log_file(
|
| 62 |
-
log_dir: Path, extensions: tuple[str, ...] = (".eval", ".json")
|
| 63 |
-
) -> Path | None:
|
| 64 |
-
"""Returns the most recent log file in log_dir matching the provided extensions."""
|
| 65 |
-
|
| 66 |
-
if not log_dir.exists():
|
| 67 |
-
return None
|
| 68 |
-
|
| 69 |
-
files: list[Path] = []
|
| 70 |
-
for ext in extensions:
|
| 71 |
-
files.extend(log_dir.glob(f"*{ext}"))
|
| 72 |
-
|
| 73 |
-
if not files:
|
| 74 |
-
return None
|
| 75 |
-
|
| 76 |
-
files.sort(key=lambda path: path.stat().st_mtime)
|
| 77 |
-
return files[-1]
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
@dataclass
|
| 81 |
-
class LeaderboardClient:
|
| 82 |
-
"""Simple helper to append JSONL rows to a HF dataset."""
|
| 83 |
-
|
| 84 |
-
repo_id: str
|
| 85 |
-
token: str
|
| 86 |
-
filename: str = DEFAULT_FILENAME
|
| 87 |
-
|
| 88 |
-
def append_record(self, record: dict[str, Any]) -> None:
|
| 89 |
-
tmp_dir = Path(tempfile.mkdtemp(prefix="leaderboard_"))
|
| 90 |
-
local_file = tmp_dir / self.filename
|
| 91 |
-
|
| 92 |
-
self._download_existing(local_file)
|
| 93 |
-
if not local_file.exists():
|
| 94 |
-
local_file.write_text("", encoding="utf-8")
|
| 95 |
-
|
| 96 |
-
with local_file.open("a", encoding="utf-8") as fh:
|
| 97 |
-
fh.write(json.dumps(record) + "\n")
|
| 98 |
-
|
| 99 |
-
HfApi(token=self.token).upload_file(
|
| 100 |
-
path_or_fileobj=str(local_file),
|
| 101 |
-
path_in_repo=self.filename,
|
| 102 |
-
repo_id=self.repo_id,
|
| 103 |
-
repo_type="dataset",
|
| 104 |
-
)
|
| 105 |
-
|
| 106 |
-
try:
|
| 107 |
-
local_file.unlink()
|
| 108 |
-
tmp_dir.rmdir()
|
| 109 |
-
except OSError:
|
| 110 |
-
pass
|
| 111 |
-
|
| 112 |
-
def _download_existing(self, destination: Path) -> None:
|
| 113 |
-
destination.parent.mkdir(parents=True, exist_ok=True)
|
| 114 |
-
|
| 115 |
-
try:
|
| 116 |
-
downloaded = hf_hub_download(
|
| 117 |
-
repo_id=self.repo_id,
|
| 118 |
-
filename=self.filename,
|
| 119 |
-
repo_type="dataset",
|
| 120 |
-
token=self.token,
|
| 121 |
-
)
|
| 122 |
-
shutil.copy(Path(downloaded), destination)
|
| 123 |
-
except Exception:
|
| 124 |
-
destination.write_text("", encoding="utf-8")
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
def build_record(
|
| 128 |
-
solver_name: str,
|
| 129 |
-
solver_kwargs: dict[str, Any],
|
| 130 |
-
dataset_name: str,
|
| 131 |
-
dataset_split: str,
|
| 132 |
-
limit: int | None,
|
| 133 |
-
score: float,
|
| 134 |
-
command: list[str],
|
| 135 |
-
log_path: Path | None,
|
| 136 |
-
criterion_checks: list[dict[str, Any]] | None = None,
|
| 137 |
-
) -> dict[str, Any]:
|
| 138 |
-
"""Assembles a JSON-serialisable record for the leaderboard dataset."""
|
| 139 |
-
|
| 140 |
-
record = {
|
| 141 |
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
| 142 |
-
"solver": solver_name,
|
| 143 |
-
"solver_kwargs": solver_kwargs,
|
| 144 |
-
"dataset_name": dataset_name,
|
| 145 |
-
"dataset_split": dataset_split,
|
| 146 |
-
"limit": limit,
|
| 147 |
-
"score": score,
|
| 148 |
-
"command": command,
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
-
if solver_name == "hf_agent":
|
| 152 |
-
record["solver_version"] = detect_agent_version(
|
| 153 |
-
solver_kwargs.get("config_path", "agent/config_mcp_example.json")
|
| 154 |
-
)
|
| 155 |
-
else:
|
| 156 |
-
version_spec = solver_kwargs.get("version")
|
| 157 |
-
if isinstance(version_spec, (list, tuple)):
|
| 158 |
-
record["solver_version"] = _hydra_join(*version_spec)
|
| 159 |
-
elif isinstance(version_spec, dict):
|
| 160 |
-
record["solver_version"] = _hydra_join(
|
| 161 |
-
*[f"{k}={v}" for k, v in version_spec.items()]
|
| 162 |
-
)
|
| 163 |
-
elif isinstance(version_spec, str):
|
| 164 |
-
record["solver_version"] = version_spec
|
| 165 |
-
else:
|
| 166 |
-
record["solver_version"] = _hydra_join(solver_name, "default")
|
| 167 |
-
|
| 168 |
-
if log_path:
|
| 169 |
-
record["log_artifact"] = str(log_path)
|
| 170 |
-
record["criterion_checks"] = criterion_checks or []
|
| 171 |
-
|
| 172 |
-
return record
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/models.py
DELETED
|
@@ -1,63 +0,0 @@
|
|
| 1 |
-
"""Shared data models for the HF agent project"""
|
| 2 |
-
|
| 3 |
-
from datetime import datetime
|
| 4 |
-
from enum import Enum
|
| 5 |
-
|
| 6 |
-
from pydantic import BaseModel, Field
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
class Discussion(BaseModel):
|
| 10 |
-
"""Model for a discussion thread"""
|
| 11 |
-
|
| 12 |
-
title: str
|
| 13 |
-
url: str
|
| 14 |
-
topic_id: int
|
| 15 |
-
category: int
|
| 16 |
-
created_at: datetime
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
class QuestionAndSolution(BaseModel):
|
| 20 |
-
"""Model for a QA pair from a discussion"""
|
| 21 |
-
|
| 22 |
-
discussion_title: str
|
| 23 |
-
discussion_url: str
|
| 24 |
-
discussion_topic_id: int
|
| 25 |
-
discussion_category: int
|
| 26 |
-
discussion_created_at: datetime
|
| 27 |
-
thread: list[dict]
|
| 28 |
-
question: str
|
| 29 |
-
solution: str
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
class Correctness(str, Enum):
|
| 33 |
-
yes = "yes"
|
| 34 |
-
no = "no"
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
class JudgementResult(BaseModel):
|
| 38 |
-
"""Structured output for LLM judge evaluation"""
|
| 39 |
-
|
| 40 |
-
extracted_final_answer: str = Field(
|
| 41 |
-
description="The final exact/snippet answer extracted from the response"
|
| 42 |
-
)
|
| 43 |
-
reasoning: str = Field(
|
| 44 |
-
description="Explanation of why the answer is correct or incorrect"
|
| 45 |
-
)
|
| 46 |
-
correct: Correctness = Field(description="'yes' if correct, 'no' if incorrect")
|
| 47 |
-
confidence: int = Field(
|
| 48 |
-
description="Confidence score between 0 and 100", ge=0, le=100
|
| 49 |
-
)
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
class EvaluationResult(BaseModel):
|
| 53 |
-
"""Model for evaluation results including metadata"""
|
| 54 |
-
|
| 55 |
-
success: bool
|
| 56 |
-
judgement: JudgementResult | None = None
|
| 57 |
-
error: str | None = None
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
class EvaluatedQuestionAndSolution(QuestionAndSolution):
|
| 61 |
-
"""Model for a QA pair with its evaluation result"""
|
| 62 |
-
|
| 63 |
-
evaluation: JudgementResult
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/rubric_eval.py
DELETED
|
@@ -1,142 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Rubric-based evaluation following the "Rubrics as Rewards" paper.
|
| 3 |
-
|
| 4 |
-
Implements RaR-Explicit: Weighted sum of individual criterion scores (Equation 1)
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
from typing import List, Optional
|
| 8 |
-
|
| 9 |
-
import litellm
|
| 10 |
-
from pydantic import BaseModel
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
class CriterionCheck(BaseModel):
|
| 14 |
-
"""Result of checking a single rubric criterion."""
|
| 15 |
-
|
| 16 |
-
title: str
|
| 17 |
-
description: str
|
| 18 |
-
weight: int
|
| 19 |
-
satisfied: bool
|
| 20 |
-
reasoning: Optional[str] = None
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
class RubricEvaluation(BaseModel):
|
| 24 |
-
"""Complete rubric-based evaluation result."""
|
| 25 |
-
|
| 26 |
-
criterion_checks: List[CriterionCheck]
|
| 27 |
-
raw_score: float # Unnormalized score
|
| 28 |
-
normalized_score: float # Score normalized to [0, 1]
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
CRITERION_PROMPT = """You are evaluating whether a response satisfies a specific evaluation criterion.
|
| 32 |
-
|
| 33 |
-
Question: {question}
|
| 34 |
-
|
| 35 |
-
Response to evaluate: {response}
|
| 36 |
-
|
| 37 |
-
Evaluation Criterion:
|
| 38 |
-
{criterion_description}
|
| 39 |
-
|
| 40 |
-
Your task: Determine if the response satisfies this criterion.
|
| 41 |
-
|
| 42 |
-
Output a JSON object with:
|
| 43 |
-
- "satisfied": true or false
|
| 44 |
-
- "reasoning": Brief explanation (1-2 sentences) of why it does or doesn't satisfy the criterion
|
| 45 |
-
|
| 46 |
-
Be strict but fair. The criterion must be clearly satisfied for you to answer true."""
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
class RubricData(BaseModel):
|
| 50 |
-
"""Rubric data loaded from file."""
|
| 51 |
-
|
| 52 |
-
title: str
|
| 53 |
-
description: str
|
| 54 |
-
weight: int
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
def check_criterion(
|
| 58 |
-
question: str, response: str, criterion: RubricData, model: str = "gpt-4o-mini"
|
| 59 |
-
) -> CriterionCheck:
|
| 60 |
-
"""
|
| 61 |
-
Check if response satisfies a single criterion.
|
| 62 |
-
|
| 63 |
-
Args:
|
| 64 |
-
question: The question being answered
|
| 65 |
-
response: The response to evaluate
|
| 66 |
-
criterion: The rubric criterion to check
|
| 67 |
-
model: LLM model for judging
|
| 68 |
-
|
| 69 |
-
Returns:
|
| 70 |
-
CriterionCheck with satisfaction result
|
| 71 |
-
"""
|
| 72 |
-
prompt = CRITERION_PROMPT.format(
|
| 73 |
-
question=question,
|
| 74 |
-
response=response,
|
| 75 |
-
criterion_description=criterion.description,
|
| 76 |
-
)
|
| 77 |
-
|
| 78 |
-
llm_response = litellm.completion(
|
| 79 |
-
model=model,
|
| 80 |
-
messages=[
|
| 81 |
-
{
|
| 82 |
-
"role": "system",
|
| 83 |
-
"content": "You are an expert evaluator for rubric-based assessment.",
|
| 84 |
-
},
|
| 85 |
-
{"role": "user", "content": prompt},
|
| 86 |
-
],
|
| 87 |
-
temperature=0.0,
|
| 88 |
-
response_format=CriterionCheck,
|
| 89 |
-
)
|
| 90 |
-
|
| 91 |
-
result = CriterionCheck.model_validate_json(llm_response.choices[0].message.content)
|
| 92 |
-
|
| 93 |
-
return result
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
def evaluate_with_rubrics(
|
| 97 |
-
question: str,
|
| 98 |
-
response: str,
|
| 99 |
-
rubrics: List[RubricData],
|
| 100 |
-
model: str = "gpt-5-nano",
|
| 101 |
-
) -> RubricEvaluation:
|
| 102 |
-
"""
|
| 103 |
-
Evaluate response using RaR-Explicit method (weighted sum).
|
| 104 |
-
|
| 105 |
-
Implements Equation 1 from paper:
|
| 106 |
-
r(x, ŷ) = Σ(w_j * c_j(x, ŷ)) / Σ(w_j)
|
| 107 |
-
|
| 108 |
-
Args:
|
| 109 |
-
question: The question
|
| 110 |
-
response: Response to evaluate
|
| 111 |
-
reference_answer: Reference answer (not directly used, but available)
|
| 112 |
-
rubrics: List of rubric criteria
|
| 113 |
-
model: LLM model for judging
|
| 114 |
-
|
| 115 |
-
Returns:
|
| 116 |
-
RubricEvaluation with normalized score
|
| 117 |
-
"""
|
| 118 |
-
# Check each criterion independently
|
| 119 |
-
checks = []
|
| 120 |
-
for rubric in rubrics:
|
| 121 |
-
check = check_criterion(question, response, rubric, model)
|
| 122 |
-
checks.append(check)
|
| 123 |
-
|
| 124 |
-
# Calculate weighted score (Equation 1)
|
| 125 |
-
# Only positive weights contribute to denominator
|
| 126 |
-
positive_weights = sum(abs(r.weight) for r in rubrics if r.weight > 0)
|
| 127 |
-
|
| 128 |
-
raw_score = 0.0
|
| 129 |
-
for check in checks:
|
| 130 |
-
if check.satisfied:
|
| 131 |
-
raw_score += check.weight
|
| 132 |
-
|
| 133 |
-
# Normalize to [0, 1]
|
| 134 |
-
normalized_score = raw_score / positive_weights if positive_weights > 0 else 0.0
|
| 135 |
-
# Clip to [0, 1] in case pitfalls make it negative
|
| 136 |
-
normalized_score = max(0.0, min(1.0, normalized_score))
|
| 137 |
-
|
| 138 |
-
return RubricEvaluation(
|
| 139 |
-
raw_score=raw_score,
|
| 140 |
-
normalized_score=normalized_score,
|
| 141 |
-
criterion_checks=checks,
|
| 142 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/run_eval_with_leaderboard.py
DELETED
|
@@ -1,215 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import argparse
|
| 4 |
-
import json
|
| 5 |
-
import os
|
| 6 |
-
import re
|
| 7 |
-
import subprocess
|
| 8 |
-
import sys
|
| 9 |
-
from pathlib import Path
|
| 10 |
-
from typing import Any
|
| 11 |
-
|
| 12 |
-
from dotenv import load_dotenv
|
| 13 |
-
from leaderboard import LeaderboardClient, build_record, latest_log_file
|
| 14 |
-
|
| 15 |
-
load_dotenv()
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
def run_command(cmd: list[str]) -> subprocess.CompletedProcess[str]:
|
| 19 |
-
print(f"[leaderboard] running: {' '.join(cmd)}")
|
| 20 |
-
return subprocess.run(cmd, capture_output=True, text=True)
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def build_inspect_command(args: argparse.Namespace) -> list[str]:
|
| 24 |
-
cmd = []
|
| 25 |
-
cmd.extend(args.inspect_launch)
|
| 26 |
-
cmd.append(args.inspect_task)
|
| 27 |
-
|
| 28 |
-
def add_task_arg(key: str, value: Any) -> None:
|
| 29 |
-
if value is None:
|
| 30 |
-
return
|
| 31 |
-
cmd.extend(["-T", f"{key}={value}"])
|
| 32 |
-
|
| 33 |
-
add_task_arg("solver_name", args.solver_name)
|
| 34 |
-
add_task_arg("solver_kwargs", json.dumps(args.solver_kwargs))
|
| 35 |
-
add_task_arg("dataset_name", args.dataset)
|
| 36 |
-
if args.limit is not None:
|
| 37 |
-
add_task_arg("limit", args.limit)
|
| 38 |
-
|
| 39 |
-
cmd.extend(["--log-dir", args.log_dir])
|
| 40 |
-
if args.log_format:
|
| 41 |
-
cmd.extend(["--log-format", args.log_format])
|
| 42 |
-
|
| 43 |
-
if args.extra_inspect_args:
|
| 44 |
-
cmd.extend(args.extra_inspect_args)
|
| 45 |
-
|
| 46 |
-
return cmd
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
def parse_score_from_outputs(log_dir: Path) -> tuple[float, Path, list[dict[str, Any]]]:
|
| 50 |
-
log_path = latest_log_file(log_dir)
|
| 51 |
-
if not log_path:
|
| 52 |
-
raise RuntimeError("Inspect log file not found.")
|
| 53 |
-
|
| 54 |
-
# Sanitization
|
| 55 |
-
content = log_path.read_text(encoding="utf-8")
|
| 56 |
-
# Regex to match hf_ followed by 34 alphanumeric chars
|
| 57 |
-
sanitized_content = re.sub(r"hf_[a-zA-Z0-9]{34}", "<REDACTED_TOKEN>", content)
|
| 58 |
-
|
| 59 |
-
if content != sanitized_content:
|
| 60 |
-
log_path.write_text(sanitized_content, encoding="utf-8")
|
| 61 |
-
print(f"[leaderboard] Redacted HF tokens in {log_path}")
|
| 62 |
-
content = sanitized_content
|
| 63 |
-
|
| 64 |
-
data = json.loads(content)
|
| 65 |
-
results = data.get("results", {})
|
| 66 |
-
scores = results.get("scores", [])
|
| 67 |
-
score_value = None
|
| 68 |
-
criterion_checks: list[dict[str, Any]] = []
|
| 69 |
-
|
| 70 |
-
for score_entry in scores:
|
| 71 |
-
metrics = score_entry.get("metrics", {})
|
| 72 |
-
for metric in metrics.values():
|
| 73 |
-
value = metric.get("value")
|
| 74 |
-
if isinstance(value, (int, float)):
|
| 75 |
-
score_value = float(value)
|
| 76 |
-
break
|
| 77 |
-
if score_value is not None:
|
| 78 |
-
break
|
| 79 |
-
|
| 80 |
-
if score_value is None:
|
| 81 |
-
raise RuntimeError("Could not find a numeric metric value in the Inspect log.")
|
| 82 |
-
|
| 83 |
-
for sample in data.get("samples", []):
|
| 84 |
-
# Grab the question from metadata (fallback to input)
|
| 85 |
-
question = "Unknown Question"
|
| 86 |
-
if "metadata" in sample and "question" in sample["metadata"]:
|
| 87 |
-
question = sample["metadata"]["question"]
|
| 88 |
-
elif "input" in sample:
|
| 89 |
-
question = sample["input"]
|
| 90 |
-
|
| 91 |
-
# Check if any scorer produced criterion_checks
|
| 92 |
-
for scorer in sample.get("scores", {}).values():
|
| 93 |
-
metadata = scorer.get("metadata") or {}
|
| 94 |
-
checks = metadata.get("criterion_checks")
|
| 95 |
-
|
| 96 |
-
if isinstance(checks, list) and checks:
|
| 97 |
-
# Create a grouped entry for this question/sample
|
| 98 |
-
grouped_entry = {"question": question, "checks": []}
|
| 99 |
-
for check in checks:
|
| 100 |
-
if isinstance(check, dict):
|
| 101 |
-
grouped_entry["checks"].append(check)
|
| 102 |
-
|
| 103 |
-
if grouped_entry["checks"]:
|
| 104 |
-
criterion_checks.append(grouped_entry)
|
| 105 |
-
|
| 106 |
-
return score_value, log_path, criterion_checks
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
def main() -> None:
|
| 110 |
-
parser = argparse.ArgumentParser(
|
| 111 |
-
description="Run Inspect eval and append the resulting score to a HF dataset."
|
| 112 |
-
)
|
| 113 |
-
parser.add_argument(
|
| 114 |
-
"--hf-dataset",
|
| 115 |
-
default="akseljoonas/hf-agent-leaderboard",
|
| 116 |
-
help="HF dataset repo id for the leaderboard (e.g. user/leaderboard).",
|
| 117 |
-
)
|
| 118 |
-
|
| 119 |
-
parser.add_argument(
|
| 120 |
-
"--solver-name",
|
| 121 |
-
required=True,
|
| 122 |
-
help="Solver name used in the Inspect task (e.g. hf_agent).",
|
| 123 |
-
)
|
| 124 |
-
parser.add_argument(
|
| 125 |
-
"--solver-kwargs",
|
| 126 |
-
type=json.loads,
|
| 127 |
-
default="{}",
|
| 128 |
-
help="JSON string with solver kwargs passed to the Inspect task.",
|
| 129 |
-
)
|
| 130 |
-
parser.add_argument(
|
| 131 |
-
"--dataset",
|
| 132 |
-
default="akseljoonas/hf-agent-rubrics@train",
|
| 133 |
-
help="Dataset spec in the form author/dataset@split.",
|
| 134 |
-
)
|
| 135 |
-
parser.add_argument(
|
| 136 |
-
"--limit",
|
| 137 |
-
type=int,
|
| 138 |
-
default=None,
|
| 139 |
-
help="Optional sample limit passed to Inspect.",
|
| 140 |
-
)
|
| 141 |
-
parser.add_argument(
|
| 142 |
-
"--inspect-task",
|
| 143 |
-
default="eval/task.py@hf-benchmark-with-rubrics",
|
| 144 |
-
help="Inspect task reference.",
|
| 145 |
-
)
|
| 146 |
-
parser.add_argument(
|
| 147 |
-
"--inspect-launch",
|
| 148 |
-
nargs="+",
|
| 149 |
-
default=["uv", "run", "inspect", "eval"],
|
| 150 |
-
help="Command used to invoke Inspect (default: uv run inspect eval).",
|
| 151 |
-
)
|
| 152 |
-
parser.add_argument(
|
| 153 |
-
"--log-dir",
|
| 154 |
-
default="logs/leaderboard",
|
| 155 |
-
help="Directory where Inspect outputs .eval logs.",
|
| 156 |
-
)
|
| 157 |
-
parser.add_argument(
|
| 158 |
-
"--extra-inspect-args",
|
| 159 |
-
nargs="*",
|
| 160 |
-
help="Additional args forwarded to Inspect after the standard task arguments.",
|
| 161 |
-
)
|
| 162 |
-
parser.add_argument(
|
| 163 |
-
"--log-format",
|
| 164 |
-
default="json",
|
| 165 |
-
help="Log format passed to Inspect (default: json).",
|
| 166 |
-
)
|
| 167 |
-
|
| 168 |
-
args = parser.parse_args()
|
| 169 |
-
|
| 170 |
-
if isinstance(args.solver_kwargs, str):
|
| 171 |
-
args.solver_kwargs = json.loads(args.solver_kwargs or "{}")
|
| 172 |
-
|
| 173 |
-
hf_token = os.getenv("HF_TOKEN")
|
| 174 |
-
if not hf_token:
|
| 175 |
-
print("ERROR: set HF_TOKEN in your environment.", file=sys.stderr)
|
| 176 |
-
sys.exit(1)
|
| 177 |
-
|
| 178 |
-
if "@" not in args.dataset:
|
| 179 |
-
raise ValueError("Dataset must be in the format 'author/dataset@split'.")
|
| 180 |
-
dataset_name, dataset_split = args.dataset.split("@", 1)
|
| 181 |
-
|
| 182 |
-
log_dir = Path(args.log_dir)
|
| 183 |
-
log_dir.mkdir(parents=True, exist_ok=True)
|
| 184 |
-
|
| 185 |
-
inspect_cmd = build_inspect_command(args)
|
| 186 |
-
result = run_command(inspect_cmd)
|
| 187 |
-
|
| 188 |
-
if result.returncode != 0:
|
| 189 |
-
print(result.stdout)
|
| 190 |
-
print(result.stderr, file=sys.stderr)
|
| 191 |
-
raise SystemExit(result.returncode)
|
| 192 |
-
|
| 193 |
-
score, log_path, criterion_checks = parse_score_from_outputs(log_dir)
|
| 194 |
-
|
| 195 |
-
client = LeaderboardClient(repo_id=args.hf_dataset, token=hf_token)
|
| 196 |
-
record = build_record(
|
| 197 |
-
solver_name=args.solver_name,
|
| 198 |
-
solver_kwargs=args.solver_kwargs,
|
| 199 |
-
dataset_name=dataset_name,
|
| 200 |
-
dataset_split=dataset_split,
|
| 201 |
-
limit=args.limit,
|
| 202 |
-
score=score,
|
| 203 |
-
command=inspect_cmd,
|
| 204 |
-
log_path=log_path,
|
| 205 |
-
criterion_checks=criterion_checks,
|
| 206 |
-
)
|
| 207 |
-
client.append_record(record)
|
| 208 |
-
|
| 209 |
-
print(
|
| 210 |
-
f"[leaderboard] recorded score {score:.3f} for solver '{args.solver_name}' to {args.hf_dataset}"
|
| 211 |
-
)
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
if __name__ == "__main__":
|
| 215 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/scrape_discussions/discussions_scraper.py
DELETED
|
@@ -1,98 +0,0 @@
|
|
| 1 |
-
import sys
|
| 2 |
-
import time
|
| 3 |
-
from pathlib import Path
|
| 4 |
-
|
| 5 |
-
import requests
|
| 6 |
-
from tenacity import (
|
| 7 |
-
retry,
|
| 8 |
-
retry_if_exception_type,
|
| 9 |
-
stop_after_attempt,
|
| 10 |
-
wait_exponential,
|
| 11 |
-
)
|
| 12 |
-
|
| 13 |
-
# Add parent directory to path to import models
|
| 14 |
-
sys.path.insert(0, str(Path(__file__).parent.parent))
|
| 15 |
-
from models import Discussion, QuestionAndSolution
|
| 16 |
-
|
| 17 |
-
BASE_URL = "https://discuss.huggingface.co"
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
# configure retry decorator for your requests
|
| 21 |
-
@retry(
|
| 22 |
-
stop=stop_after_attempt(5),
|
| 23 |
-
wait=wait_exponential(multiplier=1, min=1, max=60),
|
| 24 |
-
retry=retry_if_exception_type(requests.HTTPError),
|
| 25 |
-
)
|
| 26 |
-
def safe_get(url, **kwargs):
|
| 27 |
-
resp = requests.get(url, **kwargs)
|
| 28 |
-
if resp.status_code == 422:
|
| 29 |
-
# read retry‐after header if present
|
| 30 |
-
retry_after = resp.headers.get("Retry-After")
|
| 31 |
-
if retry_after:
|
| 32 |
-
delay = float(retry_after)
|
| 33 |
-
else:
|
| 34 |
-
# fallback to guess
|
| 35 |
-
delay = 30
|
| 36 |
-
print(f"429 hit — waiting {delay} seconds...")
|
| 37 |
-
time.sleep(delay)
|
| 38 |
-
resp.raise_for_status()
|
| 39 |
-
else:
|
| 40 |
-
resp.raise_for_status()
|
| 41 |
-
return resp
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
def get_solved_discussions(n_posts: int = 50):
|
| 45 |
-
page = 1
|
| 46 |
-
discussions = []
|
| 47 |
-
while len(discussions) < n_posts:
|
| 48 |
-
url = f"{BASE_URL}/search.json?q=status:solved+order:latest&page={page}"
|
| 49 |
-
resp = safe_get(url)
|
| 50 |
-
topics = resp.json()["topics"]
|
| 51 |
-
if not topics:
|
| 52 |
-
break
|
| 53 |
-
for post in topics:
|
| 54 |
-
discussions.append(
|
| 55 |
-
Discussion(
|
| 56 |
-
title=post["fancy_title"],
|
| 57 |
-
url=f"{BASE_URL}/t/{post['slug']}/{post['id']}",
|
| 58 |
-
topic_id=post["id"],
|
| 59 |
-
category=post["category_id"],
|
| 60 |
-
created_at=post["created_at"],
|
| 61 |
-
)
|
| 62 |
-
)
|
| 63 |
-
if len(discussions) >= n_posts:
|
| 64 |
-
break
|
| 65 |
-
page += 1
|
| 66 |
-
time.sleep(0.5) # simple pacing to avoid bursts
|
| 67 |
-
return discussions
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
def get_qa_pair(discussions, start_idx: int = 0):
|
| 71 |
-
for discussion in discussions[start_idx:]:
|
| 72 |
-
resp = safe_get(discussion.url + ".json")
|
| 73 |
-
data = resp.json()
|
| 74 |
-
posts = data["post_stream"]["posts"]
|
| 75 |
-
accepted_nr = min(
|
| 76 |
-
max(data["accepted_answer"]["post_number"] - 1, 0), len(posts) - 1
|
| 77 |
-
)
|
| 78 |
-
question = posts[0]["cooked"]
|
| 79 |
-
solution = posts[accepted_nr]["cooked"]
|
| 80 |
-
yield QuestionAndSolution(
|
| 81 |
-
discussion_title=discussion.title,
|
| 82 |
-
discussion_url=discussion.url,
|
| 83 |
-
discussion_topic_id=discussion.topic_id,
|
| 84 |
-
discussion_category=discussion.category,
|
| 85 |
-
discussion_created_at=discussion.created_at,
|
| 86 |
-
question=question,
|
| 87 |
-
solution=solution,
|
| 88 |
-
thread=posts,
|
| 89 |
-
)
|
| 90 |
-
time.sleep(0.5)
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
if __name__ == "__main__":
|
| 94 |
-
discussions = get_solved_discussions(n_posts=300)
|
| 95 |
-
print(f"Fetched {len(discussions)} discussions")
|
| 96 |
-
with open("qa_pairs.jsonl", "a") as f:
|
| 97 |
-
for qa_pair in get_qa_pair(discussions):
|
| 98 |
-
f.write(qa_pair.model_dump_json() + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/solvers.py
DELETED
|
@@ -1,165 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Collection of Inspect AI solvers used by the rubric task.
|
| 3 |
-
"""
|
| 4 |
-
|
| 5 |
-
from __future__ import annotations
|
| 6 |
-
|
| 7 |
-
import asyncio
|
| 8 |
-
import json
|
| 9 |
-
import os
|
| 10 |
-
import tempfile
|
| 11 |
-
from typing import Callable, Dict, List, Sequence
|
| 12 |
-
|
| 13 |
-
import litellm
|
| 14 |
-
from inspect_ai.model import ChatMessageAssistant, ModelOutput
|
| 15 |
-
from inspect_ai.solver import Solver, solver
|
| 16 |
-
from inspect_ai.solver._task_state import TaskState
|
| 17 |
-
|
| 18 |
-
from eval.hf_agent_connector import AgentResponseGenerator
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
async def _run_subprocess(command: Sequence[str]) -> str:
|
| 22 |
-
process = await asyncio.create_subprocess_exec(
|
| 23 |
-
*command,
|
| 24 |
-
stdout=asyncio.subprocess.PIPE,
|
| 25 |
-
stderr=asyncio.subprocess.PIPE,
|
| 26 |
-
)
|
| 27 |
-
stdout, stderr = await process.communicate()
|
| 28 |
-
if process.returncode != 0:
|
| 29 |
-
raise RuntimeError(
|
| 30 |
-
f"Command {' '.join(command)} failed with code {process.returncode}:\n"
|
| 31 |
-
f"{stderr.decode().strip()}"
|
| 32 |
-
)
|
| 33 |
-
return stdout.decode().strip()
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
@solver(name="hf_agent")
|
| 37 |
-
def hf_agent(
|
| 38 |
-
config_path: str = "agent/config_mcp_example.json",
|
| 39 |
-
max_iterations: int = 10,
|
| 40 |
-
) -> Solver:
|
| 41 |
-
|
| 42 |
-
runner = AgentResponseGenerator(
|
| 43 |
-
config_path=config_path,
|
| 44 |
-
max_iterations=max_iterations,
|
| 45 |
-
)
|
| 46 |
-
|
| 47 |
-
async def solve(state: TaskState, generate) -> TaskState:
|
| 48 |
-
response = await runner.run(state.input_text)
|
| 49 |
-
assistant_message = ChatMessageAssistant(
|
| 50 |
-
content=response,
|
| 51 |
-
model=runner.model_name,
|
| 52 |
-
source="generate",
|
| 53 |
-
)
|
| 54 |
-
state.messages.append(assistant_message)
|
| 55 |
-
state.output = ModelOutput.from_message(assistant_message)
|
| 56 |
-
state.completed = True
|
| 57 |
-
return state
|
| 58 |
-
|
| 59 |
-
return solve
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
@solver(name="claude_code")
|
| 63 |
-
def claude_code(
|
| 64 |
-
output_format: str = "json",
|
| 65 |
-
mcp_config: str | None = None,
|
| 66 |
-
) -> Solver:
|
| 67 |
-
if output_format not in {"text", "json", "stream-json"}:
|
| 68 |
-
raise ValueError("output_format must be one of: text, json, stream-json")
|
| 69 |
-
|
| 70 |
-
async def solve(state: TaskState, generate) -> TaskState:
|
| 71 |
-
prompt = state.input_text
|
| 72 |
-
|
| 73 |
-
cmd: List[str] = ["claude", "-p", prompt, "--output-format", output_format]
|
| 74 |
-
if mcp_config:
|
| 75 |
-
cmd += ["--mcp-config", mcp_config]
|
| 76 |
-
|
| 77 |
-
stdout = await _run_subprocess(cmd)
|
| 78 |
-
response_text = stdout
|
| 79 |
-
session_id = None
|
| 80 |
-
|
| 81 |
-
if output_format in {"json", "stream-json"}:
|
| 82 |
-
# stream-json may emit multiple JSON objects; take the last complete line
|
| 83 |
-
candidate_line = stdout.strip().splitlines()[-1]
|
| 84 |
-
try:
|
| 85 |
-
payload = json.loads(candidate_line)
|
| 86 |
-
response_text = (
|
| 87 |
-
payload.get("result") or payload.get("message", "") or stdout
|
| 88 |
-
)
|
| 89 |
-
session_id = payload.get("session_id")
|
| 90 |
-
except (json.JSONDecodeError, AttributeError):
|
| 91 |
-
response_text = stdout
|
| 92 |
-
|
| 93 |
-
assistant_message = ChatMessageAssistant(
|
| 94 |
-
content=response_text,
|
| 95 |
-
model="claude-code",
|
| 96 |
-
source="generate",
|
| 97 |
-
metadata={"session_id": session_id} if session_id else None,
|
| 98 |
-
)
|
| 99 |
-
state.messages.append(assistant_message)
|
| 100 |
-
state.output = ModelOutput.from_message(assistant_message)
|
| 101 |
-
state.completed = True
|
| 102 |
-
return state
|
| 103 |
-
|
| 104 |
-
return solve
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
@solver(name="claude_code+hf_mcp")
|
| 108 |
-
def claude_code_hf_mcp(
|
| 109 |
-
output_format: str = "json",
|
| 110 |
-
hf_token: str | None = None,
|
| 111 |
-
) -> Solver:
|
| 112 |
-
"""
|
| 113 |
-
A solver that uses Claude Code with the Hugging Face MCP server.
|
| 114 |
-
Requires HF_TOKEN in environment variables or passed as argument.
|
| 115 |
-
"""
|
| 116 |
-
token = hf_token or os.environ.get("HF_TOKEN")
|
| 117 |
-
if not token:
|
| 118 |
-
raise ValueError(
|
| 119 |
-
"HF_TOKEN not found. Please set HF_TOKEN env var or pass it to the solver."
|
| 120 |
-
)
|
| 121 |
-
|
| 122 |
-
# Construct the MCP configuration for Hugging Face
|
| 123 |
-
mcp_config = {
|
| 124 |
-
"mcpServers": {
|
| 125 |
-
"huggingface": {
|
| 126 |
-
"type": "http",
|
| 127 |
-
"url": "https://huggingface.co/mcp",
|
| 128 |
-
"headers": {"Authorization": f"Bearer {token}"},
|
| 129 |
-
}
|
| 130 |
-
}
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
async def solve(state: TaskState, generate) -> TaskState:
|
| 134 |
-
# Write config to a temporary file
|
| 135 |
-
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp:
|
| 136 |
-
json.dump(mcp_config, tmp, indent=2)
|
| 137 |
-
tmp_path = tmp.name
|
| 138 |
-
|
| 139 |
-
try:
|
| 140 |
-
# Delegate to the base claude_code solver
|
| 141 |
-
delegate = claude_code(output_format=output_format, mcp_config=tmp_path)
|
| 142 |
-
return await delegate(state, generate)
|
| 143 |
-
finally:
|
| 144 |
-
# Clean up the temporary file
|
| 145 |
-
if os.path.exists(tmp_path):
|
| 146 |
-
os.remove(tmp_path)
|
| 147 |
-
|
| 148 |
-
return solve
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
SOLVER_REGISTRY: Dict[str, Callable[..., Solver]] = {
|
| 152 |
-
"hf_agent": hf_agent,
|
| 153 |
-
"claude_code": claude_code,
|
| 154 |
-
"claude_code+hf_mcp": claude_code_hf_mcp,
|
| 155 |
-
}
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
def get_solver(name: str, **kwargs) -> Solver:
|
| 159 |
-
try:
|
| 160 |
-
factory = SOLVER_REGISTRY[name]
|
| 161 |
-
except KeyError as exc:
|
| 162 |
-
available = ", ".join(sorted(SOLVER_REGISTRY))
|
| 163 |
-
raise ValueError(f"Unknown solver '{name}'. Available: {available}") from exc
|
| 164 |
-
|
| 165 |
-
return factory(**kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/task.py
DELETED
|
@@ -1,121 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Inspect AI task definition that runs the existing agent and reuses the rubric scorer.
|
| 3 |
-
"""
|
| 4 |
-
|
| 5 |
-
from __future__ import annotations
|
| 6 |
-
|
| 7 |
-
import asyncio
|
| 8 |
-
import json
|
| 9 |
-
import sys
|
| 10 |
-
from pathlib import Path
|
| 11 |
-
from typing import Any, Sequence
|
| 12 |
-
|
| 13 |
-
from inspect_ai import Task, task
|
| 14 |
-
from inspect_ai.dataset import Sample, hf_dataset
|
| 15 |
-
from inspect_ai.scorer import Score, Target, mean, scorer
|
| 16 |
-
from inspect_ai.solver._task_state import TaskState
|
| 17 |
-
import litellm
|
| 18 |
-
|
| 19 |
-
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 20 |
-
if str(PROJECT_ROOT) not in sys.path:
|
| 21 |
-
sys.path.insert(0, str(PROJECT_ROOT))
|
| 22 |
-
|
| 23 |
-
from eval.rubric_eval import RubricData, evaluate_with_rubrics # noqa: E402
|
| 24 |
-
from eval.solvers import get_solver # noqa: E402
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
def _record_to_sample(record: dict[str, Any]) -> Sample:
|
| 28 |
-
rubric_payload = json.loads(record["rubric"])
|
| 29 |
-
rubrics = rubric_payload.get("rubrics", [])
|
| 30 |
-
|
| 31 |
-
metadata = {
|
| 32 |
-
"question": record["question"],
|
| 33 |
-
"discussion_title": record.get("discussion_title"),
|
| 34 |
-
"discussion_url": record.get("discussion_url"),
|
| 35 |
-
"rubric_title": rubric_payload.get("title"),
|
| 36 |
-
"rubric_description": rubric_payload.get("description"),
|
| 37 |
-
"rubrics": rubrics,
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
-
return Sample(
|
| 41 |
-
input=record["question"],
|
| 42 |
-
target=record["solution"],
|
| 43 |
-
id=record.get("discussion_topic_id"),
|
| 44 |
-
metadata=metadata,
|
| 45 |
-
)
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
def _load_dataset(dataset_name: str, split: str, limit: int | None) -> Sequence[Sample]:
|
| 49 |
-
return hf_dataset(
|
| 50 |
-
dataset_name, sample_fields=_record_to_sample, split=split, limit=limit
|
| 51 |
-
)
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
def _metadata_to_rubrics(metadata: dict[str, Any]) -> list[RubricData]:
|
| 55 |
-
raw_rubrics = metadata.get("rubrics", [])
|
| 56 |
-
return [RubricData(**rubric) for rubric in raw_rubrics]
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
@scorer(metrics=[mean()], name="rubric_scorer")
|
| 60 |
-
def rubric_scorer(judge_model: str = "gpt-5-mini"):
|
| 61 |
-
async def score(state: TaskState, target: Target) -> Score:
|
| 62 |
-
response_text = state.output.completion or state.output.message.text
|
| 63 |
-
question = state.metadata.get("question", state.input_text)
|
| 64 |
-
rubrics = _metadata_to_rubrics(state.metadata)
|
| 65 |
-
|
| 66 |
-
evaluation = await asyncio.to_thread(
|
| 67 |
-
evaluate_with_rubrics,
|
| 68 |
-
question,
|
| 69 |
-
response_text,
|
| 70 |
-
rubrics,
|
| 71 |
-
judge_model,
|
| 72 |
-
)
|
| 73 |
-
|
| 74 |
-
score_metadata = {
|
| 75 |
-
"raw_score": evaluation.raw_score,
|
| 76 |
-
"criterion_checks": [
|
| 77 |
-
check.model_dump() for check in evaluation.criterion_checks
|
| 78 |
-
],
|
| 79 |
-
"discussion_title": state.metadata.get("discussion_title"),
|
| 80 |
-
"discussion_url": state.metadata.get("discussion_url"),
|
| 81 |
-
"reference_answer": target.text,
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
return Score(
|
| 85 |
-
value=evaluation.normalized_score,
|
| 86 |
-
answer=response_text,
|
| 87 |
-
explanation=f"Normalized score {evaluation.normalized_score:.3f}",
|
| 88 |
-
metadata=score_metadata,
|
| 89 |
-
)
|
| 90 |
-
|
| 91 |
-
return score
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
@task(name="hf-benchmark-with-rubrics")
|
| 95 |
-
def hf_benchmark_with_rubrics(
|
| 96 |
-
solver_name: str = "hf_agent",
|
| 97 |
-
solver_kwargs: dict[str, Any] = {
|
| 98 |
-
"max_iterations": 10,
|
| 99 |
-
"config_path": "agent/config_mcp_example.json",
|
| 100 |
-
},
|
| 101 |
-
dataset_name: str = "akseljoonas/hf-agent-rubrics@train",
|
| 102 |
-
limit: int | None = None,
|
| 103 |
-
judge_model: str = "gpt-5-mini",
|
| 104 |
-
) -> Task:
|
| 105 |
-
litellm.drop_params = True
|
| 106 |
-
if "@" not in dataset_name:
|
| 107 |
-
raise ValueError("Dataset name must be in the format 'author/dataset@split'")
|
| 108 |
-
dataset_name, dataset_split = dataset_name.split("@")
|
| 109 |
-
dataset = _load_dataset(dataset_name, dataset_split, limit=limit)
|
| 110 |
-
|
| 111 |
-
return Task(
|
| 112 |
-
dataset=dataset,
|
| 113 |
-
solver=get_solver(solver_name, **solver_kwargs),
|
| 114 |
-
scorer=rubric_scorer(judge_model=judge_model),
|
| 115 |
-
metadata={
|
| 116 |
-
"dataset_name": dataset_name,
|
| 117 |
-
"dataset_split": dataset_split,
|
| 118 |
-
"solver_name": solver_name,
|
| 119 |
-
"judge_model": judge_model,
|
| 120 |
-
},
|
| 121 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|