openrunner-sdk 2.18.0__tar.gz → 2.18.2__tar.gz

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 (120) hide show
  1. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/PKG-INFO +1 -1
  2. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/__init__.py +1 -1
  3. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/install_commands.py +142 -137
  4. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/pyproject.toml +1 -1
  5. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/.gitignore +0 -0
  6. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/=6.0 +0 -0
  7. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/=8.1 +0 -0
  8. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/README.md +0 -0
  9. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/CLAUDE.md +0 -0
  10. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/api_client.py +0 -0
  11. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/artifact.py +0 -0
  12. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/buffer.py +0 -0
  13. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/cache.py +0 -0
  14. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/cli.py +0 -0
  15. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/config.py +0 -0
  16. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/cost.py +0 -0
  17. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/dataset.py +0 -0
  18. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/environment.py +0 -0
  19. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/evaluation.py +0 -0
  20. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/feedback.py +0 -0
  21. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/git_info.py +0 -0
  22. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/guardrails.py +0 -0
  23. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/__init__.py +0 -0
  24. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/accelerate.py +0 -0
  25. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/anthropic_tracer.py +0 -0
  26. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/catboost.py +0 -0
  27. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/diffusers.py +0 -0
  28. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/fastai.py +0 -0
  29. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/forced_alignment.py +0 -0
  30. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/gladia.py +0 -0
  31. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/gymnasium.py +0 -0
  32. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/huggingface.py +0 -0
  33. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/hydra.py +0 -0
  34. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/ignite.py +0 -0
  35. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/jax.py +0 -0
  36. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/keras.py +0 -0
  37. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/langchain.py +0 -0
  38. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/lightgbm.py +0 -0
  39. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/lightning.py +0 -0
  40. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/llamaindex.py +0 -0
  41. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/openai_finetune.py +0 -0
  42. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/openai_tracer.py +0 -0
  43. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/optuna.py +0 -0
  44. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/pytorch.py +0 -0
  45. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/sb3.py +0 -0
  46. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/sklearn.py +0 -0
  47. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/tensorflow.py +0 -0
  48. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/trl.py +0 -0
  49. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/tts.py +0 -0
  50. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/ultralytics.py +0 -0
  51. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/voice_agent.py +0 -0
  52. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/whisper.py +0 -0
  53. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/integration/xgboost.py +0 -0
  54. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/launch.py +0 -0
  55. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/mcp_server.py +0 -0
  56. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/media.py +0 -0
  57. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/migrate.py +0 -0
  58. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/model.py +0 -0
  59. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/offline.py +0 -0
  60. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/pii.py +0 -0
  61. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/plot.py +0 -0
  62. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/prompt.py +0 -0
  63. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/query_api.py +0 -0
  64. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/redact.py +0 -0
  65. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/run.py +0 -0
  66. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/scorers.py +0 -0
  67. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/sender.py +0 -0
  68. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/session.py +0 -0
  69. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/settings.py +0 -0
  70. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/summary.py +0 -0
  71. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/sweep.py +0 -0
  72. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/system_metrics.py +0 -0
  73. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/tensorboard.py +0 -0
  74. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/trace.py +0 -0
  75. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/transcript_formatter.py +0 -0
  76. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/wal.py +0 -0
  77. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/wandb_compat/__init__.py +0 -0
  78. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/wandb_compat/_shim.py +0 -0
  79. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/openrunner/wer.py +0 -0
  80. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/__init__.py +0 -0
  81. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/conftest.py +0 -0
  82. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_alert.py +0 -0
  83. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_aliases.py +0 -0
  84. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_api_client.py +0 -0
  85. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_artifact.py +0 -0
  86. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_buffer.py +0 -0
  87. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_cache.py +0 -0
  88. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_class_scorers.py +0 -0
  89. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_cli.py +0 -0
  90. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_config.py +0 -0
  91. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_evaluation.py +0 -0
  92. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_finish.py +0 -0
  93. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_git_info.py +0 -0
  94. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_init.py +0 -0
  95. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_integration_fastai.py +0 -0
  96. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_integration_huggingface.py +0 -0
  97. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_integration_keras.py +0 -0
  98. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_integration_langchain.py +0 -0
  99. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_integration_lightning.py +0 -0
  100. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_integration_pytorch.py +0 -0
  101. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_integration_sklearn.py +0 -0
  102. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_integration_xgboost.py +0 -0
  103. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_launch.py +0 -0
  104. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_log.py +0 -0
  105. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_log_code.py +0 -0
  106. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_media.py +0 -0
  107. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_migrate.py +0 -0
  108. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_offline.py +0 -0
  109. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_offline_sync.py +0 -0
  110. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_pii.py +0 -0
  111. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_plot.py +0 -0
  112. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_query_api.py +0 -0
  113. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_resume.py +0 -0
  114. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_sdk_features.py +0 -0
  115. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_sender.py +0 -0
  116. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_summary.py +0 -0
  117. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_sweep.py +0 -0
  118. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_system_metrics.py +0 -0
  119. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_trace.py +0 -0
  120. {openrunner_sdk-2.18.0 → openrunner_sdk-2.18.2}/tests/test_wandb_compat.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrunner-sdk
3
- Version: 2.18.0
3
+ Version: 2.18.2
4
4
  Summary: OpenRunner SDK - W&B-compatible ML experiment tracking client
5
5
  Project-URL: Homepage, https://github.com/jqueguiner/openrunner
6
6
  Project-URL: Repository, https://github.com/jqueguiner/openrunner
@@ -120,7 +120,7 @@ launch.from_run = _launch_from_run # type: ignore[attr-defined]
120
120
  # openrunner.trace.patch_openai() syntax
121
121
  trace.patch_openai = _patch_openai # type: ignore[attr-defined]
122
122
 
123
- __version__ = "2.18.0"
123
+ __version__ = "2.18.2"
124
124
 
125
125
  logger = logging.getLogger("openrunner")
126
126
 
@@ -504,11 +504,12 @@ if not config.get('api_key'):
504
504
 
505
505
  client = APIClient(base_url=config['base_url'], api_key=config['api_key'])
506
506
 
507
- # Resolve project
507
+ # Resolve project (case-insensitive)
508
508
  project_name = config.get('project', '')
509
509
  project_id = None
510
510
  for p in client.list_projects():
511
- if f'{{p.get(\"org_name\",\"\")}}/{{p.get(\"name\",\"\")}}' == project_name or p.get('name') == project_name:
511
+ full = f'{{p.get(\"org_name\",\"\")}}/{{p.get(\"name\",\"\")}}'
512
+ if full.lower() == project_name.lower() or (p.get('name') or '').lower() == project_name.lower():
512
513
  project_id = p.get('id')
513
514
  break
514
515
 
@@ -559,152 +560,156 @@ while True:
559
560
  else:
560
561
  resp = client._request('get', f'/projects/{{project_id}}/runs', params={{'limit': 10, 'order': 'desc'}})
561
562
  runs_data = resp.json()
562
- runs = runs_data.get('runs', runs_data) if isinstance(runs_data, dict) else runs_data
563
+ if isinstance(runs_data, dict) and 'detail' in runs_data:
564
+ print('API_ERROR=%s' % runs_data['detail'])
565
+ break
566
+ if isinstance(runs_data, dict):
567
+ runs = runs_data.get('runs', [])
568
+ elif isinstance(runs_data, list):
569
+ runs = runs_data
570
+ else:
571
+ runs = []
563
572
  if not runs:
564
573
  print('NO_RUNS')
565
- client.close()
566
- sys.exit(1)
567
-
568
- # Collect metrics
569
- bars = '▁▂▃▄▅▆▇█'
570
- results = []
571
-
572
- for run in runs[:10]:
573
- rid = run.get('id', run) if isinstance(run, dict) else run
574
- try:
575
- keys_param = ','.join(metric_keys)
576
- resp = client._request('get', f'/runs/{{rid}}/metrics', params={{'keys': keys_param}})
577
- data = resp.json()
578
- metrics = data.get('metrics', {{}})
579
- if not metrics and not show_all:
580
- continue
581
-
582
- # If none of the requested metrics found, use whatever is available
583
- found_any = any(k in metrics for k in metric_keys)
584
- if not found_any and metrics and not show_all:
585
- metric_keys = list(metrics.keys())[:2]
586
-
587
- for key, points in metrics.items():
588
- if not show_all and key not in metric_keys:
589
- continue
590
- if not points:
591
- continue
574
+ break
592
575
 
593
- values = [pt.get('value', pt) if isinstance(pt, dict) else pt for pt in points]
594
- steps = [pt.get('step', i) if isinstance(pt, dict) else i for i, pt in enumerate(points)]
595
- values = [v for v in values if isinstance(v, (int, float))]
596
- if not values:
576
+ # Collect metrics
577
+ bars = '▁▂▃▄▅▆▇█'
578
+ results = []
579
+
580
+ for run in runs[:10]:
581
+ rid = run.get('id', run) if isinstance(run, dict) else run
582
+ try:
583
+ keys_param = ','.join(metric_keys)
584
+ resp = client._request('get', f'/runs/{{rid}}/metrics', params={{'keys': keys_param}})
585
+ data = resp.json()
586
+ metrics = data.get('metrics', {{}})
587
+ if not metrics and not show_all:
597
588
  continue
598
589
 
599
- min_v = min(values)
600
- max_v = max(values)
601
- range_v = max_v - min_v or 1
602
- width = 60
603
-
604
- # Sample if too many
605
- if len(values) > width:
606
- step_size = len(values) / width
607
- sampled = [values[int(j * step_size)] for j in range(width)]
608
- else:
609
- sampled = values
610
-
611
- sparkline = ''
612
- for v in sampled:
613
- idx = int((v - min_v) / range_v * (len(bars) - 1))
614
- sparkline += bars[idx]
615
-
616
- run_name = run.get('display_name') or run.get('name') or rid[:8] if isinstance(run, dict) else rid[:8]
617
- run_state = run.get('state', 'unknown') if isinstance(run, dict) else 'unknown'
618
- direction = 'rising' if values[-1] > values[0] else 'falling'
619
- pct = ((values[-1] - values[0]) / (values[0] or 1)) * 100
620
-
621
- results.append({{
622
- 'run_id': rid,
623
- 'run_name': run_name,
624
- 'metric': key,
625
- 'sparkline': sparkline,
626
- 'min': min_v,
627
- 'max': max_v,
628
- 'latest': values[-1],
629
- 'steps': len(values),
630
- 'step_range': f'{{steps[0]}}->{{steps[-1]}}',
631
- 'direction': direction,
632
- 'pct': pct,
633
- 'state': run_state,
634
- }})
635
- except Exception as e:
636
- results.append({{'run_id': rid, 'error': str(e)}})
637
-
638
- if not results:
639
- print('NO_METRICS')
640
- client.close()
641
- sys.exit(1)
642
-
643
- # ASCII output
644
- print('=' * 72)
645
- print(f' OPENRUNNER METRICS — {{project_name}}')
646
- print('=' * 72)
590
+ # If none of the requested metrics found, use whatever is available
591
+ found_any = any(k in metrics for k in metric_keys)
592
+ if not found_any and metrics and not show_all:
593
+ metric_keys = list(metrics.keys())[:2]
594
+
595
+ for key, points in metrics.items():
596
+ if not show_all and key not in metric_keys:
597
+ continue
598
+ if not points:
599
+ continue
600
+
601
+ values = [pt.get('value', pt) if isinstance(pt, dict) else pt for pt in points]
602
+ steps = [pt.get('step', i) if isinstance(pt, dict) else i for i, pt in enumerate(points)]
603
+ values = [v for v in values if isinstance(v, (int, float))]
604
+ if not values:
605
+ continue
606
+
607
+ min_v = min(values)
608
+ max_v = max(values)
609
+ range_v = max_v - min_v or 1
610
+ w = 60
611
+
612
+ # Sample if too many
613
+ if len(values) > w:
614
+ step_size = len(values) / w
615
+ sampled = [values[int(j * step_size)] for j in range(w)]
616
+ else:
617
+ sampled = values
618
+
619
+ sparkline = ''
620
+ for v in sampled:
621
+ idx = int((v - min_v) / range_v * (len(bars) - 1))
622
+ sparkline += bars[idx]
623
+
624
+ run_name = run.get('display_name') or run.get('name') or rid[:8] if isinstance(run, dict) else rid[:8]
625
+ run_state = run.get('state', 'unknown') if isinstance(run, dict) else 'unknown'
626
+ direction = 'rising' if values[-1] > values[0] else 'falling'
627
+ pct = ((values[-1] - values[0]) / (values[0] or 1)) * 100
628
+
629
+ results.append({{
630
+ 'run_id': rid,
631
+ 'run_name': run_name,
632
+ 'metric': key,
633
+ 'sparkline': sparkline,
634
+ 'min': min_v,
635
+ 'max': max_v,
636
+ 'latest': values[-1],
637
+ 'steps': len(values),
638
+ 'step_range': f'{{steps[0]}}->{{steps[-1]}}',
639
+ 'direction': direction,
640
+ 'pct': pct,
641
+ 'state': run_state,
642
+ }})
643
+ except Exception as e:
644
+ results.append({{'run_id': rid, 'error': str(e)}})
645
+
646
+ if not results:
647
+ print('NO_METRICS')
648
+ break
647
649
 
648
- for r in results:
649
- if 'error' in r:
650
- print(f' {{r[\"run_id\"][:8]}} — error: {{r[\"error\"]}}')
651
- continue
652
- print()
653
- print(f' Run: {{r[\"run_name\"]}} ({{r[\"run_id\"][:8]}})')
654
- print(f' Metric: {{r[\"metric\"]}} | Steps: {{r[\"step_range\"]}} ({{r[\"steps\"]}} pts)')
655
- print(' Range: %.6f -> %.6f | Latest: %.6f' % (r['min'], r['max'], r['latest']))
656
- print(' ' + r['sparkline'])
657
- arrow = '↗' if r['pct'] > 0 else '↘'
658
- print(' %s %s (%+.1f%%)' % (arrow, r['direction'], r['pct']))
659
-
660
- # Summary table
661
- print()
662
- print('-' * 72)
663
- print(' %-20s %-15s %10s %10s %10s %8s' % ('Run', 'Metric', 'Latest', 'Min', 'Max', 'Trend'))
664
- print('-' * 72)
665
- for r in results:
666
- if 'error' in r:
667
- continue
668
- name = (r['run_name'] or '')[:20]
669
- met = r['metric'][:15]
670
- arrow = '↗' if r['pct'] > 0 else '↘'
671
- trend = '%s%+.0f%%' % (arrow, r['pct'])
672
- print(' %-20s %-15s %10.4f %10.4f %10.4f %8s' % (name, met, r['latest'], r['min'], r['max'], trend))
673
- print('-' * 72)
674
-
675
- # Live runs footer — show link + loss indicator for in-progress runs
676
- base_url = config.get('base_url', '').rstrip('/')
677
- live_runs = [r for r in results if r.get('state') in ('running', 'in_progress') and 'error' not in r]
678
- if live_runs:
679
- print()
650
+ # ASCII output
680
651
  print('=' * 72)
681
- print(' LIVE RUNS')
652
+ print(f' OPENRUNNER METRICS — {{project_name}}')
682
653
  print('=' * 72)
683
- seen_ids = set()
684
- for r in live_runs:
685
- if r['run_id'] in seen_ids:
654
+
655
+ for r in results:
656
+ if 'error' in r:
657
+ print(f' {{r[\"run_id\"][:8]}} — error: {{r[\"error\"]}}')
686
658
  continue
687
- seen_ids.add(r['run_id'])
688
- # Loss indicator
689
- loss_metrics = [x for x in live_runs if x['run_id'] == r['run_id'] and 'loss' in x['metric'].lower()]
690
- lr_metrics = [x for x in live_runs if x['run_id'] == r['run_id'] and 'lr' in x['metric'].lower() or 'learning' in x['metric'].lower()]
691
- indicator = ''
692
- if loss_metrics:
693
- lm = loss_metrics[0]
694
- arrow = '↘' if lm['pct'] < 0 else '↗'
695
- indicator += ' loss: %.4f %s' % (lm['latest'], arrow)
696
- if lr_metrics:
697
- lrm = lr_metrics[0]
698
- indicator += ' | lr: %.2e' % lrm['latest']
699
- run_url = '%s/%s/runs/%s' % (base_url, project_name, r['run_id'])
700
659
  print()
701
- print(' %s %s' % (r['run_name'], indicator))
702
- # Compact sparkline for loss
703
- if loss_metrics:
704
- print(' %s' % loss_metrics[0]['sparkline'])
705
- print(' -> %s' % run_url)
660
+ print(f' Run: {{r[\"run_name\"]}} ({{r[\"run_id\"][:8]}})')
661
+ print(f' Metric: {{r[\"metric\"]}} | Steps: {{r[\"step_range\"]}} ({{r[\"steps\"]}} pts)')
662
+ print(' Range: %.6f -> %.6f | Latest: %.6f' % (r['min'], r['max'], r['latest']))
663
+ print(' ' + r['sparkline'])
664
+ arrow = '↗' if r['pct'] > 0 else '↘'
665
+ print(' %s %s (%+.1f%%)' % (arrow, r['direction'], r['pct']))
666
+
667
+ # Summary table
706
668
  print()
707
- print('=' * 72)
669
+ print('-' * 72)
670
+ print(' %-20s %-15s %10s %10s %10s %8s' % ('Run', 'Metric', 'Latest', 'Min', 'Max', 'Trend'))
671
+ print('-' * 72)
672
+ for r in results:
673
+ if 'error' in r:
674
+ continue
675
+ name = (r['run_name'] or '')[:20]
676
+ met = r['metric'][:15]
677
+ arrow = '↗' if r['pct'] > 0 else '↘'
678
+ trend = '%s%+.0f%%' % (arrow, r['pct'])
679
+ print(' %-20s %-15s %10.4f %10.4f %10.4f %8s' % (name, met, r['latest'], r['min'], r['max'], trend))
680
+ print('-' * 72)
681
+
682
+ # Live runs footer — show link + loss indicator for in-progress runs
683
+ base_url = config.get('base_url', '').rstrip('/')
684
+ live_runs = [r for r in results if r.get('state') in ('running', 'in_progress') and 'error' not in r]
685
+ if live_runs:
686
+ print()
687
+ print('=' * 72)
688
+ print(' LIVE RUNS')
689
+ print('=' * 72)
690
+ seen_ids = set()
691
+ for r in live_runs:
692
+ if r['run_id'] in seen_ids:
693
+ continue
694
+ seen_ids.add(r['run_id'])
695
+ loss_metrics = [x for x in live_runs if x['run_id'] == r['run_id'] and 'loss' in x['metric'].lower()]
696
+ lr_metrics = [x for x in live_runs if x['run_id'] == r['run_id'] and ('lr' in x['metric'].lower() or 'learning' in x['metric'].lower())]
697
+ indicator = ''
698
+ if loss_metrics:
699
+ lm = loss_metrics[0]
700
+ arrow = '↘' if lm['pct'] < 0 else '↗'
701
+ indicator += ' loss: %.4f %s' % (lm['latest'], arrow)
702
+ if lr_metrics:
703
+ lrm = lr_metrics[0]
704
+ indicator += ' | lr: %.2e' % lrm['latest']
705
+ run_url = '%s/%s/runs/%s' % (base_url, project_name, r['run_id'])
706
+ print()
707
+ print(' %s %s' % (r['run_name'], indicator))
708
+ if loss_metrics:
709
+ print(' %s' % loss_metrics[0]['sparkline'])
710
+ print(' -> %s' % run_url)
711
+ print()
712
+ print('=' * 72)
708
713
 
709
714
  # Watch mode: loop or exit
710
715
  if not watch_mode:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openrunner-sdk"
3
- version = "2.18.0"
3
+ version = "2.18.2"
4
4
  description = "OpenRunner SDK - W&B-compatible ML experiment tracking client"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
File without changes
File without changes