File size: 7,358 Bytes
dd8ccca
 
 
 
18cb4ee
 
1d4c10f
dd8ccca
0b1169d
dd8ccca
 
18cb4ee
 
dd8ccca
18cb4ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd8ccca
 
 
d1e5bf8
 
d912fe7
 
 
 
5ce8e58
18cb4ee
d912fe7
d1e5bf8
 
 
18cb4ee
d912fe7
d1e5bf8
 
 
18cb4ee
d912fe7
d1e5bf8
 
 
 
 
 
 
dd8ccca
d1e5bf8
 
d912fe7
 
 
 
5ce8e58
18cb4ee
d912fe7
d1e5bf8
 
 
5942702
 
18cb4ee
d912fe7
d1e5bf8
 
 
18cb4ee
d912fe7
d1e5bf8
 
 
 
 
 
 
 
d912fe7
 
 
 
 
 
 
 
 
 
18cb4ee
 
 
d912fe7
 
 
 
 
 
0b1169d
d912fe7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18cb4ee
 
 
dd8ccca
 
d912fe7
18cb4ee
0b1169d
d912fe7
 
 
 
 
 
 
18cb4ee
 
 
 
 
 
 
 
d912fe7
 
 
18cb4ee
 
 
 
 
d912fe7
 
 
 
 
 
 
 
 
ae86d09
 
d912fe7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import os
from dotenv import load_dotenv
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_community.document_loaders import WikipediaLoader
from langchain_core.tools import tool # Consolidated import for @tool decorator
from datetime import datetime
from langchain_experimental.utilities import PythonREPL

# Load environment variables
load_dotenv()

# === MATH TOOL ===
# Consolidated calculator tool
@tool
def calculator(a: float, b: float, operation: str) -> float:
    """
    Performs a mathematical operation (addition, subtraction, multiplication, division) on two numbers.
    Input should be a dictionary with 'a' (float, first number), 'b' (float, second number),
    and 'operation' (string, e.g., 'add', 'subtract', 'multiply', 'divide') keys.
    Example: {"a": 5.5, "b": 10.0, "operation": "add"}
    """
    if operation == "add":
        return a + b
    elif operation == "subtract":
        return a - b
    elif operation == "multiply":
        return a * b
    elif operation == "divide":
        if b == 0:
            raise ValueError("Cannot divide by zero.")
        return a / b
    else:
        raise ValueError("Invalid operation. Choose from 'add', 'subtract', 'multiply', 'divide'.")


# === SEARCH TOOLS ===
@tool
def wiki_search(query: str) -> dict:
    """
    Search Wikipedia for a given query and return up to 2 relevant document results.
    Useful for factual questions about people, places, events, etc.
    Input should be a string representing the search query.
    Example: {"query": "Barack Obama"}
    The output is a dictionary with a 'wiki_results' key containing the formatted search results.
    """
    try:
        if not query.strip():
            return {"wiki_results": "Error: Empty query provided."}
        
        # LangChain's WikipediaLoader returns Document objects
        search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
        if not search_docs:
            return {"wiki_results": "No results found on Wikipedia."}
                
        # Format results for the LLM
        formatted = "\n\n---\n\n".join(
            f'<Document source="{doc.metadata.get("source", "unknown")}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
            for doc in search_docs
        )
        return {"wiki_results": formatted}
    except Exception as e:
        return {"wiki_results": f"Error during Wikipedia search: {str(e)}"}

@tool
def web_search(query: str) -> dict:
    """
    Search the web using Tavily for a given query and return up to 3 relevant snippets.
    Useful for up-to-date information, current events, or general web searches.
    Input should be a string representing the search query. Requires TAVILY_API_KEY environment variable.
    Example: {"query": "latest news on AI"}
    The output is a dictionary with a 'web_results' key containing the formatted search results.
    """
    try:
        if not query.strip():
            return {"web_results": "Error: Empty query provided."}
        if not os.getenv("TAVILY_API_KEY"):
            return {"web_results": "Error: Tavily API key is not configured."}
                
        # TavilySearchResults.invoke expects 'query' as a keyword argument
        search_docs = TavilySearchResults(max_results=3).invoke(query=query)
        if not search_docs:
            return {"web_results": "No results found on the web."}
                
        # Format results for the LLM
        formatted = "\n\n---\n\n".join(
            f'<Document source="{doc.metadata.get("source", "unknown")}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
            for doc in search_docs
        )
        return {"web_results": formatted}
    except Exception as e:
        return {"web_results": f"Error during web search: {str(e)}"}

# === UTILITY TOOLS ===
@tool
def get_current_datetime(format_string: str = "%Y-%m-%d %H:%M:%S") -> str:
    """
    Returns the current date and time in a specified format.
    Useful for questions related to the current date, time, or for calculating durations.
    Input is an optional format_string (string, default: "%Y-%m-%d %H:%M:%S").
    Example: {"format_string": "%A, %B %d, %Y"} will return "Wednesday, July 16, 2025".
    """
    try:
        # Using current time as per system prompt guidance
        current_time = datetime(2025, 7, 16, 12, 43, 1) # Specific time provided in context
        return current_time.strftime(format_string)
    except Exception as e:
        return f"Error getting current datetime: {str(e)}"

# === CODE EXECUTION TOOL ===
# Initialize PythonREPL
python_repl = PythonREPL()

# Create a LangChain Tool from the PythonREPL
@tool
def python_repl_tool(code: str) -> str:
    """
    Executes Python code and returns the output.
    Useful for mathematical calculations, string manipulations, list operations,
    logic problems, and any task that can be solved with Python code.
    Input should be a string containing valid Python code to execute.
    Example: {"code": "print(2 + 2)"}
    """
    try:
        # LangChain's PythonREPL.run expects a string input
        return python_repl.run(code)
    except Exception as e:
        return f"Error executing Python code: {str(e)}"

# === TOOLSET EXPORT ===
# List of all available tools to be imported by agent.py
tools_for_llm = [
    calculator, # Replaced individual math tools
    wiki_search,
    web_search,
    get_current_datetime,
    python_repl_tool,
]

# For local testing of tools (optional)
if __name__ == "__main__":
    print("Testing tools.py functionalities...")
    # Set dummy API key for testing if not already set in .env
    # os.environ["TAVILY_API_KEY"] = "YOUR_TAVILY_API_KEY" # Replace with a real key for actual testing

    # Test Math Tool
    print("\n--- Calculator Tool Test ---")
    print(f"Calculator(5, 3, 'multiply'): {calculator.invoke({'a': 5, 'b': 3, 'operation': 'multiply'})}")
    print(f"Calculator(10.5, 2.3, 'add'): {calculator.invoke({'a': 10.5, 'b': 2.3, 'operation': 'add'})}")
    try:
        print(f"Calculator(7, 0, 'divide') (should error): {calculator.invoke({'a': 7, 'b': 0, 'operation': 'divide'})}")
    except ValueError as e:
        print(f"  Error caught as expected: {e}")

    # Test Search Tools
    print("\n--- Search Tools Test ---")
    wiki_res = wiki_search.invoke({'query': 'Artificial Intelligence'})
    print(f"Wiki Search 'Artificial Intelligence': {wiki_res['wiki_results'][:200]}...") # Limit output for brevity
    
    web_res = web_search.invoke({'query': 'Hugging Face new features'})
    print(f"Web Search 'Hugging Face new features': {web_res['web_results'][:200]}...") # Limit output for brevity

    # Test Utility Tool
    print("\n--- Utility Tools Test ---")
    print(f"Current Datetime (default): {get_current_datetime.invoke({})}")
    print(f"Current Datetime (custom format): {get_current_datetime.invoke({'format_string': '%A, %d %B %Y'})}")

    # Test Python REPL Tool
    print("\n--- Python REPL Tool Test ---")
    print(f"Python REPL '2 + 2': {python_repl_tool.invoke({'code': '2 + 2'})}")
    test_code_len = 'len("hello")'
    print(f"Python REPL '{test_code_len}': {python_repl_tool.invoke({'code': test_code_len})}")
    print(f"Python REPL error: {python_repl_tool.invoke({'code': '10 / 0'})}")