Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- src/search.py +2 -1
- src/server.py +14 -11
- src/storage.py +72 -9
- vercel.json +2 -2
src/search.py
CHANGED
|
@@ -12,8 +12,9 @@ from rank_bm25 import BM25Okapi
|
|
| 12 |
load_dotenv()
|
| 13 |
|
| 14 |
from src.embeddings import Embedder, default_min_score, embeddings_filename
|
|
|
|
| 15 |
|
| 16 |
-
DATA_DIR =
|
| 17 |
|
| 18 |
RRF_K = 60
|
| 19 |
HYBRID_TOP_N = 100
|
|
|
|
| 12 |
load_dotenv()
|
| 13 |
|
| 14 |
from src.embeddings import Embedder, default_min_score, embeddings_filename
|
| 15 |
+
from src.storage import resolve_data_dir
|
| 16 |
|
| 17 |
+
DATA_DIR = resolve_data_dir()
|
| 18 |
|
| 19 |
RRF_K = 60
|
| 20 |
HYBRID_TOP_N = 100
|
src/server.py
CHANGED
|
@@ -21,26 +21,29 @@ engine: SearchEngine | None = None
|
|
| 21 |
|
| 22 |
|
| 23 |
def _embedding_files_to_fetch() -> list[str]:
|
| 24 |
-
"""
|
| 25 |
-
if not os.environ.get("HF_DATASET_REPO"):
|
| 26 |
-
return []
|
| 27 |
provider = get_provider()
|
| 28 |
-
return [
|
| 29 |
-
embeddings_filename(provider, multi=True),
|
| 30 |
-
embeddings_filename(provider, multi=False),
|
| 31 |
-
]
|
| 32 |
|
| 33 |
|
| 34 |
@asynccontextmanager
|
| 35 |
async def lifespan(app: FastAPI):
|
| 36 |
global engine
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
engine = SearchEngine()
|
| 42 |
print(f"Loaded {len(engine.companies)} companies with embeddings of shape {engine.embeddings.shape}")
|
| 43 |
print(f"Embedding provider: {engine.embedder.provider}")
|
|
|
|
| 44 |
yield
|
| 45 |
|
| 46 |
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
def _embedding_files_to_fetch() -> list[str]:
|
| 24 |
+
"""Filenames to ensure locally (prefer multi-field for the active provider)."""
|
|
|
|
|
|
|
| 25 |
provider = get_provider()
|
| 26 |
+
return [embeddings_filename(provider, multi=True)]
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
|
| 29 |
@asynccontextmanager
|
| 30 |
async def lifespan(app: FastAPI):
|
| 31 |
global engine
|
| 32 |
+
from src.storage import ensure_embeddings, resolve_data_dir
|
| 33 |
+
|
| 34 |
+
# On Vercel, /var/task is read-only and azure .npy is gitignored β resolve a
|
| 35 |
+
# writable data dir and pull embeddings from HF Hub when missing.
|
| 36 |
+
data_dir = resolve_data_dir()
|
| 37 |
+
os.environ["DATA_DIR"] = str(data_dir)
|
| 38 |
+
ensure_embeddings(_embedding_files_to_fetch(), data_dir=data_dir)
|
| 39 |
+
|
| 40 |
+
import src.search as search_mod
|
| 41 |
+
search_mod.DATA_DIR = data_dir
|
| 42 |
+
|
| 43 |
engine = SearchEngine()
|
| 44 |
print(f"Loaded {len(engine.companies)} companies with embeddings of shape {engine.embeddings.shape}")
|
| 45 |
print(f"Embedding provider: {engine.embedder.provider}")
|
| 46 |
+
print(f"Data dir: {data_dir}")
|
| 47 |
yield
|
| 48 |
|
| 49 |
|
src/storage.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
| 1 |
"""Download embedding files from Hugging Face Hub Dataset at startup if not present locally."""
|
| 2 |
|
| 3 |
import os
|
|
|
|
| 4 |
from pathlib import Path
|
| 5 |
|
| 6 |
-
|
|
|
|
| 7 |
|
| 8 |
|
| 9 |
def _is_valid_npy(path: Path) -> bool:
|
|
@@ -15,12 +17,71 @@ def _is_valid_npy(path: Path) -> bool:
|
|
| 15 |
return False
|
| 16 |
|
| 17 |
|
| 18 |
-
def
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
from huggingface_hub import hf_hub_download
|
| 26 |
|
|
@@ -29,18 +90,20 @@ def ensure_embeddings(filenames: list[str]) -> None:
|
|
| 29 |
|
| 30 |
if not repo_id:
|
| 31 |
raise RuntimeError(
|
| 32 |
-
"Missing
|
|
|
|
| 33 |
)
|
| 34 |
|
| 35 |
print(f"Downloading {len(missing)} embedding file(s) from HF dataset '{repo_id}' ...")
|
| 36 |
for filename in missing:
|
| 37 |
-
dest =
|
| 38 |
print(f" β {filename}")
|
| 39 |
hf_hub_download(
|
| 40 |
repo_id=repo_id,
|
| 41 |
filename=filename,
|
| 42 |
repo_type="dataset",
|
| 43 |
token=token,
|
| 44 |
-
local_dir=str(
|
| 45 |
)
|
| 46 |
print(f" β {filename} ({dest.stat().st_size / 1e6:.1f} MB)")
|
|
|
|
|
|
| 1 |
"""Download embedding files from Hugging Face Hub Dataset at startup if not present locally."""
|
| 2 |
|
| 3 |
import os
|
| 4 |
+
import shutil
|
| 5 |
from pathlib import Path
|
| 6 |
|
| 7 |
+
# Bundled/repo data directory (read-only on Vercel/Lambda).
|
| 8 |
+
REPO_DATA_DIR = Path(__file__).resolve().parent.parent / "data"
|
| 9 |
|
| 10 |
|
| 11 |
def _is_valid_npy(path: Path) -> bool:
|
|
|
|
| 17 |
return False
|
| 18 |
|
| 19 |
|
| 20 |
+
def _is_writable(path: Path) -> bool:
|
| 21 |
+
try:
|
| 22 |
+
path.mkdir(parents=True, exist_ok=True)
|
| 23 |
+
probe = path / ".write_test"
|
| 24 |
+
probe.write_text("ok")
|
| 25 |
+
probe.unlink()
|
| 26 |
+
return True
|
| 27 |
+
except Exception:
|
| 28 |
+
return False
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def resolve_data_dir() -> Path:
|
| 32 |
+
"""Return a writable data dir. On Vercel, /var/task is read-only so use /tmp."""
|
| 33 |
+
if explicit := os.environ.get("DATA_DIR"):
|
| 34 |
+
p = Path(explicit)
|
| 35 |
+
p.mkdir(parents=True, exist_ok=True)
|
| 36 |
+
return p
|
| 37 |
+
if _is_writable(REPO_DATA_DIR):
|
| 38 |
+
return REPO_DATA_DIR
|
| 39 |
+
tmp = Path("/tmp/nichefind-data")
|
| 40 |
+
tmp.mkdir(parents=True, exist_ok=True)
|
| 41 |
+
return tmp
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def ensure_companies_json(data_dir: Path) -> None:
|
| 45 |
+
"""Make sure companies.json is available in data_dir (copy from bundle if needed)."""
|
| 46 |
+
dest = data_dir / "companies.json"
|
| 47 |
+
if dest.exists():
|
| 48 |
return
|
| 49 |
+
src = REPO_DATA_DIR / "companies.json"
|
| 50 |
+
if not src.exists():
|
| 51 |
+
raise FileNotFoundError(f"Missing companies.json at {src}")
|
| 52 |
+
if src.resolve() != dest.resolve():
|
| 53 |
+
shutil.copy2(src, dest)
|
| 54 |
+
print(f"Copied companies.json β {dest}")
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def ensure_embeddings(filenames: list[str], data_dir: Path | None = None) -> Path:
|
| 58 |
+
"""Download any missing or corrupted embedding files from HF Hub Dataset.
|
| 59 |
+
|
| 60 |
+
Returns the data directory where files live.
|
| 61 |
+
"""
|
| 62 |
+
data_dir = data_dir or resolve_data_dir()
|
| 63 |
+
ensure_companies_json(data_dir)
|
| 64 |
+
|
| 65 |
+
missing = [
|
| 66 |
+
f for f in filenames
|
| 67 |
+
if not (data_dir / f).exists() or not _is_valid_npy(data_dir / f)
|
| 68 |
+
]
|
| 69 |
+
# Also check the bundled repo dir (CLI deploys that include the .npy locally)
|
| 70 |
+
if missing:
|
| 71 |
+
still_missing = []
|
| 72 |
+
for f in missing:
|
| 73 |
+
bundled = REPO_DATA_DIR / f
|
| 74 |
+
if bundled.exists() and _is_valid_npy(bundled):
|
| 75 |
+
if bundled.resolve() != (data_dir / f).resolve():
|
| 76 |
+
shutil.copy2(bundled, data_dir / f)
|
| 77 |
+
print(f"Copied {f} β {data_dir / f}")
|
| 78 |
+
else:
|
| 79 |
+
still_missing.append(f)
|
| 80 |
+
missing = still_missing
|
| 81 |
+
|
| 82 |
+
if not missing:
|
| 83 |
+
print("Embeddings already present, skipping download.")
|
| 84 |
+
return data_dir
|
| 85 |
|
| 86 |
from huggingface_hub import hf_hub_download
|
| 87 |
|
|
|
|
| 90 |
|
| 91 |
if not repo_id:
|
| 92 |
raise RuntimeError(
|
| 93 |
+
"Missing embeddings and HF_DATASET_REPO is not set. "
|
| 94 |
+
"Either bundle embeddings.azure.multi.npy or set HF_DATASET_REPO."
|
| 95 |
)
|
| 96 |
|
| 97 |
print(f"Downloading {len(missing)} embedding file(s) from HF dataset '{repo_id}' ...")
|
| 98 |
for filename in missing:
|
| 99 |
+
dest = data_dir / filename
|
| 100 |
print(f" β {filename}")
|
| 101 |
hf_hub_download(
|
| 102 |
repo_id=repo_id,
|
| 103 |
filename=filename,
|
| 104 |
repo_type="dataset",
|
| 105 |
token=token,
|
| 106 |
+
local_dir=str(data_dir),
|
| 107 |
)
|
| 108 |
print(f" β {filename} ({dest.stat().st_size / 1e6:.1f} MB)")
|
| 109 |
+
return data_dir
|
vercel.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
| 4 |
],
|
| 5 |
"functions": {
|
| 6 |
"api/index.py": {
|
| 7 |
-
"maxDuration":
|
| 8 |
-
"includeFiles": "data/
|
| 9 |
}
|
| 10 |
}
|
| 11 |
}
|
|
|
|
| 4 |
],
|
| 5 |
"functions": {
|
| 6 |
"api/index.py": {
|
| 7 |
+
"maxDuration": 300,
|
| 8 |
+
"includeFiles": "{data/companies.json,static/**}"
|
| 9 |
}
|
| 10 |
}
|
| 11 |
}
|