import os from typing import Optional import gradio as gr import requests from smolagents import CodeAgent, Tool from smolagents.models import HfApiModel from smolagents.monitoring import LogLevel from gradio import ChatMessage DEFAULT_MODEL = "Qwen/Qwen2.5-Coder-32B-Instruct" HF_API_TOKEN = os.getenv("HF_TOKEN") # Tool descriptions for the UI TOOL_DESCRIPTIONS = { "Hub Collections": "Add tool collections from Hugging Face Hub.", "Spaces": "Add tools from Hugging Face Spaces.", } def search_spaces(query, limit=1): """ Search for Hugging Face Spaces using the API. Returns the first result or None if no results. """ try: url = f"https://huggingface.co/api/spaces?search={query}&limit={limit}" response = requests.get( url, headers={"Authorization": f"Bearer {HF_API_TOKEN}"} ) response.raise_for_status() spaces = response.json() if not spaces: return None # Get the first space space = spaces[0] space_id = space["id"] # Extract title and description title = space_id.split("/")[-1] # Default to the last part of the ID description = f"Tool from {space_id}" # Try to get title from different possible locations if "title" in space: title = space["title"] elif "cardData" in space and "title" in space["cardData"]: title = space["cardData"]["title"] # Try to get description from different possible locations if "description" in space: description = space["description"] elif "cardData" in space and "description" in space["cardData"]: description = space["cardData"]["description"] return { "id": space_id, "title": title, "description": description, } except Exception as e: print(f"Error searching spaces: {e}") return None def get_space_metadata(space_id): """ Get metadata for a specific Hugging Face Space. """ try: url = f"https://huggingface.co/api/spaces/{space_id}" response = requests.get( url, headers={"Authorization": f"Bearer {HF_API_TOKEN}"} ) response.raise_for_status() space = response.json() # Extract title and description from the space data # The structure can vary, so we need to handle different cases title = space_id description = f"Tool from {space_id}" # Try to get title from different possible locations if "title" in space: title = space["title"] elif "cardData" in space and "title" in space["cardData"]: title = space["cardData"]["title"] else: # Use the last part of the space_id as a fallback title title = space_id.split("/")[-1] # Try to get description from different possible locations if "description" in space: description = space["description"] elif "cardData" in space and "description" in space["cardData"]: description = space["cardData"]["description"] return { "id": space_id, "title": title, "description": description, } except Exception as e: print(f"Error getting space metadata: {e}") return None def create_agent(model_name, space_tools=None): """ Create a CodeAgent with the specified model and tools. """ if not space_tools: space_tools = [] try: # Convert space tools to Tool objects tools = [] for tool_info in space_tools: space_id = tool_info["id"] tool = Tool.from_space( space_id, name=tool_info.get("name", space_id), description=tool_info.get("description", ""), ) tools.append(tool) # Initialize the HfApiModel with the model name model = HfApiModel(model_id=model_name, token=HF_API_TOKEN) # Create the agent with the tools and additional imports agent = CodeAgent( tools=tools, model=model, additional_authorized_imports=["PIL", "requests"], verbosity_level=LogLevel.DEBUG, # Set higher verbosity for detailed logs ) print(f"Agent created successfully with {len(tools)} tools") return agent except Exception as e: print(f"Error creating agent: {e}") # Try with a fallback model if the specified one fails try: print("Trying fallback model...") fallback_model = HfApiModel( model_id="Qwen/Qwen2.5-Coder-7B-Instruct", token=HF_API_TOKEN ) agent = CodeAgent( tools=tools, model=fallback_model, additional_authorized_imports=["PIL", "requests"], verbosity_level=LogLevel.DEBUG, # Set higher verbosity for detailed logs ) print("Agent created successfully with fallback model") return agent except Exception as e: print(f"Error creating agent: {e}") return None # Event handler functions def on_search_spaces(query): if not query: return "Please enter a search term.", "", "", "" try: space_info = search_spaces(query) if space_info is None: return "No spaces found.", "", "", "" # Format the results as markdown results_md = "### Search Results:\n" results_md += f"- ID: `{space_info['id']}`\n" results_md += f"- Title: {space_info['title']}\n" results_md += f"- Description: {space_info['description']}\n" # Return values to update the UI return ( results_md, space_info["id"], space_info["title"], space_info["description"], ) except Exception as e: print(f"Error in search: {e}") return f"Error: {str(e)}", "", "", "" def on_validate_space(space_id): if not space_id: return "Please enter a space ID or search term.", "", "" try: # First try to get metadata directly if it's a valid space ID space_info = get_space_metadata(space_id) # If not found, try to search for it if space_info is None: # Try to search for the space using the ID as a search term space_info = search_spaces(space_id) if space_info is None: return f"No spaces found for '{space_id}'.", "", "" # Format search result as markdown result_md = f"### Found Space via Search:\n" result_md += f"- ID: `{space_info['id']}`\n" result_md += f"- Title: {space_info['title']}\n" result_md += f"- Description: {space_info['description']}\n" return ( result_md, space_info["title"], space_info["description"], ) # Format direct match as markdown result_md = f"### Space Validated Successfully:\n" result_md += f"- ID: `{space_info['id']}`\n" result_md += f"- Title: {space_info['title']}\n" result_md += f"- Description: {space_info['description']}\n" return ( result_md, space_info["title"], space_info["description"], ) except Exception as e: print(f"Error validating space: {e}") return f"Error: {str(e)}", "", "" def on_add_tool(space_id, space_name, space_description, current_tools): if not space_id: return ( current_tools, "Please enter a space ID.", ) # Check if this tool is already added for tool in current_tools: if tool["id"] == space_id: return ( current_tools, f"Tool '{space_id}' is already added.", ) # Add the new tool new_tool = { "id": space_id, "name": space_name if space_name else space_id, "description": space_description if space_description else "No description", } updated_tools = current_tools + [new_tool] # Format the tools as markdown tools_md = "### Added Tools:\n" for i, tool in enumerate(updated_tools, 1): tools_md += f"{i}. **{tool['name']}** (`{tool['id']}`)\n" tools_md += f" {tool['description']}\n\n" return updated_tools, tools_md def on_create_agent(model, space_tools): if not space_tools: return ( None, [], "", "Please add at least one tool before creating an agent.", "No agent created yet.", ) try: # Create the agent agent = create_agent(model, space_tools) if agent is None: return ( None, [], "", "Failed to create agent. Please try again with different tools or model.", "No agent created yet.", ) # Format the tools for display tools_str = ", ".join( [f"{tool['name']} ({tool['id']})" for tool in space_tools] ) # Generate agent status agent_status = update_agent_status(agent) return ( agent, [], "", f"✅ Agent created successfully with {model}!\nTools: {tools_str}", agent_status, ) except Exception as e: print(f"Error creating agent: {e}") return None, [], "", f"Error creating agent: {str(e)}", "No agent created yet." def add_user_message(message, chat_history): """Add the user message to the chat history.""" # For Gradio chatbot with type="messages", we need to use ChatMessage objects if not message: return "", chat_history # Add user message to chat history chat_history = chat_history + [ChatMessage(role="user", content=message)] return message, chat_history def stream_to_gradio( agent, task: str, reset_agent_memory: bool = False, additional_args: Optional[dict] = None, ): """Runs an agent with the given task and streams the messages from the agent as gradio ChatMessages.""" from smolagents.gradio_ui import pull_messages_from_step, handle_agent_output_types from smolagents.agent_types import AgentAudio, AgentImage, AgentText for step_log in agent.run( task, stream=True, reset=reset_agent_memory, additional_args=additional_args ): for message in pull_messages_from_step( step_log, ): yield message final_answer = step_log # Last log is the run's final_answer final_answer = handle_agent_output_types(final_answer) if isinstance(final_answer, AgentImage): yield gr.ChatMessage( role="assistant", content={"path": final_answer.to_string(), "mime_type": "image/png"}, ) elif isinstance(final_answer, AgentText) and os.path.exists( final_answer.to_string() ): yield gr.ChatMessage( role="assistant", content=gr.Image(final_answer.to_string()), ) elif isinstance(final_answer, AgentAudio): yield gr.ChatMessage( role="assistant", content={"path": final_answer.to_string(), "mime_type": "audio/wav"}, ) else: yield gr.ChatMessage( role="assistant", content=f"**Final answer:** {str(final_answer)}" ) def stream_agent_response(agent, message, chat_history): """Stream the agent's response to the chat history.""" if not message or agent is None: return chat_history # First yield the current chat history yield chat_history try: # Stream the agent's response for msg in stream_to_gradio(agent, message): # Add the message to chat history chat_history = chat_history + [msg] # Yield updated chat history yield chat_history except Exception as e: # Handle errors error_msg = f"Error: {str(e)}" chat_history = chat_history + [ChatMessage(role="assistant", content=error_msg)] yield chat_history def on_clear(agent=None): """Clear the chat and reset the agent.""" return ( agent, [], "", "Agent cleared. Create a new one to continue.", "", gr.update(interactive=False), ) def update_agent_status(agent): """Update the agent status display with current information.""" if agent is None: return "No agent created yet. Add a Space tool to get started." # Get agent information tools = agent.tools if hasattr(agent, "tools") else [] tool_count = len(tools) # Create status message status = f"Agent ready with {tool_count} tools" return status # Create the Gradio app with gr.Blocks(title="AI Agent Builder") as app: gr.Markdown("# AI Agent Builder with SmolaGents") gr.Markdown("Build your own AI agent by selecting tools from Hugging Face Spaces.") # Agent state agent_state = gr.State(None) last_message = gr.State("") space_tools_state = gr.State([]) # Message store for preserving user message msg_store = gr.State("") with gr.Row(): # Left sidebar for tool configuration with gr.Column(scale=1): gr.Markdown("## Tool Configuration") gr.Markdown("Add multiple Hugging Face Spaces as tools for your agent:") # Hidden model input with default value model_input = gr.Textbox( value=DEFAULT_MODEL, label="Model", visible=False, ) # Space tool input with gr.Group(): gr.Markdown("### Add Space as Tool") space_tool_input = gr.Textbox( label="Space ID or Search Term", placeholder=("Enter a Space ID or search term"), info="Enter a Space ID (username/space-name) or search term", ) space_name_input = gr.Textbox( label="Tool Name (optional)", placeholder="Enter a name for this tool", ) space_description_input = gr.Textbox( label="Tool Description (optional)", placeholder="Enter a description for this tool", lines=2, ) add_tool_button = gr.Button("Add Tool", variant="primary") # Display added tools gr.Markdown("### Added Tools") tools_display = gr.Markdown( "No tools added yet. Add at least one tool before creating an agent." ) # Create agent button create_button = gr.Button( "Create Agent with Selected Tools", variant="secondary", size="lg" ) # Status message status_msg = gr.Markdown("") # Agent status display agent_status = gr.Markdown("No agent created yet.") # Main content area with gr.Column(scale=2): # Chat interface for the agent chatbot = gr.Chatbot( label="Agent Chat", height=600, show_copy_button=True, avatar_images=("👤", "🤖"), type="messages", # Use messages type for ChatMessage objects ) msg = gr.Textbox( label="Your message", placeholder="Type a message to your agent...", interactive=True, ) with gr.Row(): with gr.Column(scale=1, min_width=60): clear = gr.Button("🗑️", scale=1) with gr.Column(scale=8): # Empty column for spacing pass # Connect event handlers # Connect the space_tool_input submit event to the validation handler space_tool_input.submit( on_validate_space, inputs=[space_tool_input], outputs=[status_msg, space_name_input, space_description_input], ) # Connect the add tool button add_tool_button.click( on_add_tool, inputs=[ space_tool_input, space_name_input, space_description_input, space_tools_state, ], outputs=[space_tools_state, tools_display], ) # Connect the create button to the handler create_button.click( on_create_agent, inputs=[model_input, space_tools_state], outputs=[agent_state, chatbot, msg, status_msg, agent_status], ) # Connect the message input to the chain of handlers msg.submit( lambda message: (message, message, ""), # Store message and clear input inputs=[msg], outputs=[msg_store, msg, msg], queue=False, ).then( add_user_message, # Add user message to chat inputs=[msg_store, chatbot], outputs=[msg_store, chatbot], queue=False, ).then( stream_agent_response, # Generate and stream response inputs=[agent_state, msg_store, chatbot], outputs=chatbot, queue=True, ) if __name__ == "__main__": app.queue().launch()