pycityagent 2.0.0a42__cp310-cp310-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/__init__.py +23 -0
- pycityagent/agent.py +833 -0
- pycityagent/cli/wrapper.py +44 -0
- pycityagent/economy/__init__.py +5 -0
- pycityagent/economy/econ_client.py +355 -0
- pycityagent/environment/__init__.py +7 -0
- pycityagent/environment/interact/__init__.py +0 -0
- pycityagent/environment/interact/interact.py +198 -0
- pycityagent/environment/message/__init__.py +0 -0
- pycityagent/environment/sence/__init__.py +0 -0
- pycityagent/environment/sence/static.py +416 -0
- pycityagent/environment/sidecar/__init__.py +8 -0
- pycityagent/environment/sidecar/sidecarv2.py +109 -0
- pycityagent/environment/sim/__init__.py +29 -0
- pycityagent/environment/sim/aoi_service.py +39 -0
- pycityagent/environment/sim/client.py +126 -0
- pycityagent/environment/sim/clock_service.py +44 -0
- pycityagent/environment/sim/economy_services.py +192 -0
- pycityagent/environment/sim/lane_service.py +111 -0
- pycityagent/environment/sim/light_service.py +122 -0
- pycityagent/environment/sim/person_service.py +295 -0
- pycityagent/environment/sim/road_service.py +39 -0
- pycityagent/environment/sim/sim_env.py +145 -0
- pycityagent/environment/sim/social_service.py +59 -0
- pycityagent/environment/simulator.py +331 -0
- pycityagent/environment/utils/__init__.py +14 -0
- pycityagent/environment/utils/base64.py +16 -0
- pycityagent/environment/utils/const.py +244 -0
- pycityagent/environment/utils/geojson.py +24 -0
- pycityagent/environment/utils/grpc.py +57 -0
- pycityagent/environment/utils/map_utils.py +157 -0
- pycityagent/environment/utils/port.py +11 -0
- pycityagent/environment/utils/protobuf.py +41 -0
- pycityagent/llm/__init__.py +11 -0
- pycityagent/llm/embeddings.py +231 -0
- pycityagent/llm/llm.py +377 -0
- pycityagent/llm/llmconfig.py +13 -0
- pycityagent/llm/utils.py +6 -0
- pycityagent/memory/__init__.py +13 -0
- pycityagent/memory/const.py +43 -0
- pycityagent/memory/faiss_query.py +302 -0
- pycityagent/memory/memory.py +448 -0
- pycityagent/memory/memory_base.py +170 -0
- pycityagent/memory/profile.py +165 -0
- pycityagent/memory/self_define.py +165 -0
- pycityagent/memory/state.py +173 -0
- pycityagent/memory/utils.py +28 -0
- pycityagent/message/__init__.py +3 -0
- pycityagent/message/messager.py +88 -0
- pycityagent/metrics/__init__.py +6 -0
- pycityagent/metrics/mlflow_client.py +147 -0
- pycityagent/metrics/utils/const.py +0 -0
- pycityagent/pycityagent-sim +0 -0
- pycityagent/pycityagent-ui +0 -0
- pycityagent/simulation/__init__.py +8 -0
- pycityagent/simulation/agentgroup.py +580 -0
- pycityagent/simulation/simulation.py +634 -0
- pycityagent/simulation/storage/pg.py +184 -0
- pycityagent/survey/__init__.py +4 -0
- pycityagent/survey/manager.py +54 -0
- pycityagent/survey/models.py +120 -0
- pycityagent/utils/__init__.py +11 -0
- pycityagent/utils/avro_schema.py +109 -0
- pycityagent/utils/decorators.py +99 -0
- pycityagent/utils/parsers/__init__.py +13 -0
- pycityagent/utils/parsers/code_block_parser.py +37 -0
- pycityagent/utils/parsers/json_parser.py +86 -0
- pycityagent/utils/parsers/parser_base.py +60 -0
- pycityagent/utils/pg_query.py +92 -0
- pycityagent/utils/survey_util.py +53 -0
- pycityagent/workflow/__init__.py +26 -0
- pycityagent/workflow/block.py +211 -0
- pycityagent/workflow/prompt.py +79 -0
- pycityagent/workflow/tool.py +240 -0
- pycityagent/workflow/trigger.py +163 -0
- pycityagent-2.0.0a42.dist-info/LICENSE +21 -0
- pycityagent-2.0.0a42.dist-info/METADATA +235 -0
- pycityagent-2.0.0a42.dist-info/RECORD +81 -0
- pycityagent-2.0.0a42.dist-info/WHEEL +5 -0
- pycityagent-2.0.0a42.dist-info/entry_points.txt +3 -0
- pycityagent-2.0.0a42.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
|