pycityagent 2.0.0a66__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.
- 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 +130 -100
- pycityagent/cityagent/blocks/needs_block.py +101 -44
- pycityagent/cityagent/blocks/other_block.py +42 -33
- pycityagent/cityagent/blocks/plan_block.py +59 -42
- pycityagent/cityagent/blocks/social_block.py +167 -116
- pycityagent/cityagent/blocks/utils.py +13 -6
- pycityagent/cityagent/firmagent.py +17 -35
- pycityagent/cityagent/governmentagent.py +3 -3
- pycityagent/cityagent/initial.py +79 -44
- pycityagent/cityagent/memory_config.py +108 -88
- pycityagent/cityagent/message_intercept.py +0 -4
- pycityagent/cityagent/metrics.py +41 -0
- pycityagent/cityagent/nbsagent.py +24 -36
- pycityagent/cityagent/societyagent.py +7 -3
- 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 +122 -77
- 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 +393 -90
- 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 +145 -52
- pycityagent/simulation/simulation.py +257 -62
- 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.0a66.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.0a66.dist-info/RECORD +0 -105
- {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/LICENSE +0 -0
- {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/WHEEL +0 -0
- {pycityagent-2.0.0a66.dist-info → pycityagent-2.0.0a67.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
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 =
|
114
|
-
|
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(
|
141
|
-
|
142
|
-
|
143
|
-
await self.memory.status.update(
|
144
|
-
|
145
|
-
|
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(
|
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 (
|
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(
|
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 [
|
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 [
|
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 [
|
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 [
|
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(
|
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
|
264
|
-
eva_ = step[
|
315
|
+
if "evaluation" in step["evaluation"]:
|
316
|
+
eva_ = step["evaluation"]["evaluation"]
|
265
317
|
else:
|
266
|
-
eva_ =
|
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))
|
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 [
|
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(
|
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(
|
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
|
-
|
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()
|