camel-ai 0.2.59__py3-none-any.whl → 0.2.61__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 -1
- camel/agents/chat_agent.py +158 -7
- camel/configs/anthropic_config.py +6 -5
- camel/configs/cohere_config.py +1 -1
- camel/configs/mistral_config.py +1 -1
- camel/configs/openai_config.py +3 -0
- camel/configs/reka_config.py +1 -1
- camel/configs/samba_config.py +2 -2
- camel/datagen/cot_datagen.py +29 -34
- camel/datagen/evol_instruct/scorer.py +22 -23
- camel/datagen/evol_instruct/templates.py +46 -46
- camel/datasets/static_dataset.py +144 -0
- camel/embeddings/jina_embedding.py +8 -1
- camel/embeddings/sentence_transformers_embeddings.py +2 -2
- camel/embeddings/vlm_embedding.py +9 -2
- camel/loaders/__init__.py +5 -2
- camel/loaders/chunkr_reader.py +117 -91
- camel/loaders/mistral_reader.py +148 -0
- camel/memories/blocks/chat_history_block.py +1 -2
- camel/memories/records.py +3 -0
- camel/messages/base.py +15 -3
- camel/models/azure_openai_model.py +1 -0
- camel/models/model_factory.py +2 -2
- camel/models/model_manager.py +7 -3
- camel/retrievers/bm25_retriever.py +1 -2
- camel/retrievers/hybrid_retrival.py +2 -2
- camel/societies/workforce/workforce.py +65 -24
- camel/storages/__init__.py +2 -0
- camel/storages/vectordb_storages/__init__.py +2 -0
- camel/storages/vectordb_storages/faiss.py +712 -0
- camel/storages/vectordb_storages/oceanbase.py +1 -2
- camel/toolkits/__init__.py +2 -0
- camel/toolkits/async_browser_toolkit.py +80 -524
- camel/toolkits/bohrium_toolkit.py +318 -0
- camel/toolkits/browser_toolkit.py +221 -541
- camel/toolkits/browser_toolkit_commons.py +568 -0
- camel/toolkits/dalle_toolkit.py +4 -0
- camel/toolkits/excel_toolkit.py +8 -2
- camel/toolkits/file_write_toolkit.py +76 -29
- camel/toolkits/github_toolkit.py +43 -25
- camel/toolkits/image_analysis_toolkit.py +3 -0
- camel/toolkits/jina_reranker_toolkit.py +194 -77
- camel/toolkits/mcp_toolkit.py +134 -16
- camel/toolkits/page_script.js +40 -28
- camel/toolkits/twitter_toolkit.py +6 -1
- camel/toolkits/video_analysis_toolkit.py +3 -0
- camel/toolkits/video_download_toolkit.py +3 -0
- camel/toolkits/wolfram_alpha_toolkit.py +51 -23
- camel/types/enums.py +27 -6
- camel/utils/__init__.py +2 -0
- camel/utils/commons.py +27 -0
- {camel_ai-0.2.59.dist-info → camel_ai-0.2.61.dist-info}/METADATA +17 -9
- {camel_ai-0.2.59.dist-info → camel_ai-0.2.61.dist-info}/RECORD +55 -51
- {camel_ai-0.2.59.dist-info → camel_ai-0.2.61.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.59.dist-info → camel_ai-0.2.61.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
# See the License for the specific language governing permissions and
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
-
|
|
15
|
-
|
|
16
14
|
import re
|
|
17
15
|
from datetime import datetime
|
|
18
16
|
from pathlib import Path
|
|
@@ -21,7 +19,7 @@ from typing import List, Optional, Union
|
|
|
21
19
|
from camel.logger import get_logger
|
|
22
20
|
from camel.toolkits.base import BaseToolkit
|
|
23
21
|
from camel.toolkits.function_tool import FunctionTool
|
|
24
|
-
from camel.utils import MCPServer
|
|
22
|
+
from camel.utils import MCPServer, dependencies_required
|
|
25
23
|
|
|
26
24
|
logger = get_logger(__name__)
|
|
27
25
|
|
|
@@ -148,40 +146,83 @@ class FileWriteToolkit(BaseToolkit):
|
|
|
148
146
|
document.save(str(file_path))
|
|
149
147
|
logger.debug(f"Wrote DOCX to {file_path} with default formatting")
|
|
150
148
|
|
|
151
|
-
|
|
149
|
+
@dependencies_required('pylatex', 'fpdf')
|
|
150
|
+
def _write_pdf_file(
|
|
151
|
+
self, file_path: Path, content: str, use_latex: bool = False
|
|
152
|
+
) -> None:
|
|
152
153
|
r"""Write text content to a PDF file with default formatting.
|
|
153
154
|
|
|
154
155
|
Args:
|
|
155
156
|
file_path (Path): The target file path.
|
|
156
157
|
content (str): The text content to write.
|
|
158
|
+
use_latex (bool): Whether to use LaTeX for rendering. (requires
|
|
159
|
+
LaTeX toolchain). If False, uses FPDF for simpler PDF
|
|
160
|
+
generation. (default: :obj: `False`)
|
|
157
161
|
|
|
158
162
|
Raises:
|
|
159
|
-
RuntimeError: If the 'fpdf' library is not installed
|
|
163
|
+
RuntimeError: If the 'pylatex' or 'fpdf' library is not installed
|
|
164
|
+
when use_latex=True.
|
|
160
165
|
"""
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
pdf.set_margins(margin, margin, margin)
|
|
172
|
-
|
|
173
|
-
pdf.add_page()
|
|
174
|
-
pdf.set_font(font_family, style=font_style, size=font_size)
|
|
175
|
-
|
|
176
|
-
# Split content into paragraphs and add them
|
|
177
|
-
for para in content.split('\n'):
|
|
178
|
-
if para.strip(): # Skip empty paragraphs
|
|
179
|
-
pdf.multi_cell(0, line_height, para)
|
|
180
|
-
else:
|
|
181
|
-
pdf.ln(line_height) # Add empty line
|
|
166
|
+
if use_latex:
|
|
167
|
+
from pylatex import (
|
|
168
|
+
Command,
|
|
169
|
+
Document,
|
|
170
|
+
Math,
|
|
171
|
+
Section,
|
|
172
|
+
)
|
|
173
|
+
from pylatex.utils import (
|
|
174
|
+
NoEscape,
|
|
175
|
+
)
|
|
182
176
|
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
doc = Document(documentclass="article")
|
|
178
|
+
doc.packages.append(Command('usepackage', 'amsmath'))
|
|
179
|
+
|
|
180
|
+
with doc.create(Section('Generated Content')):
|
|
181
|
+
for line in content.split('\n'):
|
|
182
|
+
# Remove leading whitespace
|
|
183
|
+
stripped_line = line.strip()
|
|
184
|
+
# Check if the line is intended as a standalone math
|
|
185
|
+
# expression
|
|
186
|
+
if (
|
|
187
|
+
stripped_line.startswith('$')
|
|
188
|
+
and stripped_line.endswith('$')
|
|
189
|
+
and len(stripped_line) > 1
|
|
190
|
+
):
|
|
191
|
+
# Extract content between the '$' delimiters
|
|
192
|
+
math_data = stripped_line[1:-1]
|
|
193
|
+
doc.append(Math(data=math_data))
|
|
194
|
+
else:
|
|
195
|
+
doc.append(NoEscape(line))
|
|
196
|
+
doc.append(NoEscape(r'\par'))
|
|
197
|
+
|
|
198
|
+
doc.generate_pdf(str(file_path), clean_tex=False)
|
|
199
|
+
|
|
200
|
+
logger.info(f"Wrote PDF (with LaTeX) to {file_path}")
|
|
201
|
+
else:
|
|
202
|
+
from fpdf import FPDF
|
|
203
|
+
|
|
204
|
+
# Use default formatting values
|
|
205
|
+
font_family = 'Arial'
|
|
206
|
+
font_size = 12
|
|
207
|
+
font_style = ''
|
|
208
|
+
line_height = 10
|
|
209
|
+
margin = 10
|
|
210
|
+
|
|
211
|
+
pdf = FPDF()
|
|
212
|
+
pdf.set_margins(margin, margin, margin)
|
|
213
|
+
|
|
214
|
+
pdf.add_page()
|
|
215
|
+
pdf.set_font(font_family, style=font_style, size=font_size)
|
|
216
|
+
|
|
217
|
+
# Split content into paragraphs and add them
|
|
218
|
+
for para in content.split('\n'):
|
|
219
|
+
if para.strip(): # Skip empty paragraphs
|
|
220
|
+
pdf.multi_cell(0, line_height, para)
|
|
221
|
+
else:
|
|
222
|
+
pdf.ln(line_height) # Add empty line
|
|
223
|
+
|
|
224
|
+
pdf.output(str(file_path))
|
|
225
|
+
logger.debug(f"Wrote PDF to {file_path} with custom formatting")
|
|
185
226
|
|
|
186
227
|
def _write_csv_file(
|
|
187
228
|
self,
|
|
@@ -286,6 +327,7 @@ class FileWriteToolkit(BaseToolkit):
|
|
|
286
327
|
content: Union[str, List[List[str]]],
|
|
287
328
|
filename: str,
|
|
288
329
|
encoding: Optional[str] = None,
|
|
330
|
+
use_latex: bool = False,
|
|
289
331
|
) -> str:
|
|
290
332
|
r"""Write the given content to a file.
|
|
291
333
|
|
|
@@ -302,6 +344,9 @@ class FileWriteToolkit(BaseToolkit):
|
|
|
302
344
|
supplied, it is resolved to self.output_dir.
|
|
303
345
|
encoding (Optional[str]): The character encoding to use. (default:
|
|
304
346
|
:obj: `None`)
|
|
347
|
+
use_latex (bool): For PDF files, whether to use LaTeX rendering
|
|
348
|
+
(True) or simple FPDF rendering (False). (default: :obj:
|
|
349
|
+
`False`)
|
|
305
350
|
|
|
306
351
|
Returns:
|
|
307
352
|
str: A message indicating success or error details.
|
|
@@ -326,7 +371,9 @@ class FileWriteToolkit(BaseToolkit):
|
|
|
326
371
|
if extension in [".doc", ".docx"]:
|
|
327
372
|
self._write_docx_file(file_path, str(content))
|
|
328
373
|
elif extension == ".pdf":
|
|
329
|
-
self._write_pdf_file(
|
|
374
|
+
self._write_pdf_file(
|
|
375
|
+
file_path, str(content), use_latex=use_latex
|
|
376
|
+
)
|
|
330
377
|
elif extension == ".csv":
|
|
331
378
|
self._write_csv_file(
|
|
332
379
|
file_path, content, encoding=file_encoding
|
camel/toolkits/github_toolkit.py
CHANGED
|
@@ -32,7 +32,6 @@ class GithubToolkit(BaseToolkit):
|
|
|
32
32
|
specific issues, and creating pull requests in a GitHub repository.
|
|
33
33
|
|
|
34
34
|
Args:
|
|
35
|
-
repo_name (str): The name of the GitHub repository.
|
|
36
35
|
access_token (str, optional): The access token to authenticate with
|
|
37
36
|
GitHub. If not provided, it will be obtained using the
|
|
38
37
|
`get_github_access_token` method.
|
|
@@ -41,7 +40,6 @@ class GithubToolkit(BaseToolkit):
|
|
|
41
40
|
@dependencies_required('github')
|
|
42
41
|
def __init__(
|
|
43
42
|
self,
|
|
44
|
-
repo_name: str,
|
|
45
43
|
access_token: Optional[str] = None,
|
|
46
44
|
timeout: Optional[float] = None,
|
|
47
45
|
) -> None:
|
|
@@ -61,7 +59,6 @@ class GithubToolkit(BaseToolkit):
|
|
|
61
59
|
access_token = self.get_github_access_token()
|
|
62
60
|
|
|
63
61
|
self.github = Github(auth=Token(access_token))
|
|
64
|
-
self.repo = self.github.get_repo(repo_name)
|
|
65
62
|
|
|
66
63
|
def get_github_access_token(self) -> str:
|
|
67
64
|
r"""Retrieve the GitHub access token from environment variables.
|
|
@@ -85,6 +82,7 @@ class GithubToolkit(BaseToolkit):
|
|
|
85
82
|
|
|
86
83
|
def create_pull_request(
|
|
87
84
|
self,
|
|
85
|
+
repo_name: str,
|
|
88
86
|
file_path: str,
|
|
89
87
|
new_content: str,
|
|
90
88
|
pr_title: str,
|
|
@@ -98,6 +96,7 @@ class GithubToolkit(BaseToolkit):
|
|
|
98
96
|
description contains information about the issue title and number.
|
|
99
97
|
|
|
100
98
|
Args:
|
|
99
|
+
repo_name (str): The name of the GitHub repository.
|
|
101
100
|
file_path (str): The path of the file to be updated in the
|
|
102
101
|
repository.
|
|
103
102
|
new_content (str): The specified new content of the specified file.
|
|
@@ -111,12 +110,13 @@ class GithubToolkit(BaseToolkit):
|
|
|
111
110
|
str: A formatted report of whether the pull request was created
|
|
112
111
|
successfully or not.
|
|
113
112
|
"""
|
|
114
|
-
|
|
113
|
+
repo = self.github.get_repo(repo_name)
|
|
114
|
+
default_branch = repo.get_branch(repo.default_branch)
|
|
115
115
|
from github.GithubException import GithubException
|
|
116
116
|
|
|
117
117
|
try:
|
|
118
|
-
|
|
119
|
-
ref=f"refs/heads/{branch_name}", sha=
|
|
118
|
+
repo.create_git_ref(
|
|
119
|
+
ref=f"refs/heads/{branch_name}", sha=default_branch.commit.sha
|
|
120
120
|
)
|
|
121
121
|
except GithubException as e:
|
|
122
122
|
if e.message == "Reference already exists":
|
|
@@ -128,19 +128,19 @@ class GithubToolkit(BaseToolkit):
|
|
|
128
128
|
else:
|
|
129
129
|
raise
|
|
130
130
|
|
|
131
|
-
file =
|
|
131
|
+
file = repo.get_contents(file_path)
|
|
132
132
|
|
|
133
133
|
from github.ContentFile import ContentFile
|
|
134
134
|
|
|
135
135
|
if isinstance(file, ContentFile):
|
|
136
|
-
|
|
136
|
+
repo.update_file(
|
|
137
137
|
file.path, body, new_content, file.sha, branch=branch_name
|
|
138
138
|
)
|
|
139
|
-
pr =
|
|
139
|
+
pr = repo.create_pull(
|
|
140
140
|
title=pr_title,
|
|
141
141
|
body=body,
|
|
142
142
|
head=branch_name,
|
|
143
|
-
base=
|
|
143
|
+
base=repo.default_branch,
|
|
144
144
|
)
|
|
145
145
|
|
|
146
146
|
if pr is not None:
|
|
@@ -151,11 +151,12 @@ class GithubToolkit(BaseToolkit):
|
|
|
151
151
|
raise ValueError("PRs with multiple files aren't supported yet.")
|
|
152
152
|
|
|
153
153
|
def get_issue_list(
|
|
154
|
-
self, state: Literal["open", "closed", "all"] = "all"
|
|
154
|
+
self, repo_name: str, state: Literal["open", "closed", "all"] = "all"
|
|
155
155
|
) -> List[Dict[str, object]]:
|
|
156
156
|
r"""Retrieves all issues from the GitHub repository.
|
|
157
157
|
|
|
158
158
|
Args:
|
|
159
|
+
repo_name (str): The name of the GitHub repository.
|
|
159
160
|
state (Literal["open", "closed", "all"]): The state of pull
|
|
160
161
|
requests to retrieve. (default: :obj: `all`)
|
|
161
162
|
Options are:
|
|
@@ -167,35 +168,39 @@ class GithubToolkit(BaseToolkit):
|
|
|
167
168
|
List[Dict[str, object]]: A list of dictionaries where each
|
|
168
169
|
dictionary contains the issue number and title.
|
|
169
170
|
"""
|
|
171
|
+
repo = self.github.get_repo(repo_name)
|
|
170
172
|
issues_info = []
|
|
171
|
-
issues =
|
|
173
|
+
issues = repo.get_issues(state=state)
|
|
172
174
|
|
|
173
175
|
for issue in issues:
|
|
174
176
|
issues_info.append({"number": issue.number, "title": issue.title})
|
|
175
177
|
|
|
176
178
|
return issues_info
|
|
177
179
|
|
|
178
|
-
def get_issue_content(self, issue_number: int) -> str:
|
|
180
|
+
def get_issue_content(self, repo_name: str, issue_number: int) -> str:
|
|
179
181
|
r"""Retrieves the content of a specific issue by its number.
|
|
180
182
|
|
|
181
183
|
Args:
|
|
184
|
+
repo_name (str): The name of the GitHub repository.
|
|
182
185
|
issue_number (int): The number of the issue to retrieve.
|
|
183
186
|
|
|
184
187
|
Returns:
|
|
185
188
|
str: issues content details.
|
|
186
189
|
"""
|
|
187
190
|
try:
|
|
188
|
-
|
|
191
|
+
repo = self.github.get_repo(repo_name)
|
|
192
|
+
issue = repo.get_issue(number=issue_number)
|
|
189
193
|
return issue.body
|
|
190
194
|
except Exception as e:
|
|
191
195
|
return f"can't get Issue number {issue_number}: {e!s}"
|
|
192
196
|
|
|
193
197
|
def get_pull_request_list(
|
|
194
|
-
self, state: Literal["open", "closed", "all"] = "all"
|
|
198
|
+
self, repo_name: str, state: Literal["open", "closed", "all"] = "all"
|
|
195
199
|
) -> List[Dict[str, object]]:
|
|
196
200
|
r"""Retrieves all pull requests from the GitHub repository.
|
|
197
201
|
|
|
198
202
|
Args:
|
|
203
|
+
repo_name (str): The name of the GitHub repository.
|
|
199
204
|
state (Literal["open", "closed", "all"]): The state of pull
|
|
200
205
|
requests to retrieve. (default: :obj: `all`)
|
|
201
206
|
Options are:
|
|
@@ -207,18 +212,22 @@ class GithubToolkit(BaseToolkit):
|
|
|
207
212
|
list: A list of dictionaries where each dictionary contains the
|
|
208
213
|
pull request number and title.
|
|
209
214
|
"""
|
|
215
|
+
repo = self.github.get_repo(repo_name)
|
|
210
216
|
pull_requests_info = []
|
|
211
|
-
pull_requests =
|
|
217
|
+
pull_requests = repo.get_pulls(state=state)
|
|
212
218
|
|
|
213
219
|
for pr in pull_requests:
|
|
214
220
|
pull_requests_info.append({"number": pr.number, "title": pr.title})
|
|
215
221
|
|
|
216
222
|
return pull_requests_info
|
|
217
223
|
|
|
218
|
-
def get_pull_request_code(
|
|
224
|
+
def get_pull_request_code(
|
|
225
|
+
self, repo_name: str, pr_number: int
|
|
226
|
+
) -> List[Dict[str, str]]:
|
|
219
227
|
r"""Retrieves the code changes of a specific pull request.
|
|
220
228
|
|
|
221
229
|
Args:
|
|
230
|
+
repo_name (str): The name of the GitHub repository.
|
|
222
231
|
pr_number (int): The number of the pull request to retrieve.
|
|
223
232
|
|
|
224
233
|
Returns:
|
|
@@ -226,8 +235,9 @@ class GithubToolkit(BaseToolkit):
|
|
|
226
235
|
contains the file name and the corresponding code changes
|
|
227
236
|
(patch).
|
|
228
237
|
"""
|
|
238
|
+
repo = self.github.get_repo(repo_name)
|
|
229
239
|
# Retrieve the specific pull request
|
|
230
|
-
pr =
|
|
240
|
+
pr = repo.get_pull(number=pr_number)
|
|
231
241
|
|
|
232
242
|
# Collect the file changes from the pull request
|
|
233
243
|
files_changed = []
|
|
@@ -244,19 +254,21 @@ class GithubToolkit(BaseToolkit):
|
|
|
244
254
|
return files_changed
|
|
245
255
|
|
|
246
256
|
def get_pull_request_comments(
|
|
247
|
-
self, pr_number: int
|
|
257
|
+
self, repo_name: str, pr_number: int
|
|
248
258
|
) -> List[Dict[str, str]]:
|
|
249
259
|
r"""Retrieves the comments from a specific pull request.
|
|
250
260
|
|
|
251
261
|
Args:
|
|
262
|
+
repo_name (str): The name of the GitHub repository.
|
|
252
263
|
pr_number (int): The number of the pull request to retrieve.
|
|
253
264
|
|
|
254
265
|
Returns:
|
|
255
266
|
List[Dict[str, str]]: A list of dictionaries where each dictionary
|
|
256
267
|
contains the user ID and the comment body.
|
|
257
268
|
"""
|
|
269
|
+
repo = self.github.get_repo(repo_name)
|
|
258
270
|
# Retrieve the specific pull request
|
|
259
|
-
pr =
|
|
271
|
+
pr = repo.get_pull(number=pr_number)
|
|
260
272
|
|
|
261
273
|
# Collect the comments from the pull request
|
|
262
274
|
comments = []
|
|
@@ -266,10 +278,11 @@ class GithubToolkit(BaseToolkit):
|
|
|
266
278
|
|
|
267
279
|
return comments
|
|
268
280
|
|
|
269
|
-
def get_all_file_paths(self, path: str = "") -> List[str]:
|
|
281
|
+
def get_all_file_paths(self, repo_name: str, path: str = "") -> List[str]:
|
|
270
282
|
r"""Recursively retrieves all file paths in the GitHub repository.
|
|
271
283
|
|
|
272
284
|
Args:
|
|
285
|
+
repo_name (str): The name of the GitHub repository.
|
|
273
286
|
path (str): The repository path to start the traversal from.
|
|
274
287
|
empty string means starts from the root directory.
|
|
275
288
|
(default: :obj: `""`)
|
|
@@ -280,11 +293,13 @@ class GithubToolkit(BaseToolkit):
|
|
|
280
293
|
"""
|
|
281
294
|
from github.ContentFile import ContentFile
|
|
282
295
|
|
|
296
|
+
repo = self.github.get_repo(repo_name)
|
|
297
|
+
|
|
283
298
|
files: List[str] = []
|
|
284
299
|
|
|
285
300
|
# Retrieves all contents of the current directory
|
|
286
|
-
contents: Union[List[ContentFile], ContentFile] = (
|
|
287
|
-
|
|
301
|
+
contents: Union[List[ContentFile], ContentFile] = repo.get_contents(
|
|
302
|
+
path
|
|
288
303
|
)
|
|
289
304
|
|
|
290
305
|
if isinstance(contents, ContentFile):
|
|
@@ -299,10 +314,11 @@ class GithubToolkit(BaseToolkit):
|
|
|
299
314
|
files.append(content.path)
|
|
300
315
|
return files
|
|
301
316
|
|
|
302
|
-
def retrieve_file_content(self, file_path: str) -> str:
|
|
317
|
+
def retrieve_file_content(self, repo_name: str, file_path: str) -> str:
|
|
303
318
|
r"""Retrieves the content of a file from the GitHub repository.
|
|
304
319
|
|
|
305
320
|
Args:
|
|
321
|
+
repo_name (str): The name of the GitHub repository.
|
|
306
322
|
file_path (str): The path of the file to retrieve.
|
|
307
323
|
|
|
308
324
|
Returns:
|
|
@@ -310,7 +326,9 @@ class GithubToolkit(BaseToolkit):
|
|
|
310
326
|
"""
|
|
311
327
|
from github.ContentFile import ContentFile
|
|
312
328
|
|
|
313
|
-
|
|
329
|
+
repo = self.github.get_repo(repo_name)
|
|
330
|
+
|
|
331
|
+
file_content = repo.get_contents(file_path)
|
|
314
332
|
if isinstance(file_content, ContentFile):
|
|
315
333
|
return file_content.decoded_content.decode()
|
|
316
334
|
else:
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
|
|
15
|
+
# Enables postponed evaluation of annotations (for string-based type hints)
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
15
18
|
from io import BytesIO
|
|
16
19
|
from typing import List, Optional
|
|
17
20
|
from urllib.parse import urlparse
|