pycityagent 2.0.0a43__cp312-cp312-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 (81) hide show
  1. pycityagent/__init__.py +23 -0
  2. pycityagent/agent.py +833 -0
  3. pycityagent/cli/wrapper.py +44 -0
  4. pycityagent/economy/__init__.py +5 -0
  5. pycityagent/economy/econ_client.py +355 -0
  6. pycityagent/environment/__init__.py +7 -0
  7. pycityagent/environment/interact/__init__.py +0 -0
  8. pycityagent/environment/interact/interact.py +198 -0
  9. pycityagent/environment/message/__init__.py +0 -0
  10. pycityagent/environment/sence/__init__.py +0 -0
  11. pycityagent/environment/sence/static.py +416 -0
  12. pycityagent/environment/sidecar/__init__.py +8 -0
  13. pycityagent/environment/sidecar/sidecarv2.py +109 -0
  14. pycityagent/environment/sim/__init__.py +29 -0
  15. pycityagent/environment/sim/aoi_service.py +39 -0
  16. pycityagent/environment/sim/client.py +126 -0
  17. pycityagent/environment/sim/clock_service.py +44 -0
  18. pycityagent/environment/sim/economy_services.py +192 -0
  19. pycityagent/environment/sim/lane_service.py +111 -0
  20. pycityagent/environment/sim/light_service.py +122 -0
  21. pycityagent/environment/sim/person_service.py +295 -0
  22. pycityagent/environment/sim/road_service.py +39 -0
  23. pycityagent/environment/sim/sim_env.py +145 -0
  24. pycityagent/environment/sim/social_service.py +59 -0
  25. pycityagent/environment/simulator.py +331 -0
  26. pycityagent/environment/utils/__init__.py +14 -0
  27. pycityagent/environment/utils/base64.py +16 -0
  28. pycityagent/environment/utils/const.py +244 -0
  29. pycityagent/environment/utils/geojson.py +24 -0
  30. pycityagent/environment/utils/grpc.py +57 -0
  31. pycityagent/environment/utils/map_utils.py +157 -0
  32. pycityagent/environment/utils/port.py +11 -0
  33. pycityagent/environment/utils/protobuf.py +41 -0
  34. pycityagent/llm/__init__.py +11 -0
  35. pycityagent/llm/embeddings.py +231 -0
  36. pycityagent/llm/llm.py +377 -0
  37. pycityagent/llm/llmconfig.py +13 -0
  38. pycityagent/llm/utils.py +6 -0
  39. pycityagent/memory/__init__.py +13 -0
  40. pycityagent/memory/const.py +43 -0
  41. pycityagent/memory/faiss_query.py +302 -0
  42. pycityagent/memory/memory.py +448 -0
  43. pycityagent/memory/memory_base.py +170 -0
  44. pycityagent/memory/profile.py +165 -0
  45. pycityagent/memory/self_define.py +165 -0
  46. pycityagent/memory/state.py +173 -0
  47. pycityagent/memory/utils.py +28 -0
  48. pycityagent/message/__init__.py +3 -0
  49. pycityagent/message/messager.py +88 -0
  50. pycityagent/metrics/__init__.py +6 -0
  51. pycityagent/metrics/mlflow_client.py +147 -0
  52. pycityagent/metrics/utils/const.py +0 -0
  53. pycityagent/pycityagent-sim +0 -0
  54. pycityagent/pycityagent-ui +0 -0
  55. pycityagent/simulation/__init__.py +8 -0
  56. pycityagent/simulation/agentgroup.py +580 -0
  57. pycityagent/simulation/simulation.py +634 -0
  58. pycityagent/simulation/storage/pg.py +184 -0
  59. pycityagent/survey/__init__.py +4 -0
  60. pycityagent/survey/manager.py +54 -0
  61. pycityagent/survey/models.py +120 -0
  62. pycityagent/utils/__init__.py +11 -0
  63. pycityagent/utils/avro_schema.py +109 -0
  64. pycityagent/utils/decorators.py +99 -0
  65. pycityagent/utils/parsers/__init__.py +13 -0
  66. pycityagent/utils/parsers/code_block_parser.py +37 -0
  67. pycityagent/utils/parsers/json_parser.py +86 -0
  68. pycityagent/utils/parsers/parser_base.py +60 -0
  69. pycityagent/utils/pg_query.py +92 -0
  70. pycityagent/utils/survey_util.py +53 -0
  71. pycityagent/workflow/__init__.py +26 -0
  72. pycityagent/workflow/block.py +211 -0
  73. pycityagent/workflow/prompt.py +79 -0
  74. pycityagent/workflow/tool.py +240 -0
  75. pycityagent/workflow/trigger.py +163 -0
  76. pycityagent-2.0.0a43.dist-info/LICENSE +21 -0
  77. pycityagent-2.0.0a43.dist-info/METADATA +235 -0
  78. pycityagent-2.0.0a43.dist-info/RECORD +81 -0
  79. pycityagent-2.0.0a43.dist-info/WHEEL +5 -0
  80. pycityagent-2.0.0a43.dist-info/entry_points.txt +3 -0
  81. pycityagent-2.0.0a43.dist-info/top_level.txt +3 -0
@@ -0,0 +1,165 @@
1
+ """
2
+ Agent Profile
3
+ """
4
+
5
+ from collections.abc import Callable, Sequence
6
+ from copy import deepcopy
7
+ from typing import Any, Optional, Union, cast
8
+
9
+ from ..utils.decorators import lock_decorator
10
+ from .const import *
11
+ from .memory_base import MemoryBase, MemoryUnit
12
+ from .utils import convert_msg_to_sequence
13
+
14
+
15
+ class ProfileMemoryUnit(MemoryUnit):
16
+ def __init__(
17
+ self,
18
+ content: Optional[dict] = None,
19
+ activate_timestamp: bool = False,
20
+ ) -> None:
21
+ super().__init__(
22
+ content=content,
23
+ required_attributes=PROFILE_ATTRIBUTES,
24
+ activate_timestamp=activate_timestamp,
25
+ )
26
+
27
+
28
+ class ProfileMemory(MemoryBase):
29
+ def __init__(
30
+ self,
31
+ msg: Optional[
32
+ Union[ProfileMemoryUnit, Sequence[ProfileMemoryUnit], dict, Sequence[dict]]
33
+ ] = None,
34
+ activate_timestamp: bool = False,
35
+ ) -> None:
36
+ super().__init__()
37
+ if msg is None:
38
+ msg = deepcopy(PROFILE_ATTRIBUTES)
39
+ self.activate_timestamp = activate_timestamp
40
+ msg = convert_msg_to_sequence(
41
+ msg,
42
+ sequence_type=ProfileMemoryUnit,
43
+ activate_timestamp=self.activate_timestamp,
44
+ )
45
+ for unit in msg:
46
+ self._memories[unit] = {}
47
+
48
+ @lock_decorator
49
+ async def add(
50
+ self, msg: Union[ProfileMemoryUnit, Sequence[ProfileMemoryUnit]]
51
+ ) -> None:
52
+ _memories = self._memories
53
+ msg = convert_msg_to_sequence(
54
+ msg,
55
+ sequence_type=ProfileMemoryUnit,
56
+ activate_timestamp=self.activate_timestamp,
57
+ )
58
+ for unit in msg:
59
+ if unit not in _memories:
60
+ _memories[unit] = {}
61
+
62
+ @lock_decorator
63
+ async def pop(self, index: int) -> ProfileMemoryUnit:
64
+
65
+ _memories = self._memories
66
+ try:
67
+ pop_unit = list(_memories.keys())[index]
68
+ _memories.pop(pop_unit)
69
+
70
+ return pop_unit
71
+ except IndexError as e:
72
+
73
+ raise ValueError(f"Index {index} not in memory!")
74
+
75
+ @lock_decorator
76
+ async def load(
77
+ self,
78
+ snapshots: Union[dict, Sequence[dict]],
79
+ reset_memory: bool = False,
80
+ ) -> None:
81
+ if reset_memory:
82
+ self._memories = {}
83
+ msg = convert_msg_to_sequence(
84
+ snapshots,
85
+ sequence_type=ProfileMemoryUnit,
86
+ activate_timestamp=self.activate_timestamp,
87
+ )
88
+ for unit in msg:
89
+ if unit not in self._memories:
90
+ self._memories[unit] = {}
91
+
92
+ @lock_decorator
93
+ async def export(
94
+ self,
95
+ ) -> Sequence[dict]:
96
+ _res = []
97
+ for m in self._memories.keys():
98
+ m = cast(ProfileMemoryUnit, m)
99
+ _dict_values = await m.dict_values()
100
+ _res.append(_dict_values)
101
+
102
+ return _res
103
+
104
+ async def reset(self) -> None:
105
+ self._memories = {}
106
+
107
+ # interact
108
+ @lock_decorator
109
+ async def get(self, key: Any):
110
+ _latest_memories = self._fetch_recent_memory()
111
+ _latest_memory: ProfileMemoryUnit = _latest_memories[-1]
112
+ return _latest_memory[key]
113
+
114
+ @lock_decorator
115
+ async def get_top_k(
116
+ self,
117
+ key: Any,
118
+ metric: Callable[[Any], Any],
119
+ top_k: Optional[int] = None,
120
+ preserve_order: bool = True,
121
+ ) -> Union[Sequence[Any], Any]:
122
+ _latest_memories = self._fetch_recent_memory()
123
+ _latest_memory: ProfileMemoryUnit = _latest_memories[-1]
124
+ _top_k = await _latest_memory.top_k_values(key, metric, top_k, preserve_order)
125
+
126
+ return _top_k
127
+
128
+ @lock_decorator
129
+ async def update(self, key: Any, value: Any, store_snapshot: bool = False):
130
+ _latest_memories = self._fetch_recent_memory()
131
+ _latest_memory: ProfileMemoryUnit = _latest_memories[-1]
132
+ if not store_snapshot:
133
+ # write in place
134
+ await _latest_memory.update({key: value})
135
+
136
+ else:
137
+ # insert new unit
138
+ _dict_values = await _latest_memory.dict_values()
139
+ _content = deepcopy(
140
+ {k: v for k, v in _dict_values.items() if k not in {TIME_STAMP_KEY}}
141
+ )
142
+ _content.update({key: value})
143
+ msg = ProfileMemoryUnit(_content, self.activate_timestamp)
144
+ for unit in [msg]:
145
+ if unit not in self._memories:
146
+ self._memories[unit] = {}
147
+
148
+ @lock_decorator
149
+ async def update_dict(self, to_update_dict: dict, store_snapshot: bool = False):
150
+ _latest_memories = self._fetch_recent_memory()
151
+ _latest_memory: ProfileMemoryUnit = _latest_memories[-1]
152
+ if not store_snapshot:
153
+ # write in place
154
+ await _latest_memory.update(to_update_dict)
155
+ else:
156
+ # insert new unit
157
+ _dict_values = await _latest_memory.dict_values()
158
+ _content = deepcopy(
159
+ {k: v for k, v in _dict_values.items() if k not in {TIME_STAMP_KEY}}
160
+ )
161
+ _content.update(to_update_dict)
162
+ msg = ProfileMemoryUnit(_content, self.activate_timestamp)
163
+ for unit in [msg]:
164
+ if unit not in self._memories:
165
+ self._memories[unit] = {}
@@ -0,0 +1,165 @@
1
+ """
2
+ Self Define Data
3
+ """
4
+
5
+ from collections.abc import Callable, Sequence
6
+ from copy import deepcopy
7
+ from typing import Any, Optional, Union, cast
8
+
9
+ from ..utils.decorators import lock_decorator
10
+ from .const import *
11
+ from .memory_base import MemoryBase, MemoryUnit
12
+ from .utils import convert_msg_to_sequence
13
+
14
+
15
+ class DynamicMemoryUnit(MemoryUnit):
16
+ def __init__(
17
+ self,
18
+ content: Optional[dict] = None,
19
+ required_attributes: Optional[dict] = None,
20
+ activate_timestamp: bool = False,
21
+ ) -> None:
22
+ super().__init__(
23
+ content=content,
24
+ required_attributes=required_attributes,
25
+ activate_timestamp=activate_timestamp,
26
+ )
27
+
28
+
29
+ class DynamicMemory(MemoryBase):
30
+
31
+ def __init__(
32
+ self,
33
+ required_attributes: dict[Any, Any],
34
+ activate_timestamp: bool = False,
35
+ ) -> None:
36
+ super().__init__()
37
+ self._required_attributes = deepcopy(required_attributes)
38
+ self.activate_timestamp = activate_timestamp
39
+ msg = DynamicMemoryUnit(
40
+ self._required_attributes, None, self.activate_timestamp
41
+ )
42
+ self._memories[msg] = {}
43
+
44
+ @lock_decorator
45
+ async def add(
46
+ self, msg: Union[DynamicMemoryUnit, Sequence[DynamicMemoryUnit]]
47
+ ) -> None:
48
+ _memories = self._memories
49
+ msg = convert_msg_to_sequence(
50
+ msg,
51
+ sequence_type=DynamicMemoryUnit,
52
+ activate_timestamp=self.activate_timestamp,
53
+ )
54
+ for unit in msg:
55
+ if unit not in _memories:
56
+ _memories[unit] = {}
57
+
58
+ @lock_decorator
59
+ async def pop(self, index: int) -> DynamicMemoryUnit:
60
+ _memories = self._memories
61
+ try:
62
+ pop_unit = list(_memories.keys())[index]
63
+ _memories.pop(pop_unit)
64
+
65
+ return pop_unit
66
+ except IndexError as e:
67
+
68
+ raise ValueError(f"Index {index} not in memory!")
69
+
70
+ @lock_decorator
71
+ async def load(
72
+ self,
73
+ snapshots: Union[dict, Sequence[dict]],
74
+ reset_memory: bool = False,
75
+ ) -> None:
76
+ if reset_memory:
77
+ self._memories = {}
78
+ msg = convert_msg_to_sequence(
79
+ snapshots,
80
+ sequence_type=DynamicMemoryUnit,
81
+ activate_timestamp=self.activate_timestamp,
82
+ )
83
+ for unit in msg:
84
+ if unit not in self._memories:
85
+ self._memories[unit] = {}
86
+
87
+ @lock_decorator
88
+ async def export(
89
+ self,
90
+ ) -> Sequence[dict]:
91
+ _res = []
92
+ for m in self._memories.keys():
93
+ m = cast(DynamicMemoryUnit, m)
94
+ _dict_values = await m.dict_values()
95
+ _res.append(_dict_values)
96
+
97
+ return _res
98
+
99
+ async def reset(self) -> None:
100
+ self._memories = {}
101
+
102
+ # interact
103
+ @lock_decorator
104
+ async def get(self, key: Any):
105
+ _latest_memories = self._fetch_recent_memory()
106
+ _latest_memory: DynamicMemoryUnit = _latest_memories[-1]
107
+
108
+ return _latest_memory[key]
109
+
110
+ @lock_decorator
111
+ async def get_top_k(
112
+ self,
113
+ key: Any,
114
+ metric: Callable[[Any], Any],
115
+ top_k: Optional[int] = None,
116
+ preserve_order: bool = True,
117
+ ) -> Union[Sequence[Any], Any]:
118
+ _latest_memories = self._fetch_recent_memory()
119
+ _latest_memory: DynamicMemoryUnit = _latest_memories[-1]
120
+ _top_k = await _latest_memory.top_k_values(key, metric, top_k, preserve_order)
121
+
122
+ return _top_k
123
+
124
+ @lock_decorator
125
+ async def update(self, key: Any, value: Any, store_snapshot: bool = False):
126
+ _latest_memories = self._fetch_recent_memory()
127
+ _latest_memory: DynamicMemoryUnit = _latest_memories[-1]
128
+ if not store_snapshot:
129
+ # write in place
130
+ await _latest_memory.update({key: value})
131
+
132
+ else:
133
+ # insert new unit
134
+ _dict_values = await _latest_memory.dict_values()
135
+ _content = deepcopy(
136
+ {k: v for k, v in _dict_values.items() if k not in {TIME_STAMP_KEY}}
137
+ )
138
+ _content.update({key: value})
139
+ msg = DynamicMemoryUnit(
140
+ _content, self._required_attributes, self.activate_timestamp
141
+ )
142
+ for unit in [msg]:
143
+ if unit not in self._memories:
144
+ self._memories[unit] = {}
145
+
146
+ @lock_decorator
147
+ async def update_dict(self, to_update_dict: dict, store_snapshot: bool = False):
148
+ _latest_memories = self._fetch_recent_memory()
149
+ _latest_memory: DynamicMemoryUnit = _latest_memories[-1]
150
+ if not store_snapshot:
151
+ # write in place
152
+ await _latest_memory.update(to_update_dict)
153
+ else:
154
+ # insert new unit
155
+ _dict_values = await _latest_memory.dict_values()
156
+ _content = deepcopy(
157
+ {k: v for k, v in _dict_values.items() if k not in {TIME_STAMP_KEY}}
158
+ )
159
+ _content.update(to_update_dict)
160
+ msg = DynamicMemoryUnit(
161
+ _content, self._required_attributes, self.activate_timestamp
162
+ )
163
+ for unit in [msg]:
164
+ if unit not in self._memories:
165
+ self._memories[unit] = {}
@@ -0,0 +1,173 @@
1
+ """
2
+ Agent State
3
+ """
4
+
5
+ from collections.abc import Callable, Sequence
6
+ from copy import deepcopy
7
+ from typing import Any, Optional, Union, cast
8
+
9
+ from ..utils.decorators import lock_decorator
10
+ from .const import *
11
+ from .memory_base import MemoryBase, MemoryUnit
12
+ from .utils import convert_msg_to_sequence
13
+
14
+
15
+ class StateMemoryUnit(MemoryUnit):
16
+ def __init__(
17
+ self,
18
+ content: Optional[dict] = None,
19
+ activate_timestamp: bool = False,
20
+ ) -> None:
21
+ super().__init__(
22
+ content=content,
23
+ required_attributes=STATE_ATTRIBUTES,
24
+ activate_timestamp=activate_timestamp,
25
+ )
26
+
27
+
28
+ class StateMemory(MemoryBase):
29
+ def __init__(
30
+ self,
31
+ msg: Optional[
32
+ Union[MemoryUnit, Sequence[MemoryUnit], dict, Sequence[dict]]
33
+ ] = None,
34
+ activate_timestamp: bool = False,
35
+ ) -> None:
36
+ super().__init__()
37
+ if msg is None:
38
+ msg = deepcopy(STATE_ATTRIBUTES)
39
+ self.activate_timestamp = activate_timestamp
40
+ msg = convert_msg_to_sequence(
41
+ msg,
42
+ sequence_type=StateMemoryUnit,
43
+ activate_timestamp=self.activate_timestamp,
44
+ )
45
+ for unit in msg:
46
+ self._memories[unit] = {}
47
+
48
+ @lock_decorator
49
+ async def add(self, msg: Union[MemoryUnit, Sequence[MemoryUnit]]) -> None:
50
+
51
+ _memories = self._memories
52
+ msg = convert_msg_to_sequence(
53
+ msg,
54
+ sequence_type=StateMemoryUnit,
55
+ activate_timestamp=self.activate_timestamp,
56
+ )
57
+ for unit in msg:
58
+ if unit not in _memories:
59
+ _memories[unit] = {}
60
+
61
+ @lock_decorator
62
+ async def pop(self, index: int) -> MemoryUnit:
63
+
64
+ _memories = self._memories
65
+ try:
66
+ pop_unit = list(_memories.keys())[index]
67
+ _memories.pop(pop_unit)
68
+
69
+ return pop_unit
70
+ except IndexError as e:
71
+
72
+ raise ValueError(f"Index {index} not in memory!")
73
+
74
+ @lock_decorator
75
+ async def load(
76
+ self,
77
+ snapshots: Union[dict, Sequence[dict]],
78
+ reset_memory: bool = False,
79
+ ) -> None:
80
+
81
+ if reset_memory:
82
+ self._memories = {}
83
+ msg = convert_msg_to_sequence(
84
+ snapshots,
85
+ sequence_type=StateMemoryUnit,
86
+ activate_timestamp=self.activate_timestamp,
87
+ )
88
+ for unit in msg:
89
+ if unit not in self._memories:
90
+ self._memories[unit] = {}
91
+
92
+ @lock_decorator
93
+ async def export(
94
+ self,
95
+ ) -> Sequence[dict]:
96
+
97
+ _res = []
98
+ for m in self._memories.keys():
99
+ m = cast(StateMemoryUnit, m)
100
+ _dict_values = await m.dict_values()
101
+ _res.append(_dict_values)
102
+
103
+ return _res
104
+
105
+ async def reset(self) -> None:
106
+
107
+ self._memories = {}
108
+
109
+ # interact
110
+ @lock_decorator
111
+ async def get(self, key: Any):
112
+
113
+ _latest_memories = self._fetch_recent_memory()
114
+ _latest_memory: StateMemoryUnit = _latest_memories[-1]
115
+
116
+ return _latest_memory[key]
117
+
118
+ @lock_decorator
119
+ async def get_top_k(
120
+ self,
121
+ key: Any,
122
+ metric: Callable[[Any], Any],
123
+ top_k: Optional[int] = None,
124
+ preserve_order: bool = True,
125
+ ) -> Union[Sequence[Any], Any]:
126
+
127
+ _latest_memories = self._fetch_recent_memory()
128
+ _latest_memory: StateMemoryUnit = _latest_memories[-1]
129
+ _top_k = await _latest_memory.top_k_values(key, metric, top_k, preserve_order)
130
+
131
+ return _top_k
132
+
133
+ @lock_decorator
134
+ async def update(self, key: Any, value: Any, store_snapshot: bool = False):
135
+
136
+ _latest_memories = self._fetch_recent_memory()
137
+ _latest_memory: StateMemoryUnit = _latest_memories[-1]
138
+ if not store_snapshot:
139
+ # write in place
140
+ await _latest_memory.update({key: value})
141
+
142
+ else:
143
+ # insert new unit
144
+ _dict_values = await _latest_memory.dict_values()
145
+ _content = deepcopy(
146
+ {k: v for k, v in _dict_values.items() if k not in {TIME_STAMP_KEY}}
147
+ )
148
+ _content.update({key: value})
149
+ msg = StateMemoryUnit(_content, self.activate_timestamp)
150
+ for unit in [msg]:
151
+ if unit not in self._memories:
152
+ self._memories[unit] = {}
153
+
154
+ @lock_decorator
155
+ async def update_dict(self, to_update_dict: dict, store_snapshot: bool = False):
156
+
157
+ _latest_memories = self._fetch_recent_memory()
158
+ _latest_memory: StateMemoryUnit = _latest_memories[-1]
159
+ if not store_snapshot:
160
+ # write in place
161
+ await _latest_memory.update(to_update_dict)
162
+
163
+ else:
164
+ # insert new unit
165
+ _dict_values = await _latest_memory.dict_values()
166
+ _content = deepcopy(
167
+ {k: v for k, v in _dict_values.items() if k not in {TIME_STAMP_KEY}}
168
+ )
169
+ _content.update(to_update_dict)
170
+ msg = StateMemoryUnit(_content, self.activate_timestamp)
171
+ for unit in [msg]:
172
+ if unit not in self._memories:
173
+ self._memories[unit] = {}
@@ -0,0 +1,28 @@
1
+ from collections.abc import Sequence
2
+ from typing import Any, Union
3
+
4
+ from .memory_base import MemoryUnit
5
+
6
+
7
+ def convert_msg_to_sequence(
8
+ msg: Union[Any, Sequence[Any]],
9
+ sequence_type=MemoryUnit,
10
+ activate_timestamp: bool = False,
11
+ ) -> Sequence[Any]:
12
+ # Convert the input message to a sequence if it is not already one
13
+ if not isinstance(msg, Sequence):
14
+ _sequence_msg = [msg]
15
+ else:
16
+ _sequence_msg = msg
17
+
18
+ # Initialize an empty list to store the converted MemoryUnit objects
19
+ _sequence_unit = []
20
+
21
+ # Iterate over each unit in the sequence
22
+ for unit in _sequence_msg:
23
+ # If the unit is not already a MemoryUnit, convert it to one
24
+ if not isinstance(unit, sequence_type):
25
+ unit = sequence_type(content=unit, activate_timestamp=activate_timestamp)
26
+ _sequence_unit.append(unit)
27
+
28
+ return _sequence_unit
@@ -0,0 +1,3 @@
1
+ from .messager import Messager
2
+
3
+ __all__ = ["Messager"]
@@ -0,0 +1,88 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import math
5
+ from typing import Any, List, Union
6
+ from aiomqtt import Client
7
+ import ray
8
+
9
+ logger = logging.getLogger("pycityagent")
10
+
11
+ @ray.remote
12
+ class Messager:
13
+ def __init__(
14
+ self, hostname:str, port:int=1883, username=None, password=None, timeout=60
15
+ ):
16
+ self.client = Client(
17
+ hostname, port=port, username=username, password=password, timeout=timeout
18
+ )
19
+ self.connected = False # 是否已连接标志
20
+ self.message_queue = asyncio.Queue() # 用于存储接收到的消息
21
+ self.receive_messages_task = None
22
+
23
+ async def __aexit__(self, exc_type, exc_value, traceback):
24
+ await self.stop()
25
+
26
+ async def connect(self):
27
+ for i in range(3):
28
+ try:
29
+ await self.client.__aenter__()
30
+ self.connected = True
31
+ logger.info("Connected to MQTT Broker")
32
+ return
33
+ except Exception as e:
34
+ logger.error(f"Attempt {i+1}: Failed to connect to MQTT Broker: {e}")
35
+ await asyncio.sleep(10)
36
+ self.connected = False
37
+ logger.error("All connection attempts failed.")
38
+
39
+ async def disconnect(self):
40
+ await self.client.__aexit__(None, None, None)
41
+ self.connected = False
42
+ logger.info("Disconnected from MQTT Broker")
43
+
44
+ def is_connected(self):
45
+ """检查是否成功连接到 Broker"""
46
+ return self.connected
47
+
48
+ async def subscribe(self, topics: Union[str, List[str]], agents: Union[Any, List[Any]]):
49
+ if not self.is_connected():
50
+ logger.error(
51
+ f"Cannot subscribe to {topics} because not connected to the Broker."
52
+ )
53
+ return
54
+ if not isinstance(topics, list):
55
+ topics = [topics]
56
+ if not isinstance(agents, list):
57
+ agents = [agents]
58
+ await self.client.subscribe(topics, qos=1)
59
+
60
+ async def receive_messages(self):
61
+ """监听并将消息存入队列"""
62
+ async for message in self.client.messages:
63
+ await self.message_queue.put(message)
64
+
65
+ async def fetch_messages(self):
66
+ """从队列中批量获取消息"""
67
+ messages = []
68
+ while not self.message_queue.empty():
69
+ messages.append(await self.message_queue.get())
70
+ return messages
71
+
72
+ async def send_message(self, topic: str, payload: dict):
73
+ """通过 Messager 发送消息"""
74
+ message = json.dumps(payload, default=str)
75
+ await self.client.publish(topic=topic, payload=message, qos=1)
76
+ logger.info(f"Message sent to {topic}: {message}")
77
+
78
+ async def start_listening(self):
79
+ """启动消息监听任务"""
80
+ if self.is_connected():
81
+ self.receive_messages_task = asyncio.create_task(self.receive_messages())
82
+ else:
83
+ logger.error("Cannot start listening because not connected to the Broker.")
84
+
85
+ async def stop(self):
86
+ self.receive_messages_task.cancel()
87
+ await asyncio.gather(self.receive_messages_task, return_exceptions=True)
88
+ await self.disconnect()
@@ -0,0 +1,6 @@
1
+ from .mlflow_client import MlflowClient,init_mlflow_connection
2
+
3
+ __all__ = [
4
+ "MlflowClient",
5
+ "init_mlflow_connection",
6
+ ]