pycityagent 2.0.0a66__cp312-cp312-macosx_11_0_arm64.whl → 2.0.0a67__cp312-cp312-macosx_11_0_arm64.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.
Files changed (87) hide show
  1. pycityagent/agent/agent.py +157 -57
  2. pycityagent/agent/agent_base.py +316 -43
  3. pycityagent/cityagent/bankagent.py +49 -9
  4. pycityagent/cityagent/blocks/__init__.py +1 -2
  5. pycityagent/cityagent/blocks/cognition_block.py +54 -31
  6. pycityagent/cityagent/blocks/dispatcher.py +22 -17
  7. pycityagent/cityagent/blocks/economy_block.py +46 -32
  8. pycityagent/cityagent/blocks/mobility_block.py +130 -100
  9. pycityagent/cityagent/blocks/needs_block.py +101 -44
  10. pycityagent/cityagent/blocks/other_block.py +42 -33
  11. pycityagent/cityagent/blocks/plan_block.py +59 -42
  12. pycityagent/cityagent/blocks/social_block.py +167 -116
  13. pycityagent/cityagent/blocks/utils.py +13 -6
  14. pycityagent/cityagent/firmagent.py +17 -35
  15. pycityagent/cityagent/governmentagent.py +3 -3
  16. pycityagent/cityagent/initial.py +79 -44
  17. pycityagent/cityagent/memory_config.py +108 -88
  18. pycityagent/cityagent/message_intercept.py +0 -4
  19. pycityagent/cityagent/metrics.py +41 -0
  20. pycityagent/cityagent/nbsagent.py +24 -36
  21. pycityagent/cityagent/societyagent.py +7 -3
  22. pycityagent/cli/wrapper.py +2 -2
  23. pycityagent/economy/econ_client.py +407 -81
  24. pycityagent/environment/__init__.py +0 -3
  25. pycityagent/environment/sim/__init__.py +0 -3
  26. pycityagent/environment/sim/aoi_service.py +2 -2
  27. pycityagent/environment/sim/client.py +3 -31
  28. pycityagent/environment/sim/clock_service.py +2 -2
  29. pycityagent/environment/sim/lane_service.py +8 -8
  30. pycityagent/environment/sim/light_service.py +8 -8
  31. pycityagent/environment/sim/pause_service.py +9 -10
  32. pycityagent/environment/sim/person_service.py +20 -20
  33. pycityagent/environment/sim/road_service.py +2 -2
  34. pycityagent/environment/sim/sim_env.py +21 -5
  35. pycityagent/environment/sim/social_service.py +4 -4
  36. pycityagent/environment/simulator.py +249 -27
  37. pycityagent/environment/utils/__init__.py +2 -2
  38. pycityagent/environment/utils/geojson.py +2 -2
  39. pycityagent/environment/utils/grpc.py +4 -4
  40. pycityagent/environment/utils/map_utils.py +2 -2
  41. pycityagent/llm/embeddings.py +147 -28
  42. pycityagent/llm/llm.py +122 -77
  43. pycityagent/llm/llmconfig.py +5 -0
  44. pycityagent/llm/utils.py +4 -0
  45. pycityagent/memory/__init__.py +0 -4
  46. pycityagent/memory/const.py +2 -2
  47. pycityagent/memory/faiss_query.py +140 -61
  48. pycityagent/memory/memory.py +393 -90
  49. pycityagent/memory/memory_base.py +140 -34
  50. pycityagent/memory/profile.py +13 -13
  51. pycityagent/memory/self_define.py +13 -13
  52. pycityagent/memory/state.py +14 -14
  53. pycityagent/message/message_interceptor.py +253 -3
  54. pycityagent/message/messager.py +133 -6
  55. pycityagent/metrics/mlflow_client.py +47 -4
  56. pycityagent/pycityagent-sim +0 -0
  57. pycityagent/pycityagent-ui +0 -0
  58. pycityagent/simulation/__init__.py +3 -2
  59. pycityagent/simulation/agentgroup.py +145 -52
  60. pycityagent/simulation/simulation.py +257 -62
  61. pycityagent/survey/manager.py +45 -3
  62. pycityagent/survey/models.py +42 -2
  63. pycityagent/tools/__init__.py +1 -2
  64. pycityagent/tools/tool.py +93 -69
  65. pycityagent/utils/avro_schema.py +2 -2
  66. pycityagent/utils/parsers/code_block_parser.py +1 -1
  67. pycityagent/utils/parsers/json_parser.py +2 -2
  68. pycityagent/utils/parsers/parser_base.py +2 -2
  69. pycityagent/workflow/block.py +64 -13
  70. pycityagent/workflow/prompt.py +31 -23
  71. pycityagent/workflow/trigger.py +91 -24
  72. {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/METADATA +2 -2
  73. pycityagent-2.0.0a67.dist-info/RECORD +97 -0
  74. pycityagent/environment/interact/__init__.py +0 -0
  75. pycityagent/environment/interact/interact.py +0 -198
  76. pycityagent/environment/message/__init__.py +0 -0
  77. pycityagent/environment/sence/__init__.py +0 -0
  78. pycityagent/environment/sence/static.py +0 -416
  79. pycityagent/environment/sidecar/__init__.py +0 -8
  80. pycityagent/environment/sidecar/sidecarv2.py +0 -109
  81. pycityagent/environment/sim/economy_services.py +0 -192
  82. pycityagent/metrics/utils/const.py +0 -0
  83. pycityagent-2.0.0a66.dist-info/RECORD +0 -105
  84. {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/LICENSE +0 -0
  85. {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/WHEEL +0 -0
  86. {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/entry_points.txt +0 -0
  87. {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,12 @@
1
1
  import json
2
+ import logging
3
+
2
4
  from pycityagent import Simulator
3
- from pycityagent.memory import Memory
4
5
  from pycityagent.llm import LLM
6
+ from pycityagent.memory import Memory
5
7
  from pycityagent.workflow.block import Block
6
8
  from pycityagent.workflow.prompt import FormatPrompt
7
- import logging
9
+
8
10
  logger = logging.getLogger("pycityagent")
9
11
 
10
12
  INITIAL_NEEDS_PROMPT = """You are an intelligent agent satisfaction initialization system. Based on the profile information below, please help initialize the agent's satisfaction levels and related parameters.
@@ -96,10 +98,12 @@ Example response format for whatever need adjustment (Do not return any other te
96
98
  }}
97
99
  """
98
100
 
101
+
99
102
  class NeedsBlock(Block):
100
103
  """
101
104
  Generate needs
102
105
  """
106
+
103
107
  def __init__(self, llm: LLM, memory: Memory, simulator: Simulator):
104
108
  super().__init__("NeedsBlock", llm=llm, memory=memory, simulator=simulator)
105
109
  self.evaluation_prompt = FormatPrompt(EVALUATION_PROMPT)
@@ -110,15 +114,29 @@ class NeedsBlock(Block):
110
114
  self.trigger_time = 0
111
115
  self.token_consumption = 0
112
116
  self.initialized = False
113
- self.alpha_H, self.alpha_D, self.alpha_P, self.alpha_C = 0.2, 0.08, 0.05, 0.03 # Hunger decay rate, Energy decay rate, Safety decay rate, Social decay rate
114
- self.T_H, self.T_D, self.T_P, self.T_C = 0.4, 0.2, 0.2, 0.2 # Hunger threshold, Energy threshold, Safety threshold, Social threshold
117
+ self.alpha_H, self.alpha_D, self.alpha_P, self.alpha_C = (
118
+ 0.2,
119
+ 0.08,
120
+ 0.05,
121
+ 0.03,
122
+ ) # Hunger decay rate, Energy decay rate, Safety decay rate, Social decay rate
123
+ self.T_H, self.T_D, self.T_P, self.T_C = (
124
+ 0.4,
125
+ 0.2,
126
+ 0.2,
127
+ 0.2,
128
+ ) # Hunger threshold, Energy threshold, Safety threshold, Social threshold
115
129
 
116
130
  async def initialize(self):
117
131
  day = await self.simulator.get_simulator_day()
118
132
  if day != self.now_day:
119
133
  self.now_day = day
120
- self.need_work = True
121
134
  self.initialized = False
135
+ workday = self.simulator.sence("day")
136
+ if workday == "Workday":
137
+ self.need_work = True
138
+ else:
139
+ self.need_work = False
122
140
 
123
141
  if not self.initialized:
124
142
  self.initial_prompt.format(
@@ -128,21 +146,32 @@ class NeedsBlock(Block):
128
146
  occupation=await self.memory.status.get("occupation"),
129
147
  age=await self.memory.status.get("age"),
130
148
  income=await self.memory.status.get("income"),
131
- now_time=await self.simulator.get_time(format_time=True)
132
- )
133
- response = await self.llm.atext_request(
134
- self.initial_prompt.to_dialog()
149
+ now_time=await self.simulator.get_time(format_time=True),
135
150
  )
151
+ response = await self.llm.atext_request(self.initial_prompt.to_dialog())
152
+ response = await self.llm.atext_request(self.initial_prompt.to_dialog())
136
153
  response = self.clean_json_response(response)
137
154
  try:
138
155
  satisfaction = json.loads(response)
139
156
  satisfactions = satisfaction["current_satisfaction"]
140
- await self.memory.status.update("hunger_satisfaction", satisfactions["hunger_satisfaction"])
141
- await self.memory.status.update("energy_satisfaction", satisfactions["energy_satisfaction"])
142
- await self.memory.status.update("safety_satisfaction", satisfactions["safety_satisfaction"])
143
- await self.memory.status.update("social_satisfaction", satisfactions["social_satisfaction"])
144
- self.alpha_H, self.alpha_D, self.alpha_P, self.alpha_C = satisfaction["decay_rates"].values()
145
- self.T_H, self.T_D, self.T_P, self.T_C = satisfaction["thresholds"].values()
157
+ await self.memory.status.update(
158
+ "hunger_satisfaction", satisfactions["hunger_satisfaction"]
159
+ )
160
+ await self.memory.status.update(
161
+ "energy_satisfaction", satisfactions["energy_satisfaction"]
162
+ )
163
+ await self.memory.status.update(
164
+ "safety_satisfaction", satisfactions["safety_satisfaction"]
165
+ )
166
+ await self.memory.status.update(
167
+ "social_satisfaction", satisfactions["social_satisfaction"]
168
+ )
169
+ self.alpha_H, self.alpha_D, self.alpha_P, self.alpha_C = satisfaction[
170
+ "decay_rates"
171
+ ].values()
172
+ self.T_H, self.T_D, self.T_P, self.T_C = satisfaction[
173
+ "thresholds"
174
+ ].values()
146
175
  except json.JSONDecodeError:
147
176
  logger.warning(f"初始化响应不是有效的JSON格式: {response}")
148
177
 
@@ -151,7 +180,9 @@ class NeedsBlock(Block):
151
180
  history.append(current_plan)
152
181
  await self.memory.status.update("plan_history", history)
153
182
  await self.memory.status.update("current_plan", None)
154
- await self.memory.status.update("current_step", {"intention": "", "type": ""})
183
+ await self.memory.status.update(
184
+ "current_step", {"intention": "", "type": ""}
185
+ )
155
186
  await self.memory.status.update("execution_context", {})
156
187
  self.initialized = True
157
188
 
@@ -162,7 +193,7 @@ class NeedsBlock(Block):
162
193
  self.last_evaluation_time = time_now
163
194
  time_diff = 0
164
195
  else:
165
- time_diff = (time_now - self.last_evaluation_time)/3600
196
+ time_diff = (time_now - self.last_evaluation_time) / 3600
166
197
  self.last_evaluation_time = time_now
167
198
 
168
199
  # 获取当前需求的满意度
@@ -170,17 +201,17 @@ class NeedsBlock(Block):
170
201
  energy_satisfaction = await self.memory.status.get("energy_satisfaction")
171
202
  safety_satisfaction = await self.memory.status.get("safety_satisfaction")
172
203
  social_satisfaction = await self.memory.status.get("social_satisfaction")
173
-
204
+
174
205
  # 根据经过的时间计算饥饿与疲劳的衰减
175
206
  hungry_decay = self.alpha_H * time_diff
176
207
  energy_decay = self.alpha_D * time_diff
177
208
  safety_decay = self.alpha_P * time_diff
178
209
  social_decay = self.alpha_C * time_diff
179
- hunger_satisfaction = max(0, hunger_satisfaction - hungry_decay)
210
+ hunger_satisfaction = max(0, hunger_satisfaction - hungry_decay)
180
211
  energy_satisfaction = max(0, energy_satisfaction - energy_decay)
181
212
  safety_satisfaction = max(0, safety_satisfaction - safety_decay)
182
213
  social_satisfaction = max(0, social_satisfaction - social_decay)
183
-
214
+
184
215
  # 更新满意度
185
216
  await self.memory.status.update("hunger_satisfaction", hunger_satisfaction)
186
217
  await self.memory.status.update("energy_satisfaction", energy_satisfaction)
@@ -190,7 +221,9 @@ class NeedsBlock(Block):
190
221
  async def update_when_plan_completed(self):
191
222
  # 判断当前是否有正在执行的plan
192
223
  current_plan = await self.memory.status.get("current_plan")
193
- if current_plan and (current_plan.get("completed") or current_plan.get("failed")):
224
+ if current_plan and (
225
+ current_plan.get("completed") or current_plan.get("failed")
226
+ ):
194
227
  # 评估计划执行过程并调整需求
195
228
  await self.evaluate_and_adjust_needs(current_plan)
196
229
  # 将完成的计划添加到历史记录
@@ -198,7 +231,9 @@ class NeedsBlock(Block):
198
231
  history.append(current_plan)
199
232
  await self.memory.status.update("plan_history", history)
200
233
  await self.memory.status.update("current_plan", None)
201
- await self.memory.status.update("current_step", {"intention": "", "type": ""})
234
+ await self.memory.status.update(
235
+ "current_step", {"intention": "", "type": ""}
236
+ )
202
237
  await self.memory.status.update("execution_context", {})
203
238
 
204
239
  async def determine_current_need(self):
@@ -211,14 +246,14 @@ class NeedsBlock(Block):
211
246
  # 调整方案为,如果当前的需求为空,或有更高级的需求出现,则调整需求
212
247
  current_plan = await self.memory.status.get("current_plan")
213
248
  current_need = await self.memory.status.get("current_need")
214
-
249
+
215
250
  # 当前没有计划或计划已执行完毕,获取所有需求值,按优先级检查各需求是否达到阈值
216
251
  if not current_plan or current_plan.get("completed"):
217
252
  # 按优先级顺序检查需求
218
253
  if hunger_satisfaction <= self.T_H:
219
254
  await self.memory.status.update("current_need", "hungry")
220
255
  elif energy_satisfaction <= self.T_D:
221
- await self.memory.status.update("current_need", "tired")
256
+ await self.memory.status.update("current_need", "tired")
222
257
  elif safety_satisfaction <= self.T_P:
223
258
  await self.memory.status.update("current_need", "safe")
224
259
  elif social_satisfaction <= self.T_C:
@@ -232,19 +267,34 @@ class NeedsBlock(Block):
232
267
  # 有正在执行的计划时,只在出现更高优先级需求时调整
233
268
  needs_changed = False
234
269
  new_need = None
235
- if hunger_satisfaction <= self.T_H and current_need not in ["hungry", "tired"]:
270
+ if hunger_satisfaction <= self.T_H and current_need not in [
271
+ "hungry",
272
+ "tired",
273
+ ]:
236
274
  new_need = "hungry"
237
275
  needs_changed = True
238
- elif energy_satisfaction <= self.T_D and current_need not in ["hungry", "tired"]:
276
+ elif energy_satisfaction <= self.T_D and current_need not in [
277
+ "hungry",
278
+ "tired",
279
+ ]:
239
280
  new_need = "tired"
240
281
  needs_changed = True
241
- elif safety_satisfaction <= self.T_P and current_need not in ["hungry", "tired", "safe"]:
282
+ elif safety_satisfaction <= self.T_P and current_need not in [
283
+ "hungry",
284
+ "tired",
285
+ "safe",
286
+ ]:
242
287
  new_need = "safe"
243
288
  needs_changed = True
244
- elif social_satisfaction <= self.T_C and current_need not in ["hungry", "tired", "safe", "social"]:
289
+ elif social_satisfaction <= self.T_C and current_need not in [
290
+ "hungry",
291
+ "tired",
292
+ "safe",
293
+ "social",
294
+ ]:
245
295
  new_need = "social"
246
296
  needs_changed = True
247
-
297
+
248
298
  # 如果需求发生变化,中断当前计划
249
299
  if needs_changed:
250
300
  await self.evaluate_and_adjust_needs(current_plan)
@@ -253,17 +303,19 @@ class NeedsBlock(Block):
253
303
  await self.memory.status.update("current_need", new_need)
254
304
  await self.memory.status.update("plan_history", history)
255
305
  await self.memory.status.update("current_plan", None)
256
- await self.memory.status.update("current_step", {"intention": "", "type": ""})
306
+ await self.memory.status.update(
307
+ "current_step", {"intention": "", "type": ""}
308
+ )
257
309
  await self.memory.status.update("execution_context", {})
258
310
 
259
311
  async def evaluate_and_adjust_needs(self, completed_plan):
260
312
  # 获取执行的计划和评估结果
261
313
  evaluation_results = []
262
314
  for step in completed_plan["steps"]:
263
- if 'evaluation' in step['evaluation']:
264
- eva_ = step['evaluation']['evaluation']
315
+ if "evaluation" in step["evaluation"]:
316
+ eva_ = step["evaluation"]["evaluation"]
265
317
  else:
266
- eva_ = 'Plan failed, not completed'
318
+ eva_ = "Plan failed, not completed"
267
319
  evaluation_results.append(f"- {step['intention']} ({step['type']}): {eva_}")
268
320
  evaluation_results = "\n".join(evaluation_results)
269
321
 
@@ -279,26 +331,31 @@ class NeedsBlock(Block):
279
331
  social_satisfaction=await self.memory.status.get("social_satisfaction"),
280
332
  )
281
333
 
282
- response = await self.llm.atext_request(
283
- self.evaluation_prompt.to_dialog()
284
- )
334
+ response = await self.llm.atext_request(self.evaluation_prompt.to_dialog())
285
335
 
286
- try:
287
- new_satisfaction = json.loads(self.clean_json_response(response)) # type: ignore
336
+ try:
337
+ new_satisfaction = json.loads(self.clean_json_response(response)) # type: ignore
288
338
  # 更新所有需求的数值
289
339
  for need_type, new_value in new_satisfaction.items():
290
- if need_type in ['hunger_satisfaction', 'energy_satisfaction', 'safety_satisfaction', 'social_satisfaction']:
340
+ if need_type in [
341
+ "hunger_satisfaction",
342
+ "energy_satisfaction",
343
+ "safety_satisfaction",
344
+ "social_satisfaction",
345
+ ]:
291
346
  await self.memory.status.update(need_type, new_value)
292
347
  except json.JSONDecodeError:
293
- logger.warning(f"Evaluation response is not a valid JSON format: {response}")
348
+ logger.warning(
349
+ f"Evaluation response is not a valid JSON format: {response}"
350
+ )
294
351
  except Exception as e:
295
352
  logger.warning(f"Error processing evaluation response: {str(e)}")
296
353
  logger.warning(f"Original response: {response}")
297
354
 
298
355
  def clean_json_response(self, response: str) -> str:
299
356
  """清理LLM响应中的特殊字符"""
300
- response = response.replace('```json', '').replace('```', '')
301
- return response.strip()
357
+ response = response.replace("```json", "").replace("```", "")
358
+ return response.strip()
302
359
 
303
360
  async def forward(self):
304
361
  await self.initialize()
@@ -308,6 +365,6 @@ class NeedsBlock(Block):
308
365
 
309
366
  # update when plan completed
310
367
  await self.update_when_plan_completed()
311
-
368
+
312
369
  # determine current need
313
- await self.determine_current_need()
370
+ await self.determine_current_need()
@@ -1,15 +1,18 @@
1
1
  import json
2
+ import logging
2
3
  import random
3
4
 
4
- from .dispatcher import BlockDispatcher
5
5
  from pycityagent.llm.llm import LLM
6
- from pycityagent.workflow.block import Block
7
6
  from pycityagent.memory import Memory
7
+ from pycityagent.workflow.block import Block
8
8
  from pycityagent.workflow.prompt import FormatPrompt
9
- from .utils import clean_json_response, TIME_ESTIMATE_PROMPT
10
- import logging
9
+
10
+ from .dispatcher import BlockDispatcher
11
+ from .utils import TIME_ESTIMATE_PROMPT, clean_json_response
12
+
11
13
  logger = logging.getLogger("pycityagent")
12
14
 
15
+
13
16
  class SleepBlock(Block):
14
17
  def __init__(self, llm: LLM, memory: Memory):
15
18
  super().__init__("SleepBlock", llm=llm, memory=memory)
@@ -18,8 +21,8 @@ class SleepBlock(Block):
18
21
 
19
22
  async def forward(self, step, context):
20
23
  self.guidance_prompt.format(
21
- plan=context['plan'],
22
- intention=step['intention'],
24
+ plan=context["plan"],
25
+ intention=step["intention"],
23
26
  emotion_types=await self.memory.status.get("emotion_types"),
24
27
  )
25
28
  result = await self.llm.atext_request(self.guidance_prompt.to_dialog())
@@ -28,25 +31,27 @@ class SleepBlock(Block):
28
31
  try:
29
32
  result = json.loads(result)
30
33
  return {
31
- 'success': True,
32
- 'evaluation': f'Sleep: {step["intention"]}',
33
- 'consumed_time': result['time'],
34
- 'node_id': node_id
34
+ "success": True,
35
+ "evaluation": f'Sleep: {step["intention"]}',
36
+ "consumed_time": result["time"],
37
+ "node_id": node_id,
35
38
  }
36
39
  except Exception as e:
37
40
  logger.warning(f"解析时间评估响应时发生错误: {str(e)}, 原始结果: {result}")
38
41
  return {
39
- 'success': True,
40
- 'evaluation': f'Sleep: {step["intention"]}',
41
- 'consumed_time': random.randint(1, 10)*60,
42
- 'node_id': node_id
42
+ "success": True,
43
+ "evaluation": f'Sleep: {step["intention"]}',
44
+ "consumed_time": random.randint(1, 10) * 60,
45
+ "node_id": node_id,
43
46
  }
44
-
47
+
48
+
45
49
  class OtherNoneBlock(Block):
46
50
  """
47
51
  空操作
48
52
  OtherNoneBlock
49
53
  """
54
+
50
55
  def __init__(self, llm: LLM, memory: Memory):
51
56
  super().__init__("OtherNoneBlock", llm=llm, memory=memory)
52
57
  self.description = "Used to handle other cases"
@@ -54,30 +59,32 @@ class OtherNoneBlock(Block):
54
59
 
55
60
  async def forward(self, step, context):
56
61
  self.guidance_prompt.format(
57
- plan=context['plan'],
58
- intention=step['intention'],
62
+ plan=context["plan"],
63
+ intention=step["intention"],
59
64
  emotion_types=await self.memory.status.get("emotion_types"),
60
65
  )
61
66
  result = await self.llm.atext_request(self.guidance_prompt.to_dialog())
62
67
  result = clean_json_response(result)
63
- node_id = await self.memory.stream.add_other(description=f"I {step['intention']}")
68
+ node_id = await self.memory.stream.add_other(
69
+ description=f"I {step['intention']}"
70
+ )
64
71
  try:
65
72
  result = json.loads(result)
66
73
  return {
67
- 'success': True,
68
- 'evaluation': f'Finished executing {step["intention"]}',
69
- 'consumed_time': result['time'],
70
- 'node_id': node_id
74
+ "success": True,
75
+ "evaluation": f'Finished executing {step["intention"]}',
76
+ "consumed_time": result["time"],
77
+ "node_id": node_id,
71
78
  }
72
79
  except Exception as e:
73
80
  logger.warning(f"解析时间评估响应时发生错误: {str(e)}, 原始结果: {result}")
74
81
  return {
75
- 'success': True,
76
- 'evaluation': f'Finished executing {step["intention"]}',
77
- 'consumed_time': random.randint(1, 180),
78
- 'node_id': node_id
82
+ "success": True,
83
+ "evaluation": f'Finished executing {step["intention"]}',
84
+ "consumed_time": random.randint(1, 180),
85
+ "node_id": node_id,
79
86
  }
80
-
87
+
81
88
 
82
89
  class OtherBlock(Block):
83
90
  sleep_block: SleepBlock
@@ -97,15 +104,17 @@ class OtherBlock(Block):
97
104
 
98
105
  async def forward(self, step, context):
99
106
  self.trigger_time += 1
100
- consumption_start = self.llm.prompt_tokens_used + self.llm.completion_tokens_used
107
+ consumption_start = (
108
+ self.llm.prompt_tokens_used + self.llm.completion_tokens_used
109
+ )
101
110
 
102
111
  # Select the appropriate sub-block using dispatcher
103
112
  selected_block = await self.dispatcher.dispatch(step)
104
-
113
+
105
114
  # Execute the selected sub-block and get the result
106
- result = await selected_block.forward(step, context) # type: ignore
107
-
115
+ result = await selected_block.forward(step, context) # type: ignore
116
+
108
117
  consumption_end = self.llm.prompt_tokens_used + self.llm.completion_tokens_used
109
118
  self.token_consumption += consumption_end - consumption_start
110
-
111
- return result
119
+
120
+ return result
@@ -1,12 +1,13 @@
1
1
  import json
2
+ import logging
2
3
  import random
3
4
  from typing import Dict, List
5
+
4
6
  from pycityagent.environment.simulator import Simulator
5
- from pycityagent.workflow import Block
6
7
  from pycityagent.llm import LLM
7
8
  from pycityagent.memory import Memory
9
+ from pycityagent.workflow import Block
8
10
  from pycityagent.workflow.prompt import FormatPrompt
9
- import logging
10
11
 
11
12
  logger = logging.getLogger("pycityagent")
12
13
 
@@ -155,14 +156,11 @@ Example outputs (Do not return any other text):
155
156
  }}
156
157
  """
157
158
 
159
+
158
160
  class PlanBlock(Block):
159
161
  configurable_fields: List[str] = ["max_plan_steps"]
160
- default_values = {
161
- "max_plan_steps": 6
162
- }
163
- fields_description = {
164
- "max_plan_steps": "The maximum number of steps in a plan"
165
- }
162
+ default_values = {"max_plan_steps": 6}
163
+ fields_description = {"max_plan_steps": "The maximum number of steps in a plan"}
166
164
 
167
165
  def __init__(self, llm: LLM, memory: Memory, simulator: Simulator):
168
166
  super().__init__("PlanBlock", llm=llm, memory=memory, simulator=simulator)
@@ -171,11 +169,11 @@ class PlanBlock(Block):
171
169
  self.trigger_time = 0
172
170
  self.token_consumption = 0
173
171
  self.guidance_options = {
174
- "hungry": ['Eat at home', 'Eat outside'],
175
- "tired": ['Sleep', 'Take a nap'],
176
- "safe": ['Work'],
177
- "social": ['Online social', 'Shopping'],
178
- "whatever": ['Learning', 'Entertainment', 'Hang out', 'Exercise']
172
+ "hungry": ["Eat at home", "Eat outside"],
173
+ "tired": ["Sleep", "Take a nap"],
174
+ "safe": ["Go to work"],
175
+ "social": ["Online social", "Shopping"],
176
+ "whatever": ["Learning", "Entertainment", "Hang out", "Exercise"],
179
177
  }
180
178
 
181
179
  # configurable fields
@@ -187,9 +185,15 @@ class PlanBlock(Block):
187
185
  home_location = await self.memory.status.get("home")
188
186
  work_location = await self.memory.status.get("work")
189
187
  current_location = "Out"
190
- if 'aoi_position' in position_now and position_now['aoi_position'] == home_location['aoi_position']:
188
+ if (
189
+ "aoi_position" in position_now
190
+ and position_now["aoi_position"] == home_location["aoi_position"]
191
+ ):
191
192
  current_location = "At home"
192
- elif 'aoi_position' in position_now and position_now['aoi_position'] == work_location['aoi_position']:
193
+ elif (
194
+ "aoi_position" in position_now
195
+ and position_now["aoi_position"] == work_location["aoi_position"]
196
+ ):
193
197
  current_location = "At workplace"
194
198
  current_time = await self.simulator.get_time(format_time=True)
195
199
  environment = await self.memory.status.get("environment")
@@ -203,29 +207,37 @@ class PlanBlock(Block):
203
207
  current_time=current_time,
204
208
  environment=environment,
205
209
  emotion_types=await self.memory.status.get("emotion_types"),
206
- thought=await self.memory.status.get("thought")
210
+ thought=await self.memory.status.get("thought"),
207
211
  )
208
212
 
209
213
  response = await self.llm.atext_request(
210
214
  self.guidance_prompt.to_dialog()
211
- ) # type: ignore
215
+ ) # type: ignore
212
216
 
213
217
  try:
214
- result = json.loads(self.clean_json_response(response)) # type: ignore
218
+ result = json.loads(self.clean_json_response(response)) # type: ignore
215
219
  return result
216
220
  except Exception as e:
217
221
  logger.warning(f"Error parsing guidance selection response: {str(e)}")
218
- return None # type: ignore
222
+ return None # type: ignore
219
223
 
220
- async def generate_detailed_plan(self, current_need: str, selected_option: str) -> Dict:
224
+ async def generate_detailed_plan(
225
+ self, current_need: str, selected_option: str
226
+ ) -> Dict:
221
227
  """Generate detailed execution plan"""
222
228
  position_now = await self.memory.status.get("position")
223
229
  home_location = await self.memory.status.get("home")
224
230
  work_location = await self.memory.status.get("work")
225
231
  current_location = "Out"
226
- if 'aoi_position' in position_now and position_now['aoi_position'] == home_location['aoi_position']:
232
+ if (
233
+ "aoi_position" in position_now
234
+ and position_now["aoi_position"] == home_location["aoi_position"]
235
+ ):
227
236
  current_location = "At home"
228
- elif 'aoi_position' in position_now and position_now['aoi_position'] == work_location['aoi_position']:
237
+ elif (
238
+ "aoi_position" in position_now
239
+ and position_now["aoi_position"] == work_location["aoi_position"]
240
+ ):
229
241
  current_location = "At workplace"
230
242
  current_time = await self.simulator.get_time(format_time=True)
231
243
  environment = await self.memory.status.get("environment")
@@ -238,24 +250,24 @@ class PlanBlock(Block):
238
250
  environment=environment,
239
251
  emotion_types=await self.memory.status.get("emotion_types"),
240
252
  thought=await self.memory.status.get("thought"),
241
- max_plan_steps=self.max_plan_steps
253
+ max_plan_steps=self.max_plan_steps,
242
254
  )
243
255
 
244
- response = await self.llm.atext_request(
245
- self.detail_prompt.to_dialog()
246
- )
256
+ response = await self.llm.atext_request(self.detail_prompt.to_dialog())
247
257
 
248
258
  try:
249
- result = json.loads(self.clean_json_response(response)) # type: ignore
259
+ result = json.loads(self.clean_json_response(response)) # type: ignore
250
260
  return result
251
261
  except Exception as e:
252
262
  logger.warning(f"Error parsing detailed plan: {str(e)}")
253
- return None # type: ignore
263
+ return None # type: ignore
254
264
 
255
265
  async def forward(self):
256
266
  self.trigger_time += 1
257
- consumption_start = self.llm.prompt_tokens_used + self.llm.completion_tokens_used
258
-
267
+ consumption_start = (
268
+ self.llm.prompt_tokens_used + self.llm.completion_tokens_used
269
+ )
270
+
259
271
  # Step 1: Select guidance plan
260
272
  current_need = await self.memory.status.get("current_need")
261
273
  guidance_result = await self.select_guidance(current_need)
@@ -264,15 +276,16 @@ class PlanBlock(Block):
264
276
 
265
277
  # Step 2: Generate detailed plan
266
278
  detailed_plan = await self.generate_detailed_plan(
267
- current_need,
268
- guidance_result["selected_option"]
279
+ current_need, guidance_result["selected_option"]
269
280
  )
270
-
281
+
271
282
  if not detailed_plan or "plan" not in detailed_plan:
272
283
  await self.memory.status.update("current_plan", [])
273
- await self.memory.status.update("current_step", {"intention": "", "type": ""})
284
+ await self.memory.status.update(
285
+ "current_step", {"intention": "", "type": ""}
286
+ )
274
287
  return
275
-
288
+
276
289
  # Step 3: Update plan and current step
277
290
  steps = detailed_plan["plan"]["steps"]
278
291
  for step in steps:
@@ -284,22 +297,26 @@ class PlanBlock(Block):
284
297
  "completed": False,
285
298
  "failed": False,
286
299
  "stream_nodes": [],
287
- "guidance": guidance_result # Save the evaluation result of the plan selection
300
+ "guidance": guidance_result, # Save the evaluation result of the plan selection
288
301
  }
289
- formated_steps = "\n".join([f"{i}. {step['intention']}" for i, step in enumerate(plan['steps'], 1)])
302
+ formated_steps = "\n".join(
303
+ [f"{i}. {step['intention']}" for i, step in enumerate(plan["steps"], 1)]
304
+ )
290
305
  formated_plan = f"""
291
306
  Overall Target: {plan['target']}
292
307
  Execution Steps: \n{formated_steps}
293
308
  """
294
- plan['start_time'] = await self.simulator.get_time(format_time=True)
309
+ plan["start_time"] = await self.simulator.get_time(format_time=True)
295
310
  await self.memory.status.update("current_plan", plan)
296
- await self.memory.status.update("current_step", steps[0] if steps else {"intention": "", "type": ""})
297
- await self.memory.status.update("execution_context", {'plan': formated_plan})
311
+ await self.memory.status.update(
312
+ "current_step", steps[0] if steps else {"intention": "", "type": ""}
313
+ )
314
+ await self.memory.status.update("execution_context", {"plan": formated_plan})
298
315
 
299
316
  consumption_end = self.llm.prompt_tokens_used + self.llm.completion_tokens_used
300
317
  self.token_consumption += consumption_end - consumption_start
301
318
 
302
319
  def clean_json_response(self, response: str) -> str:
303
320
  """Clean special characters in LLM response"""
304
- response = response.replace('```json', '').replace('```', '')
305
- return response.strip()
321
+ response = response.replace("```json", "").replace("```", "")
322
+ return response.strip()