themis-eval 0.2.2__py3-none-any.whl → 1.0.0__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 (40) hide show
  1. themis/__init__.py +5 -2
  2. themis/_version.py +14 -1
  3. themis/api.py +83 -145
  4. themis/backends/storage.py +5 -0
  5. themis/cli/commands/info.py +2 -11
  6. themis/cli/main.py +231 -40
  7. themis/comparison/engine.py +7 -13
  8. themis/core/entities.py +4 -0
  9. themis/evaluation/metric_pipeline.py +12 -0
  10. themis/evaluation/pipeline.py +22 -0
  11. themis/evaluation/pipelines/__init__.py +4 -0
  12. themis/evaluation/pipelines/composable_pipeline.py +55 -0
  13. themis/evaluation/pipelines/standard_pipeline.py +18 -1
  14. themis/evaluation/strategies/attempt_aware_evaluation_strategy.py +5 -2
  15. themis/evaluation/strategies/judge_evaluation_strategy.py +6 -1
  16. themis/experiment/__init__.py +2 -2
  17. themis/experiment/cache_manager.py +15 -1
  18. themis/experiment/definitions.py +1 -1
  19. themis/experiment/orchestrator.py +21 -11
  20. themis/experiment/share.py +264 -0
  21. themis/experiment/storage.py +345 -298
  22. themis/generation/plan.py +28 -6
  23. themis/generation/router.py +22 -4
  24. themis/generation/runner.py +16 -1
  25. themis/presets/benchmarks.py +602 -17
  26. themis/server/app.py +38 -26
  27. themis/session.py +125 -0
  28. themis/specs/__init__.py +7 -0
  29. themis/specs/execution.py +26 -0
  30. themis/specs/experiment.py +33 -0
  31. themis/specs/storage.py +18 -0
  32. themis/storage/__init__.py +6 -0
  33. themis/storage/experiment_storage.py +7 -0
  34. {themis_eval-0.2.2.dist-info → themis_eval-1.0.0.dist-info}/METADATA +47 -34
  35. {themis_eval-0.2.2.dist-info → themis_eval-1.0.0.dist-info}/RECORD +38 -31
  36. {themis_eval-0.2.2.dist-info → themis_eval-1.0.0.dist-info}/WHEEL +1 -1
  37. themis/experiment/builder.py +0 -151
  38. themis/experiment/export_csv.py +0 -159
  39. {themis_eval-0.2.2.dist-info → themis_eval-1.0.0.dist-info}/licenses/LICENSE +0 -0
  40. {themis_eval-0.2.2.dist-info → themis_eval-1.0.0.dist-info}/top_level.txt +0 -0
themis/generation/plan.py CHANGED
@@ -88,9 +88,20 @@ class GenerationPlan:
88
88
  }
89
89
  if dataset_id is not None:
90
90
  metadata["dataset_id"] = dataset_id
91
- for field_name in self.metadata_fields:
92
- if field_name in row:
93
- metadata[field_name] = row[field_name]
91
+
92
+ # If metadata_fields is explicitly specified, use it as a filter (existing behavior)
93
+ # Otherwise, include all fields by default (new behavior for custom metrics)
94
+ if self.metadata_fields:
95
+ # Explicit filter - only include specified fields
96
+ for field_name in self.metadata_fields:
97
+ if field_name in row:
98
+ metadata[field_name] = row[field_name]
99
+ else:
100
+ # No filter - include all fields except those used for other purposes
101
+ for field_name, field_value in row.items():
102
+ if field_name not in (self.dataset_id_field, self.reference_field):
103
+ metadata[field_name] = field_value
104
+
94
105
  return metadata
95
106
 
96
107
  def _build_reference(
@@ -209,9 +220,20 @@ class CartesianExpansionStrategy:
209
220
  }
210
221
  if dataset_id is not None:
211
222
  metadata["dataset_id"] = dataset_id
212
- for field_name in context.metadata_fields:
213
- if field_name in row:
214
- metadata[field_name] = row[field_name]
223
+
224
+ # If metadata_fields is explicitly specified, use it as a filter (existing behavior)
225
+ # Otherwise, include all fields by default (new behavior for custom metrics)
226
+ if context.metadata_fields:
227
+ # Explicit filter - only include specified fields
228
+ for field_name in context.metadata_fields:
229
+ if field_name in row:
230
+ metadata[field_name] = row[field_name]
231
+ else:
232
+ # No filter - include all fields except those used for other purposes
233
+ for field_name, field_value in row.items():
234
+ if field_name not in (context.dataset_id_field, context.reference_field):
235
+ metadata[field_name] = field_value
236
+
215
237
  return metadata
216
238
 
217
239
  def _build_reference(
@@ -2,22 +2,40 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Mapping
5
+ from typing import Mapping, Tuple, Union
6
6
 
7
7
  from themis.core import entities as core_entities
8
8
  from themis.interfaces import ModelProvider
9
9
 
10
10
 
11
+ ProviderKey = Union[str, Tuple[str, str]]
12
+
13
+
14
+ def _model_key(provider: str, identifier: str) -> str:
15
+ return f"{provider}:{identifier}"
16
+
17
+
11
18
  class ProviderRouter(ModelProvider):
12
19
  """Dispatches generation tasks to concrete providers by model identifier."""
13
20
 
14
- def __init__(self, providers: Mapping[str, ModelProvider]):
15
- self._providers = dict(providers)
21
+ def __init__(self, providers: Mapping[ProviderKey, ModelProvider]):
22
+ normalized: dict[str, ModelProvider] = {}
23
+
24
+ for key, provider in providers.items():
25
+ if isinstance(key, tuple):
26
+ provider_name, model_id = key
27
+ normalized[_model_key(provider_name, model_id)] = provider
28
+ else:
29
+ normalized[str(key)] = provider
30
+
31
+ self._providers = normalized
16
32
 
17
33
  def generate(
18
34
  self, task: core_entities.GenerationTask
19
35
  ) -> core_entities.GenerationRecord: # type: ignore[override]
20
- provider = self._providers.get(task.model.identifier)
36
+ provider = self._providers.get(_model_key(task.model.provider, task.model.identifier))
37
+ if provider is None:
38
+ provider = self._providers.get(task.model.identifier)
21
39
  if provider is None:
22
40
  known = ", ".join(sorted(self._providers)) or "<none>"
23
41
  raise RuntimeError(
@@ -26,6 +26,7 @@ class GenerationRunner:
26
26
  [core_entities.GenerationTask], strategies.GenerationStrategy
27
27
  ]
28
28
  | None = None,
29
+ execution_backend: object | None = None,
29
30
  max_parallel: int = 1,
30
31
  max_retries: int = 3,
31
32
  retry_initial_delay: float = 0.5,
@@ -36,6 +37,7 @@ class GenerationRunner:
36
37
  self._strategy_resolver = strategy_resolver or (
37
38
  lambda task: strategies.SingleAttemptStrategy()
38
39
  )
40
+ self._execution_backend = execution_backend
39
41
  self._max_parallel = max(1, max_parallel)
40
42
  self._max_retries = max(1, int(max_retries))
41
43
  self._retry_initial_delay = max(0.0, retry_initial_delay)
@@ -53,7 +55,20 @@ class GenerationRunner:
53
55
  return
54
56
 
55
57
  logger.info(f"Runner: Starting execution of {len(task_list)} tasks with {self._max_parallel} workers")
56
-
58
+
59
+ if self._execution_backend is not None:
60
+ logger.info("Runner: Using custom execution backend")
61
+ backend = self._execution_backend
62
+ try:
63
+ for result in backend.map(
64
+ self._execute_task, task_list, max_workers=self._max_parallel
65
+ ):
66
+ yield result
67
+ except Exception as e:
68
+ logger.error(f"Runner: Execution backend failed: {e}")
69
+ raise
70
+ return
71
+
57
72
  if self._max_parallel <= 1:
58
73
  logger.info("Runner: Using sequential execution (1 worker)")
59
74
  for i, task in enumerate(task_list, 1):