Source code for pyrit.identifiers.evaluation_identity

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

"""
Evaluation identity and eval-hash computation.

This module provides:

* ``_build_eval_dict`` — builds a filtered dict for eval-hash computation.
* ``compute_eval_hash`` — free function that computes a behavioral equivalence
  hash from a ``ComponentIdentifier``.
* ``EvaluationIdentity`` — abstract base that wraps a ``ComponentIdentifier``
  with domain-specific eval-hash configuration.  Concrete subclasses declare
  *which* children are targets and *which* params are behavioral via two
  ``ClassVar`` frozensets.
"""

from __future__ import annotations

from abc import ABC
from typing import Any, ClassVar, Optional

from pyrit.identifiers.component_identifier import ComponentIdentifier, config_hash


def _build_eval_dict(
    identifier: ComponentIdentifier,
    *,
    target_child_keys: frozenset[str],
    behavioral_child_params: frozenset[str],
    param_allowlist: Optional[frozenset[str]] = None,
) -> dict[str, Any]:
    """
    Build a filtered dictionary for eval-hash computation.

    Includes only behavioral parameters. For child components whose names appear
    in ``target_child_keys``, only params in ``behavioral_child_params`` are kept
    (stripping operational params like endpoint, max_requests_per_minute).
    Non-target children receive full eval treatment recursively.

    Args:
        identifier (ComponentIdentifier): The component identity to process.
        target_child_keys (frozenset[str]): Child names that are targets
            (e.g., ``{"prompt_target", "converter_target"}``).
        behavioral_child_params (frozenset[str]): Param allowlist applied to
            target children (e.g., ``{"model_name", "temperature", "top_p"}``).
        param_allowlist (Optional[frozenset[str]]): If provided, only include
            params whose keys are in the allowlist. If None, include all params.

    Returns:
        dict[str, Any]: The filtered dictionary suitable for hashing.
    """
    eval_dict: dict[str, Any] = {
        ComponentIdentifier.KEY_CLASS_NAME: identifier.class_name,
        ComponentIdentifier.KEY_CLASS_MODULE: identifier.class_module,
    }

    eval_dict.update(
        {
            key: value
            for key, value in sorted(identifier.params.items())
            if value is not None and (param_allowlist is None or key in param_allowlist)
        }
    )

    if identifier.children:
        eval_children: dict[str, Any] = {}
        for name in sorted(identifier.children):
            child_list = identifier.get_child_list(name)
            if name in target_child_keys:
                # Targets: filter to behavioral params only
                hashes = [
                    config_hash(
                        _build_eval_dict(
                            c,
                            target_child_keys=target_child_keys,
                            behavioral_child_params=behavioral_child_params,
                            param_allowlist=behavioral_child_params,
                        )
                    )
                    for c in child_list
                ]
            else:
                # Non-targets (e.g., sub-scorers): full eval treatment, recurse without param filtering
                hashes = [
                    config_hash(
                        _build_eval_dict(
                            c,
                            target_child_keys=target_child_keys,
                            behavioral_child_params=behavioral_child_params,
                        )
                    )
                    for c in child_list
                ]
            eval_children[name] = hashes[0] if len(hashes) == 1 else hashes
        if eval_children:
            eval_dict["children"] = eval_children

    return eval_dict


[docs] def compute_eval_hash( identifier: ComponentIdentifier, *, target_child_keys: frozenset[str], behavioral_child_params: frozenset[str], ) -> str: """ Compute a behavioral equivalence hash for evaluation grouping. Unlike ``ComponentIdentifier.hash`` (which includes all params of self and children), the eval hash filters child components that are "targets" to only their behavioral params (e.g., model_name, temperature, top_p), stripping operational params like endpoint or max_requests_per_minute. This ensures the same logical configuration on different deployments produces the same eval hash. Non-target children (e.g., sub-scorers) receive full recursive eval treatment. When ``target_child_keys`` is empty, no child filtering occurs and the result equals ``identifier.hash``. Args: identifier (ComponentIdentifier): The component identity to compute the hash for. target_child_keys (frozenset[str]): Child names that are targets (e.g., ``{"prompt_target", "converter_target"}``). behavioral_child_params (frozenset[str]): Param allowlist for target children (e.g., ``{"model_name", "temperature", "top_p"}``). Returns: str: A hex-encoded SHA256 hash suitable for eval registry keying. """ if not target_child_keys: return identifier.hash eval_dict = _build_eval_dict( identifier, target_child_keys=target_child_keys, behavioral_child_params=behavioral_child_params, ) return config_hash(eval_dict)
[docs] class EvaluationIdentity(ABC): """ Wraps a ``ComponentIdentifier`` with domain-specific eval-hash configuration. Subclasses must set the two ``ClassVar`` frozensets: * ``TARGET_CHILD_KEYS`` — child names whose operational params should be stripped (e.g., ``{"prompt_target", "converter_target"}``). * ``BEHAVIORAL_CHILD_PARAMS`` — param allowlist applied to those target children (e.g., ``{"model_name", "temperature", "top_p"}``). The concrete ``eval_hash`` property delegates to the module-level ``compute_eval_hash`` free function. """ TARGET_CHILD_KEYS: ClassVar[frozenset[str]] BEHAVIORAL_CHILD_PARAMS: ClassVar[frozenset[str]]
[docs] def __init__(self, identifier: ComponentIdentifier) -> None: """Wrap a ComponentIdentifier and eagerly compute its eval hash.""" self._identifier = identifier self._eval_hash = compute_eval_hash( identifier, target_child_keys=self.TARGET_CHILD_KEYS, behavioral_child_params=self.BEHAVIORAL_CHILD_PARAMS, )
@property def identifier(self) -> ComponentIdentifier: """The underlying component identity.""" return self._identifier @property def eval_hash(self) -> str: """Behavioral equivalence hash for evaluation grouping.""" return self._eval_hash