rdune71 commited on
Commit
4a45067
Β·
1 Parent(s): 92eb899

This architecture maintains clean separation of

Browse files
Files changed (4) hide show
  1. app.py +32 -8
  2. modules/__init__.py +2 -1
  3. modules/analyzer.py +25 -22
  4. modules/server_cache.py +22 -0
app.py CHANGED
@@ -19,18 +19,19 @@ class ResearchOrchestrator:
19
  self.citation_manager = citation_manager
20
  self.formatter = formatter
21
 
22
- def run(self, query):
23
  """Execute the research pipeline with streaming updates"""
24
  try:
 
25
  logging.info(f"Starting research for query: {query}")
26
 
27
  # Step 1: Process input
28
- yield "πŸ” Processing your query..."
29
  processed_query = self.input_handler.process_query(query)
30
  logging.info("Query processed successfully")
31
 
32
  # Step 2: Retrieve data
33
- yield "🌐 Searching for relevant information..."
34
  search_results = self.retriever.search(processed_query)
35
 
36
  if not search_results:
@@ -41,21 +42,23 @@ class ResearchOrchestrator:
41
  logging.info(f"Retrieved {len(search_results)} results")
42
 
43
  # Step 3: Analyze content
 
44
  yield "🧠 Analyzing search results...\n\n⏳ The AI model may be initializing. This could take a few minutes if it's the first request..."
45
  analysis = self.analyzer.analyze(query, search_results)
46
  logging.info("Analysis completed")
47
 
48
  # Step 4: Manage citations
49
- yield "πŸ“Ž Adding citations..."
50
  cited_analysis = self.citation_manager.add_citations(analysis, search_results)
51
  logging.info("Citations added")
52
 
53
  # Step 5: Format output
54
- yield "✨ Formatting response..."
55
  formatted_output = self.formatter.format_response(cited_analysis, search_results)
56
  logging.info("Response formatted successfully")
57
 
58
  # Add completion notification
 
59
  if len(search_results) >= 3:
60
  completion_message = "\n\n---\n[ANALYSIS COMPLETE] βœ… Research finished with sufficient sources."
61
  else:
@@ -105,14 +108,32 @@ def initialize_modules():
105
  # Initialize orchestrator
106
  orchestrator = initialize_modules()
107
 
108
- def research_assistant(query):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  """Main entry point for the research assistant with streaming"""
110
  logging.info(f"Research assistant called with query: {query}")
111
- for step in orchestrator.run(query):
112
  yield step
113
 
114
  # Create Gradio interface
115
- with gr.Blocks(title="Research Assistant") as demo:
116
  gr.Markdown("# 🧠 AI Research Assistant")
117
  gr.Markdown("Enter a research topic to get a structured analysis with sources")
118
 
@@ -128,6 +149,9 @@ with gr.Blocks(title="Research Assistant") as demo:
128
  with gr.Column():
129
  output = gr.Markdown(label="Analysis Results")
130
 
 
 
 
131
  examples = gr.Examples(
132
  examples=[
133
  "Latest advancements in quantum computing",
 
19
  self.citation_manager = citation_manager
20
  self.formatter = formatter
21
 
22
+ def run(self, query, progress=gr.Progress()):
23
  """Execute the research pipeline with streaming updates"""
24
  try:
25
+ progress(0.0, desc="Starting research...")
26
  logging.info(f"Starting research for query: {query}")
27
 
28
  # Step 1: Process input
29
+ progress(0.1, desc="πŸ” Processing your query...")
30
  processed_query = self.input_handler.process_query(query)
31
  logging.info("Query processed successfully")
32
 
33
  # Step 2: Retrieve data
34
+ progress(0.3, desc="🌐 Searching for relevant information...")
35
  search_results = self.retriever.search(processed_query)
36
 
37
  if not search_results:
 
42
  logging.info(f"Retrieved {len(search_results)} results")
43
 
44
  # Step 3: Analyze content
45
+ progress(0.5, desc="🧠 Analyzing search results...")
46
  yield "🧠 Analyzing search results...\n\n⏳ The AI model may be initializing. This could take a few minutes if it's the first request..."
47
  analysis = self.analyzer.analyze(query, search_results)
48
  logging.info("Analysis completed")
49
 
50
  # Step 4: Manage citations
51
+ progress(0.8, desc="πŸ“Ž Adding citations...")
52
  cited_analysis = self.citation_manager.add_citations(analysis, search_results)
53
  logging.info("Citations added")
54
 
55
  # Step 5: Format output
56
+ progress(0.9, desc="✨ Formatting response...")
57
  formatted_output = self.formatter.format_response(cited_analysis, search_results)
58
  logging.info("Response formatted successfully")
59
 
60
  # Add completion notification
61
+ progress(1.0, desc="βœ… Research complete!")
62
  if len(search_results) >= 3:
63
  completion_message = "\n\n---\n[ANALYSIS COMPLETE] βœ… Research finished with sufficient sources."
64
  else:
 
108
  # Initialize orchestrator
109
  orchestrator = initialize_modules()
110
 
111
+ # Custom CSS for spinner
112
+ custom_css = """
113
+ .spinner {
114
+ border: 4px solid #f3f3f3;
115
+ border-top: 4px solid #3498db;
116
+ border-radius: 50%;
117
+ width: 24px;
118
+ height: 24px;
119
+ animation: spin 1s linear infinite;
120
+ display: inline-block;
121
+ margin-right: 8px;
122
+ }
123
+ @keyframes spin {
124
+ 0% { transform: rotate(0deg); }
125
+ 100% { transform: rotate(360deg); }
126
+ }
127
+ """
128
+
129
+ def research_assistant(query, progress=gr.Progress()):
130
  """Main entry point for the research assistant with streaming"""
131
  logging.info(f"Research assistant called with query: {query}")
132
+ for step in orchestrator.run(query, progress):
133
  yield step
134
 
135
  # Create Gradio interface
136
+ with gr.Blocks(css=custom_css, title="Research Assistant") as demo:
137
  gr.Markdown("# 🧠 AI Research Assistant")
138
  gr.Markdown("Enter a research topic to get a structured analysis with sources")
139
 
 
149
  with gr.Column():
150
  output = gr.Markdown(label="Analysis Results")
151
 
152
+ # Status indicator with spinner
153
+ status_indicator = gr.HTML("<div id='status'><span class='spinner'></span> Ready for your research query</div>")
154
+
155
  examples = gr.Examples(
156
  examples=[
157
  "Latest advancements in quantum computing",
modules/__init__.py CHANGED
@@ -7,5 +7,6 @@ from .retriever import Retriever
7
  from .analyzer import Analyzer
8
  from .citation import CitationManager
9
  from .formatter import OutputFormatter
 
10
 
11
- __all__ = ['InputHandler', 'Retriever', 'Analyzer', 'CitationManager', 'OutputFormatter']
 
7
  from .analyzer import Analyzer
8
  from .citation import CitationManager
9
  from .formatter import OutputFormatter
10
+ from .server_cache import server_status_cache
11
 
12
+ __all__ = ['InputHandler', 'Retriever', 'Analyzer', 'CitationManager', 'OutputFormatter', 'server_status_cache']
modules/analyzer.py CHANGED
@@ -3,6 +3,7 @@ from openai import OpenAI
3
  import requests
4
  import time
5
  import logging
 
6
 
7
  class Analyzer:
8
  def __init__(self, base_url, api_key):
@@ -12,35 +13,37 @@ class Analyzer:
12
  )
13
  self.health_check_url = base_url.rstrip('/') + "/health"
14
  self.headers = {"Authorization": f"Bearer {api_key}"}
 
15
 
16
- def wait_for_server(self, timeout=300, interval=10):
17
- """
18
- Waits for the server to become available by polling the health endpoint.
19
-
20
- Parameters:
21
- timeout (int): Max time in seconds to wait
22
- interval (int): Time between checks
23
 
24
- Returns:
25
- bool: True if server is ready, False if timeout reached
26
- """
27
- logging.info("⏳ Waiting for the server to initialize...")
 
 
 
 
 
 
 
 
 
 
 
 
28
  start_time = time.time()
29
 
30
  while time.time() - start_time < timeout:
31
- try:
32
- response = requests.get(self.health_check_url, headers=self.headers, timeout=10)
33
- if response.status_code == 200:
34
- logging.info("βœ… Server is ready!")
35
- return True
36
- else:
37
- logging.info(f"🌐 Server responded with status: {response.status_code} β€” still initializing...")
38
- except requests.exceptions.RequestException as e:
39
- logging.info("πŸ”΄ Still unreachable β€” retrying...")
40
-
41
  time.sleep(interval)
42
 
43
- logging.warning("⏰ Timeout reached. Server didn't initialize in time.")
44
  return False
45
 
46
  def analyze(self, query, search_results):
 
3
  import requests
4
  import time
5
  import logging
6
+ from modules.server_cache import server_status_cache
7
 
8
  class Analyzer:
9
  def __init__(self, base_url, api_key):
 
13
  )
14
  self.health_check_url = base_url.rstrip('/') + "/health"
15
  self.headers = {"Authorization": f"Bearer {api_key}"}
16
+ self.cache_key = f"server_status_{base_url}"
17
 
18
+ def is_server_ready(self):
19
+ # Check cache first
20
+ cached_status = server_status_cache.get(self.cache_key)
21
+ if cached_status is not None:
22
+ return cached_status
 
 
23
 
24
+ # If not cached, check server
25
+ try:
26
+ response = requests.get(self.health_check_url, headers=self.headers, timeout=5)
27
+ is_ready = response.status_code == 200
28
+ server_status_cache.set(self.cache_key, is_ready)
29
+ return is_ready
30
+ except requests.exceptions.RequestException:
31
+ server_status_cache.set(self.cache_key, False)
32
+ return False
33
+
34
+ def wait_for_server(self, timeout=180, interval=10):
35
+ if self.is_server_ready():
36
+ logging.info("βœ… Server is already ready (from cache).")
37
+ return True
38
+
39
+ logging.info("⏳ Server not ready. Starting polling...")
40
  start_time = time.time()
41
 
42
  while time.time() - start_time < timeout:
43
+ if self.is_server_ready():
44
+ return True
 
 
 
 
 
 
 
 
45
  time.sleep(interval)
46
 
 
47
  return False
48
 
49
  def analyze(self, query, search_results):
modules/server_cache.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/server_cache.py
2
+ import time
3
+
4
+ class ServerStatusCache:
5
+ def __init__(self, ttl=300): # 5 minutes default TTL
6
+ self.cache = {}
7
+ self.ttl = ttl
8
+
9
+ def get(self, server_key):
10
+ if server_key in self.cache:
11
+ timestamp, status = self.cache[server_key]
12
+ if time.time() - timestamp < self.ttl:
13
+ return status
14
+ else:
15
+ del self.cache[server_key] # Expired
16
+ return None
17
+
18
+ def set(self, server_key, status):
19
+ self.cache[server_key] = (time.time(), status)
20
+
21
+ # Global cache instance
22
+ server_status_cache = ServerStatusCache()