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 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(self, to_agent_id: int, message: str):
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}/chat"
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
- logging.info("-----Creating Economy client in remote...")
37
- self.economy_client = EconomyClient(
38
- config["simulator_request"]["economy"]["server"]
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
- # 提取 agent_id(主题格式为 "/exps/{exp_id}/agents/{agent_id}/chat")
85
- _, _, _, agent_id, _ = topic.strip("/").split("/")
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
- await agent.handle_message(payload)
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, agent_class: type[Agent], config: dict, agent_prefix: str = "agent_"
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
- self.agent_class = agent_class
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 memory_config_func is None:
62
- memory_config_func = self.default_memory_config_func
69
+ if not isinstance(agent_count, list):
70
+ agent_count = [agent_count]
63
71
 
64
- for i in range(agent_count):
65
- agent_name = f"{self.agent_prefix}{i}"
72
+ if len(self.agent_class) != len(agent_count):
73
+ raise ValueError("agent_class和agent_count的长度不一致")
66
74
 
67
- # 获取Memory配置
68
- extra_attributes, profile, base = memory_config_func()
69
- memory = Memory(
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
- # 创建智能体时传入Memory配置
74
- agent = self.agent_class(
75
- name=agent_name,
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
- self._agents[agent_name] = agent
80
-
81
- # 计算需要的组数,向上取整以处理不足一组的情况
82
- num_group = (agent_count + group_size - 1) // group_size
83
-
84
- for i in range(num_group):
85
- # 计算当前组的起始和结束索引
86
- start_idx = i * group_size
87
- end_idx = min((i + 1) * group_size, agent_count)
88
-
89
- # 获取当前组的agents
90
- agents = list(self._agents.values())[start_idx:end_idx]
91
- group_name = f"{self.agent_prefix}_group_{i}"
92
- group = AgentGroup.remote(agents, self.config, self.exp_id)
93
- self._groups[group_name] = group
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycityagent
3
- Version: 2.0.0a7
3
+ Version: 2.0.0a8
4
4
  Summary: LLM-based城市环境agent构建库
5
5
  License: MIT
6
6
  Author: Yuwei Yan
@@ -1,5 +1,5 @@
1
1
  pycityagent/__init__.py,sha256=n56bWkAUEcvjDsb7LcJpaGjlrriSKPnR0yBhwRfEYBA,212
2
- pycityagent/agent.py,sha256=s7FYOkOwdOU6ufp6HzGx0GNt3LZc4qMxRRv8hZxJI7k,11281
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=bVHXjiYtvqvvYOMsQPzufOFG8KZZDRUDTYjwpIjlQgE,4308
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=5DRRG4iTWvTTa10G4_qsoUy9wa2dmpDpfPWcK2Rnfvs,13860
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.0a7.dist-info/METADATA,sha256=jl2Q3mkfZidXJQGQuTTNMXCaGD6z7CMYy4R9jk3I0vA,7614
69
- pycityagent-2.0.0a7.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
70
- pycityagent-2.0.0a7.dist-info/RECORD,,
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,,