jefffffff9 Claude Sonnet 4.6 commited on
Commit
24b1617
·
1 Parent(s): 670a1d1

Fix Self-Teaching tab: float sliders, deduplication, Kaggle API fallback

Browse files

- Cast slider values to int (Gradio returns float; broke Wikipedia aplimit
and Kaggle ds.take() in notebook Cell 9)
- Deduplicate dataset_sources.jsonl on each HF import (avoid loading same
dataset multiple times per training run)
- Kaggle trigger: try Python API (KaggleApiExtended) first to avoid binary
PATH issues; subprocess + shutil.which as fallback
- Fix Fula dataset registry: fleurs uses legacy script loader incompatible
with datasets>=3.0; switch to google/WaxalNLP ful_asr
- Fix UI description: HF import writes to dataset_sources.jsonl (streamed
by Kaggle), not to corrections.jsonl

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +101 -32
  2. src/data/web_harvester.py +2 -2
app.py CHANGED
@@ -609,27 +609,62 @@ def _count_corrections() -> int:
609
 
610
  def _trigger_kaggle_training(lang: str = "bam") -> str:
611
  """
612
- Push the master trainer notebook to Kaggle via the kaggle CLI,
613
- which creates a new kernel version (i.e. triggers a run).
614
 
615
- Requires KAGGLE_USERNAME + KAGGLE_KEY Space secrets.
616
- The notebook is read from notebooks/ inside the Space filesystem.
617
  """
618
  if not KAGGLE_USERNAME or not KAGGLE_KEY:
619
  return "⚠️ KAGGLE_USERNAME / KAGGLE_KEY not set in Space secrets."
620
 
621
- import subprocess
622
-
623
  notebooks_dir = ROOT / "notebooks"
624
- meta_file = notebooks_dir / "kernel-metadata.json"
625
- nb_file = notebooks_dir / "kaggle_master_trainer.ipynb"
626
 
627
  if not nb_file.exists():
628
  return "❌ notebooks/kaggle_master_trainer.ipynb not found in Space."
629
  if not meta_file.exists():
630
  return "❌ notebooks/kernel-metadata.json not found in Space."
631
 
632
- # Pass credentials directly as env vars (newer kaggle CLI ignores KAGGLE_CONFIG_DIR)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633
  env = {
634
  **os.environ,
635
  "KAGGLE_USERNAME": KAGGLE_USERNAME,
@@ -637,18 +672,18 @@ def _trigger_kaggle_training(lang: str = "bam") -> str:
637
  "PYTHONUTF8": "1",
638
  "PYTHONIOENCODING": "utf-8",
639
  }
640
-
641
- result = subprocess.run(
642
- ["kaggle", "kernels", "push", "-p", str(notebooks_dir)],
643
- capture_output=True, text=True, timeout=60, env=env,
644
- )
645
-
646
- if result.returncode == 0:
647
- output = (result.stdout or "").strip()
648
- return f"✅ Kaggle training triggered!\n{output or 'Kernel version created — check Kaggle for progress.'}"
649
- else:
650
  err = (result.stderr or result.stdout or "unknown error").strip()
651
  return f"❌ Kaggle push failed:\n{err}"
 
 
652
 
653
 
654
  def _maybe_auto_trigger() -> None:
@@ -807,6 +842,7 @@ def _harvest_wikipedia(lang_label: str, max_articles: int = 100) -> str:
807
  lang = SUPPORTED_LANGUAGES.get(lang_label, "bam")
808
  if lang not in ("bam", "ful"):
809
  return "⚠️ Supported for Bambara and Fula only."
 
810
 
811
  try:
812
  from src.data.web_harvester import harvest_wikipedia_text
@@ -839,31 +875,62 @@ def _harvest_hf_dataset(lang_label: str, max_samples: int = 500) -> str:
839
  lang = SUPPORTED_LANGUAGES.get(lang_label, "bam")
840
  if lang not in ("bam", "ful"):
841
  return "⚠️ Supported for Bambara and Fula only."
 
842
 
843
  from src.data.web_harvester import get_hf_dataset_refs
844
  refs = get_hf_dataset_refs(lang)
845
  if not refs:
846
  return f"⚠️ No HF dataset configured for {lang}."
847
 
848
- # Write dataset references with user-requested max_samples cap
849
- entries = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
850
  for ref in refs:
 
 
 
 
851
  entry = dict(ref)
852
- entry["max"] = max_samples
853
- entry["enabled"] = True
854
- entry["added_at"] = datetime.now(timezone.utc).isoformat()
855
- entries.append(entry)
 
 
 
 
 
 
 
 
856
 
857
- total, err = _upload_jsonl("dataset_sources.jsonl", entries)
858
  if err:
859
  return f"❌ Upload failed: {err}"
860
 
861
  repos = ", ".join(r["repo"] for r in refs)
862
  return (
863
  f"✅ Dataset registered for training!\n"
864
- f" Source(s) : {repos}\n"
865
  f" Max samples : {max_samples}\n"
866
- f" The Kaggle notebook will load this dataset directly at training time.\n"
 
867
  f" Total registered sources: {total}"
868
  )
869
 
@@ -1225,11 +1292,13 @@ def build_ui() -> gr.Blocks:
1225
  with gr.Column():
1226
  gr.Markdown(
1227
  "### 🤗 HuggingFace Dataset Import\n"
1228
- "Pulls real audio + transcriptions from:\n"
1229
  "- **Bambara**: `RobotsMali/jeli-asr` (33,000 samples)\n"
1230
- "- **Fula**: `google/fleurs ff_sn`\n\n"
1231
- "Samples are added to `corrections.jsonl` and counted toward "
1232
- f"the auto-training threshold ({AUTO_TRAIN_THRESHOLD} entries)."
 
 
1233
  )
1234
  hf_lang = gr.Dropdown(
1235
  choices=["Bambara (bam)", "Fula (ful)"],
 
609
 
610
  def _trigger_kaggle_training(lang: str = "bam") -> str:
611
  """
612
+ Push the master trainer notebook to Kaggle, creating a new kernel version
613
+ (i.e. triggering a run). Requires KAGGLE_USERNAME + KAGGLE_KEY secrets.
614
 
615
+ Tries the Python API first (no PATH issues), falls back to subprocess.
 
616
  """
617
  if not KAGGLE_USERNAME or not KAGGLE_KEY:
618
  return "⚠️ KAGGLE_USERNAME / KAGGLE_KEY not set in Space secrets."
619
 
 
 
620
  notebooks_dir = ROOT / "notebooks"
621
+ nb_file = notebooks_dir / "kaggle_master_trainer.ipynb"
622
+ meta_file = notebooks_dir / "kernel-metadata.json"
623
 
624
  if not nb_file.exists():
625
  return "❌ notebooks/kaggle_master_trainer.ipynb not found in Space."
626
  if not meta_file.exists():
627
  return "❌ notebooks/kernel-metadata.json not found in Space."
628
 
629
+ # Inject credentials into os.environ before any kaggle import
630
+ # kaggle.authenticate() reads KAGGLE_USERNAME + KAGGLE_KEY from env.
631
+ os.environ["KAGGLE_USERNAME"] = KAGGLE_USERNAME
632
+ os.environ["KAGGLE_KEY"] = KAGGLE_KEY
633
+
634
+ # ── Method 1: Python API (no binary PATH issues) ─────────────────────────
635
+ api_err = None
636
+ try:
637
+ from kaggle.api.kaggle_api_extended import KaggleApiExtended
638
+ _kapi = KaggleApiExtended()
639
+ _kapi.authenticate()
640
+ _kapi.kernels_push_cli(str(notebooks_dir), quiet=True)
641
+ return (
642
+ "✅ Kaggle training triggered!\n"
643
+ f"Kernel: {KAGGLE_KERNEL_SLUG}\n"
644
+ "Check https://www.kaggle.com for run progress."
645
+ )
646
+ except Exception as e:
647
+ api_err = str(e)
648
+
649
+ # ── Method 2: subprocess fallback ────────────────────────────────────────
650
+ import subprocess, shutil
651
+ kaggle_bin = shutil.which("kaggle")
652
+ if kaggle_bin is None:
653
+ for cand in [
654
+ Path(sys.executable).parent / "kaggle",
655
+ Path("/usr/local/bin/kaggle"),
656
+ Path("/usr/bin/kaggle"),
657
+ ]:
658
+ if cand.exists():
659
+ kaggle_bin = str(cand)
660
+ break
661
+
662
+ if kaggle_bin is None:
663
+ return (
664
+ f"❌ Kaggle CLI not found (API error: {api_err}).\n"
665
+ "Ensure kaggle>=1.6.0 is in requirements.txt and the Space rebuilt."
666
+ )
667
+
668
  env = {
669
  **os.environ,
670
  "KAGGLE_USERNAME": KAGGLE_USERNAME,
 
672
  "PYTHONUTF8": "1",
673
  "PYTHONIOENCODING": "utf-8",
674
  }
675
+ try:
676
+ result = subprocess.run(
677
+ [kaggle_bin, "kernels", "push", "-p", str(notebooks_dir)],
678
+ capture_output=True, text=True, timeout=60, env=env,
679
+ )
680
+ if result.returncode == 0:
681
+ out = (result.stdout or "").strip()
682
+ return f"✅ Kaggle training triggered!\n{out or 'Kernel version created.'}"
 
 
683
  err = (result.stderr or result.stdout or "unknown error").strip()
684
  return f"❌ Kaggle push failed:\n{err}"
685
+ except Exception as e:
686
+ return f"❌ Kaggle push failed (API: {api_err}) (CLI: {e})"
687
 
688
 
689
  def _maybe_auto_trigger() -> None:
 
842
  lang = SUPPORTED_LANGUAGES.get(lang_label, "bam")
843
  if lang not in ("bam", "ful"):
844
  return "⚠️ Supported for Bambara and Fula only."
845
+ max_articles = int(max_articles) # Gradio slider returns float
846
 
847
  try:
848
  from src.data.web_harvester import harvest_wikipedia_text
 
875
  lang = SUPPORTED_LANGUAGES.get(lang_label, "bam")
876
  if lang not in ("bam", "ful"):
877
  return "⚠️ Supported for Bambara and Fula only."
878
+ max_samples = int(max_samples) # Gradio slider returns float
879
 
880
  from src.data.web_harvester import get_hf_dataset_refs
881
  refs = get_hf_dataset_refs(lang)
882
  if not refs:
883
  return f"⚠️ No HF dataset configured for {lang}."
884
 
885
+ # Read existing entries to avoid duplicates
886
+ from huggingface_hub import hf_hub_download
887
+ try:
888
+ local = hf_hub_download(
889
+ repo_id=FEEDBACK_REPO_ID, filename="dataset_sources.jsonl",
890
+ repo_type="dataset", token=HF_TOKEN,
891
+ )
892
+ with open(local, encoding="utf-8") as f:
893
+ existing_entries = [json.loads(l) for l in f if l.strip()]
894
+ except Exception:
895
+ existing_entries = []
896
+
897
+ existing_keys = {
898
+ (e.get("repo", e.get("repo_id", "")), e.get("config", ""))
899
+ for e in existing_entries
900
+ }
901
+
902
+ new_entries = []
903
+ already = []
904
  for ref in refs:
905
+ key = (ref.get("repo", ref.get("repo_id", "")), ref.get("config", ""))
906
+ if key in existing_keys:
907
+ already.append(ref["repo"])
908
+ continue
909
  entry = dict(ref)
910
+ entry["max"] = max_samples
911
+ entry["enabled"] = True
912
+ entry["added_at"] = datetime.now(timezone.utc).isoformat()
913
+ new_entries.append(entry)
914
+
915
+ if not new_entries:
916
+ repos = ", ".join(already)
917
+ return (
918
+ f"✅ Already registered!\n"
919
+ f" `{repos}` is already in your training config.\n"
920
+ f" Click 'Trigger Training Now' to start a run with this data."
921
+ )
922
 
923
+ total, err = _upload_jsonl("dataset_sources.jsonl", new_entries)
924
  if err:
925
  return f"❌ Upload failed: {err}"
926
 
927
  repos = ", ".join(r["repo"] for r in refs)
928
  return (
929
  f"✅ Dataset registered for training!\n"
930
+ f" Source(s) : {repos}\n"
931
  f" Max samples : {max_samples}\n"
932
+ f" The Kaggle notebook will stream this dataset directly at training time.\n"
933
+ f" Click 'Trigger Training Now' to start a training run.\n"
934
  f" Total registered sources: {total}"
935
  )
936
 
 
1292
  with gr.Column():
1293
  gr.Markdown(
1294
  "### 🤗 HuggingFace Dataset Import\n"
1295
+ "Registers large public datasets as training sources:\n"
1296
  "- **Bambara**: `RobotsMali/jeli-asr` (33,000 samples)\n"
1297
+ "- **Fula**: `google/WaxalNLP ful_asr`\n\n"
1298
+ "This writes a reference to `dataset_sources.jsonl`. "
1299
+ "The Kaggle training notebook streams the dataset directly "
1300
+ "at training time — no re-upload needed.\n\n"
1301
+ "**One click is enough** — duplicates are ignored automatically."
1302
  )
1303
  hf_lang = gr.Dropdown(
1304
  choices=["Bambara (bam)", "Fula (ful)"],
src/data/web_harvester.py CHANGED
@@ -40,8 +40,8 @@ HF_DATASET_REGISTRY = {
40
  ],
41
  "ful": [
42
  {
43
- "repo": "google/fleurs",
44
- "config": "ff_sn",
45
  "split": "train",
46
  "audio_col": "audio",
47
  "text_col": "transcription",
 
40
  ],
41
  "ful": [
42
  {
43
+ "repo": "google/WaxalNLP",
44
+ "config": "ful_asr",
45
  "split": "train",
46
  "audio_col": "audio",
47
  "text_col": "transcription",