smolSWE commited on
Commit
10ce13a
·
verified ·
1 Parent(s): e3a05b5

Fix TypeError in _update_space_id_and_agents function

Browse files

Corrected the replace method usage in the _update_space_id_and_agents function to avoid TypeError when creating the temporary directory prefix.

Files changed (1) hide show
  1. app.py +592 -565
app.py CHANGED
@@ -1,565 +1,592 @@
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()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
557
+ # Test the function with a space ID containing '/'
558
+ test_space_id_1 = "user/space"
559
+ test_ui = GradioUI(agent_name="Test", agent_description="Test")
560
+ test_state = {}
561
+ space_id, _, _, _, _ = test_ui._update_space_id_and_agents(test_space_id_1, test_state)
562
+ print(f"Test with space ID containing '/': {space_id}, temp_dir: {test_state.get('temp_dir')}")
563
+
564
+ # Test the function with a None space ID
565
+ test_space_id_2 = None
566
+ test_state = {}
567
+ space_id, _, _, _, _ = test_ui._update_space_id_and_agents(test_space_id_2, test_state)
568
+ print(f"Test with None space ID: {space_id}, temp_dir: {test_state.get('temp_dir')}")
569
+ :
570
+ """Runs the Gradio UI for the agent."""
571
+ # The GradioUI class now handles space_id input and agent initialization internally
572
+ GradioUI(
573
+ agent_name="SmolAgents Gradio Interface",
574
+ agent_description="Interact with a SmolAgents planning agent capable of code generation and execution."
575
+ ).launch()
576
+
577
+ if __name__ == "__main__":
578
+ # The script now directly launches the Gradio UI
579
+ run_gradio_agent()
580
+
581
+ # Test the function with a space ID containing '/'
582
+ test_space_id_1 = "user/space"
583
+ test_ui = GradioUI(agent_name="Test", agent_description="Test")
584
+ test_state = {}
585
+ space_id, _, _, _, _ = test_ui._update_space_id_and_agents(test_space_id_1, test_state)
586
+ print(f"Test with space ID containing '/': {space_id}, temp_dir: {test_state.get('temp_dir')}")
587
+
588
+ # Test the function with a None space ID
589
+ test_space_id_2 = None
590
+ test_state = {}
591
+ space_id, _, _, _, _ = test_ui._update_space_id_and_agents(test_space_id_2, test_state)
592
+ print(f"Test with None space ID: {space_id}, temp_dir: {test_state.get('temp_dir')}")