Download agent.py from albertCHY/AgentsCourse_Final_Assignment: direct link, hf CLI and curl.
- Browser
- Download file 10.3 kB
-
https://huggingface.co/spaces/albertCHY/AgentsCourse_Final_Assignment/resolve/main/agent.py
- Command line
-
hf download hf://spaces/albertCHY/AgentsCourse_Final_Assignment/agent.py
-
curl -L -o agent.py https://huggingface.co/spaces/albertCHY/AgentsCourse_Final_Assignment/resolve/main/agent.py
10.3 kB
| import base64 | |
| import os | |
| import io | |
| import contextlib | |
| import requests | |
| from typing import TypedDict, Annotated | |
| from langchain_core.messages import AnyMessage | |
| from langgraph.graph import START, StateGraph, add_messages | |
| from langgraph.prebuilt import ToolNode, tools_condition | |
| from langchain_community.tools import tool, DuckDuckGoSearchRun | |
| from langchain_community.utilities import DuckDuckGoSearchAPIWrapper | |
| from langchain_google_genai import ChatGoogleGenerativeAI | |
| # from pathlib import Path | |
| # import tempfile | |
| from dotenv import load_dotenv | |
| import time | |
| import random | |
| # constants | |
| API_URL = "https://agents-course-unit4-scoring.hf.space" | |
| QUESTIONS_URL = f"{API_URL}/questions" | |
| FILES_URL = f"{API_URL}/files" | |
| SUBMIT_URL = f"{API_URL}/submit" | |
| load_dotenv() | |
| class AgentState(TypedDict): | |
| messages: Annotated[list[AnyMessage], add_messages] | |
| # file_path: str | None | |
| # task_id: str | None | |
| # url: str | None | |
| def build_gemini_llm(): | |
| if not os.environ.get("GOOGLE_API_KEY"): | |
| raise ValueError("GOOGLE_API_KEY environment variable is not set.") | |
| return ChatGoogleGenerativeAI(model = "gemini-3.7-flash", temperature = 0, max_output_tokens = 1025, include_thoughts=True) | |
| # @tool | |
| # def extract_text_from_image(img_path: str) -> str: | |
| # """ | |
| # Describe the image and extract any text in it. | |
| # Args: | |
| # img_path (str): the path to the image file. | |
| # """ | |
| # all_text = "" | |
| # try: | |
| # # Read image and encode as base64 | |
| # with open(img_path, "rb") as image_file: | |
| # image_bytes = image_file.read() | |
| # image_base64 = base64.b64encode(image_bytes).decode("utf-8") | |
| # # Prepare the prompt including the base64 image data | |
| # message = [ | |
| # HumanMessage( | |
| # content=[ | |
| # { | |
| # "type": "text", | |
| # "text": ( | |
| # "Describe the image and extract any text in it." | |
| # ), | |
| # }, | |
| # { | |
| # "type": "image_url", | |
| # "image_url": { | |
| # "url": f"data:image/png;base64,{image_base64}" | |
| # }, | |
| # }, | |
| # ] | |
| # ) | |
| # ] | |
| # response = model.invoke(message) | |
| # # Append extracted text | |
| # all_text += response.text + "\n\n" | |
| # return all_text.strip() | |
| # except Exception as e: | |
| # # A butler should handle errors gracefully | |
| # error_msg = f"Error extracting text: {str(e)}" | |
| # print(error_msg) | |
| # return "" | |
| # @tool | |
| # def download_and_read_file(task_id: str) -> str: | |
| # """ | |
| # Download and read the file attached to the GAIA task its contents. | |
| # Always call this first if there is a file attached to a GAIA Task. | |
| # Args: | |
| # task_id (str): The ID of the GAIA task. | |
| # Returns: | |
| # str: The contents of the file as a string. | |
| # """ | |
| # try: | |
| # # Download the file from the GAIA API | |
| # response = requests.get(f"{FILES_URL}/{task_id}", timeout = 10) | |
| # response.raise_for_status() | |
| # # Determine the file type and read its contents | |
| # content_disposition = response.headers.get("content-disposition", "") | |
| # content_type = response.headers.get("content-type", "") | |
| # filename = None | |
| # if "filename=" in content_disposition: | |
| # filename = content_disposition.split("filename=")[1].strip('"') | |
| # if not filename: | |
| # filename = f"{task_id}.bin" | |
| # ext = Path(filename).suffix.lower() | |
| # if ext in(".txt", ".py", ".json", ".md", ".ymal", ".html", ".xml", ""): | |
| # return response.text | |
| # if ext == ".xlsx" or "xlsx" in content_type: | |
| # import pandas as pd | |
| # with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as file: | |
| # file.write(response.content) | |
| # temp_path = file.name | |
| # read_file = pd.read_excel(temp_path) | |
| # return read_file.to_string() | |
| # if ext == ".csv" or "csv" in content_type: | |
| # import pandas as pd | |
| # with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as file: | |
| # file.write(response.content) | |
| # temp_path = file.name | |
| # read_file = pd.read_csv(temp_path) | |
| # return read_file.to_string() | |
| # if ext == ".csv" or "csv" in content_type: | |
| # import pandas as pd | |
| # with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as file: | |
| # file.write(response.content) | |
| # temp_path = file.name | |
| # read_file = pd.read_csv(temp_path) | |
| # return read_file.to_string() | |
| # if ext == ".jpg" or ext == ".jpeg" or ext == ".png" or "image" in content_type: | |
| # from PIL import Image | |
| # with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as file: | |
| # file.write(response.content) | |
| # temp_path = file.name | |
| # return extract_text_from_image(temp_path) | |
| # # Unsupported file type | |
| # return ( | |
| # f"Unsupported file type: {content_type}. " | |
| # "I downloaded the file successfully, but I don't know " | |
| # "how to extract its contents." | |
| # ) | |
| # except requests.RequestException as e: | |
| # return f"Failed to download file: {e}" | |
| # except Exception as e: | |
| # return f"Failed to read file: {e}" | |
| # except Exception as e: | |
| # return f"error downloading or reading file: {str(e)}" | |
| def wikipedia_search(query: str) -> str: | |
| """ | |
| Search Wikipedia for factual and encyclopedic information. | |
| Use this tool FIRST for: | |
| - people | |
| - historical events | |
| - countries and places | |
| - musicians, artists, movies, books | |
| - scientific concepts | |
| - biographies | |
| - general factual knowledge | |
| Wikipedia is preferred for stable, well-known topics. | |
| Args: | |
| query: Keywords to search on Wikipedia. | |
| """ | |
| url = "https://en.wikipedia.org/w/api.php" | |
| params = { | |
| "action": "query", | |
| "list": "search", | |
| "srsearch": query, | |
| "format": "json", | |
| "srlimit": 3, | |
| "utf8": 1, | |
| } | |
| headers = { | |
| "User-Agent": "MyLangGraphAgent/1.0" | |
| } | |
| max_retries = 3 | |
| for attempt in range(max_retries): | |
| try: | |
| response = requests.get( | |
| url, | |
| params=params, | |
| headers=headers, | |
| timeout=10, | |
| ) | |
| print( | |
| f"Wikipedia status={response.status_code}, " | |
| f"content-type={response.headers.get('content-type')}" | |
| ) | |
| response.raise_for_status() | |
| data = response.json() | |
| results = data.get("query", {}).get("search", []) | |
| if not results: | |
| return f"No Wikipedia results found for '{query}'." | |
| MAX_CONTENT_LENGTH = 3000 | |
| return "\n\n---\n\n".join( | |
| f"Title: {item['title']}\n" | |
| f"Snippet: {item.get('snippet', '')}" | |
| for item in results | |
| ) | |
| except requests.exceptions.RequestException as e: | |
| print( | |
| f"Wikipedia request failed " | |
| f"attempt {attempt + 1}/{max_retries}: {e}" | |
| ) | |
| except requests.exceptions.JSONDecodeError: | |
| print( | |
| f"Wikipedia returned non-JSON response. " | |
| f"Status={response.status_code}" | |
| ) | |
| print("Response preview:") | |
| print(response.text[:500]) | |
| if attempt < max_retries - 1: | |
| delay = 2 ** attempt + random.random() | |
| print(f"Retrying in {delay:.2f}s") | |
| time.sleep(delay) | |
| return ( | |
| f"Wikipedia search temporarily failed for '{query}'. " | |
| "Please use another search source." | |
| ) | |
| # fix wikipedia engine builds invalid URL for region="wt-wt"(default) issue from duckducksearchrun | |
| search_wrapper = DuckDuckGoSearchAPIWrapper( | |
| region="us-en", | |
| backend="duckduckgo", | |
| ) | |
| search_ddgs = DuckDuckGoSearchRun( | |
| api_wrapper=search_wrapper | |
| ) | |
| def search_web(query: str) -> str: | |
| """ | |
| Search the public web for information not suitable for Wikipedia. | |
| Use this tool for: | |
| - recent news | |
| - current events | |
| - latest information | |
| - official websites | |
| - product information | |
| - information that may have changed recently | |
| Do NOT use this tool as the first choice for stable | |
| encyclopedic information that can be found on Wikipedia. | |
| Args: | |
| query: Keywords to search, only keywords and spaces. | |
| """ | |
| try: | |
| result = search_ddgs.invoke(query) | |
| if not result: | |
| return f"No web results found for: {query}" | |
| print("ddgs result:") | |
| print(result) | |
| return result | |
| except Exception as e: | |
| print(f"DuckDuckGo search failed for {query}: {e}") | |
| return ( | |
| f"Web search failed for query: {query}\n" | |
| f"Error: {type(e).__name__}: {e}\n" | |
| "Please try a different search query." | |
| ) | |
| model = build_gemini_llm() | |
| tools = [ | |
| search_web, | |
| wikipedia_search | |
| ] | |
| model_with_tools = model.bind_tools(tools) | |
| def assistant(state: AgentState): | |
| response = model_with_tools.invoke(state["messages"]) | |
| print("\n===== Thinking Section =====") | |
| print(response.content[0].get("thinking").strip()) | |
| print("===============================\n") | |
| print("\n===== ASSISTANT RESPONSE =====") | |
| print("TYPE:", type(response)) | |
| print("CONTENT:", response.content) | |
| print("TOOL CALLS:", response.tool_calls) | |
| print("===============================\n") | |
| return { | |
| "messages": [response], | |
| # "file_path": state["file_path"], | |
| # "task_id": state["task_id"], | |
| # "url": state["url"] | |
| } | |
| builder = StateGraph(AgentState) | |
| builder.add_node("assistant", assistant) | |
| builder.add_node("tools", ToolNode(tools)) | |
| builder.add_edge(START, "assistant") | |
| builder.add_conditional_edges("assistant", tools_condition) | |
| builder.add_edge("tools", "assistant") | |
| graph = builder.compile() | |