Skip to content

Commit a71c798

Browse files
committed
Merge branch 'main' into derekx/exception-handler
2 parents 4107903 + aac0214 commit a71c798

13 files changed

Lines changed: 849 additions & 217 deletions

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- (Your now removed features here)
2121

2222
### Fixed
23-
- (Your bug fixes here)
23+
- Pin minimum `datasets` version to `>=3.0.0` to avoid import failures with `pyarrow>=21` (AttributeError: `pyarrow.PyExtensionType`). This ensures compatibility when installing via pip/uv in clean CI environments.
2424

2525
### Security
2626
- (Your vulnerabilities patched here)

eval_protocol/models.py

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,14 @@ class InputMetadata(BaseModel):
211211

212212
model_config = ConfigDict(extra="allow")
213213

214-
row_id: Optional[str] = Field(None, description="Unique string to ID the row")
214+
row_id: Optional[str] = Field(
215+
default=None,
216+
description=(
217+
"Unique string to ID the row. If not provided, a stable hash will be generated "
218+
"based on the row's content. The hash removes fields that are not typically stable "
219+
"across processes such as created_at, execution_metadata, and pid."
220+
),
221+
)
215222
completion_params: CompletionParams = Field(
216223
default_factory=dict, description="Completion endpoint parameters used"
217224
)
@@ -430,20 +437,53 @@ def get_termination_reason(self) -> str:
430437
return "unknown"
431438

432439
def __hash__(self) -> int:
433-
# Use a stable hash by sorting keys and ensuring compact output
434-
json_str = self.stable_json(self)
435-
return hash(json_str)
440+
# Use a stable hash that works across Python processes
441+
return self._stable_hash()
442+
443+
def _stable_hash(self) -> int:
444+
"""Generate a stable hash that works across Python processes."""
445+
import hashlib
446+
447+
# Get the stable JSON representation
448+
json_str = self._stable_json()
449+
450+
# Use SHA-256 for deterministic hashing across processes
451+
hash_obj = hashlib.sha256(json_str.encode("utf-8"))
452+
453+
# Convert to a positive integer (first 8 bytes)
454+
hash_bytes = hash_obj.digest()[:8]
455+
return int.from_bytes(hash_bytes, byteorder="big")
456+
457+
def _stable_json(self) -> str:
458+
"""Generate a stable JSON string representation for hashing."""
459+
# Produce a canonical, key-sorted JSON across nested structures and
460+
# exclude volatile fields that can differ across processes
461+
import json
462+
from enum import Enum
463+
464+
def canonicalize(value):
465+
# Recursively convert to a structure with deterministic key ordering
466+
if isinstance(value, dict):
467+
return {k: canonicalize(value[k]) for k in sorted(value.keys())}
468+
if isinstance(value, list):
469+
return [canonicalize(v) for v in value]
470+
if isinstance(value, Enum):
471+
return value.value
472+
return value
436473

437-
@classmethod
438-
def stable_json(cls, row: "EvaluationRow") -> int:
439-
json_str = row.model_dump_json(
474+
# Dump to a plain Python structure first
475+
data = self.model_dump(
440476
exclude_none=True,
441477
exclude_defaults=True,
442478
by_alias=True,
443-
indent=None,
444-
exclude=["created_at", "execution_metadata"],
479+
exclude={"created_at", "execution_metadata", "pid"},
445480
)
446-
return json_str
481+
482+
# Ensure deterministic ordering for all nested dicts
483+
canonical_data = canonicalize(data)
484+
485+
# Compact, sorted JSON string
486+
return json.dumps(canonical_data, separators=(",", ":"), sort_keys=True, ensure_ascii=False)
447487

448488

449489
# Original dataclass-based models for backwards compatibility

eval_protocol/pytest/default_agent_rollout_processor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def __call__(self, rows: List[EvaluationRow], config: RolloutProcessorConfig) ->
135135
async def process_row(row: EvaluationRow) -> EvaluationRow:
136136
"""Process a single row with agent rollout."""
137137
agent = Agent(
138-
model=config.completion_params["model"],
138+
model=row.input_metadata.completion_params["model"],
139139
row=row,
140140
config_path=config.mcp_config_path,
141141
logger=config.logger,

0 commit comments

Comments
 (0)