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.
- pycityagent/__init__.py +23 -0
- pycityagent/agent.py +833 -0
- pycityagent/cli/wrapper.py +44 -0
- pycityagent/economy/__init__.py +5 -0
- pycityagent/economy/econ_client.py +355 -0
- pycityagent/environment/__init__.py +7 -0
- pycityagent/environment/interact/__init__.py +0 -0
- pycityagent/environment/interact/interact.py +198 -0
- pycityagent/environment/message/__init__.py +0 -0
- pycityagent/environment/sence/__init__.py +0 -0
- pycityagent/environment/sence/static.py +416 -0
- pycityagent/environment/sidecar/__init__.py +8 -0
- pycityagent/environment/sidecar/sidecarv2.py +109 -0
- pycityagent/environment/sim/__init__.py +29 -0
- pycityagent/environment/sim/aoi_service.py +39 -0
- pycityagent/environment/sim/client.py +126 -0
- pycityagent/environment/sim/clock_service.py +44 -0
- pycityagent/environment/sim/economy_services.py +192 -0
- pycityagent/environment/sim/lane_service.py +111 -0
- pycityagent/environment/sim/light_service.py +122 -0
- pycityagent/environment/sim/person_service.py +295 -0
- pycityagent/environment/sim/road_service.py +39 -0
- pycityagent/environment/sim/sim_env.py +145 -0
- pycityagent/environment/sim/social_service.py +59 -0
- pycityagent/environment/simulator.py +331 -0
- pycityagent/environment/utils/__init__.py +14 -0
- pycityagent/environment/utils/base64.py +16 -0
- pycityagent/environment/utils/const.py +244 -0
- pycityagent/environment/utils/geojson.py +24 -0
- pycityagent/environment/utils/grpc.py +57 -0
- pycityagent/environment/utils/map_utils.py +157 -0
- pycityagent/environment/utils/port.py +11 -0
- pycityagent/environment/utils/protobuf.py +41 -0
- pycityagent/llm/__init__.py +11 -0
- pycityagent/llm/embeddings.py +231 -0
- pycityagent/llm/llm.py +377 -0
- pycityagent/llm/llmconfig.py +13 -0
- pycityagent/llm/utils.py +6 -0
- pycityagent/memory/__init__.py +13 -0
- pycityagent/memory/const.py +43 -0
- pycityagent/memory/faiss_query.py +302 -0
- pycityagent/memory/memory.py +448 -0
- pycityagent/memory/memory_base.py +170 -0
- pycityagent/memory/profile.py +165 -0
- pycityagent/memory/self_define.py +165 -0
- pycityagent/memory/state.py +173 -0
- pycityagent/memory/utils.py +28 -0
- pycityagent/message/__init__.py +3 -0
- pycityagent/message/messager.py +88 -0
- pycityagent/metrics/__init__.py +6 -0
- pycityagent/metrics/mlflow_client.py +147 -0
- pycityagent/metrics/utils/const.py +0 -0
- pycityagent/pycityagent-sim +0 -0
- pycityagent/pycityagent-ui +0 -0
- pycityagent/simulation/__init__.py +8 -0
- pycityagent/simulation/agentgroup.py +580 -0
- pycityagent/simulation/simulation.py +634 -0
- pycityagent/simulation/storage/pg.py +184 -0
- pycityagent/survey/__init__.py +4 -0
- pycityagent/survey/manager.py +54 -0
- pycityagent/survey/models.py +120 -0
- pycityagent/utils/__init__.py +11 -0
- pycityagent/utils/avro_schema.py +109 -0
- pycityagent/utils/decorators.py +99 -0
- pycityagent/utils/parsers/__init__.py +13 -0
- pycityagent/utils/parsers/code_block_parser.py +37 -0
- pycityagent/utils/parsers/json_parser.py +86 -0
- pycityagent/utils/parsers/parser_base.py +60 -0
- pycityagent/utils/pg_query.py +92 -0
- pycityagent/utils/survey_util.py +53 -0
- pycityagent/workflow/__init__.py +26 -0
- pycityagent/workflow/block.py +211 -0
- pycityagent/workflow/prompt.py +79 -0
- pycityagent/workflow/tool.py +240 -0
- pycityagent/workflow/trigger.py +163 -0
- pycityagent-2.0.0a43.dist-info/LICENSE +21 -0
- pycityagent-2.0.0a43.dist-info/METADATA +235 -0
- pycityagent-2.0.0a43.dist-info/RECORD +81 -0
- pycityagent-2.0.0a43.dist-info/WHEEL +5 -0
- pycityagent-2.0.0a43.dist-info/entry_points.txt +3 -0
- 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,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()
|