LilNoobie commited on
Commit
a12b4e0
·
verified ·
1 Parent(s): 111945a

Update Gradio_UI.py

Browse files
Files changed (1) hide show
  1. Gradio_UI.py +181 -394
Gradio_UI.py CHANGED
@@ -13,368 +13,216 @@
13
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
  # See the License for the specific language governing permissions and
15
  # limitations under the License.
 
16
  import os
17
  import re
18
  import shutil
19
- from pathlib import Path
20
- from typing import Generator
21
 
22
- from smolagents.agent_types import AgentAudio, AgentImage, AgentText
23
- from smolagents.agents import MultiStepAgent, PlanningStep
24
- from smolagents.memory import ActionStep, FinalAnswerStep
25
- from smolagents.models import ChatMessageStreamDelta, MessageRole, agglomerate_stream_deltas
26
  from smolagents.utils import _is_package_available
27
 
28
 
29
- def get_step_footnote_content(step_log: ActionStep | PlanningStep, step_name: str) -> str:
30
- """Get a footnote string for a step log with duration and token information"""
31
- step_footnote = f"**{step_name}**"
32
- if step_log.token_usage is not None:
33
- step_footnote += f" | Input tokens: {step_log.token_usage.input_tokens:,} | Output tokens: {step_log.token_usage.output_tokens:,}"
34
- step_footnote += f" | Duration: {round(float(step_log.timing.duration), 2)}s" if step_log.timing.duration else ""
35
- step_footnote_content = f"""<span style="color: #bbbbc2; font-size: 12px;">{step_footnote}</span> """
36
- return step_footnote_content
37
-
38
-
39
- def _clean_model_output(model_output: str) -> str:
40
- """
41
- Clean up model output by removing trailing tags and extra backticks.
42
-
43
- Args:
44
- model_output (`str`): Raw model output.
45
-
46
- Returns:
47
- `str`: Cleaned model output.
48
- """
49
- if not model_output:
50
- return ""
51
- model_output = model_output.strip()
52
- # Remove any trailing <end_code> and extra backticks, handling multiple possible formats
53
- model_output = re.sub(r"```\s*<end_code>", "```", model_output) # handles ```<end_code>
54
- model_output = re.sub(r"<end_code>\s*```", "```", model_output) # handles <end_code>```
55
- model_output = re.sub(r"```\s*\n\s*<end_code>", "```", model_output) # handles ```\n<end_code>
56
- return model_output.strip()
57
-
58
-
59
- def _format_code_content(content: str) -> str:
60
- """
61
- Format code content as Python code block if it's not already formatted.
62
-
63
- Args:
64
- content (`str`): Code content to format.
65
-
66
- Returns:
67
- `str`: Code content formatted as a Python code block.
68
- """
69
- content = content.strip()
70
- # Remove existing code blocks and end_code tags
71
- content = re.sub(r"```.*?\n", "", content)
72
- content = re.sub(r"\s*<end_code>\s*", "", content)
73
- content = content.strip()
74
- # Add Python code block formatting if not already present
75
- if not content.startswith("```python"):
76
- content = f"```python\n{content}\n```"
77
- return content
78
-
79
-
80
- def _process_action_step(step_log: ActionStep, skip_model_outputs: bool = False) -> Generator:
81
- """
82
- Process an [`ActionStep`] and yield appropriate Gradio ChatMessage objects.
83
-
84
- Args:
85
- step_log ([`ActionStep`]): ActionStep to process.
86
- skip_model_outputs (`bool`): Whether to skip model outputs.
87
-
88
- Yields:
89
- `gradio.ChatMessage`: Gradio ChatMessages representing the action step.
90
- """
91
  import gradio as gr
92
 
93
- # Output the step number
94
- step_number = f"Step {step_log.step_number}"
95
- if not skip_model_outputs:
96
- yield gr.ChatMessage(role=MessageRole.ASSISTANT, content=f"**{step_number}**", metadata={"status": "done"})
97
-
98
- # First yield the thought/reasoning from the LLM
99
- if not skip_model_outputs and getattr(step_log, "model_output", ""):
100
- model_output = _clean_model_output(step_log.model_output)
101
- yield gr.ChatMessage(role=MessageRole.ASSISTANT, content=model_output, metadata={"status": "done"})
102
-
103
- # For tool calls, create a parent message
104
- if getattr(step_log, "tool_calls", []):
105
- first_tool_call = step_log.tool_calls[0]
106
- used_code = first_tool_call.name == "python_interpreter"
107
-
108
- # Process arguments based on type
109
- args = first_tool_call.arguments
110
- if isinstance(args, dict):
111
- content = str(args.get("answer", str(args)))
112
- else:
113
- content = str(args).strip()
114
-
115
- # Format code content if needed
116
- if used_code:
117
- content = _format_code_content(content)
118
-
119
- # Create the tool call message
120
- parent_message_tool = gr.ChatMessage(
121
- role=MessageRole.ASSISTANT,
122
- content=content,
123
- metadata={
124
- "title": f"🛠️ Used tool {first_tool_call.name}",
125
- "status": "done",
126
- },
127
- )
128
- yield parent_message_tool
129
-
130
- # Display execution logs if they exist
131
- if getattr(step_log, "observations", "") and step_log.observations.strip():
132
- log_content = step_log.observations.strip()
133
- if log_content:
134
- log_content = re.sub(r"^Execution logs:\s*", "", log_content)
135
- yield gr.ChatMessage(
136
- role=MessageRole.ASSISTANT,
137
- content=f"```bash\n{log_content}\n",
138
- metadata={"title": "📝 Execution Logs", "status": "done"},
139
- )
140
-
141
- # Display any images in observations
142
- if getattr(step_log, "observations_images", []):
143
- for image in step_log.observations_images:
144
- path_image = AgentImage(image).to_string()
145
- yield gr.ChatMessage(
146
- role=MessageRole.ASSISTANT,
147
- content={"path": path_image, "mime_type": f"image/{path_image.split('.')[-1]}"},
148
- metadata={"title": "🖼️ Output Image", "status": "done"},
149
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- # Handle errors
152
- if getattr(step_log, "error", None):
153
- yield gr.ChatMessage(
154
- role=MessageRole.ASSISTANT, content=str(step_log.error), metadata={"title": "💥 Error", "status": "done"}
155
- )
 
 
156
 
157
- # Add step footnote and separator
158
- yield gr.ChatMessage(
159
- role=MessageRole.ASSISTANT,
160
- content=get_step_footnote_content(step_log, step_number),
161
- metadata={"status": "done"},
162
- )
163
- yield gr.ChatMessage(role=MessageRole.ASSISTANT, content="-----", metadata={"status": "done"})
164
 
 
 
 
165
 
166
- def _process_planning_step(step_log: PlanningStep, skip_model_outputs: bool = False) -> Generator:
167
- """
168
- Process a [`PlanningStep`] and yield appropriate gradio.ChatMessage objects.
 
 
 
 
 
 
 
 
 
 
169
 
170
- Args:
171
- step_log ([`PlanningStep`]): PlanningStep to process.
172
 
173
- Yields:
174
- `gradio.ChatMessage`: Gradio ChatMessages representing the planning step.
175
- """
 
 
 
 
 
 
 
 
176
  import gradio as gr
177
 
178
- if not skip_model_outputs:
179
- yield gr.ChatMessage(role=MessageRole.ASSISTANT, content="**Planning step**", metadata={"status": "done"})
180
- yield gr.ChatMessage(role=MessageRole.ASSISTANT, content=step_log.plan, metadata={"status": "done"})
181
- yield gr.ChatMessage(
182
- role=MessageRole.ASSISTANT,
183
- content=get_step_footnote_content(step_log, "Planning step"),
184
- metadata={"status": "done"},
185
- )
186
- yield gr.ChatMessage(role=MessageRole.ASSISTANT, content="-----", metadata={"status": "done"})
187
 
 
 
 
 
 
 
 
 
188
 
189
- def _process_final_answer_step(step_log: FinalAnswerStep) -> Generator:
190
- """
191
- Process a [`FinalAnswerStep`] and yield appropriate gradio.ChatMessage objects.
 
192
 
193
- Args:
194
- step_log ([`FinalAnswerStep`]): FinalAnswerStep to process.
195
 
196
- Yields:
197
- `gradio.ChatMessage`: Gradio ChatMessages representing the final answer.
198
- """
199
- import gradio as gr
200
-
201
- final_answer = step_log.output
202
  if isinstance(final_answer, AgentText):
203
  yield gr.ChatMessage(
204
- role=MessageRole.ASSISTANT,
205
  content=f"**Final answer:**\n{final_answer.to_string()}\n",
206
- metadata={"status": "done"},
207
  )
208
  elif isinstance(final_answer, AgentImage):
209
  yield gr.ChatMessage(
210
- role=MessageRole.ASSISTANT,
211
  content={"path": final_answer.to_string(), "mime_type": "image/png"},
212
- metadata={"status": "done"},
213
  )
214
  elif isinstance(final_answer, AgentAudio):
215
  yield gr.ChatMessage(
216
- role=MessageRole.ASSISTANT,
217
  content={"path": final_answer.to_string(), "mime_type": "audio/wav"},
218
- metadata={"status": "done"},
219
- )
220
- else:
221
- yield gr.ChatMessage(
222
- role=MessageRole.ASSISTANT, content=f"**Final answer:** {str(final_answer)}", metadata={"status": "done"}
223
  )
224
-
225
-
226
- def pull_messages_from_step(step_log: ActionStep | PlanningStep | FinalAnswerStep, skip_model_outputs: bool = False):
227
- """Extract Gradio ChatMessage objects from agent steps with proper nesting.
228
-
229
- Args:
230
- step_log: The step log to display as gr.ChatMessage objects.
231
- skip_model_outputs: If True, skip the model outputs when creating the gr.ChatMessage objects:
232
- This is used for instance when streaming model outputs have already been displayed.
233
- """
234
- if not _is_package_available("gradio"):
235
- raise ModuleNotFoundError(
236
- "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"
237
- )
238
- if isinstance(step_log, ActionStep):
239
- yield from _process_action_step(step_log, skip_model_outputs)
240
- elif isinstance(step_log, PlanningStep):
241
- yield from _process_planning_step(step_log, skip_model_outputs)
242
- elif isinstance(step_log, FinalAnswerStep):
243
- yield from _process_final_answer_step(step_log)
244
  else:
245
- raise ValueError(f"Unsupported step type: {type(step_log)}")
246
-
247
-
248
- def stream_to_gradio(
249
- agent,
250
- task: str,
251
- task_images: list | None = None,
252
- reset_agent_memory: bool = False,
253
- additional_args: dict | None = None,
254
- ) -> Generator:
255
- """Runs an agent with the given task and streams the messages from the agent as gradio ChatMessages."""
256
-
257
- if not _is_package_available("gradio"):
258
- raise ModuleNotFoundError(
259
- "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"
260
- )
261
- accumulated_events: list[ChatMessageStreamDelta] = []
262
- for event in agent.run(
263
- task, images=task_images, stream=True, reset=reset_agent_memory, additional_args=additional_args
264
- ):
265
- if isinstance(event, ActionStep | PlanningStep | FinalAnswerStep):
266
- for message in pull_messages_from_step(
267
- event,
268
- # If we're streaming model outputs, no need to display them twice
269
- skip_model_outputs=getattr(agent, "stream_outputs", False),
270
- ):
271
- yield message
272
- accumulated_events = []
273
- elif isinstance(event, ChatMessageStreamDelta):
274
- accumulated_events.append(event)
275
- text = agglomerate_stream_deltas(accumulated_events).render_as_markdown()
276
- yield text
277
 
278
 
279
  class GradioUI:
280
- """
281
- Gradio interface for interacting with a [`MultiStepAgent`].
282
-
283
- This class provides a web interface to interact with the agent in real-time, allowing users to submit prompts, upload files, and receive responses in a chat-like format.
284
- It can reset the agent's memory at the start of each interaction if desired.
285
- It supports file uploads, which are saved to a specified folder.
286
- It uses the [`gradio.Chatbot`] component to display the conversation history.
287
- This class requires the `gradio` extra to be installed: `pip install 'smolagents[gradio]'`.
288
-
289
- Args:
290
- agent ([`MultiStepAgent`]): The agent to interact with.
291
- file_upload_folder (`str`, *optional*): The folder where uploaded files will be saved.
292
- If not provided, file uploads are disabled.
293
- reset_agent_memory (`bool`, *optional*, defaults to `False`): Whether to reset the agent's memory at the start of each interaction.
294
- If `True`, the agent will not remember previous interactions.
295
-
296
- Raises:
297
- ModuleNotFoundError: If the `gradio` extra is not installed.
298
-
299
- Example:
300
- ```python
301
- from smolagents import CodeAgent, GradioUI, InferenceClientModel
302
-
303
- model = InferenceClientModel(model_id="meta-llama/Meta-Llama-3.1-8B-Instruct")
304
- agent = CodeAgent(tools=[], model=model)
305
- gradio_ui = GradioUI(agent, file_upload_folder="uploads", reset_agent_memory=True)
306
- gradio_ui.launch()
307
- ```
308
- """
309
-
310
- def __init__(self, agent: MultiStepAgent, file_upload_folder: str | None = None, reset_agent_memory: bool = False):
311
  if not _is_package_available("gradio"):
312
  raise ModuleNotFoundError(
313
  "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"
314
  )
315
  self.agent = agent
316
- self.file_upload_folder = Path(file_upload_folder) if file_upload_folder is not None else None
317
- self.reset_agent_memory = reset_agent_memory
318
- self.name = getattr(agent, "name") or "Agent interface"
319
- self.description = getattr(agent, "description", None)
320
  if self.file_upload_folder is not None:
321
- if not self.file_upload_folder.exists():
322
- self.file_upload_folder.mkdir(parents=True, exist_ok=True)
323
 
324
- def interact_with_agent(self, prompt, messages, session_state):
325
  import gradio as gr
326
 
327
- # Get the agent type from the template agent
328
- if "agent" not in session_state:
329
- session_state["agent"] = self.agent
330
-
331
- try:
332
- messages.append(gr.ChatMessage(role="user", content=prompt, metadata={"status": "done"}))
333
  yield messages
334
-
335
- for msg in stream_to_gradio(
336
- session_state["agent"], task=prompt, reset_agent_memory=self.reset_agent_memory
337
- ):
338
- if isinstance(msg, gr.ChatMessage):
339
- messages[-1].metadata["status"] = "done"
340
- messages.append(msg)
341
- elif isinstance(msg, str): # Then it's only a completion delta
342
- msg = msg.replace("<", r"\<").replace(">", r"\>") # HTML tags seem to break Gradio Chatbot
343
- if messages[-1].metadata["status"] == "pending":
344
- messages[-1].content = msg
345
- else:
346
- messages.append(
347
- gr.ChatMessage(role=MessageRole.ASSISTANT, content=msg, metadata={"status": "pending"})
348
- )
349
- yield messages
350
-
351
- yield messages
352
- except Exception as e:
353
- yield messages
354
- raise gr.Error(f"Error in interaction: {str(e)}")
355
-
356
- def upload_file(self, file, file_uploads_log, allowed_file_types=None):
357
  """
358
- Upload a file and add it to the list of uploaded files in the session state.
359
-
360
- The file is saved to the `self.file_upload_folder` folder.
361
- If the file type is not allowed, it returns a message indicating the disallowed file type.
362
-
363
- Args:
364
- file (`gradio.File`): The uploaded file.
365
- file_uploads_log (`list`): A list to log uploaded files.
366
- allowed_file_types (`list`, *optional*): List of allowed file extensions. Defaults to [".pdf", ".docx", ".txt"].
367
  """
368
  import gradio as gr
369
 
370
  if file is None:
371
- return gr.Textbox(value="No file uploaded", visible=True), file_uploads_log
372
 
373
- if allowed_file_types is None:
374
- allowed_file_types = [".pdf", ".docx", ".txt"]
 
 
375
 
376
- file_ext = os.path.splitext(file.name)[1].lower()
377
- if file_ext not in allowed_file_types:
378
  return gr.Textbox("File type disallowed", visible=True), file_uploads_log
379
 
380
  # Sanitize file name
@@ -383,6 +231,16 @@ class GradioUI:
383
  r"[^\w\-.]", "_", original_name
384
  ) # Replace any non-alphanumeric, non-dash, or non-dot characters with underscores
385
 
 
 
 
 
 
 
 
 
 
 
386
  # Save the uploaded file to the specified folder
387
  file_path = os.path.join(self.file_upload_folder, os.path.basename(sanitized_name))
388
  shutil.copy(file.name, file_path)
@@ -390,8 +248,6 @@ class GradioUI:
390
  return gr.Textbox(f"File uploaded: {file_path}", visible=True), file_uploads_log + [file_path]
391
 
392
  def log_user_message(self, text_input, file_uploads_log):
393
- import gradio as gr
394
-
395
  return (
396
  text_input
397
  + (
@@ -400,110 +256,41 @@ class GradioUI:
400
  else ""
401
  ),
402
  "",
403
- gr.Button(interactive=False),
404
  )
405
 
406
- def launch(self, share: bool = True, **kwargs):
407
- """
408
- Launch the Gradio app with the agent interface.
409
-
410
- Args:
411
- share (`bool`, defaults to `True`): Whether to share the app publicly.
412
- **kwargs: Additional keyword arguments to pass to the Gradio launch method.
413
- """
414
- self.create_app().launch(debug=True, share=share, **kwargs)
415
-
416
- def create_app(self):
417
  import gradio as gr
418
 
419
- with gr.Blocks as demo:
420
- # Add session state to store session-specific data
421
- session_state = gr.State({})
422
  stored_messages = gr.State([])
423
  file_uploads_log = gr.State([])
424
-
425
- with gr.Sidebar():
426
- gr.Markdown(
427
- f"# {self.name.replace('_', ' ').capitalize()}"
428
- "\n> This web ui allows you to interact with a `smolagents` agent that can use tools and execute steps to complete tasks."
429
- + (f"\n\n**Agent description:**\n{self.description}" if self.description else "")
430
- )
431
-
432
- with gr.Group():
433
- gr.Markdown("**Your request**", container=True)
434
- text_input = gr.Textbox(
435
- lines=3,
436
- label="Chat Message",
437
- container=False,
438
- placeholder="Enter your prompt here and press Shift+Enter or press the button",
439
- )
440
- submit_btn = gr.Button("Submit", variant="primary")
441
-
442
- # If an upload folder is provided, enable the upload feature
443
- if self.file_upload_folder is not None:
444
- upload_file = gr.File(label="Upload a file")
445
- upload_status = gr.Textbox(label="Upload Status", interactive=False, visible=False)
446
- upload_file.change(
447
- self.upload_file,
448
- [upload_file, file_uploads_log],
449
- [upload_status, file_uploads_log],
450
- )
451
-
452
- gr.HTML(
453
- "<br><br><h4><center>Powered by <a target='_blank' href='https://github.com/huggingface/smolagents'><b>smolagents</b></a></center></h4>"
454
- )
455
-
456
- # Main chat interface
457
  chatbot = gr.Chatbot(
458
  label="Agent",
459
  type="messages",
460
  avatar_images=(
461
  None,
462
- "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png",
463
  ),
464
  resizeable=True,
465
  scale=1,
466
- latex_delimiters=[
467
- {"left": r"$$", "right": r"$$", "display": True},
468
- {"left": r"$", "right": r"$", "display": False},
469
- {"left": r"\[", "right": r"\]", "display": True},
470
- {"left": r"\(", "right": r"\)", "display": False},
471
- ],
472
  )
473
-
474
- # Set up event handlers
 
 
 
 
 
 
 
 
475
  text_input.submit(
476
  self.log_user_message,
477
  [text_input, file_uploads_log],
478
- [stored_messages, text_input, submit_btn],
479
- ).then(self.interact_with_agent, [stored_messages, chatbot, session_state], [chatbot]).then(
480
- lambda: (
481
- gr.Textbox(
482
- interactive=True, placeholder="Enter your prompt here and press Shift+Enter or the button"
483
- ),
484
- gr.Button(interactive=True),
485
- ),
486
- None,
487
- [text_input, submit_btn],
488
- )
489
-
490
- submit_btn.click(
491
- self.log_user_message,
492
- [text_input, file_uploads_log],
493
- [stored_messages, text_input, submit_btn],
494
- ).then(self.interact_with_agent, [stored_messages, chatbot, session_state], [chatbot]).then(
495
- lambda: (
496
- gr.Textbox(
497
- interactive=True, placeholder="Enter your prompt here and press Shift+Enter or the button"
498
- ),
499
- gr.Button(interactive=True),
500
- ),
501
- None,
502
- [text_input, submit_btn],
503
- )
504
 
505
- chatbot.clear(self.agent.memory.reset)
506
- return demo
507
 
508
 
509
  __all__ = ["stream_to_gradio", "GradioUI"]
 
13
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
  # See the License for the specific language governing permissions and
15
  # limitations under the License.
16
+ import mimetypes
17
  import os
18
  import re
19
  import shutil
20
+ from typing import Optional
 
21
 
22
+ from smolagents.agent_types import AgentAudio, AgentImage, AgentText, handle_agent_output_types
23
+ from smolagents.agents import ActionStep, MultiStepAgent
24
+ from smolagents.memory import MemoryStep
 
25
  from smolagents.utils import _is_package_available
26
 
27
 
28
+ def pull_messages_from_step(
29
+ step_log: MemoryStep,
30
+ ):
31
+ """Extract ChatMessage objects from agent steps with proper nesting"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  import gradio as gr
33
 
34
+ if isinstance(step_log, ActionStep):
35
+ # Output the step number
36
+ step_number = f"Step {step_log.step_number}" if step_log.step_number is not None else ""
37
+ yield gr.ChatMessage(role="assistant", content=f"**{step_number}**")
38
+
39
+ # First yield the thought/reasoning from the LLM
40
+ if hasattr(step_log, "model_output") and step_log.model_output is not None:
41
+ # Clean up the LLM output
42
+ model_output = step_log.model_output.strip()
43
+ # Remove any trailing <end_code> and extra backticks, handling multiple possible formats
44
+ model_output = re.sub(r"```\s*<end_code>", "```", model_output) # handles ```<end_code>
45
+ model_output = re.sub(r"<end_code>\s*```", "```", model_output) # handles <end_code>```
46
+ model_output = re.sub(r"```\s*\n\s*<end_code>", "```", model_output) # handles ```\n<end_code>
47
+ model_output = model_output.strip()
48
+ yield gr.ChatMessage(role="assistant", content=model_output)
49
+
50
+ # For tool calls, create a parent message
51
+ if hasattr(step_log, "tool_calls") and step_log.tool_calls is not None:
52
+ first_tool_call = step_log.tool_calls[0]
53
+ used_code = first_tool_call.name == "python_interpreter"
54
+ parent_id = f"call_{len(step_log.tool_calls)}"
55
+
56
+ # Tool call becomes the parent message with timing info
57
+ # First we will handle arguments based on type
58
+ args = first_tool_call.arguments
59
+ if isinstance(args, dict):
60
+ content = str(args.get("answer", str(args)))
61
+ else:
62
+ content = str(args).strip()
63
+
64
+ if used_code:
65
+ # Clean up the content by removing any end code tags
66
+ content = re.sub(r"```.*?\n", "", content) # Remove existing code blocks
67
+ content = re.sub(r"\s*<end_code>\s*", "", content) # Remove end_code tags
68
+ content = content.strip()
69
+ if not content.startswith("```python"):
70
+ content = f"```python\n{content}\n```"
71
+
72
+ parent_message_tool = gr.ChatMessage(
73
+ role="assistant",
74
+ content=content,
75
+ metadata={
76
+ "title": f"🛠️ Used tool {first_tool_call.name}",
77
+ "id": parent_id,
78
+ "status": "pending",
79
+ },
 
 
 
 
 
 
 
 
 
 
80
  )
81
+ yield parent_message_tool
82
+
83
+ # Nesting execution logs under the tool call if they exist
84
+ if hasattr(step_log, "observations") and (
85
+ step_log.observations is not None and step_log.observations.strip()
86
+ ): # Only yield execution logs if there's actual content
87
+ log_content = step_log.observations.strip()
88
+ if log_content:
89
+ log_content = re.sub(r"^Execution logs:\s*", "", log_content)
90
+ yield gr.ChatMessage(
91
+ role="assistant",
92
+ content=f"{log_content}",
93
+ metadata={"title": "📝 Execution Logs", "parent_id": parent_id, "status": "done"},
94
+ )
95
 
96
+ # Nesting any errors under the tool call
97
+ if hasattr(step_log, "error") and step_log.error is not None:
98
+ yield gr.ChatMessage(
99
+ role="assistant",
100
+ content=str(step_log.error),
101
+ metadata={"title": "💥 Error", "parent_id": parent_id, "status": "done"},
102
+ )
103
 
104
+ # Update parent message metadata to done status without yielding a new message
105
+ parent_message_tool.metadata["status"] = "done"
 
 
 
 
 
106
 
107
+ # Handle standalone errors but not from tool calls
108
+ elif hasattr(step_log, "error") and step_log.error is not None:
109
+ yield gr.ChatMessage(role="assistant", content=str(step_log.error), metadata={"title": "💥 Error"})
110
 
111
+ # Calculate duration and token information
112
+ step_footnote = f"{step_number}"
113
+ if hasattr(step_log, "input_token_count") and hasattr(step_log, "output_token_count"):
114
+ token_str = (
115
+ f" | Input-tokens:{step_log.input_token_count:,} | Output-tokens:{step_log.output_token_count:,}"
116
+ )
117
+ step_footnote += token_str
118
+ if hasattr(step_log, "duration"):
119
+ step_duration = f" | Duration: {round(float(step_log.duration), 2)}" if step_log.duration else None
120
+ step_footnote += step_duration
121
+ step_footnote = f"""<span style="color: #bbbbc2; font-size: 12px;">{step_footnote}</span> """
122
+ yield gr.ChatMessage(role="assistant", content=f"{step_footnote}")
123
+ yield gr.ChatMessage(role="assistant", content="-----")
124
 
 
 
125
 
126
+ def stream_to_gradio(
127
+ agent,
128
+ task: str,
129
+ reset_agent_memory: bool = False,
130
+ additional_args: Optional[dict] = None,
131
+ ):
132
+ """Runs an agent with the given task and streams the messages from the agent as gradio ChatMessages."""
133
+ if not _is_package_available("gradio"):
134
+ raise ModuleNotFoundError(
135
+ "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"
136
+ )
137
  import gradio as gr
138
 
139
+ total_input_tokens = 0
140
+ total_output_tokens = 0
 
 
 
 
 
 
 
141
 
142
+ for step_log in agent.run(task, stream=True, reset=reset_agent_memory, additional_args=additional_args):
143
+ # Track tokens if model provides them
144
+ if hasattr(agent.model, "last_input_token_count"):
145
+ total_input_tokens += agent.model.last_input_token_count
146
+ total_output_tokens += agent.model.last_output_token_count
147
+ if isinstance(step_log, ActionStep):
148
+ step_log.input_token_count = agent.model.last_input_token_count
149
+ step_log.output_token_count = agent.model.last_output_token_count
150
 
151
+ for message in pull_messages_from_step(
152
+ step_log,
153
+ ):
154
+ yield message
155
 
156
+ final_answer = step_log # Last log is the run's final_answer
157
+ final_answer = handle_agent_output_types(final_answer)
158
 
 
 
 
 
 
 
159
  if isinstance(final_answer, AgentText):
160
  yield gr.ChatMessage(
161
+ role="assistant",
162
  content=f"**Final answer:**\n{final_answer.to_string()}\n",
 
163
  )
164
  elif isinstance(final_answer, AgentImage):
165
  yield gr.ChatMessage(
166
+ role="assistant",
167
  content={"path": final_answer.to_string(), "mime_type": "image/png"},
 
168
  )
169
  elif isinstance(final_answer, AgentAudio):
170
  yield gr.ChatMessage(
171
+ role="assistant",
172
  content={"path": final_answer.to_string(), "mime_type": "audio/wav"},
 
 
 
 
 
173
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  else:
175
+ yield gr.ChatMessage(role="assistant", content=f"**Final answer:** {str(final_answer)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
 
178
  class GradioUI:
179
+ """A one-line interface to launch your agent in Gradio"""
180
+
181
+ def __init__(self, agent: MultiStepAgent, file_upload_folder: str | None = None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  if not _is_package_available("gradio"):
183
  raise ModuleNotFoundError(
184
  "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"
185
  )
186
  self.agent = agent
187
+ self.file_upload_folder = file_upload_folder
 
 
 
188
  if self.file_upload_folder is not None:
189
+ if not os.path.exists(file_upload_folder):
190
+ os.mkdir(file_upload_folder)
191
 
192
+ def interact_with_agent(self, prompt, messages):
193
  import gradio as gr
194
 
195
+ messages.append(gr.ChatMessage(role="user", content=prompt))
196
+ yield messages
197
+ for msg in stream_to_gradio(self.agent, task=prompt, reset_agent_memory=False):
198
+ messages.append(msg)
 
 
199
  yield messages
200
+ yield messages
201
+
202
+ def upload_file(
203
+ self,
204
+ file,
205
+ file_uploads_log,
206
+ allowed_file_types=[
207
+ "application/pdf",
208
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
209
+ "text/plain",
210
+ ],
211
+ ):
 
 
 
 
 
 
 
 
 
 
 
212
  """
213
+ Handle file uploads, default allowed types are .pdf, .docx, and .txt
 
 
 
 
 
 
 
 
214
  """
215
  import gradio as gr
216
 
217
  if file is None:
218
+ return gr.Textbox("No file uploaded", visible=True), file_uploads_log
219
 
220
+ try:
221
+ mime_type, _ = mimetypes.guess_type(file.name)
222
+ except Exception as e:
223
+ return gr.Textbox(f"Error: {e}", visible=True), file_uploads_log
224
 
225
+ if mime_type not in allowed_file_types:
 
226
  return gr.Textbox("File type disallowed", visible=True), file_uploads_log
227
 
228
  # Sanitize file name
 
231
  r"[^\w\-.]", "_", original_name
232
  ) # Replace any non-alphanumeric, non-dash, or non-dot characters with underscores
233
 
234
+ type_to_ext = {}
235
+ for ext, t in mimetypes.types_map.items():
236
+ if t not in type_to_ext:
237
+ type_to_ext[t] = ext
238
+
239
+ # Ensure the extension correlates to the mime type
240
+ sanitized_name = sanitized_name.split(".")[:-1]
241
+ sanitized_name.append("" + type_to_ext[mime_type])
242
+ sanitized_name = "".join(sanitized_name)
243
+
244
  # Save the uploaded file to the specified folder
245
  file_path = os.path.join(self.file_upload_folder, os.path.basename(sanitized_name))
246
  shutil.copy(file.name, file_path)
 
248
  return gr.Textbox(f"File uploaded: {file_path}", visible=True), file_uploads_log + [file_path]
249
 
250
  def log_user_message(self, text_input, file_uploads_log):
 
 
251
  return (
252
  text_input
253
  + (
 
256
  else ""
257
  ),
258
  "",
 
259
  )
260
 
261
+ def launch(self, **kwargs):
 
 
 
 
 
 
 
 
 
 
262
  import gradio as gr
263
 
264
+ with gr.Blocks(fill_height=True) as demo:
 
 
265
  stored_messages = gr.State([])
266
  file_uploads_log = gr.State([])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  chatbot = gr.Chatbot(
268
  label="Agent",
269
  type="messages",
270
  avatar_images=(
271
  None,
272
+ "https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/communication/Alfred.png",
273
  ),
274
  resizeable=True,
275
  scale=1,
 
 
 
 
 
 
276
  )
277
+ # If an upload folder is provided, enable the upload feature
278
+ if self.file_upload_folder is not None:
279
+ upload_file = gr.File(label="Upload a file")
280
+ upload_status = gr.Textbox(label="Upload Status", interactive=False, visible=False)
281
+ upload_file.change(
282
+ self.upload_file,
283
+ [upload_file, file_uploads_log],
284
+ [upload_status, file_uploads_log],
285
+ )
286
+ text_input = gr.Textbox(lines=1, label="Chat Message")
287
  text_input.submit(
288
  self.log_user_message,
289
  [text_input, file_uploads_log],
290
+ [stored_messages, text_input],
291
+ ).then(self.interact_with_agent, [stored_messages, chatbot], [chatbot])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
+ demo.launch(debug=True, share=True, **kwargs)
 
294
 
295
 
296
  __all__ = ["stream_to_gradio", "GradioUI"]