Skip to content

Components

components

Evolvable surface handlers for component mutation.

Currently contains the ComponentHandlerRegistry and built-in handlers for instruction, output_schema, and generate_content_config components.

Anticipated growth: custom handler registration, handler composition, component validation middleware.

ATTRIBUTE DESCRIPTION
ComponentHandlerRegistry

Registry for component handlers.

TYPE: class

InstructionHandler

Handler for agent.instruction component.

TYPE: class

OutputSchemaHandler

Handler for agent.output_schema component.

TYPE: class

GenerateContentConfigHandler

Handler for LLM generation config.

TYPE: class

component_handlers

Default registry instance.

TYPE: ComponentHandlerRegistry

get_handler

Convenience function for getting handlers.

TYPE: function

register_handler

Convenience function for registering handlers.

TYPE: function

MappingComponentHandler

Handler for one key of a caller-owned mapping.

TYPE: class

register_mapping_components

Register a handler for every key of a mapping.

TYPE: function

Examples:

Retrieve a handler for a specific component:

from gepa_adk.adapters.components import get_handler

handler = get_handler("instruction")

Register a custom handler:

from gepa_adk.adapters.components import register_handler

register_handler("my_component", MyCustomHandler())
See Also
Notes

This package centralizes component handler registration for evolution surfaces.

ComponentHandlerRegistry

Registry for component handlers with O(1) lookup.

Stores component handlers keyed by component name, providing registration, lookup, and existence checking operations.

ATTRIBUTE DESCRIPTION
_handlers

Internal dict mapping component names to handlers.

TYPE: dict[str, ComponentHandler]

Examples:

Create and use a registry:

registry = ComponentHandlerRegistry()
registry.register("instruction", InstructionHandler())
handler = registry.get("instruction")
See Also
Notes

A default registry instance is available as component_handlers module variable, with convenience functions get_handler() and register_handler().

Source code in src/gepa_adk/adapters/components/component_handlers.py
class ComponentHandlerRegistry:
    """Registry for component handlers with O(1) lookup.

    Stores component handlers keyed by component name, providing
    registration, lookup, and existence checking operations.

    Attributes:
        _handlers (dict[str, ComponentHandler]): Internal dict mapping component names to handlers.

    Examples:
        Create and use a registry:

        ```python
        registry = ComponentHandlerRegistry()
        registry.register("instruction", InstructionHandler())
        handler = registry.get("instruction")
        ```

    See Also:
        - [`get_handler()`][gepa_adk.adapters.components.component_handlers.get_handler]:
            Convenience function for default registry.

    Notes:
        A default registry instance is available as `component_handlers`
        module variable, with convenience functions `get_handler()` and
        `register_handler()`.
    """

    def __init__(self) -> None:
        """Initialize empty registry.

        Examples:
            ```python
            registry = ComponentHandlerRegistry()
            assert not registry.has("instruction")
            ```

        Notes:
            Creates an empty internal dict for handler storage.
        """
        self._handlers: dict[str, ComponentHandler] = {}

    def register(self, name: str, handler: ComponentHandler) -> None:
        """Register a handler for a component name.

        Args:
            name: Component name (e.g., "instruction", "output_schema").
                Must be a non-empty string.
            handler: Handler implementing ComponentHandler protocol.

        Raises:
            ValueError: If name is empty or None.
            TypeError: If handler doesn't implement ComponentHandler protocol.

        Examples:
            ```python
            registry.register("instruction", InstructionHandler())
            registry.register("output_schema", OutputSchemaHandler())
            ```

        Notes:
            Overwrites existing handler if name already registered.
            Logs a debug message on replacement.
        """
        if not name:
            raise ValueError("Component name must be a non-empty string")

        if not isinstance(handler, ComponentHandler):
            raise TypeError(
                f"Handler does not implement ComponentHandler protocol: {type(handler)}"
            )

        if name in self._handlers:
            logger.debug(
                "component_handler.register.replace",
                name=name,
                old_handler=type(self._handlers[name]).__name__,
                new_handler=type(handler).__name__,
            )

        self._handlers[name] = handler
        logger.debug(
            "component_handler.register.success",
            name=name,
            handler=type(handler).__name__,
        )

    def get(self, name: str) -> ComponentHandler:
        """Retrieve handler for component name.

        Args:
            name: Component name to look up.

        Returns:
            The registered ComponentHandler.

        Raises:
            ValueError: If name is empty or None.
            KeyError: If no handler registered for name.

        Examples:
            ```python
            handler = registry.get("instruction")
            original = handler.apply(agent, "New value")
            ```
        """
        if not name:
            raise ValueError("Component name must be a non-empty string")

        if name not in self._handlers:
            raise KeyError(f"No handler registered for component: {name}")

        return self._handlers[name]

    def has(self, name: str) -> bool:
        """Check if handler exists for component name.

        Args:
            name: Component name to check.

        Returns:
            True if handler registered, False otherwise.

        Examples:
            ```python
            if registry.has("instruction"):
                handler = registry.get("instruction")
            ```

        Notes:
            Outputs False for empty/None names (no ValueError).
            This allows safe checking without exception handling.
        """
        if not name:
            return False
        return name in self._handlers

    def names(self) -> list[str]:
        """Return sorted list of registered handler names.

        Returns:
            Sorted list of component names with registered handlers.

        Examples:
            ```python
            available = registry.names()
            # ["generate_content_config", "instruction", "output_schema"]
            ```

        Notes:
            Output is sorted alphabetically for consistent error messages
            and validation feedback.
        """
        return sorted(self._handlers.keys())

__init__

__init__() -> None

Initialize empty registry.

Examples:

registry = ComponentHandlerRegistry()
assert not registry.has("instruction")
Notes

Creates an empty internal dict for handler storage.

Source code in src/gepa_adk/adapters/components/component_handlers.py
def __init__(self) -> None:
    """Initialize empty registry.

    Examples:
        ```python
        registry = ComponentHandlerRegistry()
        assert not registry.has("instruction")
        ```

    Notes:
        Creates an empty internal dict for handler storage.
    """
    self._handlers: dict[str, ComponentHandler] = {}

register

register(name: str, handler: ComponentHandler) -> None

Register a handler for a component name.

PARAMETER DESCRIPTION
name

Component name (e.g., "instruction", "output_schema"). Must be a non-empty string.

TYPE: str

handler

Handler implementing ComponentHandler protocol.

TYPE: ComponentHandler

RAISES DESCRIPTION
ValueError

If name is empty or None.

TypeError

If handler doesn't implement ComponentHandler protocol.

Examples:

registry.register("instruction", InstructionHandler())
registry.register("output_schema", OutputSchemaHandler())
Notes

Overwrites existing handler if name already registered. Logs a debug message on replacement.

Source code in src/gepa_adk/adapters/components/component_handlers.py
def register(self, name: str, handler: ComponentHandler) -> None:
    """Register a handler for a component name.

    Args:
        name: Component name (e.g., "instruction", "output_schema").
            Must be a non-empty string.
        handler: Handler implementing ComponentHandler protocol.

    Raises:
        ValueError: If name is empty or None.
        TypeError: If handler doesn't implement ComponentHandler protocol.

    Examples:
        ```python
        registry.register("instruction", InstructionHandler())
        registry.register("output_schema", OutputSchemaHandler())
        ```

    Notes:
        Overwrites existing handler if name already registered.
        Logs a debug message on replacement.
    """
    if not name:
        raise ValueError("Component name must be a non-empty string")

    if not isinstance(handler, ComponentHandler):
        raise TypeError(
            f"Handler does not implement ComponentHandler protocol: {type(handler)}"
        )

    if name in self._handlers:
        logger.debug(
            "component_handler.register.replace",
            name=name,
            old_handler=type(self._handlers[name]).__name__,
            new_handler=type(handler).__name__,
        )

    self._handlers[name] = handler
    logger.debug(
        "component_handler.register.success",
        name=name,
        handler=type(handler).__name__,
    )

get

get(name: str) -> ComponentHandler

Retrieve handler for component name.

PARAMETER DESCRIPTION
name

Component name to look up.

TYPE: str

RETURNS DESCRIPTION
ComponentHandler

The registered ComponentHandler.

RAISES DESCRIPTION
ValueError

If name is empty or None.

KeyError

If no handler registered for name.

Examples:

handler = registry.get("instruction")
original = handler.apply(agent, "New value")
Source code in src/gepa_adk/adapters/components/component_handlers.py
def get(self, name: str) -> ComponentHandler:
    """Retrieve handler for component name.

    Args:
        name: Component name to look up.

    Returns:
        The registered ComponentHandler.

    Raises:
        ValueError: If name is empty or None.
        KeyError: If no handler registered for name.

    Examples:
        ```python
        handler = registry.get("instruction")
        original = handler.apply(agent, "New value")
        ```
    """
    if not name:
        raise ValueError("Component name must be a non-empty string")

    if name not in self._handlers:
        raise KeyError(f"No handler registered for component: {name}")

    return self._handlers[name]

has

has(name: str) -> bool

Check if handler exists for component name.

PARAMETER DESCRIPTION
name

Component name to check.

TYPE: str

RETURNS DESCRIPTION
bool

True if handler registered, False otherwise.

Examples:

if registry.has("instruction"):
    handler = registry.get("instruction")
Notes

Outputs False for empty/None names (no ValueError). This allows safe checking without exception handling.

Source code in src/gepa_adk/adapters/components/component_handlers.py
def has(self, name: str) -> bool:
    """Check if handler exists for component name.

    Args:
        name: Component name to check.

    Returns:
        True if handler registered, False otherwise.

    Examples:
        ```python
        if registry.has("instruction"):
            handler = registry.get("instruction")
        ```

    Notes:
        Outputs False for empty/None names (no ValueError).
        This allows safe checking without exception handling.
    """
    if not name:
        return False
    return name in self._handlers

names

names() -> list[str]

Return sorted list of registered handler names.

RETURNS DESCRIPTION
list[str]

Sorted list of component names with registered handlers.

Examples:

available = registry.names()
# ["generate_content_config", "instruction", "output_schema"]
Notes

Output is sorted alphabetically for consistent error messages and validation feedback.

Source code in src/gepa_adk/adapters/components/component_handlers.py
def names(self) -> list[str]:
    """Return sorted list of registered handler names.

    Returns:
        Sorted list of component names with registered handlers.

    Examples:
        ```python
        available = registry.names()
        # ["generate_content_config", "instruction", "output_schema"]
        ```

    Notes:
        Output is sorted alphabetically for consistent error messages
        and validation feedback.
    """
    return sorted(self._handlers.keys())

GenerateContentConfigHandler

Handler for agent.generate_content_config component.

Manages serialization, application, and restoration of the agent's LLM generation configuration during evolution.

Examples:

handler = GenerateContentConfigHandler()
original = handler.serialize(agent)  # YAML string
handler.apply(agent, "temperature: 0.5")
# ... evaluate ...
handler.restore(agent, original_config)
Notes

All state is stored in the agent object - handler is stateless. On invalid config, logs warning and keeps original.

Source code in src/gepa_adk/adapters/components/component_handlers.py
class GenerateContentConfigHandler:
    """Handler for agent.generate_content_config component.

    Manages serialization, application, and restoration of the
    agent's LLM generation configuration during evolution.

    Examples:
        ```python
        handler = GenerateContentConfigHandler()
        original = handler.serialize(agent)  # YAML string
        handler.apply(agent, "temperature: 0.5")
        # ... evaluate ...
        handler.restore(agent, original_config)
        ```

    Notes:
        All state is stored in the agent object - handler is stateless.
        On invalid config, logs warning and keeps original.
    """

    def serialize(self, agent: LlmAgent) -> str:
        """Extract generate_content_config from agent as YAML.

        Args:
            agent (LlmAgent): The agent to read.

        Returns:
            YAML string with parameter descriptions as comments.
            Returns empty string if generate_content_config is None.

        Examples:
            ```python
            yaml_str = handler.serialize(agent)
            # yaml_str contains YAML with temperature, top_p, etc.
            ```
        """
        config = getattr(agent, "generate_content_config", None)
        return serialize_generate_config(config)

    def apply(self, agent: LlmAgent, value: str) -> Any:
        """Apply new generate_content_config to agent, return original.

        Args:
            agent (LlmAgent): The agent to modify.
            value (str): YAML string defining the new config parameters.

        Returns:
            The original GenerateContentConfig (or None), whether or not the
            new config was applied.

        Examples:
            ```python
            original = handler.apply(agent, "temperature: 0.5")
            # agent.generate_content_config.temperature is now 0.5
            ```

        Notes:
            On deserialization or validation failure, logs warning and
            keeps original config. Never raises exceptions.
        """
        original = getattr(agent, "generate_content_config", None)

        try:
            new_config = deserialize_generate_config(value, original)

            # Validate the parsed config
            config_dict = {}
            for param in [
                "temperature",
                "top_p",
                "top_k",
                "max_output_tokens",
                "presence_penalty",
                "frequency_penalty",
            ]:
                param_value = getattr(new_config, param, None)
                if param_value is not None:
                    config_dict[param] = param_value

            errors = validate_generate_config(config_dict)
            if errors:
                logger.warning(
                    "generate_content_config_handler.apply.validation_failed",
                    errors=errors,
                    config_preview=value[:100] if value else "",
                )
                # Keep original - don't modify agent
                return original

            agent.generate_content_config = new_config
            logger.debug(
                "generate_content_config_handler.apply",
                original_temp=getattr(original, "temperature", None)
                if original
                else None,
                new_temp=getattr(new_config, "temperature", None),
            )
        except ConfigValidationError as e:
            logger.warning(
                "generate_content_config_handler.apply.failed",
                error=str(e),
                config_preview=value[:100] if value else "",
            )
            # Keep original - don't modify agent

        return original

    def restore(self, agent: LlmAgent, original: Any) -> None:
        """Restore the generate_content_config that apply() returned.

        Args:
            agent (LlmAgent): The agent to restore.
            original (Any): The GenerateContentConfig apply() returned, or None.

        Examples:
            ```python
            handler.restore(agent, original_config)
            # agent.generate_content_config is back to original
            ```
        """
        agent.generate_content_config = original
        logger.debug(
            "generate_content_config_handler.restore",
            has_config=original is not None,
        )

serialize

serialize(agent: LlmAgent) -> str

Extract generate_content_config from agent as YAML.

PARAMETER DESCRIPTION
agent

The agent to read.

TYPE: LlmAgent

RETURNS DESCRIPTION
str

YAML string with parameter descriptions as comments.

str

Returns empty string if generate_content_config is None.

Examples:

yaml_str = handler.serialize(agent)
# yaml_str contains YAML with temperature, top_p, etc.
Source code in src/gepa_adk/adapters/components/component_handlers.py
def serialize(self, agent: LlmAgent) -> str:
    """Extract generate_content_config from agent as YAML.

    Args:
        agent (LlmAgent): The agent to read.

    Returns:
        YAML string with parameter descriptions as comments.
        Returns empty string if generate_content_config is None.

    Examples:
        ```python
        yaml_str = handler.serialize(agent)
        # yaml_str contains YAML with temperature, top_p, etc.
        ```
    """
    config = getattr(agent, "generate_content_config", None)
    return serialize_generate_config(config)

apply

apply(agent: LlmAgent, value: str) -> Any

Apply new generate_content_config to agent, return original.

PARAMETER DESCRIPTION
agent

The agent to modify.

TYPE: LlmAgent

value

YAML string defining the new config parameters.

TYPE: str

RETURNS DESCRIPTION
Any

The original GenerateContentConfig (or None), whether or not the

Any

new config was applied.

Examples:

original = handler.apply(agent, "temperature: 0.5")
# agent.generate_content_config.temperature is now 0.5
Notes

On deserialization or validation failure, logs warning and keeps original config. Never raises exceptions.

Source code in src/gepa_adk/adapters/components/component_handlers.py
def apply(self, agent: LlmAgent, value: str) -> Any:
    """Apply new generate_content_config to agent, return original.

    Args:
        agent (LlmAgent): The agent to modify.
        value (str): YAML string defining the new config parameters.

    Returns:
        The original GenerateContentConfig (or None), whether or not the
        new config was applied.

    Examples:
        ```python
        original = handler.apply(agent, "temperature: 0.5")
        # agent.generate_content_config.temperature is now 0.5
        ```

    Notes:
        On deserialization or validation failure, logs warning and
        keeps original config. Never raises exceptions.
    """
    original = getattr(agent, "generate_content_config", None)

    try:
        new_config = deserialize_generate_config(value, original)

        # Validate the parsed config
        config_dict = {}
        for param in [
            "temperature",
            "top_p",
            "top_k",
            "max_output_tokens",
            "presence_penalty",
            "frequency_penalty",
        ]:
            param_value = getattr(new_config, param, None)
            if param_value is not None:
                config_dict[param] = param_value

        errors = validate_generate_config(config_dict)
        if errors:
            logger.warning(
                "generate_content_config_handler.apply.validation_failed",
                errors=errors,
                config_preview=value[:100] if value else "",
            )
            # Keep original - don't modify agent
            return original

        agent.generate_content_config = new_config
        logger.debug(
            "generate_content_config_handler.apply",
            original_temp=getattr(original, "temperature", None)
            if original
            else None,
            new_temp=getattr(new_config, "temperature", None),
        )
    except ConfigValidationError as e:
        logger.warning(
            "generate_content_config_handler.apply.failed",
            error=str(e),
            config_preview=value[:100] if value else "",
        )
        # Keep original - don't modify agent

    return original

restore

restore(agent: LlmAgent, original: Any) -> None

Restore the generate_content_config that apply() returned.

PARAMETER DESCRIPTION
agent

The agent to restore.

TYPE: LlmAgent

original

The GenerateContentConfig apply() returned, or None.

TYPE: Any

Examples:

handler.restore(agent, original_config)
# agent.generate_content_config is back to original
Source code in src/gepa_adk/adapters/components/component_handlers.py
def restore(self, agent: LlmAgent, original: Any) -> None:
    """Restore the generate_content_config that apply() returned.

    Args:
        agent (LlmAgent): The agent to restore.
        original (Any): The GenerateContentConfig apply() returned, or None.

    Examples:
        ```python
        handler.restore(agent, original_config)
        # agent.generate_content_config is back to original
        ```
    """
    agent.generate_content_config = original
    logger.debug(
        "generate_content_config_handler.restore",
        has_config=original is not None,
    )

InstructionHandler

Handler for agent.instruction component.

Manages serialization, application, and restoration of the agent's instruction (system prompt) during evolution.

Examples:

handler = InstructionHandler()
original = handler.serialize(agent)  # "Be helpful"
handler.apply(agent, "Be concise")
# ... evaluate ...
handler.restore(agent, original)  # Back to "Be helpful"
Notes

All state is stored in the agent object - handler is stateless. No instance attributes are maintained.

Source code in src/gepa_adk/adapters/components/component_handlers.py
class InstructionHandler:
    """Handler for agent.instruction component.

    Manages serialization, application, and restoration of the
    agent's instruction (system prompt) during evolution.

    Examples:
        ```python
        handler = InstructionHandler()
        original = handler.serialize(agent)  # "Be helpful"
        handler.apply(agent, "Be concise")
        # ... evaluate ...
        handler.restore(agent, original)  # Back to "Be helpful"
        ```

    Notes:
        All state is stored in the agent object - handler is stateless.
        No instance attributes are maintained.
    """

    def serialize(self, agent: LlmAgent) -> str:
        """Extract instruction from agent as string.

        Args:
            agent (LlmAgent): The agent to read.

        Returns:
            The agent's instruction converted with ``str()``.
            Returns empty string if instruction is None.

        Examples:
            ```python
            text = handler.serialize(agent)
            # text == "You are a helpful assistant."
            ```
        """
        instruction = agent.instruction
        if instruction is None:
            return ""
        return str(instruction)

    def apply(self, agent: LlmAgent, value: str) -> str:
        """Apply new instruction to agent, return original.

        Args:
            agent (LlmAgent): The agent to modify.
            value (str): The new instruction string.

        Returns:
            The original instruction as serialize() renders it (empty string
            when it was None).

        Examples:
            ```python
            original = handler.apply(agent, "New instruction")
            # agent.instruction is now "New instruction"
            ```
        """
        original = self.serialize(agent)
        agent.instruction = value
        logger.debug(
            "instruction_handler.apply",
            original_preview=original[:50] if original else "",
            new_preview=value[:50] if value else "",
        )
        return original

    def restore(self, agent: LlmAgent, original: str) -> None:
        """Restore the instruction that apply() returned.

        Args:
            agent (LlmAgent): The agent to restore.
            original (str): The instruction apply() returned. An agent whose
                instruction was None comes back with an empty string.

        Examples:
            ```python
            handler.restore(agent, original)
            # agent.instruction is back to original
            ```
        """
        agent.instruction = original
        logger.debug(
            "instruction_handler.restore",
            instruction_preview=original[:50] if original else "",
        )

serialize

serialize(agent: LlmAgent) -> str

Extract instruction from agent as string.

PARAMETER DESCRIPTION
agent

The agent to read.

TYPE: LlmAgent

RETURNS DESCRIPTION
str

The agent's instruction converted with str().

str

Returns empty string if instruction is None.

Examples:

text = handler.serialize(agent)
# text == "You are a helpful assistant."
Source code in src/gepa_adk/adapters/components/component_handlers.py
def serialize(self, agent: LlmAgent) -> str:
    """Extract instruction from agent as string.

    Args:
        agent (LlmAgent): The agent to read.

    Returns:
        The agent's instruction converted with ``str()``.
        Returns empty string if instruction is None.

    Examples:
        ```python
        text = handler.serialize(agent)
        # text == "You are a helpful assistant."
        ```
    """
    instruction = agent.instruction
    if instruction is None:
        return ""
    return str(instruction)

apply

apply(agent: LlmAgent, value: str) -> str

Apply new instruction to agent, return original.

PARAMETER DESCRIPTION
agent

The agent to modify.

TYPE: LlmAgent

value

The new instruction string.

TYPE: str

RETURNS DESCRIPTION
str

The original instruction as serialize() renders it (empty string

str

when it was None).

Examples:

original = handler.apply(agent, "New instruction")
# agent.instruction is now "New instruction"
Source code in src/gepa_adk/adapters/components/component_handlers.py
def apply(self, agent: LlmAgent, value: str) -> str:
    """Apply new instruction to agent, return original.

    Args:
        agent (LlmAgent): The agent to modify.
        value (str): The new instruction string.

    Returns:
        The original instruction as serialize() renders it (empty string
        when it was None).

    Examples:
        ```python
        original = handler.apply(agent, "New instruction")
        # agent.instruction is now "New instruction"
        ```
    """
    original = self.serialize(agent)
    agent.instruction = value
    logger.debug(
        "instruction_handler.apply",
        original_preview=original[:50] if original else "",
        new_preview=value[:50] if value else "",
    )
    return original

restore

restore(agent: LlmAgent, original: str) -> None

Restore the instruction that apply() returned.

PARAMETER DESCRIPTION
agent

The agent to restore.

TYPE: LlmAgent

original

The instruction apply() returned. An agent whose instruction was None comes back with an empty string.

TYPE: str

Examples:

handler.restore(agent, original)
# agent.instruction is back to original
Source code in src/gepa_adk/adapters/components/component_handlers.py
def restore(self, agent: LlmAgent, original: str) -> None:
    """Restore the instruction that apply() returned.

    Args:
        agent (LlmAgent): The agent to restore.
        original (str): The instruction apply() returned. An agent whose
            instruction was None comes back with an empty string.

    Examples:
        ```python
        handler.restore(agent, original)
        # agent.instruction is back to original
        ```
    """
    agent.instruction = original
    logger.debug(
        "instruction_handler.restore",
        instruction_preview=original[:50] if original else "",
    )

OutputSchemaHandler

Handler for agent.output_schema component.

Manages serialization, application, and restoration of the agent's output schema (Pydantic model) during evolution.

ATTRIBUTE DESCRIPTION
_constraints

Optional SchemaConstraints for field preservation.

TYPE: SchemaConstraints | None

Examples:

handler = OutputSchemaHandler()
handler.set_constraints(SchemaConstraints(required_fields=("score",)))
original_schema = handler.apply(agent, new_schema_text)
# ... evaluate ...
handler.restore(agent, original_schema)
Notes

Applies serialize_pydantic_schema and deserialize_schema utilities. On invalid schema text, keeps original and logs warning. When constraints are set, validates proposed schemas before applying.

Source code in src/gepa_adk/adapters/components/component_handlers.py
class OutputSchemaHandler:
    """Handler for agent.output_schema component.

    Manages serialization, application, and restoration of the
    agent's output schema (Pydantic model) during evolution.

    Attributes:
        _constraints (SchemaConstraints | None): Optional SchemaConstraints for field preservation.

    Examples:
        ```python
        handler = OutputSchemaHandler()
        handler.set_constraints(SchemaConstraints(required_fields=("score",)))
        original_schema = handler.apply(agent, new_schema_text)
        # ... evaluate ...
        handler.restore(agent, original_schema)
        ```

    Notes:
        Applies serialize_pydantic_schema and deserialize_schema utilities.
        On invalid schema text, keeps original and logs warning.
        When constraints are set, validates proposed schemas before applying.
    """

    def __init__(self) -> None:
        """Initialize handler with no constraints.

        Examples:
            ```python
            handler = OutputSchemaHandler()
            assert handler._constraints is None
            ```
        """
        self._constraints: SchemaConstraints | None = None

    def set_constraints(self, constraints: SchemaConstraints | None) -> None:
        """Set schema constraints for field preservation.

        Args:
            constraints: SchemaConstraints specifying required fields and
                type preservation rules. Pass None to clear constraints.

        Examples:
            ```python
            handler.set_constraints(SchemaConstraints(required_fields=("score",)))
            handler.set_constraints(None)  # Clear constraints
            ```

        Notes:
            Once set, constraints are checked during apply() - proposed schemas
            that violate constraints will be rejected and the original kept.
        """
        self._constraints = constraints
        logger.debug(
            "output_schema_handler.set_constraints",
            has_constraints=constraints is not None,
            required_fields=constraints.required_fields if constraints else None,
        )

    def serialize(self, agent: LlmAgent) -> str:
        """Extract output schema from agent as Python source.

        Args:
            agent (LlmAgent): The agent to read.

        Returns:
            Python source code defining the schema class. Returns an empty
            string if output_schema is None or its source cannot be read
            (``TypeError`` or ``OSError``, logged as a warning).

        Examples:
            ```python
            schema_text = handler.serialize(agent)
            # schema_text contains Python class definition
            ```
        """
        output_schema = getattr(agent, "output_schema", None)
        if output_schema is None:
            return ""
        try:
            return serialize_pydantic_schema(output_schema)
        except (TypeError, OSError) as e:
            logger.warning(
                "output_schema_handler.serialize.failed",
                error=str(e),
                schema_type=type(output_schema).__name__,
            )
            return ""

    def apply(self, agent: LlmAgent, value: str) -> Any:
        """Apply new output schema to agent, return original.

        Args:
            agent (LlmAgent): The agent to modify.
            value (str): Python source code defining the new schema.

        Returns:
            The original output_schema (class or None), whether or not the new
            schema was applied.

        Examples:
            ```python
            original = handler.apply(
                agent,
                '''
            class NewSchema(BaseModel):
                result: str
            ''',
            )
            ```

        Notes:
            On deserialization failure, logs warning and keeps original.
            On constraint violation, keeps original.
            Never raises exceptions - graceful degradation.
        """
        original = getattr(agent, "output_schema", None)

        try:
            new_schema = deserialize_schema(value)

            # Validate against constraints if set
            if self._constraints is not None:
                is_valid, violations = validate_schema_against_constraints(
                    new_schema, original, self._constraints
                )
                if not is_valid:
                    logger.warning(
                        "output_schema_handler.apply.constraint_violation",
                        violations=violations,
                        schema_preview=value[:100] if value else "",
                    )
                    # Keep original - constraint violation
                    return original

            agent.output_schema = new_schema
            logger.debug(
                "output_schema_handler.apply",
                original_name=original.__name__ if original else None,
                new_name=new_schema.__name__,
            )
        except SchemaValidationError as e:
            logger.warning(
                "output_schema_handler.apply.failed",
                error=str(e),
                schema_preview=value[:100] if value else "",
            )
            # Keep original - don't modify agent

        return original

    def restore(self, agent: LlmAgent, original: Any) -> None:
        """Restore the output schema that apply() returned.

        Args:
            agent (LlmAgent): The agent to restore.
            original (Any): The schema class apply() returned, or None.

        Examples:
            ```python
            handler.restore(agent, original_schema)
            # agent.output_schema is back to original
            ```
        """
        agent.output_schema = original
        logger.debug(
            "output_schema_handler.restore",
            schema_name=original.__name__ if original else None,
        )

__init__

__init__() -> None

Initialize handler with no constraints.

Examples:

handler = OutputSchemaHandler()
assert handler._constraints is None
Source code in src/gepa_adk/adapters/components/component_handlers.py
def __init__(self) -> None:
    """Initialize handler with no constraints.

    Examples:
        ```python
        handler = OutputSchemaHandler()
        assert handler._constraints is None
        ```
    """
    self._constraints: SchemaConstraints | None = None

set_constraints

set_constraints(
    constraints: SchemaConstraints | None,
) -> None

Set schema constraints for field preservation.

PARAMETER DESCRIPTION
constraints

SchemaConstraints specifying required fields and type preservation rules. Pass None to clear constraints.

TYPE: SchemaConstraints | None

Examples:

handler.set_constraints(SchemaConstraints(required_fields=("score",)))
handler.set_constraints(None)  # Clear constraints
Notes

Once set, constraints are checked during apply() - proposed schemas that violate constraints will be rejected and the original kept.

Source code in src/gepa_adk/adapters/components/component_handlers.py
def set_constraints(self, constraints: SchemaConstraints | None) -> None:
    """Set schema constraints for field preservation.

    Args:
        constraints: SchemaConstraints specifying required fields and
            type preservation rules. Pass None to clear constraints.

    Examples:
        ```python
        handler.set_constraints(SchemaConstraints(required_fields=("score",)))
        handler.set_constraints(None)  # Clear constraints
        ```

    Notes:
        Once set, constraints are checked during apply() - proposed schemas
        that violate constraints will be rejected and the original kept.
    """
    self._constraints = constraints
    logger.debug(
        "output_schema_handler.set_constraints",
        has_constraints=constraints is not None,
        required_fields=constraints.required_fields if constraints else None,
    )

serialize

serialize(agent: LlmAgent) -> str

Extract output schema from agent as Python source.

PARAMETER DESCRIPTION
agent

The agent to read.

TYPE: LlmAgent

RETURNS DESCRIPTION
str

Python source code defining the schema class. Returns an empty

str

string if output_schema is None or its source cannot be read

str

(TypeError or OSError, logged as a warning).

Examples:

schema_text = handler.serialize(agent)
# schema_text contains Python class definition
Source code in src/gepa_adk/adapters/components/component_handlers.py
def serialize(self, agent: LlmAgent) -> str:
    """Extract output schema from agent as Python source.

    Args:
        agent (LlmAgent): The agent to read.

    Returns:
        Python source code defining the schema class. Returns an empty
        string if output_schema is None or its source cannot be read
        (``TypeError`` or ``OSError``, logged as a warning).

    Examples:
        ```python
        schema_text = handler.serialize(agent)
        # schema_text contains Python class definition
        ```
    """
    output_schema = getattr(agent, "output_schema", None)
    if output_schema is None:
        return ""
    try:
        return serialize_pydantic_schema(output_schema)
    except (TypeError, OSError) as e:
        logger.warning(
            "output_schema_handler.serialize.failed",
            error=str(e),
            schema_type=type(output_schema).__name__,
        )
        return ""

apply

apply(agent: LlmAgent, value: str) -> Any

Apply new output schema to agent, return original.

PARAMETER DESCRIPTION
agent

The agent to modify.

TYPE: LlmAgent

value

Python source code defining the new schema.

TYPE: str

RETURNS DESCRIPTION
Any

The original output_schema (class or None), whether or not the new

Any

schema was applied.

Examples:

original = handler.apply(
    agent,
    '''
class NewSchema(BaseModel):
    result: str
''',
)
Notes

On deserialization failure, logs warning and keeps original. On constraint violation, keeps original. Never raises exceptions - graceful degradation.

Source code in src/gepa_adk/adapters/components/component_handlers.py
def apply(self, agent: LlmAgent, value: str) -> Any:
    """Apply new output schema to agent, return original.

    Args:
        agent (LlmAgent): The agent to modify.
        value (str): Python source code defining the new schema.

    Returns:
        The original output_schema (class or None), whether or not the new
        schema was applied.

    Examples:
        ```python
        original = handler.apply(
            agent,
            '''
        class NewSchema(BaseModel):
            result: str
        ''',
        )
        ```

    Notes:
        On deserialization failure, logs warning and keeps original.
        On constraint violation, keeps original.
        Never raises exceptions - graceful degradation.
    """
    original = getattr(agent, "output_schema", None)

    try:
        new_schema = deserialize_schema(value)

        # Validate against constraints if set
        if self._constraints is not None:
            is_valid, violations = validate_schema_against_constraints(
                new_schema, original, self._constraints
            )
            if not is_valid:
                logger.warning(
                    "output_schema_handler.apply.constraint_violation",
                    violations=violations,
                    schema_preview=value[:100] if value else "",
                )
                # Keep original - constraint violation
                return original

        agent.output_schema = new_schema
        logger.debug(
            "output_schema_handler.apply",
            original_name=original.__name__ if original else None,
            new_name=new_schema.__name__,
        )
    except SchemaValidationError as e:
        logger.warning(
            "output_schema_handler.apply.failed",
            error=str(e),
            schema_preview=value[:100] if value else "",
        )
        # Keep original - don't modify agent

    return original

restore

restore(agent: LlmAgent, original: Any) -> None

Restore the output schema that apply() returned.

PARAMETER DESCRIPTION
agent

The agent to restore.

TYPE: LlmAgent

original

The schema class apply() returned, or None.

TYPE: Any

Examples:

handler.restore(agent, original_schema)
# agent.output_schema is back to original
Source code in src/gepa_adk/adapters/components/component_handlers.py
def restore(self, agent: LlmAgent, original: Any) -> None:
    """Restore the output schema that apply() returned.

    Args:
        agent (LlmAgent): The agent to restore.
        original (Any): The schema class apply() returned, or None.

    Examples:
        ```python
        handler.restore(agent, original_schema)
        # agent.output_schema is back to original
        ```
    """
    agent.output_schema = original
    logger.debug(
        "output_schema_handler.restore",
        schema_name=original.__name__ if original else None,
    )

MappingComponentHandler

Handler for one string value in a mapping the caller owns.

Serializes, applies and restores mapping[key]. The agent argument of each method is ignored, because the value does not live on the agent.

ATTRIBUTE DESCRIPTION
mapping

The caller's mapping, held by reference so writes are visible to its other readers.

TYPE: MutableMapping[str, str]

key

The mapping key this handler evolves.

TYPE: str

Examples:

prompts = {"greeting": "hello"}
handler = MappingComponentHandler(prompts, key="greeting")
original = handler.apply(None, "hi")  # prompts["greeting"] == "hi"
handler.restore(None, original)  # prompts["greeting"] == "hello"
Notes

The handler keeps a reference to the mapping, not a copy. Replacing the mapping object elsewhere detaches the handler from it.

Source code in src/gepa_adk/adapters/components/mapping_handler.py
class MappingComponentHandler:
    """Handler for one string value in a mapping the caller owns.

    Serializes, applies and restores ``mapping[key]``. The agent argument of
    each method is ignored, because the value does not live on the agent.

    Attributes:
        mapping (MutableMapping[str, str]): The caller's mapping, held by
            reference so writes are visible to its other readers.
        key (str): The mapping key this handler evolves.

    Examples:
        ```python
        prompts = {"greeting": "hello"}
        handler = MappingComponentHandler(prompts, key="greeting")
        original = handler.apply(None, "hi")  # prompts["greeting"] == "hi"
        handler.restore(None, original)  # prompts["greeting"] == "hello"
        ```

    Notes:
        The handler keeps a reference to the mapping, not a copy. Replacing
        the mapping object elsewhere detaches the handler from it.
    """

    def __init__(self, mapping: MutableMapping[str, str], key: str) -> None:
        """Bind the handler to one key of the caller's mapping.

        Args:
            mapping: The caller-owned mapping holding the prompt text.
            key: The key whose value this handler evolves.

        Raises:
            ValueError: If ``key`` is not in ``mapping``.
            TypeError: If ``mapping[key]`` is not a ``str``.

        Examples:
            ```python
            handler = MappingComponentHandler({"k": "v"}, key="k")
            ```
        """
        if key not in mapping:
            raise ValueError(f"Mapping has no key {key!r} to evolve")
        value: object = mapping[key]
        if not isinstance(value, str):
            raise TypeError(
                f"Mapping value for key {key!r} must be str, got {type(value).__name__}"
            )
        self.mapping = mapping
        self.key = key

    def serialize(self, agent: Any) -> str:
        """Return the current text of the mapping key.

        Args:
            agent: Ignored; may be ``None``.

        Returns:
            The value of ``mapping[key]``, or an empty string when the caller
            has removed the key or replaced its value with non-text since
            registration.

        Examples:
            ```python
            text = handler.serialize(None)
            ```

        Notes:
            Never raises, per the ``ComponentHandler`` contract. A missing or
            non-text value is logged as ``mapping_handler.key_missing``.
        """
        value = self.mapping.get(self.key)
        if not isinstance(value, str):
            logger.warning(
                "mapping_handler.key_missing",
                key=self.key,
                value_type=type(value).__name__,
            )
            return ""
        return value

    def apply(self, agent: Any, value: str) -> str:
        """Write new text to the mapping key and return the previous text.

        Args:
            agent: Ignored; may be ``None``.
            value: The new text for the key.

        Returns:
            The text the key held before this call.

        Examples:
            ```python
            original = handler.apply(None, "new text")
            ```
        """
        original = self.mapping[self.key]
        self.mapping[self.key] = value
        logger.debug(
            "mapping_handler.apply",
            key=self.key,
            original_preview=original[:50],
            new_preview=value[:50],
        )
        return original

    def restore(self, agent: Any, original: str) -> None:
        """Write the original text back to the mapping key.

        Args:
            agent: Ignored; may be ``None``.
            original: The text returned by ``apply()``.

        Examples:
            ```python
            handler.restore(None, original)
            ```
        """
        self.mapping[self.key] = original
        logger.debug("mapping_handler.restore", key=self.key, preview=original[:50])

__init__

__init__(
    mapping: MutableMapping[str, str], key: str
) -> None

Bind the handler to one key of the caller's mapping.

PARAMETER DESCRIPTION
mapping

The caller-owned mapping holding the prompt text.

TYPE: MutableMapping[str, str]

key

The key whose value this handler evolves.

TYPE: str

RAISES DESCRIPTION
ValueError

If key is not in mapping.

TypeError

If mapping[key] is not a str.

Examples:

handler = MappingComponentHandler({"k": "v"}, key="k")
Source code in src/gepa_adk/adapters/components/mapping_handler.py
def __init__(self, mapping: MutableMapping[str, str], key: str) -> None:
    """Bind the handler to one key of the caller's mapping.

    Args:
        mapping: The caller-owned mapping holding the prompt text.
        key: The key whose value this handler evolves.

    Raises:
        ValueError: If ``key`` is not in ``mapping``.
        TypeError: If ``mapping[key]`` is not a ``str``.

    Examples:
        ```python
        handler = MappingComponentHandler({"k": "v"}, key="k")
        ```
    """
    if key not in mapping:
        raise ValueError(f"Mapping has no key {key!r} to evolve")
    value: object = mapping[key]
    if not isinstance(value, str):
        raise TypeError(
            f"Mapping value for key {key!r} must be str, got {type(value).__name__}"
        )
    self.mapping = mapping
    self.key = key

serialize

serialize(agent: Any) -> str

Return the current text of the mapping key.

PARAMETER DESCRIPTION
agent

Ignored; may be None.

TYPE: Any

RETURNS DESCRIPTION
str

The value of mapping[key], or an empty string when the caller

str

has removed the key or replaced its value with non-text since

str

registration.

Examples:

text = handler.serialize(None)
Notes

Never raises, per the ComponentHandler contract. A missing or non-text value is logged as mapping_handler.key_missing.

Source code in src/gepa_adk/adapters/components/mapping_handler.py
def serialize(self, agent: Any) -> str:
    """Return the current text of the mapping key.

    Args:
        agent: Ignored; may be ``None``.

    Returns:
        The value of ``mapping[key]``, or an empty string when the caller
        has removed the key or replaced its value with non-text since
        registration.

    Examples:
        ```python
        text = handler.serialize(None)
        ```

    Notes:
        Never raises, per the ``ComponentHandler`` contract. A missing or
        non-text value is logged as ``mapping_handler.key_missing``.
    """
    value = self.mapping.get(self.key)
    if not isinstance(value, str):
        logger.warning(
            "mapping_handler.key_missing",
            key=self.key,
            value_type=type(value).__name__,
        )
        return ""
    return value

apply

apply(agent: Any, value: str) -> str

Write new text to the mapping key and return the previous text.

PARAMETER DESCRIPTION
agent

Ignored; may be None.

TYPE: Any

value

The new text for the key.

TYPE: str

RETURNS DESCRIPTION
str

The text the key held before this call.

Examples:

original = handler.apply(None, "new text")
Source code in src/gepa_adk/adapters/components/mapping_handler.py
def apply(self, agent: Any, value: str) -> str:
    """Write new text to the mapping key and return the previous text.

    Args:
        agent: Ignored; may be ``None``.
        value: The new text for the key.

    Returns:
        The text the key held before this call.

    Examples:
        ```python
        original = handler.apply(None, "new text")
        ```
    """
    original = self.mapping[self.key]
    self.mapping[self.key] = value
    logger.debug(
        "mapping_handler.apply",
        key=self.key,
        original_preview=original[:50],
        new_preview=value[:50],
    )
    return original

restore

restore(agent: Any, original: str) -> None

Write the original text back to the mapping key.

PARAMETER DESCRIPTION
agent

Ignored; may be None.

TYPE: Any

original

The text returned by apply().

TYPE: str

Examples:

handler.restore(None, original)
Source code in src/gepa_adk/adapters/components/mapping_handler.py
def restore(self, agent: Any, original: str) -> None:
    """Write the original text back to the mapping key.

    Args:
        agent: Ignored; may be ``None``.
        original: The text returned by ``apply()``.

    Examples:
        ```python
        handler.restore(None, original)
        ```
    """
    self.mapping[self.key] = original
    logger.debug("mapping_handler.restore", key=self.key, preview=original[:50])

get_handler

get_handler(name: str) -> ComponentHandler

Get handler from default registry.

PARAMETER DESCRIPTION
name

Component name to look up.

TYPE: str

RETURNS DESCRIPTION
ComponentHandler

The registered ComponentHandler.

RAISES DESCRIPTION
ValueError

If name is empty or None.

KeyError

If no handler registered for name.

Examples:

handler = get_handler("instruction")
original = handler.apply(agent, "New instruction")
See Also
Notes

Shortcut for component_handlers.get(name).

Source code in src/gepa_adk/adapters/components/component_handlers.py
def get_handler(name: str) -> ComponentHandler:
    """Get handler from default registry.

    Args:
        name: Component name to look up.

    Returns:
        The registered ComponentHandler.

    Raises:
        ValueError: If name is empty or None.
        KeyError: If no handler registered for name.

    Examples:
        ```python
        handler = get_handler("instruction")
        original = handler.apply(agent, "New instruction")
        ```

    See Also:
        - [gepa_adk.adapters.components.component_handlers.ComponentHandlerRegistry.get][]:
          Underlying registry method.

    Notes:
        Shortcut for component_handlers.get(name).
    """
    return component_handlers.get(name)

register_handler

register_handler(
    name: str, handler: ComponentHandler
) -> None

Register handler in default registry.

PARAMETER DESCRIPTION
name

Component name to register.

TYPE: str

handler

Handler implementing ComponentHandler protocol.

TYPE: ComponentHandler

RAISES DESCRIPTION
ValueError

If name is empty or None.

TypeError

If handler doesn't implement ComponentHandler protocol.

Examples:

register_handler("my_component", MyHandler())
handler = get_handler("my_component")
See Also
Notes

Shortcut for component_handlers.register(name, handler).

Source code in src/gepa_adk/adapters/components/component_handlers.py
def register_handler(name: str, handler: ComponentHandler) -> None:
    """Register handler in default registry.

    Args:
        name: Component name to register.
        handler: Handler implementing ComponentHandler protocol.

    Raises:
        ValueError: If name is empty or None.
        TypeError: If handler doesn't implement ComponentHandler protocol.

    Examples:
        ```python
        register_handler("my_component", MyHandler())
        handler = get_handler("my_component")
        ```

    See Also:
        - [gepa_adk.adapters.components.component_handlers.ComponentHandlerRegistry.register][]:
          Underlying registry method.

    Notes:
        Shortcut for component_handlers.register(name, handler).
    """
    component_handlers.register(name, handler)

register_mapping_components

register_mapping_components(
    mapping: MutableMapping[str, str],
    *,
    registry: ComponentHandlerRegistry | None = None,
) -> list[str]

Register a handler for every key of a mapping.

Each key becomes a component name backed by a MappingComponentHandler. Every key is validated before any handler is registered, so a rejected mapping leaves the registry unchanged.

PARAMETER DESCRIPTION
mapping

The caller-owned mapping of prompt strings.

TYPE: MutableMapping[str, str]

registry

The registry to register into. Defaults to the default component_handlers registry that evolve() consults.

TYPE: ComponentHandlerRegistry | None DEFAULT: None

RETURNS DESCRIPTION
list[str]

The registered component names, in mapping order.

RAISES DESCRIPTION
ValueError

If the mapping is empty, a key is not a Python identifier, or a key equals instruction or output_schema.

TypeError

If a value is not a str.

Examples:

prompts = {"greeting": "hello", "farewell": "bye"}
names = register_mapping_components(prompts)
# names == ["greeting", "farewell"]
See Also
Notes

Registering a name that already has a handler replaces it.

Source code in src/gepa_adk/adapters/components/mapping_handler.py
def register_mapping_components(
    mapping: MutableMapping[str, str],
    *,
    registry: ComponentHandlerRegistry | None = None,
) -> list[str]:
    """Register a handler for every key of a mapping.

    Each key becomes a component name backed by a
    ``MappingComponentHandler``. Every key is validated before any handler
    is registered, so a rejected mapping leaves the registry unchanged.

    Args:
        mapping: The caller-owned mapping of prompt strings.
        registry: The registry to register into. Defaults to the default
            ``component_handlers`` registry that ``evolve()`` consults.

    Returns:
        The registered component names, in mapping order.

    Raises:
        ValueError: If the mapping is empty, a key is not a Python
            identifier, or a key equals ``instruction`` or ``output_schema``.
        TypeError: If a value is not a ``str``.

    Examples:
        ```python
        prompts = {"greeting": "hello", "farewell": "bye"}
        names = register_mapping_components(prompts)
        # names == ["greeting", "farewell"]
        ```

    See Also:
        - [`MappingComponentHandler`][gepa_adk.adapters.components.MappingComponentHandler]:
          The handler registered for each key.

    Notes:
        Registering a name that already has a handler replaces it.
    """
    if not mapping:
        raise ValueError("Cannot register components from an empty mapping")
    handlers: list[tuple[str, MappingComponentHandler]] = []
    for key in mapping:
        if not key.isidentifier():
            raise ValueError(
                f"Mapping key {key!r} must be a Python identifier to be a "
                "component name"
            )
        if key in _RESERVED_NAMES:
            raise ValueError(f"Mapping key {key!r} shadows a built-in component name")
        handlers.append((key, MappingComponentHandler(mapping, key)))

    target = registry if registry is not None else component_handlers
    for key, handler in handlers:
        target.register(key, handler)
    return [key for key, _ in handlers]