Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .env.example +4 -0
- .gitignore +0 -68
- README.md +25 -0
- data/transcripts/office_generated.txt +0 -0
- witgym/config.py +1 -0
- witgym/hub_data.py +64 -0
- witgym/retriever.py +12 -0
.env.example
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
HF_TOKEN=hf_...
|
| 2 |
+
WITGYM_DATA_REPO=akshay4/witgym-data
|
| 3 |
+
WITGYM_SKIP_HUB=1
|
| 4 |
+
LLM_BACKEND=hf_api
|
.gitignore
DELETED
|
@@ -1,68 +0,0 @@
|
|
| 1 |
-
# Local index is generated; ship transcripts only (Space builds index on startup)
|
| 2 |
-
data/*
|
| 3 |
-
!data/transcripts/
|
| 4 |
-
!data/transcripts/**
|
| 5 |
-
|
| 6 |
-
# Virtual environments
|
| 7 |
-
.venv/
|
| 8 |
-
venv/
|
| 9 |
-
env/
|
| 10 |
-
|
| 11 |
-
# Environment and secrets
|
| 12 |
-
.env
|
| 13 |
-
.env.*
|
| 14 |
-
!.env.example
|
| 15 |
-
|
| 16 |
-
# Python bytecode and packaging
|
| 17 |
-
__pycache__/
|
| 18 |
-
*.py[cod]
|
| 19 |
-
*$py.class
|
| 20 |
-
*.so
|
| 21 |
-
.Python
|
| 22 |
-
*.egg
|
| 23 |
-
*.egg-info/
|
| 24 |
-
.eggs/
|
| 25 |
-
dist/
|
| 26 |
-
build/
|
| 27 |
-
pip-wheel-metadata/
|
| 28 |
-
|
| 29 |
-
# Test and type-check caches
|
| 30 |
-
.pytest_cache/
|
| 31 |
-
.mypy_cache/
|
| 32 |
-
.ruff_cache/
|
| 33 |
-
htmlcov/
|
| 34 |
-
.coverage
|
| 35 |
-
.coverage.*
|
| 36 |
-
coverage.xml
|
| 37 |
-
*.cover
|
| 38 |
-
.hypothesis/
|
| 39 |
-
|
| 40 |
-
# Jupyter
|
| 41 |
-
.ipynb_checkpoints/
|
| 42 |
-
|
| 43 |
-
# IDE and editors
|
| 44 |
-
.idea/
|
| 45 |
-
.vscode/
|
| 46 |
-
*.swp
|
| 47 |
-
*.swo
|
| 48 |
-
*~
|
| 49 |
-
|
| 50 |
-
# OS
|
| 51 |
-
.DS_Store
|
| 52 |
-
Thumbs.db
|
| 53 |
-
|
| 54 |
-
# ML / Hugging Face caches and artifacts
|
| 55 |
-
.cache/
|
| 56 |
-
huggingface/
|
| 57 |
-
checkpoints/
|
| 58 |
-
runs/
|
| 59 |
-
wandb/
|
| 60 |
-
mlruns/
|
| 61 |
-
*.pt
|
| 62 |
-
*.pth
|
| 63 |
-
*.ckpt
|
| 64 |
-
*.safetensors
|
| 65 |
-
|
| 66 |
-
# Logs
|
| 67 |
-
*.log
|
| 68 |
-
logs/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -49,9 +49,34 @@ python app.py
|
|
| 49 |
|--------|--------|
|
| 50 |
| `HF_TOKEN` | Your Hugging Face access token |
|
| 51 |
| `LLM_BACKEND` | `hf_api` (recommended on Spaces) |
|
|
|
|
| 52 |
|
| 53 |
Optional: `HF_INFERENCE_PROVIDER` (defaults to `together` for Qwen3.5-9B — required for `enable_thinking: false`), `WITGYM_INDEX_PATH`.
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
## Architecture
|
| 56 |
|
| 57 |
- **Small talk** (hi, who are you) → polite identity reply, no pipeline
|
|
|
|
| 49 |
|--------|--------|
|
| 50 |
| `HF_TOKEN` | Your Hugging Face access token |
|
| 51 |
| `LLM_BACKEND` | `hf_api` (recommended on Spaces) |
|
| 52 |
+
| `WITGYM_DATA_REPO` | Private dataset repo, e.g. `akshay4/witgym-data` |
|
| 53 |
|
| 54 |
Optional: `HF_INFERENCE_PROVIDER` (defaults to `together` for Qwen3.5-9B — required for `enable_thinking: false`), `WITGYM_INDEX_PATH`.
|
| 55 |
|
| 56 |
+
## Large data on Hugging Face Hub
|
| 57 |
+
|
| 58 |
+
Files over 1 MB (`office_generated.txt`, `index.npz`) live in a **private dataset repo**, not in git. The app fetches them at startup via `hf_hub_download`.
|
| 59 |
+
|
| 60 |
+
**One-time setup** (create private dataset `witgym-data`, then upload):
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
hf upload akshay4/witgym-data \
|
| 64 |
+
data/transcripts/office_generated.txt office_generated.txt \
|
| 65 |
+
--repo-type dataset --private
|
| 66 |
+
|
| 67 |
+
hf upload akshay4/witgym-data \
|
| 68 |
+
data/index.npz index.npz \
|
| 69 |
+
--repo-type dataset
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
**When transcripts change:** re-run `witgym-index`, then re-upload `index.npz`.
|
| 73 |
+
|
| 74 |
+
**Offline local dev:** set `WITGYM_SKIP_HUB=1` and keep local copies in `data/`.
|
| 75 |
+
|
| 76 |
+
## Deploy (GitHub → Space)
|
| 77 |
+
|
| 78 |
+
Pushes to `main` sync to [build-small-hackathon/WitGym](https://huggingface.co/spaces/build-small-hackathon/WitGym) via `.github/workflows/sync-to-hub.yml`. Add `HF_TOKEN` as a GitHub repository secret.
|
| 79 |
+
|
| 80 |
## Architecture
|
| 81 |
|
| 82 |
- **Small talk** (hi, who are you) → polite identity reply, no pipeline
|
data/transcripts/office_generated.txt
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
witgym/config.py
CHANGED
|
@@ -57,6 +57,7 @@ CLICHE_PENALTY_TOKENS = 6 # Penalise first N tokens of the obvious response
|
|
| 57 |
|
| 58 |
# HuggingFace auth + inference backend
|
| 59 |
HF_TOKEN = os.getenv("HF_TOKEN", "")
|
|
|
|
| 60 |
# "local" = Transformers on device; "hf_api" = Inference Providers (Spaces default)
|
| 61 |
LLM_BACKEND = os.getenv("LLM_BACKEND", "local")
|
| 62 |
# Qwen3.5-9B thinking-mode toggle requires Together; "auto" often 400s on extra_body
|
|
|
|
| 57 |
|
| 58 |
# HuggingFace auth + inference backend
|
| 59 |
HF_TOKEN = os.getenv("HF_TOKEN", "")
|
| 60 |
+
WITGYM_DATA_REPO = os.getenv("WITGYM_DATA_REPO", "")
|
| 61 |
# "local" = Transformers on device; "hf_api" = Inference Providers (Spaces default)
|
| 62 |
LLM_BACKEND = os.getenv("LLM_BACKEND", "local")
|
| 63 |
# Qwen3.5-9B thinking-mode toggle requires Together; "auto" often 400s on extra_body
|
witgym/hub_data.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Fetch >1MB artifacts from a private HF dataset repo."""
|
| 2 |
+
import os
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional
|
| 5 |
+
|
| 6 |
+
from loguru import logger
|
| 7 |
+
|
| 8 |
+
from witgym import config
|
| 9 |
+
|
| 10 |
+
HUB_ARTIFACTS = ("index.npz", "office_generated.txt")
|
| 11 |
+
HUB_CACHE_DIR = "data/hub_cache"
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _local_default_path(filename: str) -> Optional[Path]:
|
| 15 |
+
if filename == "index.npz":
|
| 16 |
+
return Path(config.INDEX_PATH)
|
| 17 |
+
if filename == "office_generated.txt":
|
| 18 |
+
return Path(config.TRANSCRIPT_DIR) / filename
|
| 19 |
+
return None
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def ensure_artifact(filename: str, local_dir: str = HUB_CACHE_DIR) -> Path:
|
| 23 |
+
"""Return local path; use existing file, cache, or download from Hub."""
|
| 24 |
+
default = _local_default_path(filename)
|
| 25 |
+
if default is not None and default.exists():
|
| 26 |
+
return default
|
| 27 |
+
|
| 28 |
+
cache_dest = Path(local_dir) / filename
|
| 29 |
+
if cache_dest.exists():
|
| 30 |
+
return cache_dest
|
| 31 |
+
|
| 32 |
+
if os.getenv("WITGYM_SKIP_HUB") or not config.WITGYM_DATA_REPO or not config.HF_TOKEN:
|
| 33 |
+
raise FileNotFoundError(f"{filename} not found locally and Hub fetch disabled")
|
| 34 |
+
|
| 35 |
+
from huggingface_hub import hf_hub_download
|
| 36 |
+
|
| 37 |
+
logger.info(f"Downloading {filename} from {config.WITGYM_DATA_REPO}")
|
| 38 |
+
downloaded = hf_hub_download(
|
| 39 |
+
repo_id=config.WITGYM_DATA_REPO,
|
| 40 |
+
filename=filename,
|
| 41 |
+
repo_type="dataset",
|
| 42 |
+
token=config.HF_TOKEN,
|
| 43 |
+
local_dir=local_dir,
|
| 44 |
+
)
|
| 45 |
+
return Path(downloaded)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def materialize_hub_transcripts() -> None:
|
| 49 |
+
"""Copy hub transcript into data/transcripts/ so indexer glob stays unchanged."""
|
| 50 |
+
if os.getenv("WITGYM_SKIP_HUB") or not config.WITGYM_DATA_REPO:
|
| 51 |
+
return
|
| 52 |
+
|
| 53 |
+
dest = Path(config.TRANSCRIPT_DIR) / "office_generated.txt"
|
| 54 |
+
if dest.exists():
|
| 55 |
+
return
|
| 56 |
+
|
| 57 |
+
try:
|
| 58 |
+
hub_txt = ensure_artifact("office_generated.txt")
|
| 59 |
+
except FileNotFoundError:
|
| 60 |
+
return
|
| 61 |
+
|
| 62 |
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
| 63 |
+
dest.write_bytes(hub_txt.read_bytes())
|
| 64 |
+
logger.info(f"Materialized {dest} from Hub")
|
witgym/retriever.py
CHANGED
|
@@ -28,6 +28,18 @@ def load_index(index_path: str = config.INDEX_PATH) -> dict:
|
|
| 28 |
if os.path.exists(npz_fallback):
|
| 29 |
path = npz_fallback
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
if not os.path.exists(path):
|
| 32 |
transcript_dir = Path(config.TRANSCRIPT_DIR)
|
| 33 |
if transcript_dir.is_dir() and any(transcript_dir.glob("*.txt")):
|
|
|
|
| 28 |
if os.path.exists(npz_fallback):
|
| 29 |
path = npz_fallback
|
| 30 |
|
| 31 |
+
from witgym.hub_data import materialize_hub_transcripts
|
| 32 |
+
|
| 33 |
+
materialize_hub_transcripts()
|
| 34 |
+
|
| 35 |
+
if not os.path.exists(path) and config.WITGYM_DATA_REPO:
|
| 36 |
+
from witgym.hub_data import ensure_artifact
|
| 37 |
+
|
| 38 |
+
try:
|
| 39 |
+
path = str(ensure_artifact("index.npz"))
|
| 40 |
+
except FileNotFoundError:
|
| 41 |
+
pass
|
| 42 |
+
|
| 43 |
if not os.path.exists(path):
|
| 44 |
transcript_dir = Path(config.TRANSCRIPT_DIR)
|
| 45 |
if transcript_dir.is_dir() and any(transcript_dir.glob("*.txt")):
|