Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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
|
| 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 |
-
|
| 331 |
-
|
| 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 |
-
|
| 341 |
-
|
| 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 (
|
| 365 |
# ================================================================================================
|
| 366 |
#
|
| 367 |
class AgentState(TypedDict):
|
| 368 |
-
|
| 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 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
""
|
| 389 |
-
|
| 390 |
-
# The
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 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 |
-
|
| 408 |
-
|
| 409 |
-
|
| 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 |
-
|
| 424 |
-
|
| 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 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 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
|
| 475 |
-
submitted_answer =
|
| 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 |
-
#
|
| 508 |
-
gr.Markdown("# GAIA Agent Final Assessment (Open Source: Command R+)")
|
| 509 |
gr.Markdown(
|
| 510 |
"""
|
| 511 |
-
**Instructor's Note:** This version
|
| 512 |
-
|
| 513 |
1. Ensure you have a **`HUGGINGFACEHUB_API_TOKEN`** and a **`TAVILY_API_KEY`** set in your Space secrets.
|
| 514 |
-
2.
|
| 515 |
-
3.
|
| 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()
|