agercas commited on
Commit
e4223d1
·
1 Parent(s): a609c80

add structure and tools

Browse files
Files changed (3) hide show
  1. app.py +40 -24
  2. src/agent.py +0 -0
  3. src/tools.py +663 -0
app.py CHANGED
@@ -1,34 +1,37 @@
1
  import os
 
2
  import gradio as gr
3
- import requests
4
- import inspect
5
  import pandas as pd
 
6
 
7
  # (Keep Constants as is)
8
  # --- Constants ---
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
 
 
11
  # --- Basic Agent Definition ---
12
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
13
  class BasicAgent:
14
  def __init__(self):
15
  print("BasicAgent initialized.")
 
16
  def __call__(self, question: str) -> str:
17
  print(f"Agent received question (first 50 chars): {question[:50]}...")
18
  fixed_answer = "This is a default answer."
19
  print(f"Agent returning fixed answer: {fixed_answer}")
20
  return fixed_answer
21
 
22
- def run_and_submit_all( profile: gr.OAuthProfile | None):
 
23
  """
24
  Fetches all questions, runs the BasicAgent on them, submits all answers,
25
  and displays the results.
26
  """
27
  # --- Determine HF Space Runtime URL and Repo URL ---
28
- space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
29
 
30
  if profile:
31
- username= f"{profile.username}"
32
  print(f"User logged in: {username}")
33
  else:
34
  print("User not logged in.")
@@ -55,16 +58,16 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
55
  response.raise_for_status()
56
  questions_data = response.json()
57
  if not questions_data:
58
- print("Fetched questions list is empty.")
59
- return "Fetched questions list is empty or invalid format.", None
60
  print(f"Fetched {len(questions_data)} questions.")
61
  except requests.exceptions.RequestException as e:
62
  print(f"Error fetching questions: {e}")
63
  return f"Error fetching questions: {e}", None
64
  except requests.exceptions.JSONDecodeError as e:
65
- print(f"Error decoding JSON response from questions endpoint: {e}")
66
- print(f"Response text: {response.text[:500]}")
67
- return f"Error decoding server response for questions: {e}", None
68
  except Exception as e:
69
  print(f"An unexpected error occurred fetching questions: {e}")
70
  return f"An unexpected error occurred fetching questions: {e}", None
@@ -82,17 +85,33 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
82
  try:
83
  submitted_answer = agent(question_text)
84
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
85
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
 
 
 
 
 
 
86
  except Exception as e:
87
- print(f"Error running agent on task {task_id}: {e}")
88
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
 
 
 
 
 
 
89
 
90
  if not answers_payload:
91
  print("Agent did not produce any answers to submit.")
92
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
93
 
94
- # 4. Prepare Submission
95
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
 
 
 
 
96
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
97
  print(status_update)
98
 
@@ -166,16 +185,13 @@ with gr.Blocks() as demo:
166
  # Removed max_rows=10 from DataFrame constructor
167
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
168
 
169
- run_button.click(
170
- fn=run_and_submit_all,
171
- outputs=[status_output, results_table]
172
- )
173
 
174
  if __name__ == "__main__":
175
- print("\n" + "-"*30 + " App Starting " + "-"*30)
176
  # Check for SPACE_HOST and SPACE_ID at startup for information
177
  space_host_startup = os.getenv("SPACE_HOST")
178
- space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
179
 
180
  if space_host_startup:
181
  print(f"✅ SPACE_HOST found: {space_host_startup}")
@@ -183,14 +199,14 @@ if __name__ == "__main__":
183
  else:
184
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
185
 
186
- if space_id_startup: # Print repo URLs if SPACE_ID is found
187
  print(f"✅ SPACE_ID found: {space_id_startup}")
188
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
189
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
190
  else:
191
  print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
192
 
193
- print("-"*(60 + len(" App Starting ")) + "\n")
194
 
195
  print("Launching Gradio Interface for Basic Agent Evaluation...")
196
- demo.launch(debug=True, share=False)
 
1
  import os
2
+
3
  import gradio as gr
 
 
4
  import pandas as pd
5
+ import requests
6
 
7
  # (Keep Constants as is)
8
  # --- Constants ---
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
 
11
+
12
  # --- Basic Agent Definition ---
13
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
14
  class BasicAgent:
15
  def __init__(self):
16
  print("BasicAgent initialized.")
17
+
18
  def __call__(self, question: str) -> str:
19
  print(f"Agent received question (first 50 chars): {question[:50]}...")
20
  fixed_answer = "This is a default answer."
21
  print(f"Agent returning fixed answer: {fixed_answer}")
22
  return fixed_answer
23
 
24
+
25
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
26
  """
27
  Fetches all questions, runs the BasicAgent on them, submits all answers,
28
  and displays the results.
29
  """
30
  # --- Determine HF Space Runtime URL and Repo URL ---
31
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
32
 
33
  if profile:
34
+ username = f"{profile.username}"
35
  print(f"User logged in: {username}")
36
  else:
37
  print("User not logged in.")
 
58
  response.raise_for_status()
59
  questions_data = response.json()
60
  if not questions_data:
61
+ print("Fetched questions list is empty.")
62
+ return "Fetched questions list is empty or invalid format.", None
63
  print(f"Fetched {len(questions_data)} questions.")
64
  except requests.exceptions.RequestException as e:
65
  print(f"Error fetching questions: {e}")
66
  return f"Error fetching questions: {e}", None
67
  except requests.exceptions.JSONDecodeError as e:
68
+ print(f"Error decoding JSON response from questions endpoint: {e}")
69
+ print(f"Response text: {response.text[:500]}")
70
+ return f"Error decoding server response for questions: {e}", None
71
  except Exception as e:
72
  print(f"An unexpected error occurred fetching questions: {e}")
73
  return f"An unexpected error occurred fetching questions: {e}", None
 
85
  try:
86
  submitted_answer = agent(question_text)
87
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
88
+ results_log.append(
89
+ {
90
+ "Task ID": task_id,
91
+ "Question": question_text,
92
+ "Submitted Answer": submitted_answer,
93
+ }
94
+ )
95
  except Exception as e:
96
+ print(f"Error running agent on task {task_id}: {e}")
97
+ results_log.append(
98
+ {
99
+ "Task ID": task_id,
100
+ "Question": question_text,
101
+ "Submitted Answer": f"AGENT ERROR: {e}",
102
+ }
103
+ )
104
 
105
  if not answers_payload:
106
  print("Agent did not produce any answers to submit.")
107
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
108
 
109
+ # 4. Prepare Submission
110
+ submission_data = {
111
+ "username": username.strip(),
112
+ "agent_code": agent_code,
113
+ "answers": answers_payload,
114
+ }
115
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
116
  print(status_update)
117
 
 
185
  # Removed max_rows=10 from DataFrame constructor
186
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
187
 
188
+ run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
 
 
 
189
 
190
  if __name__ == "__main__":
191
+ print("\n" + "-" * 30 + " App Starting " + "-" * 30)
192
  # Check for SPACE_HOST and SPACE_ID at startup for information
193
  space_host_startup = os.getenv("SPACE_HOST")
194
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
195
 
196
  if space_host_startup:
197
  print(f"✅ SPACE_HOST found: {space_host_startup}")
 
199
  else:
200
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
201
 
202
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
203
  print(f"✅ SPACE_ID found: {space_id_startup}")
204
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
205
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
206
  else:
207
  print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
208
 
209
+ print("-" * (60 + len(" App Starting ")) + "\n")
210
 
211
  print("Launching Gradio Interface for Basic Agent Evaluation...")
212
+ demo.launch(debug=True, share=False)
src/agent.py ADDED
File without changes
src/tools.py ADDED
@@ -0,0 +1,663 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Specific tool implementations for GAIA benchmark agent.
3
+ Each tool has a focused, single responsibility.
4
+ """
5
+
6
+ import os
7
+ import re
8
+
9
+ import chess
10
+ import chess.pgn
11
+ import numpy as np
12
+ import pandas as pd
13
+ import requests
14
+ import whisper
15
+ import wikipedia
16
+ from bs4 import BeautifulSoup
17
+ from openpyxl import load_workbook
18
+ from smolagents import tool
19
+
20
+ # === SEARCH AND WEB TOOLS ===
21
+
22
+
23
+ @tool
24
+ def wikipedia_search(query: str, language: str = "en", sentences: int = 3) -> str:
25
+ """
26
+ Search Wikipedia for information on a specific topic.
27
+
28
+ Args:
29
+ query: The search term or topic
30
+ language: Wikipedia language code (default: "en")
31
+ sentences: Number of sentences to return from summary (default: 3)
32
+
33
+ Returns:
34
+ Wikipedia article information including title, summary, and URL
35
+ """
36
+ try:
37
+ wikipedia.set_lang(language)
38
+
39
+ # Try direct page access first
40
+ try:
41
+ page = wikipedia.page(query)
42
+ summary = wikipedia.summary(query, sentences=sentences)
43
+ return f"Title: {page.title}\nSummary: {summary}\nURL: {page.url}"
44
+ except wikipedia.DisambiguationError as e:
45
+ # Handle disambiguation by trying first option
46
+ page = wikipedia.page(e.options[0])
47
+ summary = wikipedia.summary(e.options[0], sentences=sentences)
48
+ return f"Title: {page.title}\nSummary: {summary}\nURL: {page.url}\nNote: Disambiguation resolved to first option from: {', '.join(e.options[:3])}"
49
+ except wikipedia.PageError:
50
+ # Search for similar pages
51
+ search_results = wikipedia.search(query, results=5)
52
+ if search_results:
53
+ page = wikipedia.page(search_results[0])
54
+ summary = wikipedia.summary(search_results[0], sentences=sentences)
55
+ return f"Title: {page.title}\nSummary: {summary}\nURL: {page.url}\nNote: Found via search, other results: {', '.join(search_results[1:3])}"
56
+ else:
57
+ return f"No Wikipedia results found for: {query}"
58
+
59
+ except Exception as e:
60
+ return f"Error searching Wikipedia: {str(e)}"
61
+
62
+
63
+ @tool
64
+ def web_search_duckduckgo(query: str, max_results: int = 5) -> str:
65
+ """
66
+ Search the web using DuckDuckGo search engine.
67
+
68
+ Args:
69
+ query: Search query string
70
+ max_results: Maximum number of results to return (default: 5)
71
+
72
+ Returns:
73
+ Formatted search results with titles, URLs, and snippets
74
+ """
75
+ try:
76
+ from duckduckgo_search import DDGS
77
+
78
+ results = []
79
+ with DDGS() as ddgs:
80
+ search_results = ddgs.text(query, max_results=max_results)
81
+ for result in search_results:
82
+ results.append(result)
83
+
84
+ if not results:
85
+ return f"No search results found for: {query}"
86
+
87
+ formatted_results = []
88
+ for i, result in enumerate(results, 1):
89
+ formatted_results.append(
90
+ f"{i}. {result.get('title', 'No title')}\n"
91
+ f" URL: {result.get('href', 'No URL')}\n"
92
+ f" Snippet: {result.get('body', 'No description')[:300]}...\n"
93
+ )
94
+
95
+ return "\n".join(formatted_results)
96
+
97
+ except Exception as e:
98
+ return f"Error in web search: {str(e)}"
99
+
100
+
101
+ @tool
102
+ def fetch_webpage_content(url: str, max_length: int = 3000) -> str:
103
+ """
104
+ Fetch and extract text content from a webpage.
105
+
106
+ Args:
107
+ url: The URL to fetch
108
+ max_length: Maximum length of content to return (default: 3000)
109
+
110
+ Returns:
111
+ Cleaned text content from the webpage
112
+ """
113
+ try:
114
+ headers = {
115
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
116
+ }
117
+ response = requests.get(url, headers=headers, timeout=10)
118
+ response.raise_for_status()
119
+
120
+ soup = BeautifulSoup(response.content, "html.parser")
121
+
122
+ # Remove unwanted elements
123
+ for element in soup(["script", "style", "nav", "header", "footer", "aside"]):
124
+ element.decompose()
125
+
126
+ # Extract text
127
+ text = soup.get_text(separator=" ", strip=True)
128
+
129
+ # Clean up whitespace
130
+ text = re.sub(r"\s+", " ", text)
131
+
132
+ if len(text) > max_length:
133
+ text = text[:max_length] + "..."
134
+
135
+ return f"Content from {url}:\n{text}"
136
+
137
+ except Exception as e:
138
+ return f"Error fetching webpage {url}: {str(e)}"
139
+
140
+
141
+ # === MATHEMATICAL OPERATIONS ===
142
+
143
+
144
+ @tool
145
+ def add_numbers(*numbers: int | float) -> str:
146
+ """
147
+ Add multiple numbers together.
148
+
149
+ Args:
150
+ numbers: Variable number of integers or floats to add
151
+
152
+ Returns:
153
+ Sum of all numbers as string
154
+ """
155
+ try:
156
+ if not numbers:
157
+ return "Error: No numbers provided to add"
158
+
159
+ # Convert strings to numbers if needed
160
+ converted_numbers = []
161
+ for num in numbers:
162
+ if isinstance(num, str):
163
+ try:
164
+ converted_numbers.append(float(num) if "." in num else int(num))
165
+ except ValueError:
166
+ return f"Error: '{num}' is not a valid number"
167
+ else:
168
+ converted_numbers.append(num)
169
+
170
+ result = sum(converted_numbers)
171
+ return f"Sum of {converted_numbers} = {result}"
172
+
173
+ except Exception as e:
174
+ return f"Error adding numbers: {str(e)}"
175
+
176
+
177
+ @tool
178
+ def multiply_numbers(*numbers: int | float) -> str:
179
+ """
180
+ Multiply multiple numbers together.
181
+
182
+ Args:
183
+ numbers: Variable number of integers or floats to multiply
184
+
185
+ Returns:
186
+ Product of all numbers as string
187
+ """
188
+ try:
189
+ if not numbers:
190
+ return "Error: No numbers provided to multiply"
191
+
192
+ # Convert strings to numbers if needed
193
+ converted_numbers = []
194
+ for num in numbers:
195
+ if isinstance(num, str):
196
+ try:
197
+ converted_numbers.append(float(num) if "." in num else int(num))
198
+ except ValueError:
199
+ return f"Error: '{num}' is not a valid number"
200
+ else:
201
+ converted_numbers.append(num)
202
+
203
+ result = 1
204
+ for num in converted_numbers:
205
+ result *= num
206
+
207
+ return f"Product of {converted_numbers} = {result}"
208
+
209
+ except Exception as e:
210
+ return f"Error multiplying numbers: {str(e)}"
211
+
212
+
213
+ @tool
214
+ def divide_numbers(dividend: int | float, divisor: int | float) -> str:
215
+ """
216
+ Divide two numbers.
217
+
218
+ Args:
219
+ dividend: The number to be divided
220
+ divisor: The number to divide by
221
+
222
+ Returns:
223
+ Result of division as string
224
+ """
225
+ try:
226
+ # Convert strings to numbers if needed
227
+ if isinstance(dividend, str):
228
+ dividend = float(dividend) if "." in dividend else int(dividend)
229
+ if isinstance(divisor, str):
230
+ divisor = float(divisor) if "." in divisor else int(divisor)
231
+
232
+ if divisor == 0:
233
+ return "Error: Cannot divide by zero"
234
+
235
+ result = dividend / divisor
236
+ return f"{dividend} ÷ {divisor} = {result}"
237
+
238
+ except ValueError as e:
239
+ return f"Error: Invalid number format - {str(e)}"
240
+ except Exception as e:
241
+ return f"Error dividing numbers: {str(e)}"
242
+
243
+
244
+ @tool
245
+ def calculate_percentage(part: int | float, whole: int | float) -> str:
246
+ """
247
+ Calculate what percentage one number is of another.
248
+
249
+ Args:
250
+ part: The part value
251
+ whole: The whole value
252
+
253
+ Returns:
254
+ Percentage calculation as string
255
+ """
256
+ try:
257
+ # Convert strings to numbers if needed
258
+ if isinstance(part, str):
259
+ part = float(part) if "." in part else int(part)
260
+ if isinstance(whole, str):
261
+ whole = float(whole) if "." in whole else int(whole)
262
+
263
+ if whole == 0:
264
+ return "Error: Cannot calculate percentage with zero as whole"
265
+
266
+ percentage = (part / whole) * 100
267
+ return f"{part} is {percentage:.2f}% of {whole}"
268
+
269
+ except ValueError as e:
270
+ return f"Error: Invalid number format - {str(e)}"
271
+ except Exception as e:
272
+ return f"Error calculating percentage: {str(e)}"
273
+
274
+
275
+ # === FILE PROCESSING TOOLS ===
276
+
277
+
278
+ @tool
279
+ def load_csv_file(
280
+ filepath: str,
281
+ max_rows: int = 100,
282
+ max_columns: int = 20,
283
+ get_all_rows: bool = False,
284
+ ) -> str:
285
+ """
286
+ Load and analyze a CSV file.
287
+
288
+ Args:
289
+ filepath: Path to the CSV file
290
+ max_rows: Maximum number of rows to display (default: 100)
291
+ max_columns: Maximum number of columns to display (default: 20)
292
+ get_all_rows: If True, return all rows regardless of max_rows (default: False)
293
+
294
+ Returns:
295
+ CSV file content and basic statistics
296
+ """
297
+ try:
298
+ if not os.path.exists(filepath):
299
+ return f"Error: File not found at {filepath}"
300
+
301
+ # Read CSV
302
+ df = pd.read_csv(filepath)
303
+
304
+ # Basic info
305
+ total_rows, total_cols = df.shape
306
+ info = f"CSV File: {filepath}\n"
307
+ info += f"Shape: {total_rows} rows × {total_cols} columns\n"
308
+ info += f"Columns: {list(df.columns)}\n\n"
309
+
310
+ # Limit display if needed
311
+ display_rows = total_rows if get_all_rows else min(max_rows, total_rows)
312
+ display_cols = min(max_columns, total_cols)
313
+
314
+ if display_cols < total_cols:
315
+ df_display = df.iloc[:display_rows, :display_cols]
316
+ info += f"Showing first {display_rows} rows and {display_cols} columns:\n"
317
+ else:
318
+ df_display = df.iloc[:display_rows]
319
+ info += f"Showing first {display_rows} rows:\n"
320
+
321
+ info += df_display.to_string(index=True)
322
+
323
+ # Basic statistics for numeric columns
324
+ numeric_cols = df.select_dtypes(include=[np.number]).columns
325
+ if len(numeric_cols) > 0:
326
+ info += "\n\nNumeric column statistics:\n"
327
+ info += df[numeric_cols].describe().to_string()
328
+
329
+ return info
330
+
331
+ except Exception as e:
332
+ return f"Error loading CSV file: {str(e)}"
333
+
334
+
335
+ @tool
336
+ def load_excel_file(
337
+ filepath: str,
338
+ sheet_name: str | None = None,
339
+ max_rows: int = 100,
340
+ max_columns: int = 20,
341
+ get_all_rows: bool = False,
342
+ ) -> str:
343
+ """
344
+ Load and analyze an Excel file.
345
+
346
+ Args:
347
+ filepath: Path to the Excel file
348
+ sheet_name: Specific sheet to load (default: None for first sheet)
349
+ max_rows: Maximum number of rows to display (default: 100)
350
+ max_columns: Maximum number of columns to display (default: 20)
351
+ get_all_rows: If True, return all rows regardless of max_rows (default: False)
352
+
353
+ Returns:
354
+ Excel file content and basic statistics
355
+ """
356
+ try:
357
+ if not os.path.exists(filepath):
358
+ return f"Error: File not found at {filepath}"
359
+
360
+ # Load workbook to get sheet names
361
+ wb = load_workbook(filepath, read_only=True)
362
+ sheet_names = wb.sheetnames
363
+
364
+ # Determine which sheet to read
365
+ if sheet_name and sheet_name in sheet_names:
366
+ target_sheet = sheet_name
367
+ else:
368
+ target_sheet = sheet_names[0] # First sheet
369
+
370
+ # Read Excel
371
+ df = pd.read_excel(filepath, sheet_name=target_sheet)
372
+
373
+ # Basic info
374
+ total_rows, total_cols = df.shape
375
+ info = f"Excel File: {filepath}\n"
376
+ info += f"Available sheets: {sheet_names}\n"
377
+ info += f"Reading sheet: '{target_sheet}'\n"
378
+ info += f"Shape: {total_rows} rows × {total_cols} columns\n"
379
+ info += f"Columns: {list(df.columns)}\n\n"
380
+
381
+ # Limit display if needed
382
+ display_rows = total_rows if get_all_rows else min(max_rows, total_rows)
383
+ display_cols = min(max_columns, total_cols)
384
+
385
+ if display_cols < total_cols:
386
+ df_display = df.iloc[:display_rows, :display_cols]
387
+ info += f"Showing first {display_rows} rows and {display_cols} columns:\n"
388
+ else:
389
+ df_display = df.iloc[:display_rows]
390
+ info += f"Showing first {display_rows} rows:\n"
391
+
392
+ info += df_display.to_string(index=True)
393
+
394
+ # Basic statistics for numeric columns
395
+ numeric_cols = df.select_dtypes(include=[np.number]).columns
396
+ if len(numeric_cols) > 0:
397
+ info += "\n\nNumeric column statistics:\n"
398
+ info += df[numeric_cols].describe().to_string()
399
+
400
+ return info
401
+
402
+ except Exception as e:
403
+ return f"Error loading Excel file: {str(e)}"
404
+
405
+
406
+ @tool
407
+ def read_text_file(filepath: str, max_length: int = 2000, encoding: str = "utf-8") -> str:
408
+ """
409
+ Read content from a text file.
410
+
411
+ Args:
412
+ filepath: Path to the text file
413
+ max_length: Maximum length of content to return (default: 2000)
414
+ encoding: File encoding (default: "utf-8")
415
+
416
+ Returns:
417
+ Text file content
418
+ """
419
+ try:
420
+ if not os.path.exists(filepath):
421
+ return f"Error: File not found at {filepath}"
422
+
423
+ with open(filepath, encoding=encoding) as f:
424
+ content = f.read()
425
+
426
+ file_info = f"Text File: {filepath}\n"
427
+ file_info += f"File size: {len(content)} characters\n"
428
+ file_info += f"Lines: {content.count(chr(10)) + 1}\n\n"
429
+
430
+ if len(content) > max_length:
431
+ file_info += f"Content (first {max_length} characters):\n"
432
+ file_info += content[:max_length] + "..."
433
+ else:
434
+ file_info += "Content:\n" + content
435
+
436
+ return file_info
437
+
438
+ except Exception as e:
439
+ return f"Error reading text file: {str(e)}"
440
+
441
+
442
+ # === AUDIO PROCESSING ===
443
+
444
+
445
+ @tool
446
+ def transcribe_audio_file(filepath: str, model_size: str = "base") -> str:
447
+ """
448
+ Transcribe audio file to text using Whisper.
449
+
450
+ Args:
451
+ filepath: Path to the audio file
452
+ model_size: Whisper model size ("tiny", "base", "small", "medium", "large")
453
+
454
+ Returns:
455
+ Transcribed text from the audio file
456
+ """
457
+ try:
458
+ if not os.path.exists(filepath):
459
+ return f"Error: Audio file not found at {filepath}"
460
+
461
+ # Load Whisper model
462
+ model = whisper.load_model(model_size)
463
+
464
+ # Transcribe audio
465
+ result = model.transcribe(filepath)
466
+
467
+ transcription_info = f"Audio File: {filepath}\n"
468
+ transcription_info += f"Model used: {model_size}\n"
469
+ transcription_info += f"Language detected: {result.get('language', 'Unknown')}\n\n"
470
+ transcription_info += f"Transcription:\n{result['text']}"
471
+
472
+ return transcription_info
473
+
474
+ except Exception as e:
475
+ return f"Error transcribing audio: {str(e)}"
476
+
477
+
478
+ # === CHESS ANALYSIS ===
479
+
480
+
481
+ @tool
482
+ def analyze_chess_position(fen_notation: str) -> str:
483
+ """
484
+ Analyze a chess position given in FEN notation.
485
+
486
+ Args:
487
+ fen_notation: Chess position in FEN (Forsyth-Edwards Notation)
488
+
489
+ Returns:
490
+ Analysis of the chess position including legal moves
491
+ """
492
+ try:
493
+ # Create board from FEN
494
+ board = chess.Board(fen_notation)
495
+
496
+ analysis = "Chess Position Analysis\n"
497
+ analysis += f"FEN: {fen_notation}\n"
498
+ analysis += f"Turn: {'White' if board.turn else 'Black'}\n"
499
+ analysis += f"Castling rights: {board.castling_rights}\n"
500
+
501
+ # Check game state
502
+ if board.is_checkmate():
503
+ analysis += "Status: CHECKMATE\n"
504
+ elif board.is_stalemate():
505
+ analysis += "Status: STALEMATE\n"
506
+ elif board.is_check():
507
+ analysis += "Status: IN CHECK\n"
508
+ else:
509
+ analysis += "Status: Normal play\n"
510
+
511
+ # List legal moves
512
+ legal_moves = list(board.legal_moves)
513
+ analysis += f"\nNumber of legal moves: {len(legal_moves)}\n"
514
+
515
+ if legal_moves:
516
+ # Categorize moves
517
+ captures = [move for move in legal_moves if board.is_capture(move)]
518
+ checks = []
519
+ for move in legal_moves:
520
+ board.push(move)
521
+ if board.is_check():
522
+ checks.append(move)
523
+ board.pop()
524
+
525
+ analysis += f"Legal moves: {', '.join([str(move) for move in legal_moves[:10]])}"
526
+ if len(legal_moves) > 10:
527
+ analysis += f" ... and {len(legal_moves) - 10} more"
528
+
529
+ if captures:
530
+ analysis += f"\nCaptures available: {', '.join([str(move) for move in captures])}"
531
+
532
+ if checks:
533
+ analysis += f"\nChecking moves: {', '.join([str(move) for move in checks])}"
534
+
535
+ return analysis
536
+
537
+ except Exception as e:
538
+ return f"Error analyzing chess position: {str(e)}"
539
+
540
+
541
+ # === STRING MANIPULATION ===
542
+
543
+
544
+ @tool
545
+ def reverse_string(text: str) -> str:
546
+ """
547
+ Reverse a string character by character.
548
+
549
+ Args:
550
+ text: The string to reverse
551
+
552
+ Returns:
553
+ Reversed string
554
+ """
555
+ try:
556
+ reversed_text = text[::-1]
557
+ return f"Original: '{text}'\nReversed: '{reversed_text}'"
558
+ except Exception as e:
559
+ return f"Error reversing string: {str(e)}"
560
+
561
+
562
+ @tool
563
+ def reverse_words_in_string(text: str) -> str:
564
+ """
565
+ Reverse the order of words in a string.
566
+
567
+ Args:
568
+ text: The string with words to reverse
569
+
570
+ Returns:
571
+ String with words in reverse order
572
+ """
573
+ try:
574
+ words = text.split()
575
+ reversed_words = " ".join(words[::-1])
576
+ return f"Original: '{text}'\nWords reversed: '{reversed_words}'"
577
+ except Exception as e:
578
+ return f"Error reversing words: {str(e)}"
579
+
580
+
581
+ # === DATA ANALYSIS ===
582
+
583
+
584
+ @tool
585
+ def analyze_table_commutativity(table_data: str) -> str:
586
+ """
587
+ Analyze a mathematical operation table for commutativity.
588
+
589
+ Args:
590
+ table_data: String representation of the operation table
591
+
592
+ Returns:
593
+ Analysis of commutativity and any counter-examples
594
+ """
595
+ try:
596
+ lines = table_data.strip().split("\n")
597
+
598
+ # Parse table
599
+ table = {}
600
+ elements = []
601
+
602
+ for line in lines:
603
+ if "|" in line and not line.strip().startswith("|---|"):
604
+ parts = [p.strip() for p in line.split("|") if p.strip()]
605
+ if len(parts) > 1:
606
+ row_element = parts[0]
607
+ if row_element != "*": # Skip header row
608
+ elements.append(row_element)
609
+ table[row_element] = parts[1:]
610
+
611
+ # Check commutativity
612
+ violations = set()
613
+
614
+ for i, elem1 in enumerate(elements):
615
+ for j, elem2 in enumerate(elements):
616
+ if i < len(table[elem1]) and j < len(table[elem2]):
617
+ # Get a*b and b*a
618
+ val1 = table[elem1][j] if j < len(table[elem1]) else None
619
+ val2 = table[elem2][i] if i < len(table[elem2]) else None
620
+
621
+ if val1 and val2 and val1 != val2:
622
+ violations.add(elem1)
623
+ violations.add(elem2)
624
+
625
+ result = "Commutativity analysis:\n"
626
+ result += f"Elements: {elements}\n"
627
+
628
+ if violations:
629
+ sorted_violations = sorted(list(violations))
630
+ result += "Operation is NOT commutative\n"
631
+ result += f"Elements involved in violations: {', '.join(sorted_violations)}"
632
+ else:
633
+ result += "Operation appears to be commutative"
634
+
635
+ return result
636
+
637
+ except Exception as e:
638
+ return f"Error analyzing table commutativity: {str(e)}"
639
+
640
+
641
+ @tool
642
+ def count_items_in_list(items_text: str, separator: str = ",") -> str:
643
+ """
644
+ Count items in a delimited list.
645
+
646
+ Args:
647
+ items_text: Text containing delimited items
648
+ separator: Delimiter to split on (default: ",")
649
+
650
+ Returns:
651
+ Count and list of items
652
+ """
653
+ try:
654
+ items = [item.strip() for item in items_text.split(separator) if item.strip()]
655
+ count = len(items)
656
+
657
+ result = f"Item count: {count}\n"
658
+ result += f"Items: {items}"
659
+
660
+ return result
661
+
662
+ except Exception as e:
663
+ return f"Error counting items: {str(e)}"