Spaces:
Sleeping
Sleeping
| """Deploy AutoSource as a fully live Docker-based Hugging Face Space. | |
| The resulting container includes the MCP server, A2A transport, all agents, | |
| the AP2 gate, seeded data, and replay traces. Each visitor gets an isolated | |
| ephemeral warehouse. Only Groq keys are synchronized; values are never printed. | |
| Run: | |
| python -m scripts.deploy_space --golden golden-candidate-02 --sync-secrets | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import shutil | |
| import sys | |
| import tempfile | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) | |
| from core.config import PROJECT_ROOT, api_key, traces_dir # noqa: E402 | |
| SPACE_README = """--- | |
| title: AutoSource | |
| emoji: 🤝 | |
| colorFrom: green | |
| colorTo: yellow | |
| sdk: docker | |
| app_port: 7860 | |
| pinned: false | |
| license: mit | |
| --- | |
| # AutoSource — live autonomous procurement | |
| AutoSource is a network of cooperating agents that detects a stock shortage | |
| over **MCP**, hands the request off over **A2A**, negotiates with multiple | |
| vendors under deterministic budget/floor guardrails, and asks a human to | |
| approve an **AP2-shaped mandate** before writing a mock purchase order. | |
| ## Try it | |
| 1. In the sidebar, leave **Live follow** selected. | |
| 2. Choose an item and press **Trigger stock check & negotiate**. | |
| 3. Watch the five-agent activity board and negotiation timeline. | |
| 4. Approve or reject the mandate when it appears. | |
| 5. Open **Under the hood** to inspect prompts, LLM calls, events, transcripts, | |
| and your private warehouse. | |
| Each browser session receives an isolated SQLite warehouse. Public runs are | |
| limited and serialized to protect the free API quota. Replay mode always works | |
| without making an API call. All payments are **MOCK**; no real money can move. | |
| Source: https://github.com/Shanmuk4622/AutoSource-Autonomous-Multi-Agent-Procurement-Negotiation-Network | |
| """ | |
| SPACE_DOCKERFILE = """FROM python:3.11-slim | |
| RUN useradd -m -u 1000 user | |
| WORKDIR /app | |
| COPY --chown=user:user requirements.txt . | |
| RUN pip install --no-cache-dir -r requirements.txt | |
| COPY --chown=user:user . . | |
| RUN mkdir -p /tmp/autosource-sessions && chown -R user:user /tmp/autosource-sessions | |
| USER user | |
| ENV HOME=/home/user \ | |
| PYTHONUNBUFFERED=1 \ | |
| PYTHONIOENCODING=utf-8 \ | |
| AUTOSOURCE_HOSTED=1 \ | |
| AUTOSOURCE_SESSION_ROOT=/tmp/autosource-sessions \ | |
| AUTOSOURCE_LIVE_LOCK=/tmp/autosource-live.lock \ | |
| AUTOSOURCE_RPM=8 \ | |
| GROQ_MODEL=llama-3.1-8b-instant | |
| EXPOSE 7860 | |
| CMD ["streamlit", "run", "ui/app.py", "--server.port", "7860", \ | |
| "--server.address", "0.0.0.0", "--server.headless", "true", \ | |
| "--browser.gatherUsageStats", "false"] | |
| """ | |
| RUNTIME_PACKAGES = ( | |
| "agents", "config", "core", "mcp_server", "orchestrator", "protocols", | |
| "scripts", "ui", | |
| ) | |
| def _copy_runtime(stage: Path) -> None: | |
| for package in RUNTIME_PACKAGES: | |
| shutil.copytree( | |
| PROJECT_ROOT / package, stage / package, | |
| ignore=shutil.ignore_patterns("__pycache__", "*.pyc"), | |
| ) | |
| shutil.copytree(PROJECT_ROOT / "data" / "seed", stage / "data" / "seed") | |
| shutil.copy2(PROJECT_ROOT / "data" / "schema.sql", stage / "data" / "schema.sql") | |
| # Only the judge prompt is imported by the dashboard's debug view. | |
| (stage / "eval").mkdir() | |
| (stage / "eval" / "__init__.py").write_text("", encoding="utf-8") | |
| shutil.copy2(PROJECT_ROOT / "eval" / "judge.py", stage / "eval" / "judge.py") | |
| if (PROJECT_ROOT / "LICENSE").exists(): | |
| shutil.copy2(PROJECT_ROOT / "LICENSE", stage / "LICENSE") | |
| def _bundle_replays(stage: Path, run_ids: list[str]) -> None: | |
| target = stage / "traces" | |
| (target / "negotiations").mkdir(parents=True) | |
| call_log = traces_dir() / "llm_calls.jsonl" | |
| if call_log.exists(): | |
| shutil.copy2(call_log, target / call_log.name) | |
| for run_id in run_ids: | |
| for suffix in (".jsonl", ".mandates.json", ".console.log"): | |
| source = traces_dir() / f"{run_id}{suffix}" | |
| if source.exists(): | |
| shutil.copy2(source, target / source.name) | |
| events_file = traces_dir() / f"{run_id}.jsonl" | |
| if not events_file.exists(): | |
| continue | |
| for line in events_file.read_text(encoding="utf-8").splitlines(): | |
| try: | |
| ref = json.loads(line).get("data", {}).get("transcript_ref") | |
| except json.JSONDecodeError: | |
| continue | |
| if ref: | |
| source = PROJECT_ROOT / ref | |
| if source.exists(): | |
| shutil.copy2(source, target / "negotiations" / source.name) | |
| def _sync_groq_secrets(api, space: str) -> None: | |
| synced = [] | |
| for name in ("GROQ_API_KEY", "GROQ_API_KEY_2"): | |
| value = api_key(name) | |
| if value: | |
| api.add_space_secret( | |
| repo_id=space, key=name, value=value, | |
| description="AutoSource live negotiation provider key", | |
| ) | |
| synced.append(name) | |
| if not synced: | |
| raise RuntimeError("No GROQ_API_KEY found locally; live Space cannot negotiate") | |
| print("[deploy_space] synchronized secret names: " + ", ".join(synced)) | |
| def main() -> None: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--space", default="Shanmuk4622/autosource") | |
| parser.add_argument("--golden", default="golden-candidate-02", | |
| help="comma-separated replay run IDs to bundle") | |
| parser.add_argument("--sync-secrets", action="store_true", | |
| help="write local Groq keys to HF Space secrets") | |
| args = parser.parse_args() | |
| token = api_key("HF_TOKEN") | |
| if not token: | |
| raise SystemExit("[deploy_space] HF_TOKEN missing") | |
| stage = Path(tempfile.mkdtemp(prefix="autosource-space-")) | |
| try: | |
| (stage / "README.md").write_text(SPACE_README, encoding="utf-8") | |
| (stage / "requirements.txt").write_text( | |
| (PROJECT_ROOT / "requirements.txt").read_text(encoding="utf-8"), | |
| encoding="utf-8", | |
| ) | |
| (stage / "Dockerfile").write_text(SPACE_DOCKERFILE, encoding="utf-8") | |
| _copy_runtime(stage) | |
| run_ids = [value.strip() for value in args.golden.split(",") if value.strip()] | |
| _bundle_replays(stage, run_ids) | |
| # Defense in depth: the upload staging tree must never contain secrets or DBs. | |
| for pattern in (".env", "*.key", "*.pem", "*.db", "*.db-journal"): | |
| for stray in stage.rglob(pattern): | |
| stray.unlink() | |
| from huggingface_hub import HfApi | |
| api = HfApi(token=token) | |
| api.create_repo(args.space, repo_type="space", space_sdk="docker", | |
| exist_ok=True) | |
| if args.sync_secrets: | |
| _sync_groq_secrets(api, args.space) | |
| api.upload_folder( | |
| folder_path=str(stage), repo_id=args.space, repo_type="space", | |
| commit_message="deploy fully live isolated AutoSource demo", | |
| ) | |
| finally: | |
| shutil.rmtree(stage, ignore_errors=True) | |
| print(f"[deploy_space] live at https://huggingface.co/spaces/{args.space}") | |
| if __name__ == "__main__": | |
| main() | |