Publish tiny_computer_agent/ from marimo
Browse files- tiny_computer_agent/__main__.py +12 -1
- tiny_computer_agent/act.py +2 -2
- tiny_computer_agent/loop.py +18 -1
tiny_computer_agent/__main__.py
CHANGED
|
@@ -80,6 +80,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
| 80 |
help="skip OCR and index a numbered grid instead")
|
| 81 |
parser.add_argument("--max-width", type=int, default=1600,
|
| 82 |
help="downscale captures wider than this before indexing")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
parser.add_argument("--allow-grid", action="store_true",
|
| 84 |
help="permit clicks on numbered grid cells with --real-input")
|
| 85 |
parser.add_argument("--kill-switch", default=DEFAULT_KILL_SWITCH,
|
|
@@ -215,6 +219,13 @@ def main(argv: list[str] | None = None) -> int:
|
|
| 215 |
|
| 216 |
warned = False
|
| 217 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
def on_step(step: Step) -> None:
|
| 219 |
nonlocal warned
|
| 220 |
elements = step.observation.elements
|
|
@@ -228,7 +239,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
| 228 |
|
| 229 |
try:
|
| 230 |
episode = AgentLoop(screen=screen, brain=brain, executor=executor,
|
| 231 |
-
policy=policy, on_step=on_step).run(args.goal)
|
| 232 |
except ScreenUnavailable as exc:
|
| 233 |
print(f"cannot see the screen: {exc}", file=sys.stderr)
|
| 234 |
print("run `python -m tiny_computer_agent --doctor --live` for details.", file=sys.stderr)
|
|
|
|
| 80 |
help="skip OCR and index a numbered grid instead")
|
| 81 |
parser.add_argument("--max-width", type=int, default=1600,
|
| 82 |
help="downscale captures wider than this before indexing")
|
| 83 |
+
parser.add_argument("--verify-text", default=None, metavar="TEXT",
|
| 84 |
+
help="stop with success once this text appears on screen "
|
| 85 |
+
"(otherwise a successful action that removes its own "
|
| 86 |
+
"target is indistinguishable from a failure)")
|
| 87 |
parser.add_argument("--allow-grid", action="store_true",
|
| 88 |
help="permit clicks on numbered grid cells with --real-input")
|
| 89 |
parser.add_argument("--kill-switch", default=DEFAULT_KILL_SWITCH,
|
|
|
|
| 219 |
|
| 220 |
warned = False
|
| 221 |
|
| 222 |
+
verify = None
|
| 223 |
+
if args.verify_text:
|
| 224 |
+
_needle = args.verify_text.lower()
|
| 225 |
+
|
| 226 |
+
def verify(observation) -> bool: # noqa: F811 - the loop calls this
|
| 227 |
+
return _needle in observation.text.lower()
|
| 228 |
+
|
| 229 |
def on_step(step: Step) -> None:
|
| 230 |
nonlocal warned
|
| 231 |
elements = step.observation.elements
|
|
|
|
| 239 |
|
| 240 |
try:
|
| 241 |
episode = AgentLoop(screen=screen, brain=brain, executor=executor,
|
| 242 |
+
policy=policy, on_step=on_step, verify=verify).run(args.goal)
|
| 243 |
except ScreenUnavailable as exc:
|
| 244 |
print(f"cannot see the screen: {exc}", file=sys.stderr)
|
| 245 |
print("run `python -m tiny_computer_agent --doctor --live` for details.", file=sys.stderr)
|
tiny_computer_agent/act.py
CHANGED
|
@@ -99,9 +99,9 @@ class PyAutoGuiExecutor:
|
|
| 99 |
def _pyautogui():
|
| 100 |
try:
|
| 101 |
import pyautogui
|
| 102 |
-
except
|
| 103 |
raise InputUnavailable(
|
| 104 |
-
"real input needs pyautogui: pip install \'tiny-computer-agent[desktop]\'"
|
| 105 |
) from exc
|
| 106 |
return pyautogui
|
| 107 |
|
|
|
|
| 99 |
def _pyautogui():
|
| 100 |
try:
|
| 101 |
import pyautogui
|
| 102 |
+
except Exception as exc: # pragma: no cover - depends on the machine
|
| 103 |
raise InputUnavailable(
|
| 104 |
+
"real input needs pyautogui and a live display: pip install \'tiny-computer-agent[desktop]\'"
|
| 105 |
) from exc
|
| 106 |
return pyautogui
|
| 107 |
|
tiny_computer_agent/loop.py
CHANGED
|
@@ -83,7 +83,11 @@ class AgentLoop:
|
|
| 83 |
def __init__(self, *, screen: ScreenSource, brain: Brain,
|
| 84 |
executor: Executor | None = None, policy: Policy | None = None,
|
| 85 |
on_step: Callable[[Step], None] | None = None,
|
| 86 |
-
max_repeats: int = 1
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
self.screen = screen
|
| 88 |
self.brain = brain
|
| 89 |
self.policy = policy or Policy()
|
|
@@ -91,6 +95,7 @@ class AgentLoop:
|
|
| 91 |
executor = DryRunExecutor() if self.policy.dry_run else PyAutoGuiExecutor()
|
| 92 |
self.executor = executor
|
| 93 |
self.on_step = on_step
|
|
|
|
| 94 |
self.max_repeats = max_repeats
|
| 95 |
|
| 96 |
def run(self, goal: str, *, max_steps: int | None = None) -> Episode:
|
|
@@ -177,8 +182,20 @@ class AgentLoop:
|
|
| 177 |
episode.stop_reason = "perception failed mid-episode"
|
| 178 |
break
|
| 179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
return episode
|
| 181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
@staticmethod
|
| 183 |
def _fingerprint(observation: Observation) -> tuple:
|
| 184 |
"""What the agent can see: enough to tell "nothing changed" from "it worked"."""
|
|
|
|
| 83 |
def __init__(self, *, screen: ScreenSource, brain: Brain,
|
| 84 |
executor: Executor | None = None, policy: Policy | None = None,
|
| 85 |
on_step: Callable[[Step], None] | None = None,
|
| 86 |
+
max_repeats: int = 1,
|
| 87 |
+
verify: Callable[[Observation], bool] | None = None) -> None:
|
| 88 |
+
"""``verify`` runs after every action: the harness, not the model, decides
|
| 89 |
+
whether the goal was reached. A brain saying "done" is a claim; a verifier
|
| 90 |
+
looking at the screen is evidence."""
|
| 91 |
self.screen = screen
|
| 92 |
self.brain = brain
|
| 93 |
self.policy = policy or Policy()
|
|
|
|
| 95 |
executor = DryRunExecutor() if self.policy.dry_run else PyAutoGuiExecutor()
|
| 96 |
self.executor = executor
|
| 97 |
self.on_step = on_step
|
| 98 |
+
self.verify = verify
|
| 99 |
self.max_repeats = max_repeats
|
| 100 |
|
| 101 |
def run(self, goal: str, *, max_steps: int | None = None) -> Episode:
|
|
|
|
| 182 |
episode.stop_reason = "perception failed mid-episode"
|
| 183 |
break
|
| 184 |
|
| 185 |
+
if self.verify is not None and self._verified(observation):
|
| 186 |
+
episode.success = True
|
| 187 |
+
episode.stop_reason = f"verified after {index + 1} action(s)"
|
| 188 |
+
break
|
| 189 |
+
|
| 190 |
return episode
|
| 191 |
|
| 192 |
+
def _verified(self, observation: Observation) -> bool:
|
| 193 |
+
"""A broken verifier must never fake a success."""
|
| 194 |
+
try:
|
| 195 |
+
return bool(self.verify(observation))
|
| 196 |
+
except Exception:
|
| 197 |
+
return False
|
| 198 |
+
|
| 199 |
@staticmethod
|
| 200 |
def _fingerprint(observation: Observation) -> tuple:
|
| 201 |
"""What the agent can see: enough to tell "nothing changed" from "it worked"."""
|