pycityagent 2.0.0a65__cp310-cp310-macosx_11_0_arm64.whl → 2.0.0a67__cp310-cp310-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.
- pycityagent/agent/agent.py +157 -57
- pycityagent/agent/agent_base.py +316 -43
- pycityagent/cityagent/bankagent.py +49 -9
- pycityagent/cityagent/blocks/__init__.py +1 -2
- pycityagent/cityagent/blocks/cognition_block.py +54 -31
- pycityagent/cityagent/blocks/dispatcher.py +22 -17
- pycityagent/cityagent/blocks/economy_block.py +46 -32
- pycityagent/cityagent/blocks/mobility_block.py +209 -105
- pycityagent/cityagent/blocks/needs_block.py +101 -54
- pycityagent/cityagent/blocks/other_block.py +42 -33
- pycityagent/cityagent/blocks/plan_block.py +59 -42
- pycityagent/cityagent/blocks/social_block.py +167 -126
- pycityagent/cityagent/blocks/utils.py +13 -6
- pycityagent/cityagent/firmagent.py +17 -35
- pycityagent/cityagent/governmentagent.py +3 -3
- pycityagent/cityagent/initial.py +79 -49
- pycityagent/cityagent/memory_config.py +123 -94
- pycityagent/cityagent/message_intercept.py +0 -4
- pycityagent/cityagent/metrics.py +41 -0
- pycityagent/cityagent/nbsagent.py +24 -36
- pycityagent/cityagent/societyagent.py +9 -4
- pycityagent/cli/wrapper.py +2 -2
- pycityagent/economy/econ_client.py +407 -81
- pycityagent/environment/__init__.py +0 -3
- pycityagent/environment/sim/__init__.py +0 -3
- pycityagent/environment/sim/aoi_service.py +2 -2
- pycityagent/environment/sim/client.py +3 -31
- pycityagent/environment/sim/clock_service.py +2 -2
- pycityagent/environment/sim/lane_service.py +8 -8
- pycityagent/environment/sim/light_service.py +8 -8
- pycityagent/environment/sim/pause_service.py +9 -10
- pycityagent/environment/sim/person_service.py +20 -20
- pycityagent/environment/sim/road_service.py +2 -2
- pycityagent/environment/sim/sim_env.py +21 -5
- pycityagent/environment/sim/social_service.py +4 -4
- pycityagent/environment/simulator.py +249 -27
- pycityagent/environment/utils/__init__.py +2 -2
- pycityagent/environment/utils/geojson.py +2 -2
- pycityagent/environment/utils/grpc.py +4 -4
- pycityagent/environment/utils/map_utils.py +2 -2
- pycityagent/llm/embeddings.py +147 -28
- pycityagent/llm/llm.py +178 -111
- pycityagent/llm/llmconfig.py +5 -0
- pycityagent/llm/utils.py +4 -0
- pycityagent/memory/__init__.py +0 -4
- pycityagent/memory/const.py +2 -2
- pycityagent/memory/faiss_query.py +140 -61
- pycityagent/memory/memory.py +394 -91
- pycityagent/memory/memory_base.py +140 -34
- pycityagent/memory/profile.py +13 -13
- pycityagent/memory/self_define.py +13 -13
- pycityagent/memory/state.py +14 -14
- pycityagent/message/message_interceptor.py +253 -3
- pycityagent/message/messager.py +133 -6
- pycityagent/metrics/mlflow_client.py +47 -4
- pycityagent/pycityagent-sim +0 -0
- pycityagent/pycityagent-ui +0 -0
- pycityagent/simulation/__init__.py +3 -2
- pycityagent/simulation/agentgroup.py +150 -54
- pycityagent/simulation/simulation.py +276 -66
- pycityagent/survey/manager.py +45 -3
- pycityagent/survey/models.py +42 -2
- pycityagent/tools/__init__.py +1 -2
- pycityagent/tools/tool.py +93 -69
- pycityagent/utils/avro_schema.py +2 -2
- pycityagent/utils/parsers/code_block_parser.py +1 -1
- pycityagent/utils/parsers/json_parser.py +2 -2
- pycityagent/utils/parsers/parser_base.py +2 -2
- pycityagent/workflow/block.py +64 -13
- pycityagent/workflow/prompt.py +31 -23
- pycityagent/workflow/trigger.py +91 -24
- {pycityagent-2.0.0a65.dist-info → pycityagent-2.0.0a67.dist-info}/METADATA +2 -2
- pycityagent-2.0.0a67.dist-info/RECORD +97 -0
- pycityagent/environment/interact/__init__.py +0 -0
- pycityagent/environment/interact/interact.py +0 -198
- pycityagent/environment/message/__init__.py +0 -0
- pycityagent/environment/sence/__init__.py +0 -0
- pycityagent/environment/sence/static.py +0 -416
- pycityagent/environment/sidecar/__init__.py +0 -8
- pycityagent/environment/sidecar/sidecarv2.py +0 -109
- pycityagent/environment/sim/economy_services.py +0 -192
- pycityagent/metrics/utils/const.py +0 -0
- pycityagent-2.0.0a65.dist-info/RECORD +0 -105
- {pycityagent-2.0.0a65.dist-info → pycityagent-2.0.0a67.dist-info}/LICENSE +0 -0
- {pycityagent-2.0.0a65.dist-info → pycityagent-2.0.0a67.dist-info}/WHEEL +0 -0
- {pycityagent-2.0.0a65.dist-info → pycityagent-2.0.0a67.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
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 =
|
120
|
-
|
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
|
-
|
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(
|
149
|
-
|
150
|
-
|
151
|
-
await self.memory.status.update(
|
152
|
-
|
153
|
-
|
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(
|
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 (
|
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(
|
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 [
|
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 [
|
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 [
|
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 [
|
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(
|
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
|
272
|
-
eva_ = step[
|
315
|
+
if "evaluation" in step["evaluation"]:
|
316
|
+
eva_ = step["evaluation"]["evaluation"]
|
273
317
|
else:
|
274
|
-
eva_ =
|
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))
|
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 [
|
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(
|
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(
|
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
|
-
|
10
|
-
import
|
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[
|
22
|
-
intention=step[
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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[
|
58
|
-
intention=step[
|
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(
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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 =
|
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)
|
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
|
-
|
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": [
|
175
|
-
"tired": [
|
176
|
-
"safe": [
|
177
|
-
"social": [
|
178
|
-
"whatever": [
|
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
|
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
|
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
|
-
)
|
215
|
+
) # type: ignore
|
212
216
|
|
213
217
|
try:
|
214
|
-
result = json.loads(self.clean_json_response(response))
|
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
|
222
|
+
return None # type: ignore
|
219
223
|
|
220
|
-
async def generate_detailed_plan(
|
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
|
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
|
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))
|
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
|
263
|
+
return None # type: ignore
|
254
264
|
|
255
265
|
async def forward(self):
|
256
266
|
self.trigger_time += 1
|
257
|
-
consumption_start =
|
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(
|
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(
|
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[
|
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(
|
297
|
-
|
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(
|
305
|
-
return response.strip()
|
321
|
+
response = response.replace("```json", "").replace("```", "")
|
322
|
+
return response.strip()
|