import os import logging from typing import Literal, Union from langchain_core.tools import tool from langchain_community.tools.tavily_search import TavilySearchResults from langchain_community.document_loaders import WikipediaLoader from langchain_community.document_loaders import ArxivLoader from googleapiclient.discovery import build # Configure basic logging for tools logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # --- Arithmetic Tool --- @tool def perform_calculation( a: Union[int, float], b: Union[int, float], operation: Literal["add", "subtract", "multiply", "divide", "modulus"], ) -> Union[int, float, str]: """Performs a specified arithmetic operation on two numbers. Args: a: The first number (integer or float). b: The second number (integer or float). operation: The arithmetic operation to perform. Must be one of "add", "subtract", "multiply", "divide", or "modulus". """ logging.info(f"Tool Call: perform_calculation(a={a}, b={b}, operation='{operation}')") try: if operation == "add": result = a + b elif operation == "subtract": result = a - b elif operation == "multiply": result = a * b elif operation == "divide": if b == 0: logging.warning("perform_calculation: Division by zero attempted.") return "Error: Cannot divide by zero." result = a / b elif operation == "modulus": if b == 0: logging.warning("perform_calculation: Modulus with zero divisor attempted.") return "Error: Cannot perform modulus with zero divisor." # Optional: enforce integer for modulus for clarity/LLM expectation if not isinstance(a, int) or not isinstance(b, int): logging.warning("perform_calculation: Modulus with non-integer operands. Results might be float.") # You might return a specific error or proceed depending on desired behavior result = a % b else: logging.error(f"perform_calculation: Invalid operation '{operation}' specified.") return "Error: Invalid operation specified." logging.info(f"perform_calculation: Result of {operation} on {a}, {b} is {result}") return result except Exception as e: logging.error(f"perform_calculation: An unexpected error occurred: {e}", exc_info=True) return f"Error during calculation: {e}" # --- Search & Document Loader Tools --- @tool def search_from_wiki(query: str) -> str: """Search Wikipedia for a query and return maximum 2 relevant sections. Args: query: The search query for Wikipedia.""" logging.info(f"Tool Call: search_from_wiki(query='{query}')") try: search_docs = WikipediaLoader(query=query, load_max_docs=2).load() if not search_docs: logging.info(f"search_from_wiki: No results found for '{query}'.") return {"wiki_results": f"No Wikipedia results found for '{query}'."} formatted_search_docs = "\n\n---\n\n".join( [ f'\n' f'{doc.page_content}\n' for doc in search_docs ] ) logging.info(f"search_from_wiki: Successfully fetched {len(search_docs)} results for '{query}'.") return {"wiki_results": formatted_search_docs} except Exception as e: logging.error(f"search_from_wiki: An error occurred for query '{query}': {e}", exc_info=True) return {"wiki_results": f"An error occurred during Wikipedia search for '{query}': {e}. Please try a different query or source."} @tool def search_from_arxiv(query: str) -> str: """Search Arxiv for a query and return maximum 3 relevant paper abstracts/summaries. Args: query: The search query for Arxiv.""" logging.info(f"Tool Call: search_from_arxiv(query='{query}')") try: search_docs = ArxivLoader(query=query, load_max_docs=3).load() if not search_docs: logging.info(f"search_from_arxiv: No results found for '{query}'.") return {"arvix_results": f"No Arxiv results found for '{query}'."} formatted_search_docs = "\n\n---\n\n".join( [ f'\n' f'{doc.page_content[:1500]}...\n' for doc in search_docs ] ) logging.info(f"search_from_arxiv: Successfully fetched {len(search_docs)} results for '{query}'.") return {"arvix_results": formatted_search_docs} except Exception as e: logging.error(f"search_from_arxiv: An error occurred for query '{query}': {e}", exc_info=True) return {"arvix_results": f"An error occurred during Arxiv search for '{query}': {e}. Please try a different query or source."} @tool def search_from_web(query: str) -> str: """Search Google and return top results.""" api_key = os.getenv("GOOGLE_API_KEY") cse_id = os.getenv("GOOGLE_CSE_ID") service = build("customsearch", "v1", developerKey=api_key) res = service.cse().list(q=query, cx=cse_id, num=3).execute() results = res.get("items", []) return "\n\n---\n\n".join(f"{item['title']}\n{item['link']}\n{item['snippet']}" for item in results) # List all tools available to the agent tools = [perform_calculation, search_from_arxiv, search_from_wiki,search_from_web]