pycityagent 2.0.0a7__py3-none-any.whl → 2.0.0a8__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 +24 -2
- pycityagent/simulation/agentgroup.py +29 -8
- pycityagent/simulation/simulation.py +102 -35
- {pycityagent-2.0.0a7.dist-info → pycityagent-2.0.0a8.dist-info}/METADATA +1 -1
- {pycityagent-2.0.0a7.dist-info → pycityagent-2.0.0a8.dist-info}/RECORD +6 -6
- {pycityagent-2.0.0a7.dist-info → pycityagent-2.0.0a8.dist-info}/WHEEL +0 -0
pycityagent/agent.py
CHANGED
@@ -287,11 +287,13 @@ class Agent(ABC):
|
|
287
287
|
f"Agent {self._agent_id} received message: '{message}' from Agent {sender_id}"
|
288
288
|
)
|
289
289
|
|
290
|
-
async def send_message(
|
290
|
+
async def send_message(
|
291
|
+
self, to_agent_id: int, message: str, sub_topic: str = "chat"
|
292
|
+
):
|
291
293
|
"""通过 Messager 发送消息,附带发送者的 ID"""
|
292
294
|
if self._messager is None:
|
293
295
|
raise RuntimeError("Messager is not set")
|
294
|
-
topic = f"/exps/{self._exp_id}/agents/{to_agent_id}/
|
296
|
+
topic = f"/exps/{self._exp_id}/agents/{to_agent_id}/{sub_topic}"
|
295
297
|
await self._messager.send_message(topic, message, self._agent_id)
|
296
298
|
|
297
299
|
@abstractmethod
|
@@ -332,6 +334,13 @@ class CitizenAgent(Agent):
|
|
332
334
|
memory,
|
333
335
|
)
|
334
336
|
|
337
|
+
async def handle_gather_message(self, payload: str):
|
338
|
+
"""处理收到的消息,识别发送者"""
|
339
|
+
# 从消息中解析发送者 ID 和消息内容
|
340
|
+
target, sender_id = payload.split("|from:")
|
341
|
+
content = await self.memory.get(f"{target}")
|
342
|
+
await self.send_message(int(sender_id), content, "gather")
|
343
|
+
|
335
344
|
|
336
345
|
class InstitutionAgent(Agent):
|
337
346
|
"""
|
@@ -356,3 +365,16 @@ class InstitutionAgent(Agent):
|
|
356
365
|
simulator,
|
357
366
|
memory,
|
358
367
|
)
|
368
|
+
|
369
|
+
async def handle_gather_message(self, payload: str):
|
370
|
+
"""处理收到的消息,识别发送者"""
|
371
|
+
# 从消息中解析发送者 ID 和消息内容
|
372
|
+
content, sender_id = payload.split("|from:")
|
373
|
+
print(
|
374
|
+
f"Agent {self._agent_id} received gather message: '{content}' from Agent {sender_id}"
|
375
|
+
)
|
376
|
+
|
377
|
+
async def gather_messages(self, agent_ids: list[int], content: str):
|
378
|
+
"""从多个智能体收集消息"""
|
379
|
+
for agent_id in agent_ids:
|
380
|
+
await self.send_message(agent_id, content, "gather")
|
@@ -22,7 +22,7 @@ class AgentGroup:
|
|
22
22
|
password=config["simulator_request"]["mqtt"].get("password", None),
|
23
23
|
)
|
24
24
|
self.initialized = False
|
25
|
-
|
25
|
+
self.id2agent = {}
|
26
26
|
# Step:1 prepare LLM client
|
27
27
|
llmConfig = LLMConfig(config["llm_request"])
|
28
28
|
logging.info("-----Creating LLM client in remote...")
|
@@ -33,10 +33,13 @@ class AgentGroup:
|
|
33
33
|
self.simulator = Simulator(config["simulator_request"])
|
34
34
|
|
35
35
|
# Step:3 prepare Economy client
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
if "economy" in config["simulator_request"]:
|
37
|
+
logging.info("-----Creating Economy client in remote...")
|
38
|
+
self.economy_client = EconomyClient(
|
39
|
+
config["simulator_request"]["economy"]["server"]
|
40
|
+
)
|
41
|
+
else:
|
42
|
+
self.economy_client = None
|
40
43
|
|
41
44
|
for agent in self.agents:
|
42
45
|
agent.set_exp_id(self.exp_id)
|
@@ -56,8 +59,20 @@ class AgentGroup:
|
|
56
59
|
agent.set_messager(self.messager)
|
57
60
|
topic = f"/exps/{self.exp_id}/agents/{agent._agent_id}/chat"
|
58
61
|
await self.messager.subscribe(topic, agent)
|
62
|
+
topic = f"/exps/{self.exp_id}/agents/{agent._agent_id}/gather"
|
63
|
+
await self.messager.subscribe(topic, agent)
|
59
64
|
self.initialized = True
|
60
65
|
|
66
|
+
async def gather(self, content: str):
|
67
|
+
results = {}
|
68
|
+
for agent in self.agents:
|
69
|
+
results[agent._agent_id] = await agent.memory.get(content)
|
70
|
+
return results
|
71
|
+
|
72
|
+
async def update(self, target_agent_id: str, target_key: str, content: any):
|
73
|
+
agent = self.id2agent[target_agent_id]
|
74
|
+
await agent.memory.update(target_key, content)
|
75
|
+
|
61
76
|
async def step(self):
|
62
77
|
if not self.initialized:
|
63
78
|
await self.init_agents()
|
@@ -73,6 +88,8 @@ class AgentGroup:
|
|
73
88
|
# Step 2: 从 Messager 获取消息
|
74
89
|
messages = await self.messager.fetch_messages()
|
75
90
|
|
91
|
+
print(f"Received {len(messages)} messages")
|
92
|
+
|
76
93
|
# Step 3: 分发消息到对应的 Agent
|
77
94
|
for message in messages:
|
78
95
|
topic = message.topic.value
|
@@ -81,13 +98,17 @@ class AgentGroup:
|
|
81
98
|
# 添加解码步骤,将bytes转换为str
|
82
99
|
if isinstance(payload, bytes):
|
83
100
|
payload = payload.decode("utf-8")
|
84
|
-
|
85
|
-
|
101
|
+
|
102
|
+
# 提取 agent_id(主题格式为 "/exps/{exp_id}/agents/{agent_id}/chat" 或 "/exps/{exp_id}/agents/{agent_id}/gather")
|
103
|
+
_, _, _, agent_id, topic_type = topic.strip("/").split("/")
|
86
104
|
agent_id = int(agent_id)
|
87
105
|
|
88
106
|
if agent_id in self.id2agent:
|
89
107
|
agent = self.id2agent[agent_id]
|
90
|
-
|
108
|
+
if topic_type == "chat":
|
109
|
+
await agent.handle_message(payload)
|
110
|
+
elif topic_type == "gather":
|
111
|
+
await agent.handle_gather_message(payload)
|
91
112
|
|
92
113
|
# Step 4: 调用每个 Agent 的运行逻辑
|
93
114
|
tasks = [agent.run() for agent in self.agents]
|
@@ -4,7 +4,7 @@ import logging
|
|
4
4
|
import uuid
|
5
5
|
from datetime import datetime
|
6
6
|
import random
|
7
|
-
from typing import Dict, List, Optional, Callable
|
7
|
+
from typing import Dict, List, Optional, Callable, Union
|
8
8
|
from mosstool.map._map_util.const import AOI_START_ID
|
9
9
|
|
10
10
|
from pycityagent.memory.memory import Memory
|
@@ -22,7 +22,10 @@ class AgentSimulation:
|
|
22
22
|
"""城市智能体模拟器"""
|
23
23
|
|
24
24
|
def __init__(
|
25
|
-
self,
|
25
|
+
self,
|
26
|
+
agent_class: Union[type[Agent], list[type[Agent]]],
|
27
|
+
config: dict,
|
28
|
+
agent_prefix: str = "agent_",
|
26
29
|
):
|
27
30
|
"""
|
28
31
|
Args:
|
@@ -31,7 +34,10 @@ class AgentSimulation:
|
|
31
34
|
agent_prefix: 智能体名称前缀
|
32
35
|
"""
|
33
36
|
self.exp_id = uuid.uuid4()
|
34
|
-
|
37
|
+
if isinstance(agent_class, list):
|
38
|
+
self.agent_class = agent_class
|
39
|
+
else:
|
40
|
+
self.agent_class = [agent_class]
|
35
41
|
self.config = config
|
36
42
|
self.agent_prefix = agent_prefix
|
37
43
|
self._agents: Dict[str, Agent] = {}
|
@@ -44,53 +50,105 @@ class AgentSimulation:
|
|
44
50
|
self._loop = asyncio.get_event_loop()
|
45
51
|
self._blocked_agents: List[str] = [] # 新增:持续阻塞的智能体列表
|
46
52
|
self._survey_manager = SurveyManager()
|
53
|
+
self._agentid2group: Dict[str, AgentGroup] = {}
|
54
|
+
self._agent_ids: List[str] = []
|
47
55
|
|
48
56
|
async def init_agents(
|
49
57
|
self,
|
50
|
-
agent_count: int,
|
58
|
+
agent_count: Union[int, list[int]],
|
51
59
|
group_size: int = 1000,
|
52
|
-
memory_config_func: Callable = None,
|
60
|
+
memory_config_func: Union[Callable, list[Callable]] = None,
|
53
61
|
) -> None:
|
54
62
|
"""初始化智能体
|
55
63
|
|
56
64
|
Args:
|
57
|
-
agent_count:
|
65
|
+
agent_count: 要创建的总智能体数量, 如果为列表,则每个元素表示一个智能体类创建的智能体数量
|
58
66
|
group_size: 每个组的智能体数量,每一个组为一个独立的ray actor
|
59
|
-
memory_config_func: 返回Memory配置的函数,需要返回(EXTRA_ATTRIBUTES, PROFILE, BASE)
|
67
|
+
memory_config_func: 返回Memory配置的函数,需要返回(EXTRA_ATTRIBUTES, PROFILE, BASE)元组, 如果为列表,则每个元素表示一个智能体类创建的Memory配置函数
|
60
68
|
"""
|
61
|
-
if
|
62
|
-
|
69
|
+
if not isinstance(agent_count, list):
|
70
|
+
agent_count = [agent_count]
|
63
71
|
|
64
|
-
|
65
|
-
|
72
|
+
if len(self.agent_class) != len(agent_count):
|
73
|
+
raise ValueError("agent_class和agent_count的长度不一致")
|
66
74
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
config=extra_attributes, profile=profile.copy(), base=base.copy()
|
75
|
+
if memory_config_func is None:
|
76
|
+
logging.warning(
|
77
|
+
"memory_config_func is None, using default memory config function"
|
71
78
|
)
|
79
|
+
memory_config_func = [self.default_memory_config_func]
|
80
|
+
elif not isinstance(memory_config_func, list):
|
81
|
+
memory_config_func = [memory_config_func]
|
72
82
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
memory=memory,
|
83
|
+
if len(memory_config_func) != len(agent_count):
|
84
|
+
logging.warning(
|
85
|
+
"memory_config_func和agent_count的长度不一致,使用默认的memory_config_func"
|
77
86
|
)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
87
|
+
memory_config_func = [self.default_memory_config_func] * len(agent_count)
|
88
|
+
|
89
|
+
class_init_index = 0
|
90
|
+
for i in range(len(self.agent_class)):
|
91
|
+
agent_class = self.agent_class[i]
|
92
|
+
agent_count_i = agent_count[i]
|
93
|
+
memory_config_func_i = memory_config_func[i]
|
94
|
+
for j in range(agent_count_i):
|
95
|
+
agent_name = f"{self.agent_prefix}_{i}_{j}"
|
96
|
+
|
97
|
+
# 获取Memory配置
|
98
|
+
extra_attributes, profile, base = memory_config_func_i()
|
99
|
+
memory = Memory(
|
100
|
+
config=extra_attributes, profile=profile.copy(), base=base.copy()
|
101
|
+
)
|
102
|
+
|
103
|
+
# 创建智能体时传入Memory配置
|
104
|
+
agent = agent_class(
|
105
|
+
name=agent_name,
|
106
|
+
memory=memory,
|
107
|
+
)
|
108
|
+
|
109
|
+
self._agents[agent_name] = agent
|
110
|
+
|
111
|
+
# 计算需要的组数,向上取整以处理不足一组的情况
|
112
|
+
num_group = (agent_count_i + group_size - 1) // group_size
|
113
|
+
|
114
|
+
for k in range(num_group):
|
115
|
+
# 计算当前组的起始和结束索引
|
116
|
+
start_idx = class_init_index + k * group_size
|
117
|
+
end_idx = min(
|
118
|
+
class_init_index + start_idx + group_size,
|
119
|
+
class_init_index + agent_count_i,
|
120
|
+
)
|
121
|
+
|
122
|
+
# 获取当前组的agents
|
123
|
+
agents = list(self._agents.values())[start_idx:end_idx]
|
124
|
+
group_name = f"{self.agent_prefix}_{i}_group_{k}"
|
125
|
+
group = AgentGroup.remote(agents, self.config, self.exp_id)
|
126
|
+
self._groups[group_name] = group
|
127
|
+
|
128
|
+
class_init_index += agent_count_i # 更新类初始索引
|
129
|
+
|
130
|
+
init_tasks = []
|
131
|
+
for group in self._groups.values():
|
132
|
+
init_tasks.append(group.init_agents.remote())
|
133
|
+
await asyncio.gather(*init_tasks)
|
134
|
+
|
135
|
+
for group in self._groups.values():
|
136
|
+
agent_ids = await group.gather.remote("id")
|
137
|
+
for agent_id in agent_ids:
|
138
|
+
self._agent_ids.append(agent_id)
|
139
|
+
self._agentid2group[agent_id] = group
|
140
|
+
|
141
|
+
async def gather(self, content: str):
|
142
|
+
"""收集所有智能体的ID"""
|
143
|
+
gather_tasks = []
|
144
|
+
for group in self._groups.values():
|
145
|
+
gather_tasks.append(group.gather.remote(content))
|
146
|
+
return await asyncio.gather(*gather_tasks)
|
147
|
+
|
148
|
+
async def update(self, target_agent_id: str, target_key: str, content: any):
|
149
|
+
"""更新指定智能体的记忆"""
|
150
|
+
group = self._agentid2group[target_agent_id]
|
151
|
+
await group.update.remote(target_agent_id, target_key, content)
|
94
152
|
|
95
153
|
def default_memory_config_func(self):
|
96
154
|
"""默认的Memory配置函数"""
|
@@ -111,6 +169,15 @@ class AgentSimulation:
|
|
111
169
|
"current_step": (dict, {"intention": "", "type": ""}, True),
|
112
170
|
"execution_context": (dict, {}, True),
|
113
171
|
"plan_history": (list, [], True),
|
172
|
+
# cognition
|
173
|
+
"fulfillment": (int, 5, True),
|
174
|
+
"emotion": (int, 5, True),
|
175
|
+
"attitude": (int, 5, True),
|
176
|
+
"thought": (str, "Currently nothing good or bad is happening", True),
|
177
|
+
"emotion_types": (str, "Relief", True),
|
178
|
+
"incident": (list, [], True),
|
179
|
+
# social
|
180
|
+
"friends": (list, [], True),
|
114
181
|
}
|
115
182
|
|
116
183
|
PROFILE = {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
pycityagent/__init__.py,sha256=n56bWkAUEcvjDsb7LcJpaGjlrriSKPnR0yBhwRfEYBA,212
|
2
|
-
pycityagent/agent.py,sha256=
|
2
|
+
pycityagent/agent.py,sha256=cn8g9Sn8g3L5LaCb-zN831bW_sZWuUZbRydS3pegHdQ,12232
|
3
3
|
pycityagent/economy/__init__.py,sha256=aonY4WHnx-6EGJ4WKrx4S-2jAkYNLtqUA04jp6q8B7w,75
|
4
4
|
pycityagent/economy/econ_client.py,sha256=qQb_kZneEXGBRaS_y5Jdoi95I8GyjKEsDSC4s6V6R7w,10829
|
5
5
|
pycityagent/environment/__init__.py,sha256=awHxlOud-btWbk0FCS4RmGJ13W84oVCkbGfcrhKqihA,240
|
@@ -46,9 +46,9 @@ pycityagent/memory/utils.py,sha256=97lkenn-36wgt7uWb3Z39BXdJ5zlEQTQnQBFpoND1gg,8
|
|
46
46
|
pycityagent/message/__init__.py,sha256=TCjazxqb5DVwbTu1fF0sNvaH_EPXVuj2XQ0p6W-QCLU,55
|
47
47
|
pycityagent/message/messager.py,sha256=2hUIuX8Jqad2p6KM8PyfOWMFl1NjqF9B5yE-FnsYx3c,2598
|
48
48
|
pycityagent/simulation/__init__.py,sha256=jYaqaNpzM5M_e_ykISS_M-mIyYdzJXJWhgpfBpA6l5k,111
|
49
|
-
pycityagent/simulation/agentgroup.py,sha256=
|
49
|
+
pycityagent/simulation/agentgroup.py,sha256=Hu5IXFYZlaZ22m0xlDPBu89rRr1bJdSDeWWJIBxv92k,5233
|
50
50
|
pycityagent/simulation/interview.py,sha256=S2uv8MFCB4-u_4Q202VFoPJOIleqpKK9Piju0BDSb_0,1158
|
51
|
-
pycityagent/simulation/simulation.py,sha256=
|
51
|
+
pycityagent/simulation/simulation.py,sha256=7ONLou5qoA0dcVp3-mr6AFop6q3JqTyLudxfnbf9trc,16910
|
52
52
|
pycityagent/simulation/survey/__init__.py,sha256=rxwou8U9KeFSP7rMzXtmtp2fVFZxK4Trzi-psx9LPIs,153
|
53
53
|
pycityagent/simulation/survey/manager.py,sha256=Tqini-4-uBZDsChVVL4ezlgXatnrxAfvceAZZRP8E48,2062
|
54
54
|
pycityagent/simulation/survey/models.py,sha256=sY4OrrG1h9iBnjBsyDage4T3mUFPBHHZQe-ORtwSjKc,1305
|
@@ -65,6 +65,6 @@ pycityagent/workflow/block.py,sha256=6EmiRMLdOZC1wMlmLMIjfrp9TuiI7Gw4s3nnXVMbrnw
|
|
65
65
|
pycityagent/workflow/prompt.py,sha256=tY69nDO8fgYfF_dOA-iceR8pAhkYmCqoox8uRPqEuGY,2956
|
66
66
|
pycityagent/workflow/tool.py,sha256=wD9WZ5rma6HCKugtHTwbShNE0f-Rjlwvn_1be3fCAsk,6682
|
67
67
|
pycityagent/workflow/trigger.py,sha256=t5X_i0WtL32bipZSsq_E3UUyYYudYLxQUpvxbgClp2s,5683
|
68
|
-
pycityagent-2.0.
|
69
|
-
pycityagent-2.0.
|
70
|
-
pycityagent-2.0.
|
68
|
+
pycityagent-2.0.0a8.dist-info/METADATA,sha256=IR7pOLENcFN0uxU3lshslneGOmYHrRjuuyJ13x5BWSc,7614
|
69
|
+
pycityagent-2.0.0a8.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
70
|
+
pycityagent-2.0.0a8.dist-info/RECORD,,
|
File without changes
|