kamorou commited on
Commit
15702f5
·
verified ·
1 Parent(s): 9dd760b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -68
app.py CHANGED
@@ -232,6 +232,20 @@
232
  #
233
  # =================================================================================================
234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  import os
236
  import io
237
  import requests
@@ -244,10 +258,11 @@ import operator
244
  # --- LangChain & LangGraph Imports ---
245
  from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage, AIMessage, SystemMessage
246
  from langchain_core.tools import tool
247
- from langchain_groq import ChatGroq
248
  from langgraph.graph import StateGraph, END
249
  from langgraph.prebuilt import ToolNode
250
- from tavily import TavilyClient # <-- Import Tavily
 
251
 
252
  # (Keep Constants as is)
253
  # --- Constants ---
@@ -255,7 +270,7 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
255
  FILES_DIR = "./files"
256
  os.makedirs(FILES_DIR, exist_ok=True)
257
 
258
- # --- The new, stricter System Prompt ---
259
  AGENT_SYSTEM_PROMPT = """You are a world-class AI agent, specialized in solving complex problems from the GAIA benchmark.
260
 
261
  Your task is to analyze the user's question, think step-by-step, and use the provided tools to find the correct answer.
@@ -278,24 +293,19 @@ Think, use your tools, and then provide ONLY the final, precise answer.
278
 
279
  #
280
  # ================================================================================================
281
- # ✅ 1. DEFINE THE AGENT'S TOOLS (NOW WITH TAVILY)
282
  # ================================================================================================
283
  #
284
- # Initialize the Tavily client. It will automatically use the TAVILY_API_KEY from secrets.
285
  tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
286
 
287
  @tool
288
  def tavily_search(query: str) -> str:
289
  """
290
  Uses the Tavily Search API to find information on the web.
291
- Tavily is optimized for AI agents and provides clean, summarized results.
292
- Use this for any questions that require current, factual, or web-based information.
293
  """
294
  print(f"--- Calling Tavily Search Tool with query: {query} ---")
295
  try:
296
- # Calling the search method with the query
297
  result = tavily.search(query=query, search_depth="advanced")
298
- # Returning the content of the search results
299
  return f"Search results for '{query}':\n" + "\n".join([f"- {r['content']}" for r in result['results']])
300
  except Exception as e:
301
  return f"Error during Tavily search: {e}"
@@ -303,7 +313,8 @@ def tavily_search(query: str) -> str:
303
  @tool
304
  def read_file(url: str) -> str:
305
  """
306
- Downloads a file from a given URL and returns its content.
 
307
  Use this tool when a question provides a URL to a file that needs to be read.
308
  """
309
  print(f"--- Calling Read File Tool with URL: {url} ---")
@@ -311,14 +322,31 @@ def read_file(url: str) -> str:
311
  filename = os.path.join(FILES_DIR, os.path.basename(url))
312
  response = requests.get(url)
313
  response.raise_for_status()
 
314
  with open(filename, 'wb') as f:
315
  f.write(response.content)
316
- try:
317
- with open(filename, 'r', encoding='utf-8') as f:
318
- content = f.read()
319
- return f"Successfully read file '{filename}'. Content:\n\n{content}"
320
- except UnicodeDecodeError:
321
- return f"Successfully downloaded binary file '{filename}'. Cannot display content."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  except requests.exceptions.RequestException as e:
323
  return f"Error downloading or reading file: {e}"
324
 
@@ -326,8 +354,6 @@ def read_file(url: str) -> str:
326
  def python_interpreter(code: str) -> str:
327
  """
328
  Executes a given string of Python code and returns the output from stdout.
329
- Use this for complex calculations, data manipulation, or any task that can be solved with code.
330
- Make sure to use a print() statement to capture the output.
331
  """
332
  print(f"--- Calling Python Interpreter Tool with code:\n{code} ---")
333
  output_buffer = io.StringIO()
@@ -340,88 +366,70 @@ def python_interpreter(code: str) -> str:
340
 
341
  #
342
  # ================================================================================================
343
- # ✅ 2. CONFIGURE AND BUILD THE AGENT GRAPH
344
  # ================================================================================================
345
  #
346
- # This section is now self-contained to be called for each new agent instance.
347
- #
348
-
349
  class AgentState(TypedDict):
350
  messages: Annotated[List[BaseMessage], operator.add]
351
 
352
  def build_agent_graph():
353
  """Builds the LangGraph agent."""
354
  tools = [tavily_search, read_file, python_interpreter]
355
- llm = ChatGroq(model="llama3-70b-8192", temperature=0)
 
 
 
 
356
  llm_with_tools = llm.bind_tools(tools)
357
 
358
  def call_model(state: AgentState) -> dict:
359
- print("--- Calling LLM ---")
360
  messages = state['messages']
361
  response = llm_with_tools.invoke(messages)
362
  return {"messages": [response]}
363
 
364
  def should_continue(state: AgentState) -> str:
365
- last_message = state['messages'][-1]
366
- if last_message.tool_calls:
367
- return "action"
368
- else:
369
- return "end"
370
 
371
  tool_node = ToolNode(tools)
372
  workflow = StateGraph(AgentState)
373
  workflow.add_node("agent", call_model)
374
  workflow.add_node("action", tool_node)
375
  workflow.set_entry_point("agent")
376
- workflow.add_conditional_edges(
377
- "agent",
378
- should_continue,
379
- {"action": "action", "end": END}
380
- )
381
  workflow.add_edge('action', 'agent')
382
  return workflow.compile()
383
 
384
  #
385
  # ================================================================================================
386
- # ✅ 3. CREATE THE AGENT CLASS THAT THE TEMPLATE USES
387
  # ================================================================================================
388
  #
389
  class GaiaAgent:
390
  def __init__(self):
391
- print("GaiaAgent initialized. Building fresh graph...")
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
  initial_input = {
398
  "messages": [
399
  SystemMessage(content=AGENT_SYSTEM_PROMPT),
400
  HumanMessage(content=question)
401
  ]
402
  }
403
-
404
  final_state = None
405
  for i, step in enumerate(self.agent_app.stream(initial_input, {"recursion_limit": 15})):
406
- if i == 0:
407
- print("--- Starting Agentic Loop ---")
408
  final_state = step
409
 
410
  final_answer_message = final_state['agent']['messages'][-1]
411
  final_answer = str(final_answer_message.content).strip()
412
-
413
  print(f"\n--- Agent finished. Final Answer: {final_answer} ---\n")
414
  return final_answer
415
 
416
- #
417
- # ================================================================================================
418
- # -- EVALUATION LOGIC - CRITICAL FIX APPLIED --
419
- # ================================================================================================
420
-
421
  def run_and_submit_all( profile: gr.OAuthProfile | None):
422
  space_id = os.getenv("SPACE_ID")
423
- if not profile:
424
- return "Please Login to Hugging Face with the button.", None
425
  username = f"{profile.username}"
426
  print(f"User logged in: {username}")
427
 
@@ -443,16 +451,11 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
443
  answers_payload = []
444
  print(f"Running agent on {len(questions_data)} questions...")
445
 
446
- #
447
- # --->>> CRITICAL FIX: Instantiate a NEW agent for EACH question <<<---
448
- #
449
  for item in questions_data:
450
  task_id = item.get("task_id")
451
  question_text = item.get("question")
452
- if not task_id or question_text is None:
453
- continue
454
  try:
455
- # A new, clean agent is created here to prevent state leakage.
456
  agent = GaiaAgent()
457
  submitted_answer = agent(question_text)
458
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
@@ -467,7 +470,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
467
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
468
  print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
469
  try:
470
- response = requests.post(submit_url, json=submission_data, timeout=60)
471
  response.raise_for_status()
472
  result_data = response.json()
473
  final_status = (
@@ -477,35 +480,29 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
477
  f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
478
  f"Message: {result_data.get('message', 'No message received.')}"
479
  )
480
- print("Submission successful.")
481
  results_df = pd.DataFrame(results_log)
482
  return final_status, results_df
483
  except Exception as e:
484
  status_message = f"An unexpected error occurred during submission: {e}"
485
- print(status_message)
486
  results_df = pd.DataFrame(results_log)
487
  return status_message, results_df
488
 
489
-
490
- # --- Gradio Interface (No Changes Needed) ---
491
  with gr.Blocks() as demo:
492
- gr.Markdown("# GAIA Agent Final Assessment (V4 - State Fixed)")
493
  gr.Markdown(
494
  """
495
- **Instructor's Note:** This version fixes the critical state-leakage bug and uses the Tavily Search API for better results.
496
- 1. Ensure `GROQ_API_KEY` and `TAVILY_API_KEY` are set in secrets.
497
- 2. Ensure `requirements.txt` includes `tavily-python`.
498
- 3. Log in and run the evaluation. Let's see that score jump!
499
  """
500
  )
501
  gr.LoginButton()
502
  run_button = gr.Button("Run Evaluation & Submit All Answers")
503
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
504
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
505
- run_button.click(
506
- fn=run_and_submit_all,
507
- outputs=[status_output, results_table]
508
- )
509
 
510
  if __name__ == "__main__":
511
  print("\n" + "-"*30 + " App Starting " + "-"*30)
 
232
  #
233
  # =================================================================================================
234
 
235
+ #
236
+
237
+ ######################
238
+ # =================================================================================================
239
+ # ✅ --- ✅ FINAL ASSESSMENT AGENT - V5 (GPT-4o & PDF Support) ✅ --- ✅
240
+ # =================================================================================================
241
+ #
242
+ # Instructions:
243
+ # 1. Add OPENAI_API_KEY, TAVILY_API_KEY, and GROQ_API_KEY to your HF Space secrets.
244
+ # 2. Update your requirements.txt to include `langchain-openai` and `pypdf`.
245
+ # 3. This version uses the GPT-4o model for superior reasoning and can read PDFs.
246
+ #
247
+ # =================================================================================================
248
+
249
  import os
250
  import io
251
  import requests
 
258
  # --- LangChain & LangGraph Imports ---
259
  from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage, AIMessage, SystemMessage
260
  from langchain_core.tools import tool
261
+ from langchain_openai import ChatOpenAI # <-- Import OpenAI
262
  from langgraph.graph import StateGraph, END
263
  from langgraph.prebuilt import ToolNode
264
+ from tavily import TavilyClient
265
+ import pypdf # <-- Import PDF library
266
 
267
  # (Keep Constants as is)
268
  # --- Constants ---
 
270
  FILES_DIR = "./files"
271
  os.makedirs(FILES_DIR, exist_ok=True)
272
 
273
+ # --- System Prompt (Unchanged, it's already strong) ---
274
  AGENT_SYSTEM_PROMPT = """You are a world-class AI agent, specialized in solving complex problems from the GAIA benchmark.
275
 
276
  Your task is to analyze the user's question, think step-by-step, and use the provided tools to find the correct answer.
 
293
 
294
  #
295
  # ================================================================================================
296
+ # ✅ 1. DEFINE THE AGENT'S UPGRADED TOOLS
297
  # ================================================================================================
298
  #
 
299
  tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
300
 
301
  @tool
302
  def tavily_search(query: str) -> str:
303
  """
304
  Uses the Tavily Search API to find information on the web.
 
 
305
  """
306
  print(f"--- Calling Tavily Search Tool with query: {query} ---")
307
  try:
 
308
  result = tavily.search(query=query, search_depth="advanced")
 
309
  return f"Search results for '{query}':\n" + "\n".join([f"- {r['content']}" for r in result['results']])
310
  except Exception as e:
311
  return f"Error during Tavily search: {e}"
 
313
  @tool
314
  def read_file(url: str) -> str:
315
  """
316
+ Downloads a file from a given URL, saves it locally, and returns its content.
317
+ It can handle both plain text files and PDF files.
318
  Use this tool when a question provides a URL to a file that needs to be read.
319
  """
320
  print(f"--- Calling Read File Tool with URL: {url} ---")
 
322
  filename = os.path.join(FILES_DIR, os.path.basename(url))
323
  response = requests.get(url)
324
  response.raise_for_status()
325
+
326
  with open(filename, 'wb') as f:
327
  f.write(response.content)
328
+
329
+ # Check if the file is a PDF
330
+ if url.lower().endswith('.pdf'):
331
+ print(f"--- File identified as PDF. Reading with pypdf. ---")
332
+ try:
333
+ pdf_reader = pypdf.PdfReader(filename)
334
+ content = ""
335
+ for page in pdf_reader.pages:
336
+ content += page.extract_text()
337
+ return f"Successfully read PDF file '{filename}'. Content:\n\n{content}"
338
+ except Exception as e:
339
+ return f"Error reading PDF file: {e}"
340
+ else:
341
+ # Assume it's a text file
342
+ print(f"--- File identified as text. Reading normally. ---")
343
+ try:
344
+ with open(filename, 'r', encoding='utf-8') as f:
345
+ content = f.read()
346
+ return f"Successfully read text file '{filename}'. Content:\n\n{content}"
347
+ except UnicodeDecodeError:
348
+ return f"Successfully downloaded binary file '{filename}'. Cannot display content as text."
349
+
350
  except requests.exceptions.RequestException as e:
351
  return f"Error downloading or reading file: {e}"
352
 
 
354
  def python_interpreter(code: str) -> str:
355
  """
356
  Executes a given string of Python code and returns the output from stdout.
 
 
357
  """
358
  print(f"--- Calling Python Interpreter Tool with code:\n{code} ---")
359
  output_buffer = io.StringIO()
 
366
 
367
  #
368
  # ================================================================================================
369
+ # ✅ 2. CONFIGURE AND BUILD THE AGENT GRAPH (NOW WITH GPT-4o)
370
  # ================================================================================================
371
  #
 
 
 
372
  class AgentState(TypedDict):
373
  messages: Annotated[List[BaseMessage], operator.add]
374
 
375
  def build_agent_graph():
376
  """Builds the LangGraph agent."""
377
  tools = [tavily_search, read_file, python_interpreter]
378
+
379
+ # --->>> THE BRAIN UPGRADE: Using GPT-4o <<<---
380
+ # It will use the OPENAI_API_KEY from your secrets.
381
+ llm = ChatOpenAI(model="gpt-4o", temperature=0)
382
+
383
  llm_with_tools = llm.bind_tools(tools)
384
 
385
  def call_model(state: AgentState) -> dict:
 
386
  messages = state['messages']
387
  response = llm_with_tools.invoke(messages)
388
  return {"messages": [response]}
389
 
390
  def should_continue(state: AgentState) -> str:
391
+ return "action" if state['messages'][-1].tool_calls else "end"
 
 
 
 
392
 
393
  tool_node = ToolNode(tools)
394
  workflow = StateGraph(AgentState)
395
  workflow.add_node("agent", call_model)
396
  workflow.add_node("action", tool_node)
397
  workflow.set_entry_point("agent")
398
+ workflow.add_conditional_edges("agent", should_continue, {"action": "action", "end": END})
 
 
 
 
399
  workflow.add_edge('action', 'agent')
400
  return workflow.compile()
401
 
402
  #
403
  # ================================================================================================
404
+ # ✅ 3. AGENT CLASS AND EVALUATION LOGIC (Unchanged)
405
  # ================================================================================================
406
  #
407
  class GaiaAgent:
408
  def __init__(self):
409
+ print("GaiaAgent initialized. Building fresh GPT-4o graph...")
410
  self.agent_app = build_agent_graph()
411
 
412
  def __call__(self, question: str) -> str:
413
  print(f"\n{'='*60}\nAgent received question: {question[:100]}...\n{'='*60}")
 
414
  initial_input = {
415
  "messages": [
416
  SystemMessage(content=AGENT_SYSTEM_PROMPT),
417
  HumanMessage(content=question)
418
  ]
419
  }
 
420
  final_state = None
421
  for i, step in enumerate(self.agent_app.stream(initial_input, {"recursion_limit": 15})):
422
+ if i == 0: print("--- Starting Agentic Loop ---")
 
423
  final_state = step
424
 
425
  final_answer_message = final_state['agent']['messages'][-1]
426
  final_answer = str(final_answer_message.content).strip()
 
427
  print(f"\n--- Agent finished. Final Answer: {final_answer} ---\n")
428
  return final_answer
429
 
 
 
 
 
 
430
  def run_and_submit_all( profile: gr.OAuthProfile | None):
431
  space_id = os.getenv("SPACE_ID")
432
+ if not profile: return "Please Login to Hugging Face with the button.", None
 
433
  username = f"{profile.username}"
434
  print(f"User logged in: {username}")
435
 
 
451
  answers_payload = []
452
  print(f"Running agent on {len(questions_data)} questions...")
453
 
 
 
 
454
  for item in questions_data:
455
  task_id = item.get("task_id")
456
  question_text = item.get("question")
457
+ if not task_id or question_text is None: continue
 
458
  try:
 
459
  agent = GaiaAgent()
460
  submitted_answer = agent(question_text)
461
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
 
470
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
471
  print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
472
  try:
473
+ response = requests.post(submit_url, json=submission_data, timeout=90) # Increased timeout for OpenAI
474
  response.raise_for_status()
475
  result_data = response.json()
476
  final_status = (
 
480
  f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
481
  f"Message: {result_data.get('message', 'No message received.')}"
482
  )
 
483
  results_df = pd.DataFrame(results_log)
484
  return final_status, results_df
485
  except Exception as e:
486
  status_message = f"An unexpected error occurred during submission: {e}"
 
487
  results_df = pd.DataFrame(results_log)
488
  return status_message, results_df
489
 
490
+ # --- Gradio Interface ---
 
491
  with gr.Blocks() as demo:
492
+ gr.Markdown("# GAIA Agent Final Assessment (V5 - GPT-4o & PDF)")
493
  gr.Markdown(
494
  """
495
+ **Instructor's Note:** This is the final version. It uses GPT-4o for SOTA reasoning and can now read PDFs.
496
+ 1. Ensure `OPENAI_API_KEY` and `TAVILY_API_KEY` are set.
497
+ 2. Ensure `requirements.txt` is updated.
498
+ 3. Good luck! Let's get that certificate.
499
  """
500
  )
501
  gr.LoginButton()
502
  run_button = gr.Button("Run Evaluation & Submit All Answers")
503
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
504
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
505
+ run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
 
 
 
506
 
507
  if __name__ == "__main__":
508
  print("\n" + "-"*30 + " App Starting " + "-"*30)