Joseph Pollack commited on
Commit
b82e5c5
·
unverified ·
1 Parent(s): eb0369d

adds correct model card info

Browse files
.gitignore CHANGED
@@ -1 +1,2 @@
1
- datasets/
 
 
1
+ datasets/
2
+ tmp_hf_push/
interface.py CHANGED
@@ -515,6 +515,16 @@ def start_voxtral_training(
515
  "model",
516
  str(output_dir),
517
  full_repo_name,
 
 
 
 
 
 
 
 
 
 
518
  ]
519
  all_logs.append(f"📤 Pushing model to Hugging Face Hub: {full_repo_name}")
520
  push_code = collect_logs_with_code(run_command_stream(push_args, env))
 
515
  "model",
516
  str(output_dir),
517
  full_repo_name,
518
+ "--author-name", "Voxtral Trainer",
519
+ "--model-description", "Fine-tuned Voxtral ASR model",
520
+ "--model-name", base_model,
521
+ "--trainer-type", ("SFTTrainer"),
522
+ "--training-config-type", ("Custom Configuration"),
523
+ "--batch-size", str(int(batch_size) if isinstance(batch_size, (int, float)) else batch_size),
524
+ "--gradient-accumulation-steps", str(int(grad_accum) if isinstance(grad_accum, (int, float)) else grad_accum),
525
+ "--learning-rate", str(learning_rate),
526
+ "--max-epochs", str(epochs),
527
+ "--trackio-url", env.get("TRACKIO_URL", "N/A"),
528
  ]
529
  all_logs.append(f"📤 Pushing model to Hugging Face Hub: {full_repo_name}")
530
  push_code = collect_logs_with_code(run_command_stream(push_args, env))
scripts/__pycache__/generate_model_card.cpython-313.pyc ADDED
Binary file (11.3 kB). View file
 
scripts/push_to_huggingface.py CHANGED
@@ -47,7 +47,18 @@ class HuggingFacePusher:
47
  author_name: Optional[str] = None,
48
  model_description: Optional[str] = None,
49
  model_name: Optional[str] = None,
50
- dataset_name: Optional[str] = None
 
 
 
 
 
 
 
 
 
 
 
51
  ):
52
  self.model_path = Path(model_path)
53
  # Original user input (may be just the repo name without username)
@@ -60,6 +71,17 @@ class HuggingFacePusher:
60
  # Model card generation details
61
  self.model_name = model_name
62
  self.dataset_name = dataset_name
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  # Initialize HF API
65
  if HF_AVAILABLE:
@@ -278,7 +300,7 @@ class HuggingFacePusher:
278
  "repo_name": self.repo_id,
279
  "model_name": self.repo_id.split('/')[-1],
280
  "experiment_name": self.experiment_name or "model_push",
281
- "dataset_repo": self.dataset_repo,
282
  "author_name": self.author_name or "Model Author",
283
  "model_description": self.model_description or "A fine-tuned version of SmolLM3-3B for improved text generation capabilities.",
284
  "training_config_type": self.training_config_type or "Custom Configuration",
@@ -286,6 +308,7 @@ class HuggingFacePusher:
286
  "dataset_name": self.dataset_name or "Custom Dataset",
287
  "trainer_type": self.trainer_type or "SFTTrainer",
288
  "batch_size": str(self.batch_size) if self.batch_size else "8",
 
289
  "learning_rate": str(self.learning_rate) if self.learning_rate else "5e-6",
290
  "max_epochs": str(self.max_epochs) if self.max_epochs else "3",
291
  "max_seq_length": str(self.max_seq_length) if self.max_seq_length else "2048",
@@ -895,6 +918,17 @@ def parse_args():
895
  model_parser.add_argument('--model-description', type=str, default=None, help='Model description for model card')
896
  model_parser.add_argument('--model-name', type=str, default=None, help='Base model name')
897
  model_parser.add_argument('--dataset-name', type=str, default=None, help='Dataset name')
 
 
 
 
 
 
 
 
 
 
 
898
 
899
  # Dataset push subcommand
900
  dataset_parser = subparsers.add_parser('dataset', help='Push dataset to Hugging Face Hub')
@@ -933,7 +967,17 @@ def main():
933
  author_name=args.author_name,
934
  model_description=args.model_description,
935
  model_name=args.model_name,
936
- dataset_name=args.dataset_name
 
 
 
 
 
 
 
 
 
 
937
  )
938
 
939
  # Push model
 
47
  author_name: Optional[str] = None,
48
  model_description: Optional[str] = None,
49
  model_name: Optional[str] = None,
50
+ dataset_name: Optional[str] = None,
51
+ # Optional metadata for model card generation
52
+ experiment_name: Optional[str] = None,
53
+ dataset_repo: Optional[str] = None,
54
+ training_config_type: Optional[str] = None,
55
+ trainer_type: Optional[str] = None,
56
+ batch_size: Optional[str] = None,
57
+ gradient_accumulation_steps: Optional[str] = None,
58
+ learning_rate: Optional[str] = None,
59
+ max_epochs: Optional[str] = None,
60
+ max_seq_length: Optional[str] = None,
61
+ trackio_url: Optional[str] = None,
62
  ):
63
  self.model_path = Path(model_path)
64
  # Original user input (may be just the repo name without username)
 
71
  # Model card generation details
72
  self.model_name = model_name
73
  self.dataset_name = dataset_name
74
+ # Optional metadata (ensure attributes always exist to avoid AttributeError)
75
+ self.experiment_name = experiment_name
76
+ self.dataset_repo = dataset_repo
77
+ self.training_config_type = training_config_type
78
+ self.trainer_type = trainer_type
79
+ self.batch_size = batch_size
80
+ self.gradient_accumulation_steps = gradient_accumulation_steps
81
+ self.learning_rate = learning_rate
82
+ self.max_epochs = max_epochs
83
+ self.max_seq_length = max_seq_length
84
+ self.trackio_url = trackio_url
85
 
86
  # Initialize HF API
87
  if HF_AVAILABLE:
 
300
  "repo_name": self.repo_id,
301
  "model_name": self.repo_id.split('/')[-1],
302
  "experiment_name": self.experiment_name or "model_push",
303
+ "dataset_repo": self.dataset_repo or "",
304
  "author_name": self.author_name or "Model Author",
305
  "model_description": self.model_description or "A fine-tuned version of SmolLM3-3B for improved text generation capabilities.",
306
  "training_config_type": self.training_config_type or "Custom Configuration",
 
308
  "dataset_name": self.dataset_name or "Custom Dataset",
309
  "trainer_type": self.trainer_type or "SFTTrainer",
310
  "batch_size": str(self.batch_size) if self.batch_size else "8",
311
+ "gradient_accumulation_steps": str(self.gradient_accumulation_steps) if self.gradient_accumulation_steps else variables.get("gradient_accumulation_steps", "16"),
312
  "learning_rate": str(self.learning_rate) if self.learning_rate else "5e-6",
313
  "max_epochs": str(self.max_epochs) if self.max_epochs else "3",
314
  "max_seq_length": str(self.max_seq_length) if self.max_seq_length else "2048",
 
918
  model_parser.add_argument('--model-description', type=str, default=None, help='Model description for model card')
919
  model_parser.add_argument('--model-name', type=str, default=None, help='Base model name')
920
  model_parser.add_argument('--dataset-name', type=str, default=None, help='Dataset name')
921
+ # Optional model card metadata
922
+ model_parser.add_argument('--experiment-name', type=str, default=None, help='Experiment name for model card')
923
+ model_parser.add_argument('--dataset-repo', type=str, default=None, help='Dataset repo for model card')
924
+ model_parser.add_argument('--training-config-type', type=str, default=None, help='Training config type for model card')
925
+ model_parser.add_argument('--trainer-type', type=str, default=None, help='Trainer type for model card')
926
+ model_parser.add_argument('--batch-size', type=str, default=None, help='Batch size for model card')
927
+ model_parser.add_argument('--gradient-accumulation-steps', type=str, default=None, help='Grad accum steps for model card')
928
+ model_parser.add_argument('--learning-rate', type=str, default=None, help='Learning rate for model card')
929
+ model_parser.add_argument('--max-epochs', type=str, default=None, help='Max epochs for model card')
930
+ model_parser.add_argument('--max-seq-length', type=str, default=None, help='Max seq length for model card')
931
+ model_parser.add_argument('--trackio-url', type=str, default=None, help='Trackio URL for model card')
932
 
933
  # Dataset push subcommand
934
  dataset_parser = subparsers.add_parser('dataset', help='Push dataset to Hugging Face Hub')
 
967
  author_name=args.author_name,
968
  model_description=args.model_description,
969
  model_name=args.model_name,
970
+ dataset_name=args.dataset_name,
971
+ experiment_name=args.experiment_name,
972
+ dataset_repo=args.dataset_repo,
973
+ training_config_type=args.training_config_type,
974
+ trainer_type=args.trainer_type,
975
+ batch_size=args.batch_size,
976
+ gradient_accumulation_steps=args.gradient_accumulation_steps,
977
+ learning_rate=args.learning_rate,
978
+ max_epochs=args.max_epochs,
979
+ max_seq_length=args.max_seq_length,
980
+ trackio_url=args.trackio_url,
981
  )
982
 
983
  # Push model
tests/test_generate_model_card.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Tests for scripts/generate_model_card.py using the real template in templates/model_card.md.
4
+
5
+ These tests verify:
6
+ - Conditional processing for quantized_models
7
+ - Variable replacement for common fields
8
+ - File writing via save_model_card
9
+ """
10
+
11
+ import sys
12
+ from pathlib import Path
13
+
14
+
15
+ def _repo_root() -> Path:
16
+ return Path(__file__).resolve().parents[1]
17
+
18
+
19
+ def _add_scripts_to_path() -> None:
20
+ scripts_dir = _repo_root() / "scripts"
21
+ if str(scripts_dir) not in sys.path:
22
+ sys.path.insert(0, str(scripts_dir))
23
+
24
+
25
+ def test_model_card_generator_conditionals_truthy(tmp_path):
26
+ _add_scripts_to_path()
27
+ from generate_model_card import ModelCardGenerator
28
+
29
+ template_path = _repo_root() / "templates" / "model_card.md"
30
+ generator = ModelCardGenerator(str(template_path))
31
+
32
+ variables = {
33
+ "model_name": "My Fine-tuned Model",
34
+ "model_description": "A test description.",
35
+ "repo_name": "user/repo",
36
+ "base_model": "HuggingFaceTB/SmolLM3-3B",
37
+ "dataset_name": "OpenHermes-FR",
38
+ "training_config_type": "Custom",
39
+ "trainer_type": "SFTTrainer",
40
+ "batch_size": "8",
41
+ "gradient_accumulation_steps": "16",
42
+ "learning_rate": "5e-6",
43
+ "max_epochs": "3",
44
+ "max_seq_length": "2048",
45
+ "hardware_info": "CPU",
46
+ "experiment_name": "exp-123",
47
+ "trackio_url": "https://trackio.space/exp",
48
+ "dataset_repo": "tonic/trackio-experiments",
49
+ "author_name": "Unit Tester",
50
+ "quantized_models": True,
51
+ }
52
+
53
+ content = generator.generate_model_card(variables)
54
+
55
+ # Conditional: when True, the quantized tag should appear
56
+ assert "- quantized" in content
57
+
58
+ # Common variables replaced in multiple locations
59
+ assert "base_model: HuggingFaceTB/SmolLM3-3B" in content
60
+ assert "trainer_type: SFTTrainer" in content
61
+ assert 'from_pretrained("user/repo")' in content
62
+ assert "Hardware\": \"CPU\"" not in content # ensure no escaped quotes left
63
+ assert "hardware: \"CPU\"" in content
64
+
65
+ # Save to file and verify
66
+ output_path = tmp_path / "README_test.md"
67
+ assert generator.save_model_card(content, str(output_path)) is True
68
+ assert output_path.exists()
69
+ assert output_path.read_text(encoding="utf-8") == content
70
+
71
+
72
+ def test_model_card_generator_conditionals_falsey(tmp_path):
73
+ _add_scripts_to_path()
74
+ from generate_model_card import ModelCardGenerator
75
+
76
+ template_path = _repo_root() / "templates" / "model_card.md"
77
+ generator = ModelCardGenerator(str(template_path))
78
+
79
+ variables = {
80
+ "model_name": "My Model",
81
+ "model_description": "A test description.",
82
+ "repo_name": "user/repo",
83
+ "base_model": "HuggingFaceTB/SmolLM3-3B",
84
+ "dataset_name": "OpenHermes-FR",
85
+ "training_config_type": "Custom",
86
+ "trainer_type": "SFTTrainer",
87
+ "batch_size": "8",
88
+ "learning_rate": "5e-6",
89
+ "max_epochs": "3",
90
+ "max_seq_length": "2048",
91
+ "hardware_info": "CPU",
92
+ "quantized_models": False,
93
+ }
94
+
95
+ content = generator.generate_model_card(variables)
96
+
97
+ # Conditional: quantized tag should be absent
98
+ assert "- quantized" not in content
99
+
100
+ # The if/else block is removed by current implementation when False
101
+ assert "{{#if quantized_models}}" not in content
102
+ assert "{{/if}}" not in content
103
+
104
+ # Variable replacement still occurs elsewhere
105
+ assert "base_model: HuggingFaceTB/SmolLM3-3B" in content
106
+ assert 'from_pretrained("user/repo")' in content
107
+
108
+ # Save to file
109
+ output_path = tmp_path / "README_no_quant.md"
110
+ assert generator.save_model_card(content, str(output_path)) is True
111
+ assert output_path.exists()
112
+
113
+
114
+ def test_model_card_generator_variable_replacement(tmp_path):
115
+ _add_scripts_to_path()
116
+ from generate_model_card import ModelCardGenerator
117
+
118
+ template_path = _repo_root() / "templates" / "model_card.md"
119
+ generator = ModelCardGenerator(str(template_path))
120
+
121
+ base_model = "custom/base-model"
122
+ repo_name = "custom/repo-name"
123
+ variables = {
124
+ "model_name": "Var Test Model",
125
+ "model_description": "Testing variable replacement.",
126
+ "repo_name": repo_name,
127
+ "base_model": base_model,
128
+ "dataset_name": "dataset-x",
129
+ "trainer_type": "SFTTrainer",
130
+ "batch_size": "4",
131
+ "gradient_accumulation_steps": "1",
132
+ "max_seq_length": "1024",
133
+ "hardware_info": "CPU",
134
+ "quantized_models": False,
135
+ }
136
+
137
+ content = generator.generate_model_card(variables)
138
+
139
+ assert f"base_model: {base_model}" in content
140
+ assert f'from_pretrained("{repo_name}")' in content
141
+ assert "trainer_type: SFTTrainer" in content
142
+
143
+
tests/test_push_model_card.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Tests for scripts/push_to_huggingface.py focusing on model card creation/upload.
4
+
5
+ We mock Hugging Face Hub interactions and create dummy model folders to verify:
6
+ - Repo id resolution via whoami
7
+ - Repository creation call
8
+ - README.md upload with expected content (fallback simple card path)
9
+ - Uploading of model files from the directory
10
+ """
11
+
12
+ import sys
13
+ import types
14
+ from pathlib import Path
15
+
16
+
17
+ def _repo_root() -> Path:
18
+ return Path(__file__).resolve().parents[1]
19
+
20
+
21
+ def _add_scripts_to_path() -> None:
22
+ scripts_dir = _repo_root() / "scripts"
23
+ if str(scripts_dir) not in sys.path:
24
+ sys.path.insert(0, str(scripts_dir))
25
+
26
+
27
+ def _make_full_model_dir(base: Path) -> Path:
28
+ model_dir = base / "full_model"
29
+ model_dir.mkdir(parents=True, exist_ok=True)
30
+ (model_dir / "config.json").write_text("{}", encoding="utf-8")
31
+ # Create an empty weight file to satisfy validation
32
+ (model_dir / "model.safetensors").write_bytes(b"")
33
+ return model_dir
34
+
35
+
36
+ def _make_lora_model_dir(base: Path) -> Path:
37
+ model_dir = base / "lora_model"
38
+ model_dir.mkdir(parents=True, exist_ok=True)
39
+ (model_dir / "adapter_config.json").write_text("{}", encoding="utf-8")
40
+ (model_dir / "adapter_model.bin").write_bytes(b"\x00")
41
+ return model_dir
42
+
43
+
44
+ def test_push_model_card_full_model(monkeypatch, tmp_path):
45
+ _add_scripts_to_path()
46
+ import push_to_huggingface as mod
47
+
48
+ # Ensure module thinks HF is available and patch API + functions
49
+ monkeypatch.setattr(mod, "HF_AVAILABLE", True, raising=False)
50
+
51
+ create_repo_calls = []
52
+ upload_file_calls = []
53
+
54
+ class DummyHfApi:
55
+ def __init__(self, token=None):
56
+ self.token = token
57
+
58
+ def whoami(self):
59
+ return {"name": "testuser"}
60
+
61
+ def fake_create_repo(*, repo_id, token=None, private=False, exist_ok=False, repo_type=None):
62
+ create_repo_calls.append({
63
+ "repo_id": repo_id,
64
+ "token": token,
65
+ "private": private,
66
+ "exist_ok": exist_ok,
67
+ "repo_type": repo_type,
68
+ })
69
+
70
+ def fake_upload_file(*, path_or_fileobj, path_in_repo, repo_id, token, repo_type=None):
71
+ path = Path(path_or_fileobj)
72
+ content = None
73
+ if path.exists() and path.is_file():
74
+ try:
75
+ content = path.read_text(encoding="utf-8")
76
+ except Exception:
77
+ content = None
78
+ upload_file_calls.append({
79
+ "path_in_repo": path_in_repo,
80
+ "repo_id": repo_id,
81
+ "token": token,
82
+ "repo_type": repo_type,
83
+ "content": content,
84
+ "local_path": str(path),
85
+ })
86
+
87
+ monkeypatch.setattr(mod, "HfApi", DummyHfApi, raising=False)
88
+ monkeypatch.setattr(mod, "create_repo", fake_create_repo, raising=False)
89
+ monkeypatch.setattr(mod, "upload_file", fake_upload_file, raising=False)
90
+
91
+ # Prepare dummy full model directory
92
+ model_dir = _make_full_model_dir(tmp_path)
93
+
94
+ pusher = mod.HuggingFacePusher(
95
+ model_path=str(model_dir),
96
+ repo_name="my-repo",
97
+ token="fake-token",
98
+ private=True,
99
+ author_name="Tester",
100
+ model_description="Desc",
101
+ model_name="BaseModel",
102
+ dataset_name="DatasetX",
103
+ )
104
+
105
+ # Execute push (this should use fallback simple model card)
106
+ ok = pusher.push_model(
107
+ training_config={"param": 1},
108
+ results={"train_loss": 0.1, "eval_loss": 0.2, "perplexity": 9.9},
109
+ )
110
+ assert ok is True
111
+
112
+ # Repo creation was called with resolved user prefix
113
+ assert any(c["repo_id"] == "testuser/my-repo" for c in create_repo_calls)
114
+
115
+ # README upload occurred and contains either generator or fallback content (full model)
116
+ readme_calls = [c for c in upload_file_calls if c["path_in_repo"] == "README.md"]
117
+ assert readme_calls, "README.md was not uploaded"
118
+ readme_content = readme_calls[-1]["content"] or ""
119
+ assert (
120
+ "fine-tuned Voxtral ASR model" in readme_content
121
+ or "SmolLM3" in readme_content
122
+ or "Model Details" in readme_content
123
+ )
124
+ assert "DatasetX" in readme_content or "Training Configuration" in readme_content
125
+
126
+ # Model files were uploaded (config and weights)
127
+ uploaded_paths = {c["path_in_repo"] for c in upload_file_calls}
128
+ assert "config.json" in uploaded_paths
129
+ assert "model.safetensors" in uploaded_paths
130
+
131
+
132
+ def test_push_model_card_lora_model_fallback(monkeypatch, tmp_path):
133
+ _add_scripts_to_path()
134
+ import push_to_huggingface as mod
135
+
136
+ # Ensure module thinks HF is available and patch API + functions
137
+ monkeypatch.setattr(mod, "HF_AVAILABLE", True, raising=False)
138
+
139
+ upload_file_calls = []
140
+
141
+ class DummyHfApi:
142
+ def __init__(self, token=None):
143
+ self.token = token
144
+
145
+ def whoami(self):
146
+ return {"username": "anotheruser"}
147
+
148
+ def fake_create_repo(*, repo_id, token=None, private=False, exist_ok=False, repo_type=None):
149
+ return None
150
+
151
+ def fake_upload_file(*, path_or_fileobj, path_in_repo, repo_id, token, repo_type=None):
152
+ path = Path(path_or_fileobj)
153
+ content = None
154
+ if path.exists() and path.is_file():
155
+ try:
156
+ content = path.read_text(encoding="utf-8")
157
+ except Exception:
158
+ content = None
159
+ upload_file_calls.append({
160
+ "path_in_repo": path_in_repo,
161
+ "repo_id": repo_id,
162
+ "content": content,
163
+ })
164
+
165
+ monkeypatch.setattr(mod, "HfApi", DummyHfApi, raising=False)
166
+ monkeypatch.setattr(mod, "create_repo", fake_create_repo, raising=False)
167
+ monkeypatch.setattr(mod, "upload_file", fake_upload_file, raising=False)
168
+
169
+ # Insert a dummy generate_model_card module that raises in generate to force fallback
170
+ dummy_mod = types.ModuleType("generate_model_card")
171
+
172
+ class RaisingGen:
173
+ def __init__(self, *args, **kwargs):
174
+ pass
175
+
176
+ def generate_model_card(self, variables):
177
+ raise RuntimeError("force fallback")
178
+
179
+ def default_vars():
180
+ return {}
181
+
182
+ dummy_mod.ModelCardGenerator = RaisingGen
183
+ dummy_mod.create_default_variables = default_vars
184
+ sys.modules["generate_model_card"] = dummy_mod
185
+
186
+ # Prepare dummy lora model directory
187
+ model_dir = _make_lora_model_dir(tmp_path)
188
+
189
+ pusher = mod.HuggingFacePusher(
190
+ model_path=str(model_dir),
191
+ repo_name="my-lora-repo",
192
+ token="fake-token",
193
+ private=False,
194
+ author_name="Tester",
195
+ model_description="Desc",
196
+ model_name="BaseModel",
197
+ dataset_name="DatasetY",
198
+ )
199
+
200
+ ok = pusher.push_model(training_config={}, results={})
201
+ assert ok is True
202
+
203
+ # README upload occurred and contains either generator or fallback content (LoRA)
204
+ readme_calls = [c for c in upload_file_calls if c["path_in_repo"] == "README.md"]
205
+ assert readme_calls, "README.md was not uploaded"
206
+ readme_content = readme_calls[-1]["content"] or ""
207
+ assert (
208
+ "LoRA adapter for Voxtral ASR" in readme_content
209
+ or "SmolLM3" in readme_content
210
+ or "Model Details" in readme_content
211
+ )
212
+ assert "DatasetY" in readme_content or "Training Configuration" in readme_content
213
+
214
+ # LoRA files uploaded
215
+ uploaded_paths = {Path(c.get("local_path", "")).name for c in upload_file_calls if c.get("local_path")}
216
+ assert any(name.startswith("adapter_") for name in uploaded_paths)
217
+
218
+