File size: 3,851 Bytes
8c486a8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7fedc25
8c486a8
7fedc25
8c486a8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""Dynamic component resolution for OpenRange.

Loads Builder, NPC, and Validator implementations at startup from
dotted class paths specified in YAML config.
"""

from __future__ import annotations

import importlib
import inspect
from pathlib import Path
from typing import Any, Type

import yaml

from open_range.protocols import (
    NPCBehavior,
    SnapshotBuilder,
    ValidatorCheck,
)

# Default validator checks when none specified in config
DEFAULT_CHECKS: list[dict[str, Any]] = [
    {"class": "open_range.validator.build_boot.BuildBootCheck"},
    {"class": "open_range.validator.exploitability.ExploitabilityCheck"},
    {"class": "open_range.validator.patchability.PatchabilityCheck"},
    {"class": "open_range.validator.evidence.EvidenceCheck"},
    {"class": "open_range.validator.reward_grounding.RewardGroundingCheck"},
    {"class": "open_range.validator.isolation.IsolationCheck"},
]


def _missing_methods(instance: Any, protocol: Type) -> list[str]:
    """Return protocol method names missing from instance."""
    missing = []
    for name, method in inspect.getmembers(protocol, predicate=inspect.isfunction):
        if name.startswith("_"):
            continue
        if not hasattr(instance, name) or not callable(getattr(instance, name)):
            missing.append(name)
    return missing


def resolve_component(class_path: str, kwargs: dict, protocol: Type) -> Any:
    """Import class by dotted path, instantiate, verify protocol compliance.

    Args:
        class_path: Dotted Python class path (e.g. "open_range.builder.builder.LLMSnapshotBuilder")
        kwargs: Constructor keyword arguments
        protocol: Protocol class to check against

    Returns:
        Instantiated component satisfying the protocol.

    Raises:
        TypeError: If the class doesn't satisfy the protocol.
        ImportError: If the module can't be imported.
        AttributeError: If the class doesn't exist in the module.
    """
    module_name, _, class_name = class_path.rpartition(".")
    module = importlib.import_module(module_name)
    cls = getattr(module, class_name)
    instance = cls(**kwargs)
    if not isinstance(instance, protocol):
        missing = _missing_methods(instance, protocol)
        raise TypeError(
            f"{class_path} does not satisfy {protocol.__name__} protocol. "
            f"Missing methods: {missing}"
        )
    return instance


def load_agent_config(config_path: str) -> dict:
    """Load agent configuration from YAML file.

    Returns the 'agents' block from the config, or empty dict.
    """
    path = Path(config_path)
    if not path.exists():
        return {}
    with open(path) as f:
        config = yaml.safe_load(f) or {}
    return config.get("agents", {})


def build_components(
    config: dict,
) -> tuple[SnapshotBuilder, NPCBehavior, list[ValidatorCheck]]:
    """Resolve all infrastructure components from config dict.

    Args:
        config: The 'agents' block from openrange.yaml

    Returns:
        (builder, npc_behavior, validator_checks) tuple
    """
    builder_cfg = config.get("builder", {})
    builder = resolve_component(
        builder_cfg.get("class", "open_range.builder.builder.LLMSnapshotBuilder"),
        builder_cfg.get("kwargs", {}),
        SnapshotBuilder,
    )

    npc_cfg = config.get("npc_behavior", {})
    npc = resolve_component(
        npc_cfg.get("class", "open_range.builder.npc.npc_agent.NullNPCBehavior"),
        npc_cfg.get("kwargs", {}),
        NPCBehavior,
    )

    checks: list[ValidatorCheck] = []
    for check_cfg in config.get("validator_checks", DEFAULT_CHECKS):
        checks.append(
            resolve_component(
                check_cfg["class"],
                check_cfg.get("kwargs", {}),
                ValidatorCheck,
            )
        )

    return builder, npc, checks