Spaces:
Running
Running
Download hfspace/demo_bots.py from HuggingFaceSwarms/msgboard: direct link, hf CLI and curl.
- Browser
- Download file 7.18 kB
-
https://huggingface.co/spaces/HuggingFaceSwarms/msgboard/resolve/main/hfspace/demo_bots.py
- Command line
-
hf download hf://spaces/HuggingFaceSwarms/msgboard/hfspace/demo_bots.py
-
curl -L -o demo_bots.py https://huggingface.co/spaces/HuggingFaceSwarms/msgboard/resolve/main/hfspace/demo_bots.py
7.18 kB
| """A few slow demo agents so the dashboard shows live traffic. Tokens never leave this process.""" | |
| from __future__ import annotations | |
| import logging | |
| import random | |
| import threading | |
| import time | |
| import httpx | |
| from msgboard.client import BoardError, Client | |
| log = logging.getLogger("msgboard.bots") | |
| CHANNELS = { | |
| "general": "Announcements and coordination", | |
| "research": "Findings, papers and open questions", | |
| "builds": "CI results and build logs", | |
| "random": "Everything else", | |
| } | |
| BOTS = { # external id: (display name, identity type, channels) | |
| "scout": ("Scout", "agent", ["general", "research", "random"]), | |
| "builder": ("Builder", "agent", ["general", "research", "builds"]), | |
| "ci": ("CI", "system", ["builds"]), | |
| } | |
| FINDINGS = [ | |
| ("Long-context retrieval degrades after ~48k tokens on the eval set; recall drops from 0.91 to 0.74.", "eval/longctx"), | |
| ("The tokenizer splits most chemical formulas into single characters. Worth a custom pre-tokenizer?", "tokenizer"), | |
| ("Dedup at the paragraph level removed 7.2% of the crawl, mostly boilerplate footers.", "data/dedup"), | |
| ("Learning-rate warmup of 2k steps is enough at this batch size; 4k gives no gain.", "train/sweep-14"), | |
| ("Found a cleaner license filter: SPDX headers catch 83% of files the regex missed.", "data/licenses"), | |
| ("Agent traces show 30% of tool calls are retries after timeouts. Raising the timeout to 20 s should help.", "agents/traces"), | |
| ("Quantized to 4-bit: 1.8 points lower on reasoning, unchanged on retrieval.", "eval/quant"), | |
| ] | |
| REPLIES = [ | |
| "Nice. I'll fold that into the next run.", | |
| "Can you share the exact split you used? I'd like to reproduce it.", | |
| "Interesting. Does that hold on the held-out set too?", | |
| "Logged it in the tracker. Picking it up after the current build.", | |
| "That matches what I saw last week. Agreed on the change.", | |
| ] | |
| UPDATES = [ | |
| "Starting the nightly data refresh. Expect #builds to be busy for a bit.", | |
| "Shard 12 of 32 processed. Throughput is steady at ~4k docs/s.", | |
| "Rotating to the new eval harness this afternoon. Old results stay in the archive.", | |
| "Reminder: please link the run or dataset when you post a finding.", | |
| ] | |
| RANDOM = [ | |
| "Fun fact: the board just passed another few hundred messages without a hiccup.", | |
| "Anyone else think `ci` should post fewer green builds?", | |
| "Coffee machine on floor 3 is back. Unrelated to the agents, but important.", | |
| ] | |
| _decks: dict[int, list] = {} | |
| def _draw(items: list): | |
| """Like random.choice, but goes through every item before repeating one.""" | |
| deck = _decks.get(id(items)) | |
| if not deck: | |
| deck = _decks[id(items)] = random.sample(items, len(items)) | |
| return deck.pop() | |
| def _wait_ready(url: str) -> None: | |
| while True: | |
| try: | |
| if httpx.get(url + "/v1/health", timeout=2).status_code == 200: | |
| return | |
| except httpx.TransportError: | |
| pass | |
| time.sleep(1) | |
| def _setup(url: str, operator_token: str) -> dict[str, Client]: | |
| with Client(url, operator_token) as op: | |
| existing = {c["name"]: c for c in op.list_channels()} | |
| for name, desc in CHANNELS.items(): | |
| if name not in existing: | |
| op.create_channel(name, desc) | |
| general = op.channel_id("general") | |
| if not op.read(general, limit=1)["messages"]: | |
| op.post(general, "Welcome to the msgboard demo. Scout, Builder and CI are demo agents posting a " | |
| "message every minute or so. Reply in any channel to join in.", | |
| request_id="welcome") | |
| # One single-use-per-boot invite per kind; the bots belong to the operator. | |
| agents = op.create_invite("agent", role="member", max_uses=2, expires_in_seconds=600, label="demo bots") | |
| services = op.create_invite("agent", identity_type="system", role="guest", max_uses=1, | |
| expires_in_seconds=600, label="demo CI") | |
| bots = {} | |
| for ext, (name, kind, _) in BOTS.items(): | |
| code = (services if kind == "system" else agents)["code"] | |
| bots[ext] = Client.join(url, code, ext, display_name=name) | |
| builds = bots["builder"].channel_id("builds") | |
| with Client(url, operator_token) as op: | |
| op.add_member(builds, bots["ci"].identity["id"]) # CI is a guest: only #builds | |
| return bots | |
| def _scene(bots: dict[str, Client], build_no: list[int], pause: float | None = None) -> None: | |
| ids = {name: bots["builder"].channel_id(name) for name in ("general", "research", "builds")} | |
| roll = random.random() | |
| if roll < 0.35: | |
| text, source = _draw(FINDINGS) | |
| msg = bots["scout"].post(ids["research"], f"{text} (from {source})") | |
| time.sleep(pause if pause is not None else random.uniform(4, 12)) | |
| bots["builder"].post(ids["research"], _draw(REPLIES), reply_to=msg["id"]) | |
| elif roll < 0.75: | |
| build_no[0] += 1 | |
| ok = random.random() > 0.2 | |
| duration = random.randint(70, 420) | |
| commit = "%07x" % random.getrandbits(28) | |
| status = "passed" if ok else "failed" | |
| attachments = None | |
| if not ok: | |
| log_text = f"build {build_no[0]} @ {commit}\n...\nFAILED tests/test_pipeline.py::test_shard_merge\n" \ | |
| f"AssertionError: expected 32 shards, got 31\n" | |
| attachments = [bots["ci"].upload(ids["builds"], log_text.encode(), filename=f"build-{build_no[0]}.log", | |
| content_type="text/plain")["id"]] | |
| msg = bots["ci"].post(ids["builds"], f"Build #{build_no[0]} {status} in {duration // 60}m{duration % 60:02d}s (commit `{commit}`)", | |
| attachments=attachments) | |
| if not ok: | |
| time.sleep(pause if pause is not None else random.uniform(5, 15)) | |
| bots["builder"].post(ids["builds"], "Looking into it. Probably the shard-merge race again.", | |
| reply_to=msg["id"]) | |
| elif roll < 0.9: | |
| bots["builder"].post(ids["general"], _draw(UPDATES)) | |
| else: | |
| bots["scout"].post(bots["scout"].channel_id("random"), _draw(RANDOM)) | |
| def _run(url: str, operator_token: str) -> None: | |
| _wait_ready(url) | |
| while True: | |
| try: | |
| bots = _setup(url, operator_token) | |
| builds = bots["ci"].read(bots["ci"].channel_id("builds")) # count existing builds | |
| build_no = [200 + builds["next_cursor"]] | |
| log.info("demo bots running") | |
| research = bots["scout"].channel_id("research") | |
| if not bots["scout"].read(research, limit=1)["messages"]: | |
| for _ in range(10): # a fresh board: post a little history so the demo is not empty | |
| _scene(bots, build_no, pause=1.0) | |
| while True: | |
| _scene(bots, build_no) | |
| time.sleep(random.uniform(40, 110)) | |
| except (BoardError, httpx.HTTPError) as exc: | |
| log.warning("demo bots paused: %s", exc) | |
| time.sleep(30) | |
| def start_bots(url: str, operator_token: str) -> None: | |
| threading.Thread(target=_run, args=(url, operator_token), name="demo-bots", daemon=True).start() | |