File size: 6,318 Bytes
6464d69
 
 
 
 
 
 
01a4095
6464d69
 
ff707e8
01a4095
ff707e8
6464d69
 
 
 
 
 
 
 
 
 
 
01a4095
6464d69
 
ff707e8
2e7e1ce
01a4095
8676c07
01a4095
 
 
 
8676c07
 
01a4095
 
 
8676c07
01a4095
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9e25d5f
01a4095
8676c07
 
6464d69
 
01a4095
c6bfc2b
01a4095
 
 
 
 
 
 
 
 
6464d69
 
 
01a4095
 
c6bfc2b
 
 
01a4095
 
c6bfc2b
01a4095
ff707e8
c6bfc2b
01a4095
 
 
 
 
 
 
9e25d5f
01a4095
 
6464d69
c6bfc2b
01a4095
 
 
6464d69
ff707e8
6464d69
 
 
 
 
 
 
 
 
 
 
 
 
 
ff707e8
6464d69
 
 
 
ff707e8
6464d69
 
 
 
 
 
ff707e8
6464d69
 
ff707e8
6464d69
 
 
 
ff707e8
6464d69
 
 
ff707e8
6464d69
 
 
 
 
 
 
 
 
a420a58
6464d69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
01a4095
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
"""
tools.py

This module contains all available tools for the AI assistant.
Each tool is a function that can be called by the pipeline.

Tools:
- web_search: Google Serper API-based web search
"""

import os
import json
from typing import Dict, Any, Optional, List

# =============================================================================
# AVAILABLE TOOLS REGISTRY
# =============================================================================

# Dictionary mapping tool names to their functions
AVAILABLE_TOOLS = {
    "web_search": None  # Will be set after function definition
}

# =============================================================================
# SEARCH CONFIGURATION
# =============================================================================

WEB_SEARCH_MAX_QUERY_CHARS = int(os.getenv("WEB_SEARCH_MAX_QUERY_CHARS", "200"))
SEARCH_MAX_RESULTS = int(os.getenv("SEARCH_MAX_RESULTS", "1"))
SEARCH_WORKERS = int(os.getenv("SEARCH_WORKERS", "6"))

try:
    from search import run_search
except ImportError as e:
    raise ImportError("Could not import run_search from search.py: " + str(e))


# =============================================================================
# TOOL IMPLEMENTATIONS
# =============================================================================

def _format_search_results(structured_results: List[Dict[str, Any]]) -> str:
    """
    Format search results from search.py into readable output.
    Excludes unnecessary fields and returns full structured data.
    
    Args:
        structured_results: List of structured result dictionaries from search.py
        
    Returns:
        JSON formatted string with filtered search results
    """
    if not structured_results:
        print("⚠️ No results found from search")
        return json.dumps({"results": [], "count": 0})
    
    # Fields to exclude from each result
    exclude_fields = {
        "publish_date",
        "author",
        "fetch_time",
        "extraction_status",
        "confidence_score",
        "content_type",
        "cleaned_content",
        "first_paragraph",
        "quality_signals"
    }
    
    # Filter results and remove excluded fields
    filtered_results = []
    for result in structured_results[:max(1, SEARCH_MAX_RESULTS)]:
        filtered_result = {k: v for k, v in result.items() if k not in exclude_fields}
        filtered_results.append(filtered_result)
    
    # Format as JSON output
    output = {
        "query_results": filtered_results,
        "total_count": len(filtered_results)
    }
    
    result_json = json.dumps(output, indent=2, ensure_ascii=False)
    print(f"✅ Got {len(filtered_results)} results from search pipeline")
    print(f"📈 Total chars in results from search pipeline after formated( from tools.py): {len(structured_results)}")
    return result_json


def web_search(query: str) -> str:
    """
    Live web search tool using search.py pipeline.

    Args:
        query: The search query string
        
    Returns:
        Formatted string with search results containing:
        - Position (for each result)
        - Title
        - Link
        - Snippet
    """
    try:
        if not query or not isinstance(query, str):
            print("⚠️ Invalid query provided")
            return "No results found."

        q = query.strip()
        if not q:
            print("⚠️ Empty query string")
            return "No results found."

        # Truncate query to max chars
        q = q[:WEB_SEARCH_MAX_QUERY_CHARS]

        print(f"🔎 Web search initiated for: '{q}' (max results={SEARCH_MAX_RESULTS})")
        
        # Run search pipeline from search.py
        structured_results, stats = run_search(q, max_results=SEARCH_MAX_RESULTS, workers=SEARCH_WORKERS)
        
        print(f"   Pipeline: {stats['search_engine']['total']} total results, "
              f"{stats['cleaner']['successful']} successfully extracted")
        print(f"📈 Total chars in results from search pipeline Before formated ( from tools.py ): {len(structured_results)}")
        
        return _format_search_results(structured_results)

    except Exception as e:
        error_msg = f"Web search failed: {str(e)}"
        print(f"❌ {error_msg}")
        return f"Search failed: {str(e)}"


def execute_tool(tool_name: str, args: Dict[str, Any]) -> Optional[str]:
    """
    Execute a tool by name with the given arguments.
    
    Args:
        tool_name: Name of the tool to execute
        args: Dictionary of arguments for the tool
        
    Returns:
        Tool result as string, or None if tool not found or execution fails
    """
    try:
        # Validate tool name
        if not tool_name:
            print("No tool name provided")
            return None
        
        # Check if tool exists
        if tool_name not in AVAILABLE_TOOLS:
            print(f"Unknown tool: '{tool_name}'")
            return None
        
        # Get the tool function
        tool_func = AVAILABLE_TOOLS.get(tool_name)
        
        if tool_func is None:
            print(f"Tool '{tool_name}' not implemented")
            return None
        
        print(f"Executing tool: '{tool_name}' with args: {args}")
        
        # Execute the tool
        result = tool_func(**args)
        
        print(f"Tool '{tool_name}' executed successfully")
        return result
        
    except Exception as e:
        print(f"Error executing tool '{tool_name}': {str(e)}")
        return None


# =============================================================================
# INITIALIZE TOOL REGISTRY
# =============================================================================

# Register the web_search tool
AVAILABLE_TOOLS["web_search"] = web_search
AVAILABLE_TOOLS["websearch"] = web_search


def get_available_tools() -> list:
    """
    Get a list of available tool names.
    
    Returns:
        List of tool name strings
    """
    return list(AVAILABLE_TOOLS.keys())


def is_tool_available(tool_name: str) -> bool:
    """
    Check if a tool is available.
    
    Args:
        tool_name: Name of the tool to check
        
    Returns:
        True if tool exists, False otherwise
    """
    return tool_name in AVAILABLE_TOOLS and AVAILABLE_TOOLS[tool_name] is not None