forked from MakazhanAlpamys/Soup
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_data_tools.py
More file actions
229 lines (181 loc) · 7.28 KB
/
Copy pathtest_data_tools.py
File metadata and controls
229 lines (181 loc) · 7.28 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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
"""Tests for data tools: convert, merge, dedup, stats, and reverse format conversion."""
import json
from pathlib import Path
import pytest
from typer.testing import CliRunner
from soup_cli.cli import app
from soup_cli.data.formats import (
format_to_messages,
messages_to_format,
)
from soup_cli.data.validator import extended_stats
runner = CliRunner()
# --- Reverse format conversion tests ---
def test_messages_to_alpaca():
"""Convert messages → alpaca format."""
row = {"messages": [
{"role": "user", "content": "What is AI?"},
{"role": "assistant", "content": "AI is artificial intelligence."},
]}
result = messages_to_format(row, "alpaca")
assert result["instruction"] == "What is AI?"
assert result["output"] == "AI is artificial intelligence."
def test_messages_to_alpaca_with_system():
"""System message should be preserved in alpaca format."""
row = {"messages": [
{"role": "system", "content": "You are helpful."},
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi!"},
]}
result = messages_to_format(row, "alpaca")
assert result["system"] == "You are helpful."
assert result["instruction"] == "Hello"
assert result["output"] == "Hi!"
def test_messages_to_sharegpt():
"""Convert messages → sharegpt format."""
row = {"messages": [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi!"},
]}
result = messages_to_format(row, "sharegpt")
assert result["conversations"][0]["from"] == "human"
assert result["conversations"][0]["value"] == "Hello"
assert result["conversations"][1]["from"] == "gpt"
assert result["conversations"][1]["value"] == "Hi!"
def test_messages_to_chatml():
"""Convert messages → chatml should be identity."""
row = {"messages": [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi!"},
]}
result = messages_to_format(row, "chatml")
assert result == row
def test_roundtrip_alpaca_to_chatml_to_alpaca():
"""Converting alpaca → chatml → alpaca should preserve content."""
original = {"instruction": "Explain X", "input": "", "output": "X is amazing."}
chatml = format_to_messages(original, "alpaca")
assert chatml is not None
back = messages_to_format(chatml, "alpaca")
assert back["instruction"] == original["instruction"]
assert back["output"] == original["output"]
def test_roundtrip_sharegpt_to_chatml_to_sharegpt():
"""Converting sharegpt → chatml → sharegpt should preserve content."""
original = {"conversations": [
{"from": "human", "value": "Hello"},
{"from": "gpt", "value": "Hi!"},
]}
chatml = format_to_messages(original, "sharegpt")
assert chatml is not None
back = messages_to_format(chatml, "sharegpt")
assert back["conversations"][0]["from"] == "human"
assert back["conversations"][0]["value"] == "Hello"
def test_messages_to_invalid_format():
"""Invalid target format should raise ValueError."""
row = {"messages": [{"role": "user", "content": "Hello"}]}
with pytest.raises(ValueError, match="Cannot convert"):
messages_to_format(row, "dpo")
# --- Extended stats tests ---
def test_extended_stats_basic():
"""Extended stats should compute percentiles and token counts."""
data = [
{"instruction": "short", "output": "ok"},
{"instruction": "a bit longer instruction here", "output": "response text here"},
{"instruction": "x" * 200, "output": "y" * 100},
]
result = extended_stats(data)
assert result["total"] == 3
assert result["length_p50"] > 0
assert result["avg_tokens"] > 0
assert result["min_tokens"] >= 1
assert len(result["lengths"]) == 3
def test_extended_stats_empty():
"""Extended stats on empty data should return zeros."""
result = extended_stats([])
assert result["total"] == 0
assert result["avg_tokens"] == 0
# --- CLI data command tests ---
@pytest.fixture
def sample_alpaca_file(tmp_path: Path) -> Path:
"""Create a sample alpaca JSONL file."""
data = [
{"instruction": "What is AI?", "input": "", "output": "AI is..."},
{"instruction": "Explain ML", "input": "", "output": "ML is..."},
{"instruction": "What is DL?", "input": "", "output": "DL is..."},
]
filepath = tmp_path / "sample.jsonl"
with open(filepath, "w") as f:
for row in data:
f.write(json.dumps(row) + "\n")
return filepath
@pytest.fixture
def sample_sharegpt_file(tmp_path: Path) -> Path:
"""Create a sample sharegpt JSONL file."""
data = [
{"conversations": [
{"from": "human", "value": "Hello"},
{"from": "gpt", "value": "Hi!"},
]},
{"conversations": [
{"from": "human", "value": "How are you?"},
{"from": "gpt", "value": "I'm good!"},
]},
]
filepath = tmp_path / "sharegpt.jsonl"
with open(filepath, "w") as f:
for row in data:
f.write(json.dumps(row) + "\n")
return filepath
def test_convert_command(sample_alpaca_file, tmp_path):
"""soup data convert should convert alpaca → sharegpt."""
output = str(tmp_path / "output.jsonl")
result = runner.invoke(
app, ["data", "convert", str(sample_alpaca_file), "--to", "sharegpt", "-o", output]
)
assert result.exit_code == 0
assert "Converted" in result.output
# Verify output file
with open(output) as f:
lines = f.readlines()
assert len(lines) == 3
first = json.loads(lines[0])
assert "conversations" in first
def test_convert_same_format(sample_alpaca_file):
"""Converting to same format should warn and exit."""
result = runner.invoke(
app, ["data", "convert", str(sample_alpaca_file), "--to", "alpaca"]
)
assert result.exit_code == 0
assert "Nothing to convert" in result.output
def test_convert_missing_file():
"""Converting nonexistent file should fail."""
result = runner.invoke(app, ["data", "convert", "nonexistent.jsonl", "--to", "chatml"])
assert result.exit_code == 1
def test_merge_command(sample_alpaca_file, sample_sharegpt_file, tmp_path):
"""soup data merge should merge files."""
output = str(tmp_path / "merged.jsonl")
result = runner.invoke(
app,
["data", "merge", str(sample_alpaca_file), str(sample_sharegpt_file), "-o", output],
)
assert result.exit_code == 0
assert "Merged" in result.output
assert "5 rows" in result.output # 3 + 2
with open(output) as f:
lines = f.readlines()
assert len(lines) == 5
def test_merge_missing_file(sample_alpaca_file):
"""Merge with nonexistent file should fail."""
result = runner.invoke(
app, ["data", "merge", str(sample_alpaca_file), "nonexistent.jsonl"]
)
assert result.exit_code == 1
def test_stats_command(sample_alpaca_file):
"""soup data stats should show extended statistics."""
result = runner.invoke(app, ["data", "stats", str(sample_alpaca_file)])
assert result.exit_code == 0
assert "p50" in result.output
assert "Tokens" in result.output
def test_stats_missing_file():
"""Stats on nonexistent file should fail."""
result = runner.invoke(app, ["data", "stats", "nonexistent.jsonl"])
assert result.exit_code == 1