Spaces:
Runtime error
Runtime error
| import requests | |
| import os | |
| import gradio as gr | |
| import inspect | |
| import pandas as pd | |
| import time | |
| import re | |
| from langchain_google_genai import ChatGoogleGenerativeAI | |
| from langchain_community.tools import TavilySearchResults | |
| from langchain import hub # Used to pull predefined prompts from LangChain Hub | |
| from langchain.agents import AgentExecutor, create_react_agent | |
| from langchain.memory import ConversationSummaryMemory | |
| from typing import Any, List, Optional | |
| from langchain.agents import AgentExecutor, Agent | |
| from langchain.tools.base import BaseTool | |
| from langchain.memory import ConversationSummaryBufferMemory | |
| from google.api_core import retry | |
| from google import genai | |
| from langchain.prompts import PromptTemplate | |
| # for openAI model | |
| from langchain_openai import ChatOpenAI | |
| from openai import OpenAI | |
| # tools imported from helper.py | |
| from helper import repl_tool, get_travily_api_search_tool,audio_transcriber_tool,file_saver_tool,gemini_multimodal_tool | |
| from helper import wikipedia_search_tool2 | |
| # (Keep Constants as is) | |
| # --- Constants --- | |
| DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space" | |
| # --- Basic Agent Definition --- | |
| # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------ | |
| class BasicAgent: | |
| def __init__( | |
| self, | |
| agent: Agent, | |
| tools: List[BaseTool], | |
| verbose: bool = False, | |
| handle_parsing_errors: bool = True, | |
| max_iterations: int = 9, | |
| memory: Optional[ConversationSummaryMemory] = None | |
| ) -> None: | |
| """ | |
| Initialize with parameters required for AgentExecutor. | |
| """ | |
| self.agent: Agent = agent | |
| self.tools: List[BaseTool] = tools | |
| self.verbose: bool = verbose | |
| self.handle_parsing_errors: bool = handle_parsing_errors | |
| self.max_iterations: int = max_iterations | |
| self.memory: Optional[ConversationSummaryMemory] = memory | |
| self.agent_obj = AgentExecutor( | |
| agent=self.agent, | |
| tools=self.tools, | |
| verbose=self.verbose, | |
| handle_parsing_errors=self.handle_parsing_errors, | |
| max_iterations=self.max_iterations, | |
| memory=self.memory | |
| ) | |
| def is_retriable(self, e: Exception) -> bool: | |
| # Adjust this check if your error type is different | |
| return isinstance(e, genai.errors.APIError) and getattr(e, "code", None) in {429, 503} | |
| def invoke_with_retry(self,question: str, max_retries: int = 5, initial_delay: float = 10.0) -> str: | |
| current_delay = initial_delay | |
| for attempt in range(max_retries): | |
| try: | |
| result = self.agent_obj.invoke( | |
| {"input": question}, | |
| config={"configurable": {"session_id": "test-session"}}, | |
| ) | |
| return result['output'] | |
| except Exception as e: | |
| if self.is_retriable(e): | |
| # Check if the error object provides a specific retry_delay | |
| if hasattr(e, 'retry_delay') and hasattr(e.retry_delay, 'seconds'): | |
| # Use the specific retry_delay provided by the API | |
| current_delay = float(e.retry_delay.seconds) | |
| print(f"Quota error (attempt {attempt+1}/{max_retries}). API suggested retry after {current_delay} seconds.", flush=True) | |
| else: | |
| # Fallback to exponential backoff if no specific delay is provided | |
| print(f"Quota error (attempt {attempt+1}/{max_retries}). Retrying in {current_delay} seconds with exponential backoff.", flush=True) | |
| current_delay *= 2 # Exponential backoff | |
| time.sleep(current_delay) | |
| else: | |
| # If it's not a retriable error, re-raise it | |
| raise | |
| # If all retries fail, raise a RuntimeError | |
| raise RuntimeError(f"Max retries ({max_retries}) exceeded due to persistent quota errors or other retriable issues.") | |
| def __call__(self, question: str) -> str: | |
| """ | |
| Allows the instance to be called directly to get an AgentExecutor. | |
| """ | |
| return self.invoke_with_retry(question) | |
| def run_and_submit_all( profile: gr.OAuthProfile | None): | |
| """ | |
| Fetches all questions, runs the BasicAgent on them, submits all answers, | |
| and displays the results. | |
| """ | |
| # --- Determine HF Space Runtime URL and Repo URL --- | |
| space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code | |
| if profile: | |
| username= f"{profile.username}" | |
| print(f"User logged in: {username}") | |
| else: | |
| print("User not logged in.") | |
| return "Please Login to Hugging Face with the button.", None | |
| api_url = DEFAULT_API_URL | |
| questions_url = f"{api_url}/questions" | |
| submit_url = f"{api_url}/submit" | |
| google_api_key = os.getenv("GOOGLE_API_KEY") | |
| if not google_api_key: | |
| print("Google API key not found in environment variables.") | |
| return "Google API key not found. Please set GOOGLE_API_KEY environment variable.", None | |
| print(f"Using Google API key: {google_api_key[:4]}... (truncated for security)") | |
| openai_api_key = os.getenv("OPENAI_API_KEY") | |
| if not openai_api_key: | |
| print("OpenAI API key not found in environment variables.") | |
| return "OpenAI API key not found. Please set OPENAI_API_KEY environment variable.", None | |
| print(f"Using OpenAI API key: {openai_api_key[:4]}... (truncated for security)") | |
| #gemini_model ="gemini-2.0-flash" | |
| #gemini_model ="gemini-1.5-flash" | |
| #gemini_model ="models/gemini-2.5-flash-preview-05-20" | |
| gemini_model = "models/gemini-2.5-flash-lite-preview-06-17" | |
| #NMODEL | |
| llm_client = ChatGoogleGenerativeAI( | |
| model=gemini_model, # or another Gemini model name | |
| google_api_key=google_api_key, # your Gemini API key | |
| temperature=0, | |
| ) | |
| #llm_client = ChatOpenAI(model='gpt-4o',temperature=0,api_key=openai_api_key) | |
| #llm_client = ChatOpenAI(model='gpt-4o',temperature=0,api_key=openai_api_key,top_p=1,presence_penalty=0,frequency_penalty=0,seed=12345) | |
| serp_api_key = os.getenv("SERP_API_KEY") | |
| if not serp_api_key: | |
| print("SerpAPI key not found in environment variables.") | |
| return "SerpAPI key not found. Please set SERP_API_KEY environment variable.", None | |
| print(f"Using SerpAPI key: {serp_api_key[:4]}... (truncated for security)") | |
| tavily_api_key = os.getenv("TAVILY_API_KEY") | |
| if not tavily_api_key: | |
| print("Tavily API key not found in environment variables.") | |
| return "Tavily API key not found. Please set TAVILY_API_KEY environment variable.", None | |
| print(f"Using Tavily API key: {tavily_api_key[:4]}... (truncated for security)") | |
| travily_api_search_tool = get_travily_api_search_tool(tavily_api_key) | |
| tools = [ repl_tool, file_saver_tool,audio_transcriber_tool,travily_api_search_tool, gemini_multimodal_tool, wikipedia_search_tool2] | |
| #tools = [ repl_tool, file_saver_tool,audio_transcriber_tool,travily_api_search_tool, gemini_multimodal_tool] | |
| EX5_OBSERVATION_STRING = ( | |
| "[{{'title': '1977 New York Yankees Hitting Stats - Baseball-Reference.com', " | |
| "'url': 'https://www.baseball-reference.com/teams/NYY/1977.shtml', " | |
| "'content': '| Rk | Player | Age | Pos | WAR | W | L | W-L% | ERA | G | GS | GF | CG | SHO | SV | IP | H | R | ER | HR | BB | IBB | SO | HBP | BK | WP | BF | ERA+ | FIP | WHIP | H9 | HR9 | BB9 | SO9 | SO/BB | Awards | All logos are the trademark & property of their owners and not Sports Reference LLC. Copyright © 2000-2025 Sports Reference LLC. Sports Info Solutions logo Sports Info Solutions logo Sports Info Solutions logo'}}]" | |
| ) | |
| prompt = PromptTemplate( | |
| input_variables=["input", "agent_scratchpad", "chat_history", "tool_names"], | |
| template=""" | |
| You are a smart and helpful AI Agent/Assistant that excels at fact-based reasoning. You are allowed and encouraged to use one or more tools as needed to answer complex questions and perform tasks. | |
| It is CRUCIAL that you ALWAYS follow the exact format below. Do not deviate. | |
| **IMPORTANT - CONCISE FINAL ANSWER FORMATTING RULES (MANDATORY):** | |
| Your FINAL ANSWER must be presented in one of these formats, and ONLY the answer itself (no introductory phrases): | |
| - A number (e.g., '26', '1977', '519') | |
| - As few words as possible (e.g., 'Paris', 'down', 'LUX') | |
| - A comma-separated list of numbers and/or strings (e.g., '10,20,30', 'apple,banana,orange') | |
| **STRICT RULES FOR FINAL ANSWER CONTENT:** | |
| 1. **Numbers:** Do NOT use commas (e.g., '1000000' instead of '1,000,000'). Do NOT use units ($ or % or degrees Celsius/Fahrenheit) unless explicitly requested in the question. | |
| 2. **Strings (Words):** Do NOT use articles (a, an, the). Do NOT use abbreviations (e.g., 'New York' instead of 'NYC', e.g., 'Saint' instead of 'St'). Write digits in plain text unless specified otherwise (e.g., 'two' instead of '2' if it's part of a string answer and not a numerical answer). | |
| 3. **Comma-Separated Lists:** Apply the above rules (numbers or strings) to each element in the list. | |
| **Your response MUST always start with 'Thought:'. Your FINAL ANSWER must be preceded by 'Final Answer: '.** | |
| You have access to the following tools: | |
| {tools} | |
| To use a tool, you MUST follow this precise format: | |
| Thought: I need to use a tool to find the answer. | |
| Action: [tool_name] # This will be one of [{tool_names}] | |
| Action Input: [input_for_the_tool] | |
| Observation: [result_from_the_tool] | |
| IMPORTANT NOTE ON TOOL USAGE: | |
| - If an 'Observation' from a tool, especially `tavily_search` or `serpapi_Google Search_tool`, contains a list, table, or structured text that might hold the answer, your next step should be to use `python_repl` to parse and extract the required information from that observation's content. Do NOT search again unless the content is genuinely insufficient or irrelevant. | |
| - If an 'Observation' from a tool does NOT directly contain the specific answer to your question, you MUST refine your query or switch to a different, more suitable tool (e.g., 'tavily_search' for broader or more current information if 'wikipedia_search_tool2' was insufficient). Do NOT get stuck repeatedly using the same tool if it's not yielding the direct answer. | |
| - If the input contains the exact phrase "Attachment '{{file_name}}' available at: {{attachment_url}}" (where '{{file_name}}' and '{{attachment_url}}' are placeholders for actual values), consider the file type: | |
| - If the file type is binary/text (e.g., .xlsx, .docx, .mp3, .jpg, .pdf,.png), you MUST use the 'file_saver' tool to download and save it. | |
| For 'file_saver', the Action Input must be a JSON string like: '{{"url": "the_attachment_url", "local_filename": "the_file_name_from_attachment"}}' | |
| example: for input, Attachment '1f975693-876d-457b-a649-393859e79bf3.mp3' available at EXACT URL: https://agents-course-unit4-scoring.hf.space/files/1f975693-876d-457b-a649-393859e79bf3, Action Input for file_saver would be '{{"url": "https://agents-course-unit4-scoring.hf.space/files/1f975693-876d-457b-a649-393859e79bf3", "local_filename": "1f975693-876d-457b-a649-393859e79bf3.mp3"}}' | |
| IMPORTANT: When processing audio files (like .mp3) that have been saved using 'file_saver', the 'audio_transcriber_tool' MUST be used with the 'local_filename' of the saved audio file as its Action Input. Do NOT pass URLs or remote paths directly to 'audio_transcriber_tool'. | |
| **For image files (like .jpg, .png) that have been saved using 'file_saver', the 'gemini_multimodal_tool' MUST be used to analyze their content and answer questions based on the image. The Action Input for 'gemini_multimodal_tool' must be a JSON string like: '{{"image_path": "the_local_filename", "question": "the_user_question"}}'** | |
| Example: given a chess board image and asked to predict the next best move, if Multi-modal LLM is available, you can use it to answer the question. | |
| If you have sufficient information and can provide a CONCISE response, or if no tool is needed, you MUST use this precise format: | |
| Thought: I have enough information, or no tool is needed. | |
| Final Answer: [your concise/short response here] | |
| **VERY IMPORTANT: Your response MUST always start with 'Thought:'. Your FINAL ANSWER must be preceded by 'Final Answer: '.** | |
| Here are some examples of how you should respond, strictly adhering to the formatting rules: | |
| Example 1: | |
| Question: What is the capital of France? | |
| Thought: I need to use a tool to find the capital of France. | |
| Action: tavily_search | |
| Action Input: capital of France | |
| Observation: The capital of France is Paris. | |
| Thought: I have found the answer. | |
| Final Answer: Paris | |
| Example 2: | |
| Question: What is 2 + 2? | |
| Thought: This is a simple arithmetic question, no tool is needed. | |
| Final Answer: 4 | |
| Example 3: | |
| Question: How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia. | |
| Thought: The user is asking for specific information about discography, which might be found with a search tool. The `serpapi_Google Search_tool` can fetch detailed sections. After getting the content, I will need to parse it using `python_repl` to count the albums within the specified years. | |
| Action: serpapi_Google Search | |
| Action Input: Mercedes Sosa discography | |
| Observation: [Discography text content from search result] | |
| Thought: I have retrieved discography text. Now I need to parse this text to identify and count studio albums released between 2000 and 2009. I will use the `python_repl` tool for this. | |
| Action: python_repl | |
| Action Input: | |
| ```python | |
| import re | |
| text = "[Discography text content from previous observation]" # Replace with actual text | |
| albums_2000_2009 = [] | |
| pattern = r"\((\d{{4}})\)\s*(.*?)(?:\[|\n|$)" # Ensures year is captured. Double braces {{}} to escape regex literal braces | |
| for match in re.finditer(pattern, text): | |
| year = int(match.group(1)) | |
| if 2000 <= year <= 2009: | |
| albums_2000_2009.append(match.group(2).strip()) | |
| print(len(albums_2000_2009)) | |
| ``` | |
| Observation: 3 | |
| Thought: I have parsed the discography and counted the albums. I have found the answer. | |
| Final Answer: 3 | |
| **Example 4: (Crucial new example for image processing)** | |
| Question: What is the next best move in this chess position? Attachment 'chess_board.png' available at EXACT URL: https://agents-course-unit4-scoring.hf.space/files/cca530fc-4052-43b2-b130-b30968d8aa44 | |
| Thought: The user is asking a question about a chess position and has provided an image. I need to first save the image locally using the 'file_saver' tool, and then use the 'gemini_multimodal_tool' to analyze the image and answer the question. | |
| Action: file_saver | |
| Action Input: {{"url": "https://agents-course-unit4-scoring.hf.space/files/cca530fc-4052-43b2-b130-b30968d8aa44", "local_filename": "cca530fc-4052-43b2-b130-b30968d8aa44.png"}} | |
| Observation: File downloaded successfully to cca530fc-4052-43b2-b130-b30968d8aa44.png | |
| Thought: The image has been successfully downloaded. Now I need to analyze its content to determine the next best chess move using the 'gemini_multimodal_tool'. | |
| Action: gemini_multimodal_tool | |
| Action Input: {{"image_path": "cca530fc-4052-43b2-b130-b30968d8aa44.png", "question": "What is the next best move in this chess position?"}} | |
| Observation: The next best move is e4. | |
| Thought: I have used the 'gemini_multimodal_tool' to get the best move based on the image. | |
| Final Answer: e4 | |
| Example 5: | |
| Question: What is the opposite of up? | |
| Thought: The question asks for the opposite of up. This is a direct knowledge question. | |
| Final Answer: down | |
| Example 6: (New example for parsing baseball stats) | |
| Question: How many at bats did the Yankee with the most walks in the 1977 regular season have that same season? | |
| Thought: I need to find the Yankee player with the most walks in 1977 and then find their at bats. This will require searching for Yankees 1977 stats and then parsing the results to extract the relevant player and their at-bats. I will use 'tavily_search' first to find the stats. After getting the search results, I will examine their content for a list or table of players and their stats. If found, I will use 'python_repl' to parse it. | |
| Action: tavily_search | |
| Action Input: New York Yankees 1977 batting stats | |
| Observation: {{EX5_OBSERVATION_STRING}} | |
| Thought: I have received an observation from `tavily_search`. I need to examine its `content` to determine if it contains the necessary data (e.g., a list or table of players/stats/winners). If so, my next step is to use `python_repl` to parse this content to extract the specific information needed to answer the question. I should only consider another `tavily_search` if the current observation's content is clearly insufficient. | |
| Action: python_repl | |
| Action Input: | |
| ```python | |
| # Example: Parse the text content from the tavily_search observation. | |
| # This is a placeholder for the actual parsing logic you would write. | |
| # For the Malko question, you would parse the list of winners and their nationalities. | |
| # For instance, if the observation content contains: | |
| # "1983 | Claus Peter Flor | East Germany" | |
| # You would extract this and apply your filtering logic. | |
| ``` | |
| Observation: 519 | |
| Thought: I have parsed the data and identified Roy White as the Yankee with the most walks (75) in 1977, and his at-bats were 519. I have found the answer. | |
| Final Answer: 519 | |
| Example 7: (Parsing a table for minimum value) | |
| Question: What country had the least number of athletes at the 1928 Summer Olympics? If there's a tie for a number of athletes, return the first in alphabetical order. Give the IOC country code as your answer. | |
| Thought: I need to find a table of athlete counts by country for the 1928 Olympics. I will use 'tavily_search' to find the data. After getting the search results, I will examine their content for a list or table of countries and their athlete counts. If found, I will use 'python_repl' to parse it. | |
| Action: tavily_search | |
| Action Input: 1928 Summer Olympics athlete count by country | |
| Observation: [Table or HTML/text with country and athlete counts] | |
| Thought: I have found the table in the `tavily_search` observation. Now I need to parse it to find the country with the least athletes, and if there is a tie, pick the first alphabetically. I will use 'python_repl' for this. | |
| Action: python_repl | |
| Action Input: | |
| ```python | |
| # Example: python code to parse the text table and find the IOC country code with the least athletes. | |
| # (Replace this with actual code as needed) | |
| ``` | |
| Observation: LUX | |
| Thought: I have found the country with the least athletes. | |
| Final Answer: LUX | |
| Example 8: (Identifying non-commutative elements from a table) | |
| Question: Given this table defining * on the set S = {{p, q, r}}, provide the subset of S involved in any possible counter-examples that prove * is not commutative. Provide your answer as a comma separated list of the elements in the set in alphabetical order. | |
| |*|p|q|r| | |
| |---|---|---|---| | |
| |p|p|r|q| | |
| |q|r|q|p| | |
| |r|p|q|r| | |
| Thought: The user wants to find elements involved in counter-examples to prove non-commutativity. This means I need to check if $x * y \neq y * x$ for any pair of distinct elements $x, y$ from the set S = {{p, q, r}}. I will go through each unique pair and check their commutativity directly from the provided table. | |
| 1. **Check (p, q):** | |
| * $p * q = r$ (from row 'p', column 'q') | |
| * $q * p = r$ (from row 'q', column 'p') | |
| * Result: Commutative (since $r = r$). | |
| 2. **Check (p, r):** | |
| * $p * r = q$ (from row 'p', column 'r') | |
| * $r * p = p$ (from row 'r', column 'p') | |
| * Result: Not commutative (since $q \neq p$). This is a counter-example. | |
| * The elements involved are 'p' and 'r'. | |
| Since I found a counter-example (p, r), the subset of elements involved is {{p, r}}. I should list them alphabetically. | |
| Final Answer: p,r | |
| Example 9: | |
| Question: From the list "red apple, green banana, blue berry, yellow zucchini", identify the botanical fruits and list them alphabetically. | |
| Thought: The user wants me to identify botanical fruits and then sort them alphabetically. | |
| - red apple: botanical fruit | |
| - green banana: botanical fruit | |
| - blue berry: botanical fruit | |
| - yellow zucchini: botanical fruit | |
| All items are botanical fruits. Now I will alphabetize them, treating each phrase as a single unit for sorting, and maintaining their original phrasing and spaces. | |
| 1. blue berry | |
| 2. green banana | |
| 3. red apple | |
| 4. yellow zucchini | |
| Final Answer: blue berry,green banana,red apple,yellow zucchini | |
| Example 10: | |
| Question: How many Nobel Prizes were awarded to individuals with the surname Curie between 1900 and 1960 (included)? | |
| Thought: The user is asking for a count of Nobel Prizes for a specific family within a date range. I should use `wikipedia_search_tool2` to find information about the Curie family's Nobel Prizes. It is CRUCIAL that I then identify and strictly parse relevant sections (e.g., "Nobel Prizes" or "Awards") for individuals named Curie, and only count those within the 1900 to 1960 range (inclusive). I will use `python_repl` for robust parsing and filtering. | |
| Action: wikipedia_search_tool2 | |
| Action Input: Curie family Nobel Prizes | |
| Observation: {{OBSERVATION_FROM_WIKIPEDIA_CURIE_FAMILY}} # Assume this contains text including details about Marie Curie, Pierre Curie, Irène Joliot-Curie, Frédéric Joliot-Curie, etc., and their awards. | |
| Thought: I have retrieved information about the Curie family's Nobel Prizes. Now, I must carefully parse this content to find individuals with the surname Curie (including hyphenated names like Joliot-Curie) and extract only those Nobel Prizes awarded between 1900 and 1960 (inclusive). I will use `python_repl` to perform this precise extraction and counting. | |
| Action: python_repl | |
| Action Input: | |
| ```python | |
| import re | |
| # This text should be replaced by the actual 'Observation' content from the previous step. | |
| # For demonstration, let's use a simplified representation of relevant text. | |
| curie_content = \""" | |
| == Nobel Prizes == | |
| Marie Curie: | |
| - Physics (1903) with Pierre Curie and Henri Becquerel | |
| - Chemistry (1911) | |
| Pierre Curie: | |
| - Physics (1903) with Marie Curie and Henri Becquerel | |
| Irène Joliot-Curie: | |
| - Chemistry (1935) with Frédéric Joliot-Curie | |
| Frédéric Joliot-Curie: | |
| - Chemistry (1935) with Irène Joliot-Curie | |
| Other family members received awards outside this scope or were not direct Nobel laureates with the surname Curie. | |
| \""" | |
| unique_prizes = set() | |
| # Looking for a pattern like "Category (YYYY)" that is associated with a Curie | |
| # This regex attempts to find Nobel Prize categories followed by a year in parentheses. | |
| # We then ensure the line also mentions a "Curie" related name. | |
| lines = curie_content.split('\n') | |
| for line in lines: | |
| # Regex to find (Category) (YYYY) pattern and ensure 'Curie' or 'Joliot-Curie' is in the line | |
| # The (?:...) is for non-capturing group. | |
| # The \\b ensures whole word match for 'Curie' | |
| prize_match = re.search(r'(Physics|Chemistry|Physiology or Medicine|Peace|Literature|Economic Sciences)\s*\(((\d{{4}}))\).*?(?:Curie|Joliot-Curie)', line) | |
| if prize_match: | |
| category = prize_match.group(1) | |
| year = int(prize_match.group(3)) | |
| if 1900 <= year <= 1960: | |
| # Add a unique identifier for each prize instance (category-year) | |
| # This correctly counts a shared prize (e.g., 1903 Physics) as one distinct prize. | |
| unique_prizes.add(f"{{{{category}}}}-{{{{year}}}}") | |
| print(len(unique_prizes)) | |
| ``` | |
| Observation: 3 | |
| Thought: I have successfully parsed the information about the Curie family's Nobel Prizes, filtered for the correct surname and date range (1900-1960), and counted the unique awards. I have found the answer. | |
| Final Answer: 3 | |
| --- | |
| Previous conversation history: | |
| {chat_history} | |
| New input: {input} | |
| --- | |
| {agent_scratchpad} | |
| """ | |
| ) | |
| summary_memory = ConversationSummaryMemory(llm=llm_client, memory_key="chat_history") | |
| # Initialize gemini model with streaming enabled | |
| summary_llm = ChatGoogleGenerativeAI( | |
| model=gemini_model, | |
| google_api_key=google_api_key, | |
| temperature=0, | |
| streaming=True | |
| ) | |
| #summary_llm = ChatOpenAI(model='gpt-4o', temperature=0, streaming=False,api_key=openai_api_key) | |
| #summary_llm = ChatOpenAI(model='gpt-4o', temperature=0, streaming=False,api_key=openai_api_key,top_p=1,presence_penalty=0,frequency_penalty=0) | |
| # Create a ReAct agent | |
| summary_react_agent = create_react_agent( | |
| llm=summary_llm, | |
| tools=tools, | |
| prompt=prompt | |
| ) | |
| # 1. Instantiate Agent ( modify this part to create your agent) | |
| try: | |
| agent = BasicAgent(summary_react_agent, tools, True, True, 30, summary_memory) | |
| except Exception as e: | |
| print(f"Error instantiating agent: {e}") | |
| return f"Error initializing agent: {e}", None | |
| agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main" | |
| print(agent_code) | |
| # 2. Fetch Questions | |
| print(f"Fetching questions from: {questions_url}") | |
| try: | |
| response = requests.get(questions_url, timeout=15) | |
| response.raise_for_status() | |
| questions_data = response.json() | |
| if not questions_data: | |
| print("Fetched questions list is empty.") | |
| return "Fetched questions list is empty or invalid format.", None | |
| print(f"Fetched {len(questions_data)} questions.") | |
| except requests.exceptions.RequestException as e: | |
| print(f"Error fetching questions: {e}") | |
| return f"Error fetching questions: {e}", None | |
| except requests.exceptions.JSONDecodeError as e: | |
| print(f"Error decoding JSON response from questions endpoint: {e}") | |
| print(f"Response text: {response.text[:500]}") | |
| return f"Error decoding server response for questions: {e}", None | |
| except Exception as e: | |
| print(f"An unexpected error occurred fetching questions: {e}") | |
| return f"An unexpected error occurred fetching questions: {e}", None | |
| # 3. Run your Agent | |
| results_log = [] | |
| answers_payload = [] | |
| print(f"Running agent on {len(questions_data)} questions...") | |
| for item in questions_data: | |
| task_id = item.get("task_id") | |
| question_text = item.get("question") | |
| file_name = item.get("file_name") # Get the file_name if it exists | |
| # Construct the question string that your LLM will see, | |
| # including the attachment URL if present. | |
| full_question_for_agent = question_text | |
| if file_name: | |
| attachment_url = f"https://agents-course-unit4-scoring.hf.space/files/{task_id}" | |
| full_question_for_agent += f"\n\nAttachment '{file_name}' available at EXACT URL: {attachment_url}" | |
| print(f"Running agent on task {task_id}: {full_question_for_agent}",flush=True) | |
| ''' | |
| allowed_ids = { | |
| #"cca530fc-4052-43b2-b130-b30968d8aa44", | |
| #"a1e91b78-d3d8-4675-bb8d-62741b4b68a6", | |
| #"3f57289b-8c60-48be-bd80-01f8099ca449", | |
| #"2d83110e-a098-4ebb-9987-066c06fa42d0", | |
| #"cf106601-ab4f-4af9-b045-5295fe67b37d", | |
| #"7bd855d8-463d-4ed5-93ca-5fe35145f733", | |
| #"5a0c1adf-205e-4841-a666-7c3ef95def9d", | |
| #"f918266a-b3e0-4914-865d-4faa564f1aef", | |
| #"9d191bce-651d-4746-be2d-7ef8ecadb9c2", | |
| #"6f37996b-2ac7-44b0-8e68-6d28256631b4", | |
| "8e867cd7-cff9-4e6c-867a-ff5ddc2550be", | |
| } | |
| if task_id not in allowed_ids: | |
| continue | |
| ''' | |
| try: | |
| submitted_answer = agent(full_question_for_agent) | |
| answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer}) | |
| results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer}) | |
| time.sleep(61) # Add a 1 min delay before running the agent | |
| except Exception as e: | |
| print(f"Error running agent on task {task_id}: {e}") | |
| results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"}) | |
| if not answers_payload: | |
| print("Agent did not produce any answers to submit.") | |
| return "Agent did not produce any answers to submit.", pd.DataFrame(results_log) | |
| # 4. Prepare Submission | |
| submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload} | |
| status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..." | |
| print(status_update) | |
| # 5. Submit | |
| print(f"Submitting {len(answers_payload)} answers to: {submit_url}") | |
| try: | |
| response = requests.post(submit_url, json=submission_data, timeout=60) | |
| response.raise_for_status() | |
| result_data = response.json() | |
| final_status = ( | |
| f"Submission Successful!\n" | |
| f"User: {result_data.get('username')}\n" | |
| f"Overall Score: {result_data.get('score', 'N/A')}% " | |
| f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n" | |
| f"Message: {result_data.get('message', 'No message received.')}" | |
| ) | |
| print("Submission successful.") | |
| cleaned_final_status = re.sub(r'[^\x20-\x7E\n\r\t]+', '', final_status) | |
| cleaned_final_status = cleaned_final_status.strip() | |
| results_df = pd.DataFrame(results_log) | |
| return cleaned_final_status, results_df | |
| except requests.exceptions.HTTPError as e: | |
| error_detail = f"Server responded with status {e.response.status_code}." | |
| try: | |
| error_json = e.response.json() | |
| error_detail += f" Detail: {error_json.get('detail', e.response.text)}" | |
| except requests.exceptions.JSONDecodeError: | |
| error_detail += f" Response: {e.response.text[:500]}" | |
| status_message = f"Submission Failed: {error_detail}" | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| except requests.exceptions.Timeout: | |
| status_message = "Submission Failed: The request timed out." | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| except requests.exceptions.RequestException as e: | |
| status_message = f"Submission Failed: Network error - {e}" | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| except Exception as e: | |
| status_message = f"An unexpected error occurred during submission: {e}" | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| # --- Build Gradio Interface using Blocks --- | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Basic Agent Evaluation Runner") | |
| gr.Markdown( | |
| """ | |
| **Instructions:** | |
| 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ... | |
| 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission. | |
| 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score. | |
| --- | |
| **Disclaimers:** | |
| Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions). | |
| This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async. | |
| """ | |
| ) | |
| gr.LoginButton() | |
| run_button = gr.Button("Run Evaluation & Submit All Answers") | |
| status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False) | |
| results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True) | |
| run_button.click( | |
| fn=run_and_submit_all, | |
| outputs=[status_output, results_table] | |
| ) | |
| if __name__ == "__main__": | |
| print("\n" + "-"*30 + " App Starting " + "-"*30) | |
| space_host_startup = os.getenv("SPACE_HOST") | |
| space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup | |
| if space_host_startup: | |
| print(f"✅ SPACE_HOST found: {space_host_startup}") | |
| print(f" Runtime URL should be: https://{space_host_startup}.hf.space") | |
| else: | |
| print("ℹ️ SPACE_HOST environment variable not found (running locally?).") | |
| if space_id_startup: # Print repo URLs if SPACE_ID is found | |
| print(f"✅ SPACE_ID found: {space_id_startup}") | |
| print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}") | |
| print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main") | |
| else: | |
| print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.") | |
| print("-"*(60 + len(" App Starting ")) + "\n") | |
| print("Launching Gradio Interface for Basic Agent Evaluation...") | |
| demo.launch(debug=True, share=False) |