Spaces:
Sleeping
Sleeping
File size: 7,059 Bytes
924a755 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | """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()
|