use std::path::Path;
use selfware::api::types::Message;
use selfware::bench_harness::*;
#[derive(Debug, Clone, serde::Deserialize)]
#[allow(dead_code)]
struct SWETask {
repo: String,
instance_id: String,
problem_statement: String,
hints_text: String,
patch: String,
#[serde(default)]
test_patch: String,
#[serde(default)]
version: String,
#[serde(rename = "FAIL_TO_PASS", default)]
fail_to_pass: String,
}
struct PatchEvaluator {
gold_files: Vec<String>,
gold_patch: String,
}
impl TaskEvaluator for PatchEvaluator {
fn evaluate(&self, response: &str) -> EvalResult {
let mut details = Vec::new();
let has_diff =
response.contains("diff --git") || response.contains("---") || response.contains("@@");
details.push(EvalDetail {
criterion: "contains_patch".into(),
score: if has_diff { 1.0 } else { 0.0 },
passed: has_diff,
message: if has_diff {
"Response contains a patch/diff".into()
} else {
"No patch/diff found in response".into()
},
});
let mut _files_hit = 0;
for file in &self.gold_files {
let short = file.split('/').next_back().unwrap_or(file);
let found = response.contains(file.as_str()) || response.contains(short);
if found {
_files_hit += 1;
}
details.push(EvalDetail {
criterion: format!("file:{}", short),
score: if found { 1.0 } else { 0.0 },
passed: found,
message: if found {
format!("Modifies {file}")
} else {
format!("Does not modify {file}")
},
});
}
let gold_lines: Vec<&str> = self
.gold_patch
.lines()
.filter(|l| l.starts_with('+') && !l.starts_with("+++"))
.map(|l| l.trim_start_matches('+').trim())
.filter(|l| l.len() > 10) .take(5) .collect();
let mut _code_hits = 0;
for line in &gold_lines {
let key_tokens: Vec<&str> = line
.split(|c: char| !c.is_alphanumeric() && c != '_')
.filter(|t| t.len() > 3)
.take(3)
.collect();
let found = key_tokens.iter().filter(|t| response.contains(**t)).count()
>= key_tokens.len().max(1) / 2;
if found {
_code_hits += 1;
}
details.push(EvalDetail {
criterion: format!("code_pattern:{}", &line[..line.len().min(40)]),
score: if found { 1.0 } else { 0.0 },
passed: found,
message: if found {
"Key code pattern found".into()
} else {
"Key code pattern missing".into()
},
});
}
let total = details.len();
let passed = details.iter().filter(|d| d.passed).count();
let score = if total > 0 {
passed as f64 / total as f64
} else {
0.0
};
EvalResult {
score,
passed: score >= 0.3, details,
}
}
}
fn extract_diff_files(patch: &str) -> Vec<String> {
patch
.lines()
.filter(|l| l.starts_with("diff --git"))
.filter_map(|l| {
let parts: Vec<&str> = l.split_whitespace().collect();
parts.get(3).map(|p| p.trim_start_matches("b/").to_string())
})
.collect()
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter("info,selfware::bench_harness=debug")
.init();
let task_file = "bench_results/swebench_lite_20.json";
if !Path::new(task_file).exists() {
eprintln!("Task file not found: {task_file}");
eprintln!("Run the Python export script first.");
return Ok(());
}
let content = std::fs::read_to_string(task_file)?;
let swe_tasks: Vec<SWETask> = serde_json::from_str(&content)?;
let config = HarnessConfig {
endpoint: "http://localhost:8000/v1".into(),
model: "qwen3.5-27b".into(),
max_concurrent: 16,
max_tokens: 8192,
temperature: 0.2,
timeout_secs: 300,
max_retries: 3,
retry_delay_ms: 500,
output_dir: "bench_results/swebench".into(),
api_key: None,
extra_body: serde_json::json!({
"chat_template_kwargs": {"enable_thinking": false}
}),
};
let runner = HarnessRunner::new(config.clone())?;
eprintln!("\n=== SWE-bench Lite Evaluation ===");
eprintln!("Endpoint: {}", config.endpoint);
eprintln!("Model: {}", config.model);
eprintln!("Tasks: {}", swe_tasks.len());
eprintln!("Concurrency: {}", config.max_concurrent);
eprintln!();
let bench_tasks: Vec<BenchTask> = swe_tasks
.iter()
.map(|task| {
let gold_files = extract_diff_files(&task.patch);
let gold_patch = task.patch.clone();
let system_msg = format!(
"You are an expert software engineer. You are given a bug report for the {} repository (version {}). \
Generate a COMPLETE unified diff patch that fixes the issue. \
The patch MUST be a valid unified diff that can be applied with `git apply`. \
Include correct line numbers and sufficient context lines (3 lines before and after changes). \
Make sure every line ends with a newline character. \
Output ONLY the diff inside a code fence, no explanation before or after:\n\
```diff\n\
diff --git a/path/to/file b/path/to/file\n\
--- a/path/to/file\n\
+++ b/path/to/file\n\
@@ -start,count +start,count @@\n\
<context lines>\n\
-<removed lines>\n\
+<added lines>\n\
<context lines>\n\
```\n\
IMPORTANT: The patch must be complete and not truncated. Include ALL necessary changes.",
task.repo, task.version,
);
let user_msg = format!(
"## Bug Report\n\n{}\n\n{}",
task.problem_statement,
if task.hints_text.is_empty() {
String::new()
} else {
format!("## Hints\n\n{}", task.hints_text)
},
);
BenchTask {
id: task.instance_id.clone(),
description: format!("{}: {}", task.repo, task.instance_id),
messages: vec![
Message::system(system_msg),
Message::user(user_msg),
],
evaluator: Box::new(PatchEvaluator { gold_files, gold_patch }),
}
})
.collect();
let report = runner.run(bench_tasks).await?;
eprintln!("\n{}", "=".repeat(70));
eprintln!("SWE-BENCH LITE RESULTS");
eprintln!("{}", "=".repeat(70));
eprintln!(
"Tasks: {}/{} passed (patch quality >= 30%)",
report.tasks_passed, report.tasks_total
);
eprintln!("Avg score: {:.1}%", report.avg_score * 100.0);
eprintln!("Throughput: {:.0} tok/s", report.tokens_per_sec);
eprintln!("Duration: {:.1}s", report.total_duration_secs);
eprintln!("Latency p50: {:.1}s", report.latency_p50_ms as f64 / 1000.0);
eprintln!("Latency p95: {:.1}s", report.latency_p95_ms as f64 / 1000.0);
eprintln!("{}", "=".repeat(70));
eprintln!(
"\n{:<45} {:>6} {:>8} {:>10}",
"Instance", "Score", "Tokens", "Latency"
);
eprintln!("{}", "-".repeat(75));
for r in &report.results {
let score_str = r
.eval
.as_ref()
.map(|e| format!("{:.0}%", e.score * 100.0))
.unwrap_or("ERR".into());
let status = if r.success { "+" } else { "-" };
eprintln!(
"{} {:<43} {:>6} {:>8} {:>8.1}s",
status,
&r.task_id[..r.task_id.len().min(43)],
score_str,
r.completion_tokens,
r.latency_ms as f64 / 1000.0,
);
if !r.success {
if let Some(eval) = &r.eval {
for d in &eval.details {
if !d.passed {
eprintln!(" x {}: {}", d.criterion, d.message);
}
}
}
if let Some(err) = &r.error {
eprintln!(" ! {}", &err[..err.len().min(80)]);
}
}
}
report.write_to_dir(Path::new("bench_results/swebench"))?;
eprintln!("\nReports written to bench_results/swebench/");
Ok(())
}