pycityagent 2.0.0a52__cp311-cp311-macosx_11_0_arm64.whl → 2.0.0a54__cp311-cp311-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 (49) hide show
  1. pycityagent/agent/agent.py +83 -62
  2. pycityagent/agent/agent_base.py +81 -54
  3. pycityagent/cityagent/bankagent.py +5 -7
  4. pycityagent/cityagent/blocks/__init__.py +0 -2
  5. pycityagent/cityagent/blocks/cognition_block.py +149 -172
  6. pycityagent/cityagent/blocks/economy_block.py +90 -129
  7. pycityagent/cityagent/blocks/mobility_block.py +56 -29
  8. pycityagent/cityagent/blocks/needs_block.py +163 -145
  9. pycityagent/cityagent/blocks/other_block.py +17 -9
  10. pycityagent/cityagent/blocks/plan_block.py +45 -57
  11. pycityagent/cityagent/blocks/social_block.py +70 -51
  12. pycityagent/cityagent/blocks/utils.py +2 -0
  13. pycityagent/cityagent/firmagent.py +6 -7
  14. pycityagent/cityagent/governmentagent.py +7 -9
  15. pycityagent/cityagent/memory_config.py +48 -48
  16. pycityagent/cityagent/message_intercept.py +99 -0
  17. pycityagent/cityagent/nbsagent.py +6 -29
  18. pycityagent/cityagent/societyagent.py +325 -127
  19. pycityagent/cli/wrapper.py +4 -0
  20. pycityagent/economy/econ_client.py +0 -2
  21. pycityagent/environment/__init__.py +7 -1
  22. pycityagent/environment/sim/client.py +10 -1
  23. pycityagent/environment/sim/clock_service.py +2 -2
  24. pycityagent/environment/sim/pause_service.py +61 -0
  25. pycityagent/environment/sim/sim_env.py +34 -46
  26. pycityagent/environment/simulator.py +18 -14
  27. pycityagent/llm/embeddings.py +0 -24
  28. pycityagent/llm/llm.py +18 -10
  29. pycityagent/memory/faiss_query.py +29 -26
  30. pycityagent/memory/memory.py +733 -247
  31. pycityagent/message/__init__.py +8 -1
  32. pycityagent/message/message_interceptor.py +322 -0
  33. pycityagent/message/messager.py +42 -11
  34. pycityagent/pycityagent-sim +0 -0
  35. pycityagent/simulation/agentgroup.py +137 -96
  36. pycityagent/simulation/simulation.py +184 -38
  37. pycityagent/simulation/storage/pg.py +2 -2
  38. pycityagent/tools/tool.py +7 -9
  39. pycityagent/utils/__init__.py +7 -2
  40. pycityagent/utils/pg_query.py +1 -0
  41. pycityagent/utils/survey_util.py +26 -23
  42. pycityagent/workflow/block.py +14 -7
  43. {pycityagent-2.0.0a52.dist-info → pycityagent-2.0.0a54.dist-info}/METADATA +2 -2
  44. {pycityagent-2.0.0a52.dist-info → pycityagent-2.0.0a54.dist-info}/RECORD +48 -46
  45. pycityagent/cityagent/blocks/time_block.py +0 -116
  46. {pycityagent-2.0.0a52.dist-info → pycityagent-2.0.0a54.dist-info}/LICENSE +0 -0
  47. {pycityagent-2.0.0a52.dist-info → pycityagent-2.0.0a54.dist-info}/WHEEL +0 -0
  48. {pycityagent-2.0.0a52.dist-info → pycityagent-2.0.0a54.dist-info}/entry_points.txt +0 -0
  49. {pycityagent-2.0.0a52.dist-info → pycityagent-2.0.0a54.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,7 @@ from .road_service import RoadService
10
10
  from .social_service import SocialService
11
11
  from .economy_services import EconomyPersonService, EconomyOrgService
12
12
  from .light_service import LightService
13
-
13
+ from .pause_service import PauseService
14
14
  from ..utils.grpc import create_aio_channel
15
15
 
16
16
  __all__ = ["CityClient"]
@@ -44,6 +44,7 @@ class CityClient:
44
44
  self._economy_person_service = EconomyPersonService(aio_channel)
45
45
  self._economy_org_service = EconomyOrgService(aio_channel)
46
46
  self._light_service = LightService(aio_channel)
47
+ self._pause_service = PauseService(aio_channel)
47
48
 
48
49
  @staticmethod
49
50
  def from_sidecar(sidecar: OnlyClientSidecar, name: str = NAME):
@@ -61,6 +62,14 @@ class CityClient:
61
62
  """
62
63
  return self._clock_service
63
64
 
65
+ @property
66
+ def pause_service(self):
67
+ """
68
+ 模拟器暂停服务子模块
69
+ Simulator pause service submodule
70
+ """
71
+ return self._pause_service
72
+
64
73
  @property
65
74
  def lane_service(self):
66
75
  """
@@ -30,10 +30,10 @@ class ClockService:
30
30
  Getting current simulation clock
31
31
 
32
32
  Args:
33
- - req (dict): https://cityproto.sim.fiblab.net/#city.clock.v1.NowRequest
33
+ - req (dict): https://cityproto.readthedocs.io/en/latest/docs.html#nowrequest
34
34
 
35
35
  Returns:
36
- - https://cityproto.sim.fiblab.net/#city.clock.v1.NowResponse
36
+ - https://cityproto.readthedocs.io/en/latest/docs.html#nowresponse
37
37
  """
38
38
  if type(req) != clock_service.NowRequest:
39
39
  req = ParseDict(req, clock_service.NowRequest())
@@ -0,0 +1,61 @@
1
+ from collections.abc import Awaitable, Coroutine
2
+ from typing import Any, Dict, Union, cast
3
+
4
+ import grpc
5
+ from google.protobuf.json_format import ParseDict
6
+ from pycityproto.city.pause.v1 import pause_service_pb2 as pause_service
7
+ from pycityproto.city.pause.v1 import pause_service_pb2_grpc as pause_grpc
8
+
9
+ from ..utils.protobuf import async_parse
10
+
11
+ __all__ = ["PauseService"]
12
+
13
+
14
+ class PauseService:
15
+ """
16
+ 城市模拟暂停服务
17
+ City simulation pause service
18
+ """
19
+
20
+ def __init__(self, aio_channel: grpc.aio.Channel):
21
+ self._aio_stub = pause_grpc.PauseServiceStub(aio_channel)
22
+
23
+ async def pause(
24
+ self,
25
+ ) -> Coroutine[Any, Any, Union[Dict[str, Any], pause_service.PauseResponse]]:
26
+ """
27
+ 暂停模拟
28
+ Pause the simulation
29
+
30
+ Args:
31
+ - req (dict): https://cityproto.readthedocs.io/en/latest/docs.html#pauserequest
32
+
33
+ Returns:
34
+ - https://cityproto.readthedocs.io/en/latest/docs.html#pauseresponse
35
+ """
36
+ req = pause_service.PauseRequest()
37
+ res = cast(
38
+ Awaitable[pause_service.PauseResponse],
39
+ self._aio_stub.Pause(req),
40
+ )
41
+ return
42
+
43
+ async def resume(
44
+ self,
45
+ ) -> Coroutine[Any, Any, Union[Dict[str, Any], pause_service.ResumeResponse]]:
46
+ """
47
+ 恢复模拟
48
+ Resume the simulation
49
+
50
+ Args:
51
+ - req (dict): https://cityproto.readthedocs.io/en/latest/docs.html#resumerequest
52
+
53
+ Returns:
54
+ - https://cityproto.readthedocs.io/en/latest/docs.html#resumeresponse
55
+ """
56
+ req = pause_service.ResumeRequest()
57
+ res = cast(
58
+ Awaitable[pause_service.ResumeResponse],
59
+ self._aio_stub.Resume(req),
60
+ )
61
+ return
@@ -1,7 +1,8 @@
1
+ import atexit
1
2
  import logging
2
3
  import os
3
4
  import time
4
- import atexit
5
+ import warnings
5
6
  from subprocess import DEVNULL, Popen
6
7
  from typing import Optional
7
8
 
@@ -51,7 +52,7 @@ class ControlSimEnv:
51
52
  log_dir: str,
52
53
  min_step_time: int = 1000,
53
54
  timeout: int = 5,
54
- simuletgo_addr: Optional[str] = None,
55
+ sim_addr: Optional[str] = None,
55
56
  ):
56
57
  self._task_name = task_name
57
58
  self._map_file = map_file
@@ -66,47 +67,38 @@ class ControlSimEnv:
66
67
  地图数据
67
68
  """
68
69
 
69
- # 检查二进制文件是否存在
70
- # simulet-go: ~/.local/bin/simulet-go
71
- self._simuletgo_path = os.path.expanduser("~/.local/bin/simulet-go")
72
- if not os.path.exists(os.path.expanduser(self._simuletgo_path)):
73
- raise FileNotFoundError("simulet-go not found, please install it first")
74
-
75
- self._simuletgo_config = _generate_yaml_config(map_file, start_step, total_step)
76
- self.simuletgo_port = None
77
- self._simuletgo_proc = None
78
- self._traffic_client = None
79
-
70
+ self._sim_config = _generate_yaml_config(map_file, start_step, total_step)
71
+ # sim
72
+ self.sim_port = None
73
+ self._sim_proc = None
80
74
  os.makedirs(log_dir, exist_ok=True)
81
75
 
82
- self.simuletgo_addr = self.reset(simuletgo_addr)
76
+ self.sim_addr = self.reset(sim_addr)
83
77
 
84
78
  def reset(
85
79
  self,
86
- simuletgo_addr: Optional[str] = None,
80
+ sim_addr: Optional[str] = None,
87
81
  ):
88
82
  """
89
83
  Args:
90
- - simuletgo_addr: str, simulet-go的地址。如果为None,则启动一个新的simulet-go
84
+ - sim_addr: str, pycityagent-sim的地址。如果为None,则启动一个新的pycityagent-sim
91
85
  """
92
-
93
- # 三个地址必须同时为None或者同时不为None
94
- if simuletgo_addr is None:
95
- # 1. 启动simulet-go
96
- # ./simulet-go -config-data configbase64 -job test -syncer http://localhost:53001 -listen :51102
97
- assert self.simuletgo_port is None
98
- assert self._simuletgo_proc is None
99
- self.simuletgo_port = find_free_port()
100
- config_base64 = encode_to_base64(self._simuletgo_config)
101
- self._simuletgo_proc = Popen(
86
+ if sim_addr is None:
87
+ # 启动pycityagent-sim
88
+ # pycityagent-sim -config-data configbase64 -job test -listen :51102
89
+ assert self.sim_port is None
90
+ assert self._sim_proc is None
91
+ self.sim_port = find_free_port()
92
+ config_base64 = encode_to_base64(self._sim_config)
93
+ self._sim_proc = Popen(
102
94
  [
103
- self._simuletgo_path,
95
+ "pycityagent-sim",
104
96
  "-config-data",
105
97
  config_base64,
106
98
  "-job",
107
99
  self._task_name,
108
100
  "-listen",
109
- f":{self.simuletgo_port}",
101
+ f":{self.sim_port}",
110
102
  "-run.min_step_time",
111
103
  f"{self._min_step_time}",
112
104
  "-output",
@@ -120,26 +112,22 @@ class ControlSimEnv:
120
112
  # stdout=DEVNULL,
121
113
  )
122
114
  logging.info(
123
- f"start simulet-go at localhost:{self.simuletgo_port}, PID={self._simuletgo_proc.pid}"
115
+ f"start pycityagent-sim at localhost:{self.sim_port}, PID={self._sim_proc.pid}"
124
116
  )
125
- simuletgo_addr = f"http://localhost:{self.simuletgo_port}"
117
+ sim_addr = f"http://localhost:{self.sim_port}"
126
118
  atexit.register(self.close)
127
- time.sleep(1)
128
- elif simuletgo_addr is not None:
129
- pass
119
+ time.sleep(0.3)
130
120
  else:
131
- # raise ValueError(
132
- # "simuletgo_addr, syncer_addr, routing_addr must be all None or all not None"
133
- # )
134
- pass
135
- return simuletgo_addr
121
+ warnings.warn("单独启动模拟器模拟将被弃用", DeprecationWarning)
136
122
 
137
- def close(self):
138
- if self._simuletgo_proc is not None:
139
- self._simuletgo_proc.terminate()
140
- simuletgo_code = self._simuletgo_proc.wait()
141
- logging.info(f"simulet-go exit with code {simuletgo_code}")
123
+ return sim_addr
142
124
 
143
- self.simuletgo_port = None
144
- self._simuletgo_proc = None
145
- self._traffic_client = None
125
+ def close(self):
126
+ if self._sim_proc is not None:
127
+ self._sim_proc.terminate()
128
+ sim_code = self._sim_proc.wait()
129
+ logging.info(f"pycityagent-sim exit with code {sim_code}")
130
+
131
+ # sim
132
+ self.sim_port = None
133
+ self._sim_proc = None
@@ -60,11 +60,11 @@ class Simulator:
60
60
  total_step=2147000000,
61
61
  log_dir=config["simulator"].get("log_dir", "./log"),
62
62
  min_step_time=config["simulator"].get("min_step_time", 1000),
63
- simuletgo_addr=config["simulator"].get("server", None),
63
+ sim_addr=config["simulator"].get("server", None),
64
64
  )
65
65
 
66
66
  # using local client
67
- self._client = CityClient(sim_env.simuletgo_addr, secure=False)
67
+ self._client = CityClient(sim_env.sim_addr, secure=False)
68
68
  """
69
69
  - 模拟器grpc客户端
70
70
  - grpc client of simulator
@@ -162,35 +162,39 @@ class Simulator:
162
162
  Returns:
163
163
  - time Union[int, str]: 时间 time in second(int) or formatted time(str)
164
164
  """
165
- t_sec = await self._client.clock_service.Now({})
166
- t_sec = cast(dict[str, int], t_sec)
167
- self.time = t_sec["t"]
165
+ now = await self._client.clock_service.Now({})
166
+ now = cast(dict[str, int], now)
167
+ self.time = now["t"]
168
168
  if format_time:
169
169
  current_date = datetime.now().date()
170
170
  start_of_day = datetime.combine(current_date, datetime.min.time())
171
- current_time = start_of_day + timedelta(seconds=t_sec["t"])
171
+ current_time = start_of_day + timedelta(seconds=now["t"])
172
172
  formatted_time = current_time.strftime(format)
173
173
  return formatted_time
174
174
  else:
175
- # BUG: 返回的time是float类型
176
- return t_sec["t"]
175
+ return int(now["t"])
176
+ async def pause(self):
177
+ await self._client.pause_service.pause()
178
+
179
+ async def resume(self):
180
+ await self._client.pause_service.resume()
177
181
 
178
182
  async def get_simulator_day(self) -> int:
179
183
  """
180
184
  获取模拟器到第几日
181
185
  """
182
- t_sec = await self._client.clock_service.Now({})
183
- t_sec = cast(dict[str, int], t_sec)
184
- day = t_sec["t"] // 86400
186
+ now = await self._client.clock_service.Now({})
187
+ now = cast(dict[str, int], now)
188
+ day = now["day"]
185
189
  return day
186
190
 
187
191
  async def get_simulator_second_from_start_of_day(self) -> int:
188
192
  """
189
193
  获取模拟器从00:00:00到当前的秒数
190
194
  """
191
- t_sec = await self._client.clock_service.Now({})
192
- t_sec = cast(dict[str, int], t_sec)
193
- return t_sec["t"] % 86400
195
+ now = await self._client.clock_service.Now({})
196
+ now = cast(dict[str, int], now)
197
+ return now["t"] % 86400
194
198
 
195
199
  async def get_person(self, person_id: int) -> dict:
196
200
  return await self._client.person_service.GetPerson(
@@ -196,30 +196,6 @@ class SimpleEmbedding(Embeddings):
196
196
  """Embed query text."""
197
197
  return self._embed(text)
198
198
 
199
- # def save(self, file_path: str):
200
- # """保存模型"""
201
- # state = {
202
- # "vector_dim": self.vector_dim,
203
- # "cache_size": self.cache_size,
204
- # "vocab": self._vocab,
205
- # "idf": self._idf,
206
- # "doc_count": self._doc_count,
207
- # }
208
- # with open(file_path, "w") as f:
209
- # json.dump(state, f)
210
-
211
- # def load(self, file_path: str):
212
- # """加载模型"""
213
- # with open(file_path, "r") as f:
214
- # state = json.load(f)
215
- # self.vector_dim = state["vector_dim"]
216
- # self.cache_size = state["cache_size"]
217
- # self._vocab = state["vocab"]
218
- # self._idf = state["idf"]
219
- # self._doc_count = state["doc_count"]
220
- # self._cache = {} # 清空缓存
221
-
222
-
223
199
  if __name__ == "__main__":
224
200
  # se = SentenceEmbedding(
225
201
  # pretrained_model_name_or_path="ignore/BAAI--bge-m3", cache_dir="ignore"
pycityagent/llm/llm.py CHANGED
@@ -1,25 +1,27 @@
1
1
  """UrbanLLM: 智能能力类及其定义"""
2
2
 
3
3
  import json
4
- from openai import OpenAI, AsyncOpenAI, APIConnectionError, OpenAIError
5
- from zhipuai import ZhipuAI
6
4
  import logging
7
5
 
6
+ from openai import APIConnectionError, AsyncOpenAI, OpenAI, OpenAIError
7
+ from zhipuai import ZhipuAI
8
+
8
9
  logging.getLogger("zhipuai").setLevel(logging.WARNING)
9
10
 
10
11
  import asyncio
12
+ import os
11
13
  from http import HTTPStatus
14
+ from io import BytesIO
15
+ from typing import Any, Optional, Union
16
+
12
17
  import dashscope
13
18
  import requests
14
19
  from dashscope import ImageSynthesis
15
20
  from PIL import Image
16
- from io import BytesIO
17
- from typing import Any, Optional, Union
21
+
18
22
  from .llmconfig import *
19
23
  from .utils import *
20
24
 
21
- import os
22
-
23
25
  os.environ["GRPC_VERBOSITY"] = "ERROR"
24
26
 
25
27
 
@@ -38,13 +40,13 @@ class LLM:
38
40
  self.request_number = 0
39
41
  self.semaphore = None
40
42
  self._current_client_index = 0
41
-
43
+
42
44
  api_keys = self.config.text["api_key"]
43
45
  if not isinstance(api_keys, list):
44
46
  api_keys = [api_keys]
45
-
47
+
46
48
  self._aclients = []
47
-
49
+
48
50
  for api_key in api_keys:
49
51
  if self.config.text["request_type"] == "openai":
50
52
  client = AsyncOpenAI(api_key=api_key, timeout=300)
@@ -62,6 +64,10 @@ class LLM:
62
64
  )
63
65
  elif self.config.text["request_type"] == "zhipuai":
64
66
  client = ZhipuAI(api_key=api_key, timeout=300)
67
+ else:
68
+ raise ValueError(
69
+ f"Unsupported `request_type` {self.config.text['request_type']}!"
70
+ )
65
71
  self._aclients.append(client)
66
72
 
67
73
  def set_semaphore(self, number_of_coroutine: int):
@@ -118,7 +124,9 @@ Token Usage:
118
124
  def _get_next_client(self):
119
125
  """获取下一个要使用的客户端"""
120
126
  client = self._aclients[self._current_client_index]
121
- self._current_client_index = (self._current_client_index + 1) % len(self._aclients)
127
+ self._current_client_index = (self._current_client_index + 1) % len(
128
+ self._aclients
129
+ )
122
130
  return client
123
131
 
124
132
  async def atext_request(
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import warnings
2
3
  from collections.abc import Sequence
3
4
  from typing import Any, Literal, Optional, Union
4
5
 
@@ -116,32 +117,34 @@ class FaissQuery:
116
117
  }
117
118
  if filter is not None:
118
119
  _filter.update(filter)
119
- if return_score_type == "L2-distance":
120
- _result = await self.vectors_store.asimilarity_search_with_score(
121
- query=query,
122
- k=k,
123
- filter=_filter,
124
- fetch_k=fetch_k,
125
- )
126
- return [(r.page_content, s, r.metadata) for r, s in _result]
127
- elif return_score_type == "none":
128
- _result = await self.vectors_store.asimilarity_search(
129
- query=query,
130
- k=k,
131
- filter=_filter,
132
- fetch_k=fetch_k,
133
- )
134
- return [(r.page_content, r.metadata) for r in _result]
135
- elif return_score_type == "similarity_score":
136
- _result = await self.vectors_store.asimilarity_search_with_relevance_scores(
137
- query=query,
138
- k=k,
139
- filter=_filter,
140
- fetch_k=fetch_k,
141
- )
142
- return [(r.page_content, s, r.metadata) for r, s in _result]
143
- else:
144
- raise ValueError(f"Invalid `return_score_type` {return_score_type}!")
120
+ with warnings.catch_warnings():
121
+ warnings.simplefilter("ignore")
122
+ if return_score_type == "L2-distance":
123
+ _result = await self.vectors_store.asimilarity_search_with_score(
124
+ query=query,
125
+ k=k,
126
+ filter=_filter,
127
+ fetch_k=fetch_k,
128
+ )
129
+ return [(r.page_content, s, r.metadata) for r, s in _result]
130
+ elif return_score_type == "none":
131
+ _result = await self.vectors_store.asimilarity_search(
132
+ query=query,
133
+ k=k,
134
+ filter=_filter,
135
+ fetch_k=fetch_k,
136
+ )
137
+ return [(r.page_content, r.metadata) for r in _result]
138
+ elif return_score_type == "similarity_score":
139
+ _result = await self.vectors_store.asimilarity_search_with_relevance_scores(
140
+ query=query,
141
+ k=k,
142
+ filter=_filter,
143
+ fetch_k=fetch_k,
144
+ )
145
+ return [(r.page_content, s, r.metadata) for r, s in _result]
146
+ else:
147
+ raise ValueError(f"Invalid `return_score_type` {return_score_type}!")
145
148
 
146
149
  @lock_decorator
147
150
  async def similarity_search_by_embedding(