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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +567 -568
app.py CHANGED
@@ -1,569 +1,568 @@
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, 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"""
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
- 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
- )
322
-
323
- # Initialize Planning Agent with improved planning focus and description
324
- planning_agent = CodeAgent(
325
- model=model,
326
- prompt_templates=planning_agent_prompt_templates,
327
- verbosity_level=1,
328
- tools=[search, scraper, read_file, write_file, commit_changes],
329
- managed_agents=[swe_agent],
330
- name="planning_agent",
331
- 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.",
332
- stream_outputs=True
333
- )
334
-
335
- return planning_agent, swe_agent
336
-
337
- def _update_space_id_and_agents(self, new_space_id: str, current_state: dict):
338
- """Handles space_id change, cleans up old temp dir, creates new, and re-initializes agents."""
339
- import gradio as gr
340
-
341
- old_temp_dir = current_state.get("temp_dir")
342
- if old_temp_dir and os.path.exists(old_temp_dir):
343
- print(f"Cleaning up old temporary directory: {old_temp_dir}")
344
- shutil.rmtree(old_temp_dir)
345
-
346
- if not new_space_id:
347
- current_state["space_id"] = None
348
- current_state["temp_dir"] = None
349
- current_state["agent"] = None
350
- current_state["file_upload_folder"] = None
351
- print("Space ID is empty. Agents are not initialized.")
352
- return new_space_id, None, None, None, gr.Textbox("Please enter a Space ID to initialize agents.", visible=True)
353
-
354
- # Create new temporary directory
355
- temp_dir = tempfile.mkdtemp(prefix=f"ai_workspace_{new_space_id.replace('/', '_')}_")
356
- file_upload_folder = Path(temp_dir) / "uploads"
357
- file_upload_folder.mkdir(parents=True, exist_ok=True)
358
-
359
- # Initialize agents
360
- planning_agent, _ = self._initialize_agents(new_space_id, temp_dir)
361
-
362
- if planning_agent is None:
363
- # Handle initialization failure
364
- shutil.rmtree(temp_dir) # Clean up the newly created temp dir
365
- current_state["space_id"] = None
366
- current_state["temp_dir"] = None
367
- current_state["agent"] = None
368
- current_state["file_upload_folder"] = None
369
- return new_space_id, None, None, None, gr.Textbox("Failed to initialize agents. Check console for errors.", visible=True)
370
-
371
-
372
- # Update session state
373
- current_state["space_id"] = new_space_id
374
- current_state["temp_dir"] = temp_dir
375
- current_state["agent"] = planning_agent
376
- current_state["file_upload_folder"] = file_upload_folder
377
-
378
- print(f"Initialized agents for Space ID: {new_space_id} in {temp_dir}")
379
- return new_space_id, [], [], file_upload_folder, gr.Textbox(f"Agents initialized for Space ID: {new_space_id}", visible=True)
380
-
381
-
382
- def interact_with_agent(self, prompt, messages, session_state):
383
- import gradio as gr
384
-
385
- agent = session_state.get("agent")
386
- if agent is None:
387
- messages.append(gr.ChatMessage(role="assistant", content="Please enter a Space ID and initialize the agents first."))
388
- yield messages
389
- return
390
-
391
- try:
392
- messages.append(gr.ChatMessage(role="user", content=prompt, metadata={"status": "done"}))
393
- yield messages
394
-
395
- for msg in stream_to_gradio(agent, task=prompt, reset_agent_memory=False):
396
- if isinstance(msg, gr.ChatMessage):
397
- messages.append(msg)
398
- elif isinstance(msg, str): # Then it's only a completion delta
399
- try:
400
- if messages[-1].metadata["status"] == "pending":
401
- messages[-1].content = msg
402
- else:
403
- messages.append(
404
- gr.ChatMessage(role="assistant", content=msg, metadata={"status": "pending"})
405
- )
406
- except Exception as e:
407
- raise e
408
- yield messages
409
-
410
- yield messages
411
- except Exception as e:
412
- print(f"Error in interaction: {str(e)}")
413
- messages.append(gr.ChatMessage(role="assistant", content=f"Error: {str(e)}"))
414
- yield messages
415
-
416
- def upload_file(self, file, file_uploads_log, session_state, allowed_file_types=None):
417
- """
418
- Handle file uploads, default allowed types are .pdf, .docx, and .txt
419
- """
420
- import gradio as gr
421
-
422
- file_upload_folder = session_state.get("file_upload_folder")
423
- if file_upload_folder is None:
424
- return gr.Textbox("Please enter a Space ID and initialize agents before uploading files.", visible=True), file_uploads_log
425
-
426
- if file is None:
427
- return gr.Textbox(value="No file uploaded", visible=True), file_uploads_log
428
-
429
- if allowed_file_types is None:
430
- allowed_file_types = [".pdf", ".docx", ".txt"]
431
-
432
- file_ext = os.path.splitext(file.name)[1].lower()
433
- if file_ext not in allowed_file_types:
434
- return gr.Textbox("File type disallowed", visible=True), file_uploads_log
435
-
436
- # Sanitize file name
437
- original_name = os.path.basename(file.name)
438
- sanitized_name = re.sub(
439
- r"[^\w\-.]", "_", original_name
440
- ) # Replace any non-alphanumeric, non-dash, or non-dot characters with underscores
441
-
442
- # Save the uploaded file to the specified folder
443
- file_path = os.path.join(file_upload_folder, os.path.basename(sanitized_name))
444
- shutil.copy(file.name, file_path)
445
-
446
- return gr.Textbox(f"File uploaded: {file_path}", visible=True), file_uploads_log + [file_path]
447
-
448
- def log_user_message(self, text_input, file_uploads_log):
449
- import gradio as gr
450
-
451
- return (
452
- text_input
453
- + (
454
- f"\nYou have been provided with these files, which might be helpful or not: {file_uploads_log}"
455
- if len(file_uploads_log) > 0
456
- else ""
457
- ),
458
- "",
459
- gr.Button(interactive=False),
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
467
-
468
- with gr.Blocks(theme="ocean", fill_height=True) as demo:
469
- # Add session state to store session-specific data
470
- session_state = gr.State({})
471
- stored_messages = gr.State([])
472
- file_uploads_log = gr.State([])
473
- space_id_state = gr.State(None) # State to hold the current space_id
474
- temp_dir_state = gr.State(None) # State to hold the current temp_dir
475
- file_upload_folder_state = gr.State(None) # State to hold the current file upload folder
476
-
477
- with gr.Sidebar():
478
- gr.Markdown(
479
- f"# {self.agent_name.replace('_', ' ').capitalize()}"
480
- "\n> This web ui allows you to interact with a `smolagents` agent that can use tools and execute steps to complete tasks."
481
- + (f"\n\n**Agent description:**\n{self.agent_description}" if self.agent_description else "")
482
- )
483
-
484
- # Add Space ID input
485
- space_id_input = gr.Textbox(
486
- label="Hugging Face Space ID",
487
- placeholder="Enter your Space ID (e.g., username/space-name)",
488
- interactive=True
489
- )
490
- initialization_status = gr.Textbox(label="Initialization Status", interactive=False, visible=True)
491
-
492
- # Trigger agent initialization when space_id changes
493
- space_id_input.change(
494
- self._update_space_id_and_agents,
495
- [space_id_input, session_state],
496
- [space_id_state, stored_messages, file_uploads_log, file_upload_folder_state, initialization_status]
497
- )
498
-
499
-
500
- with gr.Group():
501
- gr.Markdown("**Your request**", container=True)
502
- text_input = gr.Textbox(
503
- lines=3,
504
- label="Chat Message",
505
- container=False,
506
- placeholder="Enter your prompt here and press Shift+Enter or press the button",
507
- )
508
- submit_btn = gr.Button("Submit", variant="primary")
509
-
510
- gr.HTML(
511
- "<br><br><h4><center>Powered by <a target='_blank' href='https://github.com/huggingface/smolagents'><b>smolagents</b></a></center></h4>"
512
- )
513
-
514
- # Main chat interface
515
- chatbot = gr.Chatbot(
516
- label="Agent",
517
- type="messages",
518
- avatar_images=(
519
- None,
520
- "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png",
521
- ),
522
- resizeable=True,
523
- scale=1,
524
- )
525
-
526
- # Set up event handlers
527
- text_input.submit(
528
- self.log_user_message,
529
- [text_input, file_uploads_log],
530
- [stored_messages, text_input, submit_btn],
531
- ).then(self.interact_with_agent, [stored_messages, chatbot, session_state], [chatbot]).then(
532
- lambda: (
533
- gr.Textbox(
534
- interactive=True, placeholder="Enter your prompt here and press Shift+Enter or the button"
535
- ),
536
- gr.Button(interactive=True),
537
- ),
538
- None,
539
- [text_input, submit_btn],
540
- )
541
-
542
- submit_btn.click(
543
- self.log_user_message,
544
- [text_input, file_uploads_log],
545
- [stored_messages, text_input, submit_btn],
546
- ).then(self.interact_with_agent, [stored_messages, chatbot, session_state], [chatbot]).then(
547
- lambda: (
548
- gr.Textbox(
549
- interactive=True, placeholder="Enter your prompt here and press Shift+Enter or the button"
550
- ),
551
- gr.Button(interactive=True),
552
- ),
553
- None,
554
- [text_input, submit_btn],
555
- )
556
-
557
- return demo
558
-
559
- def run_gradio_agent():
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__":
568
- # The script now directly launches the Gradio UI
569
  run_gradio_agent()
 
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, 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"""
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
+ 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
+ )
322
+
323
+ # Initialize Planning Agent with improved planning focus and description
324
+ planning_agent = CodeAgent(
325
+ model=model,
326
+ prompt_templates=planning_agent_prompt_templates,
327
+ verbosity_level=1,
328
+ tools=[search, scraper, read_file, write_file, commit_changes],
329
+ managed_agents=[swe_agent],
330
+ name="planning_agent",
331
+ 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.",
332
+ stream_outputs=True
333
+ )
334
+
335
+ return planning_agent, swe_agent
336
+
337
+ def _update_space_id_and_agents(self, new_space_id: str, current_state: dict):
338
+ """Handles space_id change, cleans up old temp dir, creates new, and re-initializes agents."""
339
+ import gradio as gr
340
+
341
+ old_temp_dir = current_state.get("temp_dir")
342
+ if old_temp_dir and os.path.exists(old_temp_dir):
343
+ print(f"Cleaning up old temporary directory: {old_temp_dir}")
344
+ shutil.rmtree(old_temp_dir)
345
+
346
+ if not new_space_id:
347
+ current_state["space_id"] = None
348
+ current_state["temp_dir"] = None
349
+ current_state["agent"] = None
350
+ current_state["file_upload_folder"] = None
351
+ print("Space ID is empty. Agents are not initialized.")
352
+ return new_space_id, None, None, None, gr.Textbox("Please enter a Space ID to initialize agents.", visible=True)
353
+
354
+ # Create new temporary directory
355
+ temp_dir = tempfile.mkdtemp(prefix=f"ai_workspace_{new_space_id.replace('/', '_')}_")
356
+ file_upload_folder = Path(temp_dir) / "uploads"
357
+ file_upload_folder.mkdir(parents=True, exist_ok=True)
358
+
359
+ # Initialize agents
360
+ planning_agent, _ = self._initialize_agents(new_space_id, temp_dir)
361
+
362
+ if planning_agent is None:
363
+ # Handle initialization failure
364
+ shutil.rmtree(temp_dir) # Clean up the newly created temp dir
365
+ current_state["space_id"] = None
366
+ current_state["temp_dir"] = None
367
+ current_state["agent"] = None
368
+ current_state["file_upload_folder"] = None
369
+ return new_space_id, None, None, None, gr.Textbox("Failed to initialize agents. Check console for errors.", visible=True)
370
+
371
+
372
+ # Update session state
373
+ current_state["space_id"] = new_space_id
374
+ current_state["temp_dir"] = temp_dir
375
+ current_state["agent"] = planning_agent
376
+ current_state["file_upload_folder"] = file_upload_folder
377
+
378
+ print(f"Initialized agents for Space ID: {new_space_id} in {temp_dir}")
379
+ return new_space_id, [], [], file_upload_folder, gr.Textbox(f"Agents initialized for Space ID: {new_space_id}", visible=True)
380
+
381
+
382
+ def interact_with_agent(self, prompt, messages, session_state):
383
+ import gradio as gr
384
+
385
+ agent = session_state.get("agent")
386
+ if agent is None:
387
+ messages.append(gr.ChatMessage(role="assistant", content="Please enter a Space ID and initialize the agents first."))
388
+ yield messages
389
+ return
390
+
391
+ try:
392
+ messages.append(gr.ChatMessage(role="user", content=prompt, metadata={"status": "done"}))
393
+ yield messages
394
+
395
+ for msg in stream_to_gradio(agent, task=prompt, reset_agent_memory=False):
396
+ if isinstance(msg, gr.ChatMessage):
397
+ messages.append(msg)
398
+ elif isinstance(msg, str): # Then it's only a completion delta
399
+ try:
400
+ if messages[-1].metadata["status"] == "pending":
401
+ messages[-1].content = msg
402
+ else:
403
+ messages.append(
404
+ gr.ChatMessage(role="assistant", content=msg, metadata={"status": "pending"})
405
+ )
406
+ except Exception as e:
407
+ raise e
408
+ yield messages
409
+
410
+ yield messages
411
+ except Exception as e:
412
+ print(f"Error in interaction: {str(e)}")
413
+ messages.append(gr.ChatMessage(role="assistant", content=f"Error: {str(e)}"))
414
+ yield messages
415
+
416
+ def upload_file(self, file, file_uploads_log, session_state, allowed_file_types=None):
417
+ """
418
+ Handle file uploads, default allowed types are .pdf, .docx, and .txt
419
+ """
420
+ import gradio as gr
421
+
422
+ file_upload_folder = session_state.get("file_upload_folder")
423
+ if file_upload_folder is None:
424
+ return gr.Textbox("Please enter a Space ID and initialize agents before uploading files.", visible=True), file_uploads_log
425
+
426
+ if file is None:
427
+ return gr.Textbox(value="No file uploaded", visible=True), file_uploads_log
428
+
429
+ if allowed_file_types is None:
430
+ allowed_file_types = [".pdf", ".docx", ".txt"]
431
+
432
+ file_ext = os.path.splitext(file.name)[1].lower()
433
+ if file_ext not in allowed_file_types:
434
+ return gr.Textbox("File type disallowed", visible=True), file_uploads_log
435
+
436
+ # Sanitize file name
437
+ original_name = os.path.basename(file.name)
438
+ sanitized_name = re.sub(
439
+ r"[^\w\-.]", "_", original_name
440
+ ) # Replace any non-alphanumeric, non-dash, or non-dot characters with underscores
441
+
442
+ # Save the uploaded file to the specified folder
443
+ file_path = os.path.join(file_upload_folder, os.path.basename(sanitized_name))
444
+ shutil.copy(file.name, file_path)
445
+
446
+ return gr.Textbox(f"File uploaded: {file_path}", visible=True), file_uploads_log + [file_path]
447
+
448
+ def log_user_message(self, text_input, file_uploads_log):
449
+ import gradio as gr
450
+
451
+ return (
452
+ text_input
453
+ + (
454
+ f"\nYou have been provided with these files, which might be helpful or not: {file_uploads_log}"
455
+ if len(file_uploads_log) > 0
456
+ else ""
457
+ ),
458
+ "",
459
+ gr.Button(interactive=False),
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
467
+
468
+ with gr.Blocks(theme="ocean", fill_height=True) as demo:
469
+ # Add session state to store session-specific data
470
+ session_state = gr.State({})
471
+ stored_messages = gr.State([])
472
+ file_uploads_log = gr.State([])
473
+ space_id_state = gr.State(None) # State to hold the current space_id
474
+ temp_dir_state = gr.State(None) # State to hold the current temp_dir
475
+ file_upload_folder_state = gr.State(None) # State to hold the current file upload folder
476
+
477
+ with gr.Sidebar():
478
+ gr.Markdown(
479
+ f"# {self.agent_name.replace('_', ' ').capitalize()}"
480
+ + (f"\n\n**Agent description:**\n{self.agent_description}" if self.agent_description else "")
481
+ )
482
+
483
+ # Add Space ID input
484
+ space_id_input = gr.Textbox(
485
+ label="Hugging Face Space ID",
486
+ placeholder="Enter your Space ID (e.g., username/space-name)",
487
+ interactive=True
488
+ )
489
+ initialization_status = gr.Textbox(label="Initialization Status", interactive=False, visible=True)
490
+
491
+ # Trigger agent initialization when space_id changes
492
+ space_id_input.change(
493
+ self._update_space_id_and_agents,
494
+ [space_id_input, session_state],
495
+ [space_id_state, stored_messages, file_uploads_log, file_upload_folder_state, initialization_status]
496
+ )
497
+
498
+
499
+ with gr.Group():
500
+ gr.Markdown("**Your request**", container=True)
501
+ text_input = gr.Textbox(
502
+ lines=3,
503
+ label="Chat Message",
504
+ container=False,
505
+ placeholder="Enter your prompt here and press Shift+Enter or press the button",
506
+ )
507
+ submit_btn = gr.Button("Submit", variant="primary")
508
+
509
+ gr.HTML(
510
+ "<br><br><h4><center>Powered by <a target='_blank' href='https://github.com/huggingface/smolagents'><b>smolagents</b></a></center></h4>"
511
+ )
512
+
513
+ # Main chat interface
514
+ chatbot = gr.Chatbot(
515
+ label="Agent",
516
+ type="messages",
517
+ avatar_images=(
518
+ None,
519
+ "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png",
520
+ ),
521
+ resizeable=True,
522
+ scale=1,
523
+ )
524
+
525
+ # Set up event handlers
526
+ text_input.submit(
527
+ self.log_user_message,
528
+ [text_input, file_uploads_log],
529
+ [stored_messages, text_input, submit_btn],
530
+ ).then(self.interact_with_agent, [stored_messages, chatbot, session_state], [chatbot]).then(
531
+ lambda: (
532
+ gr.Textbox(
533
+ interactive=True, placeholder="Enter your prompt here and press Shift+Enter or the button"
534
+ ),
535
+ gr.Button(interactive=True),
536
+ ),
537
+ None,
538
+ [text_input, submit_btn],
539
+ )
540
+
541
+ submit_btn.click(
542
+ self.log_user_message,
543
+ [text_input, file_uploads_log],
544
+ [stored_messages, text_input, submit_btn],
545
+ ).then(self.interact_with_agent, [stored_messages, chatbot, session_state], [chatbot]).then(
546
+ lambda: (
547
+ gr.Textbox(
548
+ interactive=True, placeholder="Enter your prompt here and press Shift+Enter or the button"
549
+ ),
550
+ gr.Button(interactive=True),
551
+ ),
552
+ None,
553
+ [text_input, submit_btn],
554
+ )
555
+
556
+ return demo
557
+
558
+ def run_gradio_agent():
559
+ """Runs the Gradio UI for the agent."""
560
+ # The GradioUI class now handles space_id input and agent initialization internally
561
+ GradioUI(
562
+ agent_name="SmolSWE Gradio Interface",
563
+ 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."
564
+ ).launch()
565
+
566
+ if __name__ == "__main__":
567
+ # The script now directly launches the Gradio UI
 
568
  run_gradio_agent()