unaiverse 0.1.12__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.
Files changed (47) hide show
  1. unaiverse/__init__.py +19 -0
  2. unaiverse/agent.py +2226 -0
  3. unaiverse/agent_basics.py +2389 -0
  4. unaiverse/clock.py +234 -0
  5. unaiverse/dataprops.py +1282 -0
  6. unaiverse/hsm.py +2471 -0
  7. unaiverse/modules/__init__.py +18 -0
  8. unaiverse/modules/cnu/__init__.py +17 -0
  9. unaiverse/modules/cnu/cnus.py +536 -0
  10. unaiverse/modules/cnu/layers.py +261 -0
  11. unaiverse/modules/cnu/psi.py +60 -0
  12. unaiverse/modules/hl/__init__.py +15 -0
  13. unaiverse/modules/hl/hl_utils.py +411 -0
  14. unaiverse/modules/networks.py +1509 -0
  15. unaiverse/modules/utils.py +748 -0
  16. unaiverse/networking/__init__.py +16 -0
  17. unaiverse/networking/node/__init__.py +18 -0
  18. unaiverse/networking/node/connpool.py +1332 -0
  19. unaiverse/networking/node/node.py +2752 -0
  20. unaiverse/networking/node/profile.py +446 -0
  21. unaiverse/networking/node/tokens.py +79 -0
  22. unaiverse/networking/p2p/__init__.py +188 -0
  23. unaiverse/networking/p2p/go.mod +127 -0
  24. unaiverse/networking/p2p/go.sum +548 -0
  25. unaiverse/networking/p2p/golibp2p.py +18 -0
  26. unaiverse/networking/p2p/golibp2p.pyi +136 -0
  27. unaiverse/networking/p2p/lib.go +2765 -0
  28. unaiverse/networking/p2p/lib_types.py +311 -0
  29. unaiverse/networking/p2p/message_pb2.py +50 -0
  30. unaiverse/networking/p2p/messages.py +360 -0
  31. unaiverse/networking/p2p/mylogger.py +78 -0
  32. unaiverse/networking/p2p/p2p.py +900 -0
  33. unaiverse/networking/p2p/proto-go/message.pb.go +846 -0
  34. unaiverse/stats.py +1506 -0
  35. unaiverse/streamlib/__init__.py +15 -0
  36. unaiverse/streamlib/streamlib.py +210 -0
  37. unaiverse/streams.py +804 -0
  38. unaiverse/utils/__init__.py +16 -0
  39. unaiverse/utils/lone_wolf.json +28 -0
  40. unaiverse/utils/misc.py +441 -0
  41. unaiverse/utils/sandbox.py +292 -0
  42. unaiverse/world.py +384 -0
  43. unaiverse-0.1.12.dist-info/METADATA +366 -0
  44. unaiverse-0.1.12.dist-info/RECORD +47 -0
  45. unaiverse-0.1.12.dist-info/WHEEL +5 -0
  46. unaiverse-0.1.12.dist-info/licenses/LICENSE +177 -0
  47. unaiverse-0.1.12.dist-info/top_level.txt +1 -0
@@ -0,0 +1,360 @@
1
+ """
2
+ █████ █████ ██████ █████ █████ █████ █████ ██████████ ███████████ █████████ ██████████
3
+ ░░███ ░░███ ░░██████ ░░███ ░░███ ░░███ ░░███ ░░███░░░░░█░░███░░░░░███ ███░░░░░███░░███░░░░░█
4
+ ░███ ░███ ░███░███ ░███ ██████ ░███ ░███ ░███ ░███ █ ░ ░███ ░███ ░███ ░░░ ░███ █ ░
5
+ ░███ ░███ ░███░░███░███ ░░░░░███ ░███ ░███ ░███ ░██████ ░██████████ ░░█████████ ░██████
6
+ ░███ ░███ ░███ ░░██████ ███████ ░███ ░░███ ███ ░███░░█ ░███░░░░░███ ░░░░░░░░███ ░███░░█
7
+ ░███ ░███ ░███ ░░█████ ███░░███ ░███ ░░░█████░ ░███ ░ █ ░███ ░███ ███ ░███ ░███ ░ █
8
+ ░░████████ █████ ░░█████░░████████ █████ ░░███ ██████████ █████ █████░░█████████ ██████████
9
+ ░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░░ ░░░ ░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░
10
+ A Collectionless AI Project (https://collectionless.ai)
11
+ Registration/Login: https://unaiverse.io
12
+ Code Repositories: https://github.com/collectionlessai/
13
+ Main Developers: Stefano Melacci (Project Leader), Christian Di Maio, Tommaso Guidi
14
+ """
15
+ import io
16
+ import gzip
17
+ import json
18
+ import torch
19
+ from PIL import Image
20
+ from typing import Any
21
+ from datetime import datetime, timezone
22
+ from unaiverse.dataprops import FileContainer
23
+ from google.protobuf.json_format import MessageToDict, ParseDict
24
+ from google.protobuf.struct_pb2 import Value, ListValue, NULL_VALUE
25
+
26
+ # Import the Protobuf-generated module
27
+ try:
28
+ from . import message_pb2 as pb
29
+ except ImportError:
30
+ print("Error: message_pb2.py not found. Please compile the .proto file first.")
31
+ raise
32
+
33
+
34
+ class Msg:
35
+
36
+ # Message content types
37
+ PROFILE = "profile"
38
+ WORLD_APPROVAL = "world_approval"
39
+ AGENT_APPROVAL = "agent_approval"
40
+ PROFILE_REQUEST = "profile_request"
41
+ ADDRESS_UPDATE = "address_update"
42
+ STREAM_SAMPLE = "stream_sample"
43
+ ACTION_REQUEST = "action_request"
44
+ ROLE_SUGGESTION = "role_suggestion"
45
+ HSM = "hsm"
46
+ MISC = "misc"
47
+ GET_CV_FROM_ROOT = "get_cv_from_root"
48
+ BADGE_SUGGESTIONS = "badge_suggestions"
49
+ INSPECT_ON = "inspect_on"
50
+ INSPECT_CMD = "inspect_cmd"
51
+ WORLD_AGENTS_LIST = "world_agents_list"
52
+ CONSOLE_AND_BEHAV_STATUS = "console_and_behav_status"
53
+ STATS_UPDATE = "stats_update" # agent -> world
54
+ STATS_REQUEST = "stats_request"
55
+ STATS_RESPONSE = 'stats_response' # world -> agent
56
+
57
+ # Collections
58
+ CONTENT_TYPES = {PROFILE, WORLD_APPROVAL, AGENT_APPROVAL, PROFILE_REQUEST, ADDRESS_UPDATE,
59
+ STREAM_SAMPLE, ACTION_REQUEST, ROLE_SUGGESTION, HSM, MISC, GET_CV_FROM_ROOT,
60
+ BADGE_SUGGESTIONS, INSPECT_ON, INSPECT_CMD, WORLD_AGENTS_LIST, CONSOLE_AND_BEHAV_STATUS,
61
+ STATS_UPDATE, STATS_REQUEST, STATS_RESPONSE}
62
+
63
+ def __init__(self,
64
+ sender: str | None = None,
65
+ content: any = None,
66
+ timestamp_net: str | None = None,
67
+ channel: str | None = None,
68
+ content_type: str = MISC,
69
+ piggyback: str | None = None,
70
+ _proto_msg: pb.Message = None):
71
+ """The constructor should be used either to create a new message filling the fields,
72
+ or to parse an existing Protobuf message (passing _proto_msg). In the latter case,
73
+ the other fields are ignored and the Protobuf message is used as-is. The message is
74
+ simply stored in the internal `_proto_msg` field and other fields can be accessed
75
+ through properties."""
76
+
77
+ self._decoded_content: any = None # Cache for decompressed content
78
+
79
+ if _proto_msg is not None:
80
+
81
+ # Check if any other arguments were simultaneously provided
82
+ other_args = [sender, content, timestamp_net, channel, piggyback]
83
+ if any(arg is not None for arg in other_args):
84
+ raise ValueError("Cannot specify other arguments when creating a Msg from a _proto_msg.")
85
+
86
+ # This path is used by from_bytes, message is already built
87
+ self._proto_msg = _proto_msg
88
+ return
89
+
90
+ # Sanity checks
91
+ assert sender is not None, "Sender must be specified for a new message."
92
+ assert isinstance(sender, str), "Sender must be a string"
93
+ assert timestamp_net is None or isinstance(timestamp_net, str), "Invalid timestamp_net"
94
+ assert channel is None or isinstance(channel, str), "Invalid channel"
95
+ assert content_type in Msg.CONTENT_TYPES, "Invalid content type"
96
+
97
+ # --- SMART CONSTRUCTOR: Populates the correct 'oneof' field ---
98
+ self._proto_msg = pb.Message()
99
+ self._proto_msg.sender = sender if sender is not None else ""
100
+ self._proto_msg.timestamp_net = timestamp_net if timestamp_net is not None else \
101
+ datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S.%f")
102
+ self._proto_msg.content_type = content_type if content_type is not None else self.MISC
103
+ self._proto_msg.channel = channel if channel is not None else "<unknown>"
104
+ self._proto_msg.piggyback = piggyback if piggyback is not None else ""
105
+
106
+ if content is None or content == "<empty>":
107
+ return # Nothing to set in the 'oneof'
108
+
109
+ # Route the content to the correct builder
110
+ if content_type == Msg.STREAM_SAMPLE:
111
+ self._build_stream_sample_content(content)
112
+ elif content_type == Msg.STATS_UPDATE:
113
+ self._build_stats_update_content(content)
114
+ else:
115
+ # All other structured types use the generic json_content field
116
+ self._build_json_content(content)
117
+
118
+ def __str__(self):
119
+ return (f"Msg(sender={self.sender[:12]}..., content_type={self.content_type}, "
120
+ f"channel='{self.channel}', content_len={len(self.to_bytes())} bytes)")
121
+
122
+ # --- Properties for lazy-loading and easy access ---
123
+ @property
124
+ def sender(self):
125
+ return self._proto_msg.sender
126
+
127
+ @sender.setter
128
+ def sender(self, value: str):
129
+ self._proto_msg.sender = value if value is not None else ""
130
+
131
+ @property
132
+ def content_type(self):
133
+ return self._proto_msg.content_type
134
+
135
+ @content_type.setter
136
+ def content_type(self, value: str):
137
+ self._proto_msg.content_type = value if value is not None else self.MISC
138
+
139
+ @property
140
+ def channel(self):
141
+ return self._proto_msg.channel
142
+
143
+ @channel.setter
144
+ def channel(self, value: str):
145
+ self._proto_msg.channel = value if value is not None else "<unknown>"
146
+
147
+ @property
148
+ def piggyback(self):
149
+ return self._proto_msg.piggyback
150
+
151
+ @piggyback.setter
152
+ def piggyback(self, value: str):
153
+ self._proto_msg.piggyback = value if value is not None else ""
154
+
155
+ @property
156
+ def timestamp_net(self):
157
+ return self._proto_msg.timestamp_net
158
+
159
+ @timestamp_net.setter
160
+ def timestamp_net(self, value: str):
161
+ self._proto_msg.timestamp_net = value if value is not None else ""
162
+
163
+ @property
164
+ def content(self) -> any:
165
+ """The main content of the message, decoded on-the-fly with caching."""
166
+ if self._decoded_content is not None:
167
+ return self._decoded_content
168
+
169
+ payload_type = self._proto_msg.WhichOneof("content")
170
+ if payload_type == "stream_sample":
171
+ self._decoded_content = self._parse_stream_sample_content()
172
+ elif payload_type == "stats_update":
173
+ self._decoded_content = self._parse_stats_update_content()
174
+ elif payload_type == "json_content":
175
+ self._decoded_content = self._parse_json_content()
176
+ else:
177
+ self._decoded_content = "<empty>"
178
+
179
+ return self._decoded_content
180
+
181
+ # --- Serialization / Deserialization ---
182
+ def to_bytes(self) -> bytes:
183
+ """Serializes the internal Protobuf message to bytes."""
184
+ return self._proto_msg.SerializeToString()
185
+
186
+ @classmethod
187
+ def from_bytes(cls, msg_bytes: bytes) -> 'Msg':
188
+ """Deserializes a byte array into a new Msg instance."""
189
+ pb_msg = pb.Message()
190
+ pb_msg.ParseFromString(msg_bytes)
191
+
192
+ # Pass the parsed protobuf message to the constructor
193
+ return cls(_proto_msg=pb_msg)
194
+
195
+ # --- Internal Helper Methods ---
196
+ def _build_json_content(self, content: dict):
197
+ """Populates the generic json_content field."""
198
+ self._proto_msg.json_content = json.dumps(content)
199
+
200
+ def _parse_json_content(self) -> dict:
201
+ """Parses the generic json_content field back to a dict."""
202
+ return json.loads(self._proto_msg.json_content)
203
+
204
+ def _build_stream_sample_content(self, samples_dict: dict):
205
+ """Builds the complex StreamSampleContent message from a dict."""
206
+ content_pb = self._proto_msg.stream_sample
207
+ for name, sample_info in samples_dict.items():
208
+ data = sample_info.get('data')
209
+ if data is None:
210
+ continue
211
+
212
+ stream_sample_pb = content_pb.samples[name]
213
+ stream_sample_pb.data_tag = sample_info.get('data_tag', -1)
214
+ uuid = sample_info.get('data_uuid')
215
+
216
+ # Only set the field if the uuid is not None
217
+ if uuid is not None:
218
+ stream_sample_pb.data_uuid = uuid
219
+
220
+ if isinstance(data, torch.Tensor):
221
+ raw_bytes = data.detach().cpu().numpy().tobytes()
222
+ with io.BytesIO() as buffer:
223
+ with gzip.GzipFile(fileobj=buffer, mode='wb') as f:
224
+ f.write(raw_bytes)
225
+ stream_sample_pb.data.tensor_data.data = buffer.getvalue()
226
+ stream_sample_pb.data.tensor_data.dtype = str(data.dtype).split('.')[-1]
227
+ stream_sample_pb.data.tensor_data.shape.extend(list(data.shape))
228
+
229
+ elif isinstance(data, Image.Image):
230
+ with io.BytesIO() as buffer:
231
+ data.save(buffer, format="PNG", optimize=True, compress_level=9)
232
+ stream_sample_pb.data.image_data.data = buffer.getvalue()
233
+
234
+ elif isinstance(data, str):
235
+ stream_sample_pb.data.text_data.data = data
236
+
237
+ elif isinstance(data, FileContainer):
238
+ # Auto-convert string content to bytes if needed
239
+ raw_bytes = data.content.encode('utf-8') if isinstance(data.content, str) else data.content
240
+ stream_sample_pb.data.file_data.content = raw_bytes
241
+ stream_sample_pb.data.file_data.filename = data.filename
242
+ stream_sample_pb.data.file_data.mime_type = data.mime_type
243
+
244
+ def _parse_stream_sample_content(self) -> dict:
245
+ """
246
+ Parses the internal StreamSampleContent message back into a Python
247
+ dictionary of tensors and images.
248
+ """
249
+ py_dict = {}
250
+
251
+ # Iterate through the Protobuf map ('samples')
252
+ for name, sample_pb in self._proto_msg.stream_sample.samples.items():
253
+ data_payload = sample_pb.data
254
+ data = None
255
+
256
+ # Check which field in the 'oneof' is set
257
+ payload_type = data_payload.WhichOneof("data_payload")
258
+
259
+ if payload_type == "tensor_data":
260
+ tensor_data = data_payload.tensor_data
261
+
262
+ # Decompress and reconstruct the tensor
263
+ with gzip.GzipFile(fileobj=io.BytesIO(tensor_data.data), mode='rb') as f:
264
+ raw_bytes = f.read()
265
+ data = torch.frombuffer(
266
+ bytearray(raw_bytes),
267
+ dtype=getattr(torch, tensor_data.dtype)
268
+ ).reshape(list(tensor_data.shape))
269
+
270
+ elif payload_type == "image_data":
271
+ data = Image.open(io.BytesIO(data_payload.image_data.data))
272
+
273
+ elif payload_type == "text_data":
274
+ data = data_payload.text_data.data
275
+
276
+ elif payload_type == "file_data":
277
+ f_data = data_payload.file_data
278
+ data = FileContainer(
279
+ content=f_data.content,
280
+ filename=f_data.filename,
281
+ mime_type=f_data.mime_type
282
+ )
283
+
284
+ # Build the final Python dictionary for this sample
285
+ py_dict[name] = {
286
+ 'data': data,
287
+ 'data_tag': sample_pb.data_tag,
288
+ 'data_uuid': sample_pb.data_uuid if sample_pb.HasField("data_uuid") else None
289
+ }
290
+ return py_dict
291
+
292
+ def _py_value_to_proto_value(self, py_val: Any) -> Value:
293
+ """Helper to convert a Python type into a google.protobuf.Value."""
294
+ if py_val is None:
295
+ return Value(null_value=NULL_VALUE)
296
+ if isinstance(py_val, (int, float)):
297
+ return Value(number_value=py_val)
298
+ if isinstance(py_val, str):
299
+ return Value(string_value=py_val)
300
+ if isinstance(py_val, bool):
301
+ return Value(bool_value=py_val)
302
+ if isinstance(py_val, list):
303
+ lv = ListValue()
304
+ for item in py_val:
305
+ lv.values.append(self._py_value_to_proto_value(item))
306
+ return Value(list_value=lv)
307
+ if isinstance(py_val, dict):
308
+ # This is recursive for dicts/structs
309
+ s = Value(struct_value={})
310
+ ParseDict(py_val, s.struct_value)
311
+ return s
312
+
313
+ # Fallback
314
+ return Value(string_value=str(py_val))
315
+
316
+ def _build_stats_update_content(self, payload_list: list):
317
+ """Builds the StatBatch message from a List[Dict]."""
318
+ batch_pb = self._proto_msg.stats_update
319
+
320
+ for update_dict in payload_list:
321
+ update_pb = batch_pb.updates.add()
322
+ update_pb.peer_id = update_dict['peer_id']
323
+ update_pb.stat_name = update_dict['stat_name']
324
+ update_pb.timestamp = int(update_dict['timestamp']) # Ensure int
325
+
326
+ # Convert the Python 'value' to a Protobuf 'Value'
327
+ py_value = update_dict['value']
328
+ update_pb.value.CopyFrom(self._py_value_to_proto_value(py_value))
329
+
330
+ def _proto_value_to_py_value(self, proto_val: Value) -> Any:
331
+ """Helper to convert a google.protobuf.Value into a Python type."""
332
+ kind = proto_val.WhichOneof("kind")
333
+ if kind == "null_value":
334
+ return None
335
+ if kind == "number_value":
336
+ return proto_val.number_value
337
+ if kind == "string_value":
338
+ return proto_val.string_value
339
+ if kind == "bool_value":
340
+ return proto_val.bool_value
341
+ if kind == "list_value":
342
+ return [self._proto_value_to_py_value(v) for v in proto_val.list_value.values]
343
+ if kind == "struct_value":
344
+ # Use the helper to convert a Struct to a dict
345
+ return MessageToDict(proto_val.struct_value)
346
+ return None
347
+
348
+ def _parse_stats_update_content(self) -> list:
349
+ """Parses the StatBatch message back into a List[Dict]."""
350
+ py_list = []
351
+ batch_pb = self._proto_msg.stats_update
352
+
353
+ for update_pb in batch_pb.updates:
354
+ py_list.append({
355
+ "peer_id": update_pb.peer_id,
356
+ "stat_name": update_pb.stat_name,
357
+ "timestamp": update_pb.timestamp,
358
+ "value": self._proto_value_to_py_value(update_pb.value)
359
+ })
360
+ return py_list
@@ -0,0 +1,78 @@
1
+ """
2
+ █████ █████ ██████ █████ █████ █████ █████ ██████████ ███████████ █████████ ██████████
3
+ ░░███ ░░███ ░░██████ ░░███ ░░███ ░░███ ░░███ ░░███░░░░░█░░███░░░░░███ ███░░░░░███░░███░░░░░█
4
+ ░███ ░███ ░███░███ ░███ ██████ ░███ ░███ ░███ ░███ █ ░ ░███ ░███ ░███ ░░░ ░███ █ ░
5
+ ░███ ░███ ░███░░███░███ ░░░░░███ ░███ ░███ ░███ ░██████ ░██████████ ░░█████████ ░██████
6
+ ░███ ░███ ░███ ░░██████ ███████ ░███ ░░███ ███ ░███░░█ ░███░░░░░███ ░░░░░░░░███ ░███░░█
7
+ ░███ ░███ ░███ ░░█████ ███░░███ ░███ ░░░█████░ ░███ ░ █ ░███ ░███ ███ ░███ ░███ ░ █
8
+ ░░████████ █████ ░░█████░░████████ █████ ░░███ ██████████ █████ █████░░█████████ ██████████
9
+ ░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░░ ░░░ ░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░
10
+ A Collectionless AI Project (https://collectionless.ai)
11
+ Registration/Login: https://unaiverse.io
12
+ Code Repositories: https://github.com/collectionlessai/
13
+ Main Developers: Stefano Melacci (Project Leader), Christian Di Maio, Tommaso Guidi
14
+ """
15
+ import os
16
+ import logging
17
+ from logging.handlers import TimedRotatingFileHandler
18
+
19
+ LOG_FOLDER = "unaiverse/networking/p2p/logs"
20
+
21
+
22
+ def setup_logger(module_name: str, when: str = 'midnight', backup_count: int = 7) -> logging.Logger:
23
+ """
24
+ Sets up a logger for a specific module with a timed rotating file handler.
25
+ Args:
26
+ module_name (str): The name of the module for which the logger is being set up.
27
+ when (str, optional): Specifies the type of interval for log rotation.
28
+ Defaults to 'midnight'. Common values include 'S', 'M', 'H', 'D', 'midnight', etc.
29
+ backup_count (int, optional): The number of backup log files to keep. Defaults to 7.
30
+ Returns:
31
+ logging.Logger: A configured logger instance for the specified module.
32
+ Notes:
33
+ - Log files are stored in a predefined folder (`LOG_FOLDER`).
34
+ - Log rotation occurs based on the specified interval (`when`).
35
+ - The logger writes logs in UTF-8 encoding and uses UTC time by default.
36
+ - The log format includes timestamp, log level, filename, line number, and the log message.
37
+ - If the logger for the module already exists, it will not add duplicate handlers.
38
+ """
39
+ do_log = False # Stefano
40
+
41
+ # Ensure log folder exists
42
+ if do_log:
43
+ os.makedirs(LOG_FOLDER, exist_ok=True)
44
+
45
+ # Log file base path (without date suffix)
46
+ log_base_filename = os.path.join(LOG_FOLDER, f"{module_name}.log")
47
+
48
+ # Create logger
49
+ logger = logging.getLogger(module_name)
50
+ logger.setLevel(logging.DEBUG)
51
+
52
+ if do_log and not logger.handlers:
53
+
54
+ # Create rotating file handler
55
+ handler = TimedRotatingFileHandler(
56
+ log_base_filename,
57
+ when=when,
58
+ interval=1,
59
+ backupCount=backup_count,
60
+ encoding='utf-8',
61
+ utc=True # Set to False if you want local time
62
+ )
63
+
64
+ formatter = logging.Formatter(
65
+ fmt="%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(message)s",
66
+ datefmt="%Y-%m-%d %H:%M:%S"
67
+ )
68
+ handler.setFormatter(formatter)
69
+ logger.addHandler(handler)
70
+
71
+ # Avoid logging to root handlers
72
+ logger.propagate = False
73
+
74
+ if not do_log:
75
+ logger.handlers.clear()
76
+ logger.addHandler(logging.NullHandler())
77
+
78
+ return logger