KingNish commited on
Commit
2fa2ec5
·
verified ·
1 Parent(s): c39fece

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +9 -5
  2. tools.py +315 -153
app.py CHANGED
@@ -11,7 +11,7 @@ from smolagents.agent_types import AgentAudio, AgentImage, AgentText
11
  from smolagents.memory import ActionStep, FinalAnswerStep, MemoryStep
12
  from smolagents.models import ChatMessageStreamDelta
13
  from smolagents.utils import _is_package_available
14
- from tools import search, scraper, FileReader, FileWriter, CommitChanges, get_repository_structure # Assuming tools.py is in the same directory
15
 
16
  def get_step_footnote_content(step_log: MemoryStep, step_name: str) -> str:
17
  """Get a footnote string for a step log with duration and token information"""
@@ -305,13 +305,17 @@ class GradioUI:
305
  read_file = FileReader(space_id=space_id, folder_path=temp_dir)
306
  write_file = FileWriter(space_id=space_id, folder_path=temp_dir)
307
  commit_changes = CommitChanges(space_id=space_id, folder_path=temp_dir)
 
 
 
 
308
 
309
  # Initialize SWE Agent with enhanced capabilities and improved description
310
  swe_agent = CodeAgent(
311
  model=model,
312
  prompt_templates=swe_agent_prompt_templates,
313
  verbosity_level=1,
314
- tools=[search, scraper, read_file, write_file],
315
  name="swe_agent",
316
  description="An expert Software Engineer capable of designing, developing, and debugging code. This agent can read and write files, search the web, and scrape web content to assist in coding tasks. It excels at implementing detailed technical specifications provided by the Planning Agent."
317
  )
@@ -456,7 +460,7 @@ class GradioUI:
456
  )
457
 
458
  def launch(self, share: bool = True, **kwargs):
459
- self.create_app().launch(debug=True, share=share, **kwargs)
460
 
461
  def create_app(self):
462
  import gradio as gr
@@ -556,8 +560,8 @@ def run_gradio_agent():
556
  """Runs the Gradio UI for the agent."""
557
  # The GradioUI class now handles space_id input and agent initialization internally
558
  GradioUI(
559
- agent_name="SmolAgents Gradio Interface",
560
- agent_description="Interact with a SmolAgents planning agent capable of code generation and execution."
561
  ).launch()
562
 
563
  if __name__ == "__main__":
 
11
  from smolagents.memory import ActionStep, FinalAnswerStep, MemoryStep
12
  from smolagents.models import ChatMessageStreamDelta
13
  from smolagents.utils import _is_package_available
14
+ from tools import search, scraper, FileReader, FileWriter, CommitChanges, get_repository_structure, FileCopier, FileMover, FileDeleter, FileSearcher
15
 
16
  def get_step_footnote_content(step_log: MemoryStep, step_name: str) -> str:
17
  """Get a footnote string for a step log with duration and token information"""
 
305
  read_file = FileReader(space_id=space_id, folder_path=temp_dir)
306
  write_file = FileWriter(space_id=space_id, folder_path=temp_dir)
307
  commit_changes = CommitChanges(space_id=space_id, folder_path=temp_dir)
308
+ file_copier = FileCopier(space_id=space_id, folder_path=temp_dir)
309
+ file_mover = FileMover(space_id=space_id, folder_path=temp_dir)
310
+ file_deleter = FileDeleter(space_id=space_id, folder_path=temp_dir)
311
+ file_searcher = FileSearcher(space_id=space_id, folder_path=temp_dir)
312
 
313
  # Initialize SWE Agent with enhanced capabilities and improved description
314
  swe_agent = CodeAgent(
315
  model=model,
316
  prompt_templates=swe_agent_prompt_templates,
317
  verbosity_level=1,
318
+ tools=[search, scraper, read_file, write_file, file_copier, file_mover, file_deleter, file_searcher],
319
  name="swe_agent",
320
  description="An expert Software Engineer capable of designing, developing, and debugging code. This agent can read and write files, search the web, and scrape web content to assist in coding tasks. It excels at implementing detailed technical specifications provided by the Planning Agent."
321
  )
 
460
  )
461
 
462
  def launch(self, share: bool = True, **kwargs):
463
+ self.create_app().launch(debug=True, share=share, create_mcp_server=True, **kwargs)
464
 
465
  def create_app(self):
466
  import gradio as gr
 
560
  """Runs the Gradio UI for the agent."""
561
  # The GradioUI class now handles space_id input and agent initialization internally
562
  GradioUI(
563
+ agent_name="SmolSWE Gradio Interface",
564
+ agent_description="Interact with a SmolSWE an AI assistant for software development. Currently in beta. It can do tasks like refactoring, debugging, adding any features, and more."
565
  ).launch()
566
 
567
  if __name__ == "__main__":
tools.py CHANGED
@@ -1,154 +1,316 @@
1
- from googlesearch import search as google_search
2
- from smolagents import tool, Tool
3
- from curl_scraper import BasicScraper
4
- from huggingface_hub import hf_hub_download, upload_folder
5
- import os
6
-
7
- class FileReader(Tool):
8
- name = "read_file"
9
- description = "Reads a file and returns the contents as a string."
10
- inputs = {"file": {"type": "string", "description": "The absolute path to the file to be read."}}
11
- output_type = "string"
12
-
13
- def __init__(self, space_id, folder_path, **kwargs):
14
- super().__init__()
15
- self.space_id = space_id
16
- self.folder_path = folder_path
17
-
18
- def forward(self, file: str) -> str:
19
- file_path = os.path.join(self.folder_path, file)
20
- if not os.path.exists(file_path):
21
- hf_hub_download(repo_id=self.space_id, filename=file, repo_type="space", local_dir=self.folder_path, local_dir_use_symlinks=False)
22
- with open(file_path, 'r', encoding='utf-8') as f:
23
- content = f.read()
24
-
25
- return content
26
-
27
- class FileWriter(Tool):
28
- name = "write_file"
29
- description = "Overwrite or append content to a file. Use for creating new files, appending content, or modifying existing files."
30
- inputs = {
31
- "file": {"type": "string", "description": "The absolute path to the file to be written."},
32
- "content": {"type": "string", "description": "The content to be written to the file."},
33
- "append": {"type": "boolean", "description": "Whether to append to the file or overwrite it.", "nullable": True},
34
- }
35
- output_type = "string"
36
-
37
- def __init__(self, space_id, folder_path, **kwargs):
38
- super().__init__()
39
- self.space_id = space_id
40
- self.folder_path = folder_path
41
-
42
- def forward(self, file: str, content: str, append: bool = False) -> str:
43
- file_path = os.path.join(self.folder_path, file)
44
- mode = 'a' if append else 'w'
45
- with open(file_path, mode, encoding='utf-8') as f:
46
- f.write(content if append else content.strip())
47
- return f"Successfully {'appended to' if append else 'wrote to'} file at {file_path}"
48
-
49
- @tool
50
- def search(query: str) -> list:
51
- """
52
- Search for web pages and scrape their content.
53
-
54
- Args:
55
- query (str): The search query.
56
-
57
- Returns:
58
- list: List of top web results including url, title, description from the search results.
59
- """
60
- # Get search results
61
- results = []
62
- for result in google_search(query, advanced=True, unique=True):
63
- results.append(result)
64
- return results
65
-
66
- @tool
67
- def scraper(url: str, response_type: str = 'plain_text') -> str:
68
- """
69
- Scrapes the content of a web page. Important: Don't use regex to extract information from the output of a this scraper tool, Think more and find answer yourself.
70
-
71
- Args:
72
- url (str): The URL of the web page to scrape.
73
- response_type (str): The type of response to return. Valid options are 'markdown' and 'plain_text'. Default is 'plain_text'.
74
-
75
- Returns:
76
- str: The content of the web page in the specified format.
77
- """
78
- response = BasicScraper(timeout=5).get(url)
79
- if response_type == 'markdown':
80
- return response.markdown
81
- elif response_type == 'plain_text':
82
- return response.plain_text
83
- else:
84
- raise ValueError("Invalid response_type. Use 'markdown' or 'plain_text'.")
85
-
86
- class CommitChanges(Tool):
87
- name = "commit_changes"
88
- description = "Commits changes to a repository."
89
- inputs = {
90
- "commit_message": {"type": "string", "description": "The commit message."},
91
- "commit_description": {"type": "string", "description": "The commit description."},
92
- }
93
- output_type = "string"
94
-
95
- def __init__(self, space_id, folder_path, **kwargs):
96
- super().__init__()
97
- self.space_id = space_id
98
- self.folder_path = folder_path
99
-
100
- def forward(self, commit_message: str, commit_description: str) -> str:
101
- try:
102
- upload_folder(
103
- folder_path=self.folder_path,
104
- repo_id=self.space_id,
105
- repo_type="space",
106
- create_pr=True,
107
- commit_message=commit_message,
108
- commit_description=commit_description,
109
- )
110
- return "Changes committed successfully."
111
- except Exception as e:
112
- return f"Error committing changes: {str(e)}"
113
-
114
- from huggingface_hub import HfApi
115
-
116
- def get_repository_structure(space_id: str) -> str:
117
- api = HfApi()
118
- space_info = api.space_info(space_id)
119
-
120
- printed_dirs = set()
121
- output_lines = []
122
- sorted_siblings = sorted(space_info.siblings, key=lambda x: x.rfilename)
123
-
124
- for sibling in sorted_siblings:
125
- rfilename = sibling.rfilename
126
- path_parts = rfilename.split('/')
127
-
128
- current_dir_path_parts = []
129
- for i in range(len(path_parts) - 1):
130
- dir_name = path_parts[i]
131
- current_dir_path_parts.append(dir_name)
132
- dir_path = '/'.join(current_dir_path_parts)
133
-
134
- if dir_path not in printed_dirs:
135
- depth = i
136
- indent = ' ' * depth
137
- output_lines.append(f"{indent}{dir_name}")
138
- printed_dirs.add(dir_path)
139
-
140
- file_name = path_parts[-1]
141
- file_indent_depth = len(path_parts) - 1
142
- file_indent = ' ' * file_indent_depth
143
-
144
- if file_indent_depth == 0:
145
- output_lines.append(file_name)
146
- else:
147
- is_last_file = sibling == sorted_siblings[-1] or not any(
148
- s.rfilename.startswith('/'.join(path_parts[:-1]) + '/')
149
- for s in sorted_siblings[sorted_siblings.index(sibling)+1:]
150
- )
151
- file_prefix = '└─ ' if is_last_file else '├─ '
152
- output_lines.append(f"{file_indent}{file_prefix}{file_name}")
153
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  return '\n'.join(output_lines)
 
1
+ from googlesearch import search as google_search
2
+ from smolagents import tool, Tool
3
+ from curl_scraper import BasicScraper
4
+ from huggingface_hub import hf_hub_download, upload_folder
5
+ import os
6
+
7
+ class FileReader(Tool):
8
+ name = "read_file"
9
+ description = "Reads a file and returns the contents as a string."
10
+ inputs = {"file": {"type": "string", "description": "The absolute path to the file to be read."}}
11
+ output_type = "string"
12
+
13
+ def __init__(self, space_id, folder_path, **kwargs):
14
+ super().__init__()
15
+ self.space_id = space_id
16
+ self.folder_path = folder_path
17
+
18
+ def forward(self, file: str) -> str:
19
+ file_path = os.path.join(self.folder_path, file)
20
+ if not os.path.exists(file_path):
21
+ hf_hub_download(repo_id=self.space_id, filename=file, repo_type="space", local_dir=self.folder_path, local_dir_use_symlinks=False)
22
+ with open(file_path, 'r', encoding='utf-8') as f:
23
+ content = f.read()
24
+
25
+ return content
26
+
27
+ class FileWriter(Tool):
28
+ name = "write_file"
29
+ description = "Overwrite or append content to a file. Use for creating new files, appending content, or modifying existing files."
30
+ inputs = {
31
+ "file": {"type": "string", "description": "The absolute path to the file to be written."},
32
+ "content": {"type": "string", "description": "The content to be written to the file."},
33
+ "append": {"type": "boolean", "description": "Whether to append to the file or overwrite it.", "nullable": True},
34
+ }
35
+ output_type = "string"
36
+
37
+ def __init__(self, space_id, folder_path, **kwargs):
38
+ super().__init__()
39
+ self.space_id = space_id
40
+ self.folder_path = folder_path
41
+
42
+ def forward(self, file: str, content: str, append: bool = False) -> str:
43
+ file_path = os.path.join(self.folder_path, file)
44
+ mode = 'a' if append else 'w'
45
+ with open(file_path, mode, encoding='utf-8') as f:
46
+ f.write(content if append else content.strip())
47
+ return f"Successfully {'appended to' if append else 'wrote to'} file at {file_path}"
48
+
49
+ @tool
50
+ def search(query: str) -> list:
51
+ """
52
+ Search for web pages and scrape their content.
53
+
54
+ Args:
55
+ query (str): The search query.
56
+
57
+ Returns:
58
+ list: List of top web results including url, title, description from the search results.
59
+ """
60
+ # Get search results
61
+ results = []
62
+ for result in google_search(query, advanced=True, unique=True):
63
+ results.append(result)
64
+ return results
65
+
66
+ @tool
67
+ def scraper(url: str, response_type: str = 'plain_text') -> str:
68
+ """
69
+ Scrapes the content of a web page. Important: Don't use regex to extract information from the output of a this scraper tool, Think more and find answer yourself.
70
+
71
+ Args:
72
+ url (str): The URL of the web page to scrape.
73
+ response_type (str): The type of response to return. Valid options are 'markdown' and 'plain_text'. Default is 'plain_text'.
74
+
75
+ Returns:
76
+ str: The content of the web page in the specified format.
77
+ """
78
+ response = BasicScraper(timeout=5).get(url)
79
+ if response_type == 'markdown':
80
+ return response.markdown
81
+ elif response_type == 'plain_text':
82
+ return response.plain_text
83
+ else:
84
+ raise ValueError("Invalid response_type. Use 'markdown' or 'plain_text'.")
85
+
86
+ class CommitChanges(Tool):
87
+ name = "commit_changes"
88
+ description = "Commits changes to a repository."
89
+ inputs = {
90
+ "commit_message": {"type": "string", "description": "The commit message."},
91
+ "commit_description": {"type": "string", "description": "The commit description."},
92
+ }
93
+ output_type = "string"
94
+
95
+ def __init__(self, space_id, folder_path, **kwargs):
96
+ super().__init__()
97
+ self.space_id = space_id
98
+ self.folder_path = folder_path
99
+
100
+ def forward(self, commit_message: str, commit_description: str) -> str:
101
+ try:
102
+ upload_folder(
103
+ folder_path=self.folder_path,
104
+ repo_id=self.space_id,
105
+ repo_type="space",
106
+ create_pr=True,
107
+ commit_message=commit_message,
108
+ commit_description=commit_description,
109
+ )
110
+ return "Changes committed successfully."
111
+ except Exception as e:
112
+ return f"Error committing changes: {str(e)}"
113
+
114
+ from huggingface_hub import HfApi
115
+ import shutil
116
+ import os
117
+ import fnmatch
118
+ from typing import List, Dict, Any, Optional, Union
119
+ from pathlib import Path
120
+
121
+
122
+ class FileMover(Tool):
123
+ name = "move_file"
124
+ description = "Move or rename a file or directory."
125
+ inputs = {
126
+ "source": {"type": "string", "description": "Source file or directory path"},
127
+ "destination": {"type": "string", "description": "Destination path"}
128
+ }
129
+ output_type = "string"
130
+
131
+ def __init__(self, space_id, folder_path, **kwargs):
132
+ super().__init__()
133
+ self.space_id = space_id
134
+ self.folder_path = folder_path
135
+
136
+ def forward(self, source: str, destination: str) -> str:
137
+ src_path = os.path.join(self.folder_path, source)
138
+ dst_path = os.path.join(self.folder_path, destination)
139
+
140
+ if not os.path.exists(src_path):
141
+ return f"Error: Source path does not exist: {src_path}"
142
+
143
+ try:
144
+ shutil.move(src_path, dst_path)
145
+ return f"Successfully moved {src_path} to {dst_path}"
146
+ except Exception as e:
147
+ return f"Error moving {src_path}: {str(e)}"
148
+
149
+
150
+ class FileCopier(Tool):
151
+ name = "copy_file"
152
+ description = "Copy a file or directory."
153
+ inputs = {
154
+ "source": {"type": "string", "description": "Source file or directory path"},
155
+ "destination": {"type": "string", "description": "Destination path"}
156
+ }
157
+ output_type = "string"
158
+
159
+ def __init__(self, space_id, folder_path, **kwargs):
160
+ super().__init__()
161
+ self.space_id = space_id
162
+ self.folder_path = folder_path
163
+
164
+ def forward(self, source: str, destination: str) -> str:
165
+ src_path = os.path.join(self.folder_path, source)
166
+ dst_path = os.path.join(self.folder_path, destination)
167
+
168
+ if not os.path.exists(src_path):
169
+ return f"Error: Source path does not exist: {src_path}"
170
+
171
+ try:
172
+ if os.path.isdir(src_path):
173
+ shutil.copytree(src_path, dst_path)
174
+ else:
175
+ shutil.copy2(src_path, dst_path)
176
+ return f"Successfully copied {src_path} to {dst_path}"
177
+ except Exception as e:
178
+ return f"Error copying {src_path}: {str(e)}"
179
+
180
+
181
+ class FileDeleter(Tool):
182
+ name = "delete_file"
183
+ description = "Delete a file or directory."
184
+ inputs = {
185
+ "path": {"type": "string", "description": "Path to file or directory to delete"}
186
+ }
187
+ output_type = "string"
188
+
189
+ def __init__(self, space_id, folder_path, **kwargs):
190
+ super().__init__()
191
+ self.space_id = space_id
192
+ self.folder_path = folder_path
193
+
194
+ def forward(self, path: str) -> str:
195
+ full_path = os.path.join(self.folder_path, path)
196
+
197
+ if not os.path.exists(full_path):
198
+ return f"Error: Path does not exist: {full_path}"
199
+
200
+ try:
201
+ if os.path.isdir(full_path):
202
+ shutil.rmtree(full_path)
203
+ else:
204
+ os.remove(full_path)
205
+ return f"Successfully deleted: {full_path}"
206
+ except Exception as e:
207
+ return f"Error deleting {full_path}: {str(e)}"
208
+
209
+
210
+ class FileSearcher(Tool):
211
+ name = "search_files"
212
+ description = "Search for text within files in the repository."
213
+ inputs = {
214
+ "search_term": {"type": "string", "description": "Text to search for"},
215
+ "file_pattern": {"type": "string", "description": "File pattern to search in (e.g., '*.txt' for all .txt files)", "default": "*"},
216
+ "case_sensitive": {"type": "boolean", "description": "Whether the search should be case sensitive", "default": False}
217
+ }
218
+ output_type = "list"
219
+
220
+ def __init__(self, space_id, folder_path, **kwargs):
221
+ super().__init__()
222
+ self.space_id = space_id
223
+ self.folder_path = folder_path
224
+
225
+ def _find_files(self, pattern: str) -> List[str]:
226
+ """Find all files matching the pattern in the repository."""
227
+ matches = []
228
+ for root, _, filenames in os.walk(self.folder_path):
229
+ for filename in fnmatch.filter(filenames, pattern):
230
+ matches.append(os.path.join(root, filename))
231
+ return matches
232
+
233
+ def _search_in_file(self, file_path: str, search_term: str, case_sensitive: bool) -> List[Dict[str, Any]]:
234
+ """Search for the term in a single file."""
235
+ matches = []
236
+ try:
237
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
238
+ for line_num, line in enumerate(f, 1):
239
+ if not case_sensitive:
240
+ if search_term.lower() in line.lower():
241
+ matches.append({
242
+ 'file': os.path.relpath(file_path, self.folder_path),
243
+ 'line': line_num,
244
+ 'content': line.strip()
245
+ })
246
+ else:
247
+ if search_term in line:
248
+ matches.append({
249
+ 'file': os.path.relpath(file_path, self.folder_path),
250
+ 'line': line_num,
251
+ 'content': line.strip()
252
+ })
253
+ except Exception as e:
254
+ return [{'file': file_path, 'error': f"Error reading file: {str(e)}"}]
255
+ return matches
256
+
257
+ def forward(self, search_term: str, file_pattern: str = "*", case_sensitive: bool = False) -> List[Dict[str, Any]]:
258
+ if not search_term.strip():
259
+ return [{"error": "Search term cannot be empty"}]
260
+
261
+ try:
262
+ matching_files = self._find_files(file_pattern)
263
+ if not matching_files:
264
+ return [{"message": f"No files found matching pattern: {file_pattern}"}]
265
+
266
+ results = []
267
+ for file_path in matching_files:
268
+ file_matches = self._search_in_file(file_path, search_term, case_sensitive)
269
+ if file_matches:
270
+ results.extend(file_matches)
271
+
272
+ return results if results else [{"message": "No matches found"}]
273
+
274
+ except Exception as e:
275
+ return [{"error": f"Error during search: {str(e)}"}]
276
+
277
+
278
+ def get_repository_structure(space_id: str) -> str:
279
+ api = HfApi()
280
+ space_info = api.space_info(space_id)
281
+
282
+ printed_dirs = set()
283
+ output_lines = []
284
+ sorted_siblings = sorted(space_info.siblings, key=lambda x: x.rfilename)
285
+
286
+ for sibling in sorted_siblings:
287
+ rfilename = sibling.rfilename
288
+ path_parts = rfilename.split('/')
289
+
290
+ current_dir_path_parts = []
291
+ for i in range(len(path_parts) - 1):
292
+ dir_name = path_parts[i]
293
+ current_dir_path_parts.append(dir_name)
294
+ dir_path = '/'.join(current_dir_path_parts)
295
+
296
+ if dir_path not in printed_dirs:
297
+ depth = i
298
+ indent = ' ' * depth
299
+ output_lines.append(f"{indent}{dir_name}")
300
+ printed_dirs.add(dir_path)
301
+
302
+ file_name = path_parts[-1]
303
+ file_indent_depth = len(path_parts) - 1
304
+ file_indent = ' ' * file_indent_depth
305
+
306
+ if file_indent_depth == 0:
307
+ output_lines.append(file_name)
308
+ else:
309
+ is_last_file = sibling == sorted_siblings[-1] or not any(
310
+ s.rfilename.startswith('/'.join(path_parts[:-1]) + '/')
311
+ for s in sorted_siblings[sorted_siblings.index(sibling)+1:]
312
+ )
313
+ file_prefix = '└─ ' if is_last_file else '├─ '
314
+ output_lines.append(f"{file_indent}{file_prefix}{file_name}")
315
+
316
  return '\n'.join(output_lines)