pycityagent 2.0.0a43__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.
Files changed (81) hide show
  1. pycityagent/__init__.py +23 -0
  2. pycityagent/agent.py +833 -0
  3. pycityagent/cli/wrapper.py +44 -0
  4. pycityagent/economy/__init__.py +5 -0
  5. pycityagent/economy/econ_client.py +355 -0
  6. pycityagent/environment/__init__.py +7 -0
  7. pycityagent/environment/interact/__init__.py +0 -0
  8. pycityagent/environment/interact/interact.py +198 -0
  9. pycityagent/environment/message/__init__.py +0 -0
  10. pycityagent/environment/sence/__init__.py +0 -0
  11. pycityagent/environment/sence/static.py +416 -0
  12. pycityagent/environment/sidecar/__init__.py +8 -0
  13. pycityagent/environment/sidecar/sidecarv2.py +109 -0
  14. pycityagent/environment/sim/__init__.py +29 -0
  15. pycityagent/environment/sim/aoi_service.py +39 -0
  16. pycityagent/environment/sim/client.py +126 -0
  17. pycityagent/environment/sim/clock_service.py +44 -0
  18. pycityagent/environment/sim/economy_services.py +192 -0
  19. pycityagent/environment/sim/lane_service.py +111 -0
  20. pycityagent/environment/sim/light_service.py +122 -0
  21. pycityagent/environment/sim/person_service.py +295 -0
  22. pycityagent/environment/sim/road_service.py +39 -0
  23. pycityagent/environment/sim/sim_env.py +145 -0
  24. pycityagent/environment/sim/social_service.py +59 -0
  25. pycityagent/environment/simulator.py +331 -0
  26. pycityagent/environment/utils/__init__.py +14 -0
  27. pycityagent/environment/utils/base64.py +16 -0
  28. pycityagent/environment/utils/const.py +244 -0
  29. pycityagent/environment/utils/geojson.py +24 -0
  30. pycityagent/environment/utils/grpc.py +57 -0
  31. pycityagent/environment/utils/map_utils.py +157 -0
  32. pycityagent/environment/utils/port.py +11 -0
  33. pycityagent/environment/utils/protobuf.py +41 -0
  34. pycityagent/llm/__init__.py +11 -0
  35. pycityagent/llm/embeddings.py +231 -0
  36. pycityagent/llm/llm.py +377 -0
  37. pycityagent/llm/llmconfig.py +13 -0
  38. pycityagent/llm/utils.py +6 -0
  39. pycityagent/memory/__init__.py +13 -0
  40. pycityagent/memory/const.py +43 -0
  41. pycityagent/memory/faiss_query.py +302 -0
  42. pycityagent/memory/memory.py +448 -0
  43. pycityagent/memory/memory_base.py +170 -0
  44. pycityagent/memory/profile.py +165 -0
  45. pycityagent/memory/self_define.py +165 -0
  46. pycityagent/memory/state.py +173 -0
  47. pycityagent/memory/utils.py +28 -0
  48. pycityagent/message/__init__.py +3 -0
  49. pycityagent/message/messager.py +88 -0
  50. pycityagent/metrics/__init__.py +6 -0
  51. pycityagent/metrics/mlflow_client.py +147 -0
  52. pycityagent/metrics/utils/const.py +0 -0
  53. pycityagent/pycityagent-sim +0 -0
  54. pycityagent/pycityagent-ui +0 -0
  55. pycityagent/simulation/__init__.py +8 -0
  56. pycityagent/simulation/agentgroup.py +580 -0
  57. pycityagent/simulation/simulation.py +634 -0
  58. pycityagent/simulation/storage/pg.py +184 -0
  59. pycityagent/survey/__init__.py +4 -0
  60. pycityagent/survey/manager.py +54 -0
  61. pycityagent/survey/models.py +120 -0
  62. pycityagent/utils/__init__.py +11 -0
  63. pycityagent/utils/avro_schema.py +109 -0
  64. pycityagent/utils/decorators.py +99 -0
  65. pycityagent/utils/parsers/__init__.py +13 -0
  66. pycityagent/utils/parsers/code_block_parser.py +37 -0
  67. pycityagent/utils/parsers/json_parser.py +86 -0
  68. pycityagent/utils/parsers/parser_base.py +60 -0
  69. pycityagent/utils/pg_query.py +92 -0
  70. pycityagent/utils/survey_util.py +53 -0
  71. pycityagent/workflow/__init__.py +26 -0
  72. pycityagent/workflow/block.py +211 -0
  73. pycityagent/workflow/prompt.py +79 -0
  74. pycityagent/workflow/tool.py +240 -0
  75. pycityagent/workflow/trigger.py +163 -0
  76. pycityagent-2.0.0a43.dist-info/LICENSE +21 -0
  77. pycityagent-2.0.0a43.dist-info/METADATA +235 -0
  78. pycityagent-2.0.0a43.dist-info/RECORD +81 -0
  79. pycityagent-2.0.0a43.dist-info/WHEEL +5 -0
  80. pycityagent-2.0.0a43.dist-info/entry_points.txt +3 -0
  81. pycityagent-2.0.0a43.dist-info/top_level.txt +3 -0
@@ -0,0 +1,86 @@
1
+ import json
2
+ import logging
3
+ from typing import Any
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, 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,92 @@
1
+ from typing import Any
2
+
3
+ PGSQL_DICT: dict[str, list[Any]] = {
4
+ # Experiment
5
+ "experiment": [
6
+ """
7
+ CREATE TABLE IF NOT EXISTS {table_name} (
8
+ id UUID PRIMARY KEY,
9
+ name TEXT,
10
+ num_day INT4,
11
+ status INT4,
12
+ cur_day INT4,
13
+ cur_t FLOAT,
14
+ config TEXT,
15
+ error TEXT,
16
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
17
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
18
+ )
19
+ """,
20
+ ],
21
+ # Agent Profile
22
+ "agent_profile": [
23
+ """
24
+ CREATE TABLE IF NOT EXISTS {table_name} (
25
+ id UUID PRIMARY KEY,
26
+ name TEXT,
27
+ profile JSONB
28
+ )
29
+ """,
30
+ ],
31
+ # Agent Dialog
32
+ "agent_dialog": [
33
+ """
34
+ CREATE TABLE IF NOT EXISTS {table_name} (
35
+ id UUID,
36
+ day INT4,
37
+ t FLOAT,
38
+ type INT4,
39
+ speaker TEXT,
40
+ content TEXT,
41
+ created_at TIMESTAMPTZ
42
+ )
43
+ """,
44
+ "CREATE INDEX {table_name}_id_idx ON {table_name} (id)",
45
+ "CREATE INDEX {table_name}_day_t_idx ON {table_name} (day,t)",
46
+ ],
47
+ # Agent Status
48
+ "agent_status": [
49
+ """
50
+ CREATE TABLE IF NOT EXISTS {table_name} (
51
+ id UUID,
52
+ day INT4,
53
+ t FLOAT,
54
+ lng DOUBLE PRECISION,
55
+ lat DOUBLE PRECISION,
56
+ parent_id INT4,
57
+ action TEXT,
58
+ status JSONB,
59
+ created_at TIMESTAMPTZ
60
+ )
61
+ """,
62
+ "CREATE INDEX {table_name}_id_idx ON {table_name} (id)",
63
+ "CREATE INDEX {table_name}_day_t_idx ON {table_name} (day,t)",
64
+ ],
65
+ # Agent Survey
66
+ "agent_survey": [
67
+ """
68
+ CREATE TABLE IF NOT EXISTS {table_name} (
69
+ id UUID,
70
+ day INT4,
71
+ t FLOAT,
72
+ survey_id UUID,
73
+ result JSONB,
74
+ created_at TIMESTAMPTZ
75
+ )
76
+ """,
77
+ "CREATE INDEX {table_name}_id_idx ON {table_name} (id)",
78
+ "CREATE INDEX {table_name}_day_t_idx ON {table_name} (day,t)",
79
+ ],
80
+ }
81
+ TO_UPDATE_EXP_INFO_KEYS_AND_TYPES: list[tuple[str, Any]] = [
82
+ ("id", None),
83
+ ("name", str),
84
+ ("num_day", int),
85
+ ("status", int),
86
+ ("cur_day", int),
87
+ ("cur_t", float),
88
+ ("config", str),
89
+ ("error", str),
90
+ ("created_at", None),
91
+ ("updated_at", None),
92
+ ]
@@ -0,0 +1,53 @@
1
+ def process_survey_for_llm(survey_dict: dict) -> str:
2
+ """
3
+ 将问卷字典转换为LLM可以逐题处理的格式,使用英文提示
4
+ """
5
+ prompt = f"""Survey Title: {survey_dict['title']}
6
+ Survey Description: {survey_dict['description']}
7
+
8
+ Please answer each question in the following format:
9
+
10
+ """
11
+
12
+ question_count = 1
13
+ for page in survey_dict['pages']:
14
+ for question in page['elements']:
15
+ prompt += f"Question {question_count}: {question['title']}\n"
16
+
17
+ # 根据不同类型的问题生成不同的提示
18
+ if question['type'] == 'radiogroup':
19
+ prompt += "Options: " + ", ".join(question['choices']) + "\n"
20
+ prompt += "Please select ONE option\n"
21
+
22
+ elif question['type'] == 'checkbox':
23
+ prompt += "Options: " + ", ".join(question['choices']) + "\n"
24
+ prompt += "You can select MULTIPLE options\n"
25
+
26
+ elif question['type'] == 'rating':
27
+ prompt += f"Rating range: {question.get('min_rating', 1)} - {question.get('max_rating', 5)}\n"
28
+ prompt += "Please provide a rating within the range\n"
29
+
30
+ elif question['type'] == 'matrix':
31
+ prompt += "Rows: " + ", ".join(question['rows']) + "\n"
32
+ prompt += "Columns: " + ", ".join(question['columns']) + "\n"
33
+ prompt += "Please select ONE column option for EACH row\n"
34
+
35
+ elif question['type'] == 'text':
36
+ prompt += "Please provide a text response\n"
37
+
38
+ elif question['type'] == 'boolean':
39
+ prompt += "Options: Yes, No\n"
40
+ prompt += "Please select either Yes or No\n"
41
+
42
+ prompt += "\nAnswer: [Your response here]\n\n---\n\n"
43
+ question_count += 1
44
+
45
+ # 添加总结提示
46
+ prompt += """Please ensure:
47
+ 1. All required questions are answered
48
+ 2. Responses match the question type requirements
49
+ 3. Answers are clear and specific
50
+
51
+ Format your responses exactly as requested above."""
52
+
53
+ return prompt
@@ -0,0 +1,26 @@
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
+ trigger_class)
9
+ from .prompt import FormatPrompt
10
+ from .tool import ExportMlflowMetrics, GetMap, SencePOI, Tool
11
+ from .trigger import EventTrigger, MemoryChangeTrigger, TimeTrigger
12
+
13
+ __all__ = [
14
+ "SencePOI",
15
+ "Tool",
16
+ "ExportMlflowMetrics",
17
+ "GetMap",
18
+ "MemoryChangeTrigger",
19
+ "TimeTrigger",
20
+ "EventTrigger",
21
+ "Block",
22
+ "log_and_check",
23
+ "log_and_check_with_memory",
24
+ "FormatPrompt",
25
+ "trigger_class",
26
+ ]
@@ -0,0 +1,211 @@
1
+ import asyncio
2
+ import functools
3
+ import inspect
4
+ from collections.abc import Awaitable, Callable, Coroutine
5
+ from typing import Any, Optional, Union
6
+
7
+ from ..environment.simulator import Simulator
8
+ from ..llm import LLM
9
+ from ..memory import Memory
10
+ from ..utils.decorators import record_call_aio
11
+ from ..workflow.trigger import EventTrigger
12
+
13
+ TRIGGER_INTERVAL = 1
14
+
15
+
16
+ def log_and_check_with_memory(
17
+ condition: Union[
18
+ Callable[[Memory], Coroutine[Any, Any, bool]],
19
+ Callable[[], Coroutine[Any, Any, bool]],
20
+ Callable[[Memory], bool],
21
+ Callable[[], bool],
22
+ ] = lambda: True,
23
+ trigger_interval: float = TRIGGER_INTERVAL,
24
+ record_function_calling: bool = False,
25
+ ):
26
+ """
27
+ A decorator that logs function calls and optionally checks a condition before executing the function.
28
+
29
+ This decorator is specifically designed to be used with the `block` method. A 'Memory' object is required in method input.
30
+
31
+ Args:
32
+ condition (Callable): A condition function that must be satisfied before the decorated function is executed.
33
+ Can be synchronous or asynchronous.
34
+ trigger_interval (float): The interval (in seconds) to wait between condition checks.
35
+ record_function_calling (bool): Whether to log the function call information.
36
+ """
37
+
38
+ def decorator(func):
39
+ @record_call_aio(record_function_calling)
40
+ @functools.wraps(func)
41
+ async def wrapper(self, *args, **kwargs):
42
+ memory = None
43
+ for arg in list(args) + list(kwargs.values()):
44
+ if memory is not None:
45
+ break
46
+ if isinstance(arg, Memory):
47
+ memory = arg
48
+ assert isinstance(memory, Memory), "Input arguments has no `Memory` object!"
49
+ # Wait until the condition is met
50
+ sig = inspect.signature(condition)
51
+ params = list(sig.parameters.values())
52
+ if len(params) == 1 and params[0].annotation is Memory:
53
+ if inspect.iscoroutinefunction(condition):
54
+ while not await condition(memory): # type:ignore
55
+ await asyncio.sleep(trigger_interval)
56
+ else:
57
+ while not condition(memory): # type:ignore
58
+ await asyncio.sleep(trigger_interval)
59
+ elif len(params) == 0:
60
+ if inspect.iscoroutinefunction(condition):
61
+ while not await condition(): # type:ignore
62
+ await asyncio.sleep(trigger_interval)
63
+ else:
64
+ while not condition(): # type:ignore
65
+ await asyncio.sleep(trigger_interval)
66
+ else:
67
+ raise RuntimeError(
68
+ f"Invalid parameter format in condition function {condition}"
69
+ )
70
+ result = await func(self, *args, **kwargs)
71
+ return result
72
+
73
+ return wrapper
74
+
75
+ return decorator
76
+
77
+
78
+ def log_and_check(
79
+ condition: Union[
80
+ Callable[[], Coroutine[Any, Any, bool]],
81
+ Callable[[], bool],
82
+ ] = lambda: True,
83
+ trigger_interval: float = TRIGGER_INTERVAL,
84
+ record_function_calling: bool = False,
85
+ ):
86
+ """
87
+ A decorator that logs function calls and optionally checks a condition before executing the function.
88
+
89
+ This decorator is specifically designed to be used with the `block` method.
90
+
91
+ Args:
92
+ condition (Callable): A condition function that must be satisfied before the decorated function is executed.
93
+ Can be synchronous or asynchronous.
94
+ trigger_interval (float): The interval (in seconds) to wait between condition checks.
95
+ record_function_calling (bool): Whether to log the function call information.
96
+ """
97
+
98
+ def decorator(func):
99
+ @record_call_aio(record_function_calling)
100
+ @functools.wraps(func)
101
+ async def wrapper(self, *args, **kwargs):
102
+ # Wait until the condition is met
103
+ sig = inspect.signature(condition)
104
+ params = list(sig.parameters.values())
105
+ if len(params) == 0:
106
+ if inspect.iscoroutinefunction(condition):
107
+ while not await condition(): # type:ignore
108
+ await asyncio.sleep(trigger_interval)
109
+ else:
110
+ while not condition(): # type:ignore
111
+ await asyncio.sleep(trigger_interval)
112
+ else:
113
+ raise RuntimeError(
114
+ f"Invalid parameter format in condition function {condition}"
115
+ )
116
+ result = await func(self, *args, **kwargs)
117
+ return result
118
+
119
+ return wrapper
120
+
121
+ return decorator
122
+
123
+
124
+ def trigger_class():
125
+ def decorator(cls):
126
+ original_forward = cls.forward
127
+
128
+ @functools.wraps(original_forward)
129
+ async def wrapped_forward(self, *args, **kwargs):
130
+ if self.trigger is not None:
131
+ await self.trigger.wait_for_trigger()
132
+ return await original_forward(self, *args, **kwargs)
133
+
134
+ cls.forward = wrapped_forward
135
+ return cls
136
+
137
+ return decorator
138
+
139
+
140
+ # Define a Block, similar to a layer in PyTorch
141
+ class Block:
142
+ def __init__(
143
+ self,
144
+ name: str,
145
+ llm: Optional[LLM] = None,
146
+ memory: Optional[Memory] = None,
147
+ simulator: Optional[Simulator] = None,
148
+ trigger: Optional[EventTrigger] = None,
149
+ ):
150
+ self.name = name
151
+ self._llm = llm
152
+ self._memory = memory
153
+ self._simulator = simulator
154
+ # 如果传入trigger,将block注入到trigger中并立即初始化
155
+ if trigger is not None:
156
+ trigger.block = self
157
+ trigger.initialize() # 立即初始化trigger
158
+ self.trigger = trigger
159
+
160
+ async def forward(self):
161
+ """
162
+ Each block performs a specific reasoning task.
163
+ To be overridden by specific block implementations.
164
+ """
165
+ raise NotImplementedError("Subclasses should implement this method")
166
+
167
+ @property
168
+ def llm(
169
+ self,
170
+ ) -> LLM:
171
+ if self._llm is None:
172
+ raise RuntimeError(f"LLM access before assignment, please `set_llm` first!")
173
+ return self._llm
174
+
175
+ @property
176
+ def memory(
177
+ self,
178
+ ) -> Memory:
179
+ if self._memory is None:
180
+ raise RuntimeError(
181
+ f"Memory access before assignment, please `set_memory` first!"
182
+ )
183
+ return self._memory
184
+
185
+ @property
186
+ def simulator(
187
+ self,
188
+ ) -> Simulator:
189
+ if self._simulator is None:
190
+ raise RuntimeError(
191
+ f"Simulator access before assignment, please `set_simulator` first!"
192
+ )
193
+ return self._simulator
194
+
195
+ def set_llm_client(self, llm: LLM):
196
+ """
197
+ Set the llm_client of the block.
198
+ """
199
+ self._llm = llm
200
+
201
+ def set_simulator(self, simulator: Simulator):
202
+ """
203
+ Set the simulator of the block.
204
+ """
205
+ self._simulator = simulator
206
+
207
+ def set_memory(self, memory: Memory):
208
+ """
209
+ Set the memory of the block.
210
+ """
211
+ self._memory = memory
@@ -0,0 +1,79 @@
1
+ from typing import Optional, Union
2
+ import re
3
+
4
+
5
+ class FormatPrompt:
6
+ """
7
+ A class to handle the formatting of prompts based on a template,
8
+ with support for system prompts and variable extraction.
9
+
10
+ Attributes:
11
+ template (str): The template string containing placeholders.
12
+ system_prompt (Optional[str]): An optional system prompt to add to the dialog.
13
+ variables (list[str]): A list of variable names extracted from the template.
14
+ formatted_string (str): The formatted string derived from the template and provided variables.
15
+ """
16
+
17
+ def __init__(self, template: str, system_prompt: Optional[str] = None) -> None:
18
+ """
19
+ Initializes the FormatPrompt with a template and an optional system prompt.
20
+
21
+ Args:
22
+ template (str): The string template with variable placeholders.
23
+ system_prompt (Optional[str]): An optional system prompt.
24
+ """
25
+ self.template = template
26
+ self.system_prompt = system_prompt # Store the system prompt
27
+ self.variables = self._extract_variables()
28
+ self.formatted_string = "" # To store the formatted string
29
+
30
+ def _extract_variables(self) -> list[str]:
31
+ """
32
+ Extracts variable names from the template string.
33
+
34
+ Returns:
35
+ list[str]: A list of variable names found within the template.
36
+ """
37
+ return re.findall(r"\{(\w+)\}", self.template)
38
+
39
+ def format(self, **kwargs) -> str:
40
+ """
41
+ Formats the template string using the provided keyword arguments.
42
+
43
+ Args:
44
+ **kwargs: Variable names and their corresponding values to format the template.
45
+
46
+ Returns:
47
+ str: The formatted string.
48
+ """
49
+ self.formatted_string = self.template.format(
50
+ **kwargs
51
+ ) # Store the formatted string
52
+ return self.formatted_string
53
+
54
+ def to_dialog(self) -> list[dict[str, str]]:
55
+ """
56
+ Converts the formatted prompt and optional system prompt into a dialog format.
57
+
58
+ Returns:
59
+ list[dict[str, str]]: A list representing the dialog with roles and content.
60
+ """
61
+ dialog = []
62
+ if self.system_prompt:
63
+ dialog.append(
64
+ {"role": "system", "content": self.system_prompt}
65
+ ) # Add system prompt if it exists
66
+ dialog.append(
67
+ {"role": "user", "content": self.formatted_string}
68
+ ) # Add user content
69
+ return dialog
70
+
71
+ def log(self) -> None:
72
+ """
73
+ Logs the details of the FormatPrompt, including the template,
74
+ system prompt, extracted variables, and formatted string.
75
+ """
76
+ print(f"FormatPrompt: {self.template}")
77
+ print(f"System Prompt: {self.system_prompt}") # Log the system prompt
78
+ print(f"Variables: {self.variables}")
79
+ print(f"Formatted String: {self.formatted_string}") # Log the formatted string