ProCreations commited on
Commit
4eec27e
·
verified ·
1 Parent(s): 14ffd10

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -516
app.py DELETED
@@ -1,516 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Just search - A Smart Search Agent using Menlo/Lucy-128k
4
- Part of the Just, AKA Simple series
5
- Built with Gradio, DuckDuckGo Search, and Hugging Face Transformers
6
- """
7
-
8
- import gradio as gr
9
- import torch
10
- from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
11
- from duckduckgo_search import DDGS
12
- import json
13
- import re
14
- import time
15
- from typing import List, Dict, Tuple
16
- import spaces
17
-
18
- # Initialize the model and tokenizer globally for efficiency
19
- MODEL_NAME = "Menlo/Lucy-128k"
20
- tokenizer = None
21
- model = None
22
- search_pipeline = None
23
-
24
- def initialize_model():
25
- """Initialize the Menlo/Lucy-128k model and tokenizer"""
26
- global tokenizer, model, search_pipeline
27
- try:
28
- tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
29
- model = AutoModelForCausalLM.from_pretrained(
30
- MODEL_NAME,
31
- torch_dtype=torch.float16,
32
- device_map="auto",
33
- trust_remote_code=True
34
- )
35
- search_pipeline = pipeline(
36
- "text-generation",
37
- model=model,
38
- tokenizer=tokenizer,
39
- torch_dtype=torch.float16,
40
- device_map="auto",
41
- max_new_tokens=2048,
42
- temperature=0.7,
43
- do_sample=True,
44
- pad_token_id=tokenizer.eos_token_id
45
- )
46
- return True
47
- except Exception as e:
48
- print(f"Error initializing model: {e}")
49
- return False
50
-
51
- def extract_thinking_and_response(text: str) -> Tuple[str, str]:
52
- """Extract thinking process and clean response from AI output"""
53
- thinking = ""
54
- response = text
55
-
56
- # Extract thinking content
57
- thinking_match = re.search(r'<think>(.*?)</think>', text, re.DOTALL)
58
- if thinking_match:
59
- thinking = thinking_match.group(1).strip()
60
- response = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL)
61
-
62
- # Clean up the response
63
- response = re.sub(r'^(Assistant:|AI:|Response:|Answer:)\s*', '', response.strip())
64
- response = re.sub(r'\[INST\].*?\[\/INST\]', '', response)
65
- response = re.sub(r'<\|.*?\|>', '', response)
66
-
67
- return thinking.strip(), response.strip()
68
-
69
- def clean_response(text: str) -> str:
70
- """Clean up the AI response to extract just the relevant content"""
71
- _, response = extract_thinking_and_response(text)
72
- return response
73
-
74
- @spaces.GPU
75
- def generate_search_queries(user_query: str) -> Tuple[List[str], str]:
76
- """Generate multiple search queries based on user input using AI"""
77
- prompt = f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
78
- You are a search query generator. Given a user's question, generate 3-5 different search queries that would help find comprehensive information to answer their question. Return only the search queries, one per line, without numbering or bullet points.
79
-
80
- Example:
81
- User: "What are the latest developments in AI?"
82
- latest AI developments 2024
83
- artificial intelligence breakthroughs recent
84
- AI technology advances news
85
- machine learning innovations 2024
86
-
87
- <|eot_id|><|start_header_id|>user<|end_header_id|>
88
- {user_query}
89
- <|eot_id|><|start_header_id|>assistant<|end_header_id|>"""
90
-
91
- try:
92
- response = search_pipeline(prompt, max_new_tokens=200, temperature=0.3)
93
- generated_text = response[0]['generated_text']
94
-
95
- # Extract just the assistant's response
96
- assistant_response = generated_text.split('<|start_header_id|>assistant<|end_header_id|>')[-1]
97
- thinking, cleaned_response = extract_thinking_and_response(assistant_response)
98
-
99
- # Split into individual queries and clean them
100
- queries = [q.strip() for q in cleaned_response.split('\n') if q.strip()]
101
- # Filter out any non-query text
102
- queries = [q for q in queries if len(q) > 5 and not q.startswith('Note:') and not q.startswith('Example:')]
103
-
104
- return queries[:5], thinking # Return max 5 queries and thinking
105
- except Exception as e:
106
- print(f"Error generating queries: {e}")
107
- # Fallback to simple query variations
108
- return [user_query, f"{user_query} 2024", f"{user_query} latest"], ""
109
-
110
- def search_web(queries: List[str]) -> List[Dict]:
111
- """Search the web using DuckDuckGo with multiple queries"""
112
- all_results = []
113
- ddgs = DDGS()
114
-
115
- for query in queries:
116
- try:
117
- results = ddgs.text(query, max_results=5, region='wt-wt', safesearch='moderate')
118
- for result in results:
119
- result['search_query'] = query
120
- all_results.append(result)
121
- time.sleep(0.5) # Rate limiting
122
- except Exception as e:
123
- print(f"Error searching for '{query}': {e}")
124
- continue
125
-
126
- # Remove duplicates based on URL
127
- seen_urls = set()
128
- unique_results = []
129
- for result in all_results:
130
- if result['href'] not in seen_urls:
131
- seen_urls.add(result['href'])
132
- unique_results.append(result)
133
-
134
- return unique_results[:15] # Return max 15 results
135
-
136
- @spaces.GPU
137
- def filter_relevant_results(user_query: str, search_results: List[Dict]) -> Tuple[List[Dict], str]:
138
- """Use AI to filter and rank search results by relevance"""
139
- if not search_results:
140
- return [], ""
141
-
142
- # Prepare results summary for AI
143
- results_text = ""
144
- for i, result in enumerate(search_results[:12]): # Limit to avoid token overflow
145
- results_text += f"{i+1}. Title: {result.get('title', 'No title')}\n"
146
- results_text += f" URL: {result.get('href', 'No URL')}\n"
147
- results_text += f" Snippet: {result.get('body', 'No description')[:200]}...\n\n"
148
-
149
- prompt = f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
150
- You are a search result evaluator. Given a user's question and search results, identify which results are most relevant and helpful for answering the question.
151
-
152
- Return only the numbers of the most relevant results (1-5 results maximum), separated by commas. Consider:
153
- - Direct relevance to the question
154
- - Credibility of the source
155
- - Recency of information
156
- - Comprehensiveness of content
157
-
158
- Example response: 1, 3, 7
159
-
160
- <|eot_id|><|start_header_id|>user<|end_header_id|>
161
- Question: {user_query}
162
-
163
- Search Results:
164
- {results_text}
165
-
166
- <|eot_id|><|start_header_id|>assistant<|end_header_id|>"""
167
-
168
- try:
169
- response = search_pipeline(prompt, max_new_tokens=100, temperature=0.1)
170
- generated_text = response[0]['generated_text']
171
-
172
- # Extract assistant's response
173
- assistant_response = generated_text.split('<|start_header_id|>assistant<|end_header_id|>')[-1]
174
- thinking, cleaned_response = extract_thinking_and_response(assistant_response)
175
-
176
- # Extract numbers
177
- numbers = re.findall(r'\d+', cleaned_response)
178
- selected_indices = [int(n) - 1 for n in numbers if int(n) <= len(search_results)]
179
-
180
- return [search_results[i] for i in selected_indices if 0 <= i < len(search_results)][:5], thinking
181
- except Exception as e:
182
- print(f"Error filtering results: {e}")
183
- return search_results[:5], "" # Fallback to first 5 results
184
-
185
- @spaces.GPU
186
- def generate_final_answer(user_query: str, selected_results: List[Dict]) -> Tuple[str, str]:
187
- """Generate final answer based on selected search results"""
188
- if not selected_results:
189
- return "I couldn't find relevant information to answer your question. Please try rephrasing your query.", ""
190
-
191
- # Prepare context from selected results
192
- context = ""
193
- for i, result in enumerate(selected_results):
194
- context += f"Source {i+1}: {result.get('title', 'Unknown')}\n"
195
- context += f"Content: {result.get('body', 'No content available')}\n"
196
- context += f"URL: {result.get('href', 'No URL')}\n\n"
197
-
198
- prompt = f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
199
- You are a helpful research assistant. Based on the provided search results, give a comprehensive answer to the user's question.
200
-
201
- Guidelines:
202
- - Synthesize information from multiple sources
203
- - Be accurate and factual
204
- - Cite sources when possible
205
- - If information is conflicting, mention it
206
- - Keep the answer well-structured and easy to read
207
- - Include relevant URLs for further reading
208
-
209
- <|eot_id|><|start_header_id|>user<|end_header_id|>
210
- Question: {user_query}
211
-
212
- Search Results:
213
- {context}
214
-
215
- Please provide a comprehensive answer based on these sources.
216
-
217
- <|eot_id|><|start_header_id|>assistant<|end_header_id|>"""
218
-
219
- try:
220
- response = search_pipeline(prompt, max_new_tokens=1024, temperature=0.2)
221
- generated_text = response[0]['generated_text']
222
-
223
- # Extract assistant's response
224
- assistant_response = generated_text.split('<|start_header_id|>assistant<|end_header_id|>')[-1]
225
- thinking, answer = extract_thinking_and_response(assistant_response)
226
-
227
- return answer, thinking
228
- except Exception as e:
229
- print(f"Error generating final answer: {e}")
230
- return "I encountered an error while processing the search results. Please try again.", ""
231
-
232
- def search_agent_workflow(user_query: str, progress=gr.Progress()) -> Tuple[str, str, str]:
233
- """Main workflow that orchestrates the search agent"""
234
- if not user_query.strip():
235
- return "Please enter a search query.", "", ""
236
-
237
- progress(0.1, desc="Initializing...")
238
- all_thinking = []
239
-
240
- # Step 1: Generate search queries
241
- progress(0.2, desc="Generating search queries...")
242
- queries, thinking1 = generate_search_queries(user_query)
243
- if thinking1:
244
- all_thinking.append(f"**Query Generation:**\n{thinking1}")
245
- queries_text = "Generated queries:\n" + "\n".join(f"• {q}" for q in queries)
246
-
247
- # Step 2: Search the web
248
- progress(0.4, desc="Searching the web...")
249
- search_results = search_web(queries)
250
-
251
- if not search_results:
252
- return "No search results found. Please try a different query.", queries_text, "\n\n".join(all_thinking)
253
-
254
- # Step 3: Filter relevant results
255
- progress(0.6, desc="Filtering relevant results...")
256
- relevant_results, thinking2 = filter_relevant_results(user_query, search_results)
257
- if thinking2:
258
- all_thinking.append(f"**Result Filtering:**\n{thinking2}")
259
-
260
- # Step 4: Generate final answer
261
- progress(0.8, desc="Generating comprehensive answer...")
262
- final_answer, thinking3 = generate_final_answer(user_query, relevant_results)
263
- if thinking3:
264
- all_thinking.append(f"**Answer Generation:**\n{thinking3}")
265
-
266
- progress(1.0, desc="Complete!")
267
-
268
- # Prepare debug info
269
- debug_info = f"{queries_text}\n\nSelected {len(relevant_results)} relevant sources:\n"
270
- for i, result in enumerate(relevant_results):
271
- debug_info += f"{i+1}. {result.get('title', 'No title')} - {result.get('href', 'No URL')}\n"
272
-
273
- thinking_display = "\n\n".join(all_thinking) if all_thinking else "No thinking process recorded."
274
-
275
- return final_answer, debug_info, thinking_display
276
-
277
- # Custom CSS for dark blue theme and mobile responsiveness
278
- custom_css = """
279
- /* Dark blue theme */
280
- :root {
281
- --primary-bg: #0a1628;
282
- --secondary-bg: #1e3a5f;
283
- --accent-bg: #2563eb;
284
- --text-primary: #f8fafc;
285
- --text-secondary: #cbd5e1;
286
- --border-color: #334155;
287
- --input-bg: #1e293b;
288
- --button-bg: #3b82f6;
289
- --button-hover: #2563eb;
290
- }
291
-
292
- /* Global styles */
293
- .gradio-container {
294
- background: linear-gradient(135deg, var(--primary-bg) 0%, var(--secondary-bg) 100%) !important;
295
- color: var(--text-primary) !important;
296
- font-family: 'Inter', 'Segoe UI', system-ui, sans-serif !important;
297
- }
298
-
299
- /* Mobile responsiveness */
300
- @media (max-width: 768px) {
301
- .gradio-container {
302
- padding: 10px !important;
303
- }
304
-
305
- .gr-form {
306
- gap: 15px !important;
307
- }
308
-
309
- .gr-button {
310
- font-size: 16px !important;
311
- padding: 12px 20px !important;
312
- }
313
- }
314
-
315
- /* Input styling */
316
- .gr-textbox textarea, .gr-textbox input {
317
- background: var(--input-bg) !important;
318
- border: 1px solid var(--border-color) !important;
319
- color: var(--text-primary) !important;
320
- border-radius: 8px !important;
321
- }
322
-
323
- /* Button styling */
324
- .gr-button {
325
- background: linear-gradient(135deg, var(--button-bg) 0%, var(--accent-bg) 100%) !important;
326
- color: white !important;
327
- border: none !important;
328
- border-radius: 8px !important;
329
- font-weight: 600 !important;
330
- transition: all 0.3s ease !important;
331
- }
332
-
333
- .gr-button:hover {
334
- background: linear-gradient(135deg, var(--button-hover) 0%, var(--button-bg) 100%) !important;
335
- transform: translateY(-1px) !important;
336
- box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
337
- }
338
-
339
- /* Output styling */
340
- .gr-markdown, .gr-textbox {
341
- background: var(--input-bg) !important;
342
- border: 1px solid var(--border-color) !important;
343
- border-radius: 8px !important;
344
- color: var(--text-primary) !important;
345
- }
346
-
347
- /* Header styling */
348
- .gr-markdown h1 {
349
- color: var(--accent-bg) !important;
350
- text-align: center !important;
351
- margin-bottom: 20px !important;
352
- font-size: 2.5rem !important;
353
- font-weight: 700 !important;
354
- }
355
-
356
- /* Thinking section styling */
357
- #thinking-output {
358
- background: var(--secondary-bg) !important;
359
- border: 1px solid var(--border-color) !important;
360
- border-radius: 8px !important;
361
- padding: 15px !important;
362
- font-family: 'Fira Code', 'Monaco', monospace !important;
363
- font-size: 0.9rem !important;
364
- line-height: 1.4 !important;
365
- }
366
-
367
- /* Loading animation */
368
- .gr-loading {
369
- background: var(--secondary-bg) !important;
370
- border-radius: 8px !important;
371
- }
372
-
373
- /* Scrollbar styling */
374
- ::-webkit-scrollbar {
375
- width: 8px;
376
- }
377
-
378
- ::-webkit-scrollbar-track {
379
- background: var(--primary-bg);
380
- }
381
-
382
- ::-webkit-scrollbar-thumb {
383
- background: var(--accent-bg);
384
- border-radius: 4px;
385
- }
386
-
387
- ::-webkit-scrollbar-thumb:hover {
388
- background: var(--button-hover);
389
- }
390
- """
391
-
392
- def create_interface():
393
- """Create the Gradio interface"""
394
- with gr.Blocks(
395
- theme=gr.themes.Base(
396
- primary_hue="blue",
397
- secondary_hue="slate",
398
- neutral_hue="slate",
399
- text_size="lg",
400
- spacing_size="lg",
401
- radius_size="md"
402
- ),
403
- css=custom_css,
404
- title="Just search - AI Search Agent",
405
- head="<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
406
- ) as interface:
407
-
408
- gr.Markdown("# 🔍 Just search", elem_id="header")
409
- gr.Markdown(
410
- "*Part of the Just, AKA Simple series*\n\n"
411
- "**Intelligent search agent powered by Menlo/Lucy-128k**\n\n"
412
- "Ask any question and get comprehensive answers from the web.",
413
- elem_id="description"
414
- )
415
-
416
- with gr.Row():
417
- with gr.Column(scale=4):
418
- query_input = gr.Textbox(
419
- label="Your Question",
420
- placeholder="Ask me anything... (e.g., 'What are the latest developments in AI?')",
421
- lines=2,
422
- elem_id="query-input"
423
- )
424
- with gr.Column(scale=1):
425
- search_btn = gr.Button(
426
- "🔎 Search",
427
- variant="primary",
428
- size="lg",
429
- elem_id="search-button"
430
- )
431
-
432
- with gr.Row():
433
- answer_output = gr.Markdown(
434
- label="Answer",
435
- elem_id="answer-output",
436
- height=400
437
- )
438
-
439
- with gr.Accordion("🤔 AI Thinking Process", open=False):
440
- thinking_output = gr.Markdown(
441
- label="Model's Chain of Thought",
442
- elem_id="thinking-output",
443
- height=300
444
- )
445
-
446
- with gr.Accordion("🔧 Debug Info", open=False):
447
- debug_output = gr.Textbox(
448
- label="Search Process Details",
449
- lines=8,
450
- elem_id="debug-output"
451
- )
452
-
453
- # Event handlers
454
- search_btn.click(
455
- fn=search_agent_workflow,
456
- inputs=[query_input],
457
- outputs=[answer_output, debug_output, thinking_output],
458
- show_progress=True
459
- )
460
-
461
- query_input.submit(
462
- fn=search_agent_workflow,
463
- inputs=[query_input],
464
- outputs=[answer_output, debug_output, thinking_output],
465
- show_progress=True
466
- )
467
-
468
- # Example queries
469
- gr.Examples(
470
- examples=[
471
- ["What are the latest breakthroughs in quantum computing?"],
472
- ["How does climate change affect ocean currents?"],
473
- ["What are the best practices for sustainable agriculture?"],
474
- ["Explain the recent developments in renewable energy technology"],
475
- ["What are the health benefits of the Mediterranean diet?"]
476
- ],
477
- inputs=query_input,
478
- outputs=[answer_output, debug_output, thinking_output],
479
- fn=search_agent_workflow,
480
- cache_examples=False
481
- )
482
-
483
- gr.Markdown(
484
- "---\n**Note:** This search agent generates multiple queries, searches the web, "
485
- "filters results for relevance, and provides comprehensive answers. "
486
- "Results are sourced from DuckDuckGo search."
487
- )
488
-
489
- return interface
490
-
491
- def main():
492
- """Main function to initialize and launch the app"""
493
- print("🚀 Initializing Just search...")
494
-
495
- # Initialize the model
496
- if not initialize_model():
497
- print("❌ Failed to initialize model. Please check your setup.")
498
- return
499
-
500
- print("✅ Model initialized successfully!")
501
- print("🌐 Creating interface...")
502
-
503
- # Create and launch the interface
504
- interface = create_interface()
505
-
506
- print("🎉 Just search is ready!")
507
- interface.launch(
508
- server_name="0.0.0.0",
509
- server_port=7860,
510
- share=True,
511
- show_error=True,
512
- debug=True
513
- )
514
-
515
- if __name__ == "__main__":
516
- main()