openbench-ai 0.4.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 (62) hide show
  1. openbench/__init__.py +191 -0
  2. openbench/adapters/__init__.py +39 -0
  3. openbench/adapters/base.py +220 -0
  4. openbench/adapters/huggingface.py +158 -0
  5. openbench/adapters/local.py +104 -0
  6. openbench/adapters/mock.py +136 -0
  7. openbench/adapters/ollama.py +223 -0
  8. openbench/analytics.py +215 -0
  9. openbench/cache.py +315 -0
  10. openbench/catalog.py +185 -0
  11. openbench/cli.py +1665 -0
  12. openbench/comparison.py +394 -0
  13. openbench/configuration.py +490 -0
  14. openbench/dataset.py +646 -0
  15. openbench/evalstats.py +85 -0
  16. openbench/evaluator.py +994 -0
  17. openbench/exceptions.py +90 -0
  18. openbench/execution.py +456 -0
  19. openbench/experiments.py +1257 -0
  20. openbench/history.py +139 -0
  21. openbench/integrity.py +261 -0
  22. openbench/itemscores.py +178 -0
  23. openbench/lineage.py +264 -0
  24. openbench/metrics/__init__.py +127 -0
  25. openbench/metrics/accuracy.py +118 -0
  26. openbench/metrics/base.py +156 -0
  27. openbench/metrics/bleu.py +145 -0
  28. openbench/metrics/exact_match.py +52 -0
  29. openbench/metrics/f1.py +109 -0
  30. openbench/metrics/keyword_match.py +103 -0
  31. openbench/metrics/normalization.py +59 -0
  32. openbench/metrics/precision.py +243 -0
  33. openbench/metrics/recall.py +106 -0
  34. openbench/metrics/rouge.py +193 -0
  35. openbench/metrics/semantic_similarity.py +353 -0
  36. openbench/metrics/structured_output.py +259 -0
  37. openbench/paired.py +1042 -0
  38. openbench/performance.py +272 -0
  39. openbench/preferences.py +940 -0
  40. openbench/provenance.py +91 -0
  41. openbench/py.typed +0 -0
  42. openbench/quality.py +633 -0
  43. openbench/quality_notes.json +87 -0
  44. openbench/reporting.py +546 -0
  45. openbench/reporting_v040.py +391 -0
  46. openbench/security.py +258 -0
  47. openbench/stats/__init__.py +74 -0
  48. openbench/stats/_numeric.py +49 -0
  49. openbench/stats/_validation.py +24 -0
  50. openbench/stats/bootstrap.py +140 -0
  51. openbench/stats/effects.py +152 -0
  52. openbench/stats/equivalence.py +939 -0
  53. openbench/stats/results.py +214 -0
  54. openbench/stats/rng.py +54 -0
  55. openbench/stats/tests.py +323 -0
  56. openbench/stats/variability.py +324 -0
  57. openbench_ai-0.4.0.dist-info/METADATA +253 -0
  58. openbench_ai-0.4.0.dist-info/RECORD +62 -0
  59. openbench_ai-0.4.0.dist-info/WHEEL +5 -0
  60. openbench_ai-0.4.0.dist-info/entry_points.txt +2 -0
  61. openbench_ai-0.4.0.dist-info/licenses/LICENSE +21 -0
  62. openbench_ai-0.4.0.dist-info/top_level.txt +1 -0
openbench/__init__.py ADDED
@@ -0,0 +1,191 @@
1
+ """OpenBench AI: A modular, open-source LLM evaluation, execution, and benchmarking platform."""
2
+
3
+ from openbench.adapters import (
4
+ PROVIDER_REGISTRY,
5
+ BaseModelAdapter,
6
+ DeterministicLookupAdapter,
7
+ GenerationConfig,
8
+ HuggingFaceAdapter,
9
+ LocalCallableAdapter,
10
+ MockModelProvider,
11
+ OllamaAdapter,
12
+ OllamaConnectionError,
13
+ ProviderHealthStatus,
14
+ create_provider,
15
+ list_registered_providers,
16
+ register_provider,
17
+ )
18
+ from openbench.analytics import BenchmarkAnalytics
19
+ from openbench.cache import CacheRecord, ResponseCache
20
+ from openbench.catalog import DatasetCatalog, DatasetCatalogEntry
21
+ from openbench.comparison import (
22
+ ComparisonResult,
23
+ MetricLeader,
24
+ ModelComparator,
25
+ ModelComparisonEntry,
26
+ )
27
+ from openbench.configuration import EvaluationProfile, ProfileLoader
28
+ from openbench.dataset import (
29
+ Dataset,
30
+ DatasetLoader,
31
+ DatasetRecord,
32
+ IdFilterSpec,
33
+ load_ids_file,
34
+ )
35
+ from openbench.evaluator import (
36
+ EvaluationConfig,
37
+ EvaluationResult,
38
+ Evaluator,
39
+ ItemEvaluationResult,
40
+ ModelResponseSet,
41
+ ResponseItem,
42
+ ResponseLoader,
43
+ )
44
+ from openbench.exceptions import (
45
+ ConfigurationError,
46
+ DatasetValidationError,
47
+ EmptyDatasetError,
48
+ IncompatibleComparisonError,
49
+ MetricError,
50
+ OpenBenchError,
51
+ ResponseValidationError,
52
+ )
53
+ from openbench.execution import (
54
+ ExecutionConfig,
55
+ ExecutionEngine,
56
+ ExecutionOutcome,
57
+ RetryPolicy,
58
+ classify_execution_error,
59
+ )
60
+ from openbench.experiments import ExperimentManager, ExperimentRecord, RepeatGroupResult
61
+ from openbench.history import HistoryEntry, ResultHistoryStore
62
+ from openbench.integrity import (
63
+ IntegrityCheckResult,
64
+ RunIntegrityVerifier,
65
+ compute_dict_sha256,
66
+ compute_file_sha256,
67
+ )
68
+ from openbench.lineage import RunLineage
69
+ from openbench.metrics import LexicalSimilarityMetric
70
+ from openbench.paired import (
71
+ ComparisonArm,
72
+ ComparisonPolicy,
73
+ CompatibilityReport,
74
+ PairedComparisonEngine,
75
+ PairedComparisonResult,
76
+ )
77
+ from openbench.performance import (
78
+ PerformanceMonitor,
79
+ PerformanceReport,
80
+ PricingConfig,
81
+ RequestTelemetry,
82
+ compute_percentile,
83
+ )
84
+ from openbench.preferences import (
85
+ AnnotationImporter,
86
+ PreferenceAnalyzer,
87
+ PreferenceAnnotation,
88
+ PreferenceTaskBuilder,
89
+ )
90
+ from openbench.quality import AuditFinding, AuditReport, DatasetAuditor, ExperimentAuditor
91
+ from openbench.reporting import ReportGenerator, escape_markdown_cell
92
+ from openbench.security import (
93
+ redact_prompt_if_private,
94
+ redact_sensitive_data,
95
+ resolve_env_credential,
96
+ sanitize_error_message,
97
+ validate_endpoint_url,
98
+ validate_safe_output_path,
99
+ )
100
+
101
+ __version__ = "0.4.0"
102
+
103
+ __all__ = [
104
+ "__version__",
105
+ "BaseModelAdapter",
106
+ "BenchmarkAnalytics",
107
+ "CacheRecord",
108
+ "ComparisonResult",
109
+ "ConfigurationError",
110
+ "Dataset",
111
+ "DatasetCatalog",
112
+ "DatasetCatalogEntry",
113
+ "DatasetLoader",
114
+ "DatasetRecord",
115
+ "DatasetValidationError",
116
+ "DeterministicLookupAdapter",
117
+ "EmptyDatasetError",
118
+ "EvaluationConfig",
119
+ "EvaluationProfile",
120
+ "EvaluationResult",
121
+ "Evaluator",
122
+ "ExecutionConfig",
123
+ "ExecutionEngine",
124
+ "ExecutionOutcome",
125
+ "ExperimentManager",
126
+ "ExperimentRecord",
127
+ "GenerationConfig",
128
+ "HistoryEntry",
129
+ "HuggingFaceAdapter",
130
+ "IdFilterSpec",
131
+ "IntegrityCheckResult",
132
+ "ItemEvaluationResult",
133
+ "LexicalSimilarityMetric",
134
+ "LocalCallableAdapter",
135
+ "MetricError",
136
+ "MetricLeader",
137
+ "MockModelProvider",
138
+ "ModelComparator",
139
+ "ModelComparisonEntry",
140
+ "ModelResponseSet",
141
+ "OllamaAdapter",
142
+ "OllamaConnectionError",
143
+ "OpenBenchError",
144
+ "PROVIDER_REGISTRY",
145
+ "PerformanceMonitor",
146
+ "PerformanceReport",
147
+ "PricingConfig",
148
+ "ProfileLoader",
149
+ "ProviderHealthStatus",
150
+ "ReportGenerator",
151
+ "RequestTelemetry",
152
+ "ResponseCache",
153
+ "ResponseItem",
154
+ "ResponseLoader",
155
+ "ResponseValidationError",
156
+ "ResultHistoryStore",
157
+ "RetryPolicy",
158
+ "RunIntegrityVerifier",
159
+ "classify_execution_error",
160
+ "compute_dict_sha256",
161
+ "compute_file_sha256",
162
+ "compute_percentile",
163
+ "create_provider",
164
+ "escape_markdown_cell",
165
+ "list_registered_providers",
166
+ "load_ids_file",
167
+ "redact_prompt_if_private",
168
+ "redact_sensitive_data",
169
+ "register_provider",
170
+ "resolve_env_credential",
171
+ "sanitize_error_message",
172
+ "validate_endpoint_url",
173
+ "validate_safe_output_path",
174
+ # v0.4.0 additions (the 70 v0.3.1 names above are unchanged and keep their order)
175
+ "ComparisonArm",
176
+ "ComparisonPolicy",
177
+ "CompatibilityReport",
178
+ "IncompatibleComparisonError",
179
+ "PairedComparisonEngine",
180
+ "PairedComparisonResult",
181
+ "RepeatGroupResult",
182
+ "RunLineage",
183
+ "AnnotationImporter",
184
+ "PreferenceAnalyzer",
185
+ "PreferenceAnnotation",
186
+ "PreferenceTaskBuilder",
187
+ "AuditFinding",
188
+ "AuditReport",
189
+ "DatasetAuditor",
190
+ "ExperimentAuditor",
191
+ ]
@@ -0,0 +1,39 @@
1
+ """Model providers and adapters package for OpenBench AI."""
2
+
3
+ from openbench.adapters.base import (
4
+ PROVIDER_REGISTRY,
5
+ BaseModelAdapter,
6
+ GenerationConfig,
7
+ ProviderHealthStatus,
8
+ create_provider,
9
+ list_registered_providers,
10
+ register_provider,
11
+ )
12
+ from openbench.adapters.huggingface import HuggingFaceAdapter
13
+ from openbench.adapters.local import DeterministicLookupAdapter, LocalCallableAdapter
14
+ from openbench.adapters.mock import MockModelProvider
15
+ from openbench.adapters.ollama import DEFAULT_OLLAMA_ENDPOINT, OllamaAdapter, OllamaConnectionError
16
+
17
+ # Register all built-in providers
18
+ register_provider(MockModelProvider)
19
+ register_provider(OllamaAdapter)
20
+ register_provider(HuggingFaceAdapter, aliases=["huggingface", "hf"])
21
+ register_provider(LocalCallableAdapter)
22
+ register_provider(DeterministicLookupAdapter)
23
+
24
+ __all__ = [
25
+ "BaseModelAdapter",
26
+ "DEFAULT_OLLAMA_ENDPOINT",
27
+ "DeterministicLookupAdapter",
28
+ "GenerationConfig",
29
+ "HuggingFaceAdapter",
30
+ "LocalCallableAdapter",
31
+ "MockModelProvider",
32
+ "OllamaAdapter",
33
+ "OllamaConnectionError",
34
+ "PROVIDER_REGISTRY",
35
+ "ProviderHealthStatus",
36
+ "create_provider",
37
+ "list_registered_providers",
38
+ "register_provider",
39
+ ]
@@ -0,0 +1,220 @@
1
+ """Base model inference adapter interface, generation config, and provider registry for OpenBench AI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from abc import ABC, abstractmethod
7
+ from dataclasses import dataclass, field
8
+ from typing import Any
9
+
10
+ from openbench.dataset import Dataset, DatasetRecord
11
+ from openbench.evaluator import ModelResponseSet, ResponseItem
12
+ from openbench.exceptions import ConfigurationError
13
+ from openbench.security import redact_sensitive_data
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class GenerationConfig:
18
+ """Standardized generation parameters passed to model providers."""
19
+
20
+ temperature: float = 0.0
21
+ max_tokens: int = 256
22
+ top_p: float = 1.0
23
+ seed: int | None = None
24
+ stop_sequences: list[str] = field(default_factory=list)
25
+ stream: bool = False
26
+ extra_options: dict[str, Any] = field(default_factory=dict)
27
+
28
+ def to_dict(self) -> dict[str, Any]:
29
+ payload: dict[str, Any] = {
30
+ "temperature": self.temperature,
31
+ "max_tokens": self.max_tokens,
32
+ "top_p": self.top_p,
33
+ "stream": self.stream,
34
+ }
35
+ if self.seed is not None:
36
+ payload["seed"] = self.seed
37
+ if self.stop_sequences:
38
+ payload["stop_sequences"] = list(self.stop_sequences)
39
+ if self.extra_options:
40
+ payload["extra_options"] = redact_sensitive_data(dict(self.extra_options))
41
+ return payload
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class ProviderHealthStatus:
46
+ """Result of checking a model provider's availability and configuration."""
47
+
48
+ provider_name: str
49
+ available: bool
50
+ message: str
51
+ details: dict[str, Any] = field(default_factory=dict)
52
+
53
+ def to_dict(self) -> dict[str, Any]:
54
+ return {
55
+ "provider_name": self.provider_name,
56
+ "available": self.available,
57
+ "message": self.message,
58
+ "details": redact_sensitive_data(dict(self.details)),
59
+ }
60
+
61
+
62
+ class BaseModelAdapter(ABC):
63
+ """Abstract interface for connecting local or external language models to OpenBench AI.
64
+
65
+ Design Principles:
66
+ - Adapters are strictly optional; core OpenBench evaluation works directly on JSON files.
67
+ - Never hardcode tokens or automatically call paid APIs.
68
+ - Record reproducibility metadata (`model_name`, `model_version`, `model_provider`,
69
+ `inference_config`) alongside generated responses.
70
+ """
71
+
72
+ provider_name: str = "base_adapter"
73
+ description: str = "Abstract base model provider adapter."
74
+ is_experimental: bool = False
75
+ is_mock: bool = False
76
+ optional_dependencies: tuple[str, ...] = ()
77
+ #: True only for adapters that place ``GenerationConfig.seed`` in the provider request
78
+ #: (v0.4.0). A provider may still ignore the seed or be non-deterministic.
79
+ supports_generation_seed: bool = False
80
+
81
+ def __init__(
82
+ self,
83
+ model_name: str,
84
+ model_version: str = "1.0",
85
+ inference_config: dict[str, Any] | None = None,
86
+ generation_config: GenerationConfig | None = None,
87
+ timeout_seconds: float = 30.0,
88
+ ) -> None:
89
+ if not model_name or not model_name.strip():
90
+ raise ValueError("model_name must be a non-empty string")
91
+ if timeout_seconds <= 0:
92
+ raise ConfigurationError("timeout_seconds must be positive")
93
+ self.model_name = model_name.strip()
94
+ self.model_version = model_version.strip() or "1.0"
95
+ self.generation_config = generation_config or GenerationConfig()
96
+ self.timeout_seconds = float(timeout_seconds)
97
+ base_inf = self.generation_config.to_dict()
98
+ if inference_config:
99
+ base_inf.update(redact_sensitive_data(dict(inference_config)))
100
+ self.inference_config = base_inf
101
+ #: Number of provider requests into which this adapter placed a generation seed.
102
+ self.seed_forward_count = 0
103
+
104
+ def with_generation_seed(self, seed: int | None) -> BaseModelAdapter:
105
+ """Return a shallow copy of this adapter whose generation seed is ``seed`` (v0.4.0).
106
+
107
+ Used for per-repetition seeds. The copy shares any client or transport objects with
108
+ the original; only ``generation_config``, ``inference_config`` and the seed counter
109
+ are replaced.
110
+ """
111
+ import copy
112
+ from dataclasses import replace
113
+
114
+ clone = copy.copy(self)
115
+ clone.generation_config = replace(self.generation_config, seed=seed)
116
+ inference = dict(self.inference_config)
117
+ if seed is None:
118
+ inference.pop("seed", None)
119
+ else:
120
+ inference["seed"] = seed
121
+ clone.inference_config = inference
122
+ clone.seed_forward_count = 0
123
+ return clone
124
+
125
+ @abstractmethod
126
+ def generate_one(self, record: DatasetRecord) -> ResponseItem:
127
+ """Generate a single ResponseItem for a given DatasetRecord."""
128
+
129
+ def generate_responses(self, dataset: Dataset) -> ModelResponseSet:
130
+ """Run inference across all records in a Dataset and return a ModelResponseSet."""
131
+ items = [self.generate_one(record) for record in dataset.records]
132
+ return ModelResponseSet(
133
+ model_name=self.model_name,
134
+ model_version=self.model_version,
135
+ responses=items,
136
+ model_provider=self.provider_name,
137
+ inference_config=self.reproducibility_metadata(),
138
+ )
139
+
140
+ def check_connection(self) -> ProviderHealthStatus:
141
+ """Verify whether the provider is reachable and properly configured."""
142
+ return ProviderHealthStatus(
143
+ provider_name=self.provider_name,
144
+ available=True,
145
+ message=f"Provider '{self.provider_name}' is ready.",
146
+ details={"model_name": self.model_name, "is_mock": self.is_mock},
147
+ )
148
+
149
+ def reproducibility_metadata(self) -> dict[str, Any]:
150
+ """Return safe reproducibility metadata for reports and cards (with secrets redacted)."""
151
+ return redact_sensitive_data(
152
+ {
153
+ "provider": self.provider_name,
154
+ "model_name": self.model_name,
155
+ "model_version": self.model_version,
156
+ "experimental": self.is_experimental,
157
+ "is_mock": self.is_mock,
158
+ "timeout_seconds": self.timeout_seconds,
159
+ **self.inference_config,
160
+ }
161
+ )
162
+
163
+
164
+ PROVIDER_REGISTRY: dict[str, type[BaseModelAdapter]] = {}
165
+
166
+
167
+ def register_provider(adapter_cls: type[BaseModelAdapter], aliases: list[str] | None = None) -> None:
168
+ """Register a BaseModelAdapter subclass in `PROVIDER_REGISTRY`."""
169
+ if not isinstance(adapter_cls, type) or not issubclass(adapter_cls, BaseModelAdapter):
170
+ raise ConfigurationError("Provider class must inherit from BaseModelAdapter")
171
+ p_name = getattr(adapter_cls, "provider_name", "").strip().lower()
172
+ if not p_name:
173
+ raise ConfigurationError("Provider class must define a non-empty 'provider_name'")
174
+ PROVIDER_REGISTRY[p_name] = adapter_cls
175
+ for alias in aliases or []:
176
+ PROVIDER_REGISTRY[alias.strip().lower()] = adapter_cls
177
+
178
+
179
+ def create_provider(provider_name: str, **kwargs: Any) -> BaseModelAdapter:
180
+ """Instantiate a registered provider adapter by name."""
181
+ key = provider_name.strip().lower()
182
+ if key not in PROVIDER_REGISTRY:
183
+ available = sorted({cls.provider_name for cls in PROVIDER_REGISTRY.values()})
184
+ raise ConfigurationError(
185
+ f"Unknown model provider '{provider_name}'. Registered providers: {', '.join(available)}"
186
+ )
187
+ adapter_cls = PROVIDER_REGISTRY[key]
188
+ sig = inspect.signature(adapter_cls.__init__)
189
+ accepts_var_kw = any(
190
+ p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
191
+ )
192
+ filtered_kwargs: dict[str, Any] = {}
193
+ for k, v in kwargs.items():
194
+ if accepts_var_kw or k in sig.parameters:
195
+ filtered_kwargs[k] = v
196
+ else:
197
+ raise ConfigurationError(
198
+ f"Provider '{provider_name}' does not accept configuration argument '{k}'"
199
+ )
200
+ return adapter_cls(**filtered_kwargs)
201
+
202
+
203
+ def list_registered_providers() -> list[dict[str, Any]]:
204
+ """Return metadata dictionaries for all unique registered model providers."""
205
+ unique: dict[str, type[BaseModelAdapter]] = {}
206
+ for cls in PROVIDER_REGISTRY.values():
207
+ unique[cls.provider_name] = cls
208
+ result: list[dict[str, Any]] = []
209
+ for name in sorted(unique):
210
+ cls = unique[name]
211
+ result.append(
212
+ {
213
+ "provider_name": cls.provider_name,
214
+ "description": cls.description,
215
+ "is_experimental": cls.is_experimental,
216
+ "is_mock": cls.is_mock,
217
+ "optional_dependencies": list(cls.optional_dependencies),
218
+ }
219
+ )
220
+ return result
@@ -0,0 +1,158 @@
1
+ """Optional Hugging Face `transformers` adapter for OpenBench AI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from typing import Any
7
+
8
+ from openbench.adapters.base import BaseModelAdapter, GenerationConfig, ProviderHealthStatus
9
+ from openbench.dataset import DatasetRecord
10
+ from openbench.evaluator import ResponseItem
11
+ from openbench.exceptions import ConfigurationError, OpenBenchError
12
+
13
+
14
+ class HuggingFaceAdapter(BaseModelAdapter):
15
+ """Optional adapter for local Hugging Face `transformers` text-generation pipelines.
16
+
17
+ Status: Experimental (requires optional `transformers` and `torch` packages).
18
+
19
+ Safety & Reproducibility Guarantees:
20
+ - Does NOT import `transformers` at module import time.
21
+ - Defaults to `local_files_only=True` so weights are NEVER automatically downloaded
22
+ from the Hugging Face Hub unless the caller explicitly sets `allow_download=True`.
23
+ - Contains zero hardcoded API tokens and makes zero paid API calls.
24
+ - Records explicit `model_id`, `revision`, `device`, and generation parameters.
25
+ - Catches `MemoryError` and CUDA/CPU OOM errors with actionable remediation hints.
26
+ """
27
+
28
+ provider_name = "huggingface_transformers"
29
+ description = "Local Hugging Face transformers text-generation pipeline adapter (Experimental)."
30
+ is_experimental = True
31
+ is_mock = False
32
+ optional_dependencies = ("transformers", "torch")
33
+
34
+ def __init__(
35
+ self,
36
+ model_id: str = "gpt2",
37
+ revision: str = "main",
38
+ allow_download: bool = False,
39
+ device: str = "cpu",
40
+ max_new_tokens: int = 128,
41
+ temperature: float = 0.0,
42
+ pipeline_fn: Any | None = None,
43
+ generation_config: GenerationConfig | None = None,
44
+ timeout_seconds: float = 60.0,
45
+ model_name: str | None = None,
46
+ ) -> None:
47
+ resolved_id = (model_name or model_id).strip()
48
+ if not resolved_id:
49
+ raise ConfigurationError("An explicit Hugging Face model_id is required")
50
+ gen_cfg = generation_config or GenerationConfig(
51
+ temperature=temperature,
52
+ max_tokens=max_new_tokens,
53
+ )
54
+ super().__init__(
55
+ model_name=resolved_id,
56
+ model_version=revision.strip() or "main",
57
+ generation_config=gen_cfg,
58
+ timeout_seconds=timeout_seconds,
59
+ inference_config={
60
+ "model_id": resolved_id,
61
+ "revision": revision.strip() or "main",
62
+ "local_files_only": not allow_download,
63
+ "device": device,
64
+ "max_new_tokens": max_new_tokens,
65
+ "temperature": temperature,
66
+ },
67
+ )
68
+ self.model_id = resolved_id
69
+ self.revision = revision.strip() or "main"
70
+ self.allow_download = allow_download
71
+ self.device = device
72
+ self.max_new_tokens = max_new_tokens
73
+ self.temperature = temperature
74
+ self._pipeline = pipeline_fn
75
+
76
+ def check_connection(self) -> ProviderHealthStatus:
77
+ if self._pipeline is not None:
78
+ return ProviderHealthStatus(
79
+ provider_name=self.provider_name,
80
+ available=True,
81
+ message=f"HuggingFaceAdapter ready with injected pipeline for '{self.model_id}'.",
82
+ details={"model_id": self.model_id, "revision": self.revision},
83
+ )
84
+ try:
85
+ import transformers # type: ignore[import-not-found] # noqa: F401
86
+ except ImportError:
87
+ return ProviderHealthStatus(
88
+ provider_name=self.provider_name,
89
+ available=False,
90
+ message=(
91
+ "Optional dependency 'transformers' is not installed. "
92
+ "Install it with `pip install -e '.[huggingface]'`."
93
+ ),
94
+ details={"model_id": self.model_id, "device": self.device},
95
+ )
96
+ return ProviderHealthStatus(
97
+ provider_name=self.provider_name,
98
+ available=True,
99
+ message=f"transformers package is installed (model='{self.model_id}', local_files_only={not self.allow_download}).",
100
+ details={"model_id": self.model_id, "revision": self.revision, "device": self.device},
101
+ )
102
+
103
+ def _get_pipeline(self) -> Any:
104
+ if self._pipeline is not None:
105
+ return self._pipeline
106
+ try:
107
+ from transformers import pipeline # type: ignore[import-not-found]
108
+ except ImportError as exc:
109
+ raise ConfigurationError(
110
+ "Optional dependency 'transformers' is not installed. "
111
+ "Install it with `pip install -e '.[huggingface]'` to use HuggingFaceAdapter."
112
+ ) from exc
113
+
114
+ try:
115
+ self._pipeline = pipeline(
116
+ "text-generation",
117
+ model=self.model_id,
118
+ revision=self.revision,
119
+ device=self.device,
120
+ model_kwargs={"local_files_only": not self.allow_download},
121
+ )
122
+ except MemoryError as exc:
123
+ raise OpenBenchError(
124
+ f"Insufficient system memory to load Hugging Face model '{self.model_id}' on device '{self.device}'."
125
+ ) from exc
126
+ except Exception as exc:
127
+ raise OpenBenchError(
128
+ f"Failed to load Hugging Face model '{self.model_id}' (revision='{self.revision}', "
129
+ f"local_files_only={not self.allow_download}): {exc}"
130
+ ) from exc
131
+ return self._pipeline
132
+
133
+ def generate_one(self, record: DatasetRecord) -> ResponseItem:
134
+ pipe = self._get_pipeline()
135
+ start = time.perf_counter()
136
+ try:
137
+ outputs = pipe(
138
+ record.prompt,
139
+ max_new_tokens=self.max_new_tokens,
140
+ do_sample=self.temperature > 0.0,
141
+ return_full_text=False,
142
+ )
143
+ except MemoryError as exc:
144
+ raise OpenBenchError(
145
+ f"Out of memory while generating response for '{record.id}' with model '{self.model_id}'."
146
+ ) from exc
147
+ elapsed_ms = (time.perf_counter() - start) * 1000.0
148
+
149
+ if isinstance(outputs, list) and outputs and isinstance(outputs[0], dict):
150
+ generated_text = str(outputs[0].get("generated_text", ""))
151
+ else:
152
+ generated_text = str(outputs)
153
+
154
+ return ResponseItem(
155
+ id=record.id,
156
+ response=generated_text,
157
+ latency_ms=round(elapsed_ms, 3),
158
+ )