kernel-memory-dump/HuggingFaceAgentsCourse_SmolAgent1
6
1#!/usr/bin/env python2# coding=utf-83# Copyright 2024 The HuggingFace Inc. team. All rights reserved.4#5# Licensed under the Apache License, Version 2.0 (the "License");6# you may not use this file except in compliance with the License.7# You may obtain a copy of the License at8#9# http://www.apache.org/licenses/LICENSE-2.010#11# Unless required by applicable law or agreed to in writing, software12# distributed under the License is distributed on an "AS IS" BASIS,13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.14# See the License for the specific language governing permissions and15# limitations under the License.16import mimetypes17import os18import re19import shutil20from typing import Optional21 22from smolagents.agent_types import (23 AgentAudio,24 AgentImage,25 AgentText,26 handle_agent_output_types,27)28from smolagents.agents import ActionStep, MultiStepAgent29from smolagents.memory import MemoryStep30from smolagents.utils import _is_package_available31 32 33def pull_messages_from_step(34 step_log: MemoryStep,35):36 """Extract ChatMessage objects from agent steps with proper nesting"""37 import gradio as gr38 39 if isinstance(step_log, ActionStep):40 # Output the step number41 step_number = (42 f"Step {step_log.step_number}" if step_log.step_number is not None else ""43 )44 yield gr.ChatMessage(role="assistant", content=f"**{step_number}**")45 46 # First yield the thought/reasoning from the LLM47 if hasattr(step_log, "model_output") and step_log.model_output is not None:48 # Clean up the LLM output49 model_output = step_log.model_output.strip()50 # Remove any trailing <end_code> and extra backticks, handling multiple possible formats51 model_output = re.sub(52 r"```\s*<end_code>", "```", model_output53 ) # handles ```<end_code>54 model_output = re.sub(55 r"<end_code>\s*```", "```", model_output56 ) # handles <end_code>```57 model_output = re.sub(58 r"```\s*\n\s*<end_code>", "```", model_output59 ) # handles ```\n<end_code>60 model_output = model_output.strip()61 yield gr.ChatMessage(role="assistant", content=model_output)62 63 # For tool calls, create a parent message64 if hasattr(step_log, "tool_calls") and step_log.tool_calls is not None:65 first_tool_call = step_log.tool_calls[0]66 used_code = first_tool_call.name == "python_interpreter"67 parent_id = f"call_{len(step_log.tool_calls)}"68 69 # Tool call becomes the parent message with timing info70 # First we will handle arguments based on type71 args = first_tool_call.arguments72 if isinstance(args, dict):73 content = str(args.get("answer", str(args)))74 else:75 content = str(args).strip()76 77 if used_code:78 # Clean up the content by removing any end code tags79 content = re.sub(80 r"```.*?\n", "", content81 ) # Remove existing code blocks82 content = re.sub(83 r"\s*<end_code>\s*", "", content84 ) # Remove end_code tags85 content = content.strip()86 if not content.startswith("```python"):87 content = f"```python\n{content}\n```"88 89 parent_message_tool = gr.ChatMessage(90 role="assistant",91 content=content,92 metadata={93 "title": f"๐ ๏ธ Used tool {first_tool_call.name}",94 "id": parent_id,95 "status": "pending",96 },97 )98 yield parent_message_tool99 100 # Nesting execution logs under the tool call if they exist101 if hasattr(step_log, "observations") and (102 step_log.observations is not None and step_log.observations.strip()103 ): # Only yield execution logs if there's actual content104 log_content = step_log.observations.strip()105 if log_content:106 log_content = re.sub(r"^Execution logs:\s*", "", log_content)107 yield gr.ChatMessage(108 role="assistant",109 content=f"{log_content}",110 metadata={111 "title": "๐ Execution Logs",112 "parent_id": parent_id,113 "status": "done",114 },115 )116 117 # Nesting any errors under the tool call118 if hasattr(step_log, "error") and step_log.error is not None:119 yield gr.ChatMessage(120 role="assistant",121 content=str(step_log.error),122 metadata={123 "title": "๐ฅ Error",124 "parent_id": parent_id,125 "status": "done",126 },127 )128 129 # Update parent message metadata to done status without yielding a new message130 parent_message_tool.metadata["status"] = "done"131 132 # Handle standalone errors but not from tool calls133 elif hasattr(step_log, "error") and step_log.error is not None:134 yield gr.ChatMessage(135 role="assistant",136 content=str(step_log.error),137 metadata={"title": "๐ฅ Error"},138 )139 140 # Calculate duration and token information141 step_footnote = f"{step_number}"142 if hasattr(step_log, "input_token_count") and hasattr(143 step_log, "output_token_count"144 ):145 token_str = f" | Input-tokens:{step_log.input_token_count:,} | Output-tokens:{step_log.output_token_count:,}"146 step_footnote += token_str147 if hasattr(step_log, "duration"):148 step_duration = (149 f" | Duration: {round(float(step_log.duration), 2)}"150 if step_log.duration151 else None152 )153 step_footnote += step_duration154 step_footnote = f"""<span style="color: #bbbbc2; font-size: 12px;">{step_footnote}</span> """155 yield gr.ChatMessage(role="assistant", content=f"{step_footnote}")156 yield gr.ChatMessage(role="assistant", content="-----")157 158 159def stream_to_gradio(160 agent,161 task: str,162 reset_agent_memory: bool = False,163 additional_args: Optional[dict] = None,164):165 """Runs an agent with the given task and streams the messages from the agent as gradio ChatMessages."""166 if not _is_package_available("gradio"):167 raise ModuleNotFoundError(168 "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"169 )170 import gradio as gr171 172 total_input_tokens = 0173 total_output_tokens = 0174 175 for step_log in agent.run(176 task, stream=True, reset=reset_agent_memory, additional_args=additional_args177 ):178 # Track tokens if model provides them179 if hasattr(agent.model, "last_input_token_count"):180 total_input_tokens += agent.model.last_input_token_count181 total_output_tokens += agent.model.last_output_token_count182 if isinstance(step_log, ActionStep):183 step_log.input_token_count = agent.model.last_input_token_count184 step_log.output_token_count = agent.model.last_output_token_count185 186 for message in pull_messages_from_step(187 step_log,188 ):189 yield message190 191 final_answer = step_log # Last log is the run's final_answer192 final_answer = handle_agent_output_types(final_answer)193 194 if isinstance(final_answer, AgentText):195 yield gr.ChatMessage(196 role="assistant",197 content=f"**Final answer:**\n{final_answer.to_string()}\n",198 )199 elif isinstance(final_answer, AgentImage):200 yield gr.ChatMessage(201 role="assistant",202 content={"path": final_answer.to_string(), "mime_type": "image/png"},203 )204 elif isinstance(final_answer, AgentAudio):205 yield gr.ChatMessage(206 role="assistant",207 content={"path": final_answer.to_string(), "mime_type": "audio/wav"},208 )209 else:210 yield gr.ChatMessage(211 role="assistant", content=f"**Final answer:** {str(final_answer)}"212 )213 214 215class GradioUI:216 """A one-line interface to launch your agent in Gradio"""217 218 def __init__(219 self,220 agent: MultiStepAgent,221 file_upload_folder: str | None = None,222 initial_message=None,223 ):224 if not _is_package_available("gradio"):225 raise ModuleNotFoundError(226 "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"227 )228 self.agent = agent229 self.file_upload_folder = file_upload_folder230 self.initial_message = initial_message231 if self.file_upload_folder is not None:232 if not os.path.exists(file_upload_folder):233 os.mkdir(file_upload_folder)234 235 def interact_with_agent(self, prompt, messages):236 import gradio as gr237 238 messages.append(gr.ChatMessage(role="user", content=prompt))239 yield messages240 for msg in stream_to_gradio(self.agent, task=prompt, reset_agent_memory=False):241 messages.append(msg)242 yield messages243 yield messages244 245 def upload_file(246 self,247 file,248 file_uploads_log,249 allowed_file_types=[250 "application/pdf",251 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",252 "text/plain",253 ],254 ):255 """256 Handle file uploads, default allowed types are .pdf, .docx, and .txt257 """258 import gradio as gr259 260 if file is None:261 return gr.Textbox("No file uploaded", visible=True), file_uploads_log262 263 try:264 mime_type, _ = mimetypes.guess_type(file.name)265 except Exception as e:266 return gr.Textbox(f"Error: {e}", visible=True), file_uploads_log267 268 if mime_type not in allowed_file_types:269 return gr.Textbox("File type disallowed", visible=True), file_uploads_log270 271 # Sanitize file name272 original_name = os.path.basename(file.name)273 sanitized_name = re.sub(274 r"[^\w\-.]", "_", original_name275 ) # Replace any non-alphanumeric, non-dash, or non-dot characters with underscores276 277 type_to_ext = {}278 for ext, t in mimetypes.types_map.items():279 if t not in type_to_ext:280 type_to_ext[t] = ext281 282 # Ensure the extension correlates to the mime type283 sanitized_name = sanitized_name.split(".")[:-1]284 sanitized_name.append("" + type_to_ext[mime_type])285 sanitized_name = "".join(sanitized_name)286 287 # Save the uploaded file to the specified folder288 file_path = os.path.join(289 self.file_upload_folder, os.path.basename(sanitized_name)290 )291 shutil.copy(file.name, file_path)292 293 return gr.Textbox(294 f"File uploaded: {file_path}", visible=True295 ), file_uploads_log + [file_path]296 297 def log_user_message(self, text_input, file_uploads_log):298 return (299 text_input300 + (301 f"\nYou have been provided with these files, which might be helpful or not: {file_uploads_log}"302 if len(file_uploads_log) > 0303 else ""304 ),305 "",306 )307 308 def launch(self, **kwargs):309 import gradio as gr310 311 with gr.Blocks(fill_height=True) as demo:312 gr.State(self.initial_message)313 stored_messages = gr.State([])314 file_uploads_log = gr.State([])315 chatbot = gr.Chatbot(316 label="Agent",317 type="messages",318 value=(319 [{"role": "assistant", "content": self.initial_message}]320 if self.initial_message321 else []322 ),323 avatar_images=(324 None,325 "https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/communication/Alfred.png",326 ),327 resizeable=True,328 scale=1,329 )330 # If an upload folder is provided, enable the upload feature331 if self.file_upload_folder is not None:332 upload_file = gr.File(label="Upload a file")333 upload_status = gr.Textbox(334 label="Upload Status", interactive=False, visible=False335 )336 upload_file.change(337 self.upload_file,338 [upload_file, file_uploads_log],339 [upload_status, file_uploads_log],340 )341 text_input = gr.Textbox(342 submit_btn="Start the ritual of code analysis",343 lines=10,344 label="Sacred Code wrapped in ```python ``` click on button to start the ritual of code analysis, shift+enter also works as submit",345 )346 text_input.submit(347 self.log_user_message,348 [text_input, file_uploads_log],349 [stored_messages, text_input],350 ).then(self.interact_with_agent, [stored_messages, chatbot], [chatbot])351 352 demo.launch(debug=True, share=True, **kwargs)353 354 355__all__ = ["stream_to_gradio", "GradioUI"]356 