pycityagent 2.0.0a65__cp39-cp39-macosx_11_0_arm64.whl → 2.0.0a67__cp39-cp39-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 +209 -105
  9. pycityagent/cityagent/blocks/needs_block.py +101 -54
  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 -126
  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 -49
  17. pycityagent/cityagent/memory_config.py +123 -94
  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 +9 -4
  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 +178 -111
  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 +394 -91
  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 +150 -54
  60. pycityagent/simulation/simulation.py +276 -66
  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.0a65.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.0a65.dist-info/RECORD +0 -105
  84. {pycityagent-2.0.0a65.dist-info → pycityagent-2.0.0a67.dist-info}/LICENSE +0 -0
  85. {pycityagent-2.0.0a65.dist-info → pycityagent-2.0.0a67.dist-info}/WHEEL +0 -0
  86. {pycityagent-2.0.0a65.dist-info → pycityagent-2.0.0a67.dist-info}/entry_points.txt +0 -0
  87. {pycityagent-2.0.0a65.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.
@@ -17,9 +19,6 @@ Profile Information:
17
19
  - Age: {age}
18
20
  - Monthly Income: {income}
19
21
 
20
- Current Emotion: {emotion_types}
21
- Current Thought: {thought}
22
-
23
22
  Current Time: {now_time}
24
23
 
25
24
  Please initialize the agent's satisfaction levels and parameters based on the profile above. Return the values in JSON format with the following structure:
@@ -77,9 +76,6 @@ Current satisfaction:
77
76
  - safety_satisfaction: {safety_satisfaction}
78
77
  - social_satisfaction: {social_satisfaction}
79
78
 
80
- Current Emotion: {emotion_types}
81
- Current Thought: {thought}
82
-
83
79
  Please evaluate and adjust the value of {current_need} satisfaction based on the execution results above.
84
80
 
85
81
  Notes:
@@ -102,10 +98,12 @@ Example response format for whatever need adjustment (Do not return any other te
102
98
  }}
103
99
  """
104
100
 
101
+
105
102
  class NeedsBlock(Block):
106
103
  """
107
104
  Generate needs
108
105
  """
106
+
109
107
  def __init__(self, llm: LLM, memory: Memory, simulator: Simulator):
110
108
  super().__init__("NeedsBlock", llm=llm, memory=memory, simulator=simulator)
111
109
  self.evaluation_prompt = FormatPrompt(EVALUATION_PROMPT)
@@ -116,15 +114,29 @@ class NeedsBlock(Block):
116
114
  self.trigger_time = 0
117
115
  self.token_consumption = 0
118
116
  self.initialized = False
119
- 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
120
- 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
121
129
 
122
130
  async def initialize(self):
123
131
  day = await self.simulator.get_simulator_day()
124
132
  if day != self.now_day:
125
133
  self.now_day = day
126
- self.need_work = True
127
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
128
140
 
129
141
  if not self.initialized:
130
142
  self.initial_prompt.format(
@@ -134,23 +146,32 @@ class NeedsBlock(Block):
134
146
  occupation=await self.memory.status.get("occupation"),
135
147
  age=await self.memory.status.get("age"),
136
148
  income=await self.memory.status.get("income"),
137
- emotion_types=await self.memory.status.get("emotion_types"),
138
- thought=await self.memory.status.get("thought"),
139
- now_time=await self.simulator.get_time(format_time=True)
140
- )
141
- response = await self.llm.atext_request(
142
- self.initial_prompt.to_dialog()
149
+ now_time=await self.simulator.get_time(format_time=True),
143
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())
144
153
  response = self.clean_json_response(response)
145
154
  try:
146
155
  satisfaction = json.loads(response)
147
156
  satisfactions = satisfaction["current_satisfaction"]
148
- await self.memory.status.update("hunger_satisfaction", satisfactions["hunger_satisfaction"])
149
- await self.memory.status.update("energy_satisfaction", satisfactions["energy_satisfaction"])
150
- await self.memory.status.update("safety_satisfaction", satisfactions["safety_satisfaction"])
151
- await self.memory.status.update("social_satisfaction", satisfactions["social_satisfaction"])
152
- self.alpha_H, self.alpha_D, self.alpha_P, self.alpha_C = satisfaction["decay_rates"].values()
153
- 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()
154
175
  except json.JSONDecodeError:
155
176
  logger.warning(f"初始化响应不是有效的JSON格式: {response}")
156
177
 
@@ -159,7 +180,9 @@ class NeedsBlock(Block):
159
180
  history.append(current_plan)
160
181
  await self.memory.status.update("plan_history", history)
161
182
  await self.memory.status.update("current_plan", None)
162
- await self.memory.status.update("current_step", {"intention": "", "type": ""})
183
+ await self.memory.status.update(
184
+ "current_step", {"intention": "", "type": ""}
185
+ )
163
186
  await self.memory.status.update("execution_context", {})
164
187
  self.initialized = True
165
188
 
@@ -170,7 +193,7 @@ class NeedsBlock(Block):
170
193
  self.last_evaluation_time = time_now
171
194
  time_diff = 0
172
195
  else:
173
- time_diff = (time_now - self.last_evaluation_time)/3600
196
+ time_diff = (time_now - self.last_evaluation_time) / 3600
174
197
  self.last_evaluation_time = time_now
175
198
 
176
199
  # 获取当前需求的满意度
@@ -178,17 +201,17 @@ class NeedsBlock(Block):
178
201
  energy_satisfaction = await self.memory.status.get("energy_satisfaction")
179
202
  safety_satisfaction = await self.memory.status.get("safety_satisfaction")
180
203
  social_satisfaction = await self.memory.status.get("social_satisfaction")
181
-
204
+
182
205
  # 根据经过的时间计算饥饿与疲劳的衰减
183
206
  hungry_decay = self.alpha_H * time_diff
184
207
  energy_decay = self.alpha_D * time_diff
185
208
  safety_decay = self.alpha_P * time_diff
186
209
  social_decay = self.alpha_C * time_diff
187
- hunger_satisfaction = max(0, hunger_satisfaction - hungry_decay)
210
+ hunger_satisfaction = max(0, hunger_satisfaction - hungry_decay)
188
211
  energy_satisfaction = max(0, energy_satisfaction - energy_decay)
189
212
  safety_satisfaction = max(0, safety_satisfaction - safety_decay)
190
213
  social_satisfaction = max(0, social_satisfaction - social_decay)
191
-
214
+
192
215
  # 更新满意度
193
216
  await self.memory.status.update("hunger_satisfaction", hunger_satisfaction)
194
217
  await self.memory.status.update("energy_satisfaction", energy_satisfaction)
@@ -198,7 +221,9 @@ class NeedsBlock(Block):
198
221
  async def update_when_plan_completed(self):
199
222
  # 判断当前是否有正在执行的plan
200
223
  current_plan = await self.memory.status.get("current_plan")
201
- 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
+ ):
202
227
  # 评估计划执行过程并调整需求
203
228
  await self.evaluate_and_adjust_needs(current_plan)
204
229
  # 将完成的计划添加到历史记录
@@ -206,7 +231,9 @@ class NeedsBlock(Block):
206
231
  history.append(current_plan)
207
232
  await self.memory.status.update("plan_history", history)
208
233
  await self.memory.status.update("current_plan", None)
209
- await self.memory.status.update("current_step", {"intention": "", "type": ""})
234
+ await self.memory.status.update(
235
+ "current_step", {"intention": "", "type": ""}
236
+ )
210
237
  await self.memory.status.update("execution_context", {})
211
238
 
212
239
  async def determine_current_need(self):
@@ -219,14 +246,14 @@ class NeedsBlock(Block):
219
246
  # 调整方案为,如果当前的需求为空,或有更高级的需求出现,则调整需求
220
247
  current_plan = await self.memory.status.get("current_plan")
221
248
  current_need = await self.memory.status.get("current_need")
222
-
249
+
223
250
  # 当前没有计划或计划已执行完毕,获取所有需求值,按优先级检查各需求是否达到阈值
224
251
  if not current_plan or current_plan.get("completed"):
225
252
  # 按优先级顺序检查需求
226
253
  if hunger_satisfaction <= self.T_H:
227
254
  await self.memory.status.update("current_need", "hungry")
228
255
  elif energy_satisfaction <= self.T_D:
229
- await self.memory.status.update("current_need", "tired")
256
+ await self.memory.status.update("current_need", "tired")
230
257
  elif safety_satisfaction <= self.T_P:
231
258
  await self.memory.status.update("current_need", "safe")
232
259
  elif social_satisfaction <= self.T_C:
@@ -240,19 +267,34 @@ class NeedsBlock(Block):
240
267
  # 有正在执行的计划时,只在出现更高优先级需求时调整
241
268
  needs_changed = False
242
269
  new_need = None
243
- 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
+ ]:
244
274
  new_need = "hungry"
245
275
  needs_changed = True
246
- 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
+ ]:
247
280
  new_need = "tired"
248
281
  needs_changed = True
249
- 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
+ ]:
250
287
  new_need = "safe"
251
288
  needs_changed = True
252
- 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
+ ]:
253
295
  new_need = "social"
254
296
  needs_changed = True
255
-
297
+
256
298
  # 如果需求发生变化,中断当前计划
257
299
  if needs_changed:
258
300
  await self.evaluate_and_adjust_needs(current_plan)
@@ -261,17 +303,19 @@ class NeedsBlock(Block):
261
303
  await self.memory.status.update("current_need", new_need)
262
304
  await self.memory.status.update("plan_history", history)
263
305
  await self.memory.status.update("current_plan", None)
264
- await self.memory.status.update("current_step", {"intention": "", "type": ""})
306
+ await self.memory.status.update(
307
+ "current_step", {"intention": "", "type": ""}
308
+ )
265
309
  await self.memory.status.update("execution_context", {})
266
310
 
267
311
  async def evaluate_and_adjust_needs(self, completed_plan):
268
312
  # 获取执行的计划和评估结果
269
313
  evaluation_results = []
270
314
  for step in completed_plan["steps"]:
271
- if 'evaluation' in step['evaluation']:
272
- eva_ = step['evaluation']['evaluation']
315
+ if "evaluation" in step["evaluation"]:
316
+ eva_ = step["evaluation"]["evaluation"]
273
317
  else:
274
- eva_ = 'Plan failed, not completed'
318
+ eva_ = "Plan failed, not completed"
275
319
  evaluation_results.append(f"- {step['intention']} ({step['type']}): {eva_}")
276
320
  evaluation_results = "\n".join(evaluation_results)
277
321
 
@@ -285,30 +329,33 @@ class NeedsBlock(Block):
285
329
  energy_satisfaction=await self.memory.status.get("energy_satisfaction"),
286
330
  safety_satisfaction=await self.memory.status.get("safety_satisfaction"),
287
331
  social_satisfaction=await self.memory.status.get("social_satisfaction"),
288
- emotion_types=await self.memory.status.get("emotion_types"),
289
- thought=await self.memory.status.get("thought")
290
332
  )
291
333
 
292
- response = await self.llm.atext_request(
293
- self.evaluation_prompt.to_dialog()
294
- )
334
+ response = await self.llm.atext_request(self.evaluation_prompt.to_dialog())
295
335
 
296
- try:
297
- 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
298
338
  # 更新所有需求的数值
299
339
  for need_type, new_value in new_satisfaction.items():
300
- 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
+ ]:
301
346
  await self.memory.status.update(need_type, new_value)
302
347
  except json.JSONDecodeError:
303
- 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
+ )
304
351
  except Exception as e:
305
352
  logger.warning(f"Error processing evaluation response: {str(e)}")
306
353
  logger.warning(f"Original response: {response}")
307
354
 
308
355
  def clean_json_response(self, response: str) -> str:
309
356
  """清理LLM响应中的特殊字符"""
310
- response = response.replace('```json', '').replace('```', '')
311
- return response.strip()
357
+ response = response.replace("```json", "").replace("```", "")
358
+ return response.strip()
312
359
 
313
360
  async def forward(self):
314
361
  await self.initialize()
@@ -318,6 +365,6 @@ class NeedsBlock(Block):
318
365
 
319
366
  # update when plan completed
320
367
  await self.update_when_plan_completed()
321
-
368
+
322
369
  # determine current need
323
- 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()