pycityagent 1.0.0__py3-none-any.whl → 2.0.0a1__py3-none-any.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 (106) hide show
  1. pycityagent/__init__.py +7 -3
  2. pycityagent/agent.py +180 -284
  3. pycityagent/economy/__init__.py +5 -0
  4. pycityagent/economy/econ_client.py +307 -0
  5. pycityagent/environment/__init__.py +7 -0
  6. pycityagent/environment/interact/interact.py +141 -0
  7. pycityagent/environment/sence/__init__.py +0 -0
  8. pycityagent/{brain → environment/sence}/static.py +1 -1
  9. pycityagent/environment/sidecar/__init__.py +8 -0
  10. pycityagent/environment/sidecar/sidecarv2.py +109 -0
  11. pycityagent/environment/sim/__init__.py +27 -0
  12. pycityagent/environment/sim/aoi_service.py +38 -0
  13. pycityagent/environment/sim/client.py +126 -0
  14. pycityagent/environment/sim/clock_service.py +43 -0
  15. pycityagent/environment/sim/economy_services.py +191 -0
  16. pycityagent/environment/sim/lane_service.py +110 -0
  17. pycityagent/environment/sim/light_service.py +120 -0
  18. pycityagent/environment/sim/person_service.py +294 -0
  19. pycityagent/environment/sim/road_service.py +38 -0
  20. pycityagent/environment/sim/social_service.py +58 -0
  21. pycityagent/environment/simulator.py +369 -0
  22. pycityagent/environment/utils/__init__.py +8 -0
  23. pycityagent/environment/utils/geojson.py +26 -0
  24. pycityagent/environment/utils/grpc.py +57 -0
  25. pycityagent/environment/utils/map_utils.py +157 -0
  26. pycityagent/environment/utils/protobuf.py +39 -0
  27. pycityagent/llm/__init__.py +6 -0
  28. pycityagent/llm/embedding.py +136 -0
  29. pycityagent/llm/llm.py +430 -0
  30. pycityagent/llm/llmconfig.py +15 -0
  31. pycityagent/llm/utils.py +6 -0
  32. pycityagent/memory/__init__.py +11 -0
  33. pycityagent/memory/const.py +41 -0
  34. pycityagent/memory/memory.py +453 -0
  35. pycityagent/memory/memory_base.py +168 -0
  36. pycityagent/memory/profile.py +165 -0
  37. pycityagent/memory/self_define.py +165 -0
  38. pycityagent/memory/state.py +173 -0
  39. pycityagent/memory/utils.py +27 -0
  40. pycityagent/message/__init__.py +0 -0
  41. pycityagent/simulation/__init__.py +7 -0
  42. pycityagent/simulation/interview.py +36 -0
  43. pycityagent/simulation/simulation.py +286 -0
  44. pycityagent/simulation/survey/__init__.py +9 -0
  45. pycityagent/simulation/survey/manager.py +67 -0
  46. pycityagent/simulation/survey/models.py +49 -0
  47. pycityagent/simulation/ui/__init__.py +3 -0
  48. pycityagent/simulation/ui/interface.py +602 -0
  49. pycityagent/utils/__init__.py +0 -0
  50. pycityagent/utils/decorators.py +89 -0
  51. pycityagent/utils/parsers/__init__.py +12 -0
  52. pycityagent/utils/parsers/code_block_parser.py +37 -0
  53. pycityagent/utils/parsers/json_parser.py +86 -0
  54. pycityagent/utils/parsers/parser_base.py +60 -0
  55. pycityagent/workflow/__init__.py +22 -0
  56. pycityagent/workflow/block.py +137 -0
  57. pycityagent/workflow/prompt.py +72 -0
  58. pycityagent/workflow/tool.py +246 -0
  59. pycityagent/workflow/trigger.py +66 -0
  60. pycityagent-2.0.0a1.dist-info/METADATA +208 -0
  61. pycityagent-2.0.0a1.dist-info/RECORD +65 -0
  62. {pycityagent-1.0.0.dist-info → pycityagent-2.0.0a1.dist-info}/WHEEL +1 -2
  63. pycityagent/ac/__init__.py +0 -6
  64. pycityagent/ac/ac.py +0 -50
  65. pycityagent/ac/action.py +0 -14
  66. pycityagent/ac/controled.py +0 -13
  67. pycityagent/ac/converse.py +0 -31
  68. pycityagent/ac/idle.py +0 -17
  69. pycityagent/ac/shop.py +0 -80
  70. pycityagent/ac/trip.py +0 -37
  71. pycityagent/brain/__init__.py +0 -10
  72. pycityagent/brain/brain.py +0 -52
  73. pycityagent/brain/brainfc.py +0 -10
  74. pycityagent/brain/memory.py +0 -541
  75. pycityagent/brain/persistence/social.py +0 -1
  76. pycityagent/brain/persistence/spatial.py +0 -14
  77. pycityagent/brain/reason/shop.py +0 -37
  78. pycityagent/brain/reason/social.py +0 -148
  79. pycityagent/brain/reason/trip.py +0 -67
  80. pycityagent/brain/reason/user.py +0 -122
  81. pycityagent/brain/retrive/social.py +0 -6
  82. pycityagent/brain/scheduler.py +0 -408
  83. pycityagent/brain/sence.py +0 -375
  84. pycityagent/cc/__init__.py +0 -5
  85. pycityagent/cc/cc.py +0 -102
  86. pycityagent/cc/conve.py +0 -6
  87. pycityagent/cc/idle.py +0 -20
  88. pycityagent/cc/shop.py +0 -6
  89. pycityagent/cc/trip.py +0 -13
  90. pycityagent/cc/user.py +0 -13
  91. pycityagent/hubconnector/__init__.py +0 -3
  92. pycityagent/hubconnector/hubconnector.py +0 -137
  93. pycityagent/image/__init__.py +0 -3
  94. pycityagent/image/image.py +0 -158
  95. pycityagent/simulator.py +0 -161
  96. pycityagent/st/__init__.py +0 -4
  97. pycityagent/st/st.py +0 -96
  98. pycityagent/urbanllm/__init__.py +0 -3
  99. pycityagent/urbanllm/urbanllm.py +0 -132
  100. pycityagent-1.0.0.dist-info/LICENSE +0 -21
  101. pycityagent-1.0.0.dist-info/METADATA +0 -181
  102. pycityagent-1.0.0.dist-info/RECORD +0 -48
  103. pycityagent-1.0.0.dist-info/top_level.txt +0 -1
  104. /pycityagent/{brain/persistence/__init__.py → config.py} +0 -0
  105. /pycityagent/{brain/reason → environment/interact}/__init__.py +0 -0
  106. /pycityagent/{brain/retrive → environment/message}/__init__.py +0 -0
@@ -0,0 +1,37 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ from .parser_base import ParserBase
5
+
6
+
7
+ class CodeBlockParser(ParserBase):
8
+ """A parser that extracts specific objects from a response string enclosed within specific tags.
9
+
10
+ Attributes:
11
+ tag_start (str): The start tag used to identify the beginning of the object.
12
+ tag_end (str): The end tag used to identify the end of the object.
13
+ """
14
+
15
+ tag_start = "```{language_name}"
16
+ tag_end = "```"
17
+
18
+ def __init__(self, language_name: str) -> None:
19
+ """Initialize the CodeBlockParser with default tags."""
20
+ super().__init__()
21
+ self.language_name = language_name
22
+
23
+ def parse(self, response: str) -> str:
24
+ """Parse the response string to extract and return a str.
25
+
26
+ Parameters:
27
+ response (str): The response string containing the specified language object.
28
+
29
+ Returns:
30
+ str: The parsed `str` object.
31
+ """
32
+ extract_text = self._extract_text_within_tags(
33
+ response=response,
34
+ tag_start=self.tag_start.format(language_name=self.language_name),
35
+ tag_end=self.tag_end,
36
+ )
37
+ return extract_text
@@ -0,0 +1,86 @@
1
+ import json
2
+ import logging
3
+ from typing import Any, Dict
4
+
5
+ from .parser_base import ParserBase
6
+
7
+
8
+ class JsonObjectParser(ParserBase):
9
+ """A parser that extracts and parses JSON objects from a response string enclosed within specific tags.
10
+
11
+ Attributes:
12
+ tag_start (str): The start tag used to identify the beginning of the JSON object.
13
+ tag_end (str): The end tag used to identify the end of the JSON object.
14
+ """
15
+
16
+ tag_start = "```json"
17
+ tag_end = "```"
18
+
19
+ def __init__(self) -> None:
20
+ """Initialize the JsonObjectParser with default tags."""
21
+ super().__init__()
22
+
23
+ def parse(self, response: str) -> Any:
24
+ """Parse the response string to extract and return a JSON object.
25
+
26
+ Parameters:
27
+ response (str): The response string containing the JSON object.
28
+
29
+ Returns:
30
+ Any: The parsed JSON object.
31
+ """
32
+ extract_text = self._extract_text_within_tags(
33
+ response=response,
34
+ tag_start=self.tag_start,
35
+ tag_end=self.tag_end,
36
+ )
37
+ try:
38
+ eval_parsed_json = eval(extract_text)
39
+ return eval_parsed_json
40
+ except Exception as e:
41
+ # logging.warning(f"Error when handling text {extract_text} with `eval`")
42
+ pass
43
+ try:
44
+ parsed_json = json.loads(extract_text)
45
+ return parsed_json
46
+ except json.decoder.JSONDecodeError as e:
47
+ raw_response = f"{self.tag_start}{extract_text}{self.tag_end}"
48
+ raise ValueError(
49
+ f"The content between {self.tag_start} and {self.tag_end} "
50
+ f"MUST be a JSON object."
51
+ f'When parsing "{raw_response}", an error occurred: {e}',
52
+ ) from None
53
+
54
+
55
+ class JsonDictParser(JsonObjectParser):
56
+ """A parser that extends JsonObjectParser to ensure the parsed JSON is returned as a dictionary.
57
+
58
+ Attributes:
59
+ tag_start (str): The start tag used to identify the beginning of the JSON object.
60
+ tag_end (str): The end tag used to identify the end of the JSON object.
61
+ """
62
+
63
+ tag_start = "```json"
64
+ tag_end = "```"
65
+
66
+ def __init__(self) -> None:
67
+ """Initialize the JsonDictParser with default tags."""
68
+ super().__init__()
69
+
70
+ def parse(self, response: str) -> Dict:
71
+ """Parse the response string to extract and return a JSON object as a dictionary.
72
+
73
+ Parameters:
74
+ response (str): The response string containing the JSON object.
75
+
76
+ Returns:
77
+ Dict: The parsed JSON object as a dictionary.
78
+ """
79
+ parsed_json = super().parse(response)
80
+ if not isinstance(parsed_json, dict):
81
+ # If not a dictionary, raise an error
82
+ raise ValueError(
83
+ "A JSON dictionary object is wanted, "
84
+ f"but got {type(parsed_json)} instead."
85
+ )
86
+ return dict(parsed_json)
@@ -0,0 +1,60 @@
1
+ """
2
+ Base class of parser
3
+ """
4
+
5
+ import re
6
+ from abc import ABC, abstractmethod
7
+ from typing import Any, Dict, List, Optional, Tuple, Union
8
+
9
+
10
+ class ParserBase(ABC):
11
+ def __init__(
12
+ self,
13
+ ) -> None:
14
+ pass
15
+
16
+ @abstractmethod
17
+ def parse(self, response: str) -> Any:
18
+ """
19
+ Parse the string response returned by the model and convert it into a more manageable form.
20
+
21
+ Parameters:
22
+ response (str): The raw string returned by the model.
23
+
24
+ Returns:
25
+ Any: The converted data, the specific type depends on the parsing result.
26
+ """
27
+ pass
28
+
29
+ def _extract_text_within_tags(
30
+ self, response: str, tag_start: str, tag_end: str
31
+ ) -> str:
32
+ """
33
+ Return the string between the first occurrence of the start tag and the end tag in the response string.
34
+
35
+ Parameters:
36
+ response (str): The response string.
37
+ tag_start (str): The start tag.
38
+ tag_end (str): The end tag.
39
+
40
+ Returns:
41
+ str: The string between the start and end tags.
42
+ """
43
+
44
+ def _extract_substring(
45
+ text: str, tag_start: str, tag_end: str
46
+ ) -> Union[str, None]:
47
+ pattern = re.escape(tag_start) + r"(.*?)" + re.escape(tag_end)
48
+ match = re.search(pattern, text, re.DOTALL)
49
+ if match:
50
+ return match.group(1).strip()
51
+ else:
52
+ return None
53
+
54
+ sub_response = _extract_substring(response, tag_start, tag_end)
55
+ if sub_response is not None:
56
+ return sub_response
57
+ else:
58
+ raise ValueError(
59
+ f"No text between tag {tag_start} and tag {tag_end} in response {response}!"
60
+ )
@@ -0,0 +1,22 @@
1
+ # __init__.py
2
+
3
+ """
4
+ This module contains classes for creating blocks and running workflows.
5
+ """
6
+
7
+ from .block import Block, log_and_check, log_and_check_with_memory
8
+ from .prompt import FormatPrompt
9
+ from .tool import GetMap, SencePOI, Tool
10
+ from .trigger import MemoryChangeTrigger, PortMessageTrigger
11
+
12
+ __all__ = [
13
+ "SencePOI",
14
+ "Tool",
15
+ "GetMap",
16
+ "MemoryChangeTrigger",
17
+ "PortMessageTrigger",
18
+ "Block",
19
+ "log_and_check",
20
+ "log_and_check_with_memory",
21
+ "FormatPrompt",
22
+ ]
@@ -0,0 +1,137 @@
1
+ import asyncio
2
+ import functools
3
+ import inspect
4
+ import time
5
+ from typing import Any, Callable, Coroutine, Optional, Union
6
+
7
+ from ..llm import LLM
8
+ from ..memory import Memory
9
+ from ..utils.decorators import record_call_aio
10
+
11
+ TRIGGER_INTERVAL = 0.1
12
+
13
+
14
+ def log_and_check_with_memory(
15
+ condition: Union[
16
+ Callable[[Memory], Coroutine[Any, Any, bool]],
17
+ Callable[[], Coroutine[Any, Any, bool]],
18
+ Callable[[Memory], bool],
19
+ Callable[[], bool],
20
+ ] = lambda: True,
21
+ trigger_interval: float = TRIGGER_INTERVAL,
22
+ record_function_calling: bool = False,
23
+ ):
24
+ """
25
+ A decorator that logs function calls and optionally checks a condition before executing the function.
26
+
27
+ This decorator is specifically designed to be used with the `block` method. A 'Memory' object is required in method input.
28
+
29
+ Args:
30
+ condition (Callable): A condition function that must be satisfied before the decorated function is executed.
31
+ Can be synchronous or asynchronous.
32
+ trigger_interval (float): The interval (in seconds) to wait between condition checks.
33
+ record_function_calling (bool): Whether to log the function call information.
34
+ """
35
+
36
+ def decorator(func):
37
+ @record_call_aio(record_function_calling)
38
+ @functools.wraps(func)
39
+ async def wrapper(self, *args, **kwargs):
40
+ memory = None
41
+ for arg in list(args) + list(kwargs.values()):
42
+ if memory is not None:
43
+ break
44
+ if isinstance(arg, Memory):
45
+ memory = arg
46
+ assert isinstance(memory, Memory), "Input arguments has no `Memory` object!"
47
+ # Wait until the condition is met
48
+ sig = inspect.signature(condition)
49
+ params = list(sig.parameters.values())
50
+ if len(params) == 1 and params[0].annotation is Memory:
51
+ if inspect.iscoroutinefunction(condition):
52
+ while not await condition(memory): # type:ignore
53
+ await asyncio.sleep(trigger_interval)
54
+ else:
55
+ while not condition(memory): # type:ignore
56
+ await asyncio.sleep(trigger_interval)
57
+ elif len(params) == 0:
58
+ if inspect.iscoroutinefunction(condition):
59
+ while not await condition(): # type:ignore
60
+ await asyncio.sleep(trigger_interval)
61
+ else:
62
+ while not condition(): # type:ignore
63
+ await asyncio.sleep(trigger_interval)
64
+ else:
65
+ raise RuntimeError(
66
+ f"Invalid parameter format in condition function {condition}"
67
+ )
68
+ result = await func(self, *args, **kwargs)
69
+ return result
70
+
71
+ return wrapper
72
+
73
+ return decorator
74
+
75
+
76
+ def log_and_check(
77
+ condition: Union[
78
+ Callable[[], Coroutine[Any, Any, bool]],
79
+ Callable[[], bool],
80
+ ] = lambda: True,
81
+ trigger_interval: float = TRIGGER_INTERVAL,
82
+ record_function_calling: bool = False,
83
+ ):
84
+ """
85
+ A decorator that logs function calls and optionally checks a condition before executing the function.
86
+
87
+ This decorator is specifically designed to be used with the `block` method.
88
+
89
+ Args:
90
+ condition (Callable): A condition function that must be satisfied before the decorated function is executed.
91
+ Can be synchronous or asynchronous.
92
+ trigger_interval (float): The interval (in seconds) to wait between condition checks.
93
+ record_function_calling (bool): Whether to log the function call information.
94
+ """
95
+
96
+ def decorator(func):
97
+ @record_call_aio(record_function_calling)
98
+ @functools.wraps(func)
99
+ async def wrapper(self, *args, **kwargs):
100
+ # Wait until the condition is met
101
+ sig = inspect.signature(condition)
102
+ params = list(sig.parameters.values())
103
+ if len(params) == 0:
104
+ if inspect.iscoroutinefunction(condition):
105
+ while not await condition(): # type:ignore
106
+ await asyncio.sleep(trigger_interval)
107
+ else:
108
+ while not condition(): # type:ignore
109
+ await asyncio.sleep(trigger_interval)
110
+ else:
111
+ raise RuntimeError(
112
+ f"Invalid parameter format in condition function {condition}"
113
+ )
114
+ result = await func(self, *args, **kwargs)
115
+ return result
116
+
117
+ return wrapper
118
+
119
+ return decorator
120
+
121
+
122
+ # Define a Block, similar to a layer in PyTorch
123
+ class Block:
124
+ def __init__(
125
+ self,
126
+ name: str,
127
+ llm: Optional[LLM] = None,
128
+ ):
129
+ self.name = name
130
+ self.llm = llm
131
+
132
+ async def reason(self, *args, **kwargs):
133
+ """
134
+ Each block performs a specific reasoning task.
135
+ To be overridden by specific block implementations.
136
+ """
137
+ raise NotImplementedError("Subclasses should implement this method")
@@ -0,0 +1,72 @@
1
+ from typing import Any, Callable, Dict, List, Optional, Union
2
+ import re
3
+
4
+ class FormatPrompt:
5
+ """
6
+ A class to handle the formatting of prompts based on a template,
7
+ with support for system prompts and variable extraction.
8
+
9
+ Attributes:
10
+ template (str): The template string containing placeholders.
11
+ system_prompt (Optional[str]): An optional system prompt to add to the dialog.
12
+ variables (List[str]): A list of variable names extracted from the template.
13
+ formatted_string (str): The formatted string derived from the template and provided variables.
14
+ """
15
+
16
+ def __init__(self, template: str, system_prompt: Optional[str] = None) -> None:
17
+ """
18
+ Initializes the FormatPrompt with a template and an optional system prompt.
19
+
20
+ Args:
21
+ template (str): The string template with variable placeholders.
22
+ system_prompt (Optional[str]): An optional system prompt.
23
+ """
24
+ self.template = template
25
+ self.system_prompt = system_prompt # Store the system prompt
26
+ self.variables = self._extract_variables()
27
+ self.formatted_string = '' # To store the formatted string
28
+
29
+ def _extract_variables(self) -> List[str]:
30
+ """
31
+ Extracts variable names from the template string.
32
+
33
+ Returns:
34
+ List[str]: A list of variable names found within the template.
35
+ """
36
+ return re.findall(r'\{(\w+)\}', self.template)
37
+
38
+ def format(self, **kwargs) -> str:
39
+ """
40
+ Formats the template string using the provided keyword arguments.
41
+
42
+ Args:
43
+ **kwargs: Variable names and their corresponding values to format the template.
44
+
45
+ Returns:
46
+ str: The formatted string.
47
+ """
48
+ self.formatted_string = self.template.format(**kwargs) # Store the formatted string
49
+ return self.formatted_string
50
+
51
+ def to_dialog(self) -> List[Dict[str, str]]:
52
+ """
53
+ Converts the formatted prompt and optional system prompt into a dialog format.
54
+
55
+ Returns:
56
+ List[Dict[str, str]]: A list representing the dialog with roles and content.
57
+ """
58
+ dialog = []
59
+ if self.system_prompt:
60
+ dialog.append({'role': 'system', 'content': self.system_prompt}) # Add system prompt if it exists
61
+ dialog.append({'role': 'user', 'content': self.formatted_string}) # Add user content
62
+ return dialog
63
+
64
+ def log(self) -> None:
65
+ """
66
+ Logs the details of the FormatPrompt, including the template,
67
+ system prompt, extracted variables, and formatted string.
68
+ """
69
+ print(f"FormatPrompt: {self.template}")
70
+ print(f"System Prompt: {self.system_prompt}") # Log the system prompt
71
+ print(f"Variables: {self.variables}")
72
+ print(f"Formatted String: {self.formatted_string}") # Log the formatted string
@@ -0,0 +1,246 @@
1
+ import asyncio
2
+ import logging
3
+ from copy import deepcopy
4
+ from typing import Any, Callable, Dict, List, Optional, Union
5
+
6
+ from mosstool.util.format_converter import dict2pb
7
+ from pycityproto.city.person.v2 import person_pb2 as person_pb2
8
+
9
+ from ..agent import Agent
10
+ from ..environment import (LEVEL_ONE_PRE, POI_TYPE_DICT, AoiService,
11
+ PersonService)
12
+ from ..workflow import Block
13
+
14
+
15
+ class Tool:
16
+ """Abstract tool class for callable tools. Can be bound to an `Agent` or `Block` instance.
17
+
18
+ This class serves as a base for creating various tools that can perform different operations.
19
+ It is intended to be subclassed by specific tool implementations.
20
+ """
21
+
22
+ def __get__(self, instance, owner):
23
+ if instance is None:
24
+ return self
25
+ subclass = type(self)
26
+ if not hasattr(instance, "_tools"):
27
+ instance._tools = {}
28
+ if subclass not in instance._tools:
29
+ tool_instance = subclass()
30
+ tool_instance._instance = instance # type: ignore
31
+ instance._tools[subclass] = tool_instance
32
+ return instance._tools[subclass]
33
+
34
+ def __call__(self, *args: Any, **kwds: Any) -> Any:
35
+ """Invoke the tool's functionality.
36
+
37
+ This method must be implemented by subclasses to provide specific behavior.
38
+ """
39
+ raise NotImplementedError
40
+
41
+ @property
42
+ def agent(self) -> Agent:
43
+ instance = self._instance # type:ignore
44
+ if not isinstance(instance, Agent):
45
+ raise RuntimeError(
46
+ f"Tool bind to object `{type(instance).__name__}`, not an `Agent` object!"
47
+ )
48
+ return instance
49
+
50
+ @property
51
+ def block(self) -> Block:
52
+ instance = self._instance # type:ignore
53
+ if not isinstance(instance, Block):
54
+ raise RuntimeError(
55
+ f"Tool bind to object `{type(instance).__name__}`, not an `Block` object!"
56
+ )
57
+ return instance
58
+
59
+
60
+ class GetMap(Tool):
61
+ """Retrieve the map from the simulator. Can be bound only to an `Agent` instance."""
62
+
63
+ def __init__(self) -> None:
64
+ self.variables = []
65
+
66
+ async def __call__(self) -> Union[Any, Callable]:
67
+ agent = self.agent
68
+ if agent.simulator is None:
69
+ raise ValueError("Simulator is not set.")
70
+ return agent.simulator.map
71
+
72
+
73
+ class SencePOI(Tool):
74
+ """Retrieve the Point of Interest (POI) of the current scene.
75
+
76
+ This tool computes the POI based on the current `position` stored in memory and returns
77
+ points of interest (POIs) within a specified radius. Can be bound only to an `Agent` instance.
78
+
79
+ Attributes:
80
+ radius (int): The radius within which to search for POIs.
81
+ category_prefix (str): The prefix for the categories of POIs to consider.
82
+ variables (List[str]): A list of variables relevant to the tool's operation.
83
+
84
+ Args:
85
+ radius (int, optional): The circular search radius. Defaults to 100.
86
+ category_prefix (str, optional): The category prefix to filter POIs. Defaults to LEVEL_ONE_PRE.
87
+
88
+ Methods:
89
+ __call__(radius: Optional[int] = None, category_prefix: Optional[str] = None) -> Union[Any, Callable]:
90
+ Executes the AOI retrieval operation, returning POIs based on the current state of memory and simulator.
91
+ """
92
+
93
+ def __init__(self, radius: int = 100, category_prefix=LEVEL_ONE_PRE) -> None:
94
+ self.radius = radius
95
+ self.category_prefix = category_prefix
96
+ self.variables = ["position"]
97
+
98
+ async def __call__(
99
+ self, radius: Optional[int] = None, category_prefix: Optional[str] = None
100
+ ) -> Union[Any, Callable]:
101
+ """Retrieve the POIs within the specified radius and category prefix.
102
+
103
+ If both `radius` and `category_prefix` are None, the method will use the current position
104
+ from memory to query POIs using the simulator. Otherwise, it will return a new instance
105
+ of SenceAoi with the specified parameters.
106
+
107
+ Args:
108
+ radius (Optional[int]): A specific radius for the AOI query. If not provided, defaults to the instance's radius.
109
+ category_prefix (Optional[str]): A specific category prefix to filter POIs. If not provided, defaults to the instance's category_prefix.
110
+
111
+ Raises:
112
+ ValueError: If memory or simulator is not set.
113
+
114
+ Returns:
115
+ Union[Any, Callable]: The query results or a callable for a new SenceAoi instance.
116
+ """
117
+ agent = self.agent
118
+ if agent.memory is None or agent.simulator is None:
119
+ raise ValueError("Memory or Simulator is not set.")
120
+ if radius is None and category_prefix is None:
121
+ position = await agent.memory.get("position")
122
+ resp = []
123
+ for prefix in self.category_prefix:
124
+ resp += agent.simulator.map.query_pois(
125
+ center=(position["xy_position"]["x"], position["xy_position"]["y"]),
126
+ radius=self.radius,
127
+ category_prefix=prefix,
128
+ )
129
+ # * Map six-digit codes to specific types
130
+ for poi in resp:
131
+ cate_str = poi[0]["category"]
132
+ poi[0]["category"] = POI_TYPE_DICT[cate_str]
133
+ else:
134
+ radius_ = radius if radius else self.radius
135
+ return SencePOI(radius_, category_prefix)
136
+
137
+
138
+ class UpdateWithSimulator(Tool):
139
+ def __init__(
140
+ self,
141
+ person_template_func: Callable[[], dict] = PersonService.default_dict_person,
142
+ ) -> None:
143
+ self.person_template_func = person_template_func
144
+
145
+ async def _bind_to_simulator(
146
+ self,
147
+ ):
148
+ """
149
+ Bind Agent to Simulator
150
+
151
+ Args:
152
+ person_template (dict, optional): The person template in dict format. Defaults to PersonService.default_dict_person().
153
+ """
154
+ agent = self.agent
155
+ if agent._simulator is None:
156
+ return
157
+ if not agent._has_bound_to_simulator:
158
+ FROM_MEMORY_KEYS = {
159
+ "attribute",
160
+ "home",
161
+ "work",
162
+ "vehicle_attribute",
163
+ "bus_attribute",
164
+ "pedestrian_attribute",
165
+ "bike_attribute",
166
+ }
167
+ simulator = agent.simulator
168
+ memory = agent.memory
169
+ person_id = await memory.get("id")
170
+ # ATTENTION:模拟器分配的id从0开始
171
+ if person_id >= 0:
172
+ await simulator.GetPerson(person_id)
173
+ logging.debug(f"Binding to Person `{person_id}` already in Simulator")
174
+ else:
175
+ dict_person = deepcopy(self.person_template_func())
176
+ for _key in FROM_MEMORY_KEYS:
177
+ try:
178
+ _value = await memory.get(_key)
179
+ if _value:
180
+ dict_person[_key] = _value
181
+ except KeyError as e:
182
+ continue
183
+ resp = await simulator.AddPerson(
184
+ dict2pb(dict_person, person_pb2.Person())
185
+ )
186
+ person_id = resp["person_id"]
187
+ await memory.update("id", person_id, protect_llm_read_only_fields=False)
188
+ logging.debug(
189
+ f"Binding to Person `{person_id}` just added to Simulator"
190
+ )
191
+ # 防止模拟器还没有到prepare阶段导致GetPerson出错
192
+ await asyncio.sleep(5)
193
+ agent._has_bound_to_simulator = True
194
+ else:
195
+ pass
196
+
197
+ async def _update_motion_with_sim(
198
+ self,
199
+ ):
200
+ agent = self.agent
201
+ if agent._simulator is None:
202
+ return
203
+ if not agent._has_bound_to_simulator:
204
+ await self._bind_to_simulator()
205
+ simulator = agent.simulator
206
+ memory = agent.memory
207
+ person_id = await memory.get("id")
208
+ resp = await simulator.GetPerson(person_id)
209
+ resp_dict = resp["person"]
210
+ for k, v in resp_dict.get("motion", {}).items():
211
+ try:
212
+ await memory.get(k)
213
+ await memory.update(
214
+ k, v, mode="replace", protect_llm_read_only_fields=False
215
+ )
216
+ except KeyError as e:
217
+ continue
218
+
219
+ async def __call__(
220
+ self,
221
+ ):
222
+ agent = self.agent
223
+ await self._bind_to_simulator()
224
+ await self._update_motion_with_sim()
225
+
226
+
227
+ class ResetAgentPosition(Tool):
228
+ def __init__(self) -> None:
229
+ pass
230
+
231
+ async def __call__(
232
+ self,
233
+ aoi_id: Optional[int] = None,
234
+ poi_id: Optional[int] = None,
235
+ lane_id: Optional[int] = None,
236
+ s: Optional[float] = None,
237
+ ):
238
+ agent = self.agent
239
+ memory = agent.memory
240
+ await agent.simulator.ResetPersonPosition(
241
+ person_id=await memory.get("id"),
242
+ aoi_id=aoi_id,
243
+ poi_id=poi_id,
244
+ lane_id=lane_id,
245
+ s=s,
246
+ )