DSN / scripts /docker_build_assets.py
nexusbert's picture
Enhance agent workflow and integration of Gemini API for text generation
652302c
Raw
History Blame Contribute Delete
12.5 kB
#!/usr/bin/env python3
from __future__ import annotations
import hashlib
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any
def root() -> Path:
return Path(__file__).resolve().parents[1]
def hf_token() -> str | None:
t = (
os.environ.get("HF_TOKEN", "").strip()
or os.environ.get("HUGGING_FACE_HUB_TOKEN", "").strip()
)
return t or None
def hf_login() -> None:
tok = hf_token()
if not tok:
print(
"docker_build_assets: No HF_TOKEN / HUGGING_FACE_HUB_TOKEN — anonymous Hub access "
"(rate limits). On HF Spaces, pass token into the *Docker build* (not only runtime)."
)
return
try:
from huggingface_hub import login # type: ignore[import-untyped]
except ImportError:
print("docker_build_assets: huggingface_hub not installed; skipping login.")
return
login(token=tok, add_to_git_credential=False)
print("docker_build_assets: Hugging Face Hub login OK.")
def embedding_model_repo(hub_name: str) -> str:
if "/" not in hub_name.strip():
return f"sentence-transformers/{hub_name.strip()}"
return hub_name.strip()
def _local_embedding_model() -> str:
return (
os.environ.get("LOCAL_EMBEDDING_MODEL", "").strip()
or os.environ.get("TASK_B_LOCAL_EMBEDDING_MODEL", "").strip()
or os.environ.get("TASK_A_EMBEDDING_MODEL", "").strip()
or "all-MiniLM-L6-v2"
)
def _skip_local_llm_hub() -> bool:
if os.environ.get("SKIP_LOCAL_LLM_HUB_DOWNLOAD", "").strip().lower() in (
"1",
"true",
"yes",
):
return True
if os.environ.get("GENERATION_BACKEND", "").strip().lower() in ("gemini", "google"):
return True
return False
def _unique_llm_hub_ids() -> list[str]:
if _skip_local_llm_hub():
return []
seen: list[str] = []
for key in ("LOCAL_LLM_MODEL", "TASK_B_LOCAL_LLM_MODEL", "TASK_A_LOCAL_LLM_MODEL"):
v = os.environ.get(key, "").strip()
if v and v not in seen:
seen.append(v)
if not seen:
seen.append("Qwen/Qwen2.5-1.5B-Instruct")
return seen
_build_embedder: Any = None
def _get_build_embedder() -> Any:
global _build_embedder
if _build_embedder is None:
from sentence_transformers import SentenceTransformer # type: ignore[import-untyped]
emb_name = _local_embedding_model()
print(f"docker_build_assets: loading embedder once for stub JSONL -> {emb_name}")
_build_embedder = SentenceTransformer(emb_name)
return _build_embedder
def prefetch_hub_files_only() -> None:
tok = hf_token()
try:
from huggingface_hub import snapshot_download # type: ignore[import-untyped]
except ImportError:
print("docker_build_assets: huggingface_hub missing; skipping prefetch.")
return
kw: dict[str, Any] = {}
if tok:
kw["token"] = tok
emb_repo = embedding_model_repo(_local_embedding_model())
print(f"docker_build_assets: snapshot_download (disk cache) -> {emb_repo}")
snapshot_download(repo_id=emb_repo, local_files_only=False, **kw)
llm_ids = _unique_llm_hub_ids()
if not llm_ids:
print(
"docker_build_assets: skipping causal LM snapshot "
"(GENERATION_BACKEND=gemini or SKIP_LOCAL_LLM_HUB_DOWNLOAD=1)."
)
for llm_id in llm_ids:
print(f"docker_build_assets: snapshot_download (disk cache) -> {llm_id}")
snapshot_download(repo_id=llm_id, local_files_only=False, **kw)
print("docker_build_assets: Hub snapshots cached (embedder on disk; LLM via API or runtime load).")
def warm_runtime_models() -> None:
raw = os.environ.get("DOCKER_BUILD_SKIP_LLM_WARM", "1").strip().lower()
skip = raw not in ("0", "false", "no")
if skip:
print(
"docker_build_assets: skipping in-RAM LLM warm (DOCKER_BUILD_SKIP_LLM_WARM default 1). "
"Weights are on disk from snapshot_download + stub encodes; uvicorn prewarm loads them at runtime."
)
return
print("docker_build_assets: full model warm (CPU) — DOCKER_BUILD_SKIP_LLM_WARM=0; needs several GB RAM.")
import gc
st = _get_build_embedder()
st.encode(["docker-build-warmup"], batch_size=1, show_progress_bar=False, convert_to_numpy=True)
gc.collect()
import torch # type: ignore[import-untyped]
from transformers import AutoModelForCausalLM, AutoTokenizer # type: ignore[import-untyped]
for mid in _unique_llm_hub_ids():
print(f"docker_build_assets: causal LM warm — {mid}")
tok = AutoTokenizer.from_pretrained(mid, trust_remote_code=True)
mdl = AutoModelForCausalLM.from_pretrained(
mid,
torch_dtype=torch.float32,
trust_remote_code=True,
low_cpu_mem_usage=True,
)
mdl.eval()
with torch.no_grad():
batch = tok("warmup", return_tensors="pt")
mdl(**batch)
del mdl, tok
gc.collect()
print("docker_build_assets: model warm complete.")
def yelp_review_path(rt: Path) -> Path:
env_p = os.environ.get("YELP_REVIEW_JSON", "").strip()
if env_p:
return Path(env_p)
return rt / "yelp_dataset" / "extracted" / "yelp_academic_dataset_review.json"
def build_task_a_from_yelp(rt: Path, business_json: Path, review_json: Path) -> None:
py = sys.executable
max_rev = os.environ.get("DOCKER_TASK_A_MAX_REVIEW_ROWS", "10000")
subprocess.check_call(
[
py,
str(rt / "scripts" / "build_task_a_review_rag.py"),
"--review-json",
str(review_json),
"--business-json",
str(business_json),
"--output",
str(rt / "data" / "task_a_reviews_embedded.jsonl"),
"--max-rows",
max_rev,
"--batch-size",
"32",
]
)
def build_stub_task_a_embedded(rt: Path, model: Any) -> None:
rows_raw: list[dict[str, Any]] = []
for i in range(36):
uid = f"stub_user_{i % 9}"
excerpt = (
f"Stub visit {i}: food was {'solid' if i % 2 == 0 else 'hit or miss'}, "
f"service {'friendly' if i % 3 else 'slow'}."
)
bctx = (
f"name: Stub Diner {i % 6}\ncategories: Restaurants, "
f"{'Italian' if i % 3 else 'Mexican'}\nlocation: Lagos, LA\n"
f"business_avg_stars: {3.4 + (i % 8) / 10:.1f}\n"
f"business_review_count: {15 + i * 3}"
)
emb_src = f"{bctx}\nreview: {excerpt}"
rows_raw.append(
{
"user_id": uid,
"business_id": f"stub_b_{i}",
"stars": 1 + (i % 5),
"review_excerpt": excerpt,
"business_context": bctx,
"embedding_source": emb_src,
}
)
texts = [r["embedding_source"] for r in rows_raw]
mat = model.encode(texts, batch_size=8, convert_to_numpy=True, normalize_embeddings=False)
out_path = rt / "data" / "task_a_reviews_embedded.jsonl"
with out_path.open("w", encoding="utf-8") as fout:
for rec, vec in zip(rows_raw, mat, strict=True):
row_out = {k: v for k, v in rec.items() if k != "embedding_source"}
row_out["embedding"] = vec.astype(float).tolist()
fout.write(json.dumps(row_out, ensure_ascii=False) + "\n")
print(f"docker_build_assets: wrote stub Task A RAG -> {out_path}")
def yelp_business_path(rt: Path) -> Path:
env_p = os.environ.get("YELP_BUSINESS_JSON", "").strip()
if env_p:
return Path(env_p)
return rt / "yelp_dataset" / "extracted" / "yelp_academic_dataset_business.json"
def build_from_yelp(rt: Path, yelp: Path) -> None:
max_rows = os.environ.get("DOCKER_CATALOG_MAX_ROWS", "15000")
out_cat = rt / "data" / "business_catalog.jsonl"
py = sys.executable
subprocess.check_call(
[
py,
str(rt / "scripts" / "build_business_catalog.py"),
"--business-json",
str(yelp),
"--output",
str(out_cat),
"--max-rows",
max_rows,
"--only-open",
]
)
subprocess.check_call(
[
py,
str(rt / "scripts" / "embed_catalog.py"),
"--input",
str(out_cat),
"--output",
str(rt / "data" / "business_catalog_embedded.jsonl"),
"--batch-size",
"32",
]
)
def stub_catalog_rows(n: int = 48) -> list[dict[str, Any]]:
templates = [
("Riverfront Ramen", "Restaurants, Japanese, Ramen", "Portland", "OR"),
("Oak Street Bakery", "Food, Bakeries, Coffee & Tea", "Austin", "TX"),
("Queen Vietnamese", "Restaurants, Vietnamese", "Philadelphia", "PA"),
("Campus Espresso", "Coffee & Tea, Cafes", "Seattle", "WA"),
("Park Yoga Studio", "Active Life, Yoga", "Denver", "CO"),
("Midtown Books", "Shopping, Books", "Chicago", "IL"),
("East Side Brewpub", "Nightlife, Breweries", "Milwaukee", "WI"),
("Family Thai Kitchen", "Restaurants, Thai", "Tempe", "AZ"),
("Uptown Nail Spa", "Beauty & Spas, Nail Salons", "Miami", "FL"),
("Lakeside Pizza", "Restaurants, Pizza", "Minneapolis", "MN"),
]
rows = []
for i in range(n):
name, cats, city, state = templates[i % len(templates)]
suffix = i // len(templates)
disp = f"{name}" if suffix == 0 else f"{name} #{suffix}"
h = hashlib.sha256(f"{i}-{disp}".encode()).hexdigest()[:22]
bid = h
text_for_embedding = (
f"name: {disp}\n"
f"categories: {cats}\n"
f"location: {city}, {state}\n"
f"address: {100 + i} Main St\n"
f"business_avg_stars: {3.5 + (i % 15) / 10:.1f}\n"
f"business_review_count: {20 + i * 7}\n"
f"is_open: 1"
)
rows.append(
{
"business_id": bid,
"name": disp,
"categories": cats,
"city": city,
"state": state,
"stars": float(3.5 + (i % 15) / 10),
"review_count": int(20 + i * 7),
"is_open": 1,
"text_for_embedding": text_for_embedding,
}
)
return rows
def build_stub_embedded(rt: Path, model: Any) -> None:
rows = stub_catalog_rows()
texts = [r["text_for_embedding"] for r in rows]
mat = model.encode(texts, batch_size=8, convert_to_numpy=True, normalize_embeddings=False)
out_path = rt / "data" / "business_catalog_embedded.jsonl"
cat_path = rt / "data" / "business_catalog.jsonl"
out_path.parent.mkdir(parents=True, exist_ok=True)
with out_path.open("w", encoding="utf-8") as fe, cat_path.open("w", encoding="utf-8") as fc:
for row, vec in zip(rows, mat, strict=True):
fc.write(json.dumps(row, ensure_ascii=False) + "\n")
emb_row = {**row, "embedding": vec.astype(float).tolist()}
fe.write(json.dumps(emb_row, ensure_ascii=False) + "\n")
print(f"docker_build_assets: wrote stub catalog -> {cat_path} and {out_path}")
def build_stub_data(rt: Path) -> None:
model = _get_build_embedder()
build_stub_embedded(rt, model)
build_stub_task_a_embedded(rt, model)
def main() -> None:
rt = root()
(rt / "data").mkdir(parents=True, exist_ok=True)
hf_login()
prefetch_hub_files_only()
yelp = yelp_business_path(rt)
yelp_rev = yelp_review_path(rt)
if yelp.is_file():
print(f"docker_build_assets: building catalog from {yelp}")
build_from_yelp(rt, yelp)
if yelp_rev.is_file():
print(f"docker_build_assets: Task A RAG from {yelp_rev}")
build_task_a_from_yelp(rt, yelp, yelp_rev)
else:
print(
"docker_build_assets: Yelp review JSON not found — stub Task A RAG "
"(set YELP_REVIEW_JSON or add yelp_academic_dataset_review.json)."
)
build_stub_task_a_embedded(rt, _get_build_embedder())
else:
print(
"docker_build_assets: Yelp business JSON not found; "
"writing stub JSONL (mount real data at runtime or bake yelp_dataset into build context)."
)
build_stub_data(rt)
warm_runtime_models()
if __name__ == "__main__":
main()