Spaces:
Runtime error
Runtime error
| 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() | |