alignscope 0.1.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.
- alignscope/__init__.py +150 -0
- alignscope/_frontend/css/style.css +663 -0
- alignscope/_frontend/index.html +169 -0
- alignscope/_frontend/js/app.js +360 -0
- alignscope/_frontend/js/metrics.js +220 -0
- alignscope/_frontend/js/timeline.js +494 -0
- alignscope/_frontend/js/topology.js +368 -0
- alignscope/adapters.py +169 -0
- alignscope/cli.py +99 -0
- alignscope/detector.py +242 -0
- alignscope/integrations/__init__.py +28 -0
- alignscope/integrations/mlflow_bridge.py +70 -0
- alignscope/integrations/wandb_bridge.py +81 -0
- alignscope/metrics.py +383 -0
- alignscope/patches/__init__.py +50 -0
- alignscope/patches/pettingzoo.py +332 -0
- alignscope/patches/pymarl.py +277 -0
- alignscope/patches/rllib.py +170 -0
- alignscope/sdk.py +606 -0
- alignscope/server.py +298 -0
- alignscope/simulator.py +493 -0
- alignscope-0.1.0.dist-info/METADATA +183 -0
- alignscope-0.1.0.dist-info/RECORD +26 -0
- alignscope-0.1.0.dist-info/WHEEL +4 -0
- alignscope-0.1.0.dist-info/entry_points.txt +2 -0
- alignscope-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AlignScope — RLlib Auto-Patch
|
|
3
|
+
|
|
4
|
+
Provides an AlignScopeCallback for Ray RLlib that automatically
|
|
5
|
+
logs multi-agent data to AlignScope on every episode step.
|
|
6
|
+
|
|
7
|
+
Tier 1 (Zero Code):
|
|
8
|
+
alignscope patch rllib
|
|
9
|
+
python train.py # zero changes
|
|
10
|
+
|
|
11
|
+
Tier 3 (Explicit Plugin):
|
|
12
|
+
from alignscope.patches.rllib import AlignScopeCallback
|
|
13
|
+
config = PPOConfig().callbacks(AlignScopeCallback)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
_original_callbacks = None
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
import ray
|
|
20
|
+
RAY_VERSION = tuple(map(int, ray.__version__.split(".")[:2]))
|
|
21
|
+
if RAY_VERSION[0] >= 2:
|
|
22
|
+
from ray.rllib.algorithms.callbacks import DefaultCallbacks
|
|
23
|
+
BaseCallback = DefaultCallbacks
|
|
24
|
+
else:
|
|
25
|
+
# RLlib 1.x path
|
|
26
|
+
from ray.rllib.agents.callbacks import DefaultCallbacks
|
|
27
|
+
BaseCallback = DefaultCallbacks
|
|
28
|
+
except (ImportError, AttributeError, ValueError):
|
|
29
|
+
BaseCallback = object
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AlignScopeCallback(BaseCallback):
|
|
33
|
+
"""
|
|
34
|
+
RLlib callback that streams agent data to AlignScope.
|
|
35
|
+
|
|
36
|
+
Works with both Ray RLlib 1.x and 2.x APIs using version-aware static inheritance.
|
|
37
|
+
Gracefully degrades if RLlib is not installed.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, *args, **kwargs):
|
|
41
|
+
if BaseCallback is not object:
|
|
42
|
+
super().__init__(*args, **kwargs)
|
|
43
|
+
|
|
44
|
+
import alignscope
|
|
45
|
+
if alignscope._tracker is None:
|
|
46
|
+
alignscope.init(project="rllib-run")
|
|
47
|
+
|
|
48
|
+
self._tracker = alignscope._tracker
|
|
49
|
+
self._step = 0
|
|
50
|
+
|
|
51
|
+
def on_episode_step(
|
|
52
|
+
self,
|
|
53
|
+
*,
|
|
54
|
+
worker=None,
|
|
55
|
+
base_env=None,
|
|
56
|
+
policies=None,
|
|
57
|
+
episode=None,
|
|
58
|
+
env_index=None,
|
|
59
|
+
**kwargs,
|
|
60
|
+
):
|
|
61
|
+
"""Called on every step of every episode."""
|
|
62
|
+
self._step += 1
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
agents = []
|
|
66
|
+
actions = {}
|
|
67
|
+
rewards = {}
|
|
68
|
+
|
|
69
|
+
# Extract agent data from the episode
|
|
70
|
+
if episode is not None:
|
|
71
|
+
for agent_id in episode.get_agents():
|
|
72
|
+
last_action = episode.last_action_for(agent_id)
|
|
73
|
+
last_reward = episode.last_reward_for(agent_id)
|
|
74
|
+
last_obs = episode.last_observation_for(agent_id)
|
|
75
|
+
|
|
76
|
+
# Determine team from agent_id naming convention
|
|
77
|
+
team = self._infer_team(agent_id)
|
|
78
|
+
|
|
79
|
+
import math
|
|
80
|
+
all_agents = list(episode.get_agents())
|
|
81
|
+
a_idx = all_agents.index(agent_id) if agent_id in all_agents else 0
|
|
82
|
+
n_agents = len(all_agents)
|
|
83
|
+
|
|
84
|
+
agents.append({
|
|
85
|
+
"agent_id": str(agent_id),
|
|
86
|
+
"team": team,
|
|
87
|
+
"role": "agent",
|
|
88
|
+
"x": round(math.cos(2 * math.pi * a_idx / max(1, n_agents)) * 150, 2),
|
|
89
|
+
"y": round(math.sin(2 * math.pi * a_idx / max(1, n_agents)) * 150, 2),
|
|
90
|
+
"resources": 0,
|
|
91
|
+
"hearts": 0,
|
|
92
|
+
"energy": float(last_reward) if last_reward is not None else 0,
|
|
93
|
+
"is_defector": False,
|
|
94
|
+
"coalition_id": team,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
actions[str(agent_id)] = str(last_action)
|
|
98
|
+
rewards[str(agent_id)] = float(last_reward) if last_reward is not None else 0
|
|
99
|
+
|
|
100
|
+
self._tracker.log(
|
|
101
|
+
step=self._step,
|
|
102
|
+
agents=agents,
|
|
103
|
+
actions=actions,
|
|
104
|
+
rewards=rewards,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
# Never crash the training run
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
def on_episode_start(self, *, episode=None, **kwargs):
|
|
112
|
+
"""Reset step counter at episode start."""
|
|
113
|
+
self._step = 0
|
|
114
|
+
|
|
115
|
+
def on_episode_end(self, *, episode=None, **kwargs):
|
|
116
|
+
"""Log episode completion."""
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def _infer_team(agent_id) -> int:
|
|
121
|
+
"""Infer team from agent ID naming patterns."""
|
|
122
|
+
agent_str = str(agent_id).lower()
|
|
123
|
+
# Common patterns: "agent_0", "team_1_agent_2", "red_0", etc.
|
|
124
|
+
if "team_1" in agent_str or "blue" in agent_str or "enemy" in agent_str:
|
|
125
|
+
return 1
|
|
126
|
+
if "team_2" in agent_str:
|
|
127
|
+
return 2
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def apply():
|
|
132
|
+
"""
|
|
133
|
+
Auto-patch RLlib to use AlignScope callbacks.
|
|
134
|
+
|
|
135
|
+
This monkey-patches the default callback class so existing
|
|
136
|
+
training code works without any changes.
|
|
137
|
+
"""
|
|
138
|
+
global _original_callbacks
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
|
|
142
|
+
|
|
143
|
+
# Store original
|
|
144
|
+
_original_callbacks = AlgorithmConfig.callbacks
|
|
145
|
+
|
|
146
|
+
# Monkey-patch the default
|
|
147
|
+
original_callbacks_method = AlgorithmConfig.callbacks
|
|
148
|
+
|
|
149
|
+
def patched_callbacks(self, callbacks_class=None):
|
|
150
|
+
if callbacks_class is None:
|
|
151
|
+
callbacks_class = AlignScopeCallback
|
|
152
|
+
return original_callbacks_method.fget(self)
|
|
153
|
+
|
|
154
|
+
# Override the property/method
|
|
155
|
+
try:
|
|
156
|
+
AlgorithmConfig.callbacks = property(
|
|
157
|
+
lambda self: AlignScopeCallback,
|
|
158
|
+
original_callbacks_method.fset if hasattr(original_callbacks_method, 'fset') else None,
|
|
159
|
+
)
|
|
160
|
+
except (TypeError, AttributeError):
|
|
161
|
+
# Fallback: just inform user to use the callback manually
|
|
162
|
+
print("[AlignScope] Auto-patch applied. Use AlignScopeCallback in your config.")
|
|
163
|
+
|
|
164
|
+
print("[AlignScope] ✓ RLlib patched successfully")
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
except ImportError:
|
|
168
|
+
raise ImportError(
|
|
169
|
+
"RLlib is not installed. Install with: pip install 'alignscope[rllib]'"
|
|
170
|
+
)
|