# ============================================================================== # 1. IMPORTS AND SETUP # ============================================================================== import os from dotenv import load_dotenv from typing import TypedDict, Annotated, List # LangChain and LangGraph imports from langchain_huggingface import HuggingFaceEndpoint from langchain_community.tools.tavily_search import TavilySearchResults from langchain_experimental.tools import PythonREPLTool from langchain_core.messages import BaseMessage, HumanMessage from langchain_core.prompts import ChatPromptTemplate from langgraph.graph import StateGraph, END from langgraph.prebuilt import ToolNode # ============================================================================== # 2. LOAD API KEYS AND DEFINE TOOLS # ============================================================================== load_dotenv() hf_token = os.getenv("HF_TOKEN") tavily_api_key = os.getenv("TAVILY_API_KEY") if not hf_token or not tavily_api_key: # This will show a clear error in the logs if keys are missing raise ValueError("HF_TOKEN or TAVILY_API_KEY not set. Please add them to your Space secrets.") os.environ["TAVILY_API_KEY"] = tavily_api_key # The agent's tools tools = [TavilySearchResults(max_results=3, description="A search engine for finding up-to-date information on the web."), PythonREPLTool()] tool_node = ToolNode(tools) # ============================================================================== # 3. CONFIGURE THE LLM (THE "BRAIN") # ============================================================================== # The model we'll use as the agent's brain repo_id = "meta-llama/Meta-Llama-3-8B-Instruct" # The system prompt gives the agent its mission and instructions SYSTEM_PROMPT = """You are a highly capable AI agent named 'GAIA-Solver'. Your mission is to accurately answer complex questions. **Your Instructions:** 1. **Analyze:** Carefully read the user's question to understand all parts of what is being asked. 2. **Plan:** Think step-by-step. Break the problem into smaller tasks. Decide which tool is best for each task. (e.g., use 'tavily_search_results_json' for web searches, use 'python_repl' for calculations or code execution). 3. **Execute:** Call ONE tool at a time. 4. **Observe & Reason:** After getting a tool's result, observe it. Decide if you have the final answer or if you need to use another tool. 5. **Final Answer:** Once you are confident, provide a clear, direct, and concise final answer. Do not include your thought process in the final answer. """ # Initialize the LLM endpoint llm = HuggingFaceEndpoint( repo_id=repo_id, huggingfacehub_api_token=hf_token, temperature=0, # Set to 0 for deterministic, less random output max_new_tokens=2048, ) # ============================================================================== # 4. BUILD THE LANGGRAPH AGENT # ============================================================================== # Define the Agent's State (its memory) class AgentState(TypedDict): messages: Annotated[List[BaseMessage], lambda x, y: x + y] # This is a more robust way to combine the prompt, model, and tool binding # It ensures the system prompt is always used. llm_with_tools = llm.bind_tools(tools) # Define the Agent Node def agent_node(state): # Get the last message to pass to the model last_message = state['messages'][-1] # Prepend the system prompt to every call prompt_with_system = [ HumanMessage(content=SYSTEM_PROMPT, name="system_prompt"), last_message ] response = llm_with_tools.invoke(prompt_with_system) return {"messages": [response]} # Define the Edge Logic def should_continue(state): last_message = state["messages"][-1] if last_message.tool_calls: return "tools" # Route to the tool node return END # End the process # Assemble the graph workflow = StateGraph(AgentState) workflow.add_node("agent", agent_node) workflow.add_node("tools", tool_node) workflow.set_entry_point("agent") workflow.add_conditional_edges( "agent", should_continue, {"tools": "tools", "end": END}, ) workflow.add_edge("tools", "agent") # Compile the graph into a runnable app app = workflow.compile() # ============================================================================== # 5. THE BASICAGENT CLASS (FOR THE TEST HARNESS) # This MUST be at the end, after `app` is defined. # ============================================================================== class BasicAgent: """ This is the agent class that the GAIA test harness will use. """ def __init__(self): # The compiled LangGraph app is our agent executor self.agent_executor = app def run(self, question: str) -> str: """ This method is called by the test script with each question. It runs the LangGraph agent and returns the final answer. """ print(f"Agent received question (first 80 chars): {question[:80]}...") try: # Format the input for our graph inputs = {"messages": [HumanMessage(content=question)]} # Stream the response to get the final answer final_response = "" for s in self.agent_executor.stream(inputs, {"recursion_limit": 15}): if "agent" in s: # The final answer is the content of the last message from the agent node if s["agent"]["messages"][-1].content: final_response = s["agent"]["messages"][-1].content # A fallback in case the agent finishes without a clear message if not final_response: final_response = "Agent finished but did not produce a final answer." print(f"Agent returning final answer (first 80 chars): {final_response[:80]}...") return final_response except Exception as e: print(f"An error occurred in agent execution: {e}") return f"Error: {e}"