pycityagent 2.0.0a66__cp39-cp39-macosx_11_0_arm64.whl → 2.0.0a67__cp39-cp39-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.
Files changed (87) hide show
  1. pycityagent/agent/agent.py +157 -57
  2. pycityagent/agent/agent_base.py +316 -43
  3. pycityagent/cityagent/bankagent.py +49 -9
  4. pycityagent/cityagent/blocks/__init__.py +1 -2
  5. pycityagent/cityagent/blocks/cognition_block.py +54 -31
  6. pycityagent/cityagent/blocks/dispatcher.py +22 -17
  7. pycityagent/cityagent/blocks/economy_block.py +46 -32
  8. pycityagent/cityagent/blocks/mobility_block.py +130 -100
  9. pycityagent/cityagent/blocks/needs_block.py +101 -44
  10. pycityagent/cityagent/blocks/other_block.py +42 -33
  11. pycityagent/cityagent/blocks/plan_block.py +59 -42
  12. pycityagent/cityagent/blocks/social_block.py +167 -116
  13. pycityagent/cityagent/blocks/utils.py +13 -6
  14. pycityagent/cityagent/firmagent.py +17 -35
  15. pycityagent/cityagent/governmentagent.py +3 -3
  16. pycityagent/cityagent/initial.py +79 -44
  17. pycityagent/cityagent/memory_config.py +108 -88
  18. pycityagent/cityagent/message_intercept.py +0 -4
  19. pycityagent/cityagent/metrics.py +41 -0
  20. pycityagent/cityagent/nbsagent.py +24 -36
  21. pycityagent/cityagent/societyagent.py +7 -3
  22. pycityagent/cli/wrapper.py +2 -2
  23. pycityagent/economy/econ_client.py +407 -81
  24. pycityagent/environment/__init__.py +0 -3
  25. pycityagent/environment/sim/__init__.py +0 -3
  26. pycityagent/environment/sim/aoi_service.py +2 -2
  27. pycityagent/environment/sim/client.py +3 -31
  28. pycityagent/environment/sim/clock_service.py +2 -2
  29. pycityagent/environment/sim/lane_service.py +8 -8
  30. pycityagent/environment/sim/light_service.py +8 -8
  31. pycityagent/environment/sim/pause_service.py +9 -10
  32. pycityagent/environment/sim/person_service.py +20 -20
  33. pycityagent/environment/sim/road_service.py +2 -2
  34. pycityagent/environment/sim/sim_env.py +21 -5
  35. pycityagent/environment/sim/social_service.py +4 -4
  36. pycityagent/environment/simulator.py +249 -27
  37. pycityagent/environment/utils/__init__.py +2 -2
  38. pycityagent/environment/utils/geojson.py +2 -2
  39. pycityagent/environment/utils/grpc.py +4 -4
  40. pycityagent/environment/utils/map_utils.py +2 -2
  41. pycityagent/llm/embeddings.py +147 -28
  42. pycityagent/llm/llm.py +122 -77
  43. pycityagent/llm/llmconfig.py +5 -0
  44. pycityagent/llm/utils.py +4 -0
  45. pycityagent/memory/__init__.py +0 -4
  46. pycityagent/memory/const.py +2 -2
  47. pycityagent/memory/faiss_query.py +140 -61
  48. pycityagent/memory/memory.py +393 -90
  49. pycityagent/memory/memory_base.py +140 -34
  50. pycityagent/memory/profile.py +13 -13
  51. pycityagent/memory/self_define.py +13 -13
  52. pycityagent/memory/state.py +14 -14
  53. pycityagent/message/message_interceptor.py +253 -3
  54. pycityagent/message/messager.py +133 -6
  55. pycityagent/metrics/mlflow_client.py +47 -4
  56. pycityagent/pycityagent-sim +0 -0
  57. pycityagent/pycityagent-ui +0 -0
  58. pycityagent/simulation/__init__.py +3 -2
  59. pycityagent/simulation/agentgroup.py +145 -52
  60. pycityagent/simulation/simulation.py +257 -62
  61. pycityagent/survey/manager.py +45 -3
  62. pycityagent/survey/models.py +42 -2
  63. pycityagent/tools/__init__.py +1 -2
  64. pycityagent/tools/tool.py +93 -69
  65. pycityagent/utils/avro_schema.py +2 -2
  66. pycityagent/utils/parsers/code_block_parser.py +1 -1
  67. pycityagent/utils/parsers/json_parser.py +2 -2
  68. pycityagent/utils/parsers/parser_base.py +2 -2
  69. pycityagent/workflow/block.py +64 -13
  70. pycityagent/workflow/prompt.py +31 -23
  71. pycityagent/workflow/trigger.py +91 -24
  72. {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/METADATA +2 -2
  73. pycityagent-2.0.0a67.dist-info/RECORD +97 -0
  74. pycityagent/environment/interact/__init__.py +0 -0
  75. pycityagent/environment/interact/interact.py +0 -198
  76. pycityagent/environment/message/__init__.py +0 -0
  77. pycityagent/environment/sence/__init__.py +0 -0
  78. pycityagent/environment/sence/static.py +0 -416
  79. pycityagent/environment/sidecar/__init__.py +0 -8
  80. pycityagent/environment/sidecar/sidecarv2.py +0 -109
  81. pycityagent/environment/sim/economy_services.py +0 -192
  82. pycityagent/metrics/utils/const.py +0 -0
  83. pycityagent-2.0.0a66.dist-info/RECORD +0 -105
  84. {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/LICENSE +0 -0
  85. {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/WHEEL +0 -0
  86. {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/entry_points.txt +0 -0
  87. {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/top_level.txt +0 -0
@@ -19,6 +19,12 @@ From `{from_uuid}` To `{to_uuid}` abort due to block `{block_name}`
19
19
 
20
20
  logger = logging.getLogger("message_interceptor")
21
21
 
22
+ __all__ = [
23
+ "MessageBlockBase",
24
+ "MessageInterceptor",
25
+ "MessageBlockListenerBase",
26
+ ]
27
+
22
28
 
23
29
  class MessageBlockBase(ABC):
24
30
  """
@@ -82,7 +88,7 @@ class MessageBlockBase(ABC):
82
88
  @ray.remote
83
89
  class MessageInterceptor:
84
90
  """
85
- 信息拦截器
91
+ A class to intercept and process messages based on configured rules.
86
92
  """
87
93
 
88
94
  def __init__(
@@ -92,6 +98,15 @@ class MessageInterceptor:
92
98
  llm_config: Optional[dict] = None,
93
99
  queue: Optional[Queue] = None,
94
100
  ) -> None:
101
+ """
102
+ Initialize the MessageInterceptor with optional configuration.
103
+
104
+ - **Args**:
105
+ - `blocks` (Optional[list[MessageBlockBase]], optional): Initial list of message interception rules. Defaults to an empty list.
106
+ - `black_list` (Optional[list[tuple[str, str]]], optional): Initial blacklist of communication pairs. Defaults to an empty list.
107
+ - `llm_config` (Optional[dict], optional): Configuration dictionary for initializing the LLM instance. Defaults to None.
108
+ - `queue` (Optional[Queue], optional): Queue for message processing. Defaults to None.
109
+ """
95
110
  if blocks is not None:
96
111
  self._blocks: list[MessageBlockBase] = blocks
97
112
  else:
@@ -114,6 +129,18 @@ class MessageInterceptor:
114
129
  def llm(
115
130
  self,
116
131
  ) -> LLM:
132
+ """
133
+ Access the Large Language Model instance.
134
+
135
+ - **Description**:
136
+ - Provides access to the internal LLM instance. Raises an error if accessed before assignment.
137
+
138
+ - **Raises**:
139
+ - `RuntimeError`: If accessed before setting the LLM.
140
+
141
+ - **Returns**:
142
+ - `LLM`: The Large Language Model instance.
143
+ """
117
144
  if self._llm is None:
118
145
  raise RuntimeError(f"LLM access before assignment, please `set_llm` first!")
119
146
  return self._llm
@@ -122,12 +149,30 @@ class MessageInterceptor:
122
149
  async def blocks(
123
150
  self,
124
151
  ) -> list[MessageBlockBase]:
152
+ """
153
+ Retrieve the message interception rules.
154
+
155
+ - **Description**:
156
+ - Returns a copy of the current list of message interception rules.
157
+
158
+ - **Returns**:
159
+ - `list[MessageBlockBase]`: The list of message interception rules.
160
+ """
125
161
  return self._blocks
126
162
 
127
163
  @lock_decorator
128
164
  async def set_llm(self, llm: LLM):
129
165
  """
130
- Set the llm_client of the block.
166
+ Set the Large Language Model instance.
167
+
168
+ - **Description**:
169
+ - Updates the internal LLM instance used for message processing.
170
+
171
+ - **Args**:
172
+ - `llm` (LLM): The LLM instance to be set.
173
+
174
+ - **Returns**:
175
+ - `None`
131
176
  """
132
177
  if self._llm is None:
133
178
  self._llm = llm
@@ -136,24 +181,64 @@ class MessageInterceptor:
136
181
  async def violation_counts(
137
182
  self,
138
183
  ) -> dict[str, int]:
184
+ """
185
+ Retrieve the violation counts.
186
+
187
+ - **Description**:
188
+ - Returns a deep copy of the violation counts to prevent external modification of the original data.
189
+
190
+ - **Returns**:
191
+ - `dict[str, int]`: The dictionary of violation counts.
192
+ """
139
193
  return deepcopy(self._violation_counts)
140
194
 
141
195
  @property
142
196
  def has_llm(
143
197
  self,
144
198
  ) -> bool:
199
+ """
200
+ Check if a Large Language Model is configured.
201
+
202
+ - **Description**:
203
+ - Confirms whether a Large Language Model instance has been set.
204
+
205
+ - **Returns**:
206
+ - `bool`: True if an LLM is set, otherwise False.
207
+ """
145
208
  return self._llm is not None
146
209
 
147
210
  @lock_decorator
148
211
  async def black_list(
149
212
  self,
150
213
  ) -> list[tuple[str, str]]:
214
+ """
215
+ Retrieve the blacklist.
216
+
217
+ - **Description**:
218
+ - Returns a deep copy of the current blacklist to protect the original data from external modifications.
219
+
220
+ - **Returns**:
221
+ - `list[tuple[str, str]]`: The blacklist.
222
+ """
151
223
  return deepcopy(self._black_list)
152
224
 
153
225
  @lock_decorator
154
226
  async def add_to_black_list(
155
227
  self, black_list: Union[list[tuple[str, str]], tuple[str, str]]
156
228
  ):
229
+ """
230
+ Add entries to the blacklist.
231
+
232
+ - **Description**:
233
+ - Adds one or more entries to the blacklist, ensuring each entry's uniqueness.
234
+
235
+ - **Args**:
236
+ - `black_list` (Union[list[tuple[str, str]], tuple[str, str]]):
237
+ Can be a single tuple or a list of tuples indicating the entries to add to the blacklist.
238
+
239
+ - **Returns**:
240
+ - `None`
241
+ """
157
242
  if all(isinstance(s, str) for s in black_list):
158
243
  # tuple[str,str]
159
244
  _black_list = [black_list]
@@ -167,12 +252,30 @@ class MessageInterceptor:
167
252
  def has_queue(
168
253
  self,
169
254
  ) -> bool:
255
+ """
256
+ Check if a queue is configured.
257
+
258
+ - **Description**:
259
+ - Confirms whether a queue instance has been set for message processing.
260
+
261
+ - **Returns**:
262
+ - `bool`: True if a queue is set, otherwise False.
263
+ """
170
264
  return self._queue is not None
171
265
 
172
266
  @lock_decorator
173
267
  async def set_queue(self, queue: Queue):
174
268
  """
175
269
  Set the queue of the MessageInterceptor.
270
+
271
+ - **Description**:
272
+ - Assigns a queue to the MessageInterceptor for asynchronous message handling.
273
+
274
+ - **Args**:
275
+ - `queue` (Queue): The queue instance to be set.
276
+
277
+ - **Returns**:
278
+ - `None`
176
279
  """
177
280
  self._queue = queue
178
281
 
@@ -180,6 +283,19 @@ class MessageInterceptor:
180
283
  async def remove_from_black_list(
181
284
  self, to_remove_black_list: Union[list[tuple[str, str]], tuple[str, str]]
182
285
  ):
286
+ """
287
+ Remove entries from the blacklist.
288
+
289
+ - **Description**:
290
+ - Removes one or more entries from the blacklist, ensuring each entry's removal.
291
+
292
+ - **Args**:
293
+ - `to_remove_black_list` (Union[list[tuple[str, str]], tuple[str, str]]):
294
+ Can be a single tuple or a list of tuples indicating the entries to remove from the blacklist.
295
+
296
+ - **Returns**:
297
+ - `None`
298
+ """
183
299
  if all(isinstance(s, str) for s in to_remove_black_list):
184
300
  # tuple[str,str]
185
301
  _black_list = [to_remove_black_list]
@@ -192,6 +308,18 @@ class MessageInterceptor:
192
308
  def queue(
193
309
  self,
194
310
  ) -> Queue:
311
+ """
312
+ Access the queue used for message processing.
313
+
314
+ - **Description**:
315
+ - Provides access to the internal queue. Raises an error if accessed before assignment.
316
+
317
+ - **Raises**:
318
+ - `RuntimeError`: If accessed before setting the queue.
319
+
320
+ - **Returns**:
321
+ - `Queue`: The queue instance.
322
+ """
195
323
  if self._queue is None:
196
324
  raise RuntimeError(
197
325
  f"Queue access before assignment, please `set_queue` first!"
@@ -200,12 +328,37 @@ class MessageInterceptor:
200
328
 
201
329
  @lock_decorator
202
330
  async def insert_block(self, block: MessageBlockBase, index: Optional[int] = None):
331
+ """
332
+ Insert a message block into the blocks list at a specified position.
333
+
334
+ - **Description**:
335
+ - Inserts a new message interception rule into the list at the specified index or appends it if no index is provided.
336
+
337
+ - **Args**:
338
+ - `block` (MessageBlockBase): The message block to insert.
339
+ - `index` (Optional[int], optional): The position at which to insert the block. Defaults to appending at the end.
340
+
341
+ - **Returns**:
342
+ - `None`
343
+ """
203
344
  if index is None:
204
345
  index = len(self._blocks)
205
346
  self._blocks.insert(index, block)
206
347
 
207
348
  @lock_decorator
208
349
  async def pop_block(self, index: Optional[int] = None) -> MessageBlockBase:
350
+ """
351
+ Remove and return a message block from the blocks list.
352
+
353
+ - **Description**:
354
+ - Removes and returns the message block at the specified index or the last one if no index is provided.
355
+
356
+ - **Args**:
357
+ - `index` (Optional[int], optional): The position of the block to remove. Defaults to removing the last element.
358
+
359
+ - **Returns**:
360
+ - `MessageBlockBase`: The removed message block.
361
+ """
209
362
  if index is None:
210
363
  index = -1
211
364
  return self._blocks.pop(index)
@@ -214,6 +367,19 @@ class MessageInterceptor:
214
367
  async def set_black_list(
215
368
  self, black_list: Union[list[tuple[str, str]], tuple[str, str]]
216
369
  ):
370
+ """
371
+ Set the blacklist with new entries.
372
+
373
+ - **Description**:
374
+ - Updates the blacklist with new entries, ensuring each entry's uniqueness.
375
+
376
+ - **Args**:
377
+ - `black_list` (Union[list[tuple[str, str]], tuple[str, str]]):
378
+ Can be a single tuple or a list of tuples indicating the new blacklist entries.
379
+
380
+ - **Returns**:
381
+ - `None`
382
+ """
217
383
  if all(isinstance(s, str) for s in black_list):
218
384
  # tuple[str,str]
219
385
  _black_list = [black_list]
@@ -224,6 +390,18 @@ class MessageInterceptor:
224
390
 
225
391
  @lock_decorator
226
392
  async def set_blocks(self, blocks: list[MessageBlockBase]):
393
+ """
394
+ Replace the current blocks list with a new list of message blocks.
395
+
396
+ - **Description**:
397
+ - Sets a new list of message interception rules, replacing the existing list.
398
+
399
+ - **Args**:
400
+ - `blocks` (list[MessageBlockBase]): The new list of message blocks to set.
401
+
402
+ - **Returns**:
403
+ - `None`
404
+ """
227
405
  self._blocks = blocks
228
406
 
229
407
  @lock_decorator
@@ -233,6 +411,20 @@ class MessageInterceptor:
233
411
  to_uuid: str,
234
412
  msg: str,
235
413
  ):
414
+ """
415
+ Forward a message through all message blocks.
416
+
417
+ - **Description**:
418
+ - Processes a message by passing it through all configured message blocks. Each block can modify the message or prevent its forwarding based on implemented logic.
419
+
420
+ - **Args**:
421
+ - `from_uuid` (str): The UUID of the sender.
422
+ - `to_uuid` (str): The UUID of the recipient.
423
+ - `msg` (str): The message content to forward.
424
+
425
+ - **Returns**:
426
+ - `bool`: True if the message was successfully processed by all blocks, otherwise False.
427
+ """
236
428
  for _block in self._blocks:
237
429
  if not _block.has_llm and self.has_llm:
238
430
  await _block.set_llm(self.llm)
@@ -274,11 +466,29 @@ class MessageInterceptor:
274
466
 
275
467
 
276
468
  class MessageBlockListenerBase(ABC):
469
+ """
470
+ Base class for message block listeners that can listen to a queue and process items.
471
+
472
+ - **Attributes**:
473
+ - `_queue` (Optional[Queue]): Queue from which the listener retrieves items.
474
+ - `_lock` (asyncio.Lock): Lock for thread-safe access in asynchronous environments.
475
+ - `_values_from_queue` (list[Any]): List of values retrieved from the queue if saving is enabled.
476
+ - `_save_queue_values` (bool): Flag indicating whether to save values from the queue.
477
+ - `_get_queue_period` (float): Period in seconds between queue retrieval attempts.
478
+ """
479
+
277
480
  def __init__(
278
481
  self,
279
482
  save_queue_values: bool = False,
280
483
  get_queue_period: float = 0.1,
281
484
  ) -> None:
485
+ """
486
+ Initialize the MessageBlockListenerBase with optional configuration.
487
+
488
+ - **Args**:
489
+ - `save_queue_values` (bool, optional): Whether to save values retrieved from the queue. Defaults to False.
490
+ - `get_queue_period` (float, optional): Time period in seconds between queue retrieval attempts. Defaults to 0.1.
491
+ """
282
492
  self._queue = None
283
493
  self._lock = asyncio.Lock()
284
494
  self._values_from_queue: list[Any] = []
@@ -289,6 +499,18 @@ class MessageBlockListenerBase(ABC):
289
499
  def queue(
290
500
  self,
291
501
  ) -> Queue:
502
+ """
503
+ Access the queue used by the listener.
504
+
505
+ - **Description**:
506
+ - Provides access to the internal queue. Raises an error if accessed before assignment.
507
+
508
+ - **Raises**:
509
+ - `RuntimeError`: If accessed before setting the queue.
510
+
511
+ - **Returns**:
512
+ - `Queue`: The queue instance.
513
+ """
292
514
  if self._queue is None:
293
515
  raise RuntimeError(
294
516
  f"Queue access before assignment, please `set_queue` first!"
@@ -299,12 +521,30 @@ class MessageBlockListenerBase(ABC):
299
521
  def has_queue(
300
522
  self,
301
523
  ) -> bool:
524
+ """
525
+ Check if a queue is configured.
526
+
527
+ - **Description**:
528
+ - Confirms whether a queue instance has been set for the listener.
529
+
530
+ - **Returns**:
531
+ - `b
532
+ """
302
533
  return self._queue is not None
303
534
 
304
535
  @lock_decorator
305
536
  async def set_queue(self, queue: Queue):
306
537
  """
307
- Set the queue of the MessageBlockListenerBase.
538
+ Set the queue for the listener.
539
+
540
+ - **Description**:
541
+ - Assigns a queue to the listener for asynchronous item processing.
542
+
543
+ - **Args**:
544
+ - `queue` (Queue): The queue instance to be set.
545
+
546
+ - **Returns**:
547
+ - `None`
308
548
  """
309
549
  self._queue = queue
310
550
 
@@ -312,6 +552,16 @@ class MessageBlockListenerBase(ABC):
312
552
  async def forward(
313
553
  self,
314
554
  ):
555
+ """
556
+ Continuously retrieve items from the queue and process them.
557
+
558
+ - **Description**:
559
+ - Listens to the queue, retrieves items at intervals defined by `_get_queue_period`,
560
+ and processes each item. If `_save_queue_values` is True, it saves the items in `_values_from_queue`.
561
+
562
+ - **Returns**:
563
+ - `None`
564
+ """
315
565
  while True:
316
566
  if self.has_queue:
317
567
  value = await self.queue.get_async() # type: ignore
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import json
3
3
  import logging
4
+ import time
4
5
  from typing import Any, Optional, Union
5
6
 
6
7
  import ray
@@ -8,11 +9,26 @@ from aiomqtt import Client
8
9
 
9
10
  from .message_interceptor import MessageInterceptor
10
11
 
12
+ __all__ = [
13
+ "Messager",
14
+ ]
15
+
11
16
  logger = logging.getLogger("pycityagent")
12
17
 
13
18
 
14
19
  @ray.remote
15
20
  class Messager:
21
+ """
22
+ A class to manage message sending and receiving using an MQTT protocol.
23
+
24
+ - **Attributes**:
25
+ - `client` (Client): An instance of the MQTT client.
26
+ - `connected` (bool): Indicates whether the connection to the broker is established.
27
+ - `message_queue` (asyncio.Queue): Queue for storing received messages.
28
+ - `receive_messages_task` (Optional[Task]): Task for listening to incoming messages.
29
+ - `_message_interceptor` (Optional[ray.ObjectRef]): Reference to a remote message interceptor object.
30
+ """
31
+
16
32
  def __init__(
17
33
  self,
18
34
  hostname: str,
@@ -22,6 +38,17 @@ class Messager:
22
38
  timeout=60,
23
39
  message_interceptor: Optional[ray.ObjectRef] = None,
24
40
  ):
41
+ """
42
+ Initialize the Messager with connection parameters.
43
+
44
+ - **Args**:
45
+ - `hostname` (str): The hostname or IP address of the MQTT broker.
46
+ - `port` (int, optional): Port number of the MQTT broker. Defaults to 1883.
47
+ - `username` (str, optional): Username for broker authentication.
48
+ - `password` (str, optional): Password for broker authentication.
49
+ - `timeout` (int, optional): Connection timeout in seconds. Defaults to 60.
50
+ - `message_interceptor` (Optional[ray.ObjectRef], optional): Reference to a message interceptor object.
51
+ """
25
52
  self.client = Client(
26
53
  hostname, port=port, username=username, password=password, timeout=timeout
27
54
  )
@@ -29,23 +56,61 @@ class Messager:
29
56
  self.message_queue = asyncio.Queue() # 用于存储接收到的消息
30
57
  self.receive_messages_task = None
31
58
  self._message_interceptor = message_interceptor
59
+ self._log_list = []
32
60
 
33
61
  @property
34
62
  def message_interceptor(
35
63
  self,
36
64
  ) -> Union[None, ray.ObjectRef]:
65
+ """
66
+ Access the message interceptor reference.
67
+
68
+ - **Returns**:
69
+ - `Union[None, ray.ObjectRef]`: The message interceptor reference.
70
+ """
37
71
  return self._message_interceptor
72
+
73
+ def get_log_list(self):
74
+ return self._log_list
75
+
76
+ def clear_log_list(self):
77
+ self._log_list = []
38
78
 
39
79
  def set_message_interceptor(self, message_interceptor: ray.ObjectRef):
80
+ """
81
+ Set the message interceptor reference.
82
+
83
+ - **Args**:
84
+ - `message_interceptor` (ray.ObjectRef): The message interceptor reference to be set.
85
+ """
40
86
  self._message_interceptor = message_interceptor
41
87
 
42
88
  async def __aexit__(self, exc_type, exc_value, traceback):
89
+ """
90
+ Asynchronous exit method to ensure proper cleanup when used in an async context manager.
91
+
92
+ - **Args**:
93
+ - `exc_type`, `exc_value`, `traceback`: Exception information if an exception occurred.
94
+ """
43
95
  await self.stop()
44
96
 
45
97
  async def ping(self):
98
+ """
99
+ Send a ping message to the MQTT broker.
100
+
101
+ - **Description**:
102
+ - Publishes a 'ping' message on the 'ping' topic with QoS level 1.
103
+ """
46
104
  await self.client.publish(topic="ping", payload="ping", qos=1)
47
105
 
48
106
  async def connect(self):
107
+ """
108
+ Attempt to connect to the MQTT broker up to three times.
109
+
110
+ - **Description**:
111
+ - Tries to establish a connection to the MQTT broker. Retries up to three times with delays between attempts.
112
+ - Logs success or failure accordingly.
113
+ """
49
114
  for i in range(3):
50
115
  try:
51
116
  await self.client.__aenter__()
@@ -59,18 +124,35 @@ class Messager:
59
124
  logger.error("All connection attempts failed.")
60
125
 
61
126
  async def disconnect(self):
127
+ """
128
+ Disconnect from the MQTT broker.
129
+
130
+ - **Description**:
131
+ - Closes the connection to the MQTT broker and logs the disconnection.
132
+ """
62
133
  await self.client.__aexit__(None, None, None)
63
134
  self.connected = False
64
135
  logger.info("Disconnected from MQTT Broker")
65
136
 
66
137
  async def is_connected(self):
67
- """检查是否成功连接到 Broker"""
138
+ """
139
+ Check if the connection to the broker is established.
140
+
141
+ - **Returns**:
142
+ - `bool`: True if connected, otherwise False.
143
+ """
68
144
  return self.connected
69
145
 
70
- # TODO:add message interceptor
71
146
  async def subscribe(
72
147
  self, topics: Union[str, list[str]], agents: Union[Any, list[Any]]
73
148
  ):
149
+ """
150
+ Subscribe to one or more MQTT topics.
151
+
152
+ - **Args**:
153
+ - `topics` (Union[str, list[str]]): Topic or list of topics to subscribe to.
154
+ - `agents` (Union[Any, list[Any]]): Agents or list of agents associated with the subscription.
155
+ """
74
156
  if not await self.is_connected():
75
157
  logger.error(
76
158
  f"Cannot subscribe to {topics} because not connected to the Broker."
@@ -83,12 +165,22 @@ class Messager:
83
165
  await self.client.subscribe(topics, qos=1) # type: ignore
84
166
 
85
167
  async def receive_messages(self):
86
- """监听并将消息存入队列"""
168
+ """
169
+ Listen for incoming messages and store them in the queue.
170
+
171
+ - **Description**:
172
+ - Continuously listens for incoming messages and puts them into the message queue.
173
+ """
87
174
  async for message in self.client.messages:
88
175
  await self.message_queue.put(message)
89
176
 
90
177
  async def fetch_messages(self):
91
- """从队列中批量获取消息"""
178
+ """
179
+ Retrieve all messages currently in the queue.
180
+
181
+ - **Returns**:
182
+ - `list[Any]`: List of messages retrieved from the queue.
183
+ """
92
184
  messages = []
93
185
  while not self.message_queue.empty():
94
186
  messages.append(await self.message_queue.get())
@@ -101,7 +193,28 @@ class Messager:
101
193
  from_uuid: Optional[str] = None,
102
194
  to_uuid: Optional[str] = None,
103
195
  ):
104
- """通过 Messager 发送消息"""
196
+ """
197
+ Send a message through the MQTT broker.
198
+
199
+ - **Args**:
200
+ - `topic` (str): Topic to which the message should be published.
201
+ - `payload` (dict): Payload of the message to send.
202
+ - `from_uuid` (Optional[str], optional): UUID of the sender. Required for interception.
203
+ - `to_uuid` (Optional[str], optional): UUID of the recipient. Required for interception.
204
+
205
+ - **Description**:
206
+ - Serializes the payload to JSON, checks it against the message interceptor (if any),
207
+ and publishes the message to the specified topic if valid.
208
+ """
209
+ start_time = time.time()
210
+ log = {
211
+ "topic": topic,
212
+ "payload": payload,
213
+ "from_uuid": from_uuid,
214
+ "to_uuid": to_uuid,
215
+ "start_time": start_time,
216
+ "consumption": 0
217
+ }
105
218
  message = json.dumps(payload, default=str)
106
219
  interceptor = self.message_interceptor
107
220
  is_valid: bool = True
@@ -114,15 +227,29 @@ class Messager:
114
227
  logger.info(f"Message sent to {topic}: {message}")
115
228
  else:
116
229
  logger.info(f"Message not sent to {topic}: {message} due to interceptor")
230
+ log["consumption"] = time.time() - start_time
231
+ self._log_list.append(log)
117
232
 
118
233
  async def start_listening(self):
119
- """启动消息监听任务"""
234
+ """
235
+ Start the task for listening to incoming messages.
236
+
237
+ - **Description**:
238
+ - Starts a task that listens for incoming messages and puts them into the queue.
239
+ - Only starts the task if the connection to the broker is active.
240
+ """
120
241
  if await self.is_connected():
121
242
  self.receive_messages_task = asyncio.create_task(self.receive_messages())
122
243
  else:
123
244
  logger.error("Cannot start listening because not connected to the Broker.")
124
245
 
125
246
  async def stop(self):
247
+ """
248
+ Stop the listener and disconnect from the MQTT broker.
249
+
250
+ - **Description**:
251
+ - Cancels the receive_messages_task and ensures the MQTT broker connection is closed.
252
+ """
126
253
  assert self.receive_messages_task is not None
127
254
  self.receive_messages_task.cancel()
128
255
  await asyncio.gather(self.receive_messages_task, return_exceptions=True)