pycityagent 2.0.0a6__py3-none-any.whl → 2.0.0a7__py3-none-any.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/agent.py +5 -3
- pycityagent/environment/interact/interact.py +86 -29
- pycityagent/environment/sence/static.py +3 -2
- pycityagent/environment/sim/aoi_service.py +1 -1
- pycityagent/environment/sim/economy_services.py +1 -1
- pycityagent/environment/sim/road_service.py +1 -1
- pycityagent/environment/sim/social_service.py +1 -1
- pycityagent/environment/simulator.py +6 -4
- pycityagent/environment/utils/__init__.py +5 -1
- pycityagent/llm/__init__.py +1 -1
- pycityagent/llm/embedding.py +36 -35
- pycityagent/llm/llm.py +197 -161
- pycityagent/llm/llmconfig.py +7 -9
- pycityagent/llm/utils.py +2 -2
- pycityagent/memory/memory.py +1 -2
- pycityagent/memory/memory_base.py +1 -2
- pycityagent/memory/profile.py +1 -2
- pycityagent/memory/self_define.py +1 -2
- pycityagent/memory/state.py +1 -2
- pycityagent/message/__init__.py +1 -1
- pycityagent/message/messager.py +11 -4
- pycityagent/simulation/__init__.py +1 -1
- pycityagent/simulation/agentgroup.py +13 -6
- pycityagent/simulation/interview.py +9 -5
- pycityagent/simulation/simulation.py +86 -33
- pycityagent/simulation/survey/__init__.py +1 -6
- pycityagent/simulation/survey/manager.py +22 -21
- pycityagent/simulation/survey/models.py +8 -5
- pycityagent/utils/decorators.py +14 -4
- pycityagent/utils/parsers/__init__.py +2 -1
- pycityagent/workflow/block.py +4 -3
- pycityagent/workflow/prompt.py +16 -9
- pycityagent/workflow/tool.py +1 -2
- pycityagent/workflow/trigger.py +36 -23
- {pycityagent-2.0.0a6.dist-info → pycityagent-2.0.0a7.dist-info}/METADATA +1 -1
- pycityagent-2.0.0a7.dist-info/RECORD +70 -0
- pycityagent-2.0.0a6.dist-info/RECORD +0 -70
- {pycityagent-2.0.0a6.dist-info → pycityagent-2.0.0a7.dist-info}/WHEEL +0 -0
pycityagent/agent.py
CHANGED
@@ -77,7 +77,7 @@ class Agent(ABC):
|
|
77
77
|
def __getstate__(self):
|
78
78
|
state = self.__dict__.copy()
|
79
79
|
# 排除锁对象
|
80
|
-
del state[
|
80
|
+
del state["_llm_client"]
|
81
81
|
return state
|
82
82
|
|
83
83
|
async def bind_to_simulator(self):
|
@@ -278,12 +278,14 @@ class Agent(ABC):
|
|
278
278
|
def get_interview_history(self) -> List[Dict]:
|
279
279
|
"""获取采访历史记录"""
|
280
280
|
return self._interview_history
|
281
|
-
|
281
|
+
|
282
282
|
async def handle_message(self, payload: str):
|
283
283
|
"""处理收到的消息,识别发送者"""
|
284
284
|
# 从消息中解析发送者 ID 和消息内容
|
285
285
|
message, sender_id = payload.split("|from:")
|
286
|
-
print(
|
286
|
+
print(
|
287
|
+
f"Agent {self._agent_id} received message: '{message}' from Agent {sender_id}"
|
288
|
+
)
|
287
289
|
|
288
290
|
async def send_message(self, to_agent_id: int, message: str):
|
289
291
|
"""通过 Messager 发送消息,附带发送者的 ID"""
|
@@ -1,9 +1,11 @@
|
|
1
1
|
"""环境相关的Interaction定义"""
|
2
|
+
|
2
3
|
from enum import Enum
|
3
4
|
from typing import Callable, Optional, Any
|
4
5
|
from abc import ABC, abstractmethod
|
5
6
|
from typing import Callable, Any
|
6
7
|
|
8
|
+
|
7
9
|
class ActionType(Enum):
|
8
10
|
"""
|
9
11
|
行动类型枚举 所有行动本质上为数据推送
|
@@ -14,24 +16,33 @@ class ActionType(Enum):
|
|
14
16
|
- Hub = 2, 用于表示与AppHub(前端)对接的行动
|
15
17
|
- Comp = 3, 表示综合类型 (可能同时包含与Sim以及Hub的交互)
|
16
18
|
"""
|
19
|
+
|
17
20
|
Sim = 1
|
18
21
|
Hub = 2
|
19
22
|
Comp = 3
|
20
|
-
|
23
|
+
|
24
|
+
|
21
25
|
class Action:
|
22
26
|
"""
|
23
27
|
- Action
|
24
28
|
"""
|
25
|
-
|
26
|
-
|
29
|
+
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
agent,
|
33
|
+
type: ActionType,
|
34
|
+
source: Optional[str] = None,
|
35
|
+
before: Optional[Callable[[list], Any]] = None,
|
36
|
+
) -> None:
|
37
|
+
"""
|
27
38
|
默认初始化
|
28
|
-
|
39
|
+
|
29
40
|
Args:
|
30
41
|
- agent (Agent): the related agent
|
31
42
|
- type (ActionType)
|
32
43
|
- source (str): 数据来源, 默认为None, 如果为None则会从接收用户传入的数据作为Forward函数参数, 否则从WM.Reason数据缓存中取对应数据作为参数
|
33
44
|
- before (function): 数据处理方法, 用于当Reason缓存中的参数与标准格式不符时使用
|
34
|
-
|
45
|
+
"""
|
35
46
|
self._agent = agent
|
36
47
|
self._type = type
|
37
48
|
self._source = source
|
@@ -51,16 +62,30 @@ class Action:
|
|
51
62
|
|
52
63
|
@abstractmethod
|
53
64
|
async def Forward(self):
|
54
|
-
|
65
|
+
"""接口函数"""
|
66
|
+
|
55
67
|
|
56
68
|
class SimAction(Action):
|
57
69
|
"""SimAction: 模拟器关联Action"""
|
58
|
-
|
70
|
+
|
71
|
+
def __init__(
|
72
|
+
self,
|
73
|
+
agent,
|
74
|
+
source: Optional[str] = None,
|
75
|
+
before: Optional[Callable[[list], Any]] = None,
|
76
|
+
) -> None:
|
59
77
|
super().__init__(agent, ActionType.Sim, source, before)
|
60
78
|
|
79
|
+
|
61
80
|
class HubAction(Action):
|
62
81
|
"""HubAction: Apphub关联Action"""
|
63
|
-
|
82
|
+
|
83
|
+
def __init__(
|
84
|
+
self,
|
85
|
+
agent,
|
86
|
+
source: Optional[str] = None,
|
87
|
+
before: Optional[Callable[[list], Any]] = None,
|
88
|
+
) -> None:
|
64
89
|
super().__init__(agent, ActionType.Hub, source, before)
|
65
90
|
|
66
91
|
|
@@ -69,46 +94,70 @@ class SetSchedule(SimAction):
|
|
69
94
|
用于将agent的行程信息同步至模拟器 —— 仅对citizen类型agent适用
|
70
95
|
Synchronize agent's schedule to simulator —— only avalable for citizen type of agent
|
71
96
|
"""
|
72
|
-
|
97
|
+
|
98
|
+
def __init__(
|
99
|
+
self,
|
100
|
+
agent,
|
101
|
+
source: Optional[str] = None,
|
102
|
+
before: Optional[Callable[[list], Any]] = None,
|
103
|
+
) -> None:
|
73
104
|
super().__init__(agent, source, before)
|
74
105
|
|
75
|
-
async def Forward(self, schedule
|
106
|
+
async def Forward(self, schedule=None):
|
76
107
|
"""
|
77
108
|
如果当前行程已经同步至模拟器: 跳过同步, 否则同步至模拟器
|
78
109
|
If current schedule has been synchronized to simulator: skip, else sync
|
79
110
|
"""
|
80
111
|
if not schedule == None:
|
81
112
|
if not schedule.is_set:
|
82
|
-
|
113
|
+
"""同步schedule至模拟器"""
|
83
114
|
self._agent.Scheduler.now.is_set = True
|
84
115
|
departure_time = schedule.time
|
85
116
|
mode = schedule.mode
|
86
117
|
aoi_id = schedule.target_id_aoi
|
87
118
|
poi_id = schedule.target_id_poi
|
88
|
-
end = {
|
119
|
+
end = {"aoi_position": {"aoi_id": aoi_id, "poi_id": poi_id}}
|
89
120
|
activity = schedule.description
|
90
|
-
trips = [
|
91
|
-
|
121
|
+
trips = [
|
122
|
+
{
|
123
|
+
"mode": mode,
|
124
|
+
"end": end,
|
125
|
+
"departure_time": departure_time,
|
126
|
+
"activity": activity,
|
127
|
+
}
|
128
|
+
]
|
129
|
+
set_schedule = [
|
130
|
+
{"trips": trips, "loop_count": 1, "departure_time": departure_time}
|
131
|
+
]
|
92
132
|
|
93
133
|
# * 与模拟器对接
|
94
|
-
req = {
|
134
|
+
req = {"person_id": self._agent._id, "schedules": set_schedule}
|
95
135
|
await self._agent._client.person_service.SetSchedule(req)
|
96
136
|
elif self._source != None:
|
97
137
|
schedule = self.get_source()
|
98
138
|
if schedule != None and not schedule.is_set:
|
99
|
-
|
139
|
+
"""同步schedule至模拟器"""
|
100
140
|
self._agent.Scheduler.now.is_set = True
|
101
141
|
departure_time = schedule.time
|
102
142
|
mode = schedule.mode
|
103
143
|
aoi_id = schedule.target_id_aoi
|
104
144
|
poi_id = schedule.target_id_poi
|
105
|
-
end = {
|
145
|
+
end = {"aoi_position": {"aoi_id": aoi_id, "poi_id": poi_id}}
|
106
146
|
activity = schedule.description
|
107
|
-
trips = [
|
108
|
-
|
147
|
+
trips = [
|
148
|
+
{
|
149
|
+
"mode": mode,
|
150
|
+
"end": end,
|
151
|
+
"departure_time": departure_time,
|
152
|
+
"activity": activity,
|
153
|
+
}
|
154
|
+
]
|
155
|
+
set_schedule = [
|
156
|
+
{"trips": trips, "loop_count": 1, "departure_time": departure_time}
|
157
|
+
]
|
109
158
|
|
110
159
|
# * 与模拟器对接
|
111
|
-
req = {
|
160
|
+
req = {"person_id": self._agent._id, "schedules": set_schedule}
|
112
161
|
await self._agent._client.person_service.SetSchedule(req)
|
113
162
|
|
114
163
|
|
@@ -117,25 +166,33 @@ class SendAgentMessage(SimAction):
|
|
117
166
|
发送信息给其他agent
|
118
167
|
Send messages to other agents
|
119
168
|
"""
|
120
|
-
|
169
|
+
|
170
|
+
def __init__(
|
171
|
+
self,
|
172
|
+
agent,
|
173
|
+
source: Optional[str] = None,
|
174
|
+
before: Optional[Callable[[list], Any]] = None,
|
175
|
+
) -> None:
|
121
176
|
super().__init__(agent, source, before)
|
122
177
|
|
123
178
|
async def Forward(self, messages: Optional[dict] = None):
|
124
179
|
if not messages == None and len(messages) > 0:
|
125
|
-
req = {
|
180
|
+
req = {"messages": []}
|
126
181
|
for message in messages:
|
127
182
|
from_id = self._agent._id
|
128
|
-
to_id = message[
|
129
|
-
mes = message[
|
130
|
-
req[
|
183
|
+
to_id = message["id"]
|
184
|
+
mes = message["message"]
|
185
|
+
req["messages"].append({"from": from_id, "to": to_id, "message": mes})
|
131
186
|
await self._agent._client.social_service.Send(req)
|
132
187
|
elif self._source != None:
|
133
188
|
messages = self.get_source()
|
134
189
|
if not messages == None and len(messages) > 0:
|
135
|
-
req = {
|
190
|
+
req = {"messages": []}
|
136
191
|
for message in messages:
|
137
192
|
from_id = self._agent._id
|
138
|
-
to_id = message[
|
139
|
-
mes = message[
|
140
|
-
req[
|
193
|
+
to_id = message["id"]
|
194
|
+
mes = message["message"]
|
195
|
+
req["messages"].append(
|
196
|
+
{"from": from_id, "to": to_id, "message": mes}
|
197
|
+
)
|
141
198
|
await self._agent._client.social_service.Send(req)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
静态数据支持; Static Resources: Poi Type association; Type prefix.
|
3
3
|
"""
|
4
|
+
|
4
5
|
POI_TYPE_DICT = {
|
5
6
|
"100000": "\u7f8e\u98df",
|
6
7
|
"101000": "\u7f8e\u98df:\u4e2d\u9910\u5385",
|
@@ -409,7 +410,7 @@ POI_TYPE_DICT = {
|
|
409
410
|
"801012": "\u5ba4\u5185\u53ca\u9644\u5c5e\u8bbe\u65bd:\u901a\u884c\u8bbe\u65bd\u7c7b:\u7535\u68af",
|
410
411
|
"801099": "\u5ba4\u5185\u53ca\u9644\u5c5e\u8bbe\u65bd:\u901a\u884c\u8bbe\u65bd\u7c7b:\u5176\u5b83\u901a\u884c\u8bbe\u65bd\u7c7b",
|
411
412
|
"809900": "\u5ba4\u5185\u53ca\u9644\u5c5e\u8bbe\u65bd:\u5176\u5b83\u5ba4\u5185\u53ca\u9644\u5c5e\u8bbe\u65bd",
|
412
|
-
"990000": "\u5176\u5b83"
|
413
|
+
"990000": "\u5176\u5b83",
|
413
414
|
}
|
414
415
|
|
415
|
-
LEVEL_ONE_PRE = ["1", "2", "8", "9"]
|
416
|
+
LEVEL_ONE_PRE = ["1", "2", "8", "9"]
|
@@ -52,8 +52,8 @@ class Simulator:
|
|
52
52
|
with open(_map_pb_path, "wb") as f:
|
53
53
|
f.write(_map_pb.SerializeToString())
|
54
54
|
|
55
|
-
if
|
56
|
-
if
|
55
|
+
if "simulator" in config:
|
56
|
+
if "server" not in config["simulator"]:
|
57
57
|
self._sim_env = sim_env = ControlSimEnv(
|
58
58
|
task_name=config["simulator"].get("task", "citysim"),
|
59
59
|
map_file=_map_pb_path,
|
@@ -71,9 +71,11 @@ class Simulator:
|
|
71
71
|
- grpc client of simulator
|
72
72
|
"""
|
73
73
|
else:
|
74
|
-
self._client = CityClient(config[
|
74
|
+
self._client = CityClient(config["simulator"]["server"], secure=False)
|
75
75
|
else:
|
76
|
-
logging.warning(
|
76
|
+
logging.warning(
|
77
|
+
"No simulator config found, no simulator client will be used"
|
78
|
+
)
|
77
79
|
self.map = SimMap(
|
78
80
|
mongo_uri=_mongo_uri,
|
79
81
|
mongo_db=_mongo_db,
|
@@ -7,4 +7,8 @@ from .geojson import wrap_feature_collection
|
|
7
7
|
from .base64 import encode_to_base64
|
8
8
|
from .port import find_free_port
|
9
9
|
|
10
|
-
__all__ = [
|
10
|
+
__all__ = [
|
11
|
+
"wrap_feature_collection",
|
12
|
+
"find_free_port",
|
13
|
+
"find_free_port",
|
14
|
+
]
|
pycityagent/llm/__init__.py
CHANGED
pycityagent/llm/embedding.py
CHANGED
@@ -5,16 +5,17 @@ from typing import List, Dict, Optional
|
|
5
5
|
import hashlib
|
6
6
|
import json
|
7
7
|
|
8
|
+
|
8
9
|
class SimpleEmbedding:
|
9
10
|
"""简单的基于内存的embedding实现
|
10
|
-
|
11
|
+
|
11
12
|
使用简单的词袋模型(Bag of Words)和TF-IDF来生成文本的向量表示。
|
12
13
|
所有向量都保存在内存中,适用于小规模应用。
|
13
14
|
"""
|
14
|
-
|
15
|
+
|
15
16
|
def __init__(self, vector_dim: int = 128, cache_size: int = 1000):
|
16
17
|
"""初始化
|
17
|
-
|
18
|
+
|
18
19
|
Args:
|
19
20
|
vector_dim: 向量维度
|
20
21
|
cache_size: 缓存大小,超过此大小将清除最早的缓存
|
@@ -25,29 +26,29 @@ class SimpleEmbedding:
|
|
25
26
|
self._vocab: Dict[str, int] = {} # 词汇表
|
26
27
|
self._idf: Dict[str, float] = {} # 逆文档频率
|
27
28
|
self._doc_count = 0 # 文档总数
|
28
|
-
|
29
|
+
|
29
30
|
def _text_to_hash(self, text: str) -> str:
|
30
31
|
"""将文本转换为hash值"""
|
31
32
|
return hashlib.md5(text.encode()).hexdigest()
|
32
|
-
|
33
|
+
|
33
34
|
def _tokenize(self, text: str) -> List[str]:
|
34
35
|
"""简单的分词"""
|
35
36
|
# 这里使用简单的空格分词,实际应用中可以使用更复杂的分词方法
|
36
37
|
return text.lower().split()
|
37
|
-
|
38
|
+
|
38
39
|
def _update_vocab(self, tokens: List[str]):
|
39
40
|
"""更新词汇表"""
|
40
41
|
for token in set(tokens): # 使用set去重
|
41
42
|
if token not in self._vocab:
|
42
43
|
self._vocab[token] = len(self._vocab)
|
43
|
-
|
44
|
+
|
44
45
|
def _update_idf(self, tokens: List[str]):
|
45
46
|
"""更新IDF值"""
|
46
47
|
self._doc_count += 1
|
47
48
|
unique_tokens = set(tokens)
|
48
49
|
for token in unique_tokens:
|
49
50
|
self._idf[token] = self._idf.get(token, 0) + 1
|
50
|
-
|
51
|
+
|
51
52
|
def _calculate_tf(self, tokens: List[str]) -> Dict[str, float]:
|
52
53
|
"""计算词频(TF)"""
|
53
54
|
tf = {}
|
@@ -58,31 +59,31 @@ class SimpleEmbedding:
|
|
58
59
|
for token in tf:
|
59
60
|
tf[token] /= total_tokens
|
60
61
|
return tf
|
61
|
-
|
62
|
+
|
62
63
|
def _calculate_tfidf(self, tokens: List[str]) -> np.ndarray:
|
63
64
|
"""计算TF-IDF向量"""
|
64
65
|
vector = np.zeros(self.vector_dim)
|
65
66
|
tf = self._calculate_tf(tokens)
|
66
|
-
|
67
|
+
|
67
68
|
for token, tf_value in tf.items():
|
68
69
|
if token in self._idf:
|
69
70
|
idf = np.log(self._doc_count / self._idf[token])
|
70
71
|
idx = self._vocab[token] % self.vector_dim # 使用取模运算来控制向量维度
|
71
72
|
vector[idx] += tf_value * idf
|
72
|
-
|
73
|
+
|
73
74
|
# L2归一化
|
74
75
|
norm = np.linalg.norm(vector)
|
75
76
|
if norm > 0:
|
76
77
|
vector /= norm
|
77
|
-
|
78
|
+
|
78
79
|
return vector
|
79
|
-
|
80
|
+
|
80
81
|
async def embed(self, text: str) -> np.ndarray:
|
81
82
|
"""生成文本的向量表示
|
82
|
-
|
83
|
+
|
83
84
|
Args:
|
84
85
|
text: 输入文本
|
85
|
-
|
86
|
+
|
86
87
|
Returns:
|
87
88
|
np.ndarray: 文本的向量表示
|
88
89
|
"""
|
@@ -90,47 +91,47 @@ class SimpleEmbedding:
|
|
90
91
|
text_hash = self._text_to_hash(text)
|
91
92
|
if text_hash in self._cache:
|
92
93
|
return self._cache[text_hash]
|
93
|
-
|
94
|
+
|
94
95
|
# 分词
|
95
96
|
tokens = self._tokenize(text)
|
96
97
|
if not tokens:
|
97
98
|
return np.zeros(self.vector_dim)
|
98
|
-
|
99
|
+
|
99
100
|
# 更新词汇表和IDF
|
100
101
|
self._update_vocab(tokens)
|
101
102
|
self._update_idf(tokens)
|
102
|
-
|
103
|
+
|
103
104
|
# 计算向量
|
104
105
|
vector = self._calculate_tfidf(tokens)
|
105
|
-
|
106
|
+
|
106
107
|
# 更新缓存
|
107
108
|
if len(self._cache) >= self.cache_size:
|
108
109
|
# 删除最早的缓存
|
109
110
|
oldest_key = next(iter(self._cache))
|
110
111
|
del self._cache[oldest_key]
|
111
112
|
self._cache[text_hash] = vector
|
112
|
-
|
113
|
+
|
113
114
|
return vector
|
114
|
-
|
115
|
+
|
115
116
|
def save(self, file_path: str):
|
116
117
|
"""保存模型"""
|
117
118
|
state = {
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
119
|
+
"vector_dim": self.vector_dim,
|
120
|
+
"cache_size": self.cache_size,
|
121
|
+
"vocab": self._vocab,
|
122
|
+
"idf": self._idf,
|
123
|
+
"doc_count": self._doc_count,
|
123
124
|
}
|
124
|
-
with open(file_path,
|
125
|
+
with open(file_path, "w") as f:
|
125
126
|
json.dump(state, f)
|
126
|
-
|
127
|
+
|
127
128
|
def load(self, file_path: str):
|
128
129
|
"""加载模型"""
|
129
|
-
with open(file_path,
|
130
|
+
with open(file_path, "r") as f:
|
130
131
|
state = json.load(f)
|
131
|
-
self.vector_dim = state[
|
132
|
-
self.cache_size = state[
|
133
|
-
self._vocab = state[
|
134
|
-
self._idf = state[
|
135
|
-
self._doc_count = state[
|
136
|
-
self._cache = {} # 清空缓存
|
132
|
+
self.vector_dim = state["vector_dim"]
|
133
|
+
self.cache_size = state["cache_size"]
|
134
|
+
self._vocab = state["vocab"]
|
135
|
+
self._idf = state["idf"]
|
136
|
+
self._doc_count = state["doc_count"]
|
137
|
+
self._cache = {} # 清空缓存
|