Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -250,6 +250,7 @@
|
|
| 250 |
|
| 251 |
import os
|
| 252 |
import io
|
|
|
|
| 253 |
import requests
|
| 254 |
import pandas as pd
|
| 255 |
import gradio as gr
|
|
@@ -261,11 +262,7 @@ import operator
|
|
| 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
|
| 270 |
import pypdf
|
| 271 |
|
|
@@ -274,21 +271,32 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
|
| 274 |
FILES_DIR = "./files"
|
| 275 |
os.makedirs(FILES_DIR, exist_ok=True)
|
| 276 |
|
| 277 |
-
# --- System Prompt (
|
| 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 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
EXAMPLES OF CORRECT FINAL ANSWERS:
|
| 287 |
-
-
|
| 288 |
-
-
|
| 289 |
-
-
|
| 290 |
-
-
|
| 291 |
-
Think, use your tools, and then provide ONLY the final, precise answer.
|
| 292 |
"""
|
| 293 |
|
| 294 |
#
|
|
@@ -317,7 +325,7 @@ def read_file(url: str) -> str:
|
|
| 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)
|
|
@@ -341,44 +349,85 @@ def python_interpreter(code: str) -> str:
|
|
| 341 |
|
| 342 |
#
|
| 343 |
# ================================================================================================
|
| 344 |
-
# ✅ 2. CONFIGURE AND BUILD THE AGENT GRAPH (
|
| 345 |
# ================================================================================================
|
| 346 |
#
|
| 347 |
class AgentState(TypedDict):
|
| 348 |
-
|
| 349 |
-
|
| 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
|
| 359 |
huggingfacehub_api_token=os.getenv("HUGGINGFACEHUB_API_TOKEN")
|
| 360 |
)
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
|
| 383 |
#
|
| 384 |
# ================================================================================================
|
|
@@ -387,24 +436,28 @@ def build_agent_graph():
|
|
| 387 |
#
|
| 388 |
class GaiaAgent:
|
| 389 |
def __init__(self):
|
| 390 |
-
print("GaiaAgent initialized. Building Command R+ agent with
|
| 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 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
print(f"
|
| 406 |
-
|
| 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):
|
|
@@ -431,15 +484,13 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
| 431 |
answers_payload = []
|
| 432 |
print(f"Running agent on {len(questions_data)} questions...")
|
| 433 |
|
| 434 |
-
# Instantiate the agent once
|
| 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})
|
|
@@ -471,14 +522,11 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
| 471 |
return status_message, results_df
|
| 472 |
|
| 473 |
with gr.Blocks() as demo:
|
| 474 |
-
gr.Markdown("# GAIA Agent Final Assessment (Open Source: Command R+ -
|
| 475 |
gr.Markdown(
|
| 476 |
"""
|
| 477 |
-
**Instructor's Note:** This version
|
| 478 |
-
It
|
| 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()
|
|
|
|
| 250 |
|
| 251 |
import os
|
| 252 |
import io
|
| 253 |
+
import json
|
| 254 |
import requests
|
| 255 |
import pandas as pd
|
| 256 |
import gradio as gr
|
|
|
|
| 262 |
from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage, AIMessage, SystemMessage
|
| 263 |
from langchain_core.tools import tool
|
| 264 |
from langchain_huggingface import HuggingFaceEndpoint
|
|
|
|
|
|
|
|
|
|
| 265 |
from langgraph.graph import StateGraph, END
|
|
|
|
| 266 |
from tavily import TavilyClient
|
| 267 |
import pypdf
|
| 268 |
|
|
|
|
| 271 |
FILES_DIR = "./files"
|
| 272 |
os.makedirs(FILES_DIR, exist_ok=True)
|
| 273 |
|
| 274 |
+
# --- System Prompt (Updated for Manual Tool Calling) ---
|
| 275 |
AGENT_SYSTEM_PROMPT = """You are a world-class AI agent, specialized in solving complex problems from the GAIA benchmark.
|
| 276 |
Your task is to analyze the user's question, think step-by-step, and use the provided tools to find the correct answer.
|
| 277 |
+
|
| 278 |
+
**TOOL USAGE INSTRUCTIONS:**
|
| 279 |
+
When you need to use a tool, you MUST respond with a JSON object containing the tool name and its arguments. The JSON object should have two keys: "tool_name" and "parameters".
|
| 280 |
+
|
| 281 |
+
Here is an example of how to call the `tavily_search` tool:
|
| 282 |
+
```json
|
| 283 |
+
{
|
| 284 |
+
"tool_name": "tavily_search",
|
| 285 |
+
"parameters": {
|
| 286 |
+
"query": "What was the score of the 2023 FIFA Women's World Cup final?"
|
| 287 |
+
}
|
| 288 |
+
}```
|
| 289 |
+
|
| 290 |
+
**CRITICAL FINAL ANSWER INSTRUCTIONS:**
|
| 291 |
+
Once you have gathered all the necessary information and are absolutely certain of the answer, you MUST provide it directly and concisely.
|
| 292 |
+
- Your final response must ONLY be the answer itself.
|
| 293 |
+
- DO NOT wrap the final answer in a JSON object or include any conversational text like 'The answer is...'.
|
| 294 |
+
|
| 295 |
EXAMPLES OF CORRECT FINAL ANSWERS:
|
| 296 |
+
- `2023`
|
| 297 |
+
- `John Doe`
|
| 298 |
+
- `42`
|
| 299 |
+
- `broccoli, celery, lettuce, sweet potatoes`
|
|
|
|
| 300 |
"""
|
| 301 |
|
| 302 |
#
|
|
|
|
| 325 |
response = requests.get(url)
|
| 326 |
response.raise_for_status()
|
| 327 |
with open(filename, 'wb') as f: f.write(response.content)
|
| 328 |
+
|
| 329 |
if url.lower().endswith('.pdf'):
|
| 330 |
try:
|
| 331 |
pdf_reader = pypdf.PdfReader(filename)
|
|
|
|
| 349 |
|
| 350 |
#
|
| 351 |
# ================================================================================================
|
| 352 |
+
# ✅ 2. CONFIGURE AND BUILD THE AGENT GRAPH (MANUAL LANGGRAPH IMPLEMENTATION)
|
| 353 |
# ================================================================================================
|
| 354 |
#
|
| 355 |
class AgentState(TypedDict):
|
| 356 |
+
messages: Annotated[List[BaseMessage], operator.add]
|
| 357 |
+
|
|
|
|
|
|
|
| 358 |
def build_agent_graph():
|
| 359 |
"""Builds the LangGraph agent."""
|
| 360 |
tools = [tavily_search, read_file, python_interpreter]
|
| 361 |
+
tool_map = {tool.name: tool for tool in tools}
|
| 362 |
+
|
| 363 |
repo_id = "CohereForAI/c4ai-command-r-plus"
|
| 364 |
llm = HuggingFaceEndpoint(
|
| 365 |
+
repo_id=repo_id, max_new_tokens=1024, temperature=0,
|
| 366 |
huggingfacehub_api_token=os.getenv("HUGGINGFACEHUB_API_TOKEN")
|
| 367 |
)
|
| 368 |
+
|
| 369 |
+
def call_model(state: AgentState):
|
| 370 |
+
"""Invokes the LLM and wraps the response in an AIMessage."""
|
| 371 |
+
messages = state['messages']
|
| 372 |
+
# We combine the system prompt with the rest of the conversation.
|
| 373 |
+
prompt_str = ""
|
| 374 |
+
for msg in messages:
|
| 375 |
+
if isinstance(msg, SystemMessage):
|
| 376 |
+
prompt_str += f"<|SYSTEM|>\n{msg.content}\n"
|
| 377 |
+
elif isinstance(msg, HumanMessage):
|
| 378 |
+
prompt_str += f"<|USER|>\n{msg.content}\n"
|
| 379 |
+
elif isinstance(msg, AIMessage):
|
| 380 |
+
prompt_str += f"<|ASSISTANT|>\n{msg.content}\n"
|
| 381 |
+
elif isinstance(msg, ToolMessage):
|
| 382 |
+
prompt_str += f"<|TOOL_RESULT|>\n{msg.content}\n"
|
| 383 |
+
|
| 384 |
+
prompt_str += "<|ASSISTANT|>"
|
| 385 |
+
|
| 386 |
+
response_text = llm.invoke(prompt_str)
|
| 387 |
+
return {"messages": [AIMessage(content=response_text)]}
|
| 388 |
+
|
| 389 |
+
def should_continue(state: AgentState) -> str:
|
| 390 |
+
"""Determines whether to call a tool or end the loop."""
|
| 391 |
+
last_message_content = state['messages'][-1].content.strip()
|
| 392 |
+
|
| 393 |
+
# A simple check: if the response looks like a JSON object, it's a tool call.
|
| 394 |
+
if last_message_content.startswith('{') and last_message_content.endswith('}'):
|
| 395 |
+
# More robust check for JSON tool call
|
| 396 |
+
try:
|
| 397 |
+
json.loads(last_message_content)
|
| 398 |
+
return "action"
|
| 399 |
+
except json.JSONDecodeError:
|
| 400 |
+
return "end" # It's not valid JSON, so it must be the final answer
|
| 401 |
+
else:
|
| 402 |
+
return "end"
|
| 403 |
+
|
| 404 |
+
def call_tool_node(state: AgentState):
|
| 405 |
+
"""Parses the tool call from the LLM output and executes it."""
|
| 406 |
+
last_message_content = state['messages'][-1].content.strip()
|
| 407 |
+
try:
|
| 408 |
+
tool_call_data = json.loads(last_message_content)
|
| 409 |
+
tool_name = tool_call_data.get("tool_name")
|
| 410 |
+
parameters = tool_call_data.get("parameters", {})
|
| 411 |
+
|
| 412 |
+
if tool_name not in tool_map:
|
| 413 |
+
error_message = f"Error: Tool '{tool_name}' not found."
|
| 414 |
+
return {"messages": [ToolMessage(content=error_message, tool_call_id="error")]}
|
| 415 |
+
|
| 416 |
+
selected_tool = tool_map[tool_name]
|
| 417 |
+
tool_output = selected_tool.invoke(parameters)
|
| 418 |
+
return {"messages": [ToolMessage(content=str(tool_output), tool_call_id=tool_name)]}
|
| 419 |
+
|
| 420 |
+
except Exception as e:
|
| 421 |
+
error_message = f"Error processing tool call: {e}. Content: '{last_message_content}'"
|
| 422 |
+
return {"messages": [ToolMessage(content=error_message, tool_call_id="error")]}
|
| 423 |
+
|
| 424 |
+
workflow = StateGraph(AgentState)
|
| 425 |
+
workflow.add_node("agent", call_model)
|
| 426 |
+
workflow.add_node("action", call_tool_node)
|
| 427 |
+
workflow.set_entry_point("agent")
|
| 428 |
+
workflow.add_conditional_edges("agent", should_continue, {"action": "action", "end": END})
|
| 429 |
+
workflow.add_edge('action', 'agent')
|
| 430 |
+
return workflow.compile()
|
| 431 |
|
| 432 |
#
|
| 433 |
# ================================================================================================
|
|
|
|
| 436 |
#
|
| 437 |
class GaiaAgent:
|
| 438 |
def __init__(self):
|
| 439 |
+
print("GaiaAgent initialized. Building Command R+ agent with manual LangGraph loop...")
|
|
|
|
| 440 |
self.agent_app = build_agent_graph()
|
| 441 |
|
| 442 |
def __call__(self, question: str) -> str:
|
| 443 |
print(f"\n{'='*60}\nAgent received question: {question[:100]}...\n{'='*60}")
|
| 444 |
+
initial_input = {
|
| 445 |
+
"messages": [
|
| 446 |
+
SystemMessage(content=AGENT_SYSTEM_PROMPT),
|
| 447 |
+
HumanMessage(content=question)
|
| 448 |
+
]
|
| 449 |
+
}
|
| 450 |
+
final_state = None
|
| 451 |
+
for i, step in enumerate(self.agent_app.stream(initial_input, {"recursion_limit": 15})):
|
| 452 |
+
if i == 0: print("--- Starting Agentic Loop ---")
|
| 453 |
+
print(f"--- Step {i+1} ---")
|
| 454 |
+
print(step)
|
| 455 |
+
final_state = list(step.values())[0] # Get the state from the graph step
|
| 456 |
+
|
| 457 |
+
final_answer_message = final_state['messages'][-1]
|
| 458 |
+
final_answer = str(final_answer_message.content).strip()
|
| 459 |
+
print(f"\n--- Agent finished. Final Answer: {final_answer} ---\n")
|
| 460 |
+
return final_answer
|
| 461 |
|
| 462 |
# --- The rest of the file (run_and_submit_all, Gradio UI) remains the same ---
|
| 463 |
def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
|
|
| 484 |
answers_payload = []
|
| 485 |
print(f"Running agent on {len(questions_data)} questions...")
|
| 486 |
|
| 487 |
+
agent_instance = GaiaAgent() # Instantiate the agent once
|
|
|
|
| 488 |
|
| 489 |
for item in questions_data:
|
| 490 |
task_id = item.get("task_id")
|
| 491 |
question_text = item.get("question")
|
| 492 |
if not task_id or question_text is None: continue
|
| 493 |
try:
|
|
|
|
| 494 |
submitted_answer = agent_instance(question_text)
|
| 495 |
answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
|
| 496 |
results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
|
|
|
|
| 522 |
return status_message, results_df
|
| 523 |
|
| 524 |
with gr.Blocks() as demo:
|
| 525 |
+
gr.Markdown("# GAIA Agent Final Assessment (Open Source: Command R+ - Final)")
|
| 526 |
gr.Markdown(
|
| 527 |
"""
|
| 528 |
+
**Instructor's Note:** This version uses a robust, manual LangGraph loop to handle tool calls for the `HuggingFaceEndpoint`.
|
| 529 |
+
It explicitly tells the model to generate JSON for tool calls and parses this JSON from the text output. This is the correct, fundamental way to build agents with models that don't support modern tool-binding abstractions.
|
|
|
|
|
|
|
|
|
|
| 530 |
"""
|
| 531 |
)
|
| 532 |
gr.LoginButton()
|