-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconvert_swe_smith.py
More file actions
204 lines (167 loc) · 6.74 KB
/
convert_swe_smith.py
File metadata and controls
204 lines (167 loc) · 6.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#!/usr/bin/env python3
"""Convert SWE-smith trajectories from HuggingFace into moirai run format.
Usage:
python scripts/convert_swe_smith.py [--count N] [--split tool] <output-dir>
Downloads from SWE-bench/SWE-smith-trajectories on HuggingFace.
"""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
def classify_action(content: str) -> tuple[str, str, dict]:
"""Classify an assistant action message into (type, name, attrs).
SWE-agent tool-calling format uses function calls like:
{"name": "str_replace_editor", "arguments": {...}}
or bash commands.
"""
# Try to parse as tool call JSON
try:
# Sometimes the content has a tool_calls structure
if "str_replace_editor" in content or "view" in content.lower():
if "command" in content:
if '"view"' in content:
return "tool", "read", {}
if '"str_replace"' in content:
return "tool", "edit", {}
if '"create"' in content:
return "tool", "write", {}
if '"insert"' in content:
return "tool", "write", {}
except Exception:
pass
content_lower = content.lower()
# Bash / execution commands
if "bash" in content_lower or "execute" in content_lower:
if any(kw in content_lower for kw in ["pytest", "python -m test", "npm test", "cargo test", "go test", "make test", "unittest"]):
return "tool", "test", {}
if any(kw in content_lower for kw in ["find ", "ls ", "grep ", "rg ", "cat ", "head "]):
return "tool", "search", {}
return "tool", "bash", {}
# File operations
if any(kw in content_lower for kw in ["str_replace", "edit", "replace"]):
return "tool", "edit", {}
if any(kw in content_lower for kw in ["view", "read", "open_file", "cat "]):
return "tool", "read", {}
if any(kw in content_lower for kw in ["create", "write"]):
return "tool", "write", {}
if any(kw in content_lower for kw in ["find", "search", "grep", "glob"]):
return "tool", "search", {}
# Thinking / reasoning (if the agent outputs reasoning before acting)
if content.strip().startswith("I ") or content.strip().startswith("Let me"):
return "llm", "reason", {}
return "tool", "action", {}
def convert_trajectory(row: dict) -> dict | None:
"""Convert a single SWE-smith trajectory row into a moirai run."""
instance_id = row.get("instance_id", "unknown")
resolved = row.get("resolved", False)
model = row.get("model", "unknown")
traj_id = row.get("traj_id", instance_id)
# Parse messages
raw_messages = row.get("messages", "[]")
if isinstance(raw_messages, str):
try:
messages = json.loads(raw_messages)
except json.JSONDecodeError:
return None
else:
messages = raw_messages
if not messages:
return None
# Convert messages to steps
steps = []
idx = 0
for msg in messages:
role = msg.get("role", "")
msg_type = msg.get("message_type", "")
content = str(msg.get("content", ""))
if role == "system":
continue # skip system prompt
if role == "assistant" and msg_type == "action":
step_type, name, attrs = classify_action(content)
steps.append({
"idx": idx,
"type": step_type,
"name": name,
"status": "ok",
"output": {"summary": content[:150]},
"attrs": attrs,
})
idx += 1
elif role in ("user", "tool") and msg_type == "observation":
# Environment observations — we track these as system steps
# but only if they indicate something notable (errors, test results)
if "error" in content.lower() or "traceback" in content.lower():
steps.append({
"idx": idx,
"type": "system",
"name": "error_observation",
"status": "error",
"output": {"summary": content[:150]},
})
idx += 1
elif any(kw in content.lower() for kw in ["passed", "failed", "test"]):
steps.append({
"idx": idx,
"type": "judge",
"name": "test_result",
"status": "ok" if "passed" in content.lower() else "error",
"output": {"summary": content[:150]},
})
idx += 1
# Otherwise skip verbose observations to keep trajectories manageable
if not steps:
return None
# Derive task_family from instance_id (repo name)
# Format: "django-money__django-money.835c1ab8.func_pm_ctrl_shuffle__viqnyl9u"
parts = instance_id.split(".")
task_family = parts[0] if parts else instance_id
run = {
"run_id": traj_id,
"task_id": instance_id,
"task_family": task_family,
"agent": "swe-agent",
"model": model,
"harness": "swe-smith",
"tags": {
"dataset": "SWE-smith",
"resolved": resolved,
},
"steps": steps,
"result": {
"success": resolved,
},
}
return run
def main():
import argparse
parser = argparse.ArgumentParser(description="Convert SWE-smith trajectories to moirai format")
parser.add_argument("output_dir", help="Output directory for moirai runs")
parser.add_argument("--count", type=int, default=100, help="Number of trajectories to convert")
parser.add_argument("--split", default="tool", help="Dataset split (tool, xml, ticks)")
args = parser.parse_args()
output_dir = Path(args.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
print(f"Loading SWE-smith trajectories (split={args.split}, count={args.count})...")
from datasets import load_dataset
ds = load_dataset("SWE-bench/SWE-smith-trajectories", split=args.split, streaming=True)
converted = 0
skipped = 0
for i, row in enumerate(ds):
if converted >= args.count:
break
run = convert_trajectory(row)
if run is None:
skipped += 1
continue
# Use a clean filename
safe_id = run["run_id"][:80].replace("/", "_").replace(" ", "_")
out_path = output_dir / f"{safe_id}.json"
out_path.write_text(json.dumps(run, indent=2) + "\n", encoding="utf-8")
converted += 1
if converted % 25 == 0:
print(f" {converted} converted...")
print(f"\nconverted {converted} runs, skipped {skipped}")
print(f"output: {output_dir}")
if __name__ == "__main__":
main()