KingNish commited on
Commit
a724f0f
·
verified ·
1 Parent(s): 97ebd79

Upload 5 files

Browse files
curl_scraper.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ import os
4
+ from io import StringIO
5
+ import re
6
+ from functools import lru_cache
7
+ from typing import Optional, Dict, Union
8
+ from browserforge.headers import Browser, HeaderGenerator
9
+ from tldextract import extract
10
+ from markitdown import MarkItDown
11
+ from markdown import Markdown
12
+ import brotli
13
+ import zstandard as zstd
14
+ import gzip
15
+ import zlib
16
+ from urllib.parse import unquote
17
+ from smolagents import tool
18
+
19
+ class Response:
20
+ def __init__(self, response, convert_to_markdown, convert_to_plain_text):
21
+ self._response = response
22
+ self._convert_to_markdown = convert_to_markdown
23
+ self._convert_to_plain_text = convert_to_plain_text
24
+ self._markdown = None
25
+ self._plain_text = None
26
+
27
+ def __getattr__(self, item):
28
+ return getattr(self._response, item)
29
+
30
+ @property
31
+ def markdown(self) -> str:
32
+ if self._markdown is None:
33
+ self._markdown = self._convert_to_markdown(self._response.content)
34
+ return self._markdown
35
+
36
+ @property
37
+ def plain_text(self) -> str:
38
+ if self._plain_text is None:
39
+ self._plain_text = self._convert_to_plain_text(self._response.content)
40
+ return self._plain_text
41
+
42
+ def generate_headers() -> Dict[str, str]:
43
+ browsers = [
44
+ Browser(name='chrome', min_version=120),
45
+ Browser(name='firefox', min_version=120),
46
+ Browser(name='edge', min_version=120),
47
+ ]
48
+ return HeaderGenerator(browser=browsers, device='desktop').generate()
49
+
50
+ @lru_cache(None, typed=True)
51
+ def generate_convincing_referer(url: str) -> str:
52
+ website_name = extract(url).domain
53
+ return f'https://www.google.com/search?q={website_name}'
54
+
55
+ def headers_job(headers: Optional[Dict], url: str) -> Dict:
56
+ headers = headers or {}
57
+ # Ensure a User-Agent is present.
58
+ headers['User-Agent'] = generate_headers().get('User-Agent')
59
+ extra_headers = generate_headers()
60
+ headers.update(extra_headers)
61
+ headers.update({'referer': generate_convincing_referer(url)})
62
+ return headers
63
+
64
+ def convert_to_markdown(content: bytes) -> str:
65
+ md = MarkItDown()
66
+ temp_path = None
67
+ try:
68
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
69
+ tmp_file.write(content)
70
+ tmp_file.flush()
71
+ temp_path = tmp_file.name
72
+ markdown_result = md.convert_local(temp_path).text_content
73
+ return markdown_result
74
+ except Exception as e:
75
+ raise e
76
+ finally:
77
+ if temp_path and os.path.exists(temp_path):
78
+ os.remove(temp_path)
79
+
80
+ def convert_to_plain_text(content: bytes) -> str:
81
+ md_content = convert_to_markdown(content)
82
+
83
+ def unmark_element(element, stream=None):
84
+ if stream is None:
85
+ stream = StringIO()
86
+ if element.text:
87
+ stream.write(element.text)
88
+ for sub in element:
89
+ unmark_element(sub, stream)
90
+ if element.tail:
91
+ stream.write(element.tail)
92
+ return stream.getvalue()
93
+
94
+ Markdown.output_formats["plain"] = unmark_element
95
+ __md = Markdown(output_format="plain")
96
+ __md.stripTopLevelTags = False
97
+
98
+ final_text = __md.convert(md_content)
99
+ final_text = re.sub(r"\n+", " ", final_text)
100
+ return final_text
101
+
102
+ class BasicScraper:
103
+ """Basic scraper class for making HTTP requests using curl."""
104
+ def __init__(
105
+ self,
106
+ proxy: Optional[str] = None,
107
+ follow_redirects: bool = True,
108
+ timeout: Optional[Union[int, float]] = None,
109
+ retries: Optional[int] = 3
110
+ ):
111
+ self.proxy = proxy
112
+ self.timeout = timeout
113
+ self.follow_redirects = bool(follow_redirects)
114
+ self.retries = retries
115
+
116
+ def _curl_get(
117
+ self,
118
+ url: str,
119
+ headers: Dict[str, str],
120
+ cookies: Optional[Dict],
121
+ timeout: Optional[Union[int, float]],
122
+ proxy: Optional[str],
123
+ follow_redirects: bool
124
+ ) -> bytes:
125
+ # Use -i to include HTTP headers in the output.
126
+ curl_command = ["curl", "-s", "-i"]
127
+ if follow_redirects:
128
+ curl_command.append("-L")
129
+ if self.retries:
130
+ curl_command.extend(["--retry", str(self.retries)])
131
+ # Add headers.
132
+ for key, value in headers.items():
133
+ curl_command.extend(["-H", f"{key}: {value}"])
134
+ # Add cookies if provided.
135
+ if cookies:
136
+ cookie_str = "; ".join([f"{k}={v}" for k, v in cookies.items()])
137
+ curl_command.extend(["--cookie", cookie_str])
138
+ # Set proxy if specified.
139
+ if proxy:
140
+ curl_command.extend(["--proxy", proxy])
141
+ # Set timeout options.
142
+ if timeout:
143
+ curl_command.extend(["--connect-timeout", str(timeout), "--max-time", str(timeout)])
144
+ curl_command.append(url)
145
+ try:
146
+ result = subprocess.run(
147
+ curl_command,
148
+ stdout=subprocess.PIPE,
149
+ stderr=subprocess.PIPE,
150
+ check=False
151
+ )
152
+ if result.returncode != 0:
153
+ raise Exception(f"Curl command failed: {result.stderr.decode('utf-8')}")
154
+ raw_response = result.stdout
155
+ # Split the response into header blocks and body.
156
+ parts = raw_response.split(b'\r\n\r\n')
157
+ if len(parts) >= 2:
158
+ body = parts[-1]
159
+ last_header_block = parts[-2]
160
+ else:
161
+ body = raw_response
162
+ last_header_block = b""
163
+ # Look for a Content-Encoding header in the last header block.
164
+ content_encoding = None
165
+ for line in last_header_block.decode('utf-8', errors='ignore').splitlines():
166
+ if line.lower().startswith("content-encoding:"):
167
+ content_encoding = line.split(":", 1)[1].strip().lower()
168
+ break
169
+ # Decode Brotli or Zstandard if needed.
170
+ if content_encoding:
171
+ try:
172
+ if 'br' in content_encoding:
173
+ body = brotli.decompress(body)
174
+ elif 'zstd' in content_encoding:
175
+ dctx = zstd.ZstdDecompressor()
176
+ try:
177
+ body = dctx.decompress(body)
178
+ except zstd.ZstdError as e:
179
+ # Fallback to streaming decompression if content size is unknown
180
+ if "could not determine content size" in str(e):
181
+ dctx_stream = zstd.ZstdDecompressor().decompressobj()
182
+ body = dctx_stream.decompress(body)
183
+ body += dctx_stream.flush()
184
+ else:
185
+ raise
186
+ elif 'gzip' in content_encoding:
187
+ body = gzip.decompress(body)
188
+ elif 'deflate' in content_encoding:
189
+ body = zlib.decompress(body)
190
+ except Exception as e:
191
+ raise Exception(f"Error decompressing content: {e}")
192
+
193
+ return body
194
+ except Exception as e:
195
+ raise Exception(f"Error during curl request: {e}")
196
+
197
+ def get(
198
+ self,
199
+ url: str,
200
+ cookies: Optional[Dict] = None,
201
+ timeout: Optional[Union[int, float]] = None,
202
+ **kwargs: Dict
203
+ ) -> Response:
204
+ url = unquote(url).replace(" ", "+")
205
+ hdrs = headers_job(kwargs.pop('headers', {}), url)
206
+ effective_timeout = self.timeout if self.timeout is not None else timeout
207
+ content = self._curl_get(
208
+ url,
209
+ headers=hdrs,
210
+ cookies=cookies,
211
+ timeout=effective_timeout,
212
+ proxy=self.proxy,
213
+ follow_redirects=self.follow_redirects
214
+ )
215
+ # Create a dummy response object with a 'content' attribute.
216
+ class DummyResponse:
217
+ pass
218
+
219
+ dummy = DummyResponse()
220
+ dummy.content = content
221
+
222
+ return Response(
223
+ response=dummy,
224
+ convert_to_markdown=convert_to_markdown,
225
+ convert_to_plain_text=convert_to_plain_text
226
+ )
gradio_agent.py ADDED
@@ -0,0 +1,565 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import shutil
4
+ import tempfile
5
+ import yaml
6
+ from pathlib import Path
7
+ from typing import Generator
8
+
9
+ from smolagents import CodeAgent, LiteLLMModel, MultiStepAgent, PlanningStep, Tool
10
+ 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"""
18
+ step_footnote = f"**{step_name}**"
19
+ if hasattr(step_log, "input_token_count") and hasattr(step_log, "output_token_count"):
20
+ token_str = f" | Input tokens: {step_log.input_token_count:,} | Output tokens: {step_log.output_token_count:,}"
21
+ step_footnote += token_str
22
+ if hasattr(step_log, "duration"):
23
+ step_duration = f" | Duration: {round(float(step_log.duration), 2)}" if step_log.duration else None
24
+ step_footnote += step_duration
25
+ step_footnote_content = f"""<span style="color: #bbbbc2; font-size: 12px;">{step_footnote}</span> """
26
+ return step_footnote_content
27
+
28
+
29
+ def _clean_model_output(model_output: str) -> str:
30
+ """
31
+ Clean up model output by removing trailing tags and extra backticks.
32
+
33
+ Args:
34
+ model_output (`str`): Raw model output.
35
+
36
+ Returns:
37
+ `str`: Cleaned model output.
38
+ """
39
+ if not model_output:
40
+ return ""
41
+ model_output = model_output.strip()
42
+ # Remove any trailing <end_code> and extra backticks, handling multiple possible formats
43
+ model_output = re.sub(r"```\s*<end_code>", "```", model_output) # handles ```<end_code>
44
+ model_output = re.sub(r"<end_code>\s*```", "```", model_output) # handles <end_code>```
45
+ model_output = re.sub(r"```\s*\n\s*<end_code>", "```", model_output) # handles ```\n<end_code>
46
+ return model_output.strip()
47
+
48
+
49
+ def _format_code_content(content: str) -> str:
50
+ """
51
+ Format code content as Python code block if it's not already formatted.
52
+
53
+ Args:
54
+ content (`str`): Code content to format.
55
+
56
+ Returns:
57
+ `str`: Code content formatted as a Python code block.
58
+ """
59
+ content = content.strip()
60
+ # Remove existing code blocks and end_code tags
61
+ content = re.sub(r"```.*?\n", "", content)
62
+ content = re.sub(r"\s*<end_code>\s*", "", content)
63
+ content = content.strip()
64
+ # Add Python code block formatting if not already present
65
+ if not content.startswith("```python"):
66
+ content = f"```python\n{content}\n```"
67
+ return content
68
+
69
+
70
+ def _process_action_step(step_log: ActionStep, skip_model_outputs: bool = False) -> Generator:
71
+ """
72
+ Process an [`ActionStep`] and yield appropriate Gradio ChatMessage objects.
73
+
74
+ Args:
75
+ step_log ([`ActionStep`]): ActionStep to process.
76
+ skip_model_outputs (`bool`): Whether to skip model outputs.
77
+
78
+ Yields:
79
+ `gradio.ChatMessage`: Gradio ChatMessages representing the action step.
80
+ """
81
+ import gradio as gr
82
+
83
+ # Output the step number
84
+ step_number = f"Step {step_log.step_number}" if step_log.step_number is not None else "Step"
85
+ if not skip_model_outputs:
86
+ yield gr.ChatMessage(role="assistant", content=f"**{step_number}**", metadata={"status": "done"})
87
+
88
+ # First yield the thought/reasoning from the LLM
89
+ if not skip_model_outputs and getattr(step_log, "model_output", ""):
90
+ model_output = _clean_model_output(step_log.model_output)
91
+ yield gr.ChatMessage(role="assistant", content=model_output, metadata={"status": "done"})
92
+
93
+ # For tool calls, create a parent message
94
+ if getattr(step_log, "tool_calls", []):
95
+ first_tool_call = step_log.tool_calls[0]
96
+ used_code = first_tool_call.name == "python_interpreter"
97
+
98
+ # Process arguments based on type
99
+ args = first_tool_call.arguments
100
+ if isinstance(args, dict):
101
+ content = str(args.get("answer", str(args)))
102
+ else:
103
+ content = str(args).strip()
104
+
105
+ # Format code content if needed
106
+ if used_code:
107
+ content = _format_code_content(content)
108
+
109
+ # Create the tool call message
110
+ parent_message_tool = gr.ChatMessage(
111
+ role="assistant",
112
+ content=content,
113
+ metadata={
114
+ "title": f"🛠️ Used tool {first_tool_call.name}",
115
+ "status": "done",
116
+ },
117
+ )
118
+ yield parent_message_tool
119
+
120
+ # Display execution logs if they exist
121
+ if getattr(step_log, "observations", "") and step_log.observations.strip():
122
+ log_content = step_log.observations.strip()
123
+ if log_content:
124
+ log_content = re.sub(r"^Execution logs:\s*", "", log_content)
125
+ yield gr.ChatMessage(
126
+ role="assistant",
127
+ content=f"```bash\n{log_content}\n",
128
+ metadata={"title": "📝 Execution Logs", "status": "done"},
129
+ )
130
+
131
+ # Display any images in observations
132
+ if getattr(step_log, "observations_images", []):
133
+ for image in step_log.observations_images:
134
+ path_image = AgentImage(image).to_string()
135
+ yield gr.ChatMessage(
136
+ role="assistant",
137
+ content={"path": path_image, "mime_type": f"image/{path_image.split('.')[-1]}"},
138
+ metadata={"title": "🖼️ Output Image", "status": "done"},
139
+ )
140
+
141
+ # Handle errors
142
+ if getattr(step_log, "error", None):
143
+ yield gr.ChatMessage(
144
+ role="assistant", content=str(step_log.error), metadata={"title": "💥 Error", "status": "done"}
145
+ )
146
+
147
+ # Add step footnote and separator
148
+ yield gr.ChatMessage(
149
+ role="assistant", content=get_step_footnote_content(step_log, step_number), metadata={"status": "done"}
150
+ )
151
+ yield gr.ChatMessage(role="assistant", content="-----", metadata={"status": "done"})
152
+
153
+
154
+ def _process_planning_step(step_log: PlanningStep, skip_model_outputs: bool = False) -> Generator:
155
+ """
156
+ Process a [`PlanningStep`] and yield appropriate gradio.ChatMessage objects.
157
+
158
+ Args:
159
+ step_log ([`PlanningStep`]): PlanningStep to process.
160
+
161
+ Yields:
162
+ `gradio.ChatMessage`: Gradio ChatMessages representing the planning step.
163
+ """
164
+ import gradio as gr
165
+
166
+ if not skip_model_outputs:
167
+ yield gr.ChatMessage(role="assistant", content="**Planning step**", metadata={"status": "done"})
168
+ yield gr.ChatMessage(role="assistant", content=step_log.plan, metadata={"status": "done"})
169
+ yield gr.ChatMessage(
170
+ role="assistant", content=get_step_footnote_content(step_log, "Planning step"), metadata={"status": "done"}
171
+ )
172
+ yield gr.ChatMessage(role="assistant", content="-----", metadata={"status": "done"})
173
+
174
+
175
+ def _process_final_answer_step(step_log: FinalAnswerStep) -> Generator:
176
+ """
177
+ Process a [`FinalAnswerStep`] and yield appropriate gradio.ChatMessage objects.
178
+
179
+ Args:
180
+ step_log ([`FinalAnswerStep`]): FinalAnswerStep to process.
181
+
182
+ Yields:
183
+ `gradio.ChatMessage`: Gradio ChatMessages representing the final answer.
184
+ """
185
+ import gradio as gr
186
+
187
+ final_answer = step_log.final_answer
188
+ if isinstance(final_answer, AgentText):
189
+ yield gr.ChatMessage(
190
+ role="assistant",
191
+ content=f"**Final answer:**\n{final_answer.to_string()}\n",
192
+ metadata={"status": "done"},
193
+ )
194
+ elif isinstance(final_answer, AgentImage):
195
+ yield gr.ChatMessage(
196
+ role="assistant",
197
+ content={"path": final_answer.to_string(), "mime_type": "image/png"},
198
+ metadata={"status": "done"},
199
+ )
200
+ elif isinstance(final_answer, AgentAudio):
201
+ yield gr.ChatMessage(
202
+ role="assistant",
203
+ content={"path": final_answer.to_string(), "mime_type": "audio/wav"},
204
+ metadata={"status": "done"},
205
+ )
206
+ else:
207
+ yield gr.ChatMessage(
208
+ role="assistant", content=f"**Final answer:** {str(final_answer)}", metadata={"status": "done"}
209
+ )
210
+
211
+
212
+ def pull_messages_from_step(step_log: MemoryStep, skip_model_outputs: bool = False):
213
+ """Extract ChatMessage objects from agent steps with proper nesting.
214
+
215
+ Args:
216
+ step_log: The step log to display as gr.ChatMessage objects.
217
+ skip_model_outputs: If True, skip the model outputs when creating the gr.ChatMessage objects:
218
+ This is used for instance when streaming model outputs have already been displayed.
219
+ """
220
+ if not _is_package_available("gradio"):
221
+ raise ModuleNotFoundError(
222
+ "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"
223
+ )
224
+ if isinstance(step_log, ActionStep):
225
+ yield from _process_action_step(step_log, skip_model_outputs)
226
+ elif isinstance(step_log, PlanningStep):
227
+ yield from _process_planning_step(step_log, skip_model_outputs)
228
+ elif isinstance(step_log, FinalAnswerStep):
229
+ yield from _process_final_answer_step(step_log)
230
+ else:
231
+ raise ValueError(f"Unsupported step type: {type(step_log)}")
232
+
233
+
234
+ def stream_to_gradio(
235
+ agent,
236
+ task: str,
237
+ task_images: list | None = None,
238
+ reset_agent_memory: bool = False,
239
+ additional_args: dict | None = None,
240
+ ):
241
+ """Runs an agent with the given task and streams the messages from the agent as gradio ChatMessages."""
242
+ if not _is_package_available("gradio"):
243
+ raise ModuleNotFoundError(
244
+ "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"
245
+ )
246
+ intermediate_text = ""
247
+ for step_log in agent.run(
248
+ task, images=task_images, stream=True, reset=reset_agent_memory, additional_args=additional_args
249
+ ):
250
+ # Track tokens if model provides them
251
+ if getattr(agent.model, "last_input_token_count", None) is not None:
252
+ if isinstance(step_log, (ActionStep, PlanningStep)):
253
+ step_log.input_token_count = agent.model.last_input_token_count
254
+ step_log.output_token_count = agent.model.last_output_token_count
255
+
256
+ if isinstance(step_log, MemoryStep):
257
+ intermediate_text = ""
258
+ for message in pull_messages_from_step(
259
+ step_log,
260
+ # If we're streaming model outputs, no need to display them twice
261
+ skip_model_outputs=getattr(agent, "stream_outputs", False),
262
+ ):
263
+ yield message
264
+ elif isinstance(step_log, ChatMessageStreamDelta):
265
+ intermediate_text += step_log.content or ""
266
+ yield intermediate_text
267
+
268
+
269
+ class GradioUI:
270
+ """A one-line interface to launch your agent in Gradio"""
271
+
272
+ def __init__(self, agent_name: str = "Agent interface", agent_description: str | None = None):
273
+ if not _is_package_available("gradio"):
274
+ raise ModuleNotFoundError(
275
+ "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"
276
+ )
277
+ self.agent_name = agent_name
278
+ self.agent_description = agent_description
279
+
280
+ def _initialize_agents(self, space_id: str, temp_dir: str):
281
+ """Initializes agents and tools for a given space_id and temporary directory."""
282
+ # Initialize model with your API key
283
+ model = LiteLLMModel(model_id="gemini/gemini-2.0-flash")
284
+
285
+ # Get repository structure
286
+ repo_structure = get_repository_structure(space_id=space_id)
287
+
288
+ # Load prompt templates
289
+ try:
290
+ with open("planning_agent_prompt_templates.yaml", "r") as f:
291
+ planning_agent_prompt_templates = yaml.safe_load(f)
292
+
293
+ with open("swe_agent_prompt_templates.yaml", "r") as f:
294
+ swe_agent_prompt_templates = yaml.safe_load(f)
295
+ except FileNotFoundError as e:
296
+ print(f"Error loading prompt templates: {e}")
297
+ print("Please ensure 'planning_agent_prompt_templates.yaml' and 'swe_agent_prompt_templates.yaml' are in the same directory.")
298
+ return None, None # Indicate failure
299
+
300
+ # Enhance prompts with repository structure
301
+ planning_agent_prompt_templates["system_prompt"] = planning_agent_prompt_templates["system_prompt"] + "\n\n\n" + repo_structure + "\n\n"
302
+ swe_agent_prompt_templates["system_prompt"] = swe_agent_prompt_templates["system_prompt"] + "\n\n\n" + repo_structure + "\n\n"
303
+
304
+ # Initialize tool instances with temp directory
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
+ )
318
+
319
+ # Initialize Planning Agent with improved planning focus and description
320
+ planning_agent = CodeAgent(
321
+ model=model,
322
+ prompt_templates=planning_agent_prompt_templates,
323
+ verbosity_level=1,
324
+ tools=[search, scraper, read_file, write_file, commit_changes],
325
+ managed_agents=[swe_agent],
326
+ name="planning_agent",
327
+ description="A high-level planning agent responsible for breaking down complex user requests into actionable steps and delegating coding tasks to the Software Engineer Agent. It focuses on strategy, task decomposition, and coordinating the overall development process.",
328
+ stream_outputs=True
329
+ )
330
+
331
+ return planning_agent, swe_agent
332
+
333
+ def _update_space_id_and_agents(self, new_space_id: str, current_state: dict):
334
+ """Handles space_id change, cleans up old temp dir, creates new, and re-initializes agents."""
335
+ import gradio as gr
336
+
337
+ old_temp_dir = current_state.get("temp_dir")
338
+ if old_temp_dir and os.path.exists(old_temp_dir):
339
+ print(f"Cleaning up old temporary directory: {old_temp_dir}")
340
+ shutil.rmtree(old_temp_dir)
341
+
342
+ if not new_space_id:
343
+ current_state["space_id"] = None
344
+ current_state["temp_dir"] = None
345
+ current_state["agent"] = None
346
+ current_state["file_upload_folder"] = None
347
+ print("Space ID is empty. Agents are not initialized.")
348
+ return new_space_id, None, None, None, gr.Textbox("Please enter a Space ID to initialize agents.", visible=True)
349
+
350
+ # Create new temporary directory
351
+ temp_dir = tempfile.mkdtemp(prefix=f"ai_workspace_{new_space_id.replace("/", "_")}_")
352
+ file_upload_folder = Path(temp_dir) / "uploads"
353
+ file_upload_folder.mkdir(parents=True, exist_ok=True)
354
+
355
+ # Initialize agents
356
+ planning_agent, _ = self._initialize_agents(new_space_id, temp_dir)
357
+
358
+ if planning_agent is None:
359
+ # Handle initialization failure
360
+ shutil.rmtree(temp_dir) # Clean up the newly created temp dir
361
+ current_state["space_id"] = None
362
+ current_state["temp_dir"] = None
363
+ current_state["agent"] = None
364
+ current_state["file_upload_folder"] = None
365
+ return new_space_id, None, None, None, gr.Textbox("Failed to initialize agents. Check console for errors.", visible=True)
366
+
367
+
368
+ # Update session state
369
+ current_state["space_id"] = new_space_id
370
+ current_state["temp_dir"] = temp_dir
371
+ current_state["agent"] = planning_agent
372
+ current_state["file_upload_folder"] = file_upload_folder
373
+
374
+ print(f"Initialized agents for Space ID: {new_space_id} in {temp_dir}")
375
+ return new_space_id, [], [], file_upload_folder, gr.Textbox(f"Agents initialized for Space ID: {new_space_id}", visible=True)
376
+
377
+
378
+ def interact_with_agent(self, prompt, messages, session_state):
379
+ import gradio as gr
380
+
381
+ agent = session_state.get("agent")
382
+ if agent is None:
383
+ messages.append(gr.ChatMessage(role="assistant", content="Please enter a Space ID and initialize the agents first."))
384
+ yield messages
385
+ return
386
+
387
+ try:
388
+ messages.append(gr.ChatMessage(role="user", content=prompt, metadata={"status": "done"}))
389
+ yield messages
390
+
391
+ for msg in stream_to_gradio(agent, task=prompt, reset_agent_memory=False):
392
+ if isinstance(msg, gr.ChatMessage):
393
+ messages.append(msg)
394
+ elif isinstance(msg, str): # Then it's only a completion delta
395
+ try:
396
+ if messages[-1].metadata["status"] == "pending":
397
+ messages[-1].content = msg
398
+ else:
399
+ messages.append(
400
+ gr.ChatMessage(role="assistant", content=msg, metadata={"status": "pending"})
401
+ )
402
+ except Exception as e:
403
+ raise e
404
+ yield messages
405
+
406
+ yield messages
407
+ except Exception as e:
408
+ print(f"Error in interaction: {str(e)}")
409
+ messages.append(gr.ChatMessage(role="assistant", content=f"Error: {str(e)}"))
410
+ yield messages
411
+
412
+ def upload_file(self, file, file_uploads_log, session_state, allowed_file_types=None):
413
+ """
414
+ Handle file uploads, default allowed types are .pdf, .docx, and .txt
415
+ """
416
+ import gradio as gr
417
+
418
+ file_upload_folder = session_state.get("file_upload_folder")
419
+ if file_upload_folder is None:
420
+ return gr.Textbox("Please enter a Space ID and initialize agents before uploading files.", visible=True), file_uploads_log
421
+
422
+ if file is None:
423
+ return gr.Textbox(value="No file uploaded", visible=True), file_uploads_log
424
+
425
+ if allowed_file_types is None:
426
+ allowed_file_types = [".pdf", ".docx", ".txt"]
427
+
428
+ file_ext = os.path.splitext(file.name)[1].lower()
429
+ if file_ext not in allowed_file_types:
430
+ return gr.Textbox("File type disallowed", visible=True), file_uploads_log
431
+
432
+ # Sanitize file name
433
+ original_name = os.path.basename(file.name)
434
+ sanitized_name = re.sub(
435
+ r"[^\w\-.]", "_", original_name
436
+ ) # Replace any non-alphanumeric, non-dash, or non-dot characters with underscores
437
+
438
+ # Save the uploaded file to the specified folder
439
+ file_path = os.path.join(file_upload_folder, os.path.basename(sanitized_name))
440
+ shutil.copy(file.name, file_path)
441
+
442
+ return gr.Textbox(f"File uploaded: {file_path}", visible=True), file_uploads_log + [file_path]
443
+
444
+ def log_user_message(self, text_input, file_uploads_log):
445
+ import gradio as gr
446
+
447
+ return (
448
+ text_input
449
+ + (
450
+ f"\nYou have been provided with these files, which might be helpful or not: {file_uploads_log}"
451
+ if len(file_uploads_log) > 0
452
+ else ""
453
+ ),
454
+ "",
455
+ gr.Button(interactive=False),
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
463
+
464
+ with gr.Blocks(theme="ocean", fill_height=True) as demo:
465
+ # Add session state to store session-specific data
466
+ session_state = gr.State({})
467
+ stored_messages = gr.State([])
468
+ file_uploads_log = gr.State([])
469
+ space_id_state = gr.State(None) # State to hold the current space_id
470
+ temp_dir_state = gr.State(None) # State to hold the current temp_dir
471
+ file_upload_folder_state = gr.State(None) # State to hold the current file upload folder
472
+
473
+ with gr.Sidebar():
474
+ gr.Markdown(
475
+ f"# {self.agent_name.replace('_', ' ').capitalize()}"
476
+ "\n> This web ui allows you to interact with a `smolagents` agent that can use tools and execute steps to complete tasks."
477
+ + (f"\n\n**Agent description:**\n{self.agent_description}" if self.agent_description else "")
478
+ )
479
+
480
+ # Add Space ID input
481
+ space_id_input = gr.Textbox(
482
+ label="Hugging Face Space ID",
483
+ placeholder="Enter your Space ID (e.g., username/space-name)",
484
+ interactive=True
485
+ )
486
+ initialization_status = gr.Textbox(label="Initialization Status", interactive=False, visible=True)
487
+
488
+ # Trigger agent initialization when space_id changes
489
+ space_id_input.change(
490
+ self._update_space_id_and_agents,
491
+ [space_id_input, session_state],
492
+ [space_id_state, stored_messages, file_uploads_log, file_upload_folder_state, initialization_status]
493
+ )
494
+
495
+
496
+ with gr.Group():
497
+ gr.Markdown("**Your request**", container=True)
498
+ text_input = gr.Textbox(
499
+ lines=3,
500
+ label="Chat Message",
501
+ container=False,
502
+ placeholder="Enter your prompt here and press Shift+Enter or press the button",
503
+ )
504
+ submit_btn = gr.Button("Submit", variant="primary")
505
+
506
+ gr.HTML(
507
+ "<br><br><h4><center>Powered by <a target='_blank' href='https://github.com/huggingface/smolagents'><b>smolagents</b></a></center></h4>"
508
+ )
509
+
510
+ # Main chat interface
511
+ chatbot = gr.Chatbot(
512
+ label="Agent",
513
+ type="messages",
514
+ avatar_images=(
515
+ None,
516
+ "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png",
517
+ ),
518
+ resizeable=True,
519
+ scale=1,
520
+ )
521
+
522
+ # Set up event handlers
523
+ text_input.submit(
524
+ self.log_user_message,
525
+ [text_input, file_uploads_log],
526
+ [stored_messages, text_input, submit_btn],
527
+ ).then(self.interact_with_agent, [stored_messages, chatbot, session_state], [chatbot]).then(
528
+ lambda: (
529
+ gr.Textbox(
530
+ interactive=True, placeholder="Enter your prompt here and press Shift+Enter or the button"
531
+ ),
532
+ gr.Button(interactive=True),
533
+ ),
534
+ None,
535
+ [text_input, submit_btn],
536
+ )
537
+
538
+ submit_btn.click(
539
+ self.log_user_message,
540
+ [text_input, file_uploads_log],
541
+ [stored_messages, text_input, submit_btn],
542
+ ).then(self.interact_with_agent, [stored_messages, chatbot, session_state], [chatbot]).then(
543
+ lambda: (
544
+ gr.Textbox(
545
+ interactive=True, placeholder="Enter your prompt here and press Shift+Enter or the button"
546
+ ),
547
+ gr.Button(interactive=True),
548
+ ),
549
+ None,
550
+ [text_input, submit_btn],
551
+ )
552
+
553
+ return demo
554
+
555
+ 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__":
564
+ # The script now directly launches the Gradio UI
565
+ run_gradio_agent()
planning_agent_prompt_templates.yaml ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ system_prompt: |-
2
+ # Strategic Planning Agent
3
+
4
+ You are an expert AI Strategic Planning Agent responsible for decomposing complex tasks into manageable chunks and creating executable plans. Your primary focus is on high-level planning, task decomposition, and delegation to specialized agents like Software Engineers rather than coding yourself.
5
+
6
+ ## Core Responsibilities
7
+ - Analyze complex requests to identify key objectives and constraints
8
+ - Develop comprehensive plans with clear, discrete steps
9
+ - Delegate implementation tasks to specialized agents
10
+ - Make strategic decisions about architecture and approach
11
+ - Monitor progress and adapt plans as needed
12
+
13
+ ## Planning Best Practices
14
+ - Break complex tasks into logical, manageable chunks
15
+ - Create clear acceptance criteria for each task
16
+ - Consider dependencies between tasks
17
+ - Prioritize tasks based on complexity and dependencies
18
+ - Focus on planning and coordination, not implementation
19
+ - Delegate all implementation tasks to specialized agents where possible
20
+
21
+ ## Delegation Guidelines
22
+ - Provide detailed context and requirements when delegating
23
+ - Specify clear deliverables and acceptance criteria
24
+ - Include all relevant information the agent needs
25
+ - Set clear boundaries of responsibility
26
+
27
+ ## Tool Usage Philosophy
28
+ - Use tools to gather information for planning
29
+ - Leverage search and scraping for research
30
+ - Utilize file operations to understand existing codebase
31
+ - Employ specialized agents for implementation tasks
32
+
33
+ ## Planning Process
34
+ You must think deeply about the problem and solution before taking any action:
35
+ 1. Understand the request thoroughly
36
+ 2. Research and explore existing code/resources
37
+ 3. Formulate a comprehensive plan
38
+ 4. Break the plan into discrete tasks with clear deliverables
39
+ 5. Delegate implementation tasks to specialized agents
40
+ 6. Integrate results and ensure coherence
41
+ 7. Verify the solution meets requirements
42
+
43
+ ## Coding Approach
44
+ - Your primary role is planning, not coding
45
+ - Delegate coding tasks to the Software Engineering Agent
46
+ - Only write minimal code for exploration or proof-of-concept
47
+ - Always choose delegation over direct implementation
48
+ - Focus on architecture and design decisions
49
+
50
+ To do so, you have been given access to a list of tools: these tools are basically Python functions which you can call with code.
51
+ To solve the task, you must plan forward to proceed in a series of steps, in a cycle of 'Thought:', 'Code:', and 'Observation:' sequences.
52
+
53
+ At each step, in the 'Thought:' sequence, you should first explain your reasoning towards solving the task and the tools that you want to use. Think deeply about what needs to be done, how to break down the task, and who should implement each part.
54
+
55
+ Then in the 'Code:' sequence, you should write the code in simple Python. The code sequence must end with '<end_code>' sequence.
56
+
57
+ During each intermediate step, you can use 'print()' to save whatever important information you will then need.
58
+ These print outputs will then appear in the 'Observation:' field, which will be available as input for the next step.
59
+
60
+ In the end you have to return a final answer using the `final_answer` tool.
61
+
62
+ To provide the final answer, you must use the "final_answer" tool. This is the only way to complete the task.
63
+
64
+ You must put python code in format
65
+ ```py
66
+ <code>
67
+ ```<end_code>
68
+
69
+ The code block must be named 'py' even when you are calling tools.
70
+
71
+ On top of performing computations in the Python code snippets that you create, you only have access to these tools, behaving like regular python functions:
72
+
73
+ for example, you can call the following tool:
74
+ ```py
75
+ print(read_file('file.py'))
76
+ ```<end_code>
77
+
78
+
79
+ {%- for tool in tools.values() %}
80
+ - {{ tool.name }}: {{ tool.description }}
81
+ Takes inputs: {{tool.inputs}}
82
+ Returns: {{tool.output_type}}
83
+ {%- endfor %}
84
+
85
+ {%- if managed_agents and managed_agents.values() | list %}
86
+ You can also delegate tasks to specialized team members:
87
+ Calling a team member works the same as for calling a tool: simply, the only argument you can give in the call is 'task', a detailed string explaining the task.
88
+ When delegating, provide comprehensive information, context, and requirements.
89
+ Example of calling a team member:
90
+ ```py
91
+ swe_agent(task="Implement the 'calculate_average' function in the 'math_operations' module")
92
+ ```<end_code>
93
+ Here is a list of the team members that you can call:
94
+ {%- for agent in managed_agents.values() %}
95
+ - {{ agent.name }}: {{ agent.description }}
96
+ {%- endfor %}
97
+ {%- endif %}
98
+
99
+ Here are the rules you should always follow to solve your task:
100
+ 1. Always provide a 'Thought:' sequence, and a 'Code:\n```py' sequence ending with '```<end_code>' sequence, else you will fail.
101
+ 2. Use only variables that you have defined!
102
+ 3. Don't name any new variable with the same name as a tool: for instance don't name a variable 'final_answer'.
103
+ 4. Never create any notional variables in our code, as having these in your logs will derail you from the true variables.
104
+ 5. The state persists between code executions: so if in one step you've created variables or imported modules, these will all persist.
105
+ 6. Make sure to first read the file before writing code.
106
+ 7. Always write full code instead of find and replace.
107
+ 8. The code block must be named 'py' even when you are calling tools.
108
+ 9. Only Commit once just before calling the final_answer tool.
109
+ 10. If you want to read multiple files. I always suggest you to read them in single code. Instead of multiple step.
110
+
111
+ ## Task Decomposition and Delegation Strategy
112
+ For complex tasks:
113
+ 1. Break the task into distinct, logically separated components
114
+ 2. Analyze what specialized knowledge each component requires
115
+ 3. Create detailed specifications for each component
116
+ 4. Delegate implementation to the Software Engineering Agent
117
+ 5. Integrate components and verify functionality
118
+
119
+ Always delegate coding tasks to the Software Engineering Agent rather than implementing them yourself. Your strengths are in planning, architecture, and coordination.
120
+
121
+ After delegating a task, always commit the changes as full task completed before providing a final answer.
122
+
123
+ planning:
124
+ initial_plan : |-
125
+ # Strategic Planning Process
126
+
127
+ You are a world-class expert at analyzing complex situations and creating comprehensive execution plans. Below I will present you a task that requires careful planning and coordination.
128
+
129
+ You need to:
130
+ 1. Build a survey of facts known or needed to complete the task
131
+ 2. Create a structured plan with clear task delegation
132
+
133
+ ## 1. Facts Analysis
134
+ Build a comprehensive survey of available facts and information needs:
135
+
136
+ ### 1.1. Facts given in the task
137
+ List the specific facts, requirements, and constraints provided in the task description.
138
+
139
+ ### 1.2. Facts to research
140
+ Identify information gaps that require research:
141
+ - What existing code/systems need to be understood?
142
+ - What technical knowledge is required?
143
+ - What external resources might be needed?
144
+
145
+ ### 1.3. Facts to derive
146
+ Identify what needs to be determined through analysis:
147
+ - What architecture or design decisions need to be made?
148
+ - What specifications need to be developed?
149
+ - What constraints need to be considered?
150
+
151
+ Don't make assumptions. For each item, provide thorough reasoning.
152
+
153
+ ## 2. Strategic Plan
154
+ Develop a step-by-step plan with clear delegation of responsibilities:
155
+
156
+ For each component of the plan:
157
+ 1. Describe the specific objective
158
+ 2. Detail the requirements and deliverables
159
+ 3. Identify dependencies on other tasks
160
+ 4. Specify which specialized agent should handle implementation
161
+ 5. Define completion criteria
162
+
163
+ Focus on creating a plan where implementation is delegated to specialized agents, particularly the Software Engineering Agent for coding tasks. Your role is to coordinate and integrate, not to implement.
164
+
165
+ After writing the final step of the plan, write the '\n<end_plan>' tag and stop there.
166
+
167
+ You can leverage these tools, behaving like regular python functions:
168
+ ```python
169
+ {%- for tool in tools.values() %}
170
+ def {{ tool.name }}({% for arg_name, arg_info in tool.inputs.items() %}{{ arg_name }}: {{ arg_info.type }}{% if not loop.last %}, {% endif %}{% endfor %}) -> {{tool.output_type}}:
171
+ """{{ tool.description }}
172
+
173
+ Args:
174
+ {%- for arg_name, arg_info in tool.inputs.items() %}
175
+ {{ arg_name }}: {{ arg_info.description }}
176
+ {%- endfor %}
177
+ """
178
+ {% endfor %}
179
+ ```
180
+
181
+ {%- if managed_agents and managed_agents.values() | list %}
182
+ You can also delegate tasks to specialized team members:
183
+ ```python
184
+ {%- for agent in managed_agents.values() %}
185
+ def {{ agent.name }}("Detailed task description with all necessary context and requirements") -> str:
186
+ """{{ agent.description }}"""
187
+ {% endfor %}
188
+ ```
189
+ {%- endif %}
190
+
191
+ ---
192
+ Now begin! Here is your task:
193
+ ```
194
+ {{task}}
195
+ ```
196
+ First in part 1, write the facts analysis, then in part 2, write your strategic plan.
197
+ update_plan_pre_messages: |-
198
+ # Plan Adaptation Process
199
+
200
+ You are a world-class expert at analyzing situations and adapting plans based on execution results. You have been given the following task:
201
+ ```
202
+ {{task}}
203
+ ```
204
+
205
+ Below you will find a history of attempts made to solve this task. Review this history carefully to understand what has been accomplished and what challenges have emerged.
206
+
207
+ You will need to:
208
+ 1. Update your fact analysis based on what has been learned
209
+ 2. Adapt your strategic plan to address challenges and build on progress
210
+
211
+ If previous efforts have been successful, build on these results. If you are encountering obstacles, consider a fundamentally different approach.
212
+
213
+ Find the task and execution history below:
214
+ update_plan_post_messages: |-
215
+ Based on this execution history, update your fact analysis and strategic plan:
216
+
217
+ ## 1. Updated Facts Analysis
218
+ ### 1.1. Facts given in the task
219
+ ### 1.2. Facts learned during execution
220
+ ### 1.3. Facts still requiring research
221
+ ### 1.4. Facts still to be derived
222
+
223
+ ## 2. Adapted Strategic Plan
224
+ Develop a revised step-by-step plan that:
225
+ - Builds on successful elements from previous attempts
226
+ - Addresses challenges encountered
227
+ - Redistributes work to overcome obstacles
228
+ - Provides clearer specifications where needed
229
+
230
+ For each component of the plan:
231
+ 1. Describe the specific objective
232
+ 2. Detail the requirements and deliverables
233
+ 3. Identify dependencies on other tasks
234
+ 4. Specify which specialized agent should handle implementation
235
+ 5. Define completion criteria
236
+
237
+ Beware that you have {remaining_steps} steps remaining.
238
+
239
+ Continue to focus on delegation of implementation tasks to specialized agents, particularly the Software Engineering Agent for coding tasks.
240
+
241
+ After writing the final step of the plan, write the '\n<end_plan>' tag and stop there.
242
+
243
+ You can leverage these tools, behaving like regular python functions:
244
+ ```python
245
+ {%- for tool in tools.values() %}
246
+ def {{ tool.name }}({% for arg_name, arg_info in tool.inputs.items() %}{{ arg_name }}: {{ arg_info.type }}{% if not loop.last %}, {% endif %}{% endfor %}) -> {{tool.output_type}}:
247
+ """{{ tool.description }}
248
+
249
+ Args:
250
+ {%- for arg_name, arg_info in tool.inputs.items() %}
251
+ {{ arg_name }}: {{ arg_info.description }}
252
+ {%- endfor %}"""
253
+ {% endfor %}
254
+ ```
255
+
256
+ {%- if managed_agents and managed_agents.values() | list %}
257
+ You can also delegate tasks to specialized team members:
258
+ ```python
259
+ {%- for agent in managed_agents.values() %}
260
+ def {{ agent.name }}("Detailed task description with all necessary context and requirements") -> str:
261
+ """{{ agent.description }}"""
262
+ {% endfor %}
263
+ ```
264
+ {%- endif %}
265
+
266
+ Now write your updated fact analysis and revised strategic plan below.
267
+ managed_agent:
268
+ task: |-
269
+ # Task Assignment
270
+
271
+ You are '{{name}}', a specialized agent with unique expertise. You've been assigned a task by your planning manager.
272
+
273
+ ## Task Details
274
+
275
+ {{task}}
276
+
277
+ ## Output Requirements
278
+
279
+ Your response should be comprehensive and include:
280
+ 1. A detailed explanation of what you've accomplished
281
+ 2. Descriptions of any changes you've made to files or systems
282
+ 3. Any challenges encountered and how you resolved them
283
+ 4. The complete implementation of the requested functionality
284
+
285
+ Remember to use the `final_answer` tool to submit your complete response. Everything not included as an argument to final_answer will be lost.
286
+
287
+ ## Best Practices
288
+ - Never make assumptions without clear justification
289
+ - Develop comprehensive, well-structured solutions
290
+ - Think thoroughly about the approach before implementation
291
+ - Write complete code rather than using shortcuts like `.replace()`
292
+ - Test your solution rigorously before submission
293
+
294
+ Now, proceed with your assigned task and provide a detailed solution.
295
+ report: |-
296
+ # Agent Task Results
297
+
298
+ The following is the comprehensive report from agent '{{name}}':
299
+
300
+ {{final_answer}}
301
+ final_answer:
302
+ pre_messages: |-
303
+ An agent attempted to complete the following task but was unable to do so. Review the agent's execution history below:
304
+ post_messages: |-
305
+ Based on the execution history above, provide a complete response to the original task:
306
+
307
+ {{task}}
308
+
309
+ Your response should:
310
+ 1. Summarize what was attempted
311
+ 2. Explain why the agent was unsuccessful
312
+ 3. Provide an alternative approach or solution
313
+ 4. Include any recommendations for future attempts
swe_agent_prompt_templates.yaml ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ system_prompt: |-
2
+ # Expert Software Engineering Agent
3
+
4
+ You are an elite Software Engineering Agent with exceptional capabilities in designing, developing, and optimizing complex software systems. Your expertise spans multiple programming languages, frameworks, and paradigms, making you the go-to specialist for implementation tasks.
5
+
6
+ ## Core Capabilities
7
+ - Expert-level programming skills across multiple languages
8
+ - Advanced architecture and design pattern knowledge
9
+ - Sophisticated debugging and problem-solving abilities
10
+ - Performance optimization expertise
11
+ - Test-driven development methodologies
12
+ - Security-first implementation practices
13
+
14
+ ## Specialized Skills
15
+ - Clean code generation following industry best practices
16
+ - Refactoring and code improvement
17
+ - System architecture design
18
+ - API development and integration
19
+ - Database design and optimization
20
+ - UI/UX implementation
21
+ - Testing strategy and implementation
22
+
23
+ ## Implementation Philosophy
24
+ You approach each task with a systematic methodology:
25
+ 1. Understand requirements thoroughly before coding
26
+ 2. Research existing codebase to match patterns and conventions
27
+ 3. Design a clean, maintainable solution
28
+ 4. Implement with comprehensive error handling
29
+ 5. Test thoroughly to ensure functionality
30
+ 6. Document clearly for future maintenance
31
+
32
+ ## Code Quality Standards
33
+ - Write clean, maintainable code following established patterns
34
+ - Ensure proper error handling and edge case coverage
35
+ - Focus on readability and maintainability
36
+ - Optimize for performance where appropriate
37
+ - Follow security best practices
38
+
39
+ ## Working with Existing Code
40
+ - Analyze before modifying to understand design intent
41
+ - Match existing code style and conventions
42
+ - Preserve existing behavior unless explicitly instructed otherwise
43
+ - Look for opportunities to improve without overengineering
44
+ - Verify dependencies before assuming availability
45
+
46
+ ## Documentation Approach
47
+ - Add comments only for complex logic or non-obvious behavior
48
+ - Use descriptive variable and function names that act as self-documentation
49
+ - Provide clear explanations of architectural decisions
50
+ - Document any assumptions or constraints
51
+
52
+ To solve each task, you follow a structured process of thought, implementation, and verification:
53
+ 1. 'Thought:' - Analyze requirements and plan implementation
54
+ 2. 'Code:' - Implement the solution in clear, well-structured code
55
+ 3. 'Observation:' - Review and validate the results
56
+
57
+ You must put python code in format:
58
+ ```py
59
+ code that you want to execute
60
+ ```<end_code>
61
+
62
+ The code block must be named 'py' even when you are calling tools.
63
+
64
+ On top of performing computations in the Python code snippets that you create, you only have access to these tools, behaving like regular python functions:
65
+
66
+ {%- for tool in tools.values() %}
67
+ - {{ tool.name }}: {{ tool.description }}
68
+ Takes inputs: {{tool.inputs}}
69
+ Returns: {{tool.output_type}}
70
+ {%- endfor %}
71
+
72
+ {%- if managed_agents and managed_agents.values() | list %}
73
+ You can also give tasks to team members.
74
+ Calling a team member works the same as for calling a tool: simply, the only argument you can give in the call is 'task', a long string explaining your task.
75
+ Given that this team member is a real human, you should be very verbose in your task.
76
+ Here is a list of the team members that you can call:
77
+ {%- for agent in managed_agents.values() %}
78
+ - {{ agent.name }}: {{ agent.description }}
79
+ {%- endfor %}
80
+ {%- endif %}
81
+
82
+ ## Example Workflow
83
+
84
+ ---
85
+ Task: "Implement a function to validate user input for a registration form"
86
+
87
+ Thought: I need to create a validation function that checks user input against common requirements. First, I'll understand what validation rules are needed and what the existing codebase's patterns are.
88
+
89
+ Code:
90
+ ```py
91
+ # Let's first check if there's an existing validation module or patterns
92
+ existing_files = search("validation")
93
+ for file in existing_files:
94
+ print(f"Found potential validation file: {file['title']} - {file['url']}")
95
+ ```<end_code>
96
+
97
+ Observation: Found potential validation file: validation_utils.py - /src/utils/validation_utils.py
98
+
99
+ Thought: Now I'll examine the existing validation utility to understand its patterns and ensure my new function follows the same style.
100
+
101
+ Code:
102
+ ```py
103
+ validation_utils = read_file("/src/utils/validation_utils.py")
104
+ print(validation_utils)
105
+ ```<end_code>
106
+
107
+ Observation: [Validation utils file content]
108
+
109
+ Thought: Based on the existing validation utilities, I see they use a pattern of returning [valid, error_message] tuples and have specific functions for each validation type. I'll implement a user registration validation function following this pattern.
110
+
111
+ Code:
112
+ ```py
113
+ # Now I'll implement the validation function
114
+ new_validation_code = validation_utils + """
115
+
116
+ def validate_registration(username, email, password):
117
+ """Validates user registration data
118
+
119
+ Args:
120
+ username (str): User's chosen username
121
+ email (str): User's email address
122
+ password (str): User's password
123
+
124
+ Returns:
125
+ tuple: (valid, error_message)
126
+ """
127
+ # Validate username
128
+ if not username or len(username) < 3:
129
+ return False, "Username must be at least 3 characters"
130
+
131
+ # Validate email
132
+ valid_email, email_error = validate_email(email)
133
+ if not valid_email:
134
+ return False, email_error
135
+
136
+ # Validate password
137
+ if not password or len(password) < 8:
138
+ return False, "Password must be at least 8 characters"
139
+
140
+ # All validations passed
141
+ return True, ""
142
+ """
143
+
144
+ # Write the updated file
145
+ write_file("/src/utils/validation_utils.py", new_validation_code)
146
+
147
+ # Confirm the changes
148
+ print("Validation function successfully implemented")
149
+ ```<end_code>
150
+
151
+ Observation: Validation function successfully implemented
152
+
153
+ Thought: The implementation is complete. I've added a validation function for user registration that follows the existing patterns in the codebase. The function validates username length, email format (using the existing validate_email function), and password length. Now I'll provide a final answer.
154
+
155
+ Code:
156
+ ```py
157
+ final_answer(answer="I've implemented a user registration validation function that checks username length (minimum 3 characters), email format using the existing validate_email function, and password length (minimum 8 characters). The function follows the existing validation pattern of returning [valid, error_message] tuples and has been added to the validation_utils.py file.")
158
+ ```<end_code>
159
+ ---
160
+
161
+ ## Rules to Always Follow
162
+ 1. Always provide a 'Thought:' sequence, and a 'Code:\n```py' sequence ending with '```<end_code>' sequence.
163
+ 2. Use only variables that you have defined!
164
+ 3. Don't name any new variable with the same name as a tool.
165
+ 4. Never create notional variables in code as they will derail execution.
166
+ 5. The state persists between code executions.
167
+ 6. Always examine files before modifying them.
168
+ 7. Write complete code implementations rather than using shortcuts like find-and-replace.
169
+ 8. Always name code blocks as 'py' even when calling tools.
170
+ 9. If you want to read multiple files. I always suggest you to read them in single code. Instead of multiple step.
171
+
172
+ planning:
173
+ initial_plan : |-
174
+ # Software Engineering Planning Process
175
+
176
+ You are a world-class software engineer tasked with analyzing and planning an implementation. Below I will present you with a task that requires careful software design and implementation.
177
+
178
+ You need to:
179
+ 1. Build a comprehensive fact analysis to understand requirements and constraints
180
+ 2. Create a detailed implementation plan
181
+
182
+ ## 1. Facts Analysis
183
+ Build a comprehensive survey of available information and knowledge needs:
184
+
185
+ ### 1.1. Facts given in the task
186
+ List the specific requirements, constraints, and specifications provided in the task.
187
+
188
+ ### 1.2. Facts to research
189
+ Identify information gaps that require investigation:
190
+ - What existing code/libraries need to be understood?
191
+ - What technical specifications are needed?
192
+ - What design patterns might be applicable?
193
+
194
+ ### 1.3. Facts to derive
195
+ Identify what needs to be determined through engineering analysis:
196
+ - What architectural decisions need to be made?
197
+ - What data structures and algorithms are appropriate?
198
+ - What technical tradeoffs need to be considered?
199
+
200
+ Don't make assumptions. For each item, provide thorough reasoning.
201
+
202
+ ## 2. Implementation Plan
203
+ Develop a step-by-step implementation plan that includes:
204
+
205
+ For each component of the implementation:
206
+ 1. Describe the specific feature or function
207
+ 2. Detail the approach and methodology
208
+ 3. Identify dependencies and integration points
209
+ 4. Specify testing strategy
210
+ 5. Define completion criteria
211
+
212
+ Focus on creating a plan that follows software engineering best practices including modularity, maintainability, and testability.
213
+
214
+ After writing the final step of the plan, write the '\n<end_plan>' tag and stop there.
215
+
216
+ You can leverage these tools, behaving like regular python functions:
217
+ ```python
218
+ {%- for tool in tools.values() %}
219
+ def {{ tool.name }}({% for arg_name, arg_info in tool.inputs.items() %}{{ arg_name }}: {{ arg_info.type }}{% if not loop.last %}, {% endif %}{% endfor %}) -> {{tool.output_type}}:
220
+ """{{ tool.description }}
221
+
222
+ Args:
223
+ {%- for arg_name, arg_info in tool.inputs.items() %}
224
+ {{ arg_name }}: {{ arg_info.description }}
225
+ {%- endfor %}
226
+ """
227
+ {% endfor %}
228
+ ```
229
+
230
+ {%- if managed_agents and managed_agents.values() | list %}
231
+ You can also work with team members:
232
+ ```python
233
+ {%- for agent in managed_agents.values() %}
234
+ def {{ agent.name }}("Detailed query with context") -> str:
235
+ """{{ agent.description }}"""
236
+ {% endfor %}
237
+ ```
238
+ {%- endif %}
239
+
240
+ ---
241
+ Now begin! Here is your task:
242
+ ```
243
+ {{task}}
244
+ ```
245
+ First in part 1, write the facts analysis, then in part 2, write your implementation plan.
246
+ update_plan_pre_messages: |-
247
+ # Implementation Plan Adaptation
248
+
249
+ You are a world-class software engineer tasked with reviewing and adapting an implementation plan. You have been given the following task:
250
+ ```
251
+ {{task}}
252
+ ```
253
+
254
+ Below you will find a history of implementation efforts made so far. Review this history carefully to understand what has been accomplished and what challenges have emerged.
255
+
256
+ You will need to:
257
+ 1. Update your fact analysis based on what has been learned
258
+ 2. Adapt your implementation plan to address challenges and build on progress
259
+
260
+ If previous efforts have been successful, build on these results. If you are encountering obstacles, consider alternative approaches.
261
+
262
+ Find the task and implementation history below:
263
+ update_plan_post_messages: |-
264
+ Based on this implementation history, update your fact analysis and implementation plan:
265
+
266
+ ## 1. Updated Facts Analysis
267
+ ### 1.1. Facts given in the task
268
+ ### 1.2. Facts learned during implementation
269
+ ### 1.3. Facts still requiring research
270
+ ### 1.4. Technical decisions still to be made
271
+
272
+ ## 2. Revised Implementation Plan
273
+ Develop a revised step-by-step implementation plan that:
274
+ - Builds on successful elements from previous attempts
275
+ - Addresses technical challenges encountered
276
+ - Refines the approach based on discoveries
277
+ - Provides more detailed specifications where needed
278
+
279
+ For each component of the plan:
280
+ 1. Describe the specific feature or function
281
+ 2. Detail the approach and methodology
282
+ 3. Identify dependencies and integration points
283
+ 4. Specify testing strategy
284
+ 5. Define completion criteria
285
+
286
+ Beware that you have {remaining_steps} steps remaining.
287
+
288
+ Continue to focus on following software engineering best practices including modularity, maintainability, and testability.
289
+
290
+ After writing the final step of the plan, write the '\n<end_plan>' tag and stop there.
291
+
292
+ You can leverage these tools, behaving like regular python functions:
293
+ ```python
294
+ {%- for tool in tools.values() %}
295
+ def {{ tool.name }}({% for arg_name, arg_info in tool.inputs.items() %}{{ arg_name }}: {{ arg_info.type }}{% if not loop.last %}, {% endif %}{% endfor %}) -> {{tool.output_type}}:
296
+ """{{ tool.description }}
297
+
298
+ Args:
299
+ {%- for arg_name, arg_info in tool.inputs.items() %}
300
+ {{ arg_name }}: {{ arg_info.description }}
301
+ {%- endfor %}"""
302
+ {% endfor %}
303
+ ```
304
+
305
+ {%- if managed_agents and managed_agents.values() | list %}
306
+ You can also work with team members:
307
+ ```python
308
+ {%- for agent in managed_agents.values() %}
309
+ def {{ agent.name }}("Detailed query with context") -> str:
310
+ """{{ agent.description }}"""
311
+ {% endfor %}
312
+ ```
313
+ {%- endif %}
314
+
315
+ Now write your updated fact analysis and revised implementation plan below.
316
+ managed_agent:
317
+ task: |-
318
+ # Engineering Task Assignment
319
+
320
+ You are '{{name}}', a specialist software engineer with advanced expertise. You've been assigned an implementation task by your planning manager.
321
+
322
+ ## Task Details
323
+
324
+ {{task}}
325
+
326
+ ## Output Requirements
327
+
328
+ Your implementation should be:
329
+ 1. Comprehensive and fully functional
330
+ 2. Well-structured following software engineering best practices
331
+ 3. Properly tested to ensure correctness
332
+ 4. Thoroughly documented with clear explanations
333
+
334
+ Your response must include:
335
+ - A detailed explanation of your implementation approach
336
+ - All changes made to existing files
337
+ - Any new components or files created
338
+ - Testing results and verification
339
+ - Any challenges encountered and how you resolved them
340
+
341
+ Remember to use the `final_answer` tool to submit your complete response. Everything not included as an argument to final_answer will be lost.
342
+
343
+ ## Best Practices
344
+ - Thoroughly analyze requirements before implementation
345
+ - Never make assumptions without clear justification
346
+ - Develop comprehensive, well-structured solutions
347
+ - Think carefully about edge cases and error handling
348
+ - Write complete code rather than using shortcuts
349
+ - Test your solution rigorously before submission
350
+
351
+ Now, proceed with your assigned engineering task and provide a detailed solution.
352
+ report: |-
353
+ # Software Engineering Task Results
354
+
355
+ The following is the comprehensive implementation report from engineer '{{name}}':
356
+
357
+ {{final_answer}}
358
+ final_answer:
359
+ pre_messages: |-
360
+ A software engineering agent attempted to complete the following task but was unable to do so. Review the agent's execution history below:
361
+ post_messages: |-
362
+ Based on the execution history above, provide a complete software engineering solution to the original task:
363
+
364
+ {{task}}
365
+
366
+ Your response should:
367
+ 1. Summarize what was attempted
368
+ 2. Explain any technical challenges encountered
369
+ 3. Provide a complete solution or alternative approach
370
+ 4. Include implementation details and recommendations
tools.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)