pycityagent 2.0.0a47__cp39-cp39-macosx_11_0_arm64.whl → 2.0.0a49__cp39-cp39-macosx_11_0_arm64.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. pycityagent/__init__.py +3 -2
  2. pycityagent/agent.py +109 -4
  3. pycityagent/cityagent/__init__.py +20 -0
  4. pycityagent/cityagent/bankagent.py +54 -0
  5. pycityagent/cityagent/blocks/__init__.py +20 -0
  6. pycityagent/cityagent/blocks/cognition_block.py +304 -0
  7. pycityagent/cityagent/blocks/dispatcher.py +78 -0
  8. pycityagent/cityagent/blocks/economy_block.py +356 -0
  9. pycityagent/cityagent/blocks/mobility_block.py +258 -0
  10. pycityagent/cityagent/blocks/needs_block.py +305 -0
  11. pycityagent/cityagent/blocks/other_block.py +103 -0
  12. pycityagent/cityagent/blocks/plan_block.py +309 -0
  13. pycityagent/cityagent/blocks/social_block.py +345 -0
  14. pycityagent/cityagent/blocks/time_block.py +116 -0
  15. pycityagent/cityagent/blocks/utils.py +66 -0
  16. pycityagent/cityagent/firmagent.py +75 -0
  17. pycityagent/cityagent/governmentagent.py +60 -0
  18. pycityagent/cityagent/initial.py +98 -0
  19. pycityagent/cityagent/memory_config.py +202 -0
  20. pycityagent/cityagent/nbsagent.py +92 -0
  21. pycityagent/cityagent/societyagent.py +291 -0
  22. pycityagent/memory/memory.py +0 -18
  23. pycityagent/message/messager.py +6 -3
  24. pycityagent/simulation/agentgroup.py +123 -37
  25. pycityagent/simulation/simulation.py +311 -316
  26. pycityagent/workflow/block.py +66 -1
  27. pycityagent/workflow/tool.py +9 -4
  28. {pycityagent-2.0.0a47.dist-info → pycityagent-2.0.0a49.dist-info}/METADATA +2 -2
  29. {pycityagent-2.0.0a47.dist-info → pycityagent-2.0.0a49.dist-info}/RECORD +33 -14
  30. {pycityagent-2.0.0a47.dist-info → pycityagent-2.0.0a49.dist-info}/LICENSE +0 -0
  31. {pycityagent-2.0.0a47.dist-info → pycityagent-2.0.0a49.dist-info}/WHEEL +0 -0
  32. {pycityagent-2.0.0a47.dist-info → pycityagent-2.0.0a49.dist-info}/entry_points.txt +0 -0
  33. {pycityagent-2.0.0a47.dist-info → pycityagent-2.0.0a49.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, 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) # type:ignore
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": await self.memory.get("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()))