pycityagent 2.0.0a43__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.
- 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,240 @@
|
|
1
|
+
import time
|
2
|
+
from collections import defaultdict
|
3
|
+
from collections.abc import Callable, Sequence
|
4
|
+
from typing import Any, Optional, Union
|
5
|
+
|
6
|
+
from mlflow.entities import Metric
|
7
|
+
|
8
|
+
from ..agent import Agent
|
9
|
+
from ..environment import (LEVEL_ONE_PRE, POI_TYPE_DICT, AoiService,
|
10
|
+
PersonService)
|
11
|
+
from ..workflow import Block
|
12
|
+
|
13
|
+
|
14
|
+
class Tool:
|
15
|
+
"""Abstract tool class for callable tools. Can be bound to an `Agent` or `Block` instance.
|
16
|
+
|
17
|
+
This class serves as a base for creating various tools that can perform different operations.
|
18
|
+
It is intended to be subclassed by specific tool implementations.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __get__(self, instance, owner):
|
22
|
+
if instance is None:
|
23
|
+
return self
|
24
|
+
subclass = type(self)
|
25
|
+
if not hasattr(instance, "_tools"):
|
26
|
+
instance._tools = {}
|
27
|
+
if subclass not in instance._tools:
|
28
|
+
tool_instance = subclass()
|
29
|
+
tool_instance._instance = instance # type: ignore
|
30
|
+
instance._tools[subclass] = tool_instance
|
31
|
+
return instance._tools[subclass]
|
32
|
+
|
33
|
+
def __call__(self, *args: Any, **kwds: Any) -> Any:
|
34
|
+
"""Invoke the tool's functionality.
|
35
|
+
|
36
|
+
This method must be implemented by subclasses to provide specific behavior.
|
37
|
+
"""
|
38
|
+
raise NotImplementedError
|
39
|
+
|
40
|
+
@property
|
41
|
+
def agent(self) -> Agent:
|
42
|
+
instance = self._instance # type:ignore
|
43
|
+
if not isinstance(instance, Agent):
|
44
|
+
raise RuntimeError(
|
45
|
+
f"Tool bind to object `{type(instance).__name__}`, not an `Agent` object!"
|
46
|
+
)
|
47
|
+
return instance
|
48
|
+
|
49
|
+
@property
|
50
|
+
def block(self) -> Block:
|
51
|
+
instance = self._instance # type:ignore
|
52
|
+
if not isinstance(instance, Block):
|
53
|
+
raise RuntimeError(
|
54
|
+
f"Tool bind to object `{type(instance).__name__}`, not an `Block` object!"
|
55
|
+
)
|
56
|
+
return instance
|
57
|
+
|
58
|
+
|
59
|
+
class GetMap(Tool):
|
60
|
+
"""Retrieve the map from the simulator. Can be bound only to an `Agent` instance."""
|
61
|
+
|
62
|
+
def __init__(self) -> None:
|
63
|
+
self.variables = []
|
64
|
+
|
65
|
+
async def __call__(self) -> Union[Any, Callable]:
|
66
|
+
agent = self.agent
|
67
|
+
if agent.simulator is None:
|
68
|
+
raise ValueError("Simulator is not set.")
|
69
|
+
return agent.simulator.map
|
70
|
+
|
71
|
+
|
72
|
+
class SencePOI(Tool):
|
73
|
+
"""Retrieve the Point of Interest (POI) of the current scene.
|
74
|
+
|
75
|
+
This tool computes the POI based on the current `position` stored in memory and returns
|
76
|
+
points of interest (POIs) within a specified radius. Can be bound only to an `Agent` instance.
|
77
|
+
|
78
|
+
Attributes:
|
79
|
+
radius (int): The radius within which to search for POIs.
|
80
|
+
category_prefix (str): The prefix for the categories of POIs to consider.
|
81
|
+
variables (list[str]): A list of variables relevant to the tool's operation.
|
82
|
+
|
83
|
+
Args:
|
84
|
+
radius (int, optional): The circular search radius. Defaults to 100.
|
85
|
+
category_prefix (str, optional): The category prefix to filter POIs. Defaults to LEVEL_ONE_PRE.
|
86
|
+
|
87
|
+
Methods:
|
88
|
+
__call__(radius: Optional[int] = None, category_prefix: Optional[str] = None) -> Union[Any, Callable]:
|
89
|
+
Executes the AOI retrieval operation, returning POIs based on the current state of memory and simulator.
|
90
|
+
"""
|
91
|
+
|
92
|
+
def __init__(self, radius: int = 100, category_prefix=LEVEL_ONE_PRE) -> None:
|
93
|
+
self.radius = radius
|
94
|
+
self.category_prefix = category_prefix
|
95
|
+
self.variables = ["position"]
|
96
|
+
|
97
|
+
async def __call__(
|
98
|
+
self, radius: Optional[int] = None, category_prefix: Optional[str] = None
|
99
|
+
) -> Union[Any, Callable]:
|
100
|
+
"""Retrieve the POIs within the specified radius and category prefix.
|
101
|
+
|
102
|
+
If both `radius` and `category_prefix` are None, the method will use the current position
|
103
|
+
from memory to query POIs using the simulator. Otherwise, it will return a new instance
|
104
|
+
of SenceAoi with the specified parameters.
|
105
|
+
|
106
|
+
Args:
|
107
|
+
radius (Optional[int]): A specific radius for the AOI query. If not provided, defaults to the instance's radius.
|
108
|
+
category_prefix (Optional[str]): A specific category prefix to filter POIs. If not provided, defaults to the instance's category_prefix.
|
109
|
+
|
110
|
+
Raises:
|
111
|
+
ValueError: If memory or simulator is not set.
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
Union[Any, Callable]: The query results or a callable for a new SenceAoi instance.
|
115
|
+
"""
|
116
|
+
agent = self.agent
|
117
|
+
if agent.memory is None or agent.simulator is None:
|
118
|
+
raise ValueError("Memory or Simulator is not set.")
|
119
|
+
if radius is None and category_prefix is None:
|
120
|
+
position = await agent.memory.get("position")
|
121
|
+
resp = []
|
122
|
+
for prefix in self.category_prefix:
|
123
|
+
resp += agent.simulator.map.query_pois(
|
124
|
+
center=(position["xy_position"]["x"], position["xy_position"]["y"]),
|
125
|
+
radius=self.radius,
|
126
|
+
category_prefix=prefix,
|
127
|
+
)
|
128
|
+
# * Map six-digit codes to specific types
|
129
|
+
for poi in resp:
|
130
|
+
cate_str = poi[0]["category"]
|
131
|
+
poi[0]["category"] = POI_TYPE_DICT[cate_str]
|
132
|
+
else:
|
133
|
+
radius_ = radius if radius else self.radius
|
134
|
+
return SencePOI(radius_, category_prefix)
|
135
|
+
|
136
|
+
|
137
|
+
class UpdateWithSimulator(Tool):
|
138
|
+
def __init__(self) -> None:
|
139
|
+
pass
|
140
|
+
|
141
|
+
async def _update_motion_with_sim(
|
142
|
+
self,
|
143
|
+
):
|
144
|
+
agent = self.agent
|
145
|
+
if agent._simulator is None:
|
146
|
+
return
|
147
|
+
if not agent._has_bound_to_simulator:
|
148
|
+
await agent._bind_to_simulator() # type: ignore
|
149
|
+
simulator = agent.simulator
|
150
|
+
memory = agent.memory
|
151
|
+
person_id = await memory.get("id")
|
152
|
+
resp = await simulator.get_person(person_id)
|
153
|
+
resp_dict = resp["person"]
|
154
|
+
for k, v in resp_dict.get("motion", {}).items():
|
155
|
+
try:
|
156
|
+
await memory.get(k)
|
157
|
+
await memory.update(
|
158
|
+
k, v, mode="replace", protect_llm_read_only_fields=False
|
159
|
+
)
|
160
|
+
except KeyError as e:
|
161
|
+
continue
|
162
|
+
|
163
|
+
async def __call__(
|
164
|
+
self,
|
165
|
+
):
|
166
|
+
agent = self.agent
|
167
|
+
await self._update_motion_with_sim()
|
168
|
+
|
169
|
+
|
170
|
+
class ResetAgentPosition(Tool):
|
171
|
+
def __init__(self) -> None:
|
172
|
+
pass
|
173
|
+
|
174
|
+
async def __call__(
|
175
|
+
self,
|
176
|
+
aoi_id: Optional[int] = None,
|
177
|
+
poi_id: Optional[int] = None,
|
178
|
+
lane_id: Optional[int] = None,
|
179
|
+
s: Optional[float] = None,
|
180
|
+
):
|
181
|
+
agent = self.agent
|
182
|
+
memory = agent.memory
|
183
|
+
await agent.simulator.reset_person_position(
|
184
|
+
person_id=await memory.get("id"),
|
185
|
+
aoi_id=aoi_id,
|
186
|
+
poi_id=poi_id,
|
187
|
+
lane_id=lane_id,
|
188
|
+
s=s,
|
189
|
+
)
|
190
|
+
|
191
|
+
|
192
|
+
class ExportMlflowMetrics(Tool):
|
193
|
+
def __init__(self, log_batch_size: int = 100) -> None:
|
194
|
+
self._log_batch_size = log_batch_size
|
195
|
+
# TODO: support other log types
|
196
|
+
self.metric_log_cache: dict[str, list[Metric]] = defaultdict(list)
|
197
|
+
|
198
|
+
async def __call__(
|
199
|
+
self,
|
200
|
+
metric: Union[Sequence[Union[Metric, dict]], Union[Metric, dict]],
|
201
|
+
clear_cache: bool = False,
|
202
|
+
):
|
203
|
+
agent = self.agent
|
204
|
+
batch_size = self._log_batch_size
|
205
|
+
if not isinstance(metric, Sequence):
|
206
|
+
metric = [metric]
|
207
|
+
for _metric in metric:
|
208
|
+
if isinstance(_metric, Metric):
|
209
|
+
item = _metric
|
210
|
+
metric_key = item.key
|
211
|
+
else:
|
212
|
+
item = Metric(
|
213
|
+
key=_metric["key"],
|
214
|
+
value=_metric["value"],
|
215
|
+
timestamp=_metric.get("timestamp", int(1000 * time.time())),
|
216
|
+
step=_metric["step"],
|
217
|
+
)
|
218
|
+
metric_key = _metric["key"]
|
219
|
+
self.metric_log_cache[metric_key].append(item)
|
220
|
+
for metric_key, _cache in self.metric_log_cache.items():
|
221
|
+
if len(_cache) > batch_size:
|
222
|
+
client = agent.mlflow_client
|
223
|
+
await client.log_batch(
|
224
|
+
metrics=_cache[:batch_size],
|
225
|
+
)
|
226
|
+
_cache = _cache[batch_size:]
|
227
|
+
if clear_cache:
|
228
|
+
await self._clear_cache()
|
229
|
+
|
230
|
+
async def _clear_cache(
|
231
|
+
self,
|
232
|
+
):
|
233
|
+
agent = self.agent
|
234
|
+
client = agent.mlflow_client
|
235
|
+
for metric_key, _cache in self.metric_log_cache.items():
|
236
|
+
if len(_cache) > 0:
|
237
|
+
await client.log_batch(
|
238
|
+
metrics=_cache,
|
239
|
+
)
|
240
|
+
_cache = []
|
@@ -0,0 +1,163 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import Optional
|
3
|
+
import socket
|
4
|
+
from ..memory import Memory
|
5
|
+
from ..environment import Simulator
|
6
|
+
|
7
|
+
KEY_TRIGGER_COMPONENTS = [Memory, Simulator]
|
8
|
+
|
9
|
+
|
10
|
+
class EventTrigger:
|
11
|
+
"""Base class for event triggers that wait for specific conditions to be met."""
|
12
|
+
|
13
|
+
# 定义该trigger需要的组件类型
|
14
|
+
required_components: list[type] = []
|
15
|
+
|
16
|
+
def __init__(self, block=None):
|
17
|
+
self.block = block
|
18
|
+
if block is not None:
|
19
|
+
self.initialize()
|
20
|
+
|
21
|
+
def initialize(self) -> None:
|
22
|
+
"""Initialize the trigger with necessary dependencies."""
|
23
|
+
if not self.block:
|
24
|
+
raise RuntimeError("Block not set for trigger")
|
25
|
+
|
26
|
+
# 检查所需组件是否都存在
|
27
|
+
missing_components = []
|
28
|
+
for component_type in self.required_components:
|
29
|
+
component_name = component_type.__name__.lower()
|
30
|
+
if not hasattr(self.block, component_name):
|
31
|
+
missing_components.append(component_type.__name__)
|
32
|
+
|
33
|
+
if missing_components:
|
34
|
+
raise RuntimeError(
|
35
|
+
f"Block is missing required components for {self.__class__.__name__}: "
|
36
|
+
f"{', '.join(missing_components)}"
|
37
|
+
)
|
38
|
+
|
39
|
+
async def wait_for_trigger(self) -> None:
|
40
|
+
"""Wait for the event trigger to be activated.
|
41
|
+
|
42
|
+
Raises:
|
43
|
+
NotImplementedError: Subclasses must implement this method.
|
44
|
+
"""
|
45
|
+
raise NotImplementedError
|
46
|
+
|
47
|
+
|
48
|
+
class MemoryChangeTrigger(EventTrigger):
|
49
|
+
"""Event trigger that activates when a specific key in memory changes."""
|
50
|
+
|
51
|
+
required_components = [Memory]
|
52
|
+
|
53
|
+
def __init__(self, key: str) -> None:
|
54
|
+
"""Initialize the memory change trigger.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
key (str): The key in memory to monitor for changes.
|
58
|
+
"""
|
59
|
+
self.key = key
|
60
|
+
self.trigger_event = asyncio.Event()
|
61
|
+
self._initialized = False
|
62
|
+
super().__init__()
|
63
|
+
|
64
|
+
def initialize(self) -> None:
|
65
|
+
"""Initialize the trigger with memory from block."""
|
66
|
+
super().initialize() # 首先检查必需组件
|
67
|
+
self.memory = self.block.memory
|
68
|
+
asyncio.create_task(self.memory.add_watcher(self.key, self.trigger_event.set))
|
69
|
+
self._initialized = True
|
70
|
+
|
71
|
+
async def wait_for_trigger(self) -> None:
|
72
|
+
"""Wait for the memory change trigger to be activated."""
|
73
|
+
if not self._initialized:
|
74
|
+
raise RuntimeError("Trigger not properly initialized")
|
75
|
+
await self.trigger_event.wait()
|
76
|
+
self.trigger_event.clear()
|
77
|
+
|
78
|
+
|
79
|
+
class TimeTrigger(EventTrigger):
|
80
|
+
"""Event trigger that activates periodically based on time intervals."""
|
81
|
+
|
82
|
+
required_components = [Simulator]
|
83
|
+
|
84
|
+
def __init__(
|
85
|
+
self,
|
86
|
+
days: Optional[int] = None,
|
87
|
+
hours: Optional[int] = None,
|
88
|
+
minutes: Optional[int] = None,
|
89
|
+
) -> None:
|
90
|
+
"""Initialize the time trigger with interval settings.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
days (Optional[int]): Execute every N days
|
94
|
+
hours (Optional[int]): Execute every N hours
|
95
|
+
minutes (Optional[int]): Execute every N minutes
|
96
|
+
|
97
|
+
Raises:
|
98
|
+
ValueError: If all interval parameters are None or negative
|
99
|
+
"""
|
100
|
+
if all(param is None for param in (days, hours, minutes)):
|
101
|
+
raise ValueError("At least one time interval must be specified")
|
102
|
+
|
103
|
+
# 验证参数有效性
|
104
|
+
for param_name, param_value in [
|
105
|
+
("days", days),
|
106
|
+
("hours", hours),
|
107
|
+
("minutes", minutes),
|
108
|
+
]:
|
109
|
+
if param_value is not None and param_value < 0:
|
110
|
+
raise ValueError(f"{param_name} cannot be negative")
|
111
|
+
|
112
|
+
# 将所有时间间隔转换为秒
|
113
|
+
self.interval = 0
|
114
|
+
if days is not None:
|
115
|
+
self.interval += days * 24 * 60 * 60
|
116
|
+
if hours is not None:
|
117
|
+
self.interval += hours * 60 * 60
|
118
|
+
if minutes is not None:
|
119
|
+
self.interval += minutes * 60
|
120
|
+
|
121
|
+
self.trigger_event = asyncio.Event()
|
122
|
+
self._initialized = False
|
123
|
+
self._monitoring_task = None
|
124
|
+
self._last_trigger_time = None
|
125
|
+
super().__init__()
|
126
|
+
|
127
|
+
def initialize(self) -> None:
|
128
|
+
"""Initialize the trigger with necessary dependencies."""
|
129
|
+
super().initialize() # 首先检查必需组件
|
130
|
+
self.memory = self.block.memory
|
131
|
+
self.simulator = self.block.simulator
|
132
|
+
# 启动时间监控任务
|
133
|
+
self._monitoring_task = asyncio.create_task(self._monitor_time())
|
134
|
+
self._initialized = True
|
135
|
+
|
136
|
+
async def _monitor_time(self):
|
137
|
+
"""持续监控时间并在达到间隔时触发事件"""
|
138
|
+
# 第一次调用时直接触发
|
139
|
+
self.trigger_event.set()
|
140
|
+
|
141
|
+
while True:
|
142
|
+
try:
|
143
|
+
current_time = await self.simulator.get_time()
|
144
|
+
|
145
|
+
# 如果是第一次或者已经过了指定的时间间隔
|
146
|
+
if (
|
147
|
+
self._last_trigger_time is None
|
148
|
+
or current_time - self._last_trigger_time >= self.interval
|
149
|
+
):
|
150
|
+
self._last_trigger_time = current_time
|
151
|
+
self.trigger_event.set()
|
152
|
+
|
153
|
+
await asyncio.sleep(5) # 避免过于频繁的检查
|
154
|
+
except Exception as e:
|
155
|
+
print(f"Error in time monitoring: {e}")
|
156
|
+
await asyncio.sleep(10) # 发生错误时等待较长时间
|
157
|
+
|
158
|
+
async def wait_for_trigger(self) -> None:
|
159
|
+
"""Wait for the time trigger to be activated."""
|
160
|
+
if not self._initialized:
|
161
|
+
raise RuntimeError("Trigger not properly initialized")
|
162
|
+
await self.trigger_event.wait()
|
163
|
+
self.trigger_event.clear()
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 FIB LAB, Tsinghua University
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1,235 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: pycityagent
|
3
|
+
Version: 2.0.0a43
|
4
|
+
Summary: LLM-based城市环境agent构建库
|
5
|
+
Author-email: Yuwei Yan <pinkgranite86@gmail.com>, Junbo Yan <yanjb20thu@gmali.com>, Jun Zhang <zhangjun990222@gmali.com>
|
6
|
+
License: MIT License
|
7
|
+
|
8
|
+
Copyright (c) 2024 FIB LAB, Tsinghua University
|
9
|
+
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
12
|
+
in the Software without restriction, including without limitation the rights
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
15
|
+
furnished to do so, subject to the following conditions:
|
16
|
+
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
18
|
+
copies or substantial portions of the Software.
|
19
|
+
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
26
|
+
SOFTWARE.
|
27
|
+
Project-URL: Homepage, https://github.com/tsinghua-fib-lab/pycityagent
|
28
|
+
Project-URL: Repository, https://github.com/tsinghua-fib-lab/pycityagent.git
|
29
|
+
Project-URL: Issues, https://github.com/tsinghua-fib-lab/pycityagent/issues
|
30
|
+
Classifier: Programming Language :: Python :: 3
|
31
|
+
Classifier: Operating System :: POSIX :: Linux
|
32
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
33
|
+
Requires-Python: >=3.9
|
34
|
+
Description-Content-Type: text/markdown
|
35
|
+
License-File: LICENSE
|
36
|
+
Requires-Dist: aiohttp>=3.10.10
|
37
|
+
Requires-Dist: citystreetview>=1.2.4
|
38
|
+
Requires-Dist: dashscope>=1.14.1
|
39
|
+
Requires-Dist: geojson>=3.1.0
|
40
|
+
Requires-Dist: grpcio<2.0.0,>=1.67.1
|
41
|
+
Requires-Dist: matplotlib>=3.8.3
|
42
|
+
Requires-Dist: networkx>=3.2.1
|
43
|
+
Requires-Dist: numpy<2.0.0,>=1.20.0
|
44
|
+
Requires-Dist: openai>=1.58.1
|
45
|
+
Requires-Dist: Pillow<12.0.0,>=11.0.0
|
46
|
+
Requires-Dist: protobuf<5.0.0,<=4.24.0
|
47
|
+
Requires-Dist: pycitydata>=1.0.3
|
48
|
+
Requires-Dist: pycityproto>=2.1.5
|
49
|
+
Requires-Dist: requests>=2.32.3
|
50
|
+
Requires-Dist: Shapely>=2.0.6
|
51
|
+
Requires-Dist: PyYAML>=6.0.2
|
52
|
+
Requires-Dist: zhipuai>=2.1.5.20230904
|
53
|
+
Requires-Dist: gradio>=5.7.1
|
54
|
+
Requires-Dist: mosstool>=1.3.0
|
55
|
+
Requires-Dist: ray>=2.40.0
|
56
|
+
Requires-Dist: aiomqtt>=2.3.0
|
57
|
+
Requires-Dist: fastavro>=1.10.0
|
58
|
+
Requires-Dist: pandavro>=1.8.0
|
59
|
+
Requires-Dist: langchain-core>=0.3.28
|
60
|
+
Requires-Dist: mlflow>=2.19.0
|
61
|
+
Requires-Dist: psycopg[binary]>=3.2.3
|
62
|
+
Requires-Dist: transformers>=4.47.1
|
63
|
+
Requires-Dist: torch>=2.5.1
|
64
|
+
Requires-Dist: faiss-cpu>=1.9.0.post1
|
65
|
+
Requires-Dist: langchain-community>=0.3.13
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
<div style="text-align: center; background-color: white; padding: 20px; border-radius: 30px;">
|
70
|
+
<img src="./static/cityagent_logo.png" alt="CityAgent Logo" width="200" style="display: block; margin: 0 auto;">
|
71
|
+
<h1 style="color: black; margin: 0; font-size: 3em;">CityAgent: LLM Agents in City</h1>
|
72
|
+
</div>
|
73
|
+
|
74
|
+
|
75
|
+
# 🚀 CityAgent
|
76
|
+
  
|
77
|
+
[](https://docs.fiblab.net/pycityagent)  
|
78
|
+
|
79
|
+
|
80
|
+
CityAgent is an advanced framework specifically designed for building intelligent agents in urban simulation environments. With CityAgent, you can easily create and manage agents, enabling complex urban scenarios to be modeled and simulated efficiently.
|
81
|
+
|
82
|
+
## 🌟 Features
|
83
|
+
- **Modular Design**: Plug-and-play components for agent behavior.
|
84
|
+
- **Urban Environment Simulation**: Built to simulate diverse urban scenarios.
|
85
|
+
- **LLM Integration**: Connects to language models for enriched agent behavior.
|
86
|
+
- **Flexible Configuration**: YAML-based configuration for easy customization.
|
87
|
+
|
88
|
+
## 📑 Table of Contents
|
89
|
+
|
90
|
+
1. [News](#news)
|
91
|
+
2. [Framework](#framework)
|
92
|
+
3. [Setup](#setup)
|
93
|
+
4. [QuickStart](#quickstart)
|
94
|
+
5. [Contributing](#contributing)
|
95
|
+
6. [License](#license)
|
96
|
+
|
97
|
+
<a id="news"></a>
|
98
|
+
## 📰 News
|
99
|
+
|
100
|
+
- 📢 **11.10** - Initial update is now live!
|
101
|
+
- 📢 **2.x version is not compatible* with 1.x version**
|
102
|
+
|
103
|
+
Stay tuned for upcoming updates!
|
104
|
+
|
105
|
+
<a id="framework"></a>
|
106
|
+
## 🛠️ Framework
|
107
|
+
|
108
|
+
CityAgent is built with a multi-layered architecture that allows users to create and manage intelligent agents for urban environments in a scalable and flexible manner. The framework is divided into several key layers, each responsible for different functionalities as depicted in the diagram below:
|
109
|
+
|
110
|
+
<img src="./static/framework.png" alt="CityAgent Framework Overview" width="600" style="display: block; margin: 20px auto;">
|
111
|
+
|
112
|
+
### Architectural Layers
|
113
|
+
- **Model Layer**: Handles agent configuration, task definitions, logging setup, and result aggregation.
|
114
|
+
- **Task Configuration**: Defines agent behaviors and objectives.
|
115
|
+
- **Unified Execution**: Centralized entry point for agent processes.
|
116
|
+
|
117
|
+
- **Agent Layer**: Implements multi-head workflows to manage agent actions.
|
118
|
+
- **Memory**: Stores agent-related information, such as location and motion.
|
119
|
+
- **Static Profiles**: Maintains unchanging agent attributes.
|
120
|
+
- **Custom Data Pool**: Functions as a working memory.
|
121
|
+
- **Multi-Head Workflow**: Supports both normal and event-driven modes.
|
122
|
+
- **Reason Block**: Utilizes LLMs to determine decisions based on context and tools.
|
123
|
+
- **Route Block**: Chooses the best path based on defined criteria using LLMs or rules.
|
124
|
+
- **Action Block**: Executes actions as per defined contexts and tools.
|
125
|
+
|
126
|
+
- **Message Layer**: Facilitates agent communication through peer-to-peer (P2P), peer-to-group (P2G), and group chats.
|
127
|
+
|
128
|
+
- **Environment Layer**: Manages interaction with the urban environment.
|
129
|
+
- **Environment Sensing**: Reads data from the environment.
|
130
|
+
- **Interaction Handling**: Writes or modifies environmental states.
|
131
|
+
- **Message Management**: Handles incoming and outgoing agent messages.
|
132
|
+
|
133
|
+
- **LLM Layer**: Provides configuration and integration for using LLMs in the agent's workflow.
|
134
|
+
- **Prompting & Execution**: Supports model invocation and monitoring.
|
135
|
+
- **Model Support**: Compatible with various LLMs, such as OpenAI, Qwen, Deepseek, etc.
|
136
|
+
|
137
|
+
- **Tool Layer**: Provides additional utilities to the agents.
|
138
|
+
- **String Processing**: Handles parsing and formatting.
|
139
|
+
- **Result Analysis**: Parses responses in formats like JSON or dictionaries.
|
140
|
+
- **Data Storage & Retrieval**: Includes ranking and search tools.
|
141
|
+
|
142
|
+
<a id="setup"></a>
|
143
|
+
## ⚙️ Setup
|
144
|
+
|
145
|
+
You can set up CityAgent in two different ways:
|
146
|
+
|
147
|
+
### 1. From Scratch
|
148
|
+
|
149
|
+
Follow these steps to set up CityAgent from scratch by cloning the repository. The project is built using Python and managed with Poetry.
|
150
|
+
|
151
|
+
1. **Clone the Repository**
|
152
|
+
```bash
|
153
|
+
git clone [This Repository]
|
154
|
+
```
|
155
|
+
2. **Navigate to the Project Directory**
|
156
|
+
```bash
|
157
|
+
cd pycityagent
|
158
|
+
```
|
159
|
+
3. **Install Poetry** (if not installed)
|
160
|
+
```bash
|
161
|
+
curl -sSL https://install.python-poetry.org | python3 -
|
162
|
+
```
|
163
|
+
4. **Install Dependencies**
|
164
|
+
```bash
|
165
|
+
poetry install
|
166
|
+
```
|
167
|
+
5. **Activate the Virtual Environment**
|
168
|
+
```bash
|
169
|
+
poetry shell
|
170
|
+
```
|
171
|
+
|
172
|
+
### 2. Install via pip
|
173
|
+
|
174
|
+
This method is not yet available. Stay tuned for future updates!
|
175
|
+
|
176
|
+
<a id="quickstart"></a>
|
177
|
+
## 🚀 QuickStart
|
178
|
+
|
179
|
+
Get started with CityAgent in just a few minutes!
|
180
|
+
|
181
|
+
### 1. Config Configuration
|
182
|
+
CityAgent uses a configuration file written in `.yaml` format to manage settings for various components. Below is a sample configuration file (`config_template.yaml`) that showcases the structure:
|
183
|
+
|
184
|
+
```yaml
|
185
|
+
llm_request:
|
186
|
+
text_request:
|
187
|
+
request_type: openai
|
188
|
+
api_key: <YOUR_API_KEY>
|
189
|
+
model: gpt-4o
|
190
|
+
img_understand_request:
|
191
|
+
request_type: none
|
192
|
+
api_key: none
|
193
|
+
model: none
|
194
|
+
img_generate_request:
|
195
|
+
request_type: none
|
196
|
+
api_key: none
|
197
|
+
model: none
|
198
|
+
|
199
|
+
simulator_request:
|
200
|
+
simulator:
|
201
|
+
server: https://api-opencity-2x.fiblab.net:58081
|
202
|
+
map_request:
|
203
|
+
mongo_uri: <MONGO_URI>
|
204
|
+
mongo_db: llmsim
|
205
|
+
mongo_coll: map_beijing5ring_withpoi_0424
|
206
|
+
cache_dir: ./cache
|
207
|
+
route_request:
|
208
|
+
server: http://api-opencity-2x.fiblab.net:58082
|
209
|
+
streetview_request:
|
210
|
+
engine: baidumap / googlemap
|
211
|
+
mapAK: baidumap api-key (if you use baidumap engine)
|
212
|
+
proxy: googlemap proxy (if you use googlemap engine)
|
213
|
+
```
|
214
|
+
|
215
|
+
### 2. Example Usage
|
216
|
+
To get started quickly, please refer to the `examples` folder in the repository. It contains sample scripts and configurations to help you understand how to create and use agents in an urban simulation environment.
|
217
|
+
|
218
|
+
<a id="contributing"></a>
|
219
|
+
## 🤝 Contributing
|
220
|
+
We welcome contributions from the community!.
|
221
|
+
|
222
|
+
<a id="license"></a>
|
223
|
+
## 📄 License
|
224
|
+
|
225
|
+
CityAgent is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
|
226
|
+
|
227
|
+
---
|
228
|
+
|
229
|
+
Feel free to reach out if you have any questions, suggestions, or want to collaborate!
|
230
|
+
|
231
|
+
---
|
232
|
+
|
233
|
+
> **Follow us**: Stay updated with the latest news and features by watching the repository.
|
234
|
+
|
235
|
+
---
|