pycityagent 2.0.0a46__cp312-cp312-macosx_11_0_arm64.whl → 2.0.0a48__cp312-cp312-macosx_11_0_arm64.whl
Sign up to get free protection for your applications and to get access to all the features.
- pycityagent/__init__.py +3 -2
- pycityagent/agent.py +109 -4
- pycityagent/cityagent/__init__.py +20 -0
- pycityagent/cityagent/bankagent.py +54 -0
- pycityagent/cityagent/blocks/__init__.py +20 -0
- pycityagent/cityagent/blocks/cognition_block.py +304 -0
- pycityagent/cityagent/blocks/dispatcher.py +78 -0
- pycityagent/cityagent/blocks/economy_block.py +356 -0
- pycityagent/cityagent/blocks/mobility_block.py +258 -0
- pycityagent/cityagent/blocks/needs_block.py +305 -0
- pycityagent/cityagent/blocks/other_block.py +103 -0
- pycityagent/cityagent/blocks/plan_block.py +309 -0
- pycityagent/cityagent/blocks/social_block.py +345 -0
- pycityagent/cityagent/blocks/time_block.py +116 -0
- pycityagent/cityagent/blocks/utils.py +66 -0
- pycityagent/cityagent/firmagent.py +75 -0
- pycityagent/cityagent/governmentagent.py +60 -0
- pycityagent/cityagent/initial.py +98 -0
- pycityagent/cityagent/memory_config.py +202 -0
- pycityagent/cityagent/nbsagent.py +92 -0
- pycityagent/cityagent/societyagent.py +291 -0
- pycityagent/memory/memory.py +0 -18
- pycityagent/message/messager.py +6 -3
- pycityagent/simulation/agentgroup.py +122 -41
- pycityagent/simulation/simulation.py +311 -316
- pycityagent/workflow/block.py +66 -1
- pycityagent/workflow/tool.py +15 -11
- {pycityagent-2.0.0a46.dist-info → pycityagent-2.0.0a48.dist-info}/METADATA +2 -2
- {pycityagent-2.0.0a46.dist-info → pycityagent-2.0.0a48.dist-info}/RECORD +33 -14
- {pycityagent-2.0.0a46.dist-info → pycityagent-2.0.0a48.dist-info}/LICENSE +0 -0
- {pycityagent-2.0.0a46.dist-info → pycityagent-2.0.0a48.dist-info}/WHEEL +0 -0
- {pycityagent-2.0.0a46.dist-info → pycityagent-2.0.0a48.dist-info}/entry_points.txt +0 -0
- {pycityagent-2.0.0a46.dist-info → pycityagent-2.0.0a48.dist-info}/top_level.txt +0 -0
pycityagent/__init__.py
CHANGED
@@ -4,8 +4,9 @@ Pycityagent: 城市智能体构建框架
|
|
4
4
|
|
5
5
|
from .agent import Agent, CitizenAgent, InstitutionAgent
|
6
6
|
from .environment import Simulator
|
7
|
-
import logging
|
8
7
|
from .llm import SentenceEmbedding
|
8
|
+
from .simulation import AgentSimulation
|
9
|
+
import logging
|
9
10
|
|
10
11
|
# 创建一个 pycityagent 记录器
|
11
12
|
logger = logging.getLogger("pycityagent")
|
@@ -20,4 +21,4 @@ if not logger.hasHandlers():
|
|
20
21
|
handler.setFormatter(formatter)
|
21
22
|
logger.addHandler(handler)
|
22
23
|
|
23
|
-
__all__ = ["Agent", "Simulator", "CitizenAgent", "InstitutionAgent","SentenceEmbedding",]
|
24
|
+
__all__ = ["Agent", "Simulator", "CitizenAgent", "InstitutionAgent","SentenceEmbedding","AgentSimulation"]
|
pycityagent/agent.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
from __future__ import annotations
|
3
2
|
import asyncio
|
3
|
+
import inspect
|
4
4
|
import json
|
5
5
|
import logging
|
6
6
|
import random
|
@@ -9,14 +9,17 @@ from abc import ABC, abstractmethod
|
|
9
9
|
from copy import deepcopy
|
10
10
|
from datetime import datetime, timezone
|
11
11
|
from enum import Enum
|
12
|
-
from typing import Any, Optional
|
12
|
+
from typing import Any, List, Optional, Type, get_type_hints
|
13
13
|
from uuid import UUID
|
14
14
|
|
15
15
|
import fastavro
|
16
|
+
from pyparsing import Dict
|
16
17
|
import ray
|
17
18
|
from mosstool.util.format_converter import dict2pb
|
18
19
|
from pycityproto.city.person.v2 import person_pb2 as person_pb2
|
19
20
|
|
21
|
+
from pycityagent.workflow import Block
|
22
|
+
|
20
23
|
from .economy import EconomyClient
|
21
24
|
from .environment import Simulator
|
22
25
|
from .environment.sim.person_service import PersonService
|
@@ -46,6 +49,8 @@ class Agent(ABC):
|
|
46
49
|
"""
|
47
50
|
Agent base class
|
48
51
|
"""
|
52
|
+
configurable_fields: List[str] = []
|
53
|
+
default_values: dict[str, Any] = {}
|
49
54
|
|
50
55
|
def __init__(
|
51
56
|
self,
|
@@ -101,6 +106,98 @@ class Agent(ABC):
|
|
101
106
|
del state["_llm_client"]
|
102
107
|
return state
|
103
108
|
|
109
|
+
@classmethod
|
110
|
+
def export_class_config(cls) -> Dict[str, Dict]:
|
111
|
+
result = {
|
112
|
+
"agent_name": cls.__name__,
|
113
|
+
"config": {},
|
114
|
+
"blocks": []
|
115
|
+
}
|
116
|
+
config = {
|
117
|
+
field: cls.default_values.get(field, "default_value")
|
118
|
+
for field in cls.configurable_fields
|
119
|
+
}
|
120
|
+
result["config"] = config
|
121
|
+
# 解析类中的注解,找到Block类型的字段
|
122
|
+
hints = get_type_hints(cls)
|
123
|
+
for attr_name, attr_type in hints.items():
|
124
|
+
if inspect.isclass(attr_type) and issubclass(attr_type, Block):
|
125
|
+
block_config = attr_type.export_class_config()
|
126
|
+
result["blocks"].append({
|
127
|
+
"name": attr_name,
|
128
|
+
"config": block_config,
|
129
|
+
"children": cls._export_subblocks(attr_type)
|
130
|
+
})
|
131
|
+
return result
|
132
|
+
|
133
|
+
@classmethod
|
134
|
+
def _export_subblocks(cls, block_cls: Type[Block]) -> List[Dict]:
|
135
|
+
children = []
|
136
|
+
hints = get_type_hints(block_cls) # 获取类的注解
|
137
|
+
for attr_name, attr_type in hints.items():
|
138
|
+
if inspect.isclass(attr_type) and issubclass(attr_type, Block):
|
139
|
+
block_config = attr_type.export_class_config()
|
140
|
+
children.append({
|
141
|
+
"name": attr_name,
|
142
|
+
"config": block_config,
|
143
|
+
"children": cls._export_subblocks(attr_type)
|
144
|
+
})
|
145
|
+
return children
|
146
|
+
|
147
|
+
@classmethod
|
148
|
+
def export_to_file(cls, filepath: str) -> None:
|
149
|
+
config = cls.export_class_config()
|
150
|
+
with open(filepath, "w") as f:
|
151
|
+
json.dump(config, f, indent=4)
|
152
|
+
|
153
|
+
@classmethod
|
154
|
+
def import_block_config(cls, config: Dict[str, List[Dict]]) -> "Agent":
|
155
|
+
agent = cls(name=config["agent_name"])
|
156
|
+
|
157
|
+
def build_block(block_data: Dict) -> Block:
|
158
|
+
block_cls = globals()[block_data["name"]]
|
159
|
+
block_instance = block_cls.import_config(block_data)
|
160
|
+
return block_instance
|
161
|
+
|
162
|
+
# 创建顶层Block
|
163
|
+
for block_data in config["blocks"]:
|
164
|
+
block = build_block(block_data)
|
165
|
+
setattr(agent, block.name.lower(), block)
|
166
|
+
|
167
|
+
return agent
|
168
|
+
|
169
|
+
@classmethod
|
170
|
+
def import_from_file(cls, filepath: str) -> "Agent":
|
171
|
+
with open(filepath, "r") as f:
|
172
|
+
config = json.load(f)
|
173
|
+
return cls.import_block_config(config)
|
174
|
+
|
175
|
+
def load_from_config(self, config: Dict[str, List[Dict]]) -> None:
|
176
|
+
"""
|
177
|
+
使用配置更新当前Agent实例的Block层次结构。
|
178
|
+
"""
|
179
|
+
# 更新当前Agent的基础参数
|
180
|
+
for field in self.configurable_fields:
|
181
|
+
if field in config["config"]:
|
182
|
+
if config["config"][field] != "default_value":
|
183
|
+
setattr(self, field, config["config"][field])
|
184
|
+
|
185
|
+
# 递归更新或创建顶层Block
|
186
|
+
for block_data in config.get("blocks", []):
|
187
|
+
block_name = block_data["name"]
|
188
|
+
existing_block = getattr(self, block_name, None)
|
189
|
+
|
190
|
+
if existing_block:
|
191
|
+
# 如果Block已经存在,则递归更新
|
192
|
+
existing_block.load_from_config(block_data)
|
193
|
+
else:
|
194
|
+
raise KeyError(f"Block '{block_name}' not found in agent '{self.__class__.__name__}'")
|
195
|
+
|
196
|
+
def load_from_file(self, filepath: str) -> None:
|
197
|
+
with open(filepath, "r") as f:
|
198
|
+
config = json.load(f)
|
199
|
+
self.load_from_config(config)
|
200
|
+
|
104
201
|
def set_messager(self, messager: Messager): # type:ignore
|
105
202
|
"""
|
106
203
|
Set the messager of the agent.
|
@@ -218,6 +315,11 @@ class Agent(ABC):
|
|
218
315
|
f"Copy Writer access before assignment, please `set_pgsql_writer` first!"
|
219
316
|
)
|
220
317
|
return self._pgsql_writer
|
318
|
+
|
319
|
+
async def messager_ping(self):
|
320
|
+
if self._messager is None:
|
321
|
+
raise RuntimeError("Messager is not set")
|
322
|
+
return await self._messager.ping()
|
221
323
|
|
222
324
|
async def generate_user_survey_response(self, survey: dict) -> str:
|
223
325
|
"""生成回答 —— 可重写
|
@@ -527,6 +629,8 @@ class Agent(ABC):
|
|
527
629
|
统一的Agent执行入口
|
528
630
|
当_blocked为True时,不执行forward方法
|
529
631
|
"""
|
632
|
+
if self._messager is not None:
|
633
|
+
await self._messager.ping.remote()
|
530
634
|
if not self._blocked:
|
531
635
|
await self.forward()
|
532
636
|
|
@@ -621,10 +725,11 @@ class CitizenAgent(Agent):
|
|
621
725
|
except:
|
622
726
|
pass
|
623
727
|
person_id = await self.memory.get("id")
|
728
|
+
currency = await self.memory.get("currency")
|
624
729
|
await self._economy_client.add_agents(
|
625
730
|
{
|
626
731
|
"id": person_id,
|
627
|
-
"currency":
|
732
|
+
"currency": currency,
|
628
733
|
}
|
629
734
|
)
|
630
735
|
self._has_bound_to_economy = True
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from .societyagent import SocietyAgent
|
2
|
+
from .firmagent import FirmAgent
|
3
|
+
from .bankagent import BankAgent
|
4
|
+
from .nbsagent import NBSAgent
|
5
|
+
from .governmentagent import GovernmentAgent
|
6
|
+
from .memory_config import memory_config_societyagent, memory_config_government, memory_config_firm, memory_config_bank, memory_config_nbs
|
7
|
+
|
8
|
+
__all__ = [
|
9
|
+
"SocietyAgent",
|
10
|
+
"FirmAgent",
|
11
|
+
"BankAgent",
|
12
|
+
"NBSAgent",
|
13
|
+
"GovernmentAgent",
|
14
|
+
"memory_config_societyagent",
|
15
|
+
"memory_config_government",
|
16
|
+
"memory_config_firm",
|
17
|
+
"memory_config_bank",
|
18
|
+
"memory_config_nbs",
|
19
|
+
]
|
20
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
import numpy as np
|
5
|
+
from pycityagent import Simulator, InstitutionAgent
|
6
|
+
from pycityagent.llm.llm import LLM
|
7
|
+
from pycityagent.economy import EconomyClient
|
8
|
+
from pycityagent.message import Messager
|
9
|
+
from pycityagent.memory import Memory
|
10
|
+
import logging
|
11
|
+
|
12
|
+
logger = logging.getLogger("pycityagent")
|
13
|
+
|
14
|
+
class BankAgent(InstitutionAgent):
|
15
|
+
def __init__(self,
|
16
|
+
name: str,
|
17
|
+
llm_client: Optional[LLM] = None,
|
18
|
+
simulator: Optional[Simulator] = None,
|
19
|
+
memory: Optional[Memory] = None,
|
20
|
+
economy_client: Optional[EconomyClient] = None,
|
21
|
+
messager: Optional[Messager] = None,
|
22
|
+
avro_file: Optional[dict] = None,
|
23
|
+
) -> None:
|
24
|
+
super().__init__(name=name, llm_client=llm_client, simulator=simulator, memory=memory, economy_client=economy_client, messager=messager, avro_file=avro_file)
|
25
|
+
self.initailzed = False
|
26
|
+
self.last_time_trigger = None
|
27
|
+
self.time_diff = 30 * 24 * 60 * 60
|
28
|
+
self.forward_times = 0
|
29
|
+
|
30
|
+
async def month_trigger(self):
|
31
|
+
now_time = await self.simulator.get_time()
|
32
|
+
if self.last_time_trigger is None:
|
33
|
+
self.last_time_trigger = now_time
|
34
|
+
return False
|
35
|
+
if now_time - self.last_time_trigger >= self.time_diff:
|
36
|
+
self.last_time_trigger = now_time
|
37
|
+
return True
|
38
|
+
return False
|
39
|
+
|
40
|
+
async def gather_messages(self, agent_ids, content):
|
41
|
+
infos = await super().gather_messages(agent_ids, content)
|
42
|
+
return [info['content'] for info in infos]
|
43
|
+
|
44
|
+
async def forward(self):
|
45
|
+
if await self.month_trigger():
|
46
|
+
citizens = await self.memory.get("citizens")
|
47
|
+
while True:
|
48
|
+
agents_forward = await self.gather_messages(citizens, 'forward')
|
49
|
+
if np.all(np.array(agents_forward) > self.forward_times):
|
50
|
+
break
|
51
|
+
await asyncio.sleep(1)
|
52
|
+
self.forward_times += 1
|
53
|
+
for uuid in citizens:
|
54
|
+
await self.send_message_to_agent(uuid, f"bank_forward@{self.forward_times}")
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from .mobility_block import MobilityBlock
|
2
|
+
from .cognition_block import CognitionBlock
|
3
|
+
from .plan_block import PlanBlock
|
4
|
+
from .needs_block import NeedsBlock
|
5
|
+
from .social_block import SocialBlock
|
6
|
+
from .economy_block import EconomyBlock
|
7
|
+
from .other_block import OtherBlock
|
8
|
+
from .time_block import TimeBlock
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"MobilityBlock",
|
12
|
+
"CognitionBlock",
|
13
|
+
"PlanBlock",
|
14
|
+
"NeedsBlock",
|
15
|
+
"SocialBlock",
|
16
|
+
"EconomyBlock",
|
17
|
+
"OtherBlock",
|
18
|
+
"LongTermDecisionBlock",
|
19
|
+
"TimeBlock",
|
20
|
+
]
|
@@ -0,0 +1,304 @@
|
|
1
|
+
from pycityagent.llm.llm import LLM
|
2
|
+
from pycityagent.workflow.block import Block
|
3
|
+
from pycityagent.memory import Memory
|
4
|
+
from pycityagent.environment.simulator import Simulator
|
5
|
+
from pycityagent.workflow.prompt import FormatPrompt
|
6
|
+
import json
|
7
|
+
import logging
|
8
|
+
logger = logging.getLogger("pycityagent")
|
9
|
+
|
10
|
+
def extract_json(output_str):
|
11
|
+
try:
|
12
|
+
# Find the positions of the first '{' and the last '}'
|
13
|
+
start = output_str.find('{')
|
14
|
+
end = output_str.rfind('}')
|
15
|
+
|
16
|
+
# Extract the substring containing the JSON
|
17
|
+
json_str = output_str[start:end+1]
|
18
|
+
|
19
|
+
# Convert the JSON string to a dictionary
|
20
|
+
return json_str
|
21
|
+
except (ValueError, json.JSONDecodeError) as e:
|
22
|
+
logger.warning(f"Failed to extract JSON: {e}")
|
23
|
+
return None
|
24
|
+
|
25
|
+
class CognitionBlock(Block):
|
26
|
+
def __init__(self, llm: LLM, memory: Memory, simulator: Simulator):
|
27
|
+
super().__init__("CognitionBlock", llm, memory, simulator)
|
28
|
+
self.last_trigger_time = None
|
29
|
+
self.time_diff = 24*60*60 # 24小时
|
30
|
+
self.trigger_time = 0
|
31
|
+
self.token_consumption = 0
|
32
|
+
|
33
|
+
async def set_status(self, status):
|
34
|
+
for key in status:
|
35
|
+
await self.memory.update(key, status[key])
|
36
|
+
return
|
37
|
+
|
38
|
+
async def check_trigger(self):
|
39
|
+
now_time = await self.simulator.get_time()
|
40
|
+
if self.last_trigger_time is None or now_time - self.last_trigger_time > self.time_diff:
|
41
|
+
self.last_trigger_time = now_time
|
42
|
+
return True
|
43
|
+
return False
|
44
|
+
|
45
|
+
async def attitude_update(self, topic):
|
46
|
+
description_prompt = """
|
47
|
+
You are a {gender}, aged {age}, belonging to the {race} race and identifying as {religion}.
|
48
|
+
Your marital status is {marital_status}, and you currently reside in a {residence} area.
|
49
|
+
Your occupation is {occupation}, and your education level is {education}.
|
50
|
+
You are {personality}, with a consumption level of {consumption} and a family consumption level of {family_consumption}.
|
51
|
+
Your income is {income}, and you are skilled in {skill}.
|
52
|
+
My current emotion intensities are (0 meaning not at all, 10 meaning very much):
|
53
|
+
sadness: {sadness}, joy: {joy}, fear: {fear}, disgust: {disgust}, anger: {anger}, surprise: {surprise}.
|
54
|
+
You have the following thoughts: {thought}.
|
55
|
+
In the following 21 words, I have chosen {emotion_types} to represent your current status:
|
56
|
+
Joy, Distress, Resentment, Pity, Hope, Fear, Satisfaction, Relief, Disappointment, Pride, Admiration, Shame, Reproach, Liking, Disliking, Gratitude, Anger, Gratification, Remorse, Love, Hate.
|
57
|
+
"""
|
58
|
+
incident_list = await self.memory.get("incident")
|
59
|
+
if incident_list:
|
60
|
+
incident_prompt = "Today, these incidents happened:"
|
61
|
+
for incident in incident_list:
|
62
|
+
incident_prompt += incident
|
63
|
+
else:
|
64
|
+
incident_prompt = "No incidents happened today."
|
65
|
+
attitude = await self.memory.get("attitude")
|
66
|
+
if topic in attitude:
|
67
|
+
previous_attitude = str(attitude[topic]) # Convert to string
|
68
|
+
problem_prompt = (
|
69
|
+
f"You need to decide your attitude towards topic: {topic}, "
|
70
|
+
f"which you previously rated your attitude towards this topic as: {previous_attitude} "
|
71
|
+
"(0 meaning oppose, 10 meaning support). "
|
72
|
+
"Please return a new attitude rating (0-10, smaller meaning oppose, larger meaning support) in JSON format, and explain, e.g. {{\"attitude\": 5}}"
|
73
|
+
)
|
74
|
+
else:
|
75
|
+
problem_prompt = (
|
76
|
+
f"You need to decide your attitude towards topic: {topic}, "
|
77
|
+
"which you have not rated your attitude towards this topic yet. "
|
78
|
+
"(0 meaning oppose, 10 meaning support). "
|
79
|
+
"Please return a new attitude rating (0-10, smaller meaning oppose, larger meaning support) in JSON format, and explain, e.g. {{\"attitude\": 5}}"
|
80
|
+
)
|
81
|
+
question_prompt = description_prompt + incident_prompt + problem_prompt
|
82
|
+
question_prompt = FormatPrompt(question_prompt)
|
83
|
+
emotion = await self.memory.get("emotion")
|
84
|
+
sadness = emotion["sadness"]
|
85
|
+
joy = emotion["joy"]
|
86
|
+
fear = emotion["fear"]
|
87
|
+
disgust = emotion["disgust"]
|
88
|
+
anger = emotion["anger"]
|
89
|
+
surprise = emotion["surprise"]
|
90
|
+
|
91
|
+
question_prompt.format(
|
92
|
+
gender=await self.memory.get("gender"),
|
93
|
+
age=await self.memory.get("age"),
|
94
|
+
race=await self.memory.get("race"),
|
95
|
+
religion=await self.memory.get("religion"),
|
96
|
+
marital_status=await self.memory.get("marital_status"),
|
97
|
+
residence=await self.memory.get("residence"),
|
98
|
+
occupation=await self.memory.get("occupation"),
|
99
|
+
education=await self.memory.get("education"),
|
100
|
+
personality=await self.memory.get("personality"),
|
101
|
+
consumption=await self.memory.get("consumption"),
|
102
|
+
family_consumption=await self.memory.get("family_consumption"),
|
103
|
+
income=await self.memory.get("income"),
|
104
|
+
skill=await self.memory.get("skill"),
|
105
|
+
sadness=sadness,
|
106
|
+
joy=joy,
|
107
|
+
fear=fear,
|
108
|
+
disgust=disgust,
|
109
|
+
anger=anger,
|
110
|
+
surprise=surprise,
|
111
|
+
thought=await self.memory.get("thought"),
|
112
|
+
emotion_types=await self.memory.get("emotion_types")
|
113
|
+
)
|
114
|
+
evaluation = True
|
115
|
+
for retry in range(10):
|
116
|
+
try:
|
117
|
+
response = await self.llm.atext_request(question_prompt.to_dialog(), timeout=300)
|
118
|
+
response = json.loads(extract_json(response))
|
119
|
+
evaluation = False
|
120
|
+
break
|
121
|
+
except:
|
122
|
+
pass
|
123
|
+
if evaluation:
|
124
|
+
raise f"Request for attitude:{topic} update failed"
|
125
|
+
logger.info(f"""Cognition updated attitude:{topic}:
|
126
|
+
attitude: {response['attitude']}""")
|
127
|
+
attitude[topic] = response["attitude"]
|
128
|
+
await self.memory.update("attitude", attitude)
|
129
|
+
return
|
130
|
+
|
131
|
+
async def forward(self): #每日结算
|
132
|
+
whether_trigger = await self.check_trigger()
|
133
|
+
if not whether_trigger:
|
134
|
+
return
|
135
|
+
self.trigger_time += 1
|
136
|
+
consumption_start = self.llm.prompt_tokens_used + self.llm.completion_tokens_used
|
137
|
+
|
138
|
+
description_prompt = """
|
139
|
+
You are a {gender}, aged {age}, belonging to the {race} race and identifying as {religion}.
|
140
|
+
Your marital status is {marital_status}, and you currently reside in a {residence} area.
|
141
|
+
Your occupation is {occupation}, and your education level is {education}.
|
142
|
+
You are {personality}, with a consumption level of {consumption} and a family consumption level of {family_consumption}.
|
143
|
+
Your income is {income}, and you are skilled in {skill}.
|
144
|
+
My current emotion intensities are (0 meaning not at all, 10 meaning very much):
|
145
|
+
sadness: {sadness}, joy: {joy}, fear: {fear}, disgust: {disgust}, anger: {anger}, surprise: {surprise}.
|
146
|
+
You have the following thoughts: {thought}.
|
147
|
+
In the following 21 words, I have chosen {emotion_types} to represent your current status:
|
148
|
+
Joy, Distress, Resentment, Pity, Hope, Fear, Satisfaction, Relief, Disappointment, Pride, Admiration, Shame, Reproach, Liking, Disliking, Gratitude, Anger, Gratification, Remorse, Love, Hate.
|
149
|
+
"""
|
150
|
+
incident_list = await self.memory.get("incident")
|
151
|
+
if incident_list:
|
152
|
+
incident_prompt = "Today, these incidents happened:"
|
153
|
+
for incident in incident_list:
|
154
|
+
incident_prompt += incident
|
155
|
+
else:
|
156
|
+
incident_prompt = "No incidents happened today."
|
157
|
+
question_prompt = """
|
158
|
+
Please reconsider your emotion intensities:
|
159
|
+
sadness, joy, fear, disgust, anger, surprise (0 meaning not at all, 10 meaning very much).
|
160
|
+
Also summerize you current thoughts, and choose a word to describe your status: Joy, Distress, Resentment, Pity, Hope, Fear, Satisfaction, Relief, Disappointment, Pride, Admiration, Shame, Reproach, Liking, Disliking, Gratitude, Anger, Gratification, Remorse, Love, Hate.
|
161
|
+
Return in JSON format, e.g. {{"sadness": 5, "joy": 5, "fear": 5, "disgust": 5, "anger": 5, "surprise": 5, "thought": "Currently nothing good or bad is happening, I think ....", "word": "Relief"}}"""
|
162
|
+
question_prompt = description_prompt + incident_prompt + question_prompt
|
163
|
+
question_prompt = FormatPrompt(question_prompt)
|
164
|
+
emotion = await self.memory.get("emotion")
|
165
|
+
sadness = emotion["sadness"]
|
166
|
+
joy = emotion["joy"]
|
167
|
+
fear = emotion["fear"]
|
168
|
+
disgust = emotion["disgust"]
|
169
|
+
anger = emotion["anger"]
|
170
|
+
surprise = emotion["surprise"]
|
171
|
+
question_prompt.format(
|
172
|
+
gender=await self.memory.get("gender"),
|
173
|
+
age=await self.memory.get("age"),
|
174
|
+
race=await self.memory.get("race"),
|
175
|
+
religion=await self.memory.get("religion"),
|
176
|
+
marital_status=await self.memory.get("marital_status"),
|
177
|
+
residence=await self.memory.get("residence"),
|
178
|
+
occupation=await self.memory.get("occupation"),
|
179
|
+
education=await self.memory.get("education"),
|
180
|
+
personality=await self.memory.get("personality"),
|
181
|
+
consumption=await self.memory.get("consumption"),
|
182
|
+
family_consumption=await self.memory.get("family_consumption"),
|
183
|
+
income=await self.memory.get("income"),
|
184
|
+
skill=await self.memory.get("skill"),
|
185
|
+
sadness=sadness,
|
186
|
+
joy=joy,
|
187
|
+
fear=fear,
|
188
|
+
disgust=disgust,
|
189
|
+
anger=anger,
|
190
|
+
surprise=surprise,
|
191
|
+
emotion=await self.memory.get("emotion"),
|
192
|
+
thought=await self.memory.get("thought"),
|
193
|
+
emotion_types=await self.memory.get("emotion_types")
|
194
|
+
)
|
195
|
+
|
196
|
+
evaluation = True
|
197
|
+
for retry in range(10):
|
198
|
+
try:
|
199
|
+
response = await self.llm.atext_request(question_prompt.to_dialog(), timeout=300)
|
200
|
+
response = json.loads(extract_json(response))
|
201
|
+
evaluation = False
|
202
|
+
break
|
203
|
+
except:
|
204
|
+
pass
|
205
|
+
if evaluation:
|
206
|
+
raise Exception("Request for cognition update failed")
|
207
|
+
logger.info(f"""Cognition updated emotion intensities:
|
208
|
+
sadness: {response['sadness']},
|
209
|
+
joy: {response['joy']},
|
210
|
+
fear: {response['fear']},
|
211
|
+
disgust: {response['disgust']},
|
212
|
+
anger: {response['anger']},
|
213
|
+
surprise: {response['surprise']}"
|
214
|
+
thought: {response['thought']}
|
215
|
+
emotion_types: {response['word']}""")
|
216
|
+
|
217
|
+
await self.memory.update("incident", [])
|
218
|
+
await self.memory.update("emotion", {"sadness": int(response["sadness"]), "joy": int(response["joy"]), "fear": int(response["fear"]), "disgust": int(response["disgust"]), "anger": int(response["anger"]), "surprise": int(response["surprise"])})
|
219
|
+
await self.memory.update("thought", str(response["thought"]))
|
220
|
+
await self.memory.update("emotion_types", str(response["word"]))
|
221
|
+
|
222
|
+
consumption_end = self.llm.prompt_tokens_used + self.llm.completion_tokens_used
|
223
|
+
self.token_consumption += consumption_end - consumption_start
|
224
|
+
return
|
225
|
+
|
226
|
+
async def emotion_update(self, incident): #每日结算
|
227
|
+
whether_trigger = await self.check_trigger()
|
228
|
+
if not whether_trigger:
|
229
|
+
return
|
230
|
+
|
231
|
+
description_prompt = """
|
232
|
+
You are a {gender}, aged {age}, belonging to the {race} race and identifying as {religion}.
|
233
|
+
Your marital status is {marital_status}, and you currently reside in a {residence} area.
|
234
|
+
Your occupation is {occupation}, and your education level is {education}.
|
235
|
+
You are {personality}, with a consumption level of {consumption} and a family consumption level of {family_consumption}.
|
236
|
+
Your income is {income}, and you are skilled in {skill}.
|
237
|
+
My current emotion intensities are (0 meaning not at all, 10 meaning very much):
|
238
|
+
sadness: {sadness}, joy: {joy}, fear: {fear}, disgust: {disgust}, anger: {anger}, surprise: {surprise}.
|
239
|
+
You have the following thoughts: {thought}.
|
240
|
+
In the following 21 words, I have chosen {emotion_types} to represent your current status:
|
241
|
+
Joy, Distress, Resentment, Pity, Hope, Fear, Satisfaction, Relief, Disappointment, Pride, Admiration, Shame, Reproach, Liking, Disliking, Gratitude, Anger, Gratification, Remorse, Love, Hate.
|
242
|
+
"""
|
243
|
+
|
244
|
+
incident_prompt = incident #waiting for incident port
|
245
|
+
question_prompt = """
|
246
|
+
Please reconsider your emotion intensities:
|
247
|
+
sadness, joy, fear, disgust, anger, surprise (0 meaning not at all, 10 meaning very much).
|
248
|
+
Return in JSON format, e.g. {{"sadness": 5, "joy": 5, "fear": 5, "disgust": 5, "anger": 5, "surprise": 5}}"""
|
249
|
+
question_prompt = description_prompt + incident_prompt + question_prompt
|
250
|
+
question_prompt = FormatPrompt(question_prompt)
|
251
|
+
emotion = await self.memory.get("emotion")
|
252
|
+
sadness = emotion["sadness"]
|
253
|
+
joy = emotion["joy"]
|
254
|
+
fear = emotion["fear"]
|
255
|
+
disgust = emotion["disgust"]
|
256
|
+
anger = emotion["anger"]
|
257
|
+
surprise = emotion["surprise"]
|
258
|
+
question_prompt.format(
|
259
|
+
gender=await self.memory.get("gender"),
|
260
|
+
age=await self.memory.get("age"),
|
261
|
+
race=await self.memory.get("race"),
|
262
|
+
religion=await self.memory.get("religion"),
|
263
|
+
marital_status=await self.memory.get("marital_status"),
|
264
|
+
residence=await self.memory.get("residence"),
|
265
|
+
occupation=await self.memory.get("occupation"),
|
266
|
+
education=await self.memory.get("education"),
|
267
|
+
personality=await self.memory.get("personality"),
|
268
|
+
consumption=await self.memory.get("consumption"),
|
269
|
+
family_consumption=await self.memory.get("family_consumption"),
|
270
|
+
income=await self.memory.get("income"),
|
271
|
+
skill=await self.memory.get("skill"),
|
272
|
+
sadness=sadness,
|
273
|
+
joy=joy,
|
274
|
+
fear=fear,
|
275
|
+
disgust=disgust,
|
276
|
+
anger=anger,
|
277
|
+
surprise=surprise,
|
278
|
+
emotion=await self.memory.get("emotion"),
|
279
|
+
thought=await self.memory.get("thought"),
|
280
|
+
emotion_types=await self.memory.get("emotion_types")
|
281
|
+
)
|
282
|
+
|
283
|
+
evaluation = True
|
284
|
+
for retry in range(10):
|
285
|
+
try:
|
286
|
+
response = await self.llm.atext_request(question_prompt.to_dialog(), timeout=300)
|
287
|
+
response = json.loads(extract_json(response))
|
288
|
+
evaluation = False
|
289
|
+
break
|
290
|
+
except Exception as e:
|
291
|
+
print(e)
|
292
|
+
pass
|
293
|
+
if evaluation:
|
294
|
+
raise Exception("Request for cognition update failed")
|
295
|
+
logger.info(f"""Cognition updated emotion intensities:
|
296
|
+
sadness: {response['sadness']},
|
297
|
+
joy: {response['joy']},
|
298
|
+
fear: {response['fear']},
|
299
|
+
disgust: {response['disgust']},
|
300
|
+
anger: {response['anger']},
|
301
|
+
surprise: {response['surprise']}""")
|
302
|
+
|
303
|
+
await self.memory.update("emotion", {"sadness": int(response["sadness"]), "joy": int(response["joy"]), "fear": int(response["fear"]), "disgust": int(response["disgust"]), "anger": int(response["anger"]), "surprise": int(response["surprise"])})
|
304
|
+
return
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import random
|
2
|
+
from typing import Dict, List
|
3
|
+
from pycityagent.llm.llm import LLM
|
4
|
+
from pycityagent.workflow.block import Block
|
5
|
+
from pycityagent.workflow.prompt import FormatPrompt
|
6
|
+
import logging
|
7
|
+
logger = logging.getLogger("pycityagent")
|
8
|
+
|
9
|
+
DISPATCHER_PROMPT = """
|
10
|
+
Based on the step information, select the most appropriate block to handle the task.
|
11
|
+
Each block has its specific functionality as described in the function schema.
|
12
|
+
|
13
|
+
Step information:
|
14
|
+
{step}
|
15
|
+
"""
|
16
|
+
|
17
|
+
class BlockDispatcher:
|
18
|
+
def __init__(self, llm: LLM):
|
19
|
+
self.llm = llm
|
20
|
+
self.blocks: Dict[str, Block] = {}
|
21
|
+
self.prompt = FormatPrompt(DISPATCHER_PROMPT)
|
22
|
+
|
23
|
+
def register_blocks(self, blocks: List[Block]) -> None:
|
24
|
+
"""Register multiple blocks at once"""
|
25
|
+
for block in blocks:
|
26
|
+
block_name = block.__class__.__name__.lower()
|
27
|
+
self.blocks[block_name] = block
|
28
|
+
|
29
|
+
def _get_function_schema(self) -> dict:
|
30
|
+
"""Generate function schema for LLM function call"""
|
31
|
+
# 创建 block 选项说明
|
32
|
+
block_descriptions = {
|
33
|
+
name: block.description # type: ignore
|
34
|
+
for name, block in self.blocks.items()
|
35
|
+
}
|
36
|
+
|
37
|
+
return {
|
38
|
+
"type": "function",
|
39
|
+
"function": {
|
40
|
+
"name": "select_block",
|
41
|
+
"description": "Select the most appropriate block based on the step information",
|
42
|
+
"parameters": {
|
43
|
+
"type": "object",
|
44
|
+
"properties": {
|
45
|
+
"block_name": {
|
46
|
+
"type": "string",
|
47
|
+
"enum": list(self.blocks.keys()),
|
48
|
+
"description": f"Available blocks and their descriptions: {block_descriptions}"
|
49
|
+
}
|
50
|
+
},
|
51
|
+
"required": ["block_name"]
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
async def dispatch(self, step: dict) -> Block:
|
57
|
+
"""Dispatch the step to appropriate block based on LLM function call"""
|
58
|
+
try:
|
59
|
+
function_schema = self._get_function_schema()
|
60
|
+
self.prompt.format(step=step)
|
61
|
+
|
62
|
+
# Call LLM with tools schema
|
63
|
+
function_args = await self.llm.atext_request(
|
64
|
+
self.prompt.to_dialog(),
|
65
|
+
tools=[function_schema],
|
66
|
+
tool_choice={"type": "function", "function": {"name": "select_block"}}
|
67
|
+
)
|
68
|
+
|
69
|
+
selected_block = function_args.get("block_name") # type: ignore
|
70
|
+
|
71
|
+
if selected_block not in self.blocks:
|
72
|
+
raise ValueError(f"Selected block '{selected_block}' not found in registered blocks")
|
73
|
+
|
74
|
+
return self.blocks[selected_block]
|
75
|
+
|
76
|
+
except Exception as e:
|
77
|
+
logger.warning(f"Failed to dispatch block: {e}")
|
78
|
+
return random.choice(list(self.blocks.values()))
|