Spaces:
Paused
Paused
| """tools.py | |
| Reusable LangChain Tool definitions for GAIA evaluation agent. | |
| Each tool complies with the LangChain `Tool` interface so they can be | |
| plugged into a LangGraph-driven agent. All tools are stateless and rely on | |
| external services or pure-Python execution. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import requests | |
| from typing import Any, Dict, List | |
| from langchain_community.tools import DuckDuckGoSearchRun | |
| from langchain_experimental.tools import PythonREPLTool | |
| from langchain_community.utilities import DuckDuckGoSearchAPIWrapper | |
| from langchain_core.tools import tool, Tool | |
| from langchain.tools import BaseTool | |
| from langchain.callbacks.manager import CallbackManagerForToolRun | |
| # Fallback API URL (same default as in app.py) | |
| DEFAULT_API_URL = os.getenv("GAIA_API_URL", "https://agents-course-unit4-scoring.hf.space") | |
| # ----------------------------------------------------------------------------- | |
| # Simple Search Tool (DuckDuckGo) | |
| # ----------------------------------------------------------------------------- | |
| duckduck_wrapper = DuckDuckGoSearchAPIWrapper() | |
| search_tool = DuckDuckGoSearchRun(api_wrapper=duckduck_wrapper) | |
| # ----------------------------------------------------------------------------- | |
| # Simple Python REPL tool (for quick calculations / parsing) | |
| # ----------------------------------------------------------------------------- | |
| python_tool = PythonREPLTool() | |
| # ----------------------------------------------------------------------------- | |
| # Calculator using built-in eval (safe-ish) | |
| # ----------------------------------------------------------------------------- | |
| def calculator(expression: str) -> str: # noqa: D401 | |
| """Evaluate a basic mathematical expression and return the result. | |
| Uses Python's `eval` in a restricted namespace for quick arithmetic. | |
| """ | |
| try: | |
| allowed_names: Dict[str, Any] = { | |
| k: v for k, v in vars(__import__("math")).items() if not k.startswith("__") | |
| } | |
| result = eval(expression, {"__builtins__": {}}, allowed_names) # type: ignore[arg-type] | |
| return str(result) | |
| except Exception as exc: # pragma: no cover | |
| return f"ERROR: {exc}" # LLM can decide what to do with error | |
| # ----------------------------------------------------------------------------- | |
| # File Loader Tool for GAIA Task-specific files | |
| # ----------------------------------------------------------------------------- | |
| class GAIAFileLoaderTool(BaseTool): | |
| """Download auxiliary file for a GAIA task and return its contents or path.""" | |
| name: str = "load_file" | |
| description: str = ( | |
| "Use this tool to download an auxiliary file linked to a GAIA question. " | |
| "Input must be a valid task_id (integer). The tool returns text content of " | |
| "the file if it is textual, otherwise returns the local file path." | |
| ) | |
| api_url: str = DEFAULT_API_URL # injected configurable base URL | |
| def _run( | |
| self, | |
| task_id: str, | |
| *, | |
| run_manager: CallbackManagerForToolRun | None = None, | |
| ) -> str: | |
| """Synchronous run implementation.""" | |
| try: | |
| int_task_id = int(task_id) | |
| except ValueError as exc: # pragma: no cover | |
| return f"ERROR: task_id should be an integer: {exc}" | |
| files_endpoint = f"{self.api_url}/files/{int_task_id}" | |
| try: | |
| resp = requests.get(files_endpoint, timeout=30) | |
| resp.raise_for_status() | |
| except Exception as exc: # pragma: no cover | |
| return f"ERROR: failed to download file: {exc}" | |
| # Determine content-type and return appropriate value | |
| content_type = resp.headers.get("content-type", "") | |
| if content_type.startswith("text/") or content_type in {"application/json", "application/xml"}: | |
| return resp.text[:8000] # hard cap to keep prompts short | |
| # Otherwise, save binary file locally | |
| file_name = resp.headers.get("content-disposition") or f"gaia_task_{int_task_id}_file" | |
| file_path = os.path.join(os.getcwd(), file_name) | |
| with open(file_path, "wb") as f: | |
| f.write(resp.content) | |
| return file_path | |
| async def _arun(self, *args: Any, **kwargs: Any) -> str: # noqa: D401 | |
| """Asynchronous version not implemented (sync fallback).""" | |
| return self._run(*args, **kwargs) | |
| # ----------------------------------------------------------------------------- | |
| # Public helper to create list of tools | |
| # ----------------------------------------------------------------------------- | |
| def create_tools(api_url: str | None = None) -> List[Tool]: | |
| """Return a list of Tool objects for the agent. | |
| Parameters | |
| ---------- | |
| api_url : str | None | |
| Override for GAIA API base url. If None, falls back to DEFAULT_API_URL. | |
| """ | |
| file_loader = GAIAFileLoaderTool(api_url=api_url or DEFAULT_API_URL) | |
| # Wrap DuckDuckGo and Python REPL into standard Tool when necessary | |
| wrapped_search = Tool( | |
| name="search", | |
| func=search_tool.run, | |
| description="Search the web quickly for general information.", | |
| return_direct=False, | |
| ) | |
| wrapped_python = Tool( | |
| name="python", | |
| func=python_tool.run, | |
| description="Execute Python code for calculation or data parsing.", | |
| return_direct=False, | |
| ) | |
| return [wrapped_search, calculator, wrapped_python, file_loader] | |