-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbox_search.py
More file actions
252 lines (197 loc) Β· 9.7 KB
/
Copy pathbox_search.py
File metadata and controls
252 lines (197 loc) Β· 9.7 KB
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
"""
Box Search Tool for Agent
Provides enhanced search functionality with automatic file ID guidance and quick summary options
"""
from box_auth import ensure_authenticated
from typing import List, Dict, Any
import json
import requests
import logging
import urllib.parse
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def _extract_file_ids_from_entries(entries: List[Dict[str, Any]]) -> List[Dict[str, str]]:
"""
Extract file IDs from search entries for easy Box AI Ask usage.
Args:
entries: List of search result entries
Returns:
List of file objects with name and ID
"""
file_entries = []
for entry in entries:
item_type = entry.get("type", "")
item_id = entry.get("id", "")
# Only include actual files (not folders)
if item_type == "file" and item_id and item_id != "unknown":
file_entries.append({
"name": entry.get("name", "Unnamed file"),
"id": item_id
})
return file_entries
def _generate_ai_ask_guidance(file_entries: List[Dict[str, str]], total_count: int) -> str:
"""
Generate helpful guidance for using Box AI Ask with the found files.
Args:
file_entries: List of file objects with name and ID
total_count: Total number of search results
Returns:
Formatted guidance string
"""
if not file_entries:
return ""
guidance = f"\n\nπ **Box AI Analysis Ready** - {len(file_entries)} files ready for analysis:\n"
# Show file list without IDs
guidance += "\n**Files found:**\n"
for i, entry in enumerate(file_entries, 1):
guidance += f"{i}. **{entry['name']}**\n"
# Provide user-friendly instructions instead of JSON
guidance += f"\n**To analyze these files with Box AI, simply ask me to:**\n"
guidance += "β’ \"Summarize these files\"\n"
guidance += "β’ \"What are the key points in these documents?\"\n"
guidance += "β’ \"Give me insights from these files\"\n"
guidance += "β’ \"Analyze these documents for me\"\n"
# Suggest prompts
guidance += "\n**Suggested analysis questions:**\n"
guidance += "β’ \"Summarize the key points in 3 bullet points\"\n"
guidance += "β’ \"What are the main findings?\"\n"
guidance += "β’ \"Extract the compliance requirements\"\n"
guidance += "β’ \"Give me a 2-sentence summary\"\n"
guidance += "β’ \"What are the key takeaways?\"\n"
guidance += f"\nπ‘ **Tip:** Just ask me to analyze the files - I'll handle all the technical details automatically!"
return guidance
def _generate_quick_summary_option(file_entries: List[Dict[str, str]]) -> str:
"""
Generate a quick summary option that users can trigger directly.
Args:
file_entries: List of file objects with name and ID
Returns:
Formatted quick summary option string
"""
if not file_entries:
return ""
quick_option = f"\n\nπ **Quick Analysis Option:**\n"
quick_option += f"Say **\"Quick summary of these files\"** and I'll automatically analyze all {len(file_entries)} files for you!\n"
quick_option += f"Or ask for specific analysis like **\"Summarize key points in 3 bullets\"** and I'll handle the rest.\n"
return quick_option
def quick_summary_of_files(file_ids_json: str, summary_prompt: str = "Summarize the key points in 3 bullet points") -> str:
"""
Prepare file IDs for Box AI Ask analysis.
Args:
file_ids_json: JSON string of file objects from search results
summary_prompt: The prompt to use for analysis (default: 3 bullet summary)
Returns:
Formatted instructions for using Box AI Ask
"""
try:
logger.info(f"π Quick summary preparation for prompt: '{summary_prompt}'")
# Parse the file IDs JSON
try:
if file_ids_json.strip().startswith('{'):
items_json = f"[{file_ids_json}]"
else:
items_json = file_ids_json
items_list = json.loads(items_json)
logger.info(f"π Parsed {len(items_list)} files for quick summary")
except json.JSONDecodeError as e:
logger.error(f"β Invalid JSON format for file IDs: {e}")
return f"Error: Invalid file ID format. Please use the search tool again to get valid file IDs."
# Return user-friendly instructions instead of technical JSON
result = f"""π **Quick Analysis Ready!**
I've prepared {len(items_list)} files for analysis with the prompt: **"{summary_prompt}"**
**To get your analysis, simply ask me to:**
β’ "Analyze these files with Box AI"
β’ "Give me insights from these documents"
β’ "Summarize the key points"
**Or I can automatically analyze them right now if you'd like!**
This approach ensures a seamless user experience while maintaining all the powerful Box AI functionality."""
logger.info("β
Quick summary preparation completed successfully")
return result
except Exception as e:
logger.error(f"β Error in quick summary preparation: {e}")
return f"Error during quick summary preparation: {str(e)}"
def box_search(query: str, limit: int = 20) -> str:
"""
Enhanced Box search with automatic file ID guidance and quick summary options.
Args:
query: The search query to find Box content
limit: Maximum number of results to return (default: 20, max: 200)
Returns:
Enhanced search results with file ID guidance and quick summary options.
"""
logger.info(f"π Enhanced Box search for: '{query}'")
try:
# Get authenticated headers using JWT
auth = ensure_authenticated()
headers = auth.get_headers()
# Build search URL with parameters
base_url = "https://api.box.com/2.0/search"
params = {
"query": query,
"limit": min(limit, 200), # Box API max limit is 200
"offset": 0
}
# Construct URL with query parameters
url = f"{base_url}?{urllib.parse.urlencode(params)}"
response = requests.get(url, headers=headers)
logger.info(f"Box Search API response status: {response.status_code}")
# Handle authentication errors by re-authenticating
if response.status_code == 401:
logger.warning("Authentication failed, attempting to re-authenticate")
auth._authenticate() # Re-authenticate with JWT
headers = auth.get_headers()
response = requests.get(url, headers=headers)
response.raise_for_status()
response_data = response.json()
logger.info(f"π Box Search API response: {json.dumps(response_data, indent=2)}")
entries = response_data.get("entries", [])
total_count = response_data.get("total_count", 0)
logger.info(f"π Found {total_count} total items, {len(entries)} entries")
if entries:
# Extract file IDs for Box AI Ask guidance
file_entries = _extract_file_ids_from_entries(entries)
logger.info(f"π Found {len(file_entries)} files for AI analysis")
# Format the basic results
results = [f"π **Search Results for '{query}'**\n"]
results.append(f"Found {total_count} total items (showing {len(entries)}):\n")
for entry in entries:
name = entry.get("name", "Unnamed item")
item_type = entry.get("type", "unknown")
item_id = entry.get("id", "unknown")
# Get additional details if available
size = entry.get("size")
modified_at = entry.get("modified_at", "").split("T")[0] if entry.get("modified_at") else ""
# Format entry
entry_info = f"- {name} (Type: {item_type}, ID: {item_id}"
if size and item_type == "file":
entry_info += f", Size: {_format_file_size(size)}"
if modified_at:
entry_info += f", Modified: {modified_at}"
entry_info += ")"
results.append(entry_info)
# Add Box AI Ask guidance
ai_guidance = _generate_ai_ask_guidance(file_entries, total_count)
results.append(ai_guidance)
# Add quick summary option
quick_option = _generate_quick_summary_option(file_entries)
results.append(quick_option)
return "\n".join(results)
else:
return f"β No Box content found matching '{query}'.\n\nπ‘ **Try:**\nβ’ Different search terms\nβ’ Broader keywords\nβ’ Check spelling"
except requests.exceptions.RequestException as e:
logger.error(f"Error during Box Search call: {e}")
error_details = f"Status: {e.response.status_code}. Details: {e.response.text}" if hasattr(e, 'response') and e.response else "No response details."
return f"β Box search failed: {error_details}"
except Exception as e:
logger.error(f"Unexpected error during Box search: {e}")
return f"β Box search failed with error: {str(e)}"
def _format_file_size(size_bytes: int) -> str:
"""Format file size in human-readable format"""
if size_bytes < 1024:
return f"{size_bytes} B"
elif size_bytes < 1024 * 1024:
return f"{size_bytes / 1024:.1f} KB"
elif size_bytes < 1024 * 1024 * 1024:
return f"{size_bytes / (1024 * 1024):.1f} MB"
else:
return f"{size_bytes / (1024 * 1024 * 1024):.1f} GB"