Upload 6 files
Browse files- src/worker_payload.py +117 -5
src/worker_payload.py
CHANGED
|
@@ -412,10 +412,10 @@ if __name__ == "__main__":
|
|
| 412 |
demo.launch()
|
| 413 |
"""
|
| 414 |
(workspace / "app.py").write_text(app_py, encoding="utf-8")
|
| 415 |
-
req = """gradio>=
|
| 416 |
-
huggingface_hub>=0.34.0,<
|
| 417 |
spaces
|
| 418 |
-
transformers>=4.45.0
|
| 419 |
diffusers
|
| 420 |
accelerate
|
| 421 |
safetensors
|
|
@@ -465,7 +465,7 @@ Non-negotiable safety and product constraints:
|
|
| 465 |
- Work only inside the current workspace.
|
| 466 |
- The wrapper will create the private Space, request hardware best-effort, upload files, and validate the live app. Do not create/delete repos yourself in this builder worker.
|
| 467 |
- Preserve a cheap health endpoint named `health` with `api_name="health"`. It must not load weights, run GPU work, or download large files.
|
| 468 |
-
-
|
| 469 |
- README.md frontmatter must remain valid; if it uses short_description, it must be 60 characters or fewer.
|
| 470 |
|
| 471 |
Implementation contract:
|
|
@@ -519,8 +519,111 @@ def sanitize_readme_metadata(workspace: Path, events_path: Path):
|
|
| 519 |
append_event(events_path, "metadata_sanitize", "success", "Sanitized README metadata", {"short_description": "Generated model demo"})
|
| 520 |
|
| 521 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 522 |
def upload_workspace(api, workspace: Path, target_space_id: str, token: str, run_dir: Path, events_path: Path):
|
| 523 |
sanitize_readme_metadata(workspace, events_path)
|
|
|
|
| 524 |
append_event(events_path, "upload_files", "started", "Uploading generated universal model-card workspace recursively")
|
| 525 |
gen_dir = run_dir / "generated"
|
| 526 |
if gen_dir.exists():
|
|
@@ -728,7 +831,16 @@ def main():
|
|
| 728 |
selected_hardware = "default-cpu-or-existing"
|
| 729 |
write_json(run_dir / "hardware_attempts.json", {"selected_hardware": selected_hardware, "requested_sequence": requested_hardware_sequence, "attempts": hardware_attempts})
|
| 730 |
|
| 731 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 732 |
inference_gate = infer_generation_gate(workspace, implementation_mode, validation, run_dir, events_path)
|
| 733 |
|
| 734 |
# If the generated app looks like real GPU inference but automatic
|
|
|
|
| 412 |
demo.launch()
|
| 413 |
"""
|
| 414 |
(workspace / "app.py").write_text(app_py, encoding="utf-8")
|
| 415 |
+
req = """gradio>=6.0.0
|
| 416 |
+
huggingface_hub>=0.34.0,<2.0.0
|
| 417 |
spaces
|
| 418 |
+
transformers>=4.45.0,<6.0.0
|
| 419 |
diffusers
|
| 420 |
accelerate
|
| 421 |
safetensors
|
|
|
|
| 465 |
- Work only inside the current workspace.
|
| 466 |
- The wrapper will create the private Space, request hardware best-effort, upload files, and validate the live app. Do not create/delete repos yourself in this builder worker.
|
| 467 |
- Preserve a cheap health endpoint named `health` with `api_name="health"`. It must not load weights, run GPU work, or download large files.
|
| 468 |
+
- Do not pin huggingface_hub below 1.0. Use huggingface_hub>=0.34.0,<2.0.0 unless the model card requires a narrower compatible range. If transformers>=5 is used, keep huggingface_hub compatible with it, for example huggingface_hub>=1.5.0,<2.0.0.
|
| 469 |
- README.md frontmatter must remain valid; if it uses short_description, it must be 60 characters or fewer.
|
| 470 |
|
| 471 |
Implementation contract:
|
|
|
|
| 519 |
append_event(events_path, "metadata_sanitize", "success", "Sanitized README metadata", {"short_description": "Generated model demo"})
|
| 520 |
|
| 521 |
|
| 522 |
+
|
| 523 |
+
def normalize_requirements_for_modern_hub(workspace: Path, events_path: Path):
|
| 524 |
+
"""Prevent a known resolver conflict in generated Spaces.
|
| 525 |
+
|
| 526 |
+
Older builder versions forced `huggingface_hub<1.0.0` to avoid old Gradio
|
| 527 |
+
import issues. Modern Spaces can use Gradio 6 and recent Transformers;
|
| 528 |
+
Transformers 5.x requires huggingface-hub >=1.5.0, so the old pin breaks
|
| 529 |
+
builds. Keep the constraint broad and modern unless Pi intentionally uses a
|
| 530 |
+
different compatible stack.
|
| 531 |
+
"""
|
| 532 |
+
req_path = workspace / "requirements.txt"
|
| 533 |
+
if not req_path.exists():
|
| 534 |
+
return
|
| 535 |
+
raw = req_path.read_text(encoding="utf-8", errors="ignore")
|
| 536 |
+
lines = [line.rstrip() for line in raw.splitlines()]
|
| 537 |
+
changed = False
|
| 538 |
+
filtered = []
|
| 539 |
+
transformers_needs_hub_15 = False
|
| 540 |
+
for line in lines:
|
| 541 |
+
stripped = line.strip()
|
| 542 |
+
low = stripped.lower().replace("_", "-")
|
| 543 |
+
if low.startswith("huggingface-hub"):
|
| 544 |
+
if "<1" in low or "< 1" in low or ",<1" in low:
|
| 545 |
+
changed = True
|
| 546 |
+
# Always replace with the policy line to avoid duplicate/conflicting pins.
|
| 547 |
+
changed = True
|
| 548 |
+
continue
|
| 549 |
+
if low.startswith("transformers") and (">=5" in low or "==5" in low or "~=5" in low):
|
| 550 |
+
transformers_needs_hub_15 = True
|
| 551 |
+
filtered.append(line)
|
| 552 |
+
hub_line = "huggingface_hub>=1.5.0,<2.0.0" if transformers_needs_hub_15 else "huggingface_hub>=0.34.0,<2.0.0"
|
| 553 |
+
# Put hub near the top, after any --extra-index-url lines.
|
| 554 |
+
insert_at = 0
|
| 555 |
+
while insert_at < len(filtered) and filtered[insert_at].strip().startswith("--"):
|
| 556 |
+
insert_at += 1
|
| 557 |
+
filtered.insert(insert_at, hub_line)
|
| 558 |
+
new = "\n".join(line for line in filtered if line.strip()) + "\n"
|
| 559 |
+
if new != raw:
|
| 560 |
+
req_path.write_text(new, encoding="utf-8")
|
| 561 |
+
append_event(events_path, "requirements_sanitize", "success", "Normalized huggingface_hub requirement for modern dependency resolution", {"huggingface_hub": hub_line})
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
def repair_workspace_with_pi(workspace: Path, run_dir: Path, events_path: Path, pi_model: str, target_space_id: str, model_id: str, failure_reason: str):
|
| 565 |
+
"""Ask Pi for one minimal build/runtime repair pass based on collected logs."""
|
| 566 |
+
logs_dir = run_dir / "logs"
|
| 567 |
+
build_log = (logs_dir / "space_logs_build.txt").read_text(encoding="utf-8", errors="ignore") if (logs_dir / "space_logs_build.txt").exists() else ""
|
| 568 |
+
runtime_log = (logs_dir / "space_logs_runtime.txt").read_text(encoding="utf-8", errors="ignore") if (logs_dir / "space_logs_runtime.txt").exists() else ""
|
| 569 |
+
repair_dir = run_dir / "repair"
|
| 570 |
+
before_dir = repair_dir / "before"
|
| 571 |
+
after_dir = repair_dir / "after"
|
| 572 |
+
if before_dir.exists():
|
| 573 |
+
shutil.rmtree(before_dir)
|
| 574 |
+
shutil.copytree(workspace, before_dir, ignore=shutil.ignore_patterns(".git", "node_modules", "__pycache__", "*.pyc"))
|
| 575 |
+
goal = f"""You are Pi repairing a Hugging Face Space generated by Agentic Space Factory.
|
| 576 |
+
|
| 577 |
+
MODEL_ID: {model_id}
|
| 578 |
+
TARGET_SPACE_ID: {target_space_id}
|
| 579 |
+
|
| 580 |
+
The first build/runtime validation failed.
|
| 581 |
+
|
| 582 |
+
Failure summary:
|
| 583 |
+
{failure_reason[:4000]}
|
| 584 |
+
|
| 585 |
+
Build log tail:
|
| 586 |
+
```text
|
| 587 |
+
{build_log[-12000:]}
|
| 588 |
+
```
|
| 589 |
+
|
| 590 |
+
Runtime log tail:
|
| 591 |
+
```text
|
| 592 |
+
{runtime_log[-12000:]}
|
| 593 |
+
```
|
| 594 |
+
|
| 595 |
+
Repair contract:
|
| 596 |
+
- Make the smallest patch possible.
|
| 597 |
+
- Prefer fixing dependency resolver conflicts, missing imports, invalid metadata, Gradio endpoint bugs, and import-order issues.
|
| 598 |
+
- Do not replace real inference with a placeholder unless TECHNICAL_BLOCKERS.json clearly explains why full inference is impossible.
|
| 599 |
+
- Preserve a cheap health endpoint with api_name="health".
|
| 600 |
+
- Keep README frontmatter valid, short_description <= 60 chars.
|
| 601 |
+
- Do not pin huggingface_hub below 1.0. For modern generated Spaces use huggingface_hub>=0.34.0,<2.0.0. If transformers>=5 is present, use huggingface_hub>=1.5.0,<2.0.0.
|
| 602 |
+
- Do not delete the app. Do not publish anything. Work only in the current workspace.
|
| 603 |
+
|
| 604 |
+
Deliverables:
|
| 605 |
+
- patched app.py / requirements.txt / README.md as needed
|
| 606 |
+
- REPAIR_SUMMARY.md explaining the patch
|
| 607 |
+
- keep or update INFERENCE_CONTRACT.json if the inference contract changed
|
| 608 |
+
"""
|
| 609 |
+
(workspace / "REPAIR_GOAL.md").write_text(goal, encoding="utf-8")
|
| 610 |
+
append_event(events_path, "repair", "started", "Running Pi repair pass using build/runtime logs", {"model": pi_model})
|
| 611 |
+
code, out = run_cmd(["pi", "-p", goal], cwd=workspace, timeout=1500)
|
| 612 |
+
logs_dir.mkdir(parents=True, exist_ok=True)
|
| 613 |
+
(logs_dir / "pi_repair_output.txt").write_text(out, encoding="utf-8")
|
| 614 |
+
if code != 0:
|
| 615 |
+
append_event(events_path, "repair", "failed", "Pi repair returned a non-zero exit code", {"returncode": code, "output_tail": out[-3000:]})
|
| 616 |
+
return False
|
| 617 |
+
normalize_requirements_for_modern_hub(workspace, events_path)
|
| 618 |
+
if after_dir.exists():
|
| 619 |
+
shutil.rmtree(after_dir)
|
| 620 |
+
shutil.copytree(workspace, after_dir, ignore=shutil.ignore_patterns(".git", "node_modules", "__pycache__", "*.pyc"))
|
| 621 |
+
append_event(events_path, "repair", "success", "Pi repair pass completed", {"output_tail": out[-3000:]})
|
| 622 |
+
return True
|
| 623 |
+
|
| 624 |
def upload_workspace(api, workspace: Path, target_space_id: str, token: str, run_dir: Path, events_path: Path):
|
| 625 |
sanitize_readme_metadata(workspace, events_path)
|
| 626 |
+
normalize_requirements_for_modern_hub(workspace, events_path)
|
| 627 |
append_event(events_path, "upload_files", "started", "Uploading generated universal model-card workspace recursively")
|
| 628 |
gen_dir = run_dir / "generated"
|
| 629 |
if gen_dir.exists():
|
|
|
|
| 831 |
selected_hardware = "default-cpu-or-existing"
|
| 832 |
write_json(run_dir / "hardware_attempts.json", {"selected_hardware": selected_hardware, "requested_sequence": requested_hardware_sequence, "attempts": hardware_attempts})
|
| 833 |
|
| 834 |
+
try:
|
| 835 |
+
validation = validate_live_api(api, target_space_id, token, run_dir, events_path, timeout_s=1200)
|
| 836 |
+
except Exception as validation_error:
|
| 837 |
+
append_event(events_path, "repair", "started", "Initial live validation failed; attempting one repair pass", {"error": str(validation_error)[:2000]})
|
| 838 |
+
collect_space_logs(target_space_id, token, run_dir, events_path)
|
| 839 |
+
repaired = repair_workspace_with_pi(workspace, run_dir, events_path, pi_model, target_space_id, model_id, str(validation_error))
|
| 840 |
+
if not repaired:
|
| 841 |
+
raise
|
| 842 |
+
upload_workspace(api, workspace, target_space_id, token, run_dir, events_path)
|
| 843 |
+
validation = validate_live_api(api, target_space_id, token, run_dir, events_path, timeout_s=1200)
|
| 844 |
inference_gate = infer_generation_gate(workspace, implementation_mode, validation, run_dir, events_path)
|
| 845 |
|
| 846 |
# If the generated app looks like real GPU inference but automatic
|