camel-ai 0.2.23a0__py3-none-any.whl → 0.2.25__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 +16 -2
- camel/configs/anthropic_config.py +45 -11
- camel/configs/sglang_config.py +7 -5
- camel/datagen/self_improving_cot.py +2 -2
- camel/datagen/self_instruct/self_instruct.py +46 -2
- camel/interpreters/subprocess_interpreter.py +187 -46
- camel/models/__init__.py +2 -0
- camel/models/anthropic_model.py +5 -1
- camel/models/base_audio_model.py +92 -0
- camel/models/fish_audio_model.py +18 -8
- camel/models/model_manager.py +9 -0
- camel/models/openai_audio_models.py +80 -1
- camel/models/sglang_model.py +35 -5
- camel/societies/role_playing.py +119 -0
- camel/toolkits/__init__.py +17 -1
- camel/toolkits/audio_analysis_toolkit.py +238 -0
- camel/toolkits/excel_toolkit.py +172 -0
- camel/toolkits/file_write_toolkit.py +371 -0
- camel/toolkits/image_analysis_toolkit.py +202 -0
- camel/toolkits/mcp_toolkit.py +251 -0
- camel/toolkits/page_script.js +376 -0
- camel/toolkits/terminal_toolkit.py +421 -0
- camel/toolkits/video_analysis_toolkit.py +407 -0
- camel/toolkits/{video_toolkit.py → video_download_toolkit.py} +19 -25
- camel/toolkits/web_toolkit.py +1306 -0
- camel/types/enums.py +3 -0
- {camel_ai-0.2.23a0.dist-info → camel_ai-0.2.25.dist-info}/METADATA +241 -106
- {camel_ai-0.2.23a0.dist-info → camel_ai-0.2.25.dist-info}/RECORD +60 -50
- {camel_ai-0.2.23a0.dist-info → camel_ai-0.2.25.dist-info}/WHEEL +1 -1
- {camel_ai-0.2.23a0.dist-info → camel_ai-0.2.25.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,371 @@
|
|
|
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
|
+
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import List, Optional, Union
|
|
19
|
+
|
|
20
|
+
from camel.logger import get_logger
|
|
21
|
+
from camel.toolkits.base import BaseToolkit
|
|
22
|
+
from camel.toolkits.function_tool import FunctionTool
|
|
23
|
+
|
|
24
|
+
logger = get_logger(__name__)
|
|
25
|
+
|
|
26
|
+
# Default format when no extension is provided
|
|
27
|
+
DEFAULT_FORMAT = '.md'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FileWriteToolkit(BaseToolkit):
|
|
31
|
+
r"""A toolkit for creating, writing, and modifying text in files.
|
|
32
|
+
|
|
33
|
+
This class provides cross-platform (macOS, Linux, Windows) support for
|
|
34
|
+
writing to various file formats (Markdown, DOCX, PDF, and plaintext),
|
|
35
|
+
replacing text in existing files, automatic backups, custom encoding,
|
|
36
|
+
and enhanced formatting options for specialized formats.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
output_dir: str = "./",
|
|
42
|
+
timeout: Optional[float] = None,
|
|
43
|
+
default_encoding: str = "utf-8",
|
|
44
|
+
backup_enabled: bool = True,
|
|
45
|
+
) -> None:
|
|
46
|
+
r"""Initialize the FileWriteToolkit.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
output_dir (str): The default directory for output files.
|
|
50
|
+
Defaults to the current working directory.
|
|
51
|
+
timeout (Optional[float]): The timeout for the toolkit.
|
|
52
|
+
(default: :obj: `None`)
|
|
53
|
+
default_encoding (str): Default character encoding for text
|
|
54
|
+
operations. (default: :obj: `utf-8`)
|
|
55
|
+
backup_enabled (bool): Whether to create backups of existing files
|
|
56
|
+
before overwriting. (default: :obj: `True`)
|
|
57
|
+
"""
|
|
58
|
+
super().__init__(timeout=timeout)
|
|
59
|
+
self.output_dir = Path(output_dir).resolve()
|
|
60
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
self.default_encoding = default_encoding
|
|
62
|
+
self.backup_enabled = backup_enabled
|
|
63
|
+
logger.info(
|
|
64
|
+
f"FileWriteToolkit initialized with output directory"
|
|
65
|
+
f": {self.output_dir}, encoding: {default_encoding}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def _resolve_filepath(self, file_path: str) -> Path:
|
|
69
|
+
r"""Convert the given string path to a Path object.
|
|
70
|
+
|
|
71
|
+
If the provided path is not absolute, it is made relative to the
|
|
72
|
+
default output directory.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
file_path (str): The file path to resolve.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Path: A fully resolved (absolute) Path object.
|
|
79
|
+
"""
|
|
80
|
+
path_obj = Path(file_path)
|
|
81
|
+
if not path_obj.is_absolute():
|
|
82
|
+
path_obj = self.output_dir / path_obj
|
|
83
|
+
return path_obj.resolve()
|
|
84
|
+
|
|
85
|
+
def _write_text_file(
|
|
86
|
+
self, file_path: Path, content: str, encoding: str = "utf-8"
|
|
87
|
+
) -> None:
|
|
88
|
+
r"""Write text content to a plaintext file.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
file_path (Path): The target file path.
|
|
92
|
+
content (str): The text content to write.
|
|
93
|
+
encoding (str): Character encoding to use. (default: :obj: `utf-8`)
|
|
94
|
+
"""
|
|
95
|
+
with file_path.open("w", encoding=encoding) as f:
|
|
96
|
+
f.write(content)
|
|
97
|
+
logger.debug(f"Wrote text to {file_path} with {encoding} encoding")
|
|
98
|
+
|
|
99
|
+
def _create_backup(self, file_path: Path) -> None:
|
|
100
|
+
r"""Create a backup of the file if it exists and backup is enabled.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
file_path (Path): Path to the file to backup.
|
|
104
|
+
"""
|
|
105
|
+
import shutil
|
|
106
|
+
|
|
107
|
+
if not self.backup_enabled or not file_path.exists():
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
111
|
+
backup_path = file_path.parent / f"{file_path.name}.{timestamp}.bak"
|
|
112
|
+
shutil.copy2(file_path, backup_path)
|
|
113
|
+
logger.info(f"Created backup at {backup_path}")
|
|
114
|
+
|
|
115
|
+
def _write_docx_file(self, file_path: Path, content: str) -> None:
|
|
116
|
+
r"""Write text content to a DOCX file with default formatting.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
file_path (Path): The target file path.
|
|
120
|
+
content (str): The text content to write.
|
|
121
|
+
"""
|
|
122
|
+
import docx
|
|
123
|
+
|
|
124
|
+
# Use default formatting values
|
|
125
|
+
font_name = 'Calibri'
|
|
126
|
+
font_size = 11
|
|
127
|
+
line_spacing = 1.0
|
|
128
|
+
|
|
129
|
+
document = docx.Document()
|
|
130
|
+
style = document.styles['Normal']
|
|
131
|
+
style.font.name = font_name
|
|
132
|
+
style.font.size = docx.shared.Pt(font_size)
|
|
133
|
+
style.paragraph_format.line_spacing = line_spacing
|
|
134
|
+
|
|
135
|
+
# Split content into paragraphs and add them
|
|
136
|
+
for para_text in content.split('\n'):
|
|
137
|
+
para = document.add_paragraph(para_text)
|
|
138
|
+
para.style = style
|
|
139
|
+
|
|
140
|
+
document.save(str(file_path))
|
|
141
|
+
logger.debug(f"Wrote DOCX to {file_path} with default formatting")
|
|
142
|
+
|
|
143
|
+
def _write_pdf_file(self, file_path: Path, content: str, **kwargs) -> None:
|
|
144
|
+
r"""Write text content to a PDF file with default formatting.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
file_path (Path): The target file path.
|
|
148
|
+
content (str): The text content to write.
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
RuntimeError: If the 'fpdf' library is not installed.
|
|
152
|
+
"""
|
|
153
|
+
from fpdf import FPDF
|
|
154
|
+
|
|
155
|
+
# Use default formatting values
|
|
156
|
+
font_family = 'Arial'
|
|
157
|
+
font_size = 12
|
|
158
|
+
font_style = ''
|
|
159
|
+
line_height = 10
|
|
160
|
+
margin = 10
|
|
161
|
+
|
|
162
|
+
pdf = FPDF()
|
|
163
|
+
pdf.set_margins(margin, margin, margin)
|
|
164
|
+
|
|
165
|
+
pdf.add_page()
|
|
166
|
+
pdf.set_font(font_family, style=font_style, size=font_size)
|
|
167
|
+
|
|
168
|
+
# Split content into paragraphs and add them
|
|
169
|
+
for para in content.split('\n'):
|
|
170
|
+
if para.strip(): # Skip empty paragraphs
|
|
171
|
+
pdf.multi_cell(0, line_height, para)
|
|
172
|
+
else:
|
|
173
|
+
pdf.ln(line_height) # Add empty line
|
|
174
|
+
|
|
175
|
+
pdf.output(str(file_path))
|
|
176
|
+
logger.debug(f"Wrote PDF to {file_path} with custom formatting")
|
|
177
|
+
|
|
178
|
+
def _write_csv_file(
|
|
179
|
+
self,
|
|
180
|
+
file_path: Path,
|
|
181
|
+
content: Union[str, List[List]],
|
|
182
|
+
encoding: str = "utf-8",
|
|
183
|
+
) -> None:
|
|
184
|
+
r"""Write CSV content to a file.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
file_path (Path): The target file path.
|
|
188
|
+
content (Union[str, List[List]]): The CSV content as a string or
|
|
189
|
+
list of lists.
|
|
190
|
+
encoding (str): Character encoding to use. (default: :obj: `utf-8`)
|
|
191
|
+
"""
|
|
192
|
+
import csv
|
|
193
|
+
|
|
194
|
+
with file_path.open("w", encoding=encoding, newline='') as f:
|
|
195
|
+
if isinstance(content, str):
|
|
196
|
+
f.write(content)
|
|
197
|
+
else:
|
|
198
|
+
writer = csv.writer(f)
|
|
199
|
+
writer.writerows(content)
|
|
200
|
+
logger.debug(f"Wrote CSV to {file_path} with {encoding} encoding")
|
|
201
|
+
|
|
202
|
+
def _write_json_file(
|
|
203
|
+
self,
|
|
204
|
+
file_path: Path,
|
|
205
|
+
content: str,
|
|
206
|
+
encoding: str = "utf-8",
|
|
207
|
+
) -> None:
|
|
208
|
+
r"""Write JSON content to a file.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
file_path (Path): The target file path.
|
|
212
|
+
content (str): The JSON content as a string.
|
|
213
|
+
encoding (str): Character encoding to use. (default: :obj: `utf-8`)
|
|
214
|
+
"""
|
|
215
|
+
import json
|
|
216
|
+
|
|
217
|
+
with file_path.open("w", encoding=encoding) as f:
|
|
218
|
+
if isinstance(content, str):
|
|
219
|
+
try:
|
|
220
|
+
# Try parsing as JSON string first
|
|
221
|
+
data = json.loads(content)
|
|
222
|
+
json.dump(data, f)
|
|
223
|
+
except json.JSONDecodeError:
|
|
224
|
+
# If not valid JSON string, write as is
|
|
225
|
+
f.write(content)
|
|
226
|
+
else:
|
|
227
|
+
# If not string, dump as JSON
|
|
228
|
+
json.dump(content, f)
|
|
229
|
+
logger.debug(f"Wrote JSON to {file_path} with {encoding} encoding")
|
|
230
|
+
|
|
231
|
+
def _write_yaml_file(
|
|
232
|
+
self,
|
|
233
|
+
file_path: Path,
|
|
234
|
+
content: str,
|
|
235
|
+
encoding: str = "utf-8",
|
|
236
|
+
) -> None:
|
|
237
|
+
r"""Write YAML content to a file.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
file_path (Path): The target file path.
|
|
241
|
+
content (str): The YAML content as a string.
|
|
242
|
+
encoding (str): Character encoding to use. (default: :obj: `utf-8`)
|
|
243
|
+
"""
|
|
244
|
+
with file_path.open("w", encoding=encoding) as f:
|
|
245
|
+
f.write(content)
|
|
246
|
+
logger.debug(f"Wrote YAML to {file_path} with {encoding} encoding")
|
|
247
|
+
|
|
248
|
+
def _write_html_file(
|
|
249
|
+
self, file_path: Path, content: str, encoding: str = "utf-8"
|
|
250
|
+
) -> None:
|
|
251
|
+
r"""Write text content to an HTML file.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
file_path (Path): The target file path.
|
|
255
|
+
content (str): The HTML content to write.
|
|
256
|
+
encoding (str): Character encoding to use. (default: :obj: `utf-8`)
|
|
257
|
+
"""
|
|
258
|
+
with file_path.open("w", encoding=encoding) as f:
|
|
259
|
+
f.write(content)
|
|
260
|
+
logger.debug(f"Wrote HTML to {file_path} with {encoding} encoding")
|
|
261
|
+
|
|
262
|
+
def _write_markdown_file(
|
|
263
|
+
self, file_path: Path, content: str, encoding: str = "utf-8"
|
|
264
|
+
) -> None:
|
|
265
|
+
r"""Write text content to a Markdown file.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
file_path (Path): The target file path.
|
|
269
|
+
content (str): The Markdown content to write.
|
|
270
|
+
encoding (str): Character encoding to use. (default: :obj: `utf-8`)
|
|
271
|
+
"""
|
|
272
|
+
with file_path.open("w", encoding=encoding) as f:
|
|
273
|
+
f.write(content)
|
|
274
|
+
logger.debug(f"Wrote Markdown to {file_path} with {encoding} encoding")
|
|
275
|
+
|
|
276
|
+
def write_to_file(
|
|
277
|
+
self,
|
|
278
|
+
content: Union[str, List[List[str]]],
|
|
279
|
+
filename: str,
|
|
280
|
+
encoding: Optional[str] = None,
|
|
281
|
+
) -> str:
|
|
282
|
+
r"""Write the given content to a file.
|
|
283
|
+
|
|
284
|
+
If the file exists, it will be overwritten. Supports multiple formats:
|
|
285
|
+
Markdown (.md, .markdown, default), Plaintext (.txt), CSV (.csv),
|
|
286
|
+
DOC/DOCX (.doc, .docx), PDF (.pdf), JSON (.json), YAML (.yml, .yaml),
|
|
287
|
+
and HTML (.html, .htm).
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
content (Union[str, List[List[str]]]): The content to write to the
|
|
291
|
+
file. For all formats, content must be a string or list in the
|
|
292
|
+
appropriate format.
|
|
293
|
+
filename (str): The name or path of the file. If a relative path is
|
|
294
|
+
supplied, it is resolved to self.output_dir.
|
|
295
|
+
encoding (Optional[str]): The character encoding to use. (default:
|
|
296
|
+
:obj: `None`)
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
str: A message indicating success or error details.
|
|
300
|
+
"""
|
|
301
|
+
file_path = self._resolve_filepath(filename)
|
|
302
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
303
|
+
|
|
304
|
+
# Create backup if file exists
|
|
305
|
+
self._create_backup(file_path)
|
|
306
|
+
|
|
307
|
+
extension = file_path.suffix.lower()
|
|
308
|
+
|
|
309
|
+
# If no extension is provided, use the default format
|
|
310
|
+
if extension == "":
|
|
311
|
+
file_path = file_path.with_suffix(DEFAULT_FORMAT)
|
|
312
|
+
extension = DEFAULT_FORMAT
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
# Get encoding or use default
|
|
316
|
+
file_encoding = encoding or self.default_encoding
|
|
317
|
+
|
|
318
|
+
if extension in [".doc", ".docx"]:
|
|
319
|
+
self._write_docx_file(file_path, str(content))
|
|
320
|
+
elif extension == ".pdf":
|
|
321
|
+
self._write_pdf_file(file_path, str(content))
|
|
322
|
+
elif extension == ".csv":
|
|
323
|
+
self._write_csv_file(
|
|
324
|
+
file_path, content, encoding=file_encoding
|
|
325
|
+
)
|
|
326
|
+
elif extension == ".json":
|
|
327
|
+
self._write_json_file(
|
|
328
|
+
file_path,
|
|
329
|
+
content, # type: ignore[arg-type]
|
|
330
|
+
encoding=file_encoding,
|
|
331
|
+
)
|
|
332
|
+
elif extension in [".yml", ".yaml"]:
|
|
333
|
+
self._write_yaml_file(
|
|
334
|
+
file_path, str(content), encoding=file_encoding
|
|
335
|
+
)
|
|
336
|
+
elif extension in [".html", ".htm"]:
|
|
337
|
+
self._write_html_file(
|
|
338
|
+
file_path, str(content), encoding=file_encoding
|
|
339
|
+
)
|
|
340
|
+
elif extension in [".md", ".markdown"]:
|
|
341
|
+
self._write_markdown_file(
|
|
342
|
+
file_path, str(content), encoding=file_encoding
|
|
343
|
+
)
|
|
344
|
+
else:
|
|
345
|
+
# Fallback to simple text writing for unknown or .txt
|
|
346
|
+
# extensions
|
|
347
|
+
self._write_text_file(
|
|
348
|
+
file_path, str(content), encoding=file_encoding
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
msg = f"Content successfully written to file: {file_path}"
|
|
352
|
+
logger.info(msg)
|
|
353
|
+
return msg
|
|
354
|
+
except Exception as e:
|
|
355
|
+
error_msg = (
|
|
356
|
+
f"Error occurred while writing to file {file_path}: {e}"
|
|
357
|
+
)
|
|
358
|
+
logger.error(error_msg)
|
|
359
|
+
return error_msg
|
|
360
|
+
|
|
361
|
+
def get_tools(self) -> List[FunctionTool]:
|
|
362
|
+
r"""Return a list of FunctionTool objects representing the functions
|
|
363
|
+
in the toolkit.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
List[FunctionTool]: A list of FunctionTool objects representing
|
|
367
|
+
the available functions in this toolkit.
|
|
368
|
+
"""
|
|
369
|
+
return [
|
|
370
|
+
FunctionTool(self.write_to_file),
|
|
371
|
+
]
|
|
@@ -0,0 +1,202 @@
|
|
|
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
|
+
from io import BytesIO
|
|
16
|
+
from typing import List, Optional
|
|
17
|
+
from urllib.parse import urlparse
|
|
18
|
+
|
|
19
|
+
import requests
|
|
20
|
+
from PIL import Image
|
|
21
|
+
|
|
22
|
+
from camel.logger import get_logger
|
|
23
|
+
from camel.messages import BaseMessage
|
|
24
|
+
from camel.models import BaseModelBackend, ModelFactory
|
|
25
|
+
from camel.toolkits import FunctionTool
|
|
26
|
+
from camel.toolkits.base import BaseToolkit
|
|
27
|
+
from camel.types import ModelPlatformType, ModelType
|
|
28
|
+
|
|
29
|
+
logger = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ImageAnalysisToolkit(BaseToolkit):
|
|
33
|
+
r"""A toolkit for comprehensive image analysis and understanding.
|
|
34
|
+
The toolkit uses vision-capable language models to perform these tasks.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, model: Optional[BaseModelBackend] = None):
|
|
38
|
+
r"""Initialize the ImageAnalysisToolkit.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
model (Optional[BaseModelBackend]): The model backend to use for
|
|
42
|
+
image analysis tasks. This model should support processing
|
|
43
|
+
images for tasks like image description and visual question
|
|
44
|
+
answering. If None, a default model will be created using
|
|
45
|
+
ModelFactory. (default: :obj:`None`)
|
|
46
|
+
"""
|
|
47
|
+
if model:
|
|
48
|
+
self.model = model
|
|
49
|
+
else:
|
|
50
|
+
self.model = ModelFactory.create(
|
|
51
|
+
model_platform=ModelPlatformType.DEFAULT,
|
|
52
|
+
model_type=ModelType.DEFAULT,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def image_to_text(
|
|
56
|
+
self, image_path: str, sys_prompt: Optional[str] = None
|
|
57
|
+
) -> str:
|
|
58
|
+
r"""Generates textual description of an image with optional custom
|
|
59
|
+
prompt.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
image_path (str): Local path or URL to an image file.
|
|
63
|
+
sys_prompt (Optional[str]): Custom system prompt for the analysis.
|
|
64
|
+
(default: :obj:`None`)
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
str: Natural language description of the image.
|
|
68
|
+
"""
|
|
69
|
+
default_content = '''You are an image analysis expert. Provide a
|
|
70
|
+
detailed description including text if present.'''
|
|
71
|
+
|
|
72
|
+
system_msg = BaseMessage.make_assistant_message(
|
|
73
|
+
role_name="Senior Computer Vision Analyst",
|
|
74
|
+
content=sys_prompt if sys_prompt else default_content,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return self._analyze_image(
|
|
78
|
+
image_path=image_path,
|
|
79
|
+
prompt="Please describe the contents of this image.",
|
|
80
|
+
system_message=system_msg,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def ask_question_about_image(
|
|
84
|
+
self, image_path: str, question: str, sys_prompt: Optional[str] = None
|
|
85
|
+
) -> str:
|
|
86
|
+
r"""Answers image questions with optional custom instructions.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
image_path (str): Local path or URL to an image file.
|
|
90
|
+
question (str): Query about the image content.
|
|
91
|
+
sys_prompt (Optional[str]): Custom system prompt for the analysis.
|
|
92
|
+
(default: :obj:`None`)
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
str: Detailed answer based on visual understanding
|
|
96
|
+
"""
|
|
97
|
+
default_content = """Answer questions about images by:
|
|
98
|
+
1. Careful visual inspection
|
|
99
|
+
2. Contextual reasoning
|
|
100
|
+
3. Text transcription where relevant
|
|
101
|
+
4. Logical deduction from visual evidence"""
|
|
102
|
+
|
|
103
|
+
system_msg = BaseMessage.make_assistant_message(
|
|
104
|
+
role_name="Visual QA Specialist",
|
|
105
|
+
content=sys_prompt if sys_prompt else default_content,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return self._analyze_image(
|
|
109
|
+
image_path=image_path,
|
|
110
|
+
prompt=question,
|
|
111
|
+
system_message=system_msg,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _load_image(self, image_path: str) -> Image.Image:
|
|
115
|
+
r"""Loads an image from either local path or URL.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
image_path (str): Local path or URL to image.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Image.Image: Loaded PIL Image object.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
ValueError: For invalid paths/URLs or unreadable images.
|
|
125
|
+
requests.exceptions.RequestException: For URL fetch failures.
|
|
126
|
+
"""
|
|
127
|
+
parsed = urlparse(image_path)
|
|
128
|
+
|
|
129
|
+
if parsed.scheme in ("http", "https"):
|
|
130
|
+
logger.debug(f"Fetching image from URL: {image_path}")
|
|
131
|
+
try:
|
|
132
|
+
response = requests.get(image_path, timeout=15)
|
|
133
|
+
response.raise_for_status()
|
|
134
|
+
return Image.open(BytesIO(response.content))
|
|
135
|
+
except requests.exceptions.RequestException as e:
|
|
136
|
+
logger.error(f"URL fetch failed: {e}")
|
|
137
|
+
raise
|
|
138
|
+
else:
|
|
139
|
+
logger.debug(f"Loading local image: {image_path}")
|
|
140
|
+
try:
|
|
141
|
+
return Image.open(image_path)
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.error(f"Image loading failed: {e}")
|
|
144
|
+
raise ValueError(f"Invalid image file: {e}")
|
|
145
|
+
|
|
146
|
+
def _analyze_image(
|
|
147
|
+
self,
|
|
148
|
+
image_path: str,
|
|
149
|
+
prompt: str,
|
|
150
|
+
system_message: BaseMessage,
|
|
151
|
+
) -> str:
|
|
152
|
+
r"""Core analysis method handling image loading and processing.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
image_path (str): Image location.
|
|
156
|
+
prompt (str): Analysis query/instructions.
|
|
157
|
+
system_message (BaseMessage): Custom system prompt for the
|
|
158
|
+
analysis.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
str: Analysis result or error message.
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
image = self._load_image(image_path)
|
|
165
|
+
logger.info(f"Analyzing image: {image_path}")
|
|
166
|
+
|
|
167
|
+
from camel.agents.chat_agent import ChatAgent
|
|
168
|
+
|
|
169
|
+
agent = ChatAgent(
|
|
170
|
+
system_message=system_message,
|
|
171
|
+
model=self.model,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
user_msg = BaseMessage.make_user_message(
|
|
175
|
+
role_name="User",
|
|
176
|
+
content=prompt,
|
|
177
|
+
image_list=[image],
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
response = agent.step(user_msg)
|
|
181
|
+
agent.reset()
|
|
182
|
+
return response.msgs[0].content
|
|
183
|
+
|
|
184
|
+
except (ValueError, requests.exceptions.RequestException) as e:
|
|
185
|
+
logger.error(f"Image handling error: {e}")
|
|
186
|
+
return f"Image error: {e!s}"
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.error(f"Unexpected error: {e}")
|
|
189
|
+
return f"Analysis failed: {e!s}"
|
|
190
|
+
|
|
191
|
+
def get_tools(self) -> List[FunctionTool]:
|
|
192
|
+
r"""Returns a list of FunctionTool objects representing the functions
|
|
193
|
+
in the toolkit.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
List[FunctionTool]: A list of FunctionTool objects representing the
|
|
197
|
+
functions in the toolkit.
|
|
198
|
+
"""
|
|
199
|
+
return [
|
|
200
|
+
FunctionTool(self.image_to_text),
|
|
201
|
+
FunctionTool(self.ask_question_about_image),
|
|
202
|
+
]
|