pycityagent 2.0.0a88__cp312-cp312-macosx_11_0_arm64.whl → 2.0.0a90__cp312-cp312-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,7 +46,7 @@ Current temperature: {temperature}
46
46
  Your current emotion: {emotion_types}
47
47
  Your current thought: {thought}
48
48
 
49
- Please analyze how these emotions would affect travel willingness and return only a single integer number between 3000-100000 representing the maximum travel radius in meters. A more positive emotional state generally leads to greater willingness to travel further.
49
+ Please analyze how these emotions would affect travel willingness and return only a single integer number between 3000-200000 representing the maximum travel radius in meters. A more positive emotional state generally leads to greater willingness to travel further.
50
50
 
51
51
  Return only the integer number without any additional text or explanation."""
52
52
 
@@ -121,7 +121,7 @@ class PlaceSelectionBlock(Block):
121
121
  """
122
122
 
123
123
  configurable_fields: List[str] = ["search_limit"]
124
- default_values = {"search_limit": 10000}
124
+ default_values = {"search_limit": 1000}
125
125
 
126
126
  def __init__(self, llm: LLM, memory: Memory, simulator: Simulator):
127
127
  super().__init__(
@@ -134,7 +134,7 @@ class PlaceSelectionBlock(Block):
134
134
  )
135
135
  self.radiusPrompt = FormatPrompt(RADIUS_PROMPT)
136
136
  # configurable fields
137
- self.search_limit = 100
137
+ self.search_limit = 1000
138
138
 
139
139
  async def forward(self, step, context):
140
140
  poi_cate = self.simulator.get_poi_cate()
@@ -333,26 +333,30 @@ class NeedsBlock(Block):
333
333
  social_satisfaction=await self.memory.status.get("social_satisfaction"),
334
334
  )
335
335
 
336
- response = await self.llm.atext_request(self.evaluation_prompt.to_dialog())
337
-
338
- try:
339
- new_satisfaction = json.loads(self.clean_json_response(response)) # type: ignore
340
- # 更新所有需求的数值
341
- for need_type, new_value in new_satisfaction.items():
342
- if need_type in [
343
- "hunger_satisfaction",
344
- "energy_satisfaction",
345
- "safety_satisfaction",
346
- "social_satisfaction",
347
- ]:
348
- await self.memory.status.update(need_type, new_value)
349
- except json.JSONDecodeError:
350
- logger.warning(
351
- f"Evaluation response is not a valid JSON format: {response}"
352
- )
353
- except Exception as e:
354
- logger.warning(f"Error processing evaluation response: {str(e)}")
355
- logger.warning(f"Original response: {response}")
336
+ retry = 3
337
+ while retry > 0:
338
+ response = await self.llm.atext_request(self.evaluation_prompt.to_dialog())
339
+ try:
340
+ new_satisfaction = json.loads(self.clean_json_response(response)) # type: ignore
341
+ # 更新所有需求的数值
342
+ for need_type, new_value in new_satisfaction.items():
343
+ if need_type in [
344
+ "hunger_satisfaction",
345
+ "energy_satisfaction",
346
+ "safety_satisfaction",
347
+ "social_satisfaction",
348
+ ]:
349
+ await self.memory.status.update(need_type, new_value)
350
+ return
351
+ except json.JSONDecodeError:
352
+ logger.warning(
353
+ f"Evaluation response is not a valid JSON format: {response}"
354
+ )
355
+ retry -= 1
356
+ except Exception as e:
357
+ logger.warning(f"Error processing evaluation response: {str(e)}")
358
+ logger.warning(f"Original response: {response}")
359
+ retry -= 1
356
360
 
357
361
  def clean_json_response(self, response: str) -> str:
358
362
  """清理LLM响应中的特殊字符"""
@@ -213,13 +213,19 @@ class PlanBlock(Block):
213
213
  response = await self.llm.atext_request(
214
214
  self.guidance_prompt.to_dialog()
215
215
  ) # type: ignore
216
-
217
- try:
218
- result = json.loads(self.clean_json_response(response)) # type: ignore
219
- return result
220
- except Exception as e:
221
- logger.warning(f"Error parsing guidance selection response: {str(e)}")
222
- return None # type: ignore
216
+ retry = 3
217
+ while retry > 0:
218
+ try:
219
+ result = json.loads(self.clean_json_response(response)) # type: ignore
220
+ if "selected_option" not in result or "evaluation" not in result:
221
+ raise ValueError("Invalid guidance selection format")
222
+ if "attitude" not in result["evaluation"] or "subjective_norm" not in result["evaluation"] or "perceived_control" not in result["evaluation"] or "reasoning" not in result["evaluation"]:
223
+ raise ValueError("Evaluation must include attitude, subjective_norm, perceived_control, and reasoning")
224
+ return result
225
+ except Exception as e:
226
+ logger.warning(f"Error parsing guidance selection response: {str(e)}")
227
+ retry -= 1
228
+ return None
223
229
 
224
230
  async def generate_detailed_plan(
225
231
  self, current_need: str, selected_option: str
@@ -252,20 +258,22 @@ class PlanBlock(Block):
252
258
  )
253
259
 
254
260
  response = await self.llm.atext_request(self.detail_prompt.to_dialog())
255
-
256
- try:
257
- result = json.loads(self.clean_json_response(response)) # type: ignore
258
- return result
259
- except Exception as e:
260
- logger.warning(f"Error parsing detailed plan: {str(e)}")
261
- return None # type: ignore
261
+ retry = 3
262
+ while retry > 0:
263
+ try:
264
+ result = json.loads(self.clean_json_response(response)) # type: ignore
265
+ if "plan" not in result or "target" not in result["plan"] or "steps" not in result["plan"]:
266
+ raise ValueError("Invalid plan format")
267
+ for step in result["plan"]["steps"]:
268
+ if "intention" not in step or "type" not in step:
269
+ raise ValueError("Each step must have an intention and a type")
270
+ return result
271
+ except Exception as e:
272
+ logger.warning(f"Error parsing detailed plan: {str(e)}")
273
+ retry -= 1
274
+ return None
262
275
 
263
276
  async def forward(self):
264
- self.trigger_time += 1
265
- consumption_start = (
266
- self.llm.prompt_tokens_used + self.llm.completion_tokens_used
267
- )
268
-
269
277
  # Step 1: Select guidance plan
270
278
  current_need = await self.memory.status.get("current_need")
271
279
  guidance_result = await self.select_guidance(current_need)
@@ -278,7 +286,7 @@ class PlanBlock(Block):
278
286
  )
279
287
 
280
288
  if not detailed_plan or "plan" not in detailed_plan:
281
- await self.memory.status.update("current_plan", [])
289
+ await self.memory.status.update("current_plan", None)
282
290
  await self.memory.status.update(
283
291
  "current_step", {"intention": "", "type": ""}
284
292
  )
@@ -311,9 +319,6 @@ Execution Steps: \n{formated_steps}
311
319
  )
312
320
  await self.memory.status.update("execution_context", {"plan": formated_plan})
313
321
 
314
- consumption_end = self.llm.prompt_tokens_used + self.llm.completion_tokens_used
315
- self.token_consumption += consumption_end - consumption_start
316
-
317
322
  def clean_json_response(self, response: str) -> str:
318
323
  """Clean special characters in LLM response"""
319
324
  response = response.replace("```json", "").replace("```", "")
@@ -142,6 +142,7 @@ async def bind_agent_info(simulation):
142
142
  for bank_uuid in bank_uuids:
143
143
  await simulation.economy_update(uid2agent[bank_uuid], "citizens", citizen_agent_ids)
144
144
  for nbs_uuid in nbs_uuids:
145
+ await simulation.update(nbs_uuid, "citizens", citizen_uuids)
145
146
  await simulation.economy_update(uid2agent[nbs_uuid], "citizens", citizen_agent_ids)
146
147
  logger.info("Agent info binding completed!")
147
148
 
@@ -33,11 +33,11 @@ async def economy_metric(simulation):
33
33
  economy_metric.nbs_uuid = nbs_uuids[0]
34
34
 
35
35
  try:
36
- real_gdp = await simulation.economy_client.get(nbs_id, 'real_gdp')
36
+ real_gdp = await simulation.economy_client.get(economy_metric.nbs_id, 'real_gdp')
37
37
  except:
38
38
  real_gdp = []
39
39
  if len(real_gdp) > 0:
40
- real_gdp = real_gdp[0]
40
+ real_gdp = real_gdp[-1]
41
41
  forward_times_info = await simulation.gather("forward_times", [economy_metric.nbs_uuid])
42
42
  step_count = 0
43
43
  for group_gather in forward_times_info:
@@ -48,5 +48,5 @@ async def economy_metric(simulation):
48
48
  other_metrics = ['prices', 'working_hours', 'depression', 'consumption_currency', 'income_currency']
49
49
  other_metrics_names = ['price', 'working_hours', 'depression', 'consumption', 'income']
50
50
  for metric, metric_name in zip(other_metrics, other_metrics_names):
51
- metric_value = (await simulation.economy_client.get(nbs_id, metric))[-1]
51
+ metric_value = (await simulation.economy_client.get(economy_metric.nbs_id, metric))[-1]
52
52
  await simulation.mlflow_client.log_metric(key=metric_name, value=metric_value, step=step_count)
@@ -73,6 +73,8 @@ class PlanAndActionBlock(Block):
73
73
  async def step_execution(self):
74
74
  """Execute the current step"""
75
75
  current_plan = await self.memory.status.get("current_plan")
76
+ if current_plan is None:
77
+ return
76
78
  execution_context = await self.memory.status.get("execution_context")
77
79
  current_step = await self.memory.status.get("current_step")
78
80
  # check current_step is valid (not empty)
@@ -13,7 +13,9 @@ from ..utils import encode_to_base64, find_free_port
13
13
  __all__ = ["ControlSimEnv"]
14
14
 
15
15
 
16
- def _generate_yaml_config(map_file: str, max_day: int, start_step: int, total_step: int) -> str:
16
+ def _generate_yaml_config(
17
+ map_file: str, max_day: int, start_step: int, total_step: int
18
+ ) -> str:
17
19
  map_file = os.path.abspath(map_file)
18
20
  return f"""
19
21
  input:
@@ -48,6 +50,7 @@ class ControlSimEnv:
48
50
  start_step: int,
49
51
  total_step: int,
50
52
  log_dir: str,
53
+ primary_node_ip: str,
51
54
  min_step_time: int = 1000,
52
55
  timeout: int = 5,
53
56
  max_process: int = 32,
@@ -68,9 +71,12 @@ class ControlSimEnv:
68
71
  self._log_dir = log_dir
69
72
  self._min_step_time = min_step_time
70
73
  self._timeout = timeout
74
+ self._primary_node_ip = primary_node_ip
71
75
  self._max_procs = max_process
72
76
 
73
- self._sim_config = _generate_yaml_config(map_file, max_day, start_step, total_step)
77
+ self._sim_config = _generate_yaml_config(
78
+ map_file, max_day, start_step, total_step
79
+ )
74
80
  # sim
75
81
  self.sim_port = None
76
82
  self._sim_proc = None
@@ -102,6 +108,7 @@ class ControlSimEnv:
102
108
  self.sim_port = find_free_port()
103
109
  config_base64 = encode_to_base64(self._sim_config)
104
110
  os.environ["GOMAXPROCS"] = str(self._max_procs)
111
+ sim_addr = self._primary_node_ip.rstrip("/") + f":{self.sim_port}"
105
112
  self._sim_proc = Popen(
106
113
  [
107
114
  "pycityagent-sim",
@@ -110,7 +117,7 @@ class ControlSimEnv:
110
117
  "-job",
111
118
  self._task_name,
112
119
  "-listen",
113
- f":{self.sim_port}",
120
+ sim_addr,
114
121
  "-run.min_step_time",
115
122
  f"{self._min_step_time}",
116
123
  "-run.pause_after_one_day",
@@ -125,9 +132,8 @@ class ControlSimEnv:
125
132
  # stdout=DEVNULL,
126
133
  )
127
134
  logging.info(
128
- f"start pycityagent-sim at localhost:{self.sim_port}, PID={self._sim_proc.pid}"
135
+ f"start pycityagent-sim at {sim_addr}, PID={self._sim_proc.pid}"
129
136
  )
130
- sim_addr = f"http://localhost:{self.sim_port}"
131
137
  atexit.register(self.close)
132
138
  time.sleep(0.3)
133
139
  else:
@@ -126,10 +126,10 @@ class Simulator:
126
126
  ),
127
127
  log_dir=config["simulator"].get("log_dir", "./log"),
128
128
  min_step_time=config["simulator"].get("min_step_time", 1000),
129
+ primary_node_ip=config["simulator"].get("ad", "http://localhost"),
129
130
  sim_addr=config["simulator"].get("server", None),
130
131
  )
131
- primary_node_ip = config["simulator"].get("ad", "http://localhost")
132
- self.server_addr = primary_node_ip.rstrip("/")+f":{sim_env.sim_port}"
132
+ self.server_addr = sim_env.sim_addr
133
133
  config["simulator"]["server"] = self.server_addr
134
134
  config["simulator"]["_server_activated"] = True
135
135
  # using local client
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pycityagent
3
- Version: 2.0.0a88
3
+ Version: 2.0.0a90
4
4
  Summary: LLM-based city environment agent building library
5
5
  Author-email: Yuwei Yan <pinkgranite86@gmail.com>, Junbo Yan <yanjb20thu@gmali.com>, Jun Zhang <zhangjun990222@gmali.com>
6
6
  License: MIT License
@@ -1,9 +1,3 @@
1
- pycityagent-2.0.0a88.dist-info/RECORD,,
2
- pycityagent-2.0.0a88.dist-info/LICENSE,sha256=n2HPXiupinpyHMnIkbCf3OTYd3KMqbmldu1e7av0CAU,1084
3
- pycityagent-2.0.0a88.dist-info/WHEEL,sha256=VujM3ypTCyUW6hcTDdK2ej0ARVMxlU1Djlh_zWnDgqk,109
4
- pycityagent-2.0.0a88.dist-info/entry_points.txt,sha256=BZcne49AAIFv-hawxGnPbblea7X3MtAtoPyDX8L4OC4,132
5
- pycityagent-2.0.0a88.dist-info/top_level.txt,sha256=yOmeu6cSXmiUtScu53a3s0p7BGtLMaV0aff83EHCTic,43
6
- pycityagent-2.0.0a88.dist-info/METADATA,sha256=RX-Ot6Yyl_W0-bl9cJur9nQVLlAoI3WQ2YKiz37qTwk,9110
7
1
  pycityagent/pycityagent-sim,sha256=Ax6cjHjT8VcT5t07VboL_Ruor2eXSdSEml4Jvv-L5dQ,36972594
8
2
  pycityagent/__init__.py,sha256=PUKWTXc-xdMG7px8oTNclodsILUgypANj2Z647sY63k,808
9
3
  pycityagent/pycityagent-ui,sha256=Ur95yZygIaZ5l_CDqP9394M5GQ66iV5PkcNPYFWqzvk,41225346
@@ -52,7 +46,7 @@ pycityagent/workflow/prompt.py,sha256=rzenP4EFGxbWE1aq-x2036b6umKvi5cQx2xtWULwgI
52
46
  pycityagent/workflow/block.py,sha256=WJfCeL8e117GzkVPJCRNsQZZinccMnVyEubkwrf-17U,12295
53
47
  pycityagent/workflow/trigger.py,sha256=4nzAwywGQsFabgo6gzp-vD2EV4wII7Z0LHGEAsflSEY,7608
54
48
  pycityagent/environment/__init__.py,sha256=fFIth2jxxgZ92cXm-aoM2igHgaSqsYGwtBhyb7opjzk,166
55
- pycityagent/environment/simulator.py,sha256=ubOsoeyo81LZKfOSeS4eGrpn-7lC-hrdCBbcLH9NbK8,23324
49
+ pycityagent/environment/simulator.py,sha256=evctZcl9SrRDj1-1wzuEgRwhW9iGjzXmbdlA-1dhwhg,23293
56
50
  pycityagent/environment/utils/port.py,sha256=3OM6kSUt3PxvDUOlgyiendBtETaWU8Mzk_8H0TzTmYg,295
57
51
  pycityagent/environment/utils/grpc.py,sha256=_lB4-k4dTKuNvApaDiYgFxiLTPtYG42DVQtG9yOj9pQ,2022
58
52
  pycityagent/environment/utils/base64.py,sha256=hoREzQo3FXMN79pqQLO2jgsDEvudciomyKii7MWljAM,374
@@ -64,7 +58,7 @@ pycityagent/environment/utils/const.py,sha256=1LqxnYJ8FSmq37fN5kIFlWLwycEDzFa8SF
64
58
  pycityagent/environment/sim/pause_service.py,sha256=UUTa4pTBOX_MGKki9QDgKId7scch0waAWF9xRVmNlAs,1701
65
59
  pycityagent/environment/sim/person_service.py,sha256=T1pRi5jDcivSInX7iGZoHKYZmgO7_t5MYtLhU12_LC4,10793
66
60
  pycityagent/environment/sim/aoi_service.py,sha256=bYVJDrOfDn8hgOORDU7PxMaT-fpqXA5Z0A080l2EPBc,1242
67
- pycityagent/environment/sim/sim_env.py,sha256=4CW9lv9CyRUFsa0mwZ3AQsBps3i1Qh1KIRSgHdZWy0U,4658
61
+ pycityagent/environment/sim/sim_env.py,sha256=iXdM2fQo-StbB55WcRDlY4Y4MbZ7ApZp7fAv58Cg9Rk,4758
68
62
  pycityagent/environment/sim/lane_service.py,sha256=bmf0zOWkxDzMEDlhTdHNRJRiTFGgzjbeqmex387gHZs,3964
69
63
  pycityagent/environment/sim/client.py,sha256=x4REWPRFG_C2s0-CWz8MnYalD_esfu2pPHf_vpsCG78,2852
70
64
  pycityagent/environment/sim/__init__.py,sha256=CyJc5Tey9MgzomZv0ynAcV0yaUlSE3bPbegYXar8wJc,601
@@ -72,26 +66,32 @@ pycityagent/environment/sim/social_service.py,sha256=Y4A56aKXsjSv18UFumGPjQoJVMc
72
66
  pycityagent/environment/sim/light_service.py,sha256=q4pKcGrm7WU0h29I1dFIDOz2OV0BM-2s37uC6zokkoA,4290
73
67
  pycityagent/environment/sim/clock_service.py,sha256=4Hly8CToghj0x_XbDgGrIZy1YYAlDH0EUGCiCDYpk_I,1375
74
68
  pycityagent/environment/sim/road_service.py,sha256=Bb1sreO0Knt9tcqH_WJF-I3P3G92bRAlzDBEa--25GE,1297
75
- pycityagent/cityagent/metrics.py,sha256=SdCxEuO-wgsPGo3H6xywnOAfP5Lre-bc7HNAQ-n53mA,2687
69
+ pycityagent/cityagent/metrics.py,sha256=wVxLpk4jvVoPf2baiK-jMIi2rPPMRnFpSjOVyj1k74A,2718
76
70
  pycityagent/cityagent/memory_config.py,sha256=lCySjh8jpE3Sj-_XTCPizxJN8rjfcYD54sed7wi2EDw,11958
77
71
  pycityagent/cityagent/bankagent.py,sha256=I6MNG1fUbxKyWCLxCtvOjeVahong_LhHQKmJdRPhQL8,4097
78
72
  pycityagent/cityagent/__init__.py,sha256=gcBQ-a50XegFtjigQ7xDXRBZrywBKqifiQFSRnEF8gM,572
79
73
  pycityagent/cityagent/firmagent.py,sha256=vZr7kdjnxcCZ5qX7hmUIYuQQWd44GcRPbdi76WGSFy4,3760
80
74
  pycityagent/cityagent/nbsagent.py,sha256=rrzL-Ep-gWB8kvoPA8nBokY1zoEZGpyQjP9oy7ZuVL4,4676
81
- pycityagent/cityagent/initial.py,sha256=0DSOWnVVknMw6xoJWfdZUFBH3yti8y800wnkXXCocxY,6194
82
- pycityagent/cityagent/societyagent.py,sha256=1c6k3RgxQ7AAYCiBXwiSZLDkCDMgCrBzNF6vi6lK2Yc,20069
75
+ pycityagent/cityagent/initial.py,sha256=tVcO9ECGvsFatpxQirZbZf0ESFBxSpSxkm4p0x2kiO0,6263
76
+ pycityagent/cityagent/societyagent.py,sha256=i7V7BeCb_pMJpxwiR5AOUcNHRw0SXHXltt_DtCxE6pE,20121
83
77
  pycityagent/cityagent/message_intercept.py,sha256=dyT1G-nMxKb2prhgtyFFHFz593qBrkk5DnHsHvG1OIc,4418
84
78
  pycityagent/cityagent/governmentagent.py,sha256=XIyggG83FWUTZdOuoqc6ClCP3hhfkxNmtYRu9TFo0dU,3063
85
79
  pycityagent/cityagent/blocks/dispatcher.py,sha256=U5BPeUrjrTyDaznYfT6YUJIW8vfKVRDF4EO0oOn6Td4,2886
86
- pycityagent/cityagent/blocks/needs_block.py,sha256=NYKrGDoYCuXoupMNMuSNhx4Ci1paC_EuGRWvp5-ZSA8,15909
80
+ pycityagent/cityagent/blocks/needs_block.py,sha256=eFWDgsqjh4FYaGeb8wL5R9ndaXN3977ymexoItDUki8,16112
87
81
  pycityagent/cityagent/blocks/cognition_block.py,sha256=yzjB0D_95vytpa5xiVdmTSpGp8H9HXcjWzzFN0OpP0k,15398
88
82
  pycityagent/cityagent/blocks/social_block.py,sha256=eedOlwRTGI47QFELYmfe2a_aj0GuHJweSyDxA6AYXcU,15493
89
83
  pycityagent/cityagent/blocks/__init__.py,sha256=h6si6WBcVVuglIskKQKA8Cxtf_VKen1sNPqOFKI311Q,420
90
84
  pycityagent/cityagent/blocks/economy_block.py,sha256=l47x2Iq15Rjj9WsH3p398dtpcejat4aCkC3X160xBlE,19843
91
85
  pycityagent/cityagent/blocks/utils.py,sha256=K--6odjUDUu9YrwrHPaiPIHryo7m_MBmcBqDAy3cV5M,1816
92
86
  pycityagent/cityagent/blocks/other_block.py,sha256=LdtL6248xvMvvRQx6NvdlJrWWZFu8Xusjxb9yEh1M0k,4365
93
- pycityagent/cityagent/blocks/plan_block.py,sha256=A5DvtXIy98MZkRGUQmp26grNI5i0BVbl3aEM_Ebd6Z4,11271
94
- pycityagent/cityagent/blocks/mobility_block.py,sha256=qkRiV0nkGOUUoo9lGIjAFE_Hl0kiyMev12-Zm7JyV7o,15089
87
+ pycityagent/cityagent/blocks/plan_block.py,sha256=AFyjj-RimYNkZ8x6sU-ZMWwB1jqJxSvr1j0vEKmhG4w,11967
88
+ pycityagent/cityagent/blocks/mobility_block.py,sha256=OZoA-swFdX0Ezpzkyp8jJMNPQ533JpeeTCdLupoqRjI,15089
95
89
  pycityagent/survey/models.py,sha256=g3xni4GcA1Py3vlGt6z4ltutjgQ4G0uINYAM8vKRJAw,5225
96
90
  pycityagent/survey/__init__.py,sha256=rxwou8U9KeFSP7rMzXtmtp2fVFZxK4Trzi-psx9LPIs,153
97
91
  pycityagent/survey/manager.py,sha256=tHkdeq4lTfAHwvgf4-udsXri0z2l6E00rEbvwl7SqRs,3439
92
+ pycityagent-2.0.0a90.dist-info/RECORD,,
93
+ pycityagent-2.0.0a90.dist-info/LICENSE,sha256=n2HPXiupinpyHMnIkbCf3OTYd3KMqbmldu1e7av0CAU,1084
94
+ pycityagent-2.0.0a90.dist-info/WHEEL,sha256=VujM3ypTCyUW6hcTDdK2ej0ARVMxlU1Djlh_zWnDgqk,109
95
+ pycityagent-2.0.0a90.dist-info/entry_points.txt,sha256=BZcne49AAIFv-hawxGnPbblea7X3MtAtoPyDX8L4OC4,132
96
+ pycityagent-2.0.0a90.dist-info/top_level.txt,sha256=yOmeu6cSXmiUtScu53a3s0p7BGtLMaV0aff83EHCTic,43
97
+ pycityagent-2.0.0a90.dist-info/METADATA,sha256=I5U7O4c3mvMLk0rbjGwgrfStY2sjJhK0n6gF1_5nu7g,9110