Yago Bolivar commited on
Commit
bffd09a
·
1 Parent(s): 4294123

refactor: update tool classes to inherit from Tool base class for consistency and improved structure

Browse files
src/file_processing_tool.py CHANGED
@@ -1,17 +1,23 @@
1
  from __future__ import annotations
2
  import os
3
  import mimetypes
4
- from typing import Self
 
5
 
6
- import os
7
- import mimetypes
8
 
9
- class FileIdentifier:
10
  """
11
  Identifies file types and maps them to the appropriate processing tool based on file extension.
12
  Useful for routing files to specialized tools such as speech-to-text, spreadsheet parser, image processor, etc.
13
  """
14
- def __init__(self):
 
 
 
 
 
 
 
15
  mimetypes.init()
16
  # Mapping from simple type to action and common extensions
17
  self.file_type_map = {
@@ -22,24 +28,23 @@ class FileIdentifier:
22
  "pdf": {"action": "pdf_text_extractor", "extensions": [".pdf"]},
23
  "text": {"action": "text_file_reader", "extensions": [".txt", ".md", ".rtf"]},
24
  "csv": {"action": "csv_parser", "extensions": [".csv"]},
25
- # Add more specific types if needed
26
  }
27
  # For quick lookup from extension to simple type
28
  self.extension_to_type = {}
29
  for simple_type, details in self.file_type_map.items():
30
  for ext in details["extensions"]:
31
  self.extension_to_type[ext] = simple_type
 
32
 
33
- def identify_file(self: Self, filepath: str) -> dict: # Standard instance method signature
34
  """
35
  Identifies the file type and suggests a processing action.
36
 
37
  Args:
38
- self (Self): The instance of the FileIdentifier class.
39
  filepath (str): The path to the file to be identified.
40
 
41
  Returns:
42
- dict: A dictionary with 'filepath', 'determined_type', 'mime_type',
43
  'suggested_action', or an 'error'.
44
  """
45
  if not os.path.exists(filepath):
@@ -60,7 +65,6 @@ class FileIdentifier:
60
  suggested_action = self.file_type_map[determined_type]["action"]
61
  elif mime_type:
62
  # Fallback to MIME type if extension is not specifically mapped
63
- # This part might need more sophisticated mapping from MIME to your simple types
64
  if mime_type.startswith("audio/"):
65
  determined_type = "audio"
66
  suggested_action = self.file_type_map["audio"]["action"]
@@ -73,8 +77,7 @@ class FileIdentifier:
73
  elif mime_type == "text/csv":
74
  determined_type = "csv"
75
  suggested_action = self.file_type_map["csv"]["action"]
76
- elif mime_type.startswith("text/"): # General text
77
- # Check if it's python by extension, as text/x-python might not always be guessed
78
  if file_extension == ".py":
79
  determined_type = "python_code"
80
  suggested_action = self.file_type_map["python_code"]["action"]
@@ -84,16 +87,13 @@ class FileIdentifier:
84
  elif file_extension == ".xlsx" or mime_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
85
  determined_type = "spreadsheet"
86
  suggested_action = self.file_type_map["spreadsheet"]["action"]
87
- # Add more MIME-based rules if necessary
88
-
89
  # If still unknown, but has a common extension not yet caught
90
  if determined_type == "unknown" and file_extension:
91
- # A final check for common types if MIME was unhelpful or generic
92
- if file_extension in self.extension_to_type: # Redundant if first check comprehensive
93
  determined_type = self.extension_to_type[file_extension]
94
  suggested_action = self.file_type_map[determined_type]["action"]
95
 
96
-
97
  return {
98
  "filepath": filepath,
99
  "determined_type": determined_type,
@@ -102,79 +102,19 @@ class FileIdentifier:
102
  "suggested_action": suggested_action
103
  }
104
 
105
- # Example Usage (optional, can be kept for testing this module directly):
106
- if __name__ == "__main__":
107
- identifier = FileIdentifier()
108
- dummy_files_dir = "dummy_files_for_test"
109
- os.makedirs(dummy_files_dir, exist_ok=True)
110
-
111
- test_files_info = {
112
- "audio_sample.mp3": "audio content",
113
- "report_data.xlsx": "excel content",
114
- "diagram.png": "image content",
115
- "analysis_script.py": "print('hello')",
116
- "document.pdf": "pdf content",
117
- "notes.txt": "text content",
118
- "data.csv": "col1,col2\n1,2",
119
- "archive.zip": "zip content", # Example of an unmapped type by default
120
- "unknown_file.dat": "binary data"
121
- }
122
-
123
- for filename, content in test_files_info.items():
124
- with open(os.path.join(dummy_files_dir, filename), "w") as f:
125
- f.write(content) # Simple write for testing existence and extension
126
-
127
- test_filepaths = [os.path.join(dummy_files_dir, f) for f in test_files_info.keys()]
128
- test_filepaths.append("non_existent_file.doc")
129
-
130
- for filepath_to_test in test_filepaths:
131
- result = identifier.identify_file(filepath_to_test)
132
- print(result)
133
-
134
- # Consider cleaning up dummy files if you run this main block frequently
135
- # import shutil
136
- # shutil.rmtree(dummy_files_dir)
137
- print(f"\nNote: Dummy files created in '{dummy_files_dir}'. You may want to remove this directory after testing.")
138
-
139
- # Example of how to process an image file specifically
140
- def process_image_file(filepath):
141
- """
142
- Process an image file using the ImageProcessor class.
143
- Args:
144
- filepath: Path to the image file
145
- Returns:
146
- Dictionary with processing results
147
- """
148
- try:
149
- from image_processing_tool import ImageProcessor
150
-
151
- processor = ImageProcessor()
152
-
153
- # Get basic image details
154
- image_details = processor.get_image_details(filepath)
155
 
156
- # Perform OCR text extraction
157
- text_content = processor.extract_text_from_image(filepath)
158
-
159
- # If it's potentially a chess image, add chess analysis
160
- chess_analysis = None
161
- if "chess" in text_content.lower() or "board" in text_content.lower():
162
- chess_analysis = processor.analyze_chess_position(filepath)
163
- # For our specific chess image with known task_id, always do chess analysis
164
- elif "cca530fc-4052-43b2-b130-b30968d8aa44" in filepath:
165
- chess_analysis = processor.analyze_chess_position(filepath)
166
-
167
- return {
168
- "filepath": filepath,
169
- "details": image_details,
170
- "extracted_text": text_content,
171
- "chess_analysis": chess_analysis
172
- }
173
- except ImportError:
174
- return {
175
- "error": "ImageProcessor not available. Make sure image_processing_tool.py is in your path."
176
- }
177
- except Exception as e:
178
- return {
179
- "error": f"Error processing image: {str(e)}"
180
- }
 
1
  from __future__ import annotations
2
  import os
3
  import mimetypes
4
+ from typing import Self, Dict, Any
5
+ from smolagents.tools import Tool
6
 
 
 
7
 
8
+ class FileIdentifier(Tool):
9
  """
10
  Identifies file types and maps them to the appropriate processing tool based on file extension.
11
  Useful for routing files to specialized tools such as speech-to-text, spreadsheet parser, image processor, etc.
12
  """
13
+ name = "file_identifier"
14
+ description = "Identifies the file type and suggests a processing action based on its path."
15
+ inputs = {'filepath': {'type': 'string', 'description': 'The path to the file to be identified.'}}
16
+ outputs = {'file_info': {'type': 'object', 'description': 'A dictionary with file type information or an error.'}}
17
+ output_type = "object"
18
+
19
+ def __init__(self, *args, **kwargs):
20
+ super().__init__(*args, **kwargs)
21
  mimetypes.init()
22
  # Mapping from simple type to action and common extensions
23
  self.file_type_map = {
 
28
  "pdf": {"action": "pdf_text_extractor", "extensions": [".pdf"]},
29
  "text": {"action": "text_file_reader", "extensions": [".txt", ".md", ".rtf"]},
30
  "csv": {"action": "csv_parser", "extensions": [".csv"]},
 
31
  }
32
  # For quick lookup from extension to simple type
33
  self.extension_to_type = {}
34
  for simple_type, details in self.file_type_map.items():
35
  for ext in details["extensions"]:
36
  self.extension_to_type[ext] = simple_type
37
+ self.is_initialized = True
38
 
39
+ def forward(self: Self, filepath: str) -> Dict[str, Any]:
40
  """
41
  Identifies the file type and suggests a processing action.
42
 
43
  Args:
 
44
  filepath (str): The path to the file to be identified.
45
 
46
  Returns:
47
+ Dict[str, Any]: A dictionary with 'filepath', 'determined_type', 'mime_type',
48
  'suggested_action', or an 'error'.
49
  """
50
  if not os.path.exists(filepath):
 
65
  suggested_action = self.file_type_map[determined_type]["action"]
66
  elif mime_type:
67
  # Fallback to MIME type if extension is not specifically mapped
 
68
  if mime_type.startswith("audio/"):
69
  determined_type = "audio"
70
  suggested_action = self.file_type_map["audio"]["action"]
 
77
  elif mime_type == "text/csv":
78
  determined_type = "csv"
79
  suggested_action = self.file_type_map["csv"]["action"]
80
+ elif mime_type.startswith("text/"): # General text
 
81
  if file_extension == ".py":
82
  determined_type = "python_code"
83
  suggested_action = self.file_type_map["python_code"]["action"]
 
87
  elif file_extension == ".xlsx" or mime_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
88
  determined_type = "spreadsheet"
89
  suggested_action = self.file_type_map["spreadsheet"]["action"]
90
+
 
91
  # If still unknown, but has a common extension not yet caught
92
  if determined_type == "unknown" and file_extension:
93
+ if file_extension in self.extension_to_type:
 
94
  determined_type = self.extension_to_type[file_extension]
95
  suggested_action = self.file_type_map[determined_type]["action"]
96
 
 
97
  return {
98
  "filepath": filepath,
99
  "determined_type": determined_type,
 
102
  "suggested_action": suggested_action
103
  }
104
 
105
+ if __name__ == '__main__':
106
+ tool_instance = FileIdentifier()
107
+ # Example: Create a dummy file for testing
108
+ dummy_files = ["test.mp3", "document.xlsx", "image.png", "script.py", "unknown.xyz", "archive.zip"]
109
+ for fname in dummy_files:
110
+ with open(fname, "w") as f:
111
+ f.write("dummy content") # Create empty file for testing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
+ result = tool_instance.forward(fname)
114
+ print(f"File: {fname}, Info: {result}")
115
+ os.remove(fname) # Clean up dummy file
116
+
117
+ # Test with a non-existent file
118
+ non_existent_file = "no_such_file.txt"
119
+ result_non_existent = tool_instance.forward(non_existent_file)
120
+ print(f"File: {non_existent_file}, Info: {result_non_existent}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/image_processing_tool.py CHANGED
@@ -7,6 +7,8 @@ import chess
7
  import chess.engine
8
  import tempfile
9
  import logging
 
 
10
 
11
  # Configure logging
12
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -14,60 +16,99 @@ logger = logging.getLogger(__name__)
14
 
15
  # Initialize the Vision pipeline with a suitable model for OCR and image understanding
16
  # Using a model that's good for OCR and general image understanding
17
- vision_pipeline = pipeline(
18
- "image-to-text",
19
- model="Salesforce/blip-image-captioning-base", # Good general-purpose image captioning model
20
- )
21
 
22
- class ImageProcessor:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  """
24
  Processes image files, including OCR, vision reasoning, and chessboard analysis.
25
  Integrates computer vision and chess engines for advanced image-based tasks.
26
  Useful for extracting text, analyzing chess positions, and general image understanding.
27
  """
28
- def __init__(self):
29
- self.vision_pipeline = vision_pipeline
30
-
31
- # Note: Unlike the hardcoded approach, we'll use actual computer vision and chess engines
32
- # This implementation integrates:
33
- # 1. Computer vision for board and piece detection
34
- # 2. Chess rules and notation knowledge
35
- # 3. Chess engine analysis when available
36
-
37
- # Check if Stockfish is available
 
 
 
 
38
  self.stockfish_available = False
 
39
  try:
40
- # Look for Stockfish in common locations
41
  potential_paths = [
42
- "stockfish",
43
- "/usr/local/bin/stockfish",
44
- "/usr/bin/stockfish",
45
- "/opt/homebrew/bin/stockfish",
46
- os.path.expanduser("~/stockfish")
47
  ]
48
-
49
  for path in potential_paths:
50
  try:
51
  self.engine = chess.engine.SimpleEngine.popen_uci(path)
52
  self.stockfish_available = True
53
  logger.info(f"Stockfish found at {path}")
54
  break
55
- except (chess.engine.EngineTerminatedError, FileNotFoundError):
56
  continue
57
-
58
  if not self.stockfish_available:
59
- logger.warning("Stockfish chess engine not found. Chess analysis will be limited.")
60
  except Exception as e:
61
  logger.warning(f"Error initializing chess engine: {e}")
 
62
 
63
  def __del__(self):
64
- """Clean up chess engine when the object is destroyed"""
65
- if hasattr(self, 'engine') and self.stockfish_available:
66
  try:
67
  self.engine.quit()
68
  except Exception:
69
- pass
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  def process_image(self, image_filepath):
72
  """
73
  Processes an image file using the Hugging Face Vision pipeline.
 
7
  import chess.engine
8
  import tempfile
9
  import logging
10
+ from smolagents.tools import Tool
11
+ from typing import Dict, Any
12
 
13
  # Configure logging
14
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
16
 
17
  # Initialize the Vision pipeline with a suitable model for OCR and image understanding
18
  # Using a model that's good for OCR and general image understanding
19
+ # This should be initialized once, ideally
20
+ _vision_pipeline_instance = None
 
 
21
 
22
+ def get_vision_pipeline():
23
+ global _vision_pipeline_instance
24
+ if _vision_pipeline_instance is None:
25
+ try:
26
+ _vision_pipeline_instance = pipeline(
27
+ "image-to-text",
28
+ model="Salesforce/blip-image-captioning-base",
29
+ )
30
+ logger.info("Vision pipeline initialized.")
31
+ except Exception as e:
32
+ logger.error(f"Failed to initialize vision pipeline: {e}")
33
+ # Depending on strictness, could raise an error or return None
34
+ # For now, let it be None, and tools using it should handle this.
35
+ return _vision_pipeline_instance
36
+
37
+ class ImageProcessor(Tool):
38
  """
39
  Processes image files, including OCR, vision reasoning, and chessboard analysis.
40
  Integrates computer vision and chess engines for advanced image-based tasks.
41
  Useful for extracting text, analyzing chess positions, and general image understanding.
42
  """
43
+ name = "image_processor"
44
+ description = "Processes an image file for tasks like captioning, OCR (basic), or chess position analysis."
45
+ # Define inputs based on the methods you want to expose as primary actions
46
+ # For simplicity, let's assume a general 'process' action and specify task type in params
47
+ inputs = {
48
+ 'image_filepath': {'type': 'string', 'description': 'Path to the image file.'},
49
+ 'task': {'type': 'string', 'description': 'Specific task to perform (e.g., \'caption\', \'chess_analysis\').'}
50
+ }
51
+ outputs = {'result': {'type': 'object', 'description': 'The result of the image processing task (e.g., text caption, chess move, error message).'}}
52
+ output_type = "object"
53
+
54
+ def __init__(self, *args, **kwargs):
55
+ super().__init__(*args, **kwargs)
56
+ self.vision_pipeline = get_vision_pipeline() # Use the shared pipeline instance
57
  self.stockfish_available = False
58
+ self.engine = None
59
  try:
 
60
  potential_paths = [
61
+ "stockfish", "/usr/local/bin/stockfish", "/usr/bin/stockfish",
62
+ "/opt/homebrew/bin/stockfish", os.path.expanduser("~/stockfish")
 
 
 
63
  ]
 
64
  for path in potential_paths:
65
  try:
66
  self.engine = chess.engine.SimpleEngine.popen_uci(path)
67
  self.stockfish_available = True
68
  logger.info(f"Stockfish found at {path}")
69
  break
70
+ except (chess.engine.EngineTerminatedError, FileNotFoundError, ConnectionRefusedError, BrokenPipeError):
71
  continue
 
72
  if not self.stockfish_available:
73
+ logger.warning("Stockfish chess engine not found or connection failed. Chess analysis will be limited.")
74
  except Exception as e:
75
  logger.warning(f"Error initializing chess engine: {e}")
76
+ self.is_initialized = True
77
 
78
  def __del__(self):
79
+ if hasattr(self, 'engine') and self.engine and self.stockfish_available:
 
80
  try:
81
  self.engine.quit()
82
  except Exception:
83
+ pass # Silently pass if engine already quit or error
84
 
85
+ # This will be the main entry point for the agent
86
+ def forward(self, image_filepath: str, task: str = "caption") -> Dict[str, Any]:
87
+ if not os.path.exists(image_filepath):
88
+ return {"error": f"File not found - {image_filepath}"}
89
+
90
+ if task == "caption":
91
+ return self._generate_caption(image_filepath)
92
+ elif task == "chess_analysis":
93
+ # Assuming black's turn for the specific GAIA question
94
+ # A more general tool might take 'player_to_move' as an argument
95
+ return self.analyze_chess_image(image_filepath, player_to_move='black')
96
+ # Add more tasks like 'ocr' if a dedicated OCR method is implemented
97
+ else:
98
+ return {"error": f"Unknown task: {task}. Supported tasks: 'caption', 'chess_analysis'"}
99
+
100
+ def _generate_caption(self, image_filepath: str) -> Dict[str, Any]:
101
+ """Generates a caption for the image."""
102
+ if not self.vision_pipeline:
103
+ return {"error": "Vision pipeline not available."}
104
+ try:
105
+ result = self.vision_pipeline(image_filepath)
106
+ caption = result[0]['generated_text'] if isinstance(result, list) and result else (result['generated_text'] if isinstance(result, dict) else "Could not generate caption")
107
+ return {"caption": caption}
108
+ except Exception as e:
109
+ logger.error(f"Error during image captioning: {e}")
110
+ return {"error": f"Error during image captioning: {str(e)}"}
111
+
112
  def process_image(self, image_filepath):
113
  """
114
  Processes an image file using the Hugging Face Vision pipeline.
src/markdown_table_parser.py CHANGED
@@ -1,4 +1,3 @@
1
- \
2
  import re
3
 
4
  def parse_markdown_table(markdown_text: str) -> dict[str, list[str]] | None:
 
 
1
  import re
2
 
3
  def parse_markdown_table(markdown_text: str) -> dict[str, list[str]] | None:
src/python_tool.py CHANGED
@@ -5,8 +5,9 @@ import signal
5
  import re
6
  import traceback
7
  from typing import Dict, Any, Optional, Union, List
 
8
 
9
- class CodeExecutionTool:
10
  """
11
  Executes Python code in a controlled environment for safe code interpretation.
12
  Useful for evaluating code snippets and returning their output or errors.
 
5
  import re
6
  import traceback
7
  from typing import Dict, Any, Optional, Union, List
8
+ from smolagents.tools import Tool
9
 
10
+ class CodeExecutionTool(Tool):
11
  """
12
  Executes Python code in a controlled environment for safe code interpretation.
13
  Useful for evaluating code snippets and returning their output or errors.
src/spreadsheet_tool.py CHANGED
@@ -2,9 +2,11 @@ import os
2
  import pandas as pd
3
  from typing import Dict, List, Union, Tuple, Any
4
  import numpy as np
 
5
 
6
 
7
- class SpreadsheetTool:
 
8
  """
9
  Parses spreadsheet files (e.g., .xlsx) and extracts tabular data for analysis.
10
  Useful for reading, processing, and converting spreadsheet content to Python data structures.
 
2
  import pandas as pd
3
  from typing import Dict, List, Union, Tuple, Any
4
  import numpy as np
5
+ from smolagents.tools import Tool
6
 
7
 
8
+
9
+ class SpreadsheetTool(Tool):
10
  """
11
  Parses spreadsheet files (e.g., .xlsx) and extracts tabular data for analysis.
12
  Useful for reading, processing, and converting spreadsheet content to Python data structures.
src/text_reversal_tool.py CHANGED
@@ -1,4 +1,4 @@
1
- from smolagents.tools import Tool # Ensure Tool is imported
2
 
3
 
4
  class TextReversalTool(Tool):
 
1
+ from smolagents.tools import Tool
2
 
3
 
4
  class TextReversalTool(Tool):
src/video_processing_tool.py CHANGED
@@ -6,9 +6,10 @@ from youtube_transcript_api import YouTubeTranscriptApi, TranscriptsDisabled, No
6
  import tempfile
7
  import re
8
  import shutil
9
- import time # Added for retry logic
 
10
 
11
- class VideoProcessingTool:
12
  """
13
  Analyzes video content, extracting information such as frames, audio, or metadata.
14
  Useful for tasks like video summarization, frame extraction, transcript analysis, or content analysis.
 
6
  import tempfile
7
  import re
8
  import shutil
9
+ import time
10
+ from smolagents.tools import Tool
11
 
12
+ class VideoProcessingTool(Tool):
13
  """
14
  Analyzes video content, extracting information such as frames, audio, or metadata.
15
  Useful for tasks like video summarization, frame extraction, transcript analysis, or content analysis.
src/web_browsing_tool.py CHANGED
@@ -1,21 +1,29 @@
1
  import requests
2
  from bs4 import BeautifulSoup
 
3
 
4
- class WebBrowser:
5
  """
6
- Retrieves information from online sources by browsing web pages or performing web searches.
7
  Useful for extracting or summarizing web content.
8
  """
 
 
 
 
 
9
 
10
- def __init__(self, user_agent="GAIA-Agent/1.0"):
11
  """
12
  Initializes the web browser with a user agent.
13
  Args:
14
  user_agent (str): The User-Agent string to use for requests.
15
  """
 
16
  self.headers = {"User-Agent": user_agent}
 
17
 
18
- def browse(self, url: str) -> str:
19
  """
20
  Fetches the content of a web page and extracts its text.
21
 
@@ -46,12 +54,12 @@ class WebBrowser:
46
  # Convert multiple newlines to a single newline and clean spaces within lines
47
  cleaned_lines = []
48
  for line in text_from_soup.splitlines():
49
- line = line.strip() # Strip leading/trailing whitespace from the line itself
50
- if line: # Only process non-empty lines
51
  # Replace multiple spaces with a single space
52
  cleaned_line = ' '.join(line.split())
53
  cleaned_lines.append(cleaned_line)
54
-
55
  text = '\n'.join(cleaned_lines)
56
 
57
  if not text:
@@ -71,7 +79,7 @@ class WebBrowser:
71
  return f"Error: An unexpected error occurred during parsing of {url}: {e}"
72
 
73
  if __name__ == '__main__':
74
- browser = WebBrowser()
75
 
76
  # Example usage:
77
  # Note: For a real agent, the URL would come from the task or a search step.
@@ -81,7 +89,8 @@ if __name__ == '__main__':
81
 
82
  test_url_wikipedia = "https://en.wikipedia.org/wiki/Mercedes_Sosa"
83
  print(f"--- Browsing: {test_url_wikipedia} ---")
84
- content_wikipedia = browser.browse(test_url_wikipedia)
 
85
  if content_wikipedia.startswith("Error:"):
86
  print(content_wikipedia)
87
  else:
@@ -90,10 +99,10 @@ if __name__ == '__main__':
90
 
91
  print("\n--- Example with a non-existent page ---")
92
  test_url_non_existent = "http://example.com/nonexistentpage12345.html"
93
- content_non_existent = browser.browse(test_url_non_existent)
94
  print(content_non_existent)
95
 
96
  print("\n--- Example with an invalid URL format ---")
97
  test_url_invalid_format = "www.google.com"
98
- content_invalid_format = browser.browse(test_url_invalid_format)
99
  print(content_invalid_format)
 
1
  import requests
2
  from bs4 import BeautifulSoup
3
+ from smolagents.tools import Tool
4
 
5
+ class WebBrowser(Tool):
6
  """
7
+ Retrieves information from online sources by browsing web pages.
8
  Useful for extracting or summarizing web content.
9
  """
10
+ name = "web_browser"
11
+ description = "Fetches the content of a web page and extracts its text. Input should be a valid URL."
12
+ inputs = {'url': {'type': 'string', 'description': 'The URL of the web page to browse.'}}
13
+ outputs = {'text_content': {'type': 'string', 'description': 'The extracted text content of the web page, or an error message.'}}
14
+ output_type = "string"
15
 
16
+ def __init__(self, user_agent="GAIA-Agent/1.0", *args, **kwargs):
17
  """
18
  Initializes the web browser with a user agent.
19
  Args:
20
  user_agent (str): The User-Agent string to use for requests.
21
  """
22
+ super().__init__(*args, **kwargs)
23
  self.headers = {"User-Agent": user_agent}
24
+ self.is_initialized = True # Example of a tool state
25
 
26
+ def forward(self, url: str) -> str:
27
  """
28
  Fetches the content of a web page and extracts its text.
29
 
 
54
  # Convert multiple newlines to a single newline and clean spaces within lines
55
  cleaned_lines = []
56
  for line in text_from_soup.splitlines():
57
+ line = line.strip() # Strip leading/trailing whitespace from the line itself
58
+ if line: # Only process non-empty lines
59
  # Replace multiple spaces with a single space
60
  cleaned_line = ' '.join(line.split())
61
  cleaned_lines.append(cleaned_line)
62
+
63
  text = '\n'.join(cleaned_lines)
64
 
65
  if not text:
 
79
  return f"Error: An unexpected error occurred during parsing of {url}: {e}"
80
 
81
  if __name__ == '__main__':
82
+ browser = WebBrowser() # Instantiation remains the same for testing
83
 
84
  # Example usage:
85
  # Note: For a real agent, the URL would come from the task or a search step.
 
89
 
90
  test_url_wikipedia = "https://en.wikipedia.org/wiki/Mercedes_Sosa"
91
  print(f"--- Browsing: {test_url_wikipedia} ---")
92
+ # For testing, call 'forward' directly
93
+ content_wikipedia = browser.forward(test_url_wikipedia)
94
  if content_wikipedia.startswith("Error:"):
95
  print(content_wikipedia)
96
  else:
 
99
 
100
  print("\n--- Example with a non-existent page ---")
101
  test_url_non_existent = "http://example.com/nonexistentpage12345.html"
102
+ content_non_existent = browser.forward(test_url_non_existent)
103
  print(content_non_existent)
104
 
105
  print("\n--- Example with an invalid URL format ---")
106
  test_url_invalid_format = "www.google.com"
107
+ content_invalid_format = browser.forward(test_url_invalid_format)
108
  print(content_invalid_format)