CoolFace
Apppublic

pabloescobar18/Final_Assignment_Template

sourceHugging Faceapache-2.0updated 2d agoView on Hugging Face
0likes
agent.py268 linesDownload Raw Back to root
1"""LangGraph Agent"""2 3import os4import requests5from dotenv import load_dotenv6from bs4 import BeautifulSoup7from langchain_community.document_loaders import WikipediaLoader8from langchain_community.document_loaders import ArxivLoader9from langchain_community.utilities import WikipediaAPIWrapper10from langchain_core.messages import HumanMessage, SystemMessage11from langchain_core.tools import tool12from langchain_google_genai import ChatGoogleGenerativeAI13from langchain_community.tools.tavily_search import TavilySearchResults14from langchain_tavily import TavilySearch15from langchain_groq import ChatGroq16from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint17from langgraph.graph import START, MessagesState, StateGraph18from langgraph.prebuilt import ToolNode, tools_condition19from tools.web_search import duckduckgo_web_search20from tools.code_interpreter import python_interpreter21from tools.transcribe_audio import transcribe_audio22import wikipedia23 24 25 26load_dotenv()27 28groq_api_key = os.getenv("GROQ_API_KEY")29print(30    f"DEBUG: GROQ_API_KEY encontrada: {bool(os.getenv('GROQ_API_KEY'))}"31)32 33# load the system prompt from the file34with open("system_prompt.txt", "r", encoding="utf-8") as f:35    system_prompt = f.read()# System message36sys_msg = SystemMessage(content=system_prompt)37 38 39 40wikipedia.set_user_agent(41    "GAIA_LangGraph_Agent/1.0"42    " (https://huggingface.co/agents-course; student_agent@example.com)"43)44# Instancia el wrapper fuera de la función para no re-crearlo en cada llamada45api_wrapper = WikipediaAPIWrapper(46    top_k_results=2,           # Máximo 2 resultados47    doc_content_chars_max=3500, # Limita el tamaño de cada texto48    lang="en"                  # Idioma (cambia a "en" si buscas en inglés)49)50 51@tool52def wikipedia_search(query: str) -> str:53  """Search English Wikipedia for a query and return rich document context with source URLs.54 55  Args:56      query: The search query string or topic name.57  """58  try:59    # .load() devuelve una lista de objetos Document60    docs = api_wrapper.load(query)61 62    if not docs:63      return f"No results found on Wikipedia for '{query}'."64 65    formatted_docs = []66    for doc in docs:67      # Extraer los metadatos que extrae WikipediaAPIWrapper68      source_url = doc.metadata.get(69          "source", f"https://en.wikipedia.org/wiki/{query}"70      )71      title = doc.metadata.get("title", query)72 73      # Formato XML enriquecido para que el LLM tenga contexto y la URL directa74      formatted_doc = (75          f'<Document title="{title}" source="{source_url}">\n'76          f"{doc.page_content}\n"77          f"</Document>"78      )79      formatted_docs.append(formatted_doc)80 81    return "\n\n---\n\n".join(formatted_docs)82 83  except Exception as e:84    return (85        f"Error searching Wikipedia: {str(e)}. Fallback to using"86        " 'duckduckgo_web_search'."87    )88 89@tool90def fetch_web_content(url: str) -> str:91  """Downloads and extracts clean plain text from a web page or file URL.92 93  Use this tool when you need to read the full content of a specific web URL,94  plain text file, or source code file. It automatically strips HTML tags,95  scripts, and extra whitespace to return readable text.96 97  Args:98      url: The full HTTP or HTTPS URL of the webpage or document to fetch (e.g.,99        'https://example.com/file.txt').100 101  Returns:102      str: The clean text content of the page (truncated if too long), or an103      error message if the request fails.104  """105  try:106    headers = {"User-Agent": "Mozilla/5.0"}107    response = requests.get(url, headers=headers, timeout=10)108    response.raise_for_status()109 110    # Convertir el HTML a texto plano usando BeautifulSoup111    soup = BeautifulSoup(response.text, "html.parser")112 113    # Eliminar scripts y estilos CSS114    for script in soup(["script", "style"]):115      script.decompose()116 117    text = soup.get_text(separator="\n")118 119    # Limpiar espacios en blanco excesivos120    lines = (line.strip() for line in text.splitlines())121    chunks = (phrase.strip() for line in lines for phrase in line.split("  "))122    clean_text = "\n".join(chunk for chunk in chunks if chunk)123 124    # RECORTE DE SEGURIDAD: Máximo 4000 caracteres para no saturar al LLM125    if len(clean_text) > 4000:126      clean_text = (127          clean_text[:4000]128          + "\n... [Texto truncado por longitud excesiva] ..."129      )130 131    return clean_text132 133  except Exception as e:134    return f"Error al obtener el contenido de la URL: {e}"135 136 137@tool138def multiply(a: int, b: int) -> int:139  """Multiply two numbers."""140  return a * b141 142 143@tool144def add(a: int, b: int) -> int:145  """Add two numbers."""146  return a + b147 148 149@tool150def subtract(a: int, b: int) -> int:151  """Subtract two numbers."""152  return a - b153 154@tool155def web_search(query: str) -> str:156    """Search Tavily for a query and return maximum 3 results.157    158    Args:159        query: The search query."""160    search_docs = TavilySearch(max_results=3).invoke({"query": query})161 162    formatted_search_docs = "\n\n---\n\n".join(163        [164            f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'165            for doc in search_docs166        ])167    return {"web_results": formatted_search_docs}168 169# Instanciar la tool nativa con la configuración deseada170web_search = TavilySearch(max_results=3)171 172 173@tool174def arvix_search(query: str) -> str:175    """Search Arxiv for a query and return maximum 3 result.176    177    Args:178        query: The search query."""179    search_docs = ArxivLoader(query=query, load_max_docs=3).load()180    formatted_search_docs = "\n\n---\n\n".join(181        [182            f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'183            for doc in search_docs184        ])185    return {"arvix_results": formatted_search_docs}186 187 188tools = [multiply, add, subtract, wikipedia_search,189         arvix_search, 190          web_search, python_interpreter,191          transcribe_audio]192#fetch_web_content193 194def build_graph(provider: str = "groq"):195  """Build the graph"""196 197  if provider == "huggingface":198    # Lee el token guardado en los Secrets de tu Space199    hf_token = os.getenv("HUGGINGFACEHUB_API_TOKEN") or os.getenv("HF_TOKEN")200 201    endpoint = HuggingFaceEndpoint(202      repo_id="Qwen/Qwen2.5-Coder-32B-Instruct",  # Excelente para tareas de agentes y herramientas203      task="text-generation",204      temperature=0.1)205 206    llm = ChatHuggingFace(llm=endpoint)207 208  elif provider == "google":209    llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)210 211  elif provider == "groq":212    # 1. Leer la clave213    key = os.getenv("GROQ_API_KEY")214 215    if not key:216      raise ValueError("GROQ_API_KEY no encontrada.")217 218    # 2. FIX CRÍTICO: Limpiar cualquier redirección inyectada por Hugging Face Space219    os.environ.pop("OPENAI_BASE_URL", None)220    os.environ.pop("OPENAI_API_BASE", None)221    os.environ.pop("OPENAI_API_KEY", None)222 223    # 3. Instanciar ChatGroq con la URL base explícita y un modelo 100% estable224    llm = ChatGroq(225        model="openai/gpt-oss-120b",                    # Usamos el nombre base más estable de Groq226        api_key=key,227        temperature=0,228        max_tokens=512,229        max_retries=3,230    )231    print("Modelo Groq cargado con URL protegida")232 233    if llm:234      print('Modelo cargado correctamente')235 236  else:237    raise ValueError(238        "Invalid provider. Choose 'huggingface', 'google' or 'groq'."239    )240 241  llm_with_tools = llm.bind_tools(tools)242 243  def assistant(state: MessagesState):244    """Assistant node con control de ventana de contexto para no agotar tokens de Groq."""245    messages = state["messages"]246 247    # Si el historial crece demasiado (más de 6 mensajes en la misma pregunta),248    # preservamos el SystemMessage (messages[0]) y los últimos 4 mensajes.249    #if len(messages) > 10:250    #  messages = [messages[0]] + messages[-4:]251 252    return {"messages": [llm_with_tools.invoke(messages)]}253 254  def retriever(state: MessagesState):255    """Retriever node simplified"""256    return {"messages": [sys_msg] + state["messages"]}257 258  builder = StateGraph(MessagesState)259  builder.add_node("retriever", retriever)260  builder.add_node("assistant", assistant)261  builder.add_node("tools", ToolNode(tools))262 263  builder.add_edge(START, "retriever")264  builder.add_edge("retriever", "assistant")265  builder.add_conditional_edges("assistant", tools_condition)266  builder.add_edge("tools", "assistant")267 268  return builder.compile()