hkblockchaintechnology commited on
Commit
b89c3c8
·
verified ·
1 Parent(s): e9262d8

Publish showcase_catalog.py from marimo

Browse files
Files changed (1) hide show
  1. showcase_catalog.py +231 -2
showcase_catalog.py CHANGED
@@ -14,11 +14,14 @@ from showcase_core import (
14
  )
15
  import os
16
  import subprocess
 
 
17
 
18
 
19
- def term(sid, category, title, shows, note="", argv=None, code=None, env=None):
 
20
  CATALOG.append(Shot(id=sid, category=category, title=title, shows=shows, note=note,
21
- kind="terminal", argv=argv, code=code, env=env))
22
 
23
 
24
  def desk(sid, category, title, shows, note="", scene_name=None, make=None):
@@ -1491,3 +1494,229 @@ _edit("exec-refuses-unresolved",
1491
  'print(" ", InputUnavailable("real input needs pyautogui and a live display: '
1492
  'pip install \'tiny-computer-agent[desktop]\'"))\nprint()\n'
1493
  'resolved = executor.execute(Action("done", reason="nothing to do"), frame)')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  )
15
  import os
16
  import subprocess
17
+ import time
18
+ from pathlib import Path
19
 
20
 
21
+ def term(sid, category, title, shows, note="", argv=None, code=None, env=None,
22
+ prepare=None):
23
  CATALOG.append(Shot(id=sid, category=category, title=title, shows=shows, note=note,
24
+ kind="terminal", argv=argv, code=code, env=env, prepare=prepare))
25
 
26
 
27
  def desk(sid, category, title, shows, note="", scene_name=None, make=None):
 
1494
  'print(" ", InputUnavailable("real input needs pyautogui and a live display: '
1495
  'pip install \'tiny-computer-agent[desktop]\'"))\nprint()\n'
1496
  'resolved = executor.execute(Action("done", reason="nothing to do"), frame)')
1497
+
1498
+ # =========================================================================== #
1499
+ # 15. the stepwise loop, the masked decoder, and the accessibility tree
1500
+ # =========================================================================== #
1501
+
1502
+ term("stepwise", "The loop",
1503
+ "The same loop, one turn at a time, with pause and resume",
1504
+ ["AgentLoop", "AgentLoop.begin", "AgentLoop.step_once", "AgentLoop.pause",
1505
+ "AgentLoop.resume", "AgentLoop.finished", "AgentLoop.run", "Step.line"],
1506
+ note="run() is batch; a console needs the caller to own the loop. These are the same rules.",
1507
+ code="""
1508
+ from tiny_computer_agent import AgentLoop, Element, FakeScreen, MockBrain, Policy
1509
+
1510
+ screen = FakeScreen([
1511
+ (Element("Files", x=10, y=6, width=60, height=20),),
1512
+ (Element("Save", x=320, y=120, width=80, height=24),),
1513
+ ])
1514
+ loop = AgentLoop(
1515
+ screen=screen,
1516
+ brain=MockBrain([{"kind": "wait", "seconds": 0.1},
1517
+ {"kind": "click", "label": "Save"},
1518
+ {"kind": "done", "reason": "saved"}]),
1519
+ policy=Policy(step_budget=6),
1520
+ )
1521
+ loop.begin("save the report")
1522
+ print("after begin(): finished =", loop.finished, "| steps =", len(loop.episode.steps))
1523
+
1524
+ print("step_once() -> ", loop.step_once().line())
1525
+ print(" finished =", loop.finished, "| steps =", len(loop.episode.steps))
1526
+
1527
+ loop.pause()
1528
+ loop.run() # returns at once: the caller said pause
1529
+ print("after pause(): paused =", loop.paused, "| steps =", len(loop.episode.steps))
1530
+
1531
+ loop.resume()
1532
+ episode = loop.run()
1533
+ print("after resume(): finished =", loop.finished, "| success =", episode.success)
1534
+ print()
1535
+ print(episode.render())
1536
+ """)
1537
+
1538
+ term("decoding", "Models",
1539
+ "Constrained decoding: make an echo impossible, not merely detected",
1540
+ ["MenuMask", "digit_token_ids", "choice_token_ids", "pair_token_ids", "read_choice",
1541
+ "read_verdict", "brevity_ladder", "majority_choice", "DIGITS"],
1542
+ note="The 256M model's failure was answering with the prompt. A mask removes that option.",
1543
+ code="""
1544
+ from tiny_computer_agent.decoding import (DIGITS, MenuMask, brevity_ladder,
1545
+ choice_token_ids, digit_token_ids,
1546
+ majority_choice, pair_token_ids, read_choice,
1547
+ read_verdict)
1548
+ from tiny_computer_agent.models import DEFAULT_VISION_MODEL
1549
+
1550
+ try:
1551
+ from transformers import AutoTokenizer
1552
+ tokenizer = AutoTokenizer.from_pretrained(DEFAULT_VISION_MODEL)
1553
+ source = DEFAULT_VISION_MODEL
1554
+ except Exception as exc: # pragma: no cover - env
1555
+ class _StandIn:
1556
+ table = {str(d): 100 + d for d in range(10)}
1557
+ def encode(self, text, add_special_tokens=False):
1558
+ return [self.table[text]] if text in self.table else [7, 7]
1559
+ tokenizer, source = _StandIn(), f"a stand-in ({type(exc).__name__})"
1560
+
1561
+ print("tokenizer:", source)
1562
+ print("digits we allow :", DIGITS)
1563
+ print("digit token ids :", digit_token_ids(tokenizer)[:10], "...")
1564
+ print("ids for a 4-item menu:", choice_token_ids(tokenizer, 4))
1565
+ yes_ids, no_ids = pair_token_ids(tokenizer)
1566
+ print("yes / no token ids :", yes_ids[:3], "/", no_ids[:3])
1567
+ mask = MenuMask(choice_token_ids(tokenizer, 4), eos_token_id=2)
1568
+ print("the mask allows :", mask.allowed_ids, "(everything else is -inf)")
1569
+ print()
1570
+ print("reading an answer, and refusing anything that is not one:")
1571
+ for reply in ["3", " 3 ", "1000", "The goal is to click Save", "3 or 4", ""]:
1572
+ print(f" read_choice({reply[:26]!r:30}) -> {read_choice(reply, 4)}")
1573
+ for reply in ["Yes", " no", "maybe", ""]:
1574
+ print(f" read_verdict({reply!r:9}) -> {read_verdict(reply)!r}")
1575
+ print()
1576
+ print("the retry ladder (echoing grows with prompt length):", brevity_ladder(3))
1577
+ print("a vote over samples:", majority_choice([2, 3, 2]), "| ties go to the first seen:",
1578
+ majority_choice([3, 2]))
1579
+ """)
1580
+
1581
+ # --- the accessibility half needs a real GTK window and a session bus --------
1582
+ _GTK = {"process": None}
1583
+ GTK_BOX = (140, 140, 660, 420)
1584
+
1585
+
1586
+ def open_gtk_scene():
1587
+ """A session bus plus the GTK window, so AT-SPI has something to read."""
1588
+ if _GTK["process"] is not None:
1589
+ return
1590
+ address = subprocess.run(["dbus-daemon", "--session", "--fork", "--print-address=1"],
1591
+ capture_output=True, text=True).stdout.strip()
1592
+ os.environ["DBUS_SESSION_BUS_ADDRESS"] = address
1593
+ os.environ["NO_AT_BRIDGE"] = "0"
1594
+ ready = Path("/tmp/gallery-gtk.ready")
1595
+ ready.unlink(missing_ok=True)
1596
+ _GTK["process"] = subprocess.Popen(
1597
+ [PY, str(ROOT / "showcase_scenes" / "gtk_dialog.py"),
1598
+ "/tmp/gallery-gtk.marker", str(ready)],
1599
+ env=dict(os.environ), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
1600
+ for _ in range(60):
1601
+ if ready.exists():
1602
+ break
1603
+ time.sleep(0.25)
1604
+ time.sleep(2.5)
1605
+
1606
+
1607
+ def close_gtk_scene():
1608
+ process, _GTK["process"] = _GTK["process"], None
1609
+ if process is not None:
1610
+ process.terminate()
1611
+ try:
1612
+ process.wait(timeout=10)
1613
+ except subprocess.TimeoutExpired: # pragma: no cover
1614
+ process.kill()
1615
+ time.sleep(0.5)
1616
+
1617
+
1618
+ def _with_gtk(tmp=None):
1619
+ open_gtk_scene()
1620
+ return close_gtk_scene
1621
+
1622
+
1623
+ term("atspi-index", "Perception",
1624
+ "The accessibility tree: elements the OS already knows about",
1625
+ ["AtspiDetector", "AtspiDetector.detect", "AtspiDetector.text", "AtspiReader",
1626
+ "AtspiReader.read", "AccessibleNode", "TreeReader", "TreeReader.read",
1627
+ "accessibility_available",
1628
+ "INTERACTIVE_ROLES", "TEXT_ROLES"],
1629
+ note="No pixels are read: GTK publishes every button's name and screen extents.",
1630
+ prepare=_with_gtk,
1631
+ code="""
1632
+ from tiny_computer_agent import (INTERACTIVE_ROLES, TEXT_ROLES, AtspiDetector, AtspiReader,
1633
+ accessibility_available)
1634
+
1635
+ available, detail = accessibility_available("atspi")
1636
+ print("accessibility_available('atspi') ->", available)
1637
+ print(" ", detail)
1638
+ print()
1639
+ print("interactive roles offered to a model:")
1640
+ print(" ", ", ".join(sorted(INTERACTIVE_ROLES)))
1641
+ print("text roles - readable, but not clickable:")
1642
+ print(" ", ", ".join(sorted(TEXT_ROLES)))
1643
+ print()
1644
+
1645
+ reader = AtspiReader()
1646
+ nodes = reader.read()
1647
+ print(f"AtspiReader().read() -> {len(nodes)} AccessibleNode(s) on the live desktop")
1648
+ print()
1649
+
1650
+ detector = AtspiDetector(reader=reader)
1651
+ elements = detector.detect()
1652
+ print(f"AtspiDetector().detect() -> {len(elements)} clickable elements, with no OCR at all:")
1653
+ for element in elements:
1654
+ print(f" {element.label!r:12} kind={element.kind:8} "
1655
+ f"centre={str(element.center):12} box={element.width}x{element.height}")
1656
+ print()
1657
+ print("the words on screen, straight from the tree:")
1658
+ print(" ", detector.text()[:150])
1659
+ """)
1660
+
1661
+ term("models-decision-modes", "Models",
1662
+ "Four ways to ask a tiny model, and what each one costs",
1663
+ ["ModelBrain", "ModelBrain.loaded", "TextPlannerBrain", "VLMBrain", "make_model_brain"],
1664
+ note="json=free text, choice=one masked number, scan=yes/no per item, rank=log-odds order.",
1665
+ code="""
1666
+ from tiny_computer_agent import ModelBrain, make_model_brain
1667
+ from tiny_computer_agent.models import DEFAULT_TEXT_MODEL, DEFAULT_VISION_MODEL
1668
+
1669
+ print("decision_mode decides how the model is asked:")
1670
+ print(" json - reply with JSON, parsed leniently (the original behaviour)")
1671
+ print(" choice - one generation, masked to a menu number")
1672
+ print(" scan - one masked yes/no question per item, first yes wins")
1673
+ print(" rank - the same questions, ranked by the yes/no log-odds margin")
1674
+ print()
1675
+ for name in ("text-planner", "vlm"):
1676
+ brain = make_model_brain(name, decision_mode="scan")
1677
+ print(f"{name:13} -> {type(brain).__name__}")
1678
+ print(f" model : {brain.model_id}")
1679
+ print(f" supports masking : {brain.supports_constrained}")
1680
+ print(f" decision_mode : {brain.decision_mode} | retries {brain.retries} | votes {brain.votes}")
1681
+ print(f" weights loaded : {brain.loaded}")
1682
+ brain.unload()
1683
+ print()
1684
+ print("defaults are unchanged:", make_model_brain("vlm").decision_mode)
1685
+ """)
1686
+
1687
+ # --- the accessibility tree, on a real desktop, with a real click ------------
1688
+ def make_atspi_click(tmp):
1689
+ from tiny_computer_agent import (AgentLoop, AtspiDetector, GrabScreen, IndexedScreen,
1690
+ PlannerBrain, Policy, PyAutoGuiExecutor)
1691
+ open_gtk_scene()
1692
+ try:
1693
+ elements = AtspiDetector().detect()
1694
+ before = crop(annotate(grab(), elements), GTK_BOX)
1695
+ episode = AgentLoop(
1696
+ screen=IndexedScreen(GrabScreen(monitor=1), AtspiDetector()),
1697
+ brain=PlannerBrain(), executor=PyAutoGuiExecutor(),
1698
+ policy=Policy(dry_run=False, step_budget=3),
1699
+ ).run("click 'Save' to save the document")
1700
+ target = next((e for e in elements if e.matches("Save")), None)
1701
+ after = crop(annotate(grab(), [], click=target.center if target else None), GTK_BOX)
1702
+ marker = Path("/tmp/gallery-gtk.marker")
1703
+ lines = [
1704
+ "the window's own status label now reads 'last action: Save',",
1705
+ "and it wrote " + (repr(marker.read_text()) if marker.exists() else "nothing")
1706
+ + " to " + marker.name,
1707
+ "result: " + ("success" if episode.success else "failure")
1708
+ + " - " + episode.stop_reason,
1709
+ ]
1710
+ return overlay_lines(side_by_side(
1711
+ before, after,
1712
+ title="AT-SPI index -> a real click: not one pixel was read to find the button"), lines)
1713
+ finally:
1714
+ close_gtk_scene()
1715
+
1716
+
1717
+ desk("desk-atspi", "Real desktop",
1718
+ "Indexed by the accessibility tree, acted on with the real mouse",
1719
+ ["AtspiDetector", "AtspiDetector.detect", "AtspiReader", "IndexedScreen",
1720
+ "PyAutoGuiExecutor", "PlannerBrain"],
1721
+ note="Roles and extents come from GTK itself; the click is a real one and the app reacts.",
1722
+ scene_name="gtk", make=make_atspi_click)