openadapt-ml 0.2.0__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. openadapt_ml/baselines/__init__.py +121 -0
  2. openadapt_ml/baselines/adapter.py +185 -0
  3. openadapt_ml/baselines/cli.py +314 -0
  4. openadapt_ml/baselines/config.py +448 -0
  5. openadapt_ml/baselines/parser.py +922 -0
  6. openadapt_ml/baselines/prompts.py +787 -0
  7. openadapt_ml/benchmarks/__init__.py +13 -115
  8. openadapt_ml/benchmarks/agent.py +265 -421
  9. openadapt_ml/benchmarks/azure.py +28 -19
  10. openadapt_ml/benchmarks/azure_ops_tracker.py +521 -0
  11. openadapt_ml/benchmarks/cli.py +1722 -4847
  12. openadapt_ml/benchmarks/trace_export.py +631 -0
  13. openadapt_ml/benchmarks/viewer.py +22 -5
  14. openadapt_ml/benchmarks/vm_monitor.py +530 -29
  15. openadapt_ml/benchmarks/waa_deploy/Dockerfile +47 -53
  16. openadapt_ml/benchmarks/waa_deploy/api_agent.py +21 -20
  17. openadapt_ml/cloud/azure_inference.py +3 -5
  18. openadapt_ml/cloud/lambda_labs.py +722 -307
  19. openadapt_ml/cloud/local.py +2038 -487
  20. openadapt_ml/cloud/ssh_tunnel.py +68 -26
  21. openadapt_ml/datasets/next_action.py +40 -30
  22. openadapt_ml/evals/grounding.py +8 -3
  23. openadapt_ml/evals/plot_eval_metrics.py +15 -13
  24. openadapt_ml/evals/trajectory_matching.py +41 -26
  25. openadapt_ml/experiments/demo_prompt/format_demo.py +16 -6
  26. openadapt_ml/experiments/demo_prompt/run_experiment.py +26 -16
  27. openadapt_ml/experiments/representation_shootout/__init__.py +70 -0
  28. openadapt_ml/experiments/representation_shootout/conditions.py +708 -0
  29. openadapt_ml/experiments/representation_shootout/config.py +390 -0
  30. openadapt_ml/experiments/representation_shootout/evaluator.py +659 -0
  31. openadapt_ml/experiments/representation_shootout/runner.py +687 -0
  32. openadapt_ml/experiments/waa_demo/runner.py +29 -14
  33. openadapt_ml/export/parquet.py +36 -24
  34. openadapt_ml/grounding/detector.py +18 -14
  35. openadapt_ml/ingest/__init__.py +8 -6
  36. openadapt_ml/ingest/capture.py +25 -22
  37. openadapt_ml/ingest/loader.py +7 -4
  38. openadapt_ml/ingest/synthetic.py +189 -100
  39. openadapt_ml/models/api_adapter.py +14 -4
  40. openadapt_ml/models/base_adapter.py +10 -2
  41. openadapt_ml/models/providers/__init__.py +288 -0
  42. openadapt_ml/models/providers/anthropic.py +266 -0
  43. openadapt_ml/models/providers/base.py +299 -0
  44. openadapt_ml/models/providers/google.py +376 -0
  45. openadapt_ml/models/providers/openai.py +342 -0
  46. openadapt_ml/models/qwen_vl.py +46 -19
  47. openadapt_ml/perception/__init__.py +35 -0
  48. openadapt_ml/perception/integration.py +399 -0
  49. openadapt_ml/retrieval/demo_retriever.py +50 -24
  50. openadapt_ml/retrieval/embeddings.py +9 -8
  51. openadapt_ml/retrieval/retriever.py +3 -1
  52. openadapt_ml/runtime/__init__.py +50 -0
  53. openadapt_ml/runtime/policy.py +18 -5
  54. openadapt_ml/runtime/safety_gate.py +471 -0
  55. openadapt_ml/schema/__init__.py +9 -0
  56. openadapt_ml/schema/converters.py +74 -27
  57. openadapt_ml/schema/episode.py +31 -18
  58. openadapt_ml/scripts/capture_screenshots.py +530 -0
  59. openadapt_ml/scripts/compare.py +85 -54
  60. openadapt_ml/scripts/demo_policy.py +4 -1
  61. openadapt_ml/scripts/eval_policy.py +15 -9
  62. openadapt_ml/scripts/make_gif.py +1 -1
  63. openadapt_ml/scripts/prepare_synthetic.py +3 -1
  64. openadapt_ml/scripts/train.py +21 -9
  65. openadapt_ml/segmentation/README.md +920 -0
  66. openadapt_ml/segmentation/__init__.py +97 -0
  67. openadapt_ml/segmentation/adapters/__init__.py +5 -0
  68. openadapt_ml/segmentation/adapters/capture_adapter.py +420 -0
  69. openadapt_ml/segmentation/annotator.py +610 -0
  70. openadapt_ml/segmentation/cache.py +290 -0
  71. openadapt_ml/segmentation/cli.py +674 -0
  72. openadapt_ml/segmentation/deduplicator.py +656 -0
  73. openadapt_ml/segmentation/frame_describer.py +788 -0
  74. openadapt_ml/segmentation/pipeline.py +340 -0
  75. openadapt_ml/segmentation/schemas.py +622 -0
  76. openadapt_ml/segmentation/segment_extractor.py +634 -0
  77. openadapt_ml/training/azure_ops_viewer.py +1097 -0
  78. openadapt_ml/training/benchmark_viewer.py +52 -41
  79. openadapt_ml/training/shared_ui.py +7 -7
  80. openadapt_ml/training/stub_provider.py +57 -35
  81. openadapt_ml/training/trainer.py +143 -86
  82. openadapt_ml/training/trl_trainer.py +70 -21
  83. openadapt_ml/training/viewer.py +323 -108
  84. openadapt_ml/training/viewer_components.py +180 -0
  85. {openadapt_ml-0.2.0.dist-info → openadapt_ml-0.2.1.dist-info}/METADATA +215 -14
  86. openadapt_ml-0.2.1.dist-info/RECORD +116 -0
  87. openadapt_ml/benchmarks/base.py +0 -366
  88. openadapt_ml/benchmarks/data_collection.py +0 -432
  89. openadapt_ml/benchmarks/live_tracker.py +0 -180
  90. openadapt_ml/benchmarks/runner.py +0 -418
  91. openadapt_ml/benchmarks/waa.py +0 -761
  92. openadapt_ml/benchmarks/waa_live.py +0 -619
  93. openadapt_ml-0.2.0.dist-info/RECORD +0 -86
  94. {openadapt_ml-0.2.0.dist-info → openadapt_ml-0.2.1.dist-info}/WHEEL +0 -0
  95. {openadapt_ml-0.2.0.dist-info → openadapt_ml-0.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -12,7 +12,7 @@ from pathlib import Path
12
12
 
13
13
  def _get_background_tasks_panel_css() -> str:
14
14
  """Return CSS for background tasks panel."""
15
- return '''
15
+ return """
16
16
  .tasks-panel {
17
17
  background: linear-gradient(135deg, rgba(100, 100, 255, 0.1) 0%, rgba(100, 100, 255, 0.05) 100%);
18
18
  border: 1px solid rgba(100, 100, 255, 0.3);
@@ -287,12 +287,12 @@ def _get_background_tasks_panel_css() -> str:
287
287
  border-radius: 3px;
288
288
  transition: width 0.5s ease;
289
289
  }
290
- '''
290
+ """
291
291
 
292
292
 
293
293
  def _get_background_tasks_panel_html() -> str:
294
294
  """Return HTML for background tasks panel with JS polling and improved styling."""
295
- return '''
295
+ return """
296
296
  <div class="tasks-panel" id="tasks-panel">
297
297
  <div class="tasks-header">
298
298
  <div class="tasks-title">
@@ -701,12 +701,12 @@ def _get_background_tasks_panel_html() -> str:
701
701
  fetchBackgroundTasks();
702
702
  setInterval(fetchBackgroundTasks, 10000);
703
703
  </script>
704
- '''
704
+ """
705
705
 
706
706
 
707
707
  def _get_live_evaluation_panel_css() -> str:
708
708
  """Return CSS for live evaluation progress panel."""
709
- return '''
709
+ return """
710
710
  .live-eval-panel {
711
711
  background: linear-gradient(135deg, rgba(139, 92, 246, 0.15) 0%, rgba(139, 92, 246, 0.05) 100%);
712
712
  border: 1px solid rgba(139, 92, 246, 0.3);
@@ -859,12 +859,12 @@ def _get_live_evaluation_panel_css() -> str:
859
859
  0%, 100% { opacity: 1; }
860
860
  50% { opacity: 0.3; }
861
861
  }
862
- '''
862
+ """
863
863
 
864
864
 
865
865
  def _get_live_evaluation_panel_html() -> str:
866
866
  """Return HTML for live evaluation panel with SSE and polling fallback."""
867
- return '''
867
+ return """
868
868
  <div class="live-eval-panel" id="live-eval-panel">
869
869
  <div class="live-eval-header">
870
870
  <div class="live-eval-title">
@@ -1237,12 +1237,12 @@ def _get_live_evaluation_panel_html() -> str:
1237
1237
  if (window.sseManager) window.sseManager.disconnect();
1238
1238
  });
1239
1239
  </script>
1240
- '''
1240
+ """
1241
1241
 
1242
1242
 
1243
1243
  def _get_azure_jobs_panel_css() -> str:
1244
1244
  """Return CSS for the Azure jobs status panel with color-coded status indicators."""
1245
- return '''
1245
+ return """
1246
1246
  .azure-jobs-panel {
1247
1247
  background: linear-gradient(135deg, rgba(0, 120, 212, 0.15) 0%, rgba(0, 120, 212, 0.05) 100%);
1248
1248
  border: 1px solid rgba(0, 120, 212, 0.3);
@@ -1535,7 +1535,7 @@ def _get_azure_jobs_panel_css() -> str:
1535
1535
  @keyframes spin {
1536
1536
  to { transform: rotate(360deg); }
1537
1537
  }
1538
- '''
1538
+ """
1539
1539
 
1540
1540
 
1541
1541
  def _get_azure_jobs_panel_html() -> str:
@@ -1545,7 +1545,7 @@ def _get_azure_jobs_panel_html() -> str:
1545
1545
  is used for training jobs, not for WAA benchmarks (which require nested virtualization
1546
1546
  that managed compute doesn't support).
1547
1547
  """
1548
- return '''
1548
+ return """
1549
1549
  <div class="azure-jobs-panel collapsed" id="azure-jobs-panel">
1550
1550
  <div class="azure-jobs-header" onclick="toggleAzureJobsPanel()" title="Azure ML training jobs">
1551
1551
  <div class="azure-jobs-title">
@@ -1928,12 +1928,12 @@ def _get_azure_jobs_panel_html() -> str:
1928
1928
  setInterval(fetchAzureJobs, 30000);
1929
1929
  setInterval(fetchJobLogs, 5000); // Poll logs every 5 seconds
1930
1930
  </script>
1931
- '''
1931
+ """
1932
1932
 
1933
1933
 
1934
1934
  def _get_vm_discovery_panel_css() -> str:
1935
1935
  """Return CSS for VM Discovery panel with prominent VNC button."""
1936
- return '''
1936
+ return """
1937
1937
  .vm-discovery-panel {
1938
1938
  background: linear-gradient(135deg, rgba(16, 185, 129, 0.15) 0%, rgba(5, 150, 105, 0.05) 100%);
1939
1939
  border: 1px solid rgba(16, 185, 129, 0.3);
@@ -2262,12 +2262,12 @@ def _get_vm_discovery_panel_css() -> str:
2262
2262
  cursor: pointer;
2263
2263
  font-size: 0.85rem;
2264
2264
  }
2265
- '''
2265
+ """
2266
2266
 
2267
2267
 
2268
2268
  def _get_vm_discovery_panel_html() -> str:
2269
2269
  """Return HTML for VM Discovery panel with prominent VNC button and loading states."""
2270
- return '''
2270
+ return """
2271
2271
  <div class="vm-discovery-panel" id="vm-discovery-panel">
2272
2272
  <div class="vm-discovery-header">
2273
2273
  <div class="vm-discovery-title">
@@ -2571,12 +2571,12 @@ def _get_vm_discovery_panel_html() -> str:
2571
2571
  fetchVMs();
2572
2572
  setInterval(fetchVMs, 10000);
2573
2573
  </script>
2574
- '''
2574
+ """
2575
2575
 
2576
2576
 
2577
2577
  def _get_run_benchmark_panel_css() -> str:
2578
2578
  """Return CSS for the Run Benchmark configuration panel."""
2579
- return '''
2579
+ return """
2580
2580
  .run-benchmark-panel {
2581
2581
  background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(16, 185, 129, 0.05) 100%);
2582
2582
  border: 1px solid rgba(16, 185, 129, 0.3);
@@ -2742,12 +2742,12 @@ def _get_run_benchmark_panel_css() -> str:
2742
2742
  color: #6ee7b7;
2743
2743
  border: 1px solid rgba(16, 185, 129, 0.3);
2744
2744
  }
2745
- '''
2745
+ """
2746
2746
 
2747
2747
 
2748
2748
  def _get_run_benchmark_panel_html() -> str:
2749
2749
  """Return HTML for the Run Benchmark configuration panel."""
2750
- return '''
2750
+ return """
2751
2751
  <div class="run-benchmark-panel" id="run-benchmark-panel">
2752
2752
  <div class="run-benchmark-header">
2753
2753
  <div class="run-benchmark-title">
@@ -2828,7 +2828,7 @@ def _get_run_benchmark_panel_html() -> str:
2828
2828
 
2829
2829
  <div class="run-benchmark-status" id="run-benchmark-status"></div>
2830
2830
  </div>
2831
- '''
2831
+ """
2832
2832
 
2833
2833
 
2834
2834
  def _get_run_benchmark_panel_js(include_script_tags: bool = True) -> str:
@@ -2838,7 +2838,7 @@ def _get_run_benchmark_panel_js(include_script_tags: bool = True) -> str:
2838
2838
  include_script_tags: If True, wrap JS in <script> tags. Set to False when
2839
2839
  inserting into an existing script block.
2840
2840
  """
2841
- js_code = '''
2841
+ js_code = """
2842
2842
  // Handle model dropdown change to show/hide custom input
2843
2843
  function handleModelChange() {
2844
2844
  const select = document.getElementById('benchmark-model');
@@ -2958,9 +2958,9 @@ def _get_run_benchmark_panel_js(include_script_tags: bool = True) -> str:
2958
2958
  document.addEventListener('DOMContentLoaded', function() {
2959
2959
  updateTaskSelectionState();
2960
2960
  });
2961
- '''
2961
+ """
2962
2962
  if include_script_tags:
2963
- return f'<script>{js_code}</script>'
2963
+ return f"<script>{js_code}</script>"
2964
2964
  return js_code
2965
2965
 
2966
2966
 
@@ -3045,7 +3045,10 @@ def generate_benchmark_viewer(
3045
3045
  task_results.append(task_result)
3046
3046
 
3047
3047
  # Import shared header components from trainer
3048
- from openadapt_ml.training.trainer import _get_shared_header_css, _generate_shared_header_html
3048
+ from openadapt_ml.training.trainer import (
3049
+ _get_shared_header_css,
3050
+ _generate_shared_header_html,
3051
+ )
3049
3052
 
3050
3053
  # Generate HTML
3051
3054
  html = _generate_benchmark_viewer_html(
@@ -3127,21 +3130,26 @@ def generate_multi_run_benchmark_viewer(
3127
3130
  }
3128
3131
  task_results.append(task_result)
3129
3132
 
3130
- all_runs.append({
3131
- "run_name": metadata.get("run_name", benchmark_dir.name),
3132
- "model_id": metadata.get("model_id", "unknown"),
3133
- "created_at": metadata.get("created_at", ""),
3134
- "benchmark_name": metadata.get("benchmark_name", ""),
3135
- "dir_name": benchmark_dir.name, # For screenshot paths
3136
- "summary": summary,
3137
- "tasks": task_results,
3138
- })
3133
+ all_runs.append(
3134
+ {
3135
+ "run_name": metadata.get("run_name", benchmark_dir.name),
3136
+ "model_id": metadata.get("model_id", "unknown"),
3137
+ "created_at": metadata.get("created_at", ""),
3138
+ "benchmark_name": metadata.get("benchmark_name", ""),
3139
+ "dir_name": benchmark_dir.name, # For screenshot paths
3140
+ "summary": summary,
3141
+ "tasks": task_results,
3142
+ }
3143
+ )
3139
3144
 
3140
3145
  if not all_runs:
3141
3146
  return generate_empty_benchmark_viewer(output_path)
3142
3147
 
3143
3148
  # Import shared header components from trainer
3144
- from openadapt_ml.training.trainer import _get_shared_header_css, _generate_shared_header_html
3149
+ from openadapt_ml.training.trainer import (
3150
+ _get_shared_header_css,
3151
+ _generate_shared_header_html,
3152
+ )
3145
3153
 
3146
3154
  # Generate HTML
3147
3155
  html = _generate_multi_run_benchmark_viewer_html(
@@ -3167,7 +3175,10 @@ def generate_empty_benchmark_viewer(output_path: Path | str) -> Path:
3167
3175
  output_path = Path(output_path)
3168
3176
 
3169
3177
  # Import shared header components from trainer
3170
- from openadapt_ml.training.trainer import _get_shared_header_css, _generate_shared_header_html
3178
+ from openadapt_ml.training.trainer import (
3179
+ _get_shared_header_css,
3180
+ _generate_shared_header_html,
3181
+ )
3171
3182
 
3172
3183
  shared_header_css = _get_shared_header_css()
3173
3184
  shared_header_html = _generate_shared_header_html("benchmarks")
@@ -3182,7 +3193,7 @@ def generate_empty_benchmark_viewer(output_path: Path | str) -> Path:
3182
3193
  vm_discovery_css = _get_vm_discovery_panel_css()
3183
3194
  vm_discovery_html = _get_vm_discovery_panel_html()
3184
3195
 
3185
- html = f'''<!DOCTYPE html>
3196
+ html = f"""<!DOCTYPE html>
3186
3197
  <html lang="en">
3187
3198
  <head>
3188
3199
  <meta charset="UTF-8">
@@ -3331,7 +3342,7 @@ uv run python -m openadapt_ml.benchmarks.cli run-azure --workers 4</code>
3331
3342
  </div>
3332
3343
  </div>
3333
3344
  </body>
3334
- </html>'''
3345
+ </html>"""
3335
3346
 
3336
3347
  output_path.write_text(html)
3337
3348
  return output_path
@@ -3368,7 +3379,7 @@ def _generate_benchmark_viewer_html(
3368
3379
  domains_json = json.dumps(domains)
3369
3380
 
3370
3381
  # Generate HTML
3371
- html = f'''<!DOCTYPE html>
3382
+ html = f"""<!DOCTYPE html>
3372
3383
  <html lang="en">
3373
3384
  <head>
3374
3385
  <meta charset="UTF-8">
@@ -3987,7 +3998,7 @@ def _generate_benchmark_viewer_html(
3987
3998
  init();
3988
3999
  </script>
3989
4000
  </body>
3990
- </html>'''
4001
+ </html>"""
3991
4002
 
3992
4003
  return html
3993
4004
 
@@ -4039,7 +4050,7 @@ def _generate_multi_run_benchmark_viewer_html(
4039
4050
  run_options_html = "\n".join(run_options)
4040
4051
 
4041
4052
  # Generate HTML
4042
- html = f'''<!DOCTYPE html>
4053
+ html = f"""<!DOCTYPE html>
4043
4054
  <html lang="en">
4044
4055
  <head>
4045
4056
  <meta charset="UTF-8">
@@ -4758,6 +4769,6 @@ def _generate_multi_run_benchmark_viewer_html(
4758
4769
  init();
4759
4770
  </script>
4760
4771
  </body>
4761
- </html>'''
4772
+ </html>"""
4762
4773
 
4763
4774
  return html
@@ -13,7 +13,7 @@ def get_shared_header_css() -> str:
13
13
  This CSS is used by both the Training Dashboard and the Viewer.
14
14
  Any changes here will affect all dashboards consistently.
15
15
  """
16
- return '''
16
+ return """
17
17
  .unified-header {
18
18
  display: flex;
19
19
  align-items: center;
@@ -101,7 +101,7 @@ def get_shared_header_css() -> str:
101
101
  color: var(--text-muted);
102
102
  font-family: "SF Mono", Monaco, monospace;
103
103
  }
104
- '''
104
+ """
105
105
 
106
106
 
107
107
  def generate_shared_header_html(
@@ -125,14 +125,14 @@ def generate_shared_header_html(
125
125
 
126
126
  controls_section = ""
127
127
  if controls_html or meta_html:
128
- controls_section = f'''
128
+ controls_section = f"""
129
129
  <div class="controls-section">
130
130
  {controls_html}
131
- {f'<span class="header-meta">{meta_html}</span>' if meta_html else ''}
131
+ {f'<span class="header-meta">{meta_html}</span>' if meta_html else ""}
132
132
  </div>
133
- '''
133
+ """
134
134
 
135
- return f'''
135
+ return f"""
136
136
  <div class="unified-header">
137
137
  <div class="nav-tabs">
138
138
  <a href="dashboard.html" class="nav-tab {training_active}">Training</a>
@@ -141,7 +141,7 @@ def generate_shared_header_html(
141
141
  </div>
142
142
  {controls_section}
143
143
  </div>
144
- '''
144
+ """
145
145
 
146
146
 
147
147
  def build_nav_links() -> list[tuple[str, str]]:
@@ -2,7 +2,6 @@
2
2
 
3
3
  import json
4
4
  import random
5
- import sys
6
5
  import time
7
6
  from datetime import datetime
8
7
  from pathlib import Path
@@ -92,13 +91,15 @@ class StubTrainingProvider:
92
91
 
93
92
  elapsed = time.time() - self.start_time
94
93
 
95
- self.losses.append({
96
- "epoch": self.current_epoch,
97
- "step": self.current_step + 1,
98
- "loss": loss,
99
- "lr": 5e-5,
100
- "time": elapsed,
101
- })
94
+ self.losses.append(
95
+ {
96
+ "epoch": self.current_epoch,
97
+ "step": self.current_step + 1,
98
+ "loss": loss,
99
+ "lr": 5e-5,
100
+ "time": elapsed,
101
+ }
102
+ )
102
103
 
103
104
  self.current_step += 1
104
105
 
@@ -123,32 +124,37 @@ class StubTrainingProvider:
123
124
  if not sample_path.exists():
124
125
  # Try to copy from common capture location
125
126
  import shutil
126
- capture_screenshots = Path.home() / "oa/src/openadapt-capture/turn-off-nightshift/screenshots"
127
+
128
+ capture_screenshots = (
129
+ Path.home() / "oa/src/openadapt-capture/turn-off-nightshift/screenshots"
130
+ )
127
131
  if capture_screenshots.exists():
128
132
  sample_path.parent.mkdir(parents=True, exist_ok=True)
129
133
  for img in capture_screenshots.glob("*.png"):
130
134
  shutil.copy(img, sample_path)
131
135
  break # Just copy the first one
132
136
 
133
- self.evaluations.append({
134
- "epoch": self.current_epoch,
135
- "sample_idx": 7, # Match the real training sample
136
- "image_path": "screenshots/sample.png",
137
- "human_action": {
138
- "type": "click",
139
- "x": 0.65,
140
- "y": 0.65,
141
- "text": None,
142
- },
143
- "predicted_action": {
144
- "type": "click",
145
- "x": 0.65 + random.uniform(-0.15, 0.15) * (1 - accuracy_boost),
146
- "y": 0.65 + random.uniform(-0.15, 0.15) * (1 - accuracy_boost),
147
- "raw_output": f"Thought: [Stub] Epoch {self.current_epoch} - analyzing screenshot to find target element. The model is learning to identify UI components.\nAction: CLICK(x=0.65, y=0.65)",
148
- },
149
- "distance": random.uniform(0.05, 0.2) * (1 - accuracy_boost),
150
- "correct": random.random() > (0.5 - accuracy_boost),
151
- })
137
+ self.evaluations.append(
138
+ {
139
+ "epoch": self.current_epoch,
140
+ "sample_idx": 7, # Match the real training sample
141
+ "image_path": "screenshots/sample.png",
142
+ "human_action": {
143
+ "type": "click",
144
+ "x": 0.65,
145
+ "y": 0.65,
146
+ "text": None,
147
+ },
148
+ "predicted_action": {
149
+ "type": "click",
150
+ "x": 0.65 + random.uniform(-0.15, 0.15) * (1 - accuracy_boost),
151
+ "y": 0.65 + random.uniform(-0.15, 0.15) * (1 - accuracy_boost),
152
+ "raw_output": f"Thought: [Stub] Epoch {self.current_epoch} - analyzing screenshot to find target element. The model is learning to identify UI components.\nAction: CLICK(x=0.65, y=0.65)",
153
+ },
154
+ "distance": random.uniform(0.05, 0.2) * (1 - accuracy_boost),
155
+ "correct": random.random() > (0.5 - accuracy_boost),
156
+ }
157
+ )
152
158
 
153
159
  def get_status(self) -> dict:
154
160
  """Return current training status.
@@ -161,7 +167,11 @@ class StubTrainingProvider:
161
167
 
162
168
  # Determine status
163
169
  if self.termination_status:
164
- status = "completed" if self.termination_status == "auto_complete" else self.termination_status
170
+ status = (
171
+ "completed"
172
+ if self.termination_status == "auto_complete"
173
+ else self.termination_status
174
+ )
165
175
  elif self.is_complete():
166
176
  status = "completed"
167
177
  else:
@@ -215,11 +225,17 @@ class StubTrainingProvider:
215
225
  Args:
216
226
  callback: Optional function called after each step with status dict
217
227
  """
218
- self._log(f"[Stub] Starting simulated training: {self.epochs} epochs, {self.steps_per_epoch} steps/epoch")
228
+ self._log(
229
+ f"[Stub] Starting simulated training: {self.epochs} epochs, {self.steps_per_epoch} steps/epoch"
230
+ )
219
231
  self._log(f"[Stub] Output: {self.output_dir}")
220
- self._log(f"[Stub] Step delay: {self.step_delay}s (total ~{self.epochs * self.steps_per_epoch * self.step_delay:.0f}s)")
232
+ self._log(
233
+ f"[Stub] Step delay: {self.step_delay}s (total ~{self.epochs * self.steps_per_epoch * self.step_delay:.0f}s)"
234
+ )
221
235
  if self.early_stop_loss > 0:
222
- self._log(f"[Stub] Early stop: loss < {self.early_stop_loss} for {self.early_stop_patience} steps")
236
+ self._log(
237
+ f"[Stub] Early stop: loss < {self.early_stop_loss} for {self.early_stop_patience} steps"
238
+ )
223
239
  self._log("")
224
240
 
225
241
  while not self.is_complete():
@@ -239,9 +255,13 @@ class StubTrainingProvider:
239
255
  if self.early_stop_loss > 0 and loss < self.early_stop_loss:
240
256
  self.consecutive_low_loss += 1
241
257
  if self.consecutive_low_loss >= self.early_stop_patience:
242
- self._log(f"\n[Stub] Auto-stopped: loss ({loss:.4f}) < {self.early_stop_loss} for {self.early_stop_patience} steps")
258
+ self._log(
259
+ f"\n[Stub] Auto-stopped: loss ({loss:.4f}) < {self.early_stop_loss} for {self.early_stop_patience} steps"
260
+ )
243
261
  self.termination_status = "auto_low_loss"
244
- self.termination_message = f"Loss reached {loss:.4f} (< {self.early_stop_loss})"
262
+ self.termination_message = (
263
+ f"Loss reached {loss:.4f} (< {self.early_stop_loss})"
264
+ )
245
265
  self.write_status()
246
266
  break
247
267
  else:
@@ -253,7 +273,9 @@ class StubTrainingProvider:
253
273
  epoch = status["epoch"]
254
274
  step = status["step"]
255
275
  display_epoch = min(epoch + 1, self.epochs) # Cap at max for display
256
- self._log(f" Epoch {display_epoch}/{self.epochs} | Step {step} | Loss: {loss:.4f}")
276
+ self._log(
277
+ f" Epoch {display_epoch}/{self.epochs} | Step {step} | Loss: {loss:.4f}"
278
+ )
257
279
 
258
280
  if callback:
259
281
  callback(status)