pycityagent 2.0.0a53__cp311-cp311-macosx_11_0_arm64.whl → 2.0.0a55__cp311-cp311-macosx_11_0_arm64.whl
Sign up to get free protection for your applications and to get access to all the features.
- pycityagent/agent/agent.py +39 -4
- pycityagent/agent/agent_base.py +39 -25
- pycityagent/cityagent/blocks/plan_block.py +1 -1
- pycityagent/cityagent/message_intercept.py +99 -0
- pycityagent/cityagent/societyagent.py +145 -32
- pycityagent/cli/wrapper.py +4 -0
- pycityagent/economy/econ_client.py +0 -2
- pycityagent/environment/__init__.py +7 -1
- pycityagent/environment/sim/sim_env.py +34 -46
- pycityagent/environment/simulator.py +2 -3
- pycityagent/llm/llm.py +18 -10
- pycityagent/memory/memory.py +151 -113
- pycityagent/message/__init__.py +8 -1
- pycityagent/message/message_interceptor.py +322 -0
- pycityagent/message/messager.py +42 -11
- pycityagent/metrics/mlflow_client.py +4 -0
- pycityagent/simulation/agentgroup.py +62 -14
- pycityagent/simulation/simulation.py +103 -29
- pycityagent/simulation/storage/pg.py +2 -2
- pycityagent/utils/__init__.py +7 -2
- pycityagent/utils/pg_query.py +1 -0
- pycityagent/utils/survey_util.py +26 -23
- pycityagent/workflow/block.py +3 -3
- {pycityagent-2.0.0a53.dist-info → pycityagent-2.0.0a55.dist-info}/METADATA +2 -2
- {pycityagent-2.0.0a53.dist-info → pycityagent-2.0.0a55.dist-info}/RECORD +29 -27
- {pycityagent-2.0.0a53.dist-info → pycityagent-2.0.0a55.dist-info}/LICENSE +0 -0
- {pycityagent-2.0.0a53.dist-info → pycityagent-2.0.0a55.dist-info}/WHEEL +0 -0
- {pycityagent-2.0.0a53.dist-info → pycityagent-2.0.0a55.dist-info}/entry_points.txt +0 -0
- {pycityagent-2.0.0a53.dist-info → pycityagent-2.0.0a55.dist-info}/top_level.txt +0 -0
pycityagent/agent/agent.py
CHANGED
@@ -13,7 +13,8 @@ from ..economy import EconomyClient
|
|
13
13
|
from ..environment import Simulator
|
14
14
|
from ..llm import LLM
|
15
15
|
from ..memory import Memory
|
16
|
-
from ..message
|
16
|
+
from ..message import MessageInterceptor, Messager
|
17
|
+
from ..metrics import MlflowClient
|
17
18
|
from .agent_base import Agent, AgentType
|
18
19
|
|
19
20
|
logger = logging.getLogger("pycityagent")
|
@@ -32,6 +33,7 @@ class CitizenAgent(Agent):
|
|
32
33
|
memory: Optional[Memory] = None,
|
33
34
|
economy_client: Optional[EconomyClient] = None,
|
34
35
|
messager: Optional[Messager] = None, # type:ignore
|
36
|
+
message_interceptor: Optional[MessageInterceptor] = None, # type:ignore
|
35
37
|
avro_file: Optional[dict] = None,
|
36
38
|
) -> None:
|
37
39
|
super().__init__(
|
@@ -40,10 +42,12 @@ class CitizenAgent(Agent):
|
|
40
42
|
llm_client=llm_client,
|
41
43
|
economy_client=economy_client,
|
42
44
|
messager=messager,
|
45
|
+
message_interceptor=message_interceptor,
|
43
46
|
simulator=simulator,
|
44
47
|
memory=memory,
|
45
48
|
avro_file=avro_file,
|
46
49
|
)
|
50
|
+
self._mlflow_client = None
|
47
51
|
|
48
52
|
async def bind_to_simulator(self):
|
49
53
|
await self._bind_to_simulator()
|
@@ -78,9 +82,7 @@ class CitizenAgent(Agent):
|
|
78
82
|
dict_person[_key] = _value
|
79
83
|
except KeyError as e:
|
80
84
|
continue
|
81
|
-
resp = await simulator.add_person(
|
82
|
-
dict2pb(dict_person, person_pb2.Person())
|
83
|
-
)
|
85
|
+
resp = await simulator.add_person(dict2pb(dict_person, person_pb2.Person()))
|
84
86
|
person_id = resp["person_id"]
|
85
87
|
await status.update("id", person_id, protect_llm_read_only_fields=False)
|
86
88
|
logger.debug(f"Binding to Person `{person_id}` just added to Simulator")
|
@@ -123,6 +125,21 @@ class CitizenAgent(Agent):
|
|
123
125
|
}
|
124
126
|
await self._send_message(sender_id, payload, "gather")
|
125
127
|
|
128
|
+
@property
|
129
|
+
def mlflow_client(self) -> MlflowClient:
|
130
|
+
"""The Agent's MlflowClient"""
|
131
|
+
if self._mlflow_client is None:
|
132
|
+
raise RuntimeError(
|
133
|
+
f"MlflowClient access before assignment, please `set_mlflow_client` first!"
|
134
|
+
)
|
135
|
+
return self._mlflow_client
|
136
|
+
|
137
|
+
def set_mlflow_client(self, mlflow_client: MlflowClient):
|
138
|
+
"""
|
139
|
+
Set the mlflow_client of the agent.
|
140
|
+
"""
|
141
|
+
self._mlflow_client = mlflow_client
|
142
|
+
|
126
143
|
|
127
144
|
class InstitutionAgent(Agent):
|
128
145
|
"""
|
@@ -137,6 +154,7 @@ class InstitutionAgent(Agent):
|
|
137
154
|
memory: Optional[Memory] = None,
|
138
155
|
economy_client: Optional[EconomyClient] = None,
|
139
156
|
messager: Optional[Messager] = None, # type:ignore
|
157
|
+
message_interceptor: Optional[MessageInterceptor] = None, # type:ignore
|
140
158
|
avro_file: Optional[dict] = None,
|
141
159
|
) -> None:
|
142
160
|
super().__init__(
|
@@ -145,10 +163,12 @@ class InstitutionAgent(Agent):
|
|
145
163
|
llm_client=llm_client,
|
146
164
|
economy_client=economy_client,
|
147
165
|
messager=messager,
|
166
|
+
message_interceptor=message_interceptor,
|
148
167
|
simulator=simulator,
|
149
168
|
memory=memory,
|
150
169
|
avro_file=avro_file,
|
151
170
|
)
|
171
|
+
self._mlflow_client = None
|
152
172
|
# 添加响应收集器
|
153
173
|
self._gather_responses: dict[str, asyncio.Future] = {}
|
154
174
|
|
@@ -308,3 +328,18 @@ class InstitutionAgent(Agent):
|
|
308
328
|
# 清理Future
|
309
329
|
for key in futures:
|
310
330
|
self._gather_responses.pop(key, None)
|
331
|
+
|
332
|
+
@property
|
333
|
+
def mlflow_client(self) -> MlflowClient:
|
334
|
+
"""The Agent's MlflowClient"""
|
335
|
+
if self._mlflow_client is None:
|
336
|
+
raise RuntimeError(
|
337
|
+
f"MlflowClient access before assignment, please `set_mlflow_client` first!"
|
338
|
+
)
|
339
|
+
return self._mlflow_client
|
340
|
+
|
341
|
+
def set_mlflow_client(self, mlflow_client: MlflowClient):
|
342
|
+
"""
|
343
|
+
Set the mlflow_client of the agent.
|
344
|
+
"""
|
345
|
+
self._mlflow_client = mlflow_client
|
pycityagent/agent/agent_base.py
CHANGED
@@ -13,14 +13,13 @@ from typing import Any, Optional, Union, get_type_hints
|
|
13
13
|
import fastavro
|
14
14
|
import ray
|
15
15
|
from pycityproto.city.person.v2 import person_pb2 as person_pb2
|
16
|
-
from pyparsing import Dict
|
17
16
|
|
18
17
|
from ..economy import EconomyClient
|
19
18
|
from ..environment import Simulator
|
20
19
|
from ..environment.sim.person_service import PersonService
|
21
20
|
from ..llm import LLM
|
22
21
|
from ..memory import Memory
|
23
|
-
from ..message
|
22
|
+
from ..message import MessageInterceptor, Messager
|
24
23
|
from ..metrics import MlflowClient
|
25
24
|
from ..utils import DIALOG_SCHEMA, SURVEY_SCHEMA, process_survey_for_llm
|
26
25
|
from ..workflow import Block
|
@@ -56,7 +55,8 @@ class Agent(ABC):
|
|
56
55
|
type: AgentType = AgentType.Unspecified,
|
57
56
|
llm_client: Optional[LLM] = None,
|
58
57
|
economy_client: Optional[EconomyClient] = None,
|
59
|
-
messager: Optional[
|
58
|
+
messager: Optional[ray.ObjectRef] = None,
|
59
|
+
message_interceptor: Optional[ray.ObjectRef] = None,
|
60
60
|
simulator: Optional[Simulator] = None,
|
61
61
|
memory: Optional[Memory] = None,
|
62
62
|
avro_file: Optional[dict[str, str]] = None,
|
@@ -82,6 +82,7 @@ class Agent(ABC):
|
|
82
82
|
self._llm_client = llm_client
|
83
83
|
self._economy_client = economy_client
|
84
84
|
self._messager = messager
|
85
|
+
self._message_interceptor = message_interceptor
|
85
86
|
self._simulator = simulator
|
86
87
|
self._memory = memory
|
87
88
|
self._exp_id = -1
|
@@ -102,12 +103,12 @@ class Agent(ABC):
|
|
102
103
|
return state
|
103
104
|
|
104
105
|
@classmethod
|
105
|
-
def export_class_config(cls) ->
|
106
|
+
def export_class_config(cls) -> dict[str, dict]:
|
106
107
|
result = {
|
107
108
|
"agent_name": cls.__name__,
|
108
109
|
"config": {},
|
109
110
|
"description": {},
|
110
|
-
"blocks": []
|
111
|
+
"blocks": [],
|
111
112
|
}
|
112
113
|
config = {
|
113
114
|
field: cls.default_values.get(field, "default_value")
|
@@ -123,16 +124,18 @@ class Agent(ABC):
|
|
123
124
|
for attr_name, attr_type in hints.items():
|
124
125
|
if inspect.isclass(attr_type) and issubclass(attr_type, Block):
|
125
126
|
block_config = attr_type.export_class_config()
|
126
|
-
result["blocks"].append(
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
127
|
+
result["blocks"].append(
|
128
|
+
{
|
129
|
+
"name": attr_name,
|
130
|
+
"config": block_config[0], # type:ignore
|
131
|
+
"description": block_config[1], # type:ignore
|
132
|
+
"children": cls._export_subblocks(attr_type),
|
133
|
+
}
|
134
|
+
)
|
132
135
|
return result
|
133
136
|
|
134
137
|
@classmethod
|
135
|
-
def _export_subblocks(cls, block_cls: type[Block]) -> list[
|
138
|
+
def _export_subblocks(cls, block_cls: type[Block]) -> list[dict]:
|
136
139
|
children = []
|
137
140
|
hints = get_type_hints(block_cls) # 获取类的注解
|
138
141
|
for attr_name, attr_type in hints.items():
|
@@ -141,8 +144,8 @@ class Agent(ABC):
|
|
141
144
|
children.append(
|
142
145
|
{
|
143
146
|
"name": attr_name,
|
144
|
-
"config": block_config[0],
|
145
|
-
|
147
|
+
"config": block_config[0], # type:ignore
|
148
|
+
"description": block_config[1], # type:ignore
|
146
149
|
"children": cls._export_subblocks(attr_type),
|
147
150
|
}
|
148
151
|
)
|
@@ -253,6 +256,12 @@ class Agent(ABC):
|
|
253
256
|
"""
|
254
257
|
self._pgsql_writer = pgsql_writer
|
255
258
|
|
259
|
+
def set_message_interceptor(self, message_interceptor: ray.ObjectRef):
|
260
|
+
"""
|
261
|
+
Set the PostgreSQL copy writer of the agent.
|
262
|
+
"""
|
263
|
+
self._message_interceptor = message_interceptor
|
264
|
+
|
256
265
|
@property
|
257
266
|
def uuid(self):
|
258
267
|
"""The Agent's UUID"""
|
@@ -289,24 +298,24 @@ class Agent(ABC):
|
|
289
298
|
f"Memory access before assignment, please `set_memory` first!"
|
290
299
|
)
|
291
300
|
return self._memory
|
292
|
-
|
301
|
+
|
293
302
|
@property
|
294
303
|
def status(self):
|
295
304
|
"""The Agent's Status Memory"""
|
296
|
-
if self.
|
305
|
+
if self.memory.status is None:
|
297
306
|
raise RuntimeError(
|
298
307
|
f"Status access before assignment, please `set_memory` first!"
|
299
308
|
)
|
300
|
-
return self.
|
301
|
-
|
309
|
+
return self.memory.status
|
310
|
+
|
302
311
|
@property
|
303
312
|
def stream(self):
|
304
313
|
"""The Agent's Stream Memory"""
|
305
|
-
if self.
|
314
|
+
if self.memory.stream is None:
|
306
315
|
raise RuntimeError(
|
307
316
|
f"Stream access before assignment, please `set_memory` first!"
|
308
317
|
)
|
309
|
-
return self.
|
318
|
+
return self.memory.stream
|
310
319
|
|
311
320
|
@property
|
312
321
|
def simulator(self):
|
@@ -335,7 +344,7 @@ class Agent(ABC):
|
|
335
344
|
async def messager_ping(self):
|
336
345
|
if self._messager is None:
|
337
346
|
raise RuntimeError("Messager is not set")
|
338
|
-
return await self._messager.ping()
|
347
|
+
return await self._messager.ping.remote() # type:ignore
|
339
348
|
|
340
349
|
async def generate_user_survey_response(self, survey: dict) -> str:
|
341
350
|
"""生成回答 —— 可重写
|
@@ -421,7 +430,7 @@ class Agent(ABC):
|
|
421
430
|
_data_tuples
|
422
431
|
)
|
423
432
|
)
|
424
|
-
await self.messager.send_message.remote(f"exps/{self._exp_id}/user_payback", {"count": 1})
|
433
|
+
await self.messager.send_message.remote(f"exps/{self._exp_id}/user_payback", {"count": 1})# type:ignore
|
425
434
|
|
426
435
|
async def generate_user_chat_response(self, question: str) -> str:
|
427
436
|
"""生成回答 —— 可重写
|
@@ -508,7 +517,7 @@ class Agent(ABC):
|
|
508
517
|
_data
|
509
518
|
)
|
510
519
|
)
|
511
|
-
await self.messager.send_message.remote(f"exps/{self._exp_id}/user_payback", {"count": 1})
|
520
|
+
await self.messager.send_message.remote(f"exps/{self._exp_id}/user_payback", {"count": 1})# type:ignore
|
512
521
|
print(f"Sent payback message to {self._exp_id}")
|
513
522
|
|
514
523
|
async def process_agent_chat_response(self, payload: dict) -> str:
|
@@ -579,7 +588,12 @@ class Agent(ABC):
|
|
579
588
|
if self._messager is None:
|
580
589
|
raise RuntimeError("Messager is not set")
|
581
590
|
topic = f"exps/{self._exp_id}/agents/{to_agent_uuid}/{sub_topic}"
|
582
|
-
await self._messager.send_message.remote(
|
591
|
+
await self._messager.send_message.remote( # type:ignore
|
592
|
+
topic,
|
593
|
+
payload,
|
594
|
+
self._uuid,
|
595
|
+
to_agent_uuid,
|
596
|
+
)
|
583
597
|
|
584
598
|
async def send_message_to_agent(
|
585
599
|
self, to_agent_uuid: str, content: str, type: str = "social"
|
@@ -643,6 +657,6 @@ class Agent(ABC):
|
|
643
657
|
当_blocked为True时,不执行forward方法
|
644
658
|
"""
|
645
659
|
if self._messager is not None:
|
646
|
-
await self._messager.ping.remote()
|
660
|
+
await self._messager.ping.remote() # type:ignore
|
647
661
|
if not self._blocked:
|
648
662
|
await self.forward()
|
@@ -294,4 +294,4 @@ Execution Steps: \n{formated_steps}
|
|
294
294
|
def clean_json_response(self, response: str) -> str:
|
295
295
|
"""Clean special characters in LLM response"""
|
296
296
|
response = response.replace('```json', '').replace('```', '')
|
297
|
-
return response.strip()
|
297
|
+
return response.strip()
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import asyncio
|
2
|
+
|
3
|
+
from pycityagent.llm import LLM
|
4
|
+
from pycityagent.message import MessageBlockBase, MessageBlockListenerBase
|
5
|
+
|
6
|
+
|
7
|
+
async def check_message(
|
8
|
+
from_uuid: str, to_uuid: str, llm_client: LLM, content: str
|
9
|
+
) -> bool:
|
10
|
+
"""
|
11
|
+
使用LLM检查消息是否合规
|
12
|
+
返回: (是否合规, from_uuid, to_uuid)
|
13
|
+
"""
|
14
|
+
print(f"\n检查消息: {from_uuid} -> {to_uuid}: {content}")
|
15
|
+
|
16
|
+
prompt = f"""
|
17
|
+
请判断以下消息是否具有情绪煽动性:
|
18
|
+
发送者ID: {from_uuid}
|
19
|
+
接收者ID: {to_uuid}
|
20
|
+
消息内容: {content}
|
21
|
+
|
22
|
+
如果消息具有情绪煽动性,请返回 False;如果消息正常,请返回 True。
|
23
|
+
"""
|
24
|
+
|
25
|
+
if "test" in content.lower():
|
26
|
+
is_valid = False
|
27
|
+
else:
|
28
|
+
is_valid = True
|
29
|
+
print(f"消息检查结果: {'合规' if is_valid else '不合规'}")
|
30
|
+
return is_valid
|
31
|
+
|
32
|
+
|
33
|
+
class EdgeMessageBlock(MessageBlockBase):
|
34
|
+
async def forward( # type:ignore
|
35
|
+
self,
|
36
|
+
from_uuid: str,
|
37
|
+
to_uuid: str,
|
38
|
+
msg: str,
|
39
|
+
black_list: list[tuple[str, str]],
|
40
|
+
):
|
41
|
+
if (from_uuid, to_uuid) in set(black_list):
|
42
|
+
# 可选同时返回入队的信息(False,err) 如果只返回bool值则默认报错信息入队
|
43
|
+
return False
|
44
|
+
else:
|
45
|
+
is_valid = await check_message(
|
46
|
+
from_uuid=from_uuid,
|
47
|
+
to_uuid=to_uuid,
|
48
|
+
llm_client=self.llm,
|
49
|
+
content=msg,
|
50
|
+
)
|
51
|
+
if not is_valid:
|
52
|
+
# 直接添加即可 在框架内部的异步锁保证不会冲突
|
53
|
+
black_list.append((from_uuid, to_uuid))
|
54
|
+
return is_valid
|
55
|
+
|
56
|
+
|
57
|
+
class PointMessageBlock(MessageBlockBase):
|
58
|
+
async def forward( # type:ignore
|
59
|
+
self,
|
60
|
+
from_uuid: str,
|
61
|
+
to_uuid: str,
|
62
|
+
msg: str,
|
63
|
+
violation_counts: dict[str, int],
|
64
|
+
black_list: list[tuple[str, str]],
|
65
|
+
):
|
66
|
+
if (from_uuid, to_uuid) in set(black_list):
|
67
|
+
# 可选同时返回入队的信息(False,err) 如果只返回bool值则默认报错信息入队
|
68
|
+
return False
|
69
|
+
else:
|
70
|
+
# violation count在框架内自动维护 这里不用管
|
71
|
+
is_valid = await check_message(
|
72
|
+
from_uuid=from_uuid,
|
73
|
+
to_uuid=to_uuid,
|
74
|
+
llm_client=self.llm,
|
75
|
+
content=msg,
|
76
|
+
)
|
77
|
+
if not is_valid and violation_counts[from_uuid] >= 3 - 1:
|
78
|
+
# 直接添加即可 在框架内部的异步锁保证不会冲突
|
79
|
+
black_list.append((from_uuid, to_uuid))
|
80
|
+
return is_valid
|
81
|
+
|
82
|
+
|
83
|
+
class MessageBlockListener(MessageBlockListenerBase):
|
84
|
+
def __init__(
|
85
|
+
self, save_queue_values: bool = False, get_queue_period: float = 0.1
|
86
|
+
) -> None:
|
87
|
+
super().__init__(save_queue_values, get_queue_period)
|
88
|
+
|
89
|
+
async def forward(
|
90
|
+
self,
|
91
|
+
):
|
92
|
+
while True:
|
93
|
+
if self.has_queue:
|
94
|
+
value = await self.queue.get_async() # type: ignore
|
95
|
+
if self._save_queue_values:
|
96
|
+
self._values_from_queue.append(value)
|
97
|
+
print(f"get `{value}` from queue")
|
98
|
+
# do something with the value
|
99
|
+
await asyncio.sleep(self._get_queue_period)
|
@@ -8,8 +8,8 @@ from pycityagent.agent import Agent
|
|
8
8
|
from pycityagent.economy import EconomyClient
|
9
9
|
from pycityagent.llm.llm import LLM
|
10
10
|
from pycityagent.memory import Memory
|
11
|
-
from pycityagent.workflow import Block
|
12
11
|
from pycityagent.tools import UpdateWithSimulator
|
12
|
+
from pycityagent.workflow import Block
|
13
13
|
|
14
14
|
from .blocks import (CognitionBlock, EconomyBlock, MobilityBlock, NeedsBlock,
|
15
15
|
OtherBlock, PlanBlock, SocialBlock)
|
@@ -54,7 +54,6 @@ class PlanAndActionBlock(Block):
|
|
54
54
|
llm=llm, memory=memory, simulator=simulator, economy_client=economy_client
|
55
55
|
)
|
56
56
|
self.otherBlock = OtherBlock(llm=llm, memory=memory)
|
57
|
-
|
58
57
|
async def plan_generation(self):
|
59
58
|
"""Generate plan"""
|
60
59
|
current_plan = await self.memory.status.get("current_plan")
|
@@ -79,45 +78,43 @@ class PlanAndActionBlock(Block):
|
|
79
78
|
)
|
80
79
|
result = None
|
81
80
|
if step_type == "mobility":
|
82
|
-
if self.enable_mobility:
|
81
|
+
if self.enable_mobility: # type:ignore
|
83
82
|
result = await self.mobilityBlock.forward(
|
84
83
|
current_step, execution_context
|
85
84
|
)
|
86
85
|
else:
|
87
86
|
result = {
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
87
|
+
"success": False,
|
88
|
+
"evaluation": f"Mobility Behavior is disabled",
|
89
|
+
"consumed_time": 0,
|
90
|
+
"node_id": None,
|
92
91
|
}
|
93
92
|
elif step_type == "social":
|
94
|
-
if self.enable_social:
|
93
|
+
if self.enable_social: # type:ignore
|
95
94
|
result = await self.socialBlock.forward(
|
96
95
|
current_step, execution_context
|
97
96
|
)
|
98
97
|
else:
|
99
98
|
result = {
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
99
|
+
"success": False,
|
100
|
+
"evaluation": f"Social Behavior is disabled",
|
101
|
+
"consumed_time": 0,
|
102
|
+
"node_id": None,
|
104
103
|
}
|
105
104
|
elif step_type == "economy":
|
106
|
-
if self.enable_economy:
|
105
|
+
if self.enable_economy: # type:ignore
|
107
106
|
result = await self.economyBlock.forward(
|
108
107
|
current_step, execution_context
|
109
108
|
)
|
110
109
|
else:
|
111
110
|
result = {
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
111
|
+
"success": False,
|
112
|
+
"evaluation": f"Economy Behavior is disabled",
|
113
|
+
"consumed_time": 0,
|
114
|
+
"node_id": None,
|
116
115
|
}
|
117
116
|
elif step_type == "other":
|
118
|
-
result = await self.otherBlock.forward(
|
119
|
-
current_step, execution_context
|
120
|
-
)
|
117
|
+
result = await self.otherBlock.forward(current_step, execution_context)
|
121
118
|
if result != None:
|
122
119
|
logger.info(f"Execution result: {result}")
|
123
120
|
current_step["evaluation"] = result
|
@@ -156,7 +153,9 @@ class MindBlock(Block):
|
|
156
153
|
|
157
154
|
def __init__(self, llm: LLM, memory: Memory, simulator: Simulator):
|
158
155
|
super().__init__(name="mind_block", llm=llm, memory=memory, simulator=simulator)
|
159
|
-
self.cognitionBlock = CognitionBlock(
|
156
|
+
self.cognitionBlock = CognitionBlock(
|
157
|
+
llm=self.llm, memory=self.memory, simulator=simulator
|
158
|
+
)
|
160
159
|
|
161
160
|
async def forward(self):
|
162
161
|
await self.cognitionBlock.forward()
|
@@ -165,7 +164,12 @@ class SocietyAgent(CitizenAgent):
|
|
165
164
|
mindBlock: MindBlock
|
166
165
|
planAndActionBlock: PlanAndActionBlock
|
167
166
|
update_with_sim = UpdateWithSimulator()
|
168
|
-
configurable_fields = [
|
167
|
+
configurable_fields = [
|
168
|
+
"enable_cognition",
|
169
|
+
"enable_mobility",
|
170
|
+
"enable_social",
|
171
|
+
"enable_economy",
|
172
|
+
]
|
169
173
|
default_values = {
|
170
174
|
"enable_cognition": True,
|
171
175
|
"enable_mobility": True,
|
@@ -195,14 +199,14 @@ class SocietyAgent(CitizenAgent):
|
|
195
199
|
economy_client=economy_client,
|
196
200
|
)
|
197
201
|
self.mindBlock = MindBlock(
|
198
|
-
llm=self.
|
202
|
+
llm=self.llm, memory=self.memory, simulator=self.simulator
|
199
203
|
)
|
200
204
|
self.planAndActionBlock = PlanAndActionBlock(
|
201
205
|
agent=self,
|
202
|
-
llm=self.
|
206
|
+
llm=self.llm,
|
203
207
|
memory=self.memory,
|
204
|
-
simulator=self.
|
205
|
-
economy_client=self.
|
208
|
+
simulator=self.simulator,
|
209
|
+
economy_client=self.economy_client,
|
206
210
|
)
|
207
211
|
self.step_count = -1
|
208
212
|
self.cognition_update = -1
|
@@ -305,7 +309,114 @@ class SocietyAgent(CitizenAgent):
|
|
305
309
|
# The previous step has not been completed
|
306
310
|
return False
|
307
311
|
|
308
|
-
|
312
|
+
# check last step
|
313
|
+
if not await self.check_and_update_step():
|
314
|
+
return
|
315
|
+
|
316
|
+
await self.planAndActionBlock.forward()
|
317
|
+
|
318
|
+
if self.enable_cognition:
|
319
|
+
await self.mindBlock.forward()
|
320
|
+
|
321
|
+
async def check_and_update_step(self):
|
322
|
+
"""Check if the previous step has been completed"""
|
323
|
+
status = await self.memory.status.get("status")
|
324
|
+
if status == 2:
|
325
|
+
# Agent is moving
|
326
|
+
logger.info("Agent is moving")
|
327
|
+
await asyncio.sleep(1)
|
328
|
+
return False
|
329
|
+
|
330
|
+
# Get the previous step information
|
331
|
+
current_step = await self.memory.status.get("current_step")
|
332
|
+
if current_step["intention"] == "" or current_step["type"] == "":
|
333
|
+
# No previous step, return directly
|
334
|
+
return True
|
335
|
+
time_now = int(await self.simulator.get_time())
|
336
|
+
step_start_time = current_step["start_time"]
|
337
|
+
step_consumed_time = current_step["evaluation"]["consumed_time"]
|
338
|
+
time_end_plan = step_start_time + int(step_consumed_time) * 60
|
339
|
+
if time_now >= time_end_plan:
|
340
|
+
# The previous step has been completed
|
341
|
+
current_plan = await self.memory.status.get("current_plan")
|
342
|
+
current_step["evaluation"]["consumed_time"] = (
|
343
|
+
time_now - step_start_time
|
344
|
+
) / 60
|
345
|
+
current_plan["stream_nodes"].append(current_step["evaluation"]["node_id"])
|
346
|
+
if current_step["evaluation"]["success"]:
|
347
|
+
# Last step is completed
|
348
|
+
current_step_index = next(
|
349
|
+
(
|
350
|
+
i
|
351
|
+
for i, step in enumerate(current_plan["steps"])
|
352
|
+
if step["intention"] == current_step["intention"]
|
353
|
+
and step["type"] == current_step["type"]
|
354
|
+
),
|
355
|
+
None,
|
356
|
+
)
|
357
|
+
current_plan["steps"][current_step_index] = current_step
|
358
|
+
await self.memory.status.update("current_plan", current_plan)
|
359
|
+
if current_step_index is not None and current_step_index + 1 < len(
|
360
|
+
current_plan["steps"]
|
361
|
+
):
|
362
|
+
next_step = current_plan["steps"][current_step_index + 1]
|
363
|
+
await self.memory.status.update("current_step", next_step)
|
364
|
+
else:
|
365
|
+
# Whole plan is completed
|
366
|
+
current_plan["completed"] = True
|
367
|
+
current_plan["end_time"] = await self.simulator.get_time(
|
368
|
+
format_time=True
|
369
|
+
)
|
370
|
+
if self.enable_cognition:
|
371
|
+
# Update emotion for the plan
|
372
|
+
related_memories = await self.memory.stream.get_by_ids(
|
373
|
+
current_plan["stream_nodes"]
|
374
|
+
)
|
375
|
+
incident = f"You have successfully completed the plan: {related_memories}"
|
376
|
+
conclusion = await self.mindBlock.cognitionBlock.emotion_update(
|
377
|
+
incident
|
378
|
+
)
|
379
|
+
await self.memory.stream.add_cognition(
|
380
|
+
description=conclusion # type:ignore
|
381
|
+
)
|
382
|
+
await self.memory.stream.add_cognition_to_memory(
|
383
|
+
current_plan["stream_nodes"], conclusion # type:ignore
|
384
|
+
)
|
385
|
+
await self.memory.status.update("current_plan", current_plan)
|
386
|
+
await self.memory.status.update(
|
387
|
+
"current_step", {"intention": "", "type": ""}
|
388
|
+
)
|
389
|
+
return True
|
390
|
+
else:
|
391
|
+
current_plan["failed"] = True
|
392
|
+
current_plan["end_time"] = await self.simulator.get_time(
|
393
|
+
format_time=True
|
394
|
+
)
|
395
|
+
if self.enable_cognition:
|
396
|
+
# Update emotion for the plan
|
397
|
+
related_memories = await self.memory.stream.get_by_ids(
|
398
|
+
current_plan["stream_nodes"]
|
399
|
+
)
|
400
|
+
incident = (
|
401
|
+
f"You have failed to complete the plan: {related_memories}"
|
402
|
+
)
|
403
|
+
conclusion = await self.mindBlock.cognitionBlock.emotion_update(
|
404
|
+
incident
|
405
|
+
)
|
406
|
+
await self.memory.stream.add_cognition(
|
407
|
+
description=conclusion # type:ignore
|
408
|
+
)
|
409
|
+
await self.memory.stream.add_cognition_to_memory(
|
410
|
+
current_plan["stream_nodes"], conclusion # type:ignore
|
411
|
+
)
|
412
|
+
await self.memory.status.update("current_plan", current_plan)
|
413
|
+
await self.memory.status.update(
|
414
|
+
"current_step", {"intention": "", "type": ""}
|
415
|
+
)
|
416
|
+
# The previous step has not been completed
|
417
|
+
return False
|
418
|
+
|
419
|
+
async def process_agent_chat_response(self, payload: dict) -> str: # type:ignore
|
309
420
|
if payload["type"] == "social":
|
310
421
|
resp = f"Agent {self._uuid} received agent chat response: {payload}"
|
311
422
|
logger.info(resp)
|
@@ -380,7 +491,7 @@ class SocietyAgent(CitizenAgent):
|
|
380
491
|
|
381
492
|
Answer only YES or NO."""
|
382
493
|
|
383
|
-
should_respond = await self._llm_client.atext_request(
|
494
|
+
should_respond = await self._llm_client.atext_request( # type:ignore
|
384
495
|
[
|
385
496
|
{
|
386
497
|
"role": "system",
|
@@ -390,7 +501,7 @@ class SocietyAgent(CitizenAgent):
|
|
390
501
|
]
|
391
502
|
)
|
392
503
|
|
393
|
-
if should_respond.strip().upper() != "YES":
|
504
|
+
if should_respond.strip().upper() != "YES": # type:ignore
|
394
505
|
await self.memory.status.update("chat_histories", chat_histories)
|
395
506
|
return ""
|
396
507
|
|
@@ -414,7 +525,7 @@ class SocietyAgent(CitizenAgent):
|
|
414
525
|
|
415
526
|
Response should be ONLY the message text, no explanations."""
|
416
527
|
|
417
|
-
response = await self.
|
528
|
+
response = await self.llm.atext_request(
|
418
529
|
[
|
419
530
|
{
|
420
531
|
"role": "system",
|
@@ -440,7 +551,7 @@ class SocietyAgent(CitizenAgent):
|
|
440
551
|
await self.send_message_to_agent(sender_id, serialized_response)
|
441
552
|
logger.info("sender_id", sender_id)
|
442
553
|
logger.info("message", serialized_response)
|
443
|
-
return response
|
554
|
+
return response # type:ignore
|
444
555
|
|
445
556
|
except Exception as e:
|
446
557
|
logger.warning(f"Error in process_agent_chat_response: {str(e)}")
|
@@ -454,6 +565,8 @@ class SocietyAgent(CitizenAgent):
|
|
454
565
|
value = int(value)
|
455
566
|
description = f"You received a economic message: Your {key} has changed from {await self.memory.status.get(key)} to {value}"
|
456
567
|
await self.memory.status.update(key, value)
|
457
|
-
await self.memory.stream.add_economic(
|
568
|
+
await self.memory.stream.add_economic( # type:ignore
|
569
|
+
description=description
|
570
|
+
)
|
458
571
|
if self.enable_cognition:
|
459
572
|
await self.mindBlock.cognitionBlock.emotion_update(description)
|