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.

Files changed (99) hide show
  1. camel/__init__.py +1 -11
  2. camel/agents/__init__.py +5 -5
  3. camel/agents/chat_agent.py +124 -63
  4. camel/agents/critic_agent.py +28 -17
  5. camel/agents/deductive_reasoner_agent.py +235 -0
  6. camel/agents/embodied_agent.py +92 -40
  7. camel/agents/role_assignment_agent.py +27 -17
  8. camel/agents/task_agent.py +60 -34
  9. camel/agents/tool_agents/base.py +0 -1
  10. camel/agents/tool_agents/hugging_face_tool_agent.py +7 -4
  11. camel/configs.py +119 -7
  12. camel/embeddings/__init__.py +2 -0
  13. camel/embeddings/base.py +3 -2
  14. camel/embeddings/openai_embedding.py +3 -3
  15. camel/embeddings/sentence_transformers_embeddings.py +65 -0
  16. camel/functions/__init__.py +13 -3
  17. camel/functions/google_maps_function.py +335 -0
  18. camel/functions/math_functions.py +7 -7
  19. camel/functions/openai_function.py +344 -42
  20. camel/functions/search_functions.py +100 -35
  21. camel/functions/twitter_function.py +484 -0
  22. camel/functions/weather_functions.py +36 -23
  23. camel/generators.py +65 -46
  24. camel/human.py +17 -11
  25. camel/interpreters/__init__.py +25 -0
  26. camel/interpreters/base.py +49 -0
  27. camel/{utils/python_interpreter.py → interpreters/internal_python_interpreter.py} +129 -48
  28. camel/interpreters/interpreter_error.py +19 -0
  29. camel/interpreters/subprocess_interpreter.py +190 -0
  30. camel/loaders/__init__.py +22 -0
  31. camel/{functions/base_io_functions.py → loaders/base_io.py} +38 -35
  32. camel/{functions/unstructured_io_fuctions.py → loaders/unstructured_io.py} +199 -110
  33. camel/memories/__init__.py +17 -7
  34. camel/memories/agent_memories.py +156 -0
  35. camel/memories/base.py +97 -32
  36. camel/memories/blocks/__init__.py +21 -0
  37. camel/memories/{chat_history_memory.py → blocks/chat_history_block.py} +34 -34
  38. camel/memories/blocks/vectordb_block.py +101 -0
  39. camel/memories/context_creators/__init__.py +3 -2
  40. camel/memories/context_creators/score_based.py +32 -20
  41. camel/memories/records.py +6 -5
  42. camel/messages/__init__.py +2 -2
  43. camel/messages/base.py +99 -16
  44. camel/messages/func_message.py +7 -4
  45. camel/models/__init__.py +4 -2
  46. camel/models/anthropic_model.py +132 -0
  47. camel/models/base_model.py +3 -2
  48. camel/models/model_factory.py +10 -8
  49. camel/models/open_source_model.py +25 -13
  50. camel/models/openai_model.py +9 -10
  51. camel/models/stub_model.py +6 -5
  52. camel/prompts/__init__.py +7 -5
  53. camel/prompts/ai_society.py +21 -14
  54. camel/prompts/base.py +54 -47
  55. camel/prompts/code.py +22 -14
  56. camel/prompts/evaluation.py +8 -5
  57. camel/prompts/misalignment.py +26 -19
  58. camel/prompts/object_recognition.py +35 -0
  59. camel/prompts/prompt_templates.py +14 -8
  60. camel/prompts/role_description_prompt_template.py +16 -10
  61. camel/prompts/solution_extraction.py +9 -5
  62. camel/prompts/task_prompt_template.py +24 -21
  63. camel/prompts/translation.py +9 -5
  64. camel/responses/agent_responses.py +5 -2
  65. camel/retrievers/__init__.py +24 -0
  66. camel/retrievers/auto_retriever.py +319 -0
  67. camel/retrievers/base.py +64 -0
  68. camel/retrievers/bm25_retriever.py +149 -0
  69. camel/retrievers/vector_retriever.py +166 -0
  70. camel/societies/__init__.py +1 -1
  71. camel/societies/babyagi_playing.py +56 -32
  72. camel/societies/role_playing.py +188 -133
  73. camel/storages/__init__.py +18 -0
  74. camel/storages/graph_storages/__init__.py +23 -0
  75. camel/storages/graph_storages/base.py +82 -0
  76. camel/storages/graph_storages/graph_element.py +74 -0
  77. camel/storages/graph_storages/neo4j_graph.py +582 -0
  78. camel/storages/key_value_storages/base.py +1 -2
  79. camel/storages/key_value_storages/in_memory.py +1 -2
  80. camel/storages/key_value_storages/json.py +8 -13
  81. camel/storages/vectordb_storages/__init__.py +33 -0
  82. camel/storages/vectordb_storages/base.py +202 -0
  83. camel/storages/vectordb_storages/milvus.py +396 -0
  84. camel/storages/vectordb_storages/qdrant.py +371 -0
  85. camel/terminators/__init__.py +1 -1
  86. camel/terminators/base.py +2 -3
  87. camel/terminators/response_terminator.py +21 -12
  88. camel/terminators/token_limit_terminator.py +5 -3
  89. camel/types/__init__.py +12 -6
  90. camel/types/enums.py +86 -13
  91. camel/types/openai_types.py +10 -5
  92. camel/utils/__init__.py +18 -13
  93. camel/utils/commons.py +242 -81
  94. camel/utils/token_counting.py +135 -15
  95. {camel_ai-0.1.1.dist-info → camel_ai-0.1.3.dist-info}/METADATA +116 -74
  96. camel_ai-0.1.3.dist-info/RECORD +101 -0
  97. {camel_ai-0.1.1.dist-info → camel_ai-0.1.3.dist-info}/WHEEL +1 -1
  98. camel/memories/context_creators/base.py +0 -72
  99. 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 = PromptTemplateGenerator(
47
- ).get_system_prompt(
48
- task_type,
49
- RoleType.ASSISTANT,
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 = PromptTemplateGenerator(
56
- ).get_system_prompt(
57
- task_type,
58
- RoleType.CRITIC,
56
+ critic_prompt_template = (
57
+ PromptTemplateGenerator().get_system_prompt(
58
+ task_type,
59
+ RoleType.CRITIC,
60
+ )
59
61
  )
60
- embodiment_prompt_template = PromptTemplateGenerator(
61
- ).get_system_prompt(
62
- task_type,
63
- RoleType.EMBODIMENT,
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("The keys of the meta_dict should be in "
89
- f"{self.sys_msg_meta_dict_keys}. "
90
- f"Got {set(meta_dict.keys())} instead.")
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(role_name=role_name, role_type=role_type,
113
- meta_dict=meta_dict, content=sys_prompt)
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
- def __init__(self, assistant_role_names_path:
148
- str = "data/ai_society/assistant_roles.txt",
149
- user_role_names_path: str = "data/ai_society/user_roles.txt",
150
- assistant_role_names: Optional[List[str]] = None,
151
- user_role_names: Optional[List[str]] = None) -> None:
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 = PromptTemplateGenerator(
185
- ).get_generate_tasks_prompt(TaskType.AI_SOCIETY)
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).from_role_files()
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, user_role=role_2,
200
- num_tasks=self.num_tasks)
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, user_role=role_2,
210
- num_tasks=self.num_tasks)
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
- self.generate_tasks_prompt = PromptTemplateGenerator(
241
- ).get_generate_tasks_prompt(TaskType.CODE)
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, languages_path: str = "data/code/languages.txt",
247
- domains_path: str = "data/code/domains.txt"
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).from_role_files()
266
+ languages_path
267
+ ).from_role_files()
251
268
 
252
269
  for language in language_generator:
253
270
  domains_generator = SingleTxtGenerator(
254
- domains_path).from_role_files()
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__(self, name: str = "Kill Switch Engineer",
43
- logger_color: Any = Fore.MAGENTA) -> None:
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(self.logger_color +
86
- "\n> Invalid choice. Please try again.\n")
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 reveive uncertain input variable name.
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 names to
68
- their corresponding functions or objects. The interpreter can only
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 (Optional[List[str]], optional): A list that stores
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
- def __init__(self, action_space: Dict[str, Any],
83
- import_white_list: Optional[List[str]] = None) -> None:
84
- self.action_space = action_space
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(self, code: str, state: Optional[Dict[str, Any]] = None,
90
- fuzz_state: Optional[Dict[str, Any]] = None,
91
- keep_state: bool = True) -> Any:
92
- r""" Execute the input python codes in a security environment.
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 varibles
99
- that do not have certain varible names. The interpreter will
100
- use fuzzy matching to access these varibales. For example, if
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
- raise InterpreterError(f"Syntax error in code: {e}")
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 = (f"Evaluation of the code stopped at node {idx}. "
133
- f"See:\n{e}")
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
- raise InterpreterError(msg)
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(f"Expected type tuple, but got"
242
- f"{value.__class__.__name__} instead.")
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(f"Unsupported variable type. Expected "
251
- f"ast.Name or ast.Tuple, got "
252
- f"{target.__class__.__name__} instead.")
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(f"It is not permitted to import modules "
382
- f"than module white list (try to import "
383
- f"{full_name}).")
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 = (difflib.get_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