kamorou commited on
Commit
20bc99a
·
verified ·
1 Parent(s): e59d229

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -99
app.py CHANGED
@@ -260,8 +260,10 @@ import operator
260
  # --- LangChain & LangGraph Imports ---
261
  from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage, AIMessage, SystemMessage
262
  from langchain_core.tools import tool
263
- # <<<--- CHANGE: Import the HuggingFaceEndpoint for open-source models --->>>
264
  from langchain_huggingface import HuggingFaceEndpoint
 
 
 
265
  from langgraph.graph import StateGraph, END
266
  from langgraph.prebuilt import ToolNode
267
  from tavily import TavilyClient
@@ -272,7 +274,7 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
272
  FILES_DIR = "./files"
273
  os.makedirs(FILES_DIR, exist_ok=True)
274
 
275
- # --- System Prompt (Unchanged, it's strong and model-agnostic) ---
276
  AGENT_SYSTEM_PROMPT = """You are a world-class AI agent, specialized in solving complex problems from the GAIA benchmark.
277
  Your task is to analyze the user's question, think step-by-step, and use the provided tools to find the correct answer.
278
  CRITICAL INSTRUCTIONS:
@@ -298,9 +300,7 @@ tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
298
 
299
  @tool
300
  def tavily_search(query: str) -> str:
301
- """
302
- Uses the Tavily Search API to find information on the web.
303
- """
304
  print(f"--- Calling Tavily Search Tool with query: {query} ---")
305
  try:
306
  result = tavily.search(query=query, search_depth="advanced")
@@ -310,108 +310,75 @@ def tavily_search(query: str) -> str:
310
 
311
  @tool
312
  def read_file(url: str) -> str:
313
- """
314
- Downloads a file from a given URL, saves it locally, and returns its content.
315
- It can handle both plain text files and PDF files.
316
- """
317
  print(f"--- Calling Read File Tool with URL: {url} ---")
318
  try:
319
  filename = os.path.join(FILES_DIR, os.path.basename(url))
320
  response = requests.get(url)
321
  response.raise_for_status()
322
-
323
- with open(filename, 'wb') as f:
324
- f.write(response.content)
325
 
326
  if url.lower().endswith('.pdf'):
327
- print(f"--- File identified as PDF. Reading with pypdf. ---")
328
  try:
329
  pdf_reader = pypdf.PdfReader(filename)
330
- content = ""
331
- for page in pdf_reader.pages:
332
- content += page.extract_text()
333
- return f"Successfully read PDF file '{filename}'. Content:\n\n{content}"
334
- except Exception as e:
335
- return f"Error reading PDF file: {e}"
336
  else:
337
- print(f"--- File identified as text. Reading normally. ---")
338
  try:
339
- with open(filename, 'r', encoding='utf-8') as f:
340
- content = f.read()
341
- return f"Successfully read text file '{filename}'. Content:\n\n{content}"
342
- except UnicodeDecodeError:
343
- return f"Successfully downloaded binary file '{filename}'. Cannot display content as text."
344
-
345
- except requests.exceptions.RequestException as e:
346
- return f"Error downloading or reading file: {e}"
347
 
348
  @tool
349
  def python_interpreter(code: str) -> str:
350
- """
351
- Executes a given string of Python code and returns the output from stdout.
352
- """
353
  print(f"--- Calling Python Interpreter Tool with code:\n{code} ---")
354
  output_buffer = io.StringIO()
355
  try:
356
- with redirect_stdout(output_buffer):
357
- exec(code, globals())
358
  return f"Code executed successfully. Output:\n{output_buffer.getvalue()}"
359
- except Exception as e:
360
- return f"Error executing Python code: {e}"
361
 
362
  #
363
  # ================================================================================================
364
- # ✅ 2. CONFIGURE AND BUILD THE AGENT GRAPH (WITH HUGGING FACE)
365
  # ================================================================================================
366
  #
367
  class AgentState(TypedDict):
368
- messages: Annotated[List[BaseMessage], operator.add]
369
-
 
 
370
  def build_agent_graph():
371
  """Builds the LangGraph agent."""
372
  tools = [tavily_search, read_file, python_interpreter]
373
 
374
- # <<<--- CHANGE: Instantiate the Hugging Face Model Endpoint --->>>
375
- # This uses the recommended Command R+ model for its excellent tool-use capabilities.
376
- # It will automatically use the HUGGINGFACEHUB_API_TOKEN secret.
377
  repo_id = "CohereForAI/c4ai-command-r-plus"
378
  llm = HuggingFaceEndpoint(
379
- repo_id=repo_id,
380
- max_new_tokens=1024,
381
- temperature=0, # Keep temperature low for fact-based tasks
382
  huggingfacehub_api_token=os.getenv("HUGGINGFACEHUB_API_TOKEN")
383
  )
384
 
385
- llm_with_tools = llm.bind_tools(tools)
386
-
387
- def call_model(state: AgentState) -> dict:
388
- """Helper function to prepare messages and call the model."""
389
- messages = state['messages']
390
- # The HuggingFaceEndpoint doesn't support a separate SystemMessage.
391
- # We'll format the system prompt and the latest human message together.
392
- if isinstance(messages[0], SystemMessage):
393
- # Start with the system message content
394
- formatted_messages = [HumanMessage(content=messages[0].content + "\n\nHere is the user's question:\n" + messages[-1].content)]
395
- # Add any previous tool outputs
396
- formatted_messages.extend(messages[1:-1])
397
- else:
398
- formatted_messages = messages
399
-
400
- response = llm_with_tools.invoke(formatted_messages)
401
- return {"messages": [response]}
402
-
403
- def should_continue(state: AgentState) -> str:
404
- """Determines whether to continue the loop or end."""
405
- return "action" if state['messages'][-1].tool_calls else "end"
406
 
407
- tool_node = ToolNode(tools)
408
- workflow = StateGraph(AgentState)
409
- workflow.add_node("agent", call_model)
410
- workflow.add_node("action", tool_node)
411
- workflow.set_entry_point("agent")
412
- workflow.add_conditional_edges("agent", should_continue, {"action": "action", "end": END})
413
- workflow.add_edge('action', 'agent')
414
- return workflow.compile()
415
 
416
  #
417
  # ================================================================================================
@@ -420,28 +387,26 @@ def build_agent_graph():
420
  #
421
  class GaiaAgent:
422
  def __init__(self):
423
- # <<<--- CHANGE: Update print statement for new model --->>>
424
- print("GaiaAgent initialized. Building fresh Command R+ agent graph...")
425
  self.agent_app = build_agent_graph()
426
 
427
  def __call__(self, question: str) -> str:
428
  print(f"\n{'='*60}\nAgent received question: {question[:100]}...\n{'='*60}")
429
- initial_input = {
430
- "messages": [
431
- SystemMessage(content=AGENT_SYSTEM_PROMPT),
432
- HumanMessage(content=question)
433
- ]
434
- }
435
- final_state = None
436
- for i, step in enumerate(self.agent_app.stream(initial_input, {"recursion_limit": 15})):
437
- if i == 0: print("--- Starting Agentic Loop ---")
438
- final_state = step
439
-
440
- final_answer_message = final_state['agent']['messages'][-1]
441
- final_answer = str(final_answer_message.content).strip()
442
- print(f"\n--- Agent finished. Final Answer: {final_answer} ---\n")
443
- return final_answer
444
 
 
445
  def run_and_submit_all( profile: gr.OAuthProfile | None):
446
  space_id = os.getenv("SPACE_ID")
447
  if not profile: return "Please Login to Hugging Face with the button.", None
@@ -466,13 +431,16 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
466
  answers_payload = []
467
  print(f"Running agent on {len(questions_data)} questions...")
468
 
 
 
 
469
  for item in questions_data:
470
  task_id = item.get("task_id")
471
  question_text = item.get("question")
472
  if not task_id or question_text is None: continue
473
  try:
474
- agent = GaiaAgent()
475
- submitted_answer = agent(question_text)
476
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
477
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
478
  except Exception as e:
@@ -502,17 +470,15 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
502
  results_df = pd.DataFrame(results_log)
503
  return status_message, results_df
504
 
505
- # --- Gradio Interface ---
506
  with gr.Blocks() as demo:
507
- # <<<--- CHANGE: Update UI titles and descriptions for the new model --->>>
508
- gr.Markdown("# GAIA Agent Final Assessment (Open Source: Command R+)")
509
  gr.Markdown(
510
  """
511
- **Instructor's Note:** This version runs a top-tier open-source model from the Hugging Face Hub: **`CohereForAI/c4ai-command-r-plus`**.
512
- This model is state-of-the-art for agentic tool use.
513
  1. Ensure you have a **`HUGGINGFACEHUB_API_TOKEN`** and a **`TAVILY_API_KEY`** set in your Space secrets.
514
- 2. Ensure your `requirements.txt` includes `langchain-huggingface`.
515
- 3. Good luck! Let's see how this powerful open model performs.
516
  """
517
  )
518
  gr.LoginButton()
 
260
  # --- LangChain & LangGraph Imports ---
261
  from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage, AIMessage, SystemMessage
262
  from langchain_core.tools import tool
 
263
  from langchain_huggingface import HuggingFaceEndpoint
264
+ # <<<--- CHANGE 1: Import new components for building the agent --->>>
265
+ from langchain.agents import AgentExecutor, create_tool_calling_agent
266
+ from langchain_core.prompts import ChatPromptTemplate
267
  from langgraph.graph import StateGraph, END
268
  from langgraph.prebuilt import ToolNode
269
  from tavily import TavilyClient
 
274
  FILES_DIR = "./files"
275
  os.makedirs(FILES_DIR, exist_ok=True)
276
 
277
+ # --- System Prompt (Unchanged) ---
278
  AGENT_SYSTEM_PROMPT = """You are a world-class AI agent, specialized in solving complex problems from the GAIA benchmark.
279
  Your task is to analyze the user's question, think step-by-step, and use the provided tools to find the correct answer.
280
  CRITICAL INSTRUCTIONS:
 
300
 
301
  @tool
302
  def tavily_search(query: str) -> str:
303
+ """Uses the Tavily Search API to find information on the web."""
 
 
304
  print(f"--- Calling Tavily Search Tool with query: {query} ---")
305
  try:
306
  result = tavily.search(query=query, search_depth="advanced")
 
310
 
311
  @tool
312
  def read_file(url: str) -> str:
313
+ """Downloads and reads the content of a file (text or PDF) from a URL."""
 
 
 
314
  print(f"--- Calling Read File Tool with URL: {url} ---")
315
  try:
316
  filename = os.path.join(FILES_DIR, os.path.basename(url))
317
  response = requests.get(url)
318
  response.raise_for_status()
319
+ with open(filename, 'wb') as f: f.write(response.content)
 
 
320
 
321
  if url.lower().endswith('.pdf'):
 
322
  try:
323
  pdf_reader = pypdf.PdfReader(filename)
324
+ return f"Successfully read PDF file '{filename}'. Content:\n\n{''.join(p.extract_text() for p in pdf_reader.pages)}"
325
+ except Exception as e: return f"Error reading PDF file: {e}"
 
 
 
 
326
  else:
 
327
  try:
328
+ with open(filename, 'r', encoding='utf-8') as f: return f"Successfully read text file '{filename}'. Content:\n\n{f.read()}"
329
+ except UnicodeDecodeError: return f"Successfully downloaded binary file '{filename}'. Cannot display content as text."
330
+ except requests.exceptions.RequestException as e: return f"Error downloading or reading file: {e}"
 
 
 
 
 
331
 
332
  @tool
333
  def python_interpreter(code: str) -> str:
334
+ """Executes Python code and returns its stdout."""
 
 
335
  print(f"--- Calling Python Interpreter Tool with code:\n{code} ---")
336
  output_buffer = io.StringIO()
337
  try:
338
+ with redirect_stdout(output_buffer): exec(code, globals())
 
339
  return f"Code executed successfully. Output:\n{output_buffer.getvalue()}"
340
+ except Exception as e: return f"Error executing Python code: {e}"
 
341
 
342
  #
343
  # ================================================================================================
344
+ # ✅ 2. CONFIGURE AND BUILD THE AGENT GRAPH (CORRECTED IMPLEMENTATION)
345
  # ================================================================================================
346
  #
347
  class AgentState(TypedDict):
348
+ # <<<--- CHANGE 2: The state is now simpler. It tracks the input and the agent's output. --->>>
349
+ input: str
350
+ agent_outcome: dict | None
351
+
352
  def build_agent_graph():
353
  """Builds the LangGraph agent."""
354
  tools = [tavily_search, read_file, python_interpreter]
355
 
 
 
 
356
  repo_id = "CohereForAI/c4ai-command-r-plus"
357
  llm = HuggingFaceEndpoint(
358
+ repo_id=repo_id, max_new_tokens=1024, temperature=0.1,
 
 
359
  huggingfacehub_api_token=os.getenv("HUGGINGFACEHUB_API_TOKEN")
360
  )
361
 
362
+ # <<<--- CHANGE 3: Create a proper agent prompt template --->>>
363
+ # This prompt is specifically designed to instruct the model on how to use tools.
364
+ prompt = ChatPromptTemplate.from_messages([
365
+ ("system", AGENT_SYSTEM_PROMPT),
366
+ ("human", "{input}"),
367
+ # The 'agent_scratchpad' placeholder is crucial for the agent to remember previous tool calls.
368
+ ("placeholder", "{agent_scratchpad}"),
369
+ ])
370
+
371
+ # <<<--- CHANGE 4: Create the tool-calling agent runnable --->>>
372
+ agent = create_tool_calling_agent(llm, tools, prompt)
373
+
374
+ # <<<--- CHANGE 5: Create the AgentExecutor which will run the agent loop --->>>
375
+ # This replaces the manual LangGraph loop for calling the model and tools.
376
+ # It's a robust, pre-built component for this exact purpose.
377
+ agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
 
 
 
 
 
378
 
379
+ # We are not using LangGraph for the agent loop anymore, as AgentExecutor handles it.
380
+ # We just need a simple callable class that invokes it.
381
+ return agent_executor
 
 
 
 
 
382
 
383
  #
384
  # ================================================================================================
 
387
  #
388
  class GaiaAgent:
389
  def __init__(self):
390
+ print("GaiaAgent initialized. Building Command R+ agent with AgentExecutor...")
391
+ # The agent_app is now the fully-formed AgentExecutor
392
  self.agent_app = build_agent_graph()
393
 
394
  def __call__(self, question: str) -> str:
395
  print(f"\n{'='*60}\nAgent received question: {question[:100]}...\n{'='*60}")
396
+
397
+ # <<<--- CHANGE 6: Invoke the AgentExecutor and extract the final answer --->>>
398
+ try:
399
+ # The AgentExecutor takes a dictionary and returns the final output in the 'output' key.
400
+ response = self.agent_app.invoke({"input": question})
401
+ final_answer = str(response.get("output", "")).strip()
402
+ print(f"\n--- Agent finished. Final Answer: {final_answer} ---\n")
403
+ return final_answer
404
+ except Exception as e:
405
+ print(f"An error occurred during agent execution: {e}")
406
+ return f"AGENT_EXECUTION_ERROR: {e}"
407
+
 
 
 
408
 
409
+ # --- The rest of the file (run_and_submit_all, Gradio UI) remains the same ---
410
  def run_and_submit_all( profile: gr.OAuthProfile | None):
411
  space_id = os.getenv("SPACE_ID")
412
  if not profile: return "Please Login to Hugging Face with the button.", None
 
431
  answers_payload = []
432
  print(f"Running agent on {len(questions_data)} questions...")
433
 
434
+ # Instantiate the agent once to save time
435
+ agent_instance = GaiaAgent()
436
+
437
  for item in questions_data:
438
  task_id = item.get("task_id")
439
  question_text = item.get("question")
440
  if not task_id or question_text is None: continue
441
  try:
442
+ # Reuse the same agent instance
443
+ submitted_answer = agent_instance(question_text)
444
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
445
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
446
  except Exception as e:
 
470
  results_df = pd.DataFrame(results_log)
471
  return status_message, results_df
472
 
 
473
  with gr.Blocks() as demo:
474
+ gr.Markdown("# GAIA Agent Final Assessment (Open Source: Command R+ - Corrected)")
 
475
  gr.Markdown(
476
  """
477
+ **Instructor's Note:** This version corrects the agent construction logic to be compatible with the `HuggingFaceEndpoint`.
478
+ It now uses the standard `create_tool_calling_agent` and `AgentExecutor` from LangChain for robust tool use.
479
  1. Ensure you have a **`HUGGINGFACEHUB_API_TOKEN`** and a **`TAVILY_API_KEY`** set in your Space secrets.
480
+ 2. Your `requirements.txt` should include `langchain`, `langchain-huggingface`, and `langchain-core`.
481
+ 3. Let's run the evaluation again!
482
  """
483
  )
484
  gr.LoginButton()