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.

@@ -0,0 +1,238 @@
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
+ import os
15
+ import uuid
16
+ from typing import List, Optional
17
+ from urllib.parse import urlparse
18
+
19
+ import requests
20
+
21
+ from camel.logger import get_logger
22
+ from camel.messages import BaseMessage
23
+ from camel.models import BaseAudioModel, BaseModelBackend, OpenAIAudioModels
24
+ from camel.toolkits.base import BaseToolkit
25
+ from camel.toolkits.function_tool import FunctionTool
26
+
27
+ logger = get_logger(__name__)
28
+
29
+
30
+ def download_file(url: str, cache_dir: str) -> str:
31
+ r"""Download a file from a URL to a local cache directory.
32
+
33
+ Args:
34
+ url (str): The URL of the file to download.
35
+ cache_dir (str): The directory to save the downloaded file.
36
+
37
+ Returns:
38
+ str: The path to the downloaded file.
39
+
40
+ Raises:
41
+ Exception: If the download fails.
42
+ """
43
+ # Create cache directory if it doesn't exist
44
+ os.makedirs(cache_dir, exist_ok=True)
45
+
46
+ # Extract filename from URL or generate a unique one
47
+ parsed_url = urlparse(url)
48
+ filename = os.path.basename(parsed_url.path)
49
+ if not filename:
50
+ # Generate a unique filename if none is provided in the URL
51
+ file_ext = ".mp3" # Default extension
52
+ content_type = None
53
+
54
+ # Try to get the file extension from the content type
55
+ try:
56
+ response = requests.head(url)
57
+ content_type = response.headers.get('Content-Type', '')
58
+ if 'audio/wav' in content_type:
59
+ file_ext = '.wav'
60
+ elif 'audio/mpeg' in content_type:
61
+ file_ext = '.mp3'
62
+ elif 'audio/ogg' in content_type:
63
+ file_ext = '.ogg'
64
+ except Exception:
65
+ pass
66
+
67
+ filename = f"{uuid.uuid4()}{file_ext}"
68
+
69
+ local_path = os.path.join(cache_dir, filename)
70
+
71
+ # Download the file
72
+ response = requests.get(url, stream=True)
73
+ response.raise_for_status()
74
+
75
+ with open(local_path, 'wb') as f:
76
+ for chunk in response.iter_content(chunk_size=8192):
77
+ f.write(chunk)
78
+
79
+ logger.debug(f"Downloaded file from {url} to {local_path}")
80
+ return local_path
81
+
82
+
83
+ class AudioAnalysisToolkit(BaseToolkit):
84
+ r"""A toolkit for audio processing and analysis.
85
+
86
+ This class provides methods for processing, transcribing, and extracting
87
+ information from audio data, including direct question answering about
88
+ audio content.
89
+
90
+ Args:
91
+ cache_dir (Optional[str]): Directory path for caching downloaded audio
92
+ files. If not provided, 'tmp/' will be used. (default: :obj:`None`)
93
+ transcribe_model (Optional[BaseAudioModel]): Model used for audio
94
+ transcription. If not provided, OpenAIAudioModels will be used.
95
+ (default: :obj:`None`)
96
+ audio_reasoning_model (Optional[BaseModelBackend]): Model used for
97
+ audio reasoning and question answering. If not provided, uses the
98
+ default model from ChatAgent. (default: :obj:`None`)
99
+ """
100
+
101
+ def __init__(
102
+ self,
103
+ cache_dir: Optional[str] = None,
104
+ transcribe_model: Optional[BaseAudioModel] = None,
105
+ audio_reasoning_model: Optional[BaseModelBackend] = None,
106
+ ):
107
+ self.cache_dir = 'tmp/'
108
+ if cache_dir:
109
+ self.cache_dir = cache_dir
110
+
111
+ if transcribe_model:
112
+ self.transcribe_model = transcribe_model
113
+ else:
114
+ self.transcribe_model = OpenAIAudioModels()
115
+ logger.warning(
116
+ "No audio transcription model provided. "
117
+ "Using OpenAIAudioModels."
118
+ )
119
+
120
+ from camel.agents import ChatAgent
121
+
122
+ if audio_reasoning_model:
123
+ self.audio_agent = ChatAgent(model=audio_reasoning_model)
124
+ else:
125
+ self.audio_agent = ChatAgent()
126
+ logger.warning(
127
+ "No audio reasoning model provided. Using default model in"
128
+ " ChatAgent."
129
+ )
130
+
131
+ def audio2text(self, audio_path: str) -> str:
132
+ r"""Transcribe audio to text.
133
+
134
+ Args:
135
+ audio_path (str): The path to the audio file or URL.
136
+
137
+ Returns:
138
+ str: The transcribed text.
139
+ """
140
+ logger.debug(
141
+ f"Calling transcribe_audio method for audio file `{audio_path}`."
142
+ )
143
+
144
+ try:
145
+ audio_transcript = self.transcribe_model.speech_to_text(audio_path)
146
+ if not audio_transcript:
147
+ logger.warning("Audio transcription returned empty result")
148
+ return "No audio transcription available."
149
+ return audio_transcript
150
+ except Exception as e:
151
+ logger.error(f"Audio transcription failed: {e}")
152
+ return "Audio transcription failed."
153
+
154
+ def ask_question_about_audio(self, audio_path: str, question: str) -> str:
155
+ r"""Ask any question about the audio and get the answer using
156
+ multimodal model.
157
+
158
+ Args:
159
+ audio_path (str): The path to the audio file.
160
+ question (str): The question to ask about the audio.
161
+
162
+ Returns:
163
+ str: The answer to the question.
164
+ """
165
+
166
+ logger.debug(
167
+ f"Calling ask_question_about_audio method for audio file \
168
+ `{audio_path}` and question `{question}`."
169
+ )
170
+
171
+ parsed_url = urlparse(audio_path)
172
+ is_url = all([parsed_url.scheme, parsed_url.netloc])
173
+ local_audio_path = audio_path
174
+
175
+ # If the audio is a URL, download it first
176
+ if is_url:
177
+ try:
178
+ local_audio_path = download_file(audio_path, self.cache_dir)
179
+ except Exception as e:
180
+ logger.error(f"Failed to download audio file: {e}")
181
+ return f"Failed to download audio file: {e!s}"
182
+
183
+ # Try direct audio question answering first
184
+ try:
185
+ # Check if the transcribe_model supports audio_question_answering
186
+ if hasattr(self.transcribe_model, 'audio_question_answering'):
187
+ logger.debug("Using direct audio question answering")
188
+ response = self.transcribe_model.audio_question_answering(
189
+ local_audio_path, question
190
+ )
191
+ return response
192
+ except Exception as e:
193
+ logger.warning(
194
+ f"Direct audio question answering failed: {e}. "
195
+ "Falling back to transcription-based approach."
196
+ )
197
+
198
+ # Fallback to transcription-based approach
199
+ try:
200
+ transcript = self.audio2text(local_audio_path)
201
+ reasoning_prompt = f"""
202
+ <speech_transcription_result>{transcript}</
203
+ speech_transcription_result>
204
+
205
+ Please answer the following question based on the speech
206
+ transcription result above:
207
+ <question>{question}</question>
208
+ """
209
+ msg = BaseMessage.make_user_message(
210
+ role_name="User", content=reasoning_prompt
211
+ )
212
+ response = self.audio_agent.step(msg)
213
+
214
+ if not response or not response.msgs:
215
+ logger.error("Model returned empty response")
216
+ return (
217
+ "Failed to generate an answer. "
218
+ "The model returned an empty response."
219
+ )
220
+
221
+ answer = response.msgs[0].content
222
+ return answer
223
+ except Exception as e:
224
+ logger.error(f"Audio question answering failed: {e}")
225
+ return f"Failed to answer question about audio: {e!s}"
226
+
227
+ def get_tools(self) -> List[FunctionTool]:
228
+ r"""Returns a list of FunctionTool objects representing the functions
229
+ in the toolkit.
230
+
231
+ Returns:
232
+ List[FunctionTool]: A list of FunctionTool objects representing the
233
+ functions in the toolkit.
234
+ """
235
+ return [
236
+ FunctionTool(self.ask_question_about_audio),
237
+ FunctionTool(self.audio2text),
238
+ ]
@@ -0,0 +1,172 @@
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 typing import List
16
+
17
+ import pandas as pd
18
+
19
+ from camel.logger import get_logger
20
+ from camel.toolkits.base import BaseToolkit
21
+ from camel.toolkits.function_tool import FunctionTool
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ class ExcelToolkit(BaseToolkit):
27
+ r"""A class representing a toolkit for extract detailed cell information
28
+ from an Excel file.
29
+
30
+ This class provides method for processing docx, pdf, pptx, etc. It cannot
31
+ process excel files.
32
+ """
33
+
34
+ def _convert_to_markdown(self, df: pd.DataFrame) -> str:
35
+ r"""Convert DataFrame to Markdown format table.
36
+
37
+ Args:
38
+ df (pd.DataFrame): DataFrame containing the Excel data.
39
+
40
+ Returns:
41
+ str: Markdown formatted table.
42
+ """
43
+ from tabulate import tabulate
44
+
45
+ md_table = tabulate(df, headers='keys', tablefmt='pipe')
46
+ return str(md_table)
47
+
48
+ def extract_excel_content(self, document_path: str) -> str:
49
+ r"""Extract detailed cell information from an Excel file, including
50
+ multiple sheets.
51
+
52
+ Args:
53
+ document_path (str): The path of the Excel file.
54
+
55
+ Returns:
56
+ str: Extracted excel information, including details of each sheet.
57
+ """
58
+ from openpyxl import load_workbook
59
+ from xls2xlsx import XLS2XLSX
60
+
61
+ logger.debug(
62
+ f"Calling extract_excel_content with document_path"
63
+ f": {document_path}"
64
+ )
65
+
66
+ if not (
67
+ document_path.endswith("xls")
68
+ or document_path.endswith("xlsx")
69
+ or document_path.endswith("csv")
70
+ ):
71
+ logger.error("Only xls, xlsx, csv files are supported.")
72
+ return (
73
+ f"Failed to process file {document_path}: "
74
+ f"It is not excel format. Please try other ways."
75
+ )
76
+
77
+ if document_path.endswith("csv"):
78
+ try:
79
+ df = pd.read_csv(document_path)
80
+ md_table = self._convert_to_markdown(df)
81
+ return f"CSV File Processed:\n{md_table}"
82
+ except Exception as e:
83
+ logger.error(f"Failed to process file {document_path}: {e}")
84
+ return f"Failed to process file {document_path}: {e}"
85
+
86
+ if document_path.endswith("xls"):
87
+ output_path = document_path.replace(".xls", ".xlsx")
88
+ x2x = XLS2XLSX(document_path)
89
+ x2x.to_xlsx(output_path)
90
+ document_path = output_path
91
+
92
+ # Load the Excel workbook
93
+ wb = load_workbook(document_path, data_only=True)
94
+ sheet_info_list = []
95
+
96
+ # Iterate through all sheets
97
+ for sheet in wb.sheetnames:
98
+ ws = wb[sheet]
99
+ cell_info_list = []
100
+
101
+ for row in ws.iter_rows():
102
+ for cell in row:
103
+ row_num = cell.row
104
+ col_letter = cell.column_letter
105
+
106
+ cell_value = cell.value
107
+
108
+ font_color = None
109
+ if (
110
+ cell.font
111
+ and cell.font.color
112
+ and "rgb=None" not in str(cell.font.color)
113
+ ): # Handle font color
114
+ font_color = cell.font.color.rgb
115
+
116
+ fill_color = None
117
+ if (
118
+ cell.fill
119
+ and cell.fill.fgColor
120
+ and "rgb=None" not in str(cell.fill.fgColor)
121
+ ): # Handle fill color
122
+ fill_color = cell.fill.fgColor.rgb
123
+
124
+ cell_info_list.append(
125
+ {
126
+ "index": f"{row_num}{col_letter}",
127
+ "value": cell_value,
128
+ "font_color": font_color,
129
+ "fill_color": fill_color,
130
+ }
131
+ )
132
+
133
+ # Convert the sheet to a DataFrame and then to markdown
134
+ sheet_df = pd.read_excel(
135
+ document_path, sheet_name=sheet, engine='openpyxl'
136
+ )
137
+ markdown_content = self._convert_to_markdown(sheet_df)
138
+
139
+ # Collect all information for the sheet
140
+ sheet_info = {
141
+ "sheet_name": sheet,
142
+ "cell_info_list": cell_info_list,
143
+ "markdown_content": markdown_content,
144
+ }
145
+ sheet_info_list.append(sheet_info)
146
+
147
+ result_str = ""
148
+ for sheet_info in sheet_info_list:
149
+ result_str += f"""
150
+ Sheet Name: {sheet_info['sheet_name']}
151
+ Cell information list:
152
+ {sheet_info['cell_info_list']}
153
+
154
+ Markdown View of the content:
155
+ {sheet_info['markdown_content']}
156
+
157
+ {'-'*40}
158
+ """
159
+
160
+ return result_str
161
+
162
+ def get_tools(self) -> List[FunctionTool]:
163
+ r"""Returns a list of FunctionTool objects representing the functions
164
+ in the toolkit.
165
+
166
+ Returns:
167
+ List[FunctionTool]: A list of FunctionTool objects representing
168
+ the functions in the toolkit.
169
+ """
170
+ return [
171
+ FunctionTool(self.extract_excel_content),
172
+ ]