pycityagent 2.0.0a65__cp310-cp310-macosx_11_0_arm64.whl → 2.0.0a67__cp310-cp310-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 +209 -105
  9. pycityagent/cityagent/blocks/needs_block.py +101 -54
  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 -126
  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 -49
  17. pycityagent/cityagent/memory_config.py +123 -94
  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 +9 -4
  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 +178 -111
  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 +394 -91
  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 +150 -54
  60. pycityagent/simulation/simulation.py +276 -66
  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.0a65.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.0a65.dist-info/RECORD +0 -105
  84. {pycityagent-2.0.0a65.dist-info → pycityagent-2.0.0a67.dist-info}/LICENSE +0 -0
  85. {pycityagent-2.0.0a65.dist-info → pycityagent-2.0.0a67.dist-info}/WHEEL +0 -0
  86. {pycityagent-2.0.0a65.dist-info → pycityagent-2.0.0a67.dist-info}/entry_points.txt +0 -0
  87. {pycityagent-2.0.0a65.dist-info → pycityagent-2.0.0a67.dist-info}/top_level.txt +0 -0
@@ -15,6 +15,8 @@ from mlflow.entities import (Dataset, DatasetInput, Document, Experiment,
15
15
 
16
16
  from ..utils.decorators import lock_decorator
17
17
 
18
+ __all__ = ["init_mlflow_connection","MlflowClient",]
19
+
18
20
  logger = logging.getLogger("mlflow")
19
21
 
20
22
 
@@ -26,7 +28,20 @@ def init_mlflow_connection(
26
28
  experiment_description: Optional[str] = None,
27
29
  experiment_tags: Optional[dict[str, Any]] = None,
28
30
  ) -> tuple[str, tuple[str, mlflow.MlflowClient, Run, str]]:
29
-
31
+ """
32
+ Initialize an MLflow connection with a new or existing experiment and run.
33
+
34
+ - **Args**:
35
+ config (dict): Configuration dictionary containing MLflow credentials and URI.
36
+ experiment_uuid (str): A unique identifier for the experiment.
37
+ mlflow_run_name (str, optional): Name of the MLflow run. Defaults to a generated name.
38
+ experiment_name (str, optional): Name of the experiment. Defaults to a generated name.
39
+ experiment_description (str, optional): Description for the experiment. Defaults to None.
40
+ experiment_tags (dict, optional): Tags to associate with the experiment. Defaults to None.
41
+
42
+ - **Returns**:
43
+ tuple: A tuple containing the run_id and another tuple with the MLflow URI, client, run object, and run UUID.
44
+ """
30
45
  os.environ["MLFLOW_TRACKING_USERNAME"] = config.get("username", None)
31
46
  os.environ["MLFLOW_TRACKING_PASSWORD"] = config.get("password", None)
32
47
 
@@ -70,9 +85,7 @@ def init_mlflow_connection(
70
85
 
71
86
 
72
87
  class MlflowClient:
73
- """
74
- - Mlflow client
75
- """
88
+ """A wrapper around MLflow's MlflowClient for managing experiments and runs."""
76
89
 
77
90
  def __init__(
78
91
  self,
@@ -84,6 +97,18 @@ class MlflowClient:
84
97
  experiment_tags: Optional[dict[str, Any]] = None,
85
98
  run_id: Optional[str] = None,
86
99
  ) -> None:
100
+ """
101
+ Initialize the MlflowClient.
102
+
103
+ - **Args**:
104
+ config (dict): Configuration dictionary containing MLflow credentials and URI.
105
+ experiment_uuid (str): A unique identifier for the experiment.
106
+ mlflow_run_name (str, optional): Name of the MLflow run. Defaults to a generated name.
107
+ experiment_name (str, optional): Name of the experiment. Defaults to a generated name.
108
+ experiment_description (str, optional): Description for the experiment. Defaults to None.
109
+ experiment_tags (dict, optional): Tags to associate with the experiment. Defaults to None.
110
+ run_id (str, optional): Existing MLflow run ID to attach to. Defaults to None.
111
+ """
87
112
  if run_id is None:
88
113
  self._run_id, (
89
114
  self._mlflow_uri,
@@ -112,12 +137,14 @@ class MlflowClient:
112
137
  def client(
113
138
  self,
114
139
  ) -> mlflow.MlflowClient:
140
+ """Return the underlying MLflow client."""
115
141
  return self._client
116
142
 
117
143
  @property
118
144
  def run_id(
119
145
  self,
120
146
  ) -> str:
147
+ """Return the current run ID."""
121
148
  assert self._run_id is not None
122
149
  return self._run_id
123
150
 
@@ -128,6 +155,11 @@ class MlflowClient:
128
155
  params: Sequence[Param] = (),
129
156
  tags: Sequence[RunTag] = (),
130
157
  ):
158
+ """
159
+ Log a batch of metrics, parameters, and tags to the MLflow run.
160
+
161
+ This method is thread-safe due to the `@lock_decorator`.
162
+ """
131
163
  self.client.log_batch(
132
164
  run_id=self.run_id, metrics=metrics, params=params, tags=tags
133
165
  )
@@ -140,6 +172,17 @@ class MlflowClient:
140
172
  step: Optional[int] = None,
141
173
  timestamp: Optional[int] = None,
142
174
  ):
175
+ """
176
+ Log a single metric to the MLflow run.
177
+
178
+ This method is thread-safe due to the `@lock_decorator`.
179
+
180
+ - **Args**:
181
+ key (str): The name of the metric.
182
+ value (float): The value of the metric.
183
+ step (int, optional): The step at which the metric was recorded. Defaults to None.
184
+ timestamp (int, optional): The timestamp when the metric was recorded. Defaults to None.
185
+ """
143
186
  if timestamp is not None:
144
187
  timestamp = int(timestamp)
145
188
  self.client.log_metric(
Binary file
Binary file
@@ -3,6 +3,7 @@
3
3
  """
4
4
 
5
5
  from .simulation import AgentSimulation
6
- from .storage.pg import PgWriter, create_pg_tables
7
6
 
8
- __all__ = ["AgentSimulation", "PgWriter", "create_pg_tables"]
7
+ __all__ = [
8
+ "AgentSimulation",
9
+ ]
@@ -26,6 +26,7 @@ from ..utils import (DIALOG_SCHEMA, INSTITUTION_STATUS_SCHEMA, PROFILE_SCHEMA,
26
26
  STATUS_SCHEMA, SURVEY_SCHEMA)
27
27
 
28
28
  logger = logging.getLogger("pycityagent")
29
+ __all__ = ["AgentGroup"]
29
30
 
30
31
 
31
32
  @ray.remote
@@ -37,7 +38,7 @@ class AgentGroup:
37
38
  memory_config_function_group: dict[type[Agent], Callable],
38
39
  config: dict,
39
40
  exp_name: str,
40
- exp_id: str | UUID,
41
+ exp_id: Union[str, UUID],
41
42
  enable_avro: bool,
42
43
  avro_path: Path,
43
44
  enable_pgsql: bool,
@@ -49,6 +50,33 @@ class AgentGroup:
49
50
  agent_config_file: Optional[dict[type[Agent], str]] = None,
50
51
  environment: Optional[dict[str, str]] = None,
51
52
  ):
53
+ """
54
+ Represents a group of agents that can be deployed in a Ray distributed environment.
55
+
56
+ - **Description**:
57
+ - Manages the creation and initialization of multiple agents, each potentially of different types,
58
+ with associated memory configurations, and connects them to various services such as MLflow, MQTT messager,
59
+ PostgreSQL writer, message interceptor, and LLM client. It also sets up an economy client and simulator for
60
+ agent interaction within a simulated environment.
61
+
62
+ - **Args**:
63
+ - `agent_class` (Union[Type[Agent], List[Type[Agent]]]): A single or list of agent classes to instantiate.
64
+ - `number_of_agents` (Union[int, List[int]]): Number of instances to create for each agent class.
65
+ - `memory_config_function_group` (Dict[Type[Agent], Callable]): Functions to configure memory for each agent type.
66
+ - `config` (dict): Configuration settings for the agent group.
67
+ - `exp_name` (str): Name of the experiment.
68
+ - `exp_id` (str | UUID): Identifier for the experiment.
69
+ - `enable_avro` (bool): Flag to enable AVRO file support.
70
+ - `avro_path` (Path): Path where AVRO files will be stored.
71
+ - `enable_pgsql` (bool): Flag to enable PostgreSQL support.
72
+ - `pgsql_writer` (ray.ObjectRef): Reference to a PostgreSQL writer object.
73
+ - `message_interceptor` (ray.ObjectRef): Reference to a message interceptor object.
74
+ - `mlflow_run_id` (str): Run identifier for MLflow tracking.
75
+ - `embedding_model` (Embeddings): Model used for generating embeddings.
76
+ - `logging_level` (int): Logging level for the agent group.
77
+ - `agent_config_file` (Optional[Dict[Type[Agent], str]], optional): File paths for loading agent configurations. Defaults to None.
78
+ - `environment` (Optional[Dict[str, str]], optional): Environment variables for the simulator. Defaults to None.
79
+ """
52
80
  logger.setLevel(logging_level)
53
81
  self._uuid = str(uuid.uuid4())
54
82
  if not isinstance(agent_class, list):
@@ -112,12 +140,13 @@ class AgentGroup:
112
140
  llmConfig = LLMConfig(config["llm_request"])
113
141
  logger.info(f"-----Creating LLM client in AgentGroup {self._uuid} ...")
114
142
  self.llm = LLM(llmConfig)
143
+ self.llm.set_semaphore(200)
115
144
 
116
145
  # prepare Simulator
117
146
  logger.info(f"-----Creating Simulator in AgentGroup {self._uuid} ...")
118
147
  self.simulator = Simulator(config["simulator_request"])
119
148
  self.projector = pyproj.Proj(self.simulator.map.header["projection"])
120
- self.simulator.set_environment(environment)
149
+ self.simulator.set_environment(environment) # type:ignore
121
150
  # prepare Economy client
122
151
  logger.info(f"-----Creating Economy client in AgentGroup {self._uuid} ...")
123
152
  self.economy_client = EconomyClient(
@@ -135,7 +164,9 @@ class AgentGroup:
135
164
  agent_class_i = agent_class[i]
136
165
  number_of_agents_i = number_of_agents[i]
137
166
  for j in range(number_of_agents_i):
138
- memory_config_function_group_i = memory_config_function_group[agent_class_i]
167
+ memory_config_function_group_i = memory_config_function_group[
168
+ agent_class_i
169
+ ]
139
170
  extra_attributes, profile, base = memory_config_function_group_i()
140
171
  memory = Memory(config=extra_attributes, profile=profile, base=base)
141
172
  agent = agent_class_i(
@@ -154,7 +185,10 @@ class AgentGroup:
154
185
  agent.set_avro_file(self.avro_file) # type: ignore
155
186
  if self.enable_pgsql:
156
187
  agent.set_pgsql_writer(self._pgsql_writer)
157
- if self.agent_config_file is not None and self.agent_config_file[agent_class_i]:
188
+ if (
189
+ self.agent_config_file is not None
190
+ and self.agent_config_file[agent_class_i]
191
+ ):
158
192
  agent.load_from_file(self.agent_config_file[agent_class_i])
159
193
  if self._message_interceptor is not None:
160
194
  agent.set_message_interceptor(self._message_interceptor)
@@ -172,6 +206,12 @@ class AgentGroup:
172
206
  @property
173
207
  def agent_type(self):
174
208
  return self.agent_class
209
+
210
+ async def get_economy_ids(self):
211
+ return await self.economy_client.get_ids()
212
+
213
+ async def set_economy_ids(self, agent_ids: set[int], org_ids: set[int]):
214
+ await self.economy_client.set_ids(agent_ids, org_ids)
175
215
 
176
216
  def get_agent_count(self):
177
217
  return self.agent_count
@@ -186,7 +226,7 @@ class AgentGroup:
186
226
  self.message_dispatch_task.cancel() # type: ignore
187
227
  await asyncio.gather(self.message_dispatch_task, return_exceptions=True) # type: ignore
188
228
 
189
- async def insert_agents(self):
229
+ async def insert_agent(self):
190
230
  bind_tasks = []
191
231
  for agent in self.agents:
192
232
  bind_tasks.append(agent.bind_to_simulator()) # type: ignore
@@ -200,7 +240,7 @@ class AgentGroup:
200
240
  if day == 0:
201
241
  break
202
242
  await asyncio.sleep(1)
203
- await self.insert_agents()
243
+ await self.insert_agent()
204
244
  self.id2agent = {agent._uuid: agent for agent in self.agents}
205
245
  logger.debug(f"-----Binding Agents to Messager in AgentGroup {self._uuid} ...")
206
246
  assert self.messager is not None
@@ -314,6 +354,17 @@ class AgentGroup:
314
354
  keys: Optional[list[str]] = None,
315
355
  values: Optional[list[Any]] = None,
316
356
  ) -> list[str]:
357
+ """
358
+ Filters agents based on type and/or key-value pairs in their status.
359
+
360
+ - **Args**:
361
+ - `types` (Optional[List[Type[Agent]]]): A list of agent types to filter by.
362
+ - `keys` (Optional[List[str]]): A list of keys to check in the agent's status.
363
+ - `values` (Optional[List[Any]]): The corresponding values for each key in the `keys` list.
364
+
365
+ - **Returns**:
366
+ - `List[str]`: A list of UUIDs for agents that match the filter criteria.
367
+ """
317
368
  filtered_uuids = []
318
369
  for agent in self.agents:
319
370
  add = True
@@ -340,6 +391,16 @@ class AgentGroup:
340
391
  async def gather(
341
392
  self, content: str, target_agent_uuids: Optional[list[str]] = None
342
393
  ):
394
+ """
395
+ Gathers specific content from all or targeted agents within the group.
396
+
397
+ - **Args**:
398
+ - `content` (str): The key of the status content to gather from the agents.
399
+ - `target_agent_uuids` (Optional[List[str]]): A list of agent UUIDs to target. If None, targets all agents.
400
+
401
+ - **Returns**:
402
+ - `Dict[str, Any]`: A dictionary mapping agent UUIDs to the gathered content.
403
+ """
343
404
  logger.debug(f"-----Gathering {content} from all agents in group {self._uuid}")
344
405
  results = {}
345
406
  if target_agent_uuids is None:
@@ -350,6 +411,14 @@ class AgentGroup:
350
411
  return results
351
412
 
352
413
  async def update(self, target_agent_uuid: str, target_key: str, content: Any):
414
+ """
415
+ Updates a specific key in the status of a targeted agent.
416
+
417
+ - **Args**:
418
+ - `target_agent_uuid` (str): The UUID of the agent to update.
419
+ - `target_key` (str): The key in the agent's status to update.
420
+ - `content` (Any): The new value for the specified key.
421
+ """
353
422
  logger.debug(
354
423
  f"-----Updating {target_key} for agent {target_agent_uuid} in group {self._uuid}"
355
424
  )
@@ -357,9 +426,25 @@ class AgentGroup:
357
426
  await agent.status.update(target_key, content)
358
427
 
359
428
  async def update_environment(self, key: str, value: str):
429
+ """
430
+ Updates the environment with a given key-value pair.
431
+
432
+ - **Args**:
433
+ - `key` (str): The key to update in the environment.
434
+ - `value` (str): The value to set for the specified key.
435
+ """
360
436
  self.simulator.update_environment(key, value)
361
437
 
362
438
  async def message_dispatch(self):
439
+ """
440
+ Dispatches messages received via MQTT to the appropriate agents.
441
+
442
+ - **Description**:
443
+ - Continuously listens for incoming MQTT messages and dispatches them to the relevant agents based on the topic.
444
+ - Messages are expected to have a topic formatted as "exps/{exp_id}/agents/{agent_uuid}/{topic_type}".
445
+ - The payload is decoded from bytes to string and then parsed as JSON.
446
+ - Depending on the `topic_type`, different handler methods on the agent are called to process the message.
447
+ """
363
448
  logger.debug(f"-----Starting message dispatch for group {self._uuid}")
364
449
  while True:
365
450
  assert self.messager is not None
@@ -402,6 +487,13 @@ class AgentGroup:
402
487
  async def save_status(
403
488
  self, simulator_day: Optional[int] = None, simulator_t: Optional[int] = None
404
489
  ):
490
+ """
491
+ Saves the current status of the agents at a given point in the simulation.
492
+
493
+ - **Args**:
494
+ - `simulator_day` (Optional[int]): The day number in the simulation time.
495
+ - `simulator_t` (Optional[int]): The tick or time unit in the simulation day.
496
+ """
405
497
  _statuses_time_list: list[tuple[dict, datetime]] = []
406
498
  if self.enable_avro:
407
499
  logger.debug(f"-----Saving status for group {self._uuid} with Avro")
@@ -603,54 +695,18 @@ class AgentGroup:
603
695
  lng, lat = self.projector(x, y, inverse=True)
604
696
  # ATTENTION: no valid position for an institution
605
697
  parent_id = -1
606
- try:
607
- nominal_gdp = await agent.status.get("nominal_gdp")
608
- except:
609
- nominal_gdp = []
610
- try:
611
- real_gdp = await agent.status.get("real_gdp")
612
- except:
613
- real_gdp = []
614
- try:
615
- unemployment = await agent.status.get("unemployment")
616
- except:
617
- unemployment = []
618
- try:
619
- wages = await agent.status.get("wages")
620
- except:
621
- wages = []
622
- try:
623
- prices = await agent.status.get("prices")
624
- except:
625
- prices = []
626
- try:
627
- inventory = await agent.status.get("inventory")
628
- except:
629
- inventory = 0
630
- try:
631
- price = await agent.status.get("price")
632
- except:
633
- price = 0.0
634
- try:
635
- interest_rate = await agent.status.get("interest_rate")
636
- except:
637
- interest_rate = 0.0
638
- try:
639
- bracket_cutoffs = await agent.status.get("bracket_cutoffs")
640
- except:
641
- bracket_cutoffs = []
642
- try:
643
- bracket_rates = await agent.status.get("bracket_rates")
644
- except:
645
- bracket_rates = []
646
- try:
647
- employees = await agent.status.get("employees")
648
- except:
649
- employees = []
650
- try:
651
- friend_ids = await agent.status.get("friends")
652
- except:
653
- friend_ids = []
698
+ nominal_gdp = await agent.status.get("nominal_gdp", [])
699
+ real_gdp = await agent.status.get("real_gdp", [])
700
+ unemployment = await agent.status.get("unemployment", [])
701
+ wages = await agent.status.get("wages", [])
702
+ prices = await agent.status.get("prices", [])
703
+ inventory = await agent.status.get("inventory", 0)
704
+ price = await agent.status.get("price", 0.0)
705
+ interest_rate = await agent.status.get("interest_rate", 0.0)
706
+ bracket_cutoffs = await agent.status.get("bracket_cutoffs", [])
707
+ bracket_rates = await agent.status.get("bracket_rates", [])
708
+ employees = await agent.status.get("employees", [])
709
+ friend_ids = await agent.status.get("friends", [])
654
710
  _status_dict = {
655
711
  "id": agent._uuid,
656
712
  "day": _day,
@@ -705,10 +761,40 @@ class AgentGroup:
705
761
  )
706
762
  )
707
763
 
764
+ def get_llm_consumption(self):
765
+ """
766
+ Retrieves the consumption statistics from the LLM client.
767
+
768
+ - **Returns**:
769
+ - The consumption data provided by the LLM client.
770
+ """
771
+ return self.llm.get_consumption()
772
+
708
773
  async def step(self):
774
+ """
775
+ Executes a single simulation step by running all agents concurrently.
776
+
777
+ - **Description**:
778
+ - This method initiates the `run` coroutine for each agent in parallel using asyncio.gather.
779
+ - Any exceptions raised during the execution are caught, logged, and re-raised as a RuntimeError.
780
+
781
+ - **Raises**:
782
+ - `RuntimeError`: If an exception occurs during the execution of any agent's `run` method.
783
+ """
709
784
  try:
710
785
  tasks = [agent.run() for agent in self.agents]
711
786
  await asyncio.gather(*tasks)
787
+ simulator_log = self.simulator.get_log_list() + self.economy_client.get_log_list()
788
+ group_logs = {
789
+ "llm_log": self.llm.get_log_list(),
790
+ "mqtt_log": ray.get(self.messager.get_log_list.remote()),
791
+ "simulator_log": simulator_log
792
+ }
793
+ self.llm.clear_log_list()
794
+ self.messager.clear_log_list.remote()
795
+ self.simulator.clear_log_list()
796
+ self.economy_client.clear_log_list()
797
+ return group_logs
712
798
  except Exception as e:
713
799
  import traceback
714
800
 
@@ -716,6 +802,16 @@ class AgentGroup:
716
802
  raise RuntimeError(str(e)) from e
717
803
 
718
804
  async def save(self, day: int, t: int):
805
+ """
806
+ Saves the current status of the agents at a given point in the simulation.
807
+
808
+ - **Args**:
809
+ - `day` (int): The day number in the simulation time.
810
+ - `t` (int): The tick or time unit within the simulation day.
811
+
812
+ - **Raises**:
813
+ - `RuntimeError`: If an exception occurs while saving the status.
814
+ """
719
815
  try:
720
816
  await self.save_status(day, t)
721
817
  except Exception as e: