camel-ai 0.1.1__py3-none-any.whl → 0.1.3__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.
Potentially problematic release.
This version of camel-ai might be problematic. Click here for more details.
- camel/__init__.py +1 -11
- camel/agents/__init__.py +5 -5
- camel/agents/chat_agent.py +124 -63
- camel/agents/critic_agent.py +28 -17
- camel/agents/deductive_reasoner_agent.py +235 -0
- camel/agents/embodied_agent.py +92 -40
- camel/agents/role_assignment_agent.py +27 -17
- camel/agents/task_agent.py +60 -34
- camel/agents/tool_agents/base.py +0 -1
- camel/agents/tool_agents/hugging_face_tool_agent.py +7 -4
- camel/configs.py +119 -7
- camel/embeddings/__init__.py +2 -0
- camel/embeddings/base.py +3 -2
- camel/embeddings/openai_embedding.py +3 -3
- camel/embeddings/sentence_transformers_embeddings.py +65 -0
- camel/functions/__init__.py +13 -3
- camel/functions/google_maps_function.py +335 -0
- camel/functions/math_functions.py +7 -7
- camel/functions/openai_function.py +344 -42
- camel/functions/search_functions.py +100 -35
- camel/functions/twitter_function.py +484 -0
- camel/functions/weather_functions.py +36 -23
- camel/generators.py +65 -46
- camel/human.py +17 -11
- camel/interpreters/__init__.py +25 -0
- camel/interpreters/base.py +49 -0
- camel/{utils/python_interpreter.py → interpreters/internal_python_interpreter.py} +129 -48
- camel/interpreters/interpreter_error.py +19 -0
- camel/interpreters/subprocess_interpreter.py +190 -0
- camel/loaders/__init__.py +22 -0
- camel/{functions/base_io_functions.py → loaders/base_io.py} +38 -35
- camel/{functions/unstructured_io_fuctions.py → loaders/unstructured_io.py} +199 -110
- camel/memories/__init__.py +17 -7
- camel/memories/agent_memories.py +156 -0
- camel/memories/base.py +97 -32
- camel/memories/blocks/__init__.py +21 -0
- camel/memories/{chat_history_memory.py → blocks/chat_history_block.py} +34 -34
- camel/memories/blocks/vectordb_block.py +101 -0
- camel/memories/context_creators/__init__.py +3 -2
- camel/memories/context_creators/score_based.py +32 -20
- camel/memories/records.py +6 -5
- camel/messages/__init__.py +2 -2
- camel/messages/base.py +99 -16
- camel/messages/func_message.py +7 -4
- camel/models/__init__.py +4 -2
- camel/models/anthropic_model.py +132 -0
- camel/models/base_model.py +3 -2
- camel/models/model_factory.py +10 -8
- camel/models/open_source_model.py +25 -13
- camel/models/openai_model.py +9 -10
- camel/models/stub_model.py +6 -5
- camel/prompts/__init__.py +7 -5
- camel/prompts/ai_society.py +21 -14
- camel/prompts/base.py +54 -47
- camel/prompts/code.py +22 -14
- camel/prompts/evaluation.py +8 -5
- camel/prompts/misalignment.py +26 -19
- camel/prompts/object_recognition.py +35 -0
- camel/prompts/prompt_templates.py +14 -8
- camel/prompts/role_description_prompt_template.py +16 -10
- camel/prompts/solution_extraction.py +9 -5
- camel/prompts/task_prompt_template.py +24 -21
- camel/prompts/translation.py +9 -5
- camel/responses/agent_responses.py +5 -2
- camel/retrievers/__init__.py +24 -0
- camel/retrievers/auto_retriever.py +319 -0
- camel/retrievers/base.py +64 -0
- camel/retrievers/bm25_retriever.py +149 -0
- camel/retrievers/vector_retriever.py +166 -0
- camel/societies/__init__.py +1 -1
- camel/societies/babyagi_playing.py +56 -32
- camel/societies/role_playing.py +188 -133
- camel/storages/__init__.py +18 -0
- camel/storages/graph_storages/__init__.py +23 -0
- camel/storages/graph_storages/base.py +82 -0
- camel/storages/graph_storages/graph_element.py +74 -0
- camel/storages/graph_storages/neo4j_graph.py +582 -0
- camel/storages/key_value_storages/base.py +1 -2
- camel/storages/key_value_storages/in_memory.py +1 -2
- camel/storages/key_value_storages/json.py +8 -13
- camel/storages/vectordb_storages/__init__.py +33 -0
- camel/storages/vectordb_storages/base.py +202 -0
- camel/storages/vectordb_storages/milvus.py +396 -0
- camel/storages/vectordb_storages/qdrant.py +371 -0
- camel/terminators/__init__.py +1 -1
- camel/terminators/base.py +2 -3
- camel/terminators/response_terminator.py +21 -12
- camel/terminators/token_limit_terminator.py +5 -3
- camel/types/__init__.py +12 -6
- camel/types/enums.py +86 -13
- camel/types/openai_types.py +10 -5
- camel/utils/__init__.py +18 -13
- camel/utils/commons.py +242 -81
- camel/utils/token_counting.py +135 -15
- {camel_ai-0.1.1.dist-info → camel_ai-0.1.3.dist-info}/METADATA +116 -74
- camel_ai-0.1.3.dist-info/RECORD +101 -0
- {camel_ai-0.1.1.dist-info → camel_ai-0.1.3.dist-info}/WHEEL +1 -1
- camel/memories/context_creators/base.py +0 -72
- camel_ai-0.1.1.dist-info/RECORD +0 -75
camel/generators.py
CHANGED
|
@@ -43,24 +43,27 @@ class SystemMessageGenerator:
|
|
|
43
43
|
self.sys_prompts = sys_prompts
|
|
44
44
|
self.sys_msg_meta_dict_keys = sys_msg_meta_dict_keys or set()
|
|
45
45
|
else:
|
|
46
|
-
assistant_prompt_template =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
assistant_prompt_template = (
|
|
47
|
+
PromptTemplateGenerator().get_system_prompt(
|
|
48
|
+
task_type,
|
|
49
|
+
RoleType.ASSISTANT,
|
|
50
|
+
)
|
|
50
51
|
)
|
|
51
52
|
user_prompt_template = PromptTemplateGenerator().get_system_prompt(
|
|
52
53
|
task_type,
|
|
53
54
|
RoleType.USER,
|
|
54
55
|
)
|
|
55
|
-
critic_prompt_template =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
critic_prompt_template = (
|
|
57
|
+
PromptTemplateGenerator().get_system_prompt(
|
|
58
|
+
task_type,
|
|
59
|
+
RoleType.CRITIC,
|
|
60
|
+
)
|
|
59
61
|
)
|
|
60
|
-
embodiment_prompt_template =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
embodiment_prompt_template = (
|
|
63
|
+
PromptTemplateGenerator().get_system_prompt(
|
|
64
|
+
task_type,
|
|
65
|
+
RoleType.EMBODIMENT,
|
|
66
|
+
)
|
|
64
67
|
)
|
|
65
68
|
|
|
66
69
|
self.sys_prompts = dict()
|
|
@@ -73,7 +76,8 @@ class SystemMessageGenerator:
|
|
|
73
76
|
assistant_prompt_template.key_words
|
|
74
77
|
| user_prompt_template.key_words
|
|
75
78
|
| critic_prompt_template.key_words
|
|
76
|
-
| embodiment_prompt_template.key_words
|
|
79
|
+
| embodiment_prompt_template.key_words
|
|
80
|
+
)
|
|
77
81
|
|
|
78
82
|
if RoleType.DEFAULT not in self.sys_prompts:
|
|
79
83
|
self.sys_prompts[RoleType.DEFAULT] = "You are a helpful assistant."
|
|
@@ -85,9 +89,11 @@ class SystemMessageGenerator:
|
|
|
85
89
|
meta_dict (Dict[str, str]): The dictionary to validate.
|
|
86
90
|
"""
|
|
87
91
|
if not set(meta_dict.keys()).issubset(self.sys_msg_meta_dict_keys):
|
|
88
|
-
raise ValueError(
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
raise ValueError(
|
|
93
|
+
"The keys of the meta_dict should be in "
|
|
94
|
+
f"{self.sys_msg_meta_dict_keys}. "
|
|
95
|
+
f"Got {set(meta_dict.keys())} instead."
|
|
96
|
+
)
|
|
91
97
|
|
|
92
98
|
def from_dict(
|
|
93
99
|
self,
|
|
@@ -109,8 +115,12 @@ class SystemMessageGenerator:
|
|
|
109
115
|
role_name, role_type = role_tuple
|
|
110
116
|
sys_prompt = self.sys_prompts[role_type]
|
|
111
117
|
sys_prompt = sys_prompt.format(**meta_dict)
|
|
112
|
-
return BaseMessage(
|
|
113
|
-
|
|
118
|
+
return BaseMessage(
|
|
119
|
+
role_name=role_name,
|
|
120
|
+
role_type=role_type,
|
|
121
|
+
meta_dict=meta_dict,
|
|
122
|
+
content=sys_prompt,
|
|
123
|
+
)
|
|
114
124
|
|
|
115
125
|
def from_dicts(
|
|
116
126
|
self,
|
|
@@ -134,7 +144,8 @@ class SystemMessageGenerator:
|
|
|
134
144
|
"""
|
|
135
145
|
if len(meta_dicts) != len(role_tuples):
|
|
136
146
|
raise ValueError(
|
|
137
|
-
"The number of meta_dicts and role_types should be the same."
|
|
147
|
+
"The number of meta_dicts and role_types should be the same."
|
|
148
|
+
)
|
|
138
149
|
|
|
139
150
|
return [
|
|
140
151
|
self.from_dict(meta_dict, role_tuple)
|
|
@@ -143,13 +154,13 @@ class SystemMessageGenerator:
|
|
|
143
154
|
|
|
144
155
|
|
|
145
156
|
class RoleNameGenerator:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
def __init__(
|
|
158
|
+
self,
|
|
159
|
+
assistant_role_names_path: str = "data/ai_society/assistant_roles.txt",
|
|
160
|
+
user_role_names_path: str = "data/ai_society/user_roles.txt",
|
|
161
|
+
assistant_role_names: Optional[List[str]] = None,
|
|
162
|
+
user_role_names: Optional[List[str]] = None,
|
|
163
|
+
) -> None:
|
|
153
164
|
if assistant_role_names is None:
|
|
154
165
|
with open(assistant_role_names_path, "r") as f:
|
|
155
166
|
assistant_role_names_: List[str] = f.read().splitlines()
|
|
@@ -176,13 +187,15 @@ class RoleNameGenerator:
|
|
|
176
187
|
|
|
177
188
|
|
|
178
189
|
class AISocietyTaskPromptGenerator:
|
|
179
|
-
|
|
180
190
|
def __init__(
|
|
181
191
|
self,
|
|
182
192
|
num_tasks: int = 10,
|
|
183
193
|
) -> None:
|
|
184
|
-
self.generate_tasks_prompt =
|
|
185
|
-
|
|
194
|
+
self.generate_tasks_prompt = (
|
|
195
|
+
PromptTemplateGenerator().get_generate_tasks_prompt(
|
|
196
|
+
TaskType.AI_SOCIETY
|
|
197
|
+
)
|
|
198
|
+
)
|
|
186
199
|
|
|
187
200
|
self.num_tasks = num_tasks
|
|
188
201
|
|
|
@@ -190,14 +203,17 @@ class AISocietyTaskPromptGenerator:
|
|
|
190
203
|
def from_role_files(
|
|
191
204
|
self,
|
|
192
205
|
assistant_role_names_path: str = "data/ai_society/assistant_roles.txt",
|
|
193
|
-
user_role_names_path: str = "data/ai_society/user_roles.txt"
|
|
206
|
+
user_role_names_path: str = "data/ai_society/user_roles.txt",
|
|
194
207
|
) -> Generator[Tuple[str, Tuple[str, str]], None, None]:
|
|
195
208
|
roles_generator = RoleNameGenerator(
|
|
196
|
-
assistant_role_names_path, user_role_names_path
|
|
209
|
+
assistant_role_names_path, user_role_names_path
|
|
210
|
+
).from_role_files()
|
|
197
211
|
for role_1, role_2 in roles_generator:
|
|
198
212
|
generate_tasks_prompt = self.generate_tasks_prompt.format(
|
|
199
|
-
assistant_role=role_1,
|
|
200
|
-
|
|
213
|
+
assistant_role=role_1,
|
|
214
|
+
user_role=role_2,
|
|
215
|
+
num_tasks=self.num_tasks,
|
|
216
|
+
)
|
|
201
217
|
|
|
202
218
|
yield (generate_tasks_prompt, (role_1, role_2))
|
|
203
219
|
|
|
@@ -206,19 +222,19 @@ class AISocietyTaskPromptGenerator:
|
|
|
206
222
|
) -> Generator[Tuple[str, Tuple[str, str]], None, None]:
|
|
207
223
|
for role_1, role_2 in role_generator:
|
|
208
224
|
generate_tasks_prompt = self.generate_tasks_prompt.format(
|
|
209
|
-
assistant_role=role_1,
|
|
210
|
-
|
|
225
|
+
assistant_role=role_1,
|
|
226
|
+
user_role=role_2,
|
|
227
|
+
num_tasks=self.num_tasks,
|
|
228
|
+
)
|
|
211
229
|
|
|
212
230
|
yield (generate_tasks_prompt, (role_1, role_2))
|
|
213
231
|
|
|
214
232
|
|
|
215
233
|
class SingleTxtGenerator:
|
|
216
|
-
|
|
217
234
|
def __init__(
|
|
218
235
|
self,
|
|
219
236
|
text_file_path: str,
|
|
220
237
|
) -> None:
|
|
221
|
-
|
|
222
238
|
with open(text_file_path, "r") as f:
|
|
223
239
|
data_list: List[str] = f.read().splitlines()
|
|
224
240
|
self.data_list = [
|
|
@@ -231,30 +247,33 @@ class SingleTxtGenerator:
|
|
|
231
247
|
|
|
232
248
|
|
|
233
249
|
class CodeTaskPromptGenerator:
|
|
234
|
-
|
|
235
250
|
def __init__(
|
|
236
251
|
self,
|
|
237
252
|
num_tasks: int = 50,
|
|
238
253
|
) -> None:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
254
|
+
self.generate_tasks_prompt = (
|
|
255
|
+
PromptTemplateGenerator().get_generate_tasks_prompt(TaskType.CODE)
|
|
256
|
+
)
|
|
242
257
|
|
|
243
258
|
self.num_tasks = num_tasks
|
|
244
259
|
|
|
245
260
|
def from_role_files(
|
|
246
|
-
self,
|
|
247
|
-
|
|
261
|
+
self,
|
|
262
|
+
languages_path: str = "data/code/languages.txt",
|
|
263
|
+
domains_path: str = "data/code/domains.txt",
|
|
248
264
|
) -> Generator[Tuple[TextPrompt, str, str], None, None]:
|
|
249
265
|
language_generator = SingleTxtGenerator(
|
|
250
|
-
languages_path
|
|
266
|
+
languages_path
|
|
267
|
+
).from_role_files()
|
|
251
268
|
|
|
252
269
|
for language in language_generator:
|
|
253
270
|
domains_generator = SingleTxtGenerator(
|
|
254
|
-
domains_path
|
|
271
|
+
domains_path
|
|
272
|
+
).from_role_files()
|
|
255
273
|
for domain in domains_generator:
|
|
256
274
|
generated_tasks_prompt = self.generate_tasks_prompt.format(
|
|
257
|
-
language=language, domain=domain, num_tasks=self.num_tasks
|
|
275
|
+
language=language, domain=domain, num_tasks=self.num_tasks
|
|
276
|
+
)
|
|
258
277
|
yield generated_tasks_prompt, language, domain
|
|
259
278
|
|
|
260
279
|
def from_role_generator(
|
camel/human.py
CHANGED
|
@@ -39,8 +39,11 @@ class Human:
|
|
|
39
39
|
displayed to the user.
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
|
-
def __init__(
|
|
43
|
-
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
name: str = "Kill Switch Engineer",
|
|
45
|
+
logger_color: Any = Fore.MAGENTA,
|
|
46
|
+
) -> None:
|
|
44
47
|
self.name = name
|
|
45
48
|
self.logger_color = logger_color
|
|
46
49
|
self.input_button = f"Input by {self.name}."
|
|
@@ -62,11 +65,13 @@ class Human:
|
|
|
62
65
|
print_text_animated(
|
|
63
66
|
self.logger_color + "\n> Proposals from "
|
|
64
67
|
f"{messages[0].role_name} ({messages[0].role_type}). "
|
|
65
|
-
"Please choose an option:\n"
|
|
68
|
+
"Please choose an option:\n"
|
|
69
|
+
)
|
|
66
70
|
for index, option in enumerate(options):
|
|
67
71
|
print_text_animated(
|
|
68
|
-
self.logger_color
|
|
69
|
-
f"\x1b[3mOption {index + 1}:\n{option}\x1b[0m\n"
|
|
72
|
+
self.logger_color
|
|
73
|
+
+ f"\x1b[3mOption {index + 1}:\n{option}\x1b[0m\n"
|
|
74
|
+
)
|
|
70
75
|
self.options_dict[str(index + 1)] = option
|
|
71
76
|
|
|
72
77
|
def get_input(self) -> str:
|
|
@@ -77,13 +82,15 @@ class Human:
|
|
|
77
82
|
"""
|
|
78
83
|
while True:
|
|
79
84
|
human_input = input(
|
|
80
|
-
self.logger_color
|
|
81
|
-
f"Please enter your choice ([1-{len(self.options_dict)}]): "
|
|
85
|
+
self.logger_color
|
|
86
|
+
+ f"Please enter your choice ([1-{len(self.options_dict)}]): "
|
|
87
|
+
)
|
|
82
88
|
print("\n")
|
|
83
89
|
if human_input in self.options_dict:
|
|
84
90
|
break
|
|
85
|
-
print_text_animated(
|
|
86
|
-
|
|
91
|
+
print_text_animated(
|
|
92
|
+
self.logger_color + "\n> Invalid choice. Please try again.\n"
|
|
93
|
+
)
|
|
87
94
|
|
|
88
95
|
return human_input
|
|
89
96
|
|
|
@@ -105,8 +112,7 @@ class Human:
|
|
|
105
112
|
|
|
106
113
|
return content
|
|
107
114
|
|
|
108
|
-
def reduce_step(self,
|
|
109
|
-
messages: Sequence[BaseMessage]) -> ChatAgentResponse:
|
|
115
|
+
def reduce_step(self, messages: Sequence[BaseMessage]) -> ChatAgentResponse:
|
|
110
116
|
r"""Performs one step of the conversation by displaying options to the
|
|
111
117
|
user, getting their input, and parsing their choice.
|
|
112
118
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the “License”);
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an “AS IS” BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
|
+
|
|
15
|
+
from .base import BaseInterpreter
|
|
16
|
+
from .internal_python_interpreter import InternalPythonInterpreter
|
|
17
|
+
from .interpreter_error import InterpreterError
|
|
18
|
+
from .subprocess_interpreter import SubprocessInterpreter
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
'BaseInterpreter',
|
|
22
|
+
'InterpreterError',
|
|
23
|
+
'InternalPythonInterpreter',
|
|
24
|
+
'SubprocessInterpreter',
|
|
25
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the “License”);
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an “AS IS” BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from typing import Any, Dict, List
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BaseInterpreter(ABC):
|
|
19
|
+
r"""An abstract base class for code interpreters."""
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def run(self, code: str, code_type: str) -> str:
|
|
23
|
+
r"""Executes the given code based on its type.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
code (str): The code to be executed.
|
|
27
|
+
code_type (str): The type of the code, which must be one of the
|
|
28
|
+
types returned by `supported_code_types()`.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
str: The result of the code execution. If the execution fails, this
|
|
32
|
+
should include sufficient information to diagnose and correct
|
|
33
|
+
the issue.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
InterpreterError: If the code execution encounters errors that
|
|
37
|
+
could be resolved by modifying or regenerating the code.
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def supported_code_types(self) -> List[str]:
|
|
43
|
+
r"""Provides supported code types by the interpreter."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def update_action_space(self, action_space: Dict[str, Any]) -> None:
|
|
48
|
+
r"""Updates action space for *python* interpreter"""
|
|
49
|
+
pass
|
|
@@ -15,22 +15,17 @@ import ast
|
|
|
15
15
|
import difflib
|
|
16
16
|
import importlib
|
|
17
17
|
import typing
|
|
18
|
-
from typing import Any, Dict, List, Optional
|
|
18
|
+
from typing import Any, ClassVar, Dict, List, Optional
|
|
19
19
|
|
|
20
|
+
from camel.interpreters.base import BaseInterpreter
|
|
21
|
+
from camel.interpreters.interpreter_error import InterpreterError
|
|
20
22
|
|
|
21
|
-
class InterpreterError(ValueError):
|
|
22
|
-
r"""An error raised when the interpreter cannot evaluate a Python
|
|
23
|
-
expression, due to syntax error or unsupported operations.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
pass
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
class PythonInterpreter():
|
|
24
|
+
class InternalPythonInterpreter(BaseInterpreter):
|
|
30
25
|
r"""A customized python interpreter to control the execution of
|
|
31
26
|
LLM-generated codes. The interpreter makes sure the code can only execute
|
|
32
27
|
functions given in action space and import white list. It also supports
|
|
33
|
-
fuzzy variable matching to
|
|
28
|
+
fuzzy variable matching to retrieve uncertain input variable name.
|
|
34
29
|
|
|
35
30
|
.. highlight:: none
|
|
36
31
|
|
|
@@ -64,40 +59,101 @@ class PythonInterpreter():
|
|
|
64
59
|
Modifications copyright (C) 2023 CAMEL-AI.org
|
|
65
60
|
|
|
66
61
|
Args:
|
|
67
|
-
action_space (Dict[str, Any]): A dictionary that maps action
|
|
68
|
-
their corresponding functions or objects. The interpreter
|
|
69
|
-
execute functions that are either directly listed in this
|
|
62
|
+
action_space (Dict[str, Any], optional): A dictionary that maps action
|
|
63
|
+
names to their corresponding functions or objects. The interpreter
|
|
64
|
+
can only execute functions that are either directly listed in this
|
|
70
65
|
dictionary or are member functions of objects listed in this
|
|
71
66
|
dictionary. The concept of :obj:`action_space` is derived from
|
|
72
67
|
EmbodiedAgent, representing the actions that an agent is capable of
|
|
73
|
-
performing.
|
|
74
|
-
import_white_list (
|
|
68
|
+
performing. If `None`, set to empty dict. (default: :obj:`None`)
|
|
69
|
+
import_white_list (List[str], optional): A list that stores
|
|
75
70
|
the Python modules or functions that can be imported in the code.
|
|
76
71
|
All submodules and functions of the modules listed in this list are
|
|
77
72
|
importable. Any other import statements will be rejected. The
|
|
78
73
|
module and its submodule or function name are separated by a period
|
|
79
74
|
(:obj:`.`). (default: :obj:`None`)
|
|
75
|
+
unsafe_mode (bool, optional): If `True`, the interpreter runs the code
|
|
76
|
+
by `eval()` without any security check. (default: :obj:`False`)
|
|
77
|
+
raise_error (bool, optional): Raise error if the interpreter fails.
|
|
78
|
+
(default: :obj:`False`)
|
|
80
79
|
"""
|
|
81
80
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
_CODE_TYPES: ClassVar[List[str]] = ["python", "py", "python3", "python2"]
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
action_space: Optional[Dict[str, Any]] = None,
|
|
86
|
+
import_white_list: Optional[List[str]] = None,
|
|
87
|
+
unsafe_mode: bool = False,
|
|
88
|
+
raise_error: bool = False,
|
|
89
|
+
) -> None:
|
|
90
|
+
self.action_space = action_space or dict()
|
|
85
91
|
self.state = self.action_space.copy()
|
|
86
|
-
self.fuzz_state: Dict[str, Any] =
|
|
87
|
-
self.import_white_list = import_white_list or
|
|
92
|
+
self.fuzz_state: Dict[str, Any] = dict()
|
|
93
|
+
self.import_white_list = import_white_list or list()
|
|
94
|
+
self.raise_error = raise_error
|
|
95
|
+
self.unsafe_mode = unsafe_mode
|
|
96
|
+
|
|
97
|
+
def run(self, code: str, code_type: str) -> str:
|
|
98
|
+
r"""Executes the given code with specified code type in the
|
|
99
|
+
interpreter.
|
|
100
|
+
|
|
101
|
+
This method takes a string of code and its type, checks if the code
|
|
102
|
+
type is supported, and then executes the code. If `unsafe_mode` is
|
|
103
|
+
set to `False`, the code is executed in a controlled environment using
|
|
104
|
+
the `execute` method. If `unsafe_mode` is `True`, the code is executed
|
|
105
|
+
using `eval()` with the action space as the global context. An
|
|
106
|
+
`InterpreterError` is raised if the code type is unsupported or if any
|
|
107
|
+
runtime error occurs during execution.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
code (str): The python code to be executed.
|
|
111
|
+
code_type (str): The type of the code, which should be one of the
|
|
112
|
+
supported code types (`python`, `py`, `python3`, `python2`).
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
str: The string representation of the output of the executed code.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
InterpreterError: If the `code_type` is not supported or if any
|
|
120
|
+
runtime error occurs during the execution of the code.
|
|
121
|
+
"""
|
|
122
|
+
if code_type not in self._CODE_TYPES:
|
|
123
|
+
raise InterpreterError(
|
|
124
|
+
f"Unsupported code type {code_type}. "
|
|
125
|
+
f"`{self.__class__.__name__}` only supports "
|
|
126
|
+
f"{', '.join(self._CODE_TYPES)}."
|
|
127
|
+
)
|
|
128
|
+
if not self.unsafe_mode:
|
|
129
|
+
return str(self.execute(code))
|
|
130
|
+
else:
|
|
131
|
+
return str(eval(code, self.action_space))
|
|
132
|
+
|
|
133
|
+
def update_action_space(self, action_space: Dict[str, Any]) -> None:
|
|
134
|
+
r"""Updates action space for *python* interpreter."""
|
|
135
|
+
self.action_space.update(action_space)
|
|
136
|
+
|
|
137
|
+
def supported_code_types(self) -> List[str]:
|
|
138
|
+
r"""Provides supported code types by the interpreter."""
|
|
139
|
+
return self._CODE_TYPES
|
|
88
140
|
|
|
89
|
-
def execute(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
141
|
+
def execute(
|
|
142
|
+
self,
|
|
143
|
+
code: str,
|
|
144
|
+
state: Optional[Dict[str, Any]] = None,
|
|
145
|
+
fuzz_state: Optional[Dict[str, Any]] = None,
|
|
146
|
+
keep_state: bool = True,
|
|
147
|
+
) -> Any:
|
|
148
|
+
r"""Execute the input python codes in a security environment.
|
|
93
149
|
|
|
94
150
|
Args:
|
|
95
151
|
code (str): Generated python code to be executed.
|
|
96
152
|
state (Optional[Dict[str, Any]], optional): External variables that
|
|
97
153
|
may be used in the generated code. (default: :obj:`None`)
|
|
98
|
-
fuzz_state (Optional[Dict[str, Any]], optional): External
|
|
99
|
-
that do not have certain
|
|
100
|
-
use fuzzy matching to access these
|
|
154
|
+
fuzz_state (Optional[Dict[str, Any]], optional): External variables
|
|
155
|
+
that do not have certain variable names. The interpreter will
|
|
156
|
+
use fuzzy matching to access these variables. For example, if
|
|
101
157
|
:obj:`fuzz_state` has a variable :obj:`image`, the generated
|
|
102
158
|
code can use :obj:`input_image` to access it. (default:
|
|
103
159
|
:obj:`None`)
|
|
@@ -120,7 +176,12 @@ class PythonInterpreter():
|
|
|
120
176
|
try:
|
|
121
177
|
expression = ast.parse(code)
|
|
122
178
|
except SyntaxError as e:
|
|
123
|
-
|
|
179
|
+
if self.raise_error:
|
|
180
|
+
raise InterpreterError(f"Syntax error in code: {e}")
|
|
181
|
+
else:
|
|
182
|
+
import traceback
|
|
183
|
+
|
|
184
|
+
return traceback.format_exc()
|
|
124
185
|
|
|
125
186
|
result = None
|
|
126
187
|
for idx, node in enumerate(expression.body):
|
|
@@ -129,11 +190,18 @@ class PythonInterpreter():
|
|
|
129
190
|
except InterpreterError as e:
|
|
130
191
|
if not keep_state:
|
|
131
192
|
self.clear_state()
|
|
132
|
-
msg = (
|
|
133
|
-
|
|
193
|
+
msg = (
|
|
194
|
+
f"Evaluation of the code stopped at node {idx}. "
|
|
195
|
+
f"See:\n{e}"
|
|
196
|
+
)
|
|
134
197
|
# More information can be provided by `ast.unparse()`,
|
|
135
198
|
# which is new in python 3.9.
|
|
136
|
-
|
|
199
|
+
if self.raise_error:
|
|
200
|
+
raise InterpreterError(msg)
|
|
201
|
+
else:
|
|
202
|
+
import traceback
|
|
203
|
+
|
|
204
|
+
return traceback.format_exc()
|
|
137
205
|
if line_result is not None:
|
|
138
206
|
result = line_result
|
|
139
207
|
|
|
@@ -143,7 +211,7 @@ class PythonInterpreter():
|
|
|
143
211
|
return result
|
|
144
212
|
|
|
145
213
|
def clear_state(self) -> None:
|
|
146
|
-
r"""Initialize :obj:`state` and :obj:`fuzz_state
|
|
214
|
+
r"""Initialize :obj:`state` and :obj:`fuzz_state`."""
|
|
147
215
|
self.state = self.action_space.copy()
|
|
148
216
|
self.fuzz_state = {}
|
|
149
217
|
|
|
@@ -204,7 +272,8 @@ class PythonInterpreter():
|
|
|
204
272
|
return self._execute_ast(expression.value)
|
|
205
273
|
elif isinstance(expression, ast.JoinedStr):
|
|
206
274
|
return "".join(
|
|
207
|
-
[str(self._execute_ast(v)) for v in expression.values]
|
|
275
|
+
[str(self._execute_ast(v)) for v in expression.values]
|
|
276
|
+
)
|
|
208
277
|
elif isinstance(expression, ast.List):
|
|
209
278
|
# List -> evaluate all elements
|
|
210
279
|
return [self._execute_ast(elt) for elt in expression.elts]
|
|
@@ -223,7 +292,8 @@ class PythonInterpreter():
|
|
|
223
292
|
# For now we refuse anything else. Let's add things as we need
|
|
224
293
|
# them.
|
|
225
294
|
raise InterpreterError(
|
|
226
|
-
f"{expression.__class__.__name__} is not supported."
|
|
295
|
+
f"{expression.__class__.__name__} is not supported."
|
|
296
|
+
)
|
|
227
297
|
|
|
228
298
|
def _execute_assign(self, assign: ast.Assign) -> Any:
|
|
229
299
|
targets = assign.targets
|
|
@@ -238,18 +308,23 @@ class PythonInterpreter():
|
|
|
238
308
|
self.state[target.id] = value
|
|
239
309
|
elif isinstance(target, ast.Tuple):
|
|
240
310
|
if not isinstance(value, tuple):
|
|
241
|
-
raise InterpreterError(
|
|
242
|
-
|
|
311
|
+
raise InterpreterError(
|
|
312
|
+
f"Expected type tuple, but got"
|
|
313
|
+
f"{value.__class__.__name__} instead."
|
|
314
|
+
)
|
|
243
315
|
if len(target.elts) != len(value):
|
|
244
316
|
raise InterpreterError(
|
|
245
317
|
f"Expected {len(target.elts)} values but got"
|
|
246
|
-
f" {len(value)}."
|
|
318
|
+
f" {len(value)}."
|
|
319
|
+
)
|
|
247
320
|
for t, v in zip(target.elts, value):
|
|
248
321
|
self.state[self._execute_ast(t)] = v
|
|
249
322
|
else:
|
|
250
|
-
raise InterpreterError(
|
|
251
|
-
|
|
252
|
-
|
|
323
|
+
raise InterpreterError(
|
|
324
|
+
f"Unsupported variable type. Expected "
|
|
325
|
+
f"ast.Name or ast.Tuple, got "
|
|
326
|
+
f"{target.__class__.__name__} instead."
|
|
327
|
+
)
|
|
253
328
|
|
|
254
329
|
def _execute_call(self, call: ast.Call) -> Any:
|
|
255
330
|
callable_func = self._execute_ast(call.func)
|
|
@@ -268,7 +343,8 @@ class PythonInterpreter():
|
|
|
268
343
|
if not isinstance(subscript.ctx, ast.Load):
|
|
269
344
|
raise InterpreterError(
|
|
270
345
|
f"{subscript.ctx.__class__.__name__} is not supported for "
|
|
271
|
-
"subscript."
|
|
346
|
+
"subscript."
|
|
347
|
+
)
|
|
272
348
|
if isinstance(value, (list, tuple)):
|
|
273
349
|
return value[int(index)]
|
|
274
350
|
if index in value:
|
|
@@ -294,7 +370,8 @@ class PythonInterpreter():
|
|
|
294
370
|
def _execute_condition(self, condition: ast.Compare):
|
|
295
371
|
if len(condition.ops) > 1:
|
|
296
372
|
raise InterpreterError(
|
|
297
|
-
"Cannot evaluate conditions with multiple operators"
|
|
373
|
+
"Cannot evaluate conditions with multiple operators"
|
|
374
|
+
)
|
|
298
375
|
|
|
299
376
|
left = self._execute_ast(condition.left)
|
|
300
377
|
comparator = condition.ops[0]
|
|
@@ -328,7 +405,8 @@ class PythonInterpreter():
|
|
|
328
405
|
if not isinstance(if_statement.test, ast.Compare):
|
|
329
406
|
raise InterpreterError(
|
|
330
407
|
"Only Campare expr supported in if statement, get"
|
|
331
|
-
f" {if_statement.test.__class__.__name__}"
|
|
408
|
+
f" {if_statement.test.__class__.__name__}"
|
|
409
|
+
)
|
|
332
410
|
if self._execute_condition(if_statement.test):
|
|
333
411
|
for line in if_statement.body:
|
|
334
412
|
line_result = self._execute_ast(line)
|
|
@@ -378,9 +456,11 @@ class PythonInterpreter():
|
|
|
378
456
|
return
|
|
379
457
|
|
|
380
458
|
if not found_name:
|
|
381
|
-
raise InterpreterError(
|
|
382
|
-
|
|
383
|
-
|
|
459
|
+
raise InterpreterError(
|
|
460
|
+
f"It is not permitted to import modules "
|
|
461
|
+
f"than module white list (try to import "
|
|
462
|
+
f"{full_name})."
|
|
463
|
+
)
|
|
384
464
|
|
|
385
465
|
def _execute_binop(self, binop: ast.BinOp):
|
|
386
466
|
left = self._execute_ast(binop.left)
|
|
@@ -427,8 +507,9 @@ class PythonInterpreter():
|
|
|
427
507
|
if key in self.state:
|
|
428
508
|
return self.state[key]
|
|
429
509
|
else:
|
|
430
|
-
close_matches =
|
|
431
|
-
key, list(self.fuzz_state.keys()), n=1
|
|
510
|
+
close_matches = difflib.get_close_matches(
|
|
511
|
+
key, list(self.fuzz_state.keys()), n=1
|
|
512
|
+
)
|
|
432
513
|
if close_matches:
|
|
433
514
|
return self.fuzz_state[close_matches[0]]
|
|
434
515
|
else:
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the “License”);
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an “AS IS” BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
|
+
|
|
15
|
+
# TODO: Do we need a file to store this error class?
|
|
16
|
+
class InterpreterError(Exception):
|
|
17
|
+
r"""Exception raised for errors that can be solved by regenerating code"""
|
|
18
|
+
|
|
19
|
+
pass
|