unaiverse 0.1.11__cp311-cp311-macosx_11_0_arm64.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.

Potentially problematic release.


This version of unaiverse might be problematic. Click here for more details.

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