camel-ai 0.2.51__py3-none-any.whl → 0.2.53__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 CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  from camel.logger import disable_logging, enable_logging, set_log_level
16
16
 
17
- __version__ = '0.2.51'
17
+ __version__ = '0.2.53'
18
18
 
19
19
  __all__ = [
20
20
  '__version__',
camel/agents/__init__.py CHANGED
@@ -16,6 +16,7 @@ from .chat_agent import ChatAgent
16
16
  from .critic_agent import CriticAgent
17
17
  from .embodied_agent import EmbodiedAgent
18
18
  from .knowledge_graph_agent import KnowledgeGraphAgent
19
+ from .mcp_agent import MCPAgent
19
20
  from .repo_agent import RepoAgent
20
21
  from .role_assignment_agent import RoleAssignmentAgent
21
22
  from .search_agent import SearchAgent
@@ -42,5 +43,6 @@ __all__ = [
42
43
  'RoleAssignmentAgent',
43
44
  'SearchAgent',
44
45
  'KnowledgeGraphAgent',
46
+ 'MCPAgent',
45
47
  'RepoAgent',
46
48
  ]
@@ -16,6 +16,7 @@ from __future__ import annotations
16
16
  import json
17
17
  import logging
18
18
  import textwrap
19
+ import threading
19
20
  import uuid
20
21
  from collections import defaultdict
21
22
  from datetime import datetime
@@ -151,6 +152,9 @@ class ChatAgent(BaseAgent):
151
152
  model calling at each step. (default: :obj:`False`)
152
153
  agent_id (str, optional): The ID of the agent. If not provided, a
153
154
  random UUID will be generated. (default: :obj:`None`)
155
+ stop_event (Optional[threading.Event], optional): Event to signal
156
+ termination of the agent's operation. When set, the agent will
157
+ terminate its execution. (default: :obj:`None`)
154
158
  """
155
159
 
156
160
  def __init__(
@@ -182,6 +186,7 @@ class ChatAgent(BaseAgent):
182
186
  scheduling_strategy: str = "round_robin",
183
187
  single_iteration: bool = False,
184
188
  agent_id: Optional[str] = None,
189
+ stop_event: Optional[threading.Event] = None,
185
190
  ) -> None:
186
191
  # Resolve model backends and set up model manager
187
192
  resolved_models = self._resolve_models(model)
@@ -252,6 +257,7 @@ class ChatAgent(BaseAgent):
252
257
  self.terminated = False
253
258
  self.response_terminators = response_terminators or []
254
259
  self.single_iteration = single_iteration
260
+ self.stop_event = stop_event
255
261
 
256
262
  def reset(self):
257
263
  r"""Resets the :obj:`ChatAgent` to its initial state."""
@@ -757,6 +763,13 @@ class ChatAgent(BaseAgent):
757
763
  self._get_full_tool_schemas(),
758
764
  )
759
765
 
766
+ # Terminate Agent if stop_event is set
767
+ if self.stop_event and self.stop_event.is_set():
768
+ # Use the _step_token_exceed to terminate the agent with reason
769
+ return self._step_token_exceed(
770
+ num_tokens, tool_call_records, "termination_triggered"
771
+ )
772
+
760
773
  if tool_call_requests := response.tool_call_requests:
761
774
  # Process all tool calls
762
775
  for tool_call_request in tool_call_requests:
@@ -849,6 +862,13 @@ class ChatAgent(BaseAgent):
849
862
  self._get_full_tool_schemas(),
850
863
  )
851
864
 
865
+ # Terminate Agent if stop_event is set
866
+ if self.stop_event and self.stop_event.is_set():
867
+ # Use the _step_token_exceed to terminate the agent with reason
868
+ return self._step_token_exceed(
869
+ num_tokens, tool_call_records, "termination_triggered"
870
+ )
871
+
852
872
  if tool_call_requests := response.tool_call_requests:
853
873
  # Process all tool calls
854
874
  for tool_call_request in tool_call_requests:
@@ -0,0 +1,233 @@
1
+ # ========= Copyright 2023-2024 @ 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-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+
15
+ import json
16
+ import re
17
+ from typing import Optional
18
+
19
+ from camel.agents import ChatAgent
20
+ from camel.logger import get_logger
21
+ from camel.messages import BaseMessage
22
+ from camel.models import BaseModelBackend
23
+ from camel.prompts import TextPrompt
24
+ from camel.responses import ChatAgentResponse
25
+ from camel.toolkits import MCPToolkit
26
+ from camel.types import RoleType
27
+
28
+ # AgentOps decorator setting
29
+ try:
30
+ import os
31
+
32
+ if os.getenv("AGENTOPS_API_KEY") is not None:
33
+ from agentops import track_agent
34
+ else:
35
+ raise ImportError
36
+ except (ImportError, AttributeError):
37
+ from camel.utils import track_agent
38
+
39
+ logger = get_logger(__name__)
40
+
41
+ SYS_MSG_CONTENT = """
42
+ You are a helpful assistant, and you prefer to use tools provided by the user
43
+ to solve problems.
44
+ Using a tool, you will tell the user `server_idx`, `tool_name` and
45
+ `tool_args` formatted in JSON as following:
46
+ ```json
47
+ {
48
+ "server_idx": idx,
49
+ "tool_name": "tool_name",
50
+ "tool_args": {
51
+ "arg1": value1,
52
+ "arg2": value2,
53
+ ...
54
+ }
55
+ }
56
+ ```
57
+ For example:
58
+ ```json
59
+ {
60
+ "server_idx": 0,
61
+ "tool_name": "multiply",
62
+ "tool_args": {"a": 5, "b": 50}
63
+ }
64
+ ```
65
+ Otherwise, you should respond to the user directly.
66
+ """
67
+
68
+
69
+ TOOLS_PROMPT = """
70
+ ## Available Tools:
71
+
72
+ {tools}
73
+ """
74
+
75
+ FINAL_RESPONSE_PROMPT = """
76
+ The result `{results}` is provided by tools you proposed.
77
+ Please answer me according to the result directly.
78
+ """
79
+
80
+
81
+ @track_agent(name="MCPAgent")
82
+ class MCPAgent(ChatAgent):
83
+ def __init__(
84
+ self,
85
+ config_path: str,
86
+ model: Optional[BaseModelBackend] = None,
87
+ function_calling_available: bool = False,
88
+ ) -> None:
89
+ r"""A class for the MCP agent that assists using MCP tools.
90
+
91
+ Args:
92
+ config_path (str): Path to the MCP configuration file.
93
+ model (Optional[BaseModelBackend]): Model backend for the agent.
94
+ (default: :obj:`None`)
95
+ function_calling_available (bool): Flag indicating whether the
96
+ model is equipped with the function calling ability.
97
+ (default: :obj:`False`)
98
+ """
99
+ if function_calling_available:
100
+ sys_msg_content = "You are a helpful assistant, and you prefer "
101
+ "to use tools provided by the user to solve problems."
102
+ else:
103
+ sys_msg_content = SYS_MSG_CONTENT
104
+
105
+ system_message = BaseMessage(
106
+ role_name="MCPRouter",
107
+ role_type=RoleType.ASSISTANT,
108
+ meta_dict=None,
109
+ content=sys_msg_content,
110
+ )
111
+
112
+ super().__init__(system_message, model=model)
113
+
114
+ self._mcp_toolkit = MCPToolkit(config_path=config_path)
115
+ self._function_calling_available = function_calling_available
116
+ self._text_tools = None
117
+
118
+ async def connect(self):
119
+ r"""Explicitly connect to all MCP servers."""
120
+ await self._mcp_toolkit.connect()
121
+
122
+ async def close(self):
123
+ r"""Explicitly disconnect from all MCP servers."""
124
+ await self._mcp_toolkit.disconnect()
125
+
126
+ def add_mcp_tools(self):
127
+ r"""Get the MCP tools and wrap into the models"""
128
+
129
+ if not self._mcp_toolkit.is_connected():
130
+ raise RuntimeError("The MCP server is not connected")
131
+
132
+ prompt = TextPrompt(TOOLS_PROMPT)
133
+ self._text_tools = prompt.format(
134
+ tools=self._mcp_toolkit.get_text_tools()
135
+ )
136
+ if self._function_calling_available:
137
+ tools = self._mcp_toolkit.get_tools()
138
+ for tool in tools:
139
+ self.add_tool(tool)
140
+
141
+ async def run(
142
+ self,
143
+ prompt: str,
144
+ ) -> ChatAgentResponse:
145
+ r"""Run the agent to interact with the MCP tools.
146
+
147
+ Args:
148
+ prompt (str): The user's input prompt or query to be processed by
149
+ the agent.
150
+
151
+ Returns:
152
+ ChatAgentResponse: The agent's response after processing the
153
+ prompt and potentially executing MCP tool calls.
154
+
155
+ Raises:
156
+ RuntimeError: If the MCP server is not connected when attempting
157
+ to run.
158
+ """
159
+
160
+ if not self._mcp_toolkit.is_connected():
161
+ raise RuntimeError("The MCP server is not connected")
162
+
163
+ if self._function_calling_available:
164
+ response = await self.astep(prompt)
165
+ return response
166
+ else:
167
+ task = f"## Task:\n {prompt}"
168
+ response = await self.astep(str(self._text_tools) + task)
169
+ content = response.msgs[0].content.lower()
170
+
171
+ tool_calls = []
172
+ while "```json" in content:
173
+ json_match = re.search(r'```json', content)
174
+ if not json_match:
175
+ break
176
+ json_start = json_match.span()[1]
177
+
178
+ end_match = re.search(r'```', content[json_start:])
179
+ if not end_match:
180
+ break
181
+ json_end = end_match.span()[0] + json_start
182
+
183
+ tool_json = content[json_start:json_end].strip('\n')
184
+ try:
185
+ tool_calls.append(json.loads(tool_json))
186
+ except json.JSONDecodeError:
187
+ logger.warning(f"Failed to parse JSON: {tool_json}")
188
+ continue
189
+ content = content[json_end:]
190
+
191
+ if not tool_calls:
192
+ return response
193
+ else:
194
+ tools_results = []
195
+ for tool_call in tool_calls:
196
+ server_idx = tool_call['server_idx']
197
+ tool_name = tool_call['tool_name']
198
+ tool_args = tool_call['tool_args']
199
+ server = self._mcp_toolkit.servers[server_idx]
200
+ result = await server.call_tool(tool_name, tool_args)
201
+ tools_results.append({tool_name: result.content[0].text})
202
+ results = json.dumps(tools_results)
203
+ final_prompt = TextPrompt(FINAL_RESPONSE_PROMPT).format(
204
+ results=results
205
+ )
206
+ response = await self.astep(final_prompt)
207
+ return response
208
+
209
+ @classmethod
210
+ async def create(
211
+ cls,
212
+ config_path: str,
213
+ model: Optional[BaseModelBackend] = None,
214
+ function_calling_available: bool = False,
215
+ ) -> "MCPAgent":
216
+ r"""Factory method to create and initialize an MCPAgent.
217
+
218
+ Args:
219
+ config_path (str): Path to the MCP configuration file that contains
220
+ server settings and other configuration parameters.
221
+ model (Optional[BaseModelBackend]): Model backend for the agent.
222
+ If None, the default model will be used. (default: :obj:`None`)
223
+ function_calling_available (bool): Flag indicating whether the
224
+ model is equipped with function calling ability. This affects
225
+ the system message content. (default: :obj:`False`)
226
+
227
+ Returns:
228
+ MCPAgent: A fully initialized MCPAgent instance ready for use.
229
+ """
230
+ agent = cls(config_path, model, function_calling_available)
231
+ await agent.connect()
232
+ agent.add_mcp_tools()
233
+ return agent
@@ -13,6 +13,7 @@
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
  from .azure_embedding import AzureEmbedding
15
15
  from .base import BaseEmbedding
16
+ from .gemini_embedding import GeminiEmbedding
16
17
  from .jina_embedding import JinaEmbedding
17
18
  from .mistral_embedding import MistralEmbedding
18
19
  from .openai_compatible_embedding import OpenAICompatibleEmbedding
@@ -31,4 +32,5 @@ __all__ = [
31
32
  "OpenAICompatibleEmbedding",
32
33
  "JinaEmbedding",
33
34
  "TogetherEmbedding",
35
+ "GeminiEmbedding",
34
36
  ]
@@ -0,0 +1,115 @@
1
+ # ========= Copyright 2023-2024 @ 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-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ from typing import Any, Optional
18
+
19
+ from camel.embeddings.base import BaseEmbedding
20
+ from camel.types import EmbeddingModelType, GeminiEmbeddingTaskType
21
+ from camel.utils import api_keys_required
22
+
23
+
24
+ class GeminiEmbedding(BaseEmbedding[str]):
25
+ r"""Provides text embedding functionalities using Google's Gemini models.
26
+
27
+ Args:
28
+ model_type (EmbeddingModelType, optional): The model type to be
29
+ used for text embeddings.
30
+ (default: :obj:`GEMINI_EMBEDDING_EXP`)
31
+ api_key (str, optional): The API key for authenticating with the
32
+ Gemini service. (default: :obj:`None`)
33
+ dimensions (int, optional): The text embedding output dimensions.
34
+ (default: :obj:`None`)
35
+ task_type (GeminiEmbeddingTaskType, optional): The task type for which
36
+ to optimize the embeddings. (default: :obj:`None`)
37
+
38
+ Raises:
39
+ RuntimeError: If an unsupported model type is specified.
40
+ """
41
+
42
+ @api_keys_required(
43
+ [
44
+ ("api_key", 'GEMINI_API_KEY'),
45
+ ]
46
+ )
47
+ def __init__(
48
+ self,
49
+ model_type: EmbeddingModelType = (
50
+ EmbeddingModelType.GEMINI_EMBEDDING_EXP
51
+ ),
52
+ api_key: Optional[str] = None,
53
+ dimensions: Optional[int] = None,
54
+ task_type: Optional[GeminiEmbeddingTaskType] = None,
55
+ ) -> None:
56
+ from google import genai
57
+
58
+ if not model_type.is_gemini:
59
+ raise ValueError("Invalid Gemini embedding model type.")
60
+
61
+ self.model_type = model_type
62
+ if dimensions is None:
63
+ self.output_dim = model_type.output_dim
64
+ else:
65
+ assert isinstance(dimensions, int)
66
+ self.output_dim = dimensions
67
+
68
+ self._api_key = api_key or os.environ.get("GEMINI_API_KEY")
69
+ self._task_type = task_type
70
+
71
+ # Initialize Gemini client
72
+ self._client = genai.Client(api_key=self._api_key)
73
+
74
+ def embed_list(
75
+ self,
76
+ objs: list[str],
77
+ **kwargs: Any,
78
+ ) -> list[list[float]]:
79
+ r"""Generates embeddings for the given texts.
80
+
81
+ Args:
82
+ objs (list[str]): The texts for which to generate the embeddings.
83
+ **kwargs (Any): Extra kwargs passed to the embedding API.
84
+
85
+ Returns:
86
+ list[list[float]]: A list that represents the generated embedding
87
+ as a list of floating-point numbers.
88
+ """
89
+ from google.genai import types
90
+
91
+ # Create embedding config if task_type is specified
92
+ embed_config = None
93
+ if self._task_type:
94
+ embed_config = types.EmbedContentConfig(
95
+ task_type=self._task_type.value
96
+ )
97
+
98
+ # Process each text separately since Gemini API
99
+ # expects single content item
100
+ responses = self._client.models.embed_content(
101
+ model=self.model_type.value,
102
+ contents=objs, # type: ignore[arg-type]
103
+ config=embed_config,
104
+ **kwargs,
105
+ )
106
+
107
+ return [response.values for response in responses.embeddings] # type: ignore[misc,union-attr]
108
+
109
+ def get_output_dim(self) -> int:
110
+ r"""Returns the output dimension of the embeddings.
111
+
112
+ Returns:
113
+ int: The dimensionality of the embedding for the current model.
114
+ """
115
+ return self.output_dim
@@ -14,7 +14,7 @@
14
14
  from __future__ import annotations
15
15
 
16
16
  import os
17
- from typing import Any
17
+ from typing import Any, Optional, Union
18
18
 
19
19
  from openai import OpenAI
20
20
 
@@ -27,15 +27,15 @@ class OpenAIEmbedding(BaseEmbedding[str]):
27
27
  r"""Provides text embedding functionalities using OpenAI's models.
28
28
 
29
29
  Args:
30
- model_type (EmbeddingModelType, optional): The model type to be
31
- used for text embeddings.
30
+ model_type (EmbeddingModelType): The model type to be used for text
31
+ embeddings.
32
32
  (default: :obj:`TEXT_EMBEDDING_3_SMALL`)
33
- url (Optional[str], optional): The url to the OpenAI service.
33
+ url (Optional[str]): The url to the OpenAI service.
34
34
  (default: :obj:`None`)
35
- api_key (str, optional): The API key for authenticating with the
36
- OpenAI service. (default: :obj:`None`)
37
- dimensions (int, optional): The text embedding output dimensions.
38
- (default: :obj:`NOT_GIVEN`)
35
+ api_key (Optional[str]): The API key for authenticating with
36
+ the OpenAI service. (default: :obj:`None`)
37
+ dimensions (Union[int, NotGiven]): The text embedding output
38
+ dimensions. (default: :obj:`NOT_GIVEN`)
39
39
 
40
40
  Raises:
41
41
  RuntimeError: If an unsupported model type is specified.
@@ -51,9 +51,9 @@ class OpenAIEmbedding(BaseEmbedding[str]):
51
51
  model_type: EmbeddingModelType = (
52
52
  EmbeddingModelType.TEXT_EMBEDDING_3_SMALL
53
53
  ),
54
- url: str | None = None,
55
- api_key: str | None = None,
56
- dimensions: int | NotGiven = NOT_GIVEN,
54
+ url: Optional[str] = None,
55
+ api_key: Optional[str] = None,
56
+ dimensions: Union[int, NotGiven] = NOT_GIVEN,
57
57
  ) -> None:
58
58
  if not model_type.is_openai:
59
59
  raise ValueError("Invalid OpenAI embedding model type.")
@@ -27,7 +27,7 @@ class TogetherEmbedding(BaseEmbedding[str]):
27
27
  r"""Provides text embedding functionalities using Together AI's models.
28
28
 
29
29
  Args:
30
- model_name (str, optional): The model name to be used for text
30
+ model_type (str, optional): The model name to be used for text
31
31
  embeddings.
32
32
  (default: :obj:`togethercomputer/m2-bert-80M-8k-retrieval`)
33
33
  api_key (str, optional): The API key for authenticating with the
@@ -44,17 +44,17 @@ class TogetherEmbedding(BaseEmbedding[str]):
44
44
  @api_keys_required([("api_key", 'TOGETHER_API_KEY')])
45
45
  def __init__(
46
46
  self,
47
- model_name: str = "togethercomputer/m2-bert-80M-8k-retrieval",
47
+ model_type: str = "togethercomputer/m2-bert-80M-8k-retrieval",
48
48
  api_key: Optional[str] = None,
49
49
  dimensions: Optional[int] = None,
50
50
  ) -> None:
51
- if not isinstance(model_name, str) or not model_name.strip():
51
+ if not isinstance(model_type, str) or not model_type.strip():
52
52
  raise ValueError("Model name must be a non-empty string")
53
53
 
54
54
  if dimensions is not None and dimensions <= 0:
55
55
  raise ValueError("Dimensions must be a positive integer")
56
56
 
57
- self.model_name = model_name
57
+ self.model_type = model_type
58
58
  self._api_key = api_key or os.environ.get("TOGETHER_API_KEY")
59
59
  self.output_dim = dimensions
60
60
 
@@ -91,7 +91,7 @@ class TogetherEmbedding(BaseEmbedding[str]):
91
91
  try:
92
92
  response = self.client.embeddings.create(
93
93
  input=objs,
94
- model=self.model_name,
94
+ model=self.model_type,
95
95
  **kwargs,
96
96
  )
97
97
 
@@ -100,7 +100,7 @@ class TogetherEmbedding(BaseEmbedding[str]):
100
100
  self.output_dim = len(response.data[0].embedding)
101
101
  logger.debug(
102
102
  f"Set output dimension to {self.output_dim} for model "
103
- f"{self.model_name}"
103
+ f"{self.model_type}"
104
104
  )
105
105
 
106
106
  return [data.embedding for data in response.data]
@@ -130,7 +130,7 @@ class TogetherEmbedding(BaseEmbedding[str]):
130
130
  if self.output_dim is None:
131
131
  raise ValueError(
132
132
  "Failed to determine embedding dimension for model: "
133
- f"{self.model_name}"
133
+ f"{self.model_type}"
134
134
  )
135
135
 
136
136
  return self.output_dim
@@ -71,6 +71,7 @@ class ModelFactory:
71
71
  api_key: Optional[str] = None,
72
72
  url: Optional[str] = None,
73
73
  timeout: Optional[float] = None,
74
+ **kwargs,
74
75
  ) -> BaseModelBackend:
75
76
  r"""Creates an instance of `BaseModelBackend` of the specified type.
76
77
 
@@ -94,6 +95,10 @@ class ModelFactory:
94
95
  (default: :obj:`None`)
95
96
  timeout (Optional[float], optional): The timeout value in seconds
96
97
  for API calls. (default: :obj:`None`)
98
+ **kwargs: Additional model-specific parameters that will be passed
99
+ to the model constructor. For example, Azure OpenAI models may
100
+ require `api_version`, `azure_deployment_name`,
101
+ `azure_ad_token_provider`, and `azure_ad_token`.
97
102
 
98
103
  Returns:
99
104
  BaseModelBackend: The initialized backend.
@@ -202,6 +207,7 @@ class ModelFactory:
202
207
  url=url,
203
208
  token_counter=token_counter,
204
209
  timeout=timeout,
210
+ **kwargs,
205
211
  )
206
212
 
207
213
  @classmethod
@@ -39,8 +39,5 @@ class ChatAgentResponse(BaseModel):
39
39
  @property
40
40
  def msg(self):
41
41
  if len(self.msgs) != 1:
42
- raise RuntimeError(
43
- "Property msg is only available "
44
- "for a single message in msgs."
45
- )
42
+ return None
46
43
  return self.msgs[0]
camel/runtime/__init__.py CHANGED
@@ -13,6 +13,7 @@
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
  from .base import BaseRuntime
15
15
  from .configs import TaskConfig
16
+ from .daytona_runtime import DaytonaRuntime
16
17
  from .docker_runtime import DockerRuntime
17
18
  from .llm_guard_runtime import LLMGuardRuntime
18
19
  from .remote_http_runtime import RemoteHttpRuntime
@@ -28,4 +29,5 @@ __all__ = [
28
29
  "LLMGuardRuntime",
29
30
  "TaskConfig",
30
31
  "UbuntuDockerRuntime",
32
+ "DaytonaRuntime",
31
33
  ]