rdune71 commited on
Commit
001a1f0
·
1 Parent(s): 01b9b5d

Update with enhanced AI Research Assistant - streaming output, 8192 tokens, improved UI

Browse files
README.md CHANGED
@@ -1,10 +1,4 @@
1
  ---
2
- license: apache-2.0
3
- title: AI Research Assistant
4
- sdk: gradio
5
- ---
6
- # README.md
7
- ---
8
  title: AI Research Assistant
9
  sdk: gradio
10
  sdk_version: 4.38.1
@@ -12,61 +6,128 @@ app_file: app.py
12
  license: apache-2.0
13
  ---
14
 
15
- # AI Research Assistant
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
- An AI-powered research assistant that gathers and analyzes information with web search, weather, and space weather context.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- ## Features
20
 
21
- - Web search integration with Tavily API
22
- - Context enrichment with weather and space weather data
23
- - LLM analysis using Hugging Face Inference Endpoint
24
- - Redis caching for improved performance
25
- - Citation generation for sources
26
- - Responsive Gradio interface
27
 
28
- ## Architecture
29
 
30
- The application follows a modular architecture:
31
 
32
- - `app.py`: Main Gradio interface
33
- - `modules/analyzer.py`: Interacts with Hugging Face Inference Endpoint
34
- - `modules/citation.py`: Manages source tracking and formatting
35
- - `modules/context_enhancer.py`: Adds weather, space weather, and time context
36
- - `modules/formatter.py`: Structures and formats final output
37
- - `modules/input_handler.py`: Validates and prepares user input
38
- - `modules/retriever.py`: Uses Tavily API for web search
39
- - `modules/server_cache.py`: Uses Redis for caching frequent queries
40
- - `modules/status_logger.py`: Logs system status and performance
41
- - `modules/visualizer.py`: Renders output in a user-friendly format
42
- - `modules/visualize_uptime.py`: Monitors system uptime
43
 
44
- ## API Integrations
45
 
46
- - **Tavily**: Web search capabilities
47
- - **Hugging Face Inference Endpoint**: LLM processing
48
- - **Redis**: Caching layer
49
- - **NASA**: Space weather and astronomical data
50
- - **OpenWeatherMap**: Current weather data
 
 
51
 
52
- ## Setup Instructions
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  1. Clone the repository
55
- 2. Set up the required secrets in your environment:
56
- - `HF_TOKEN`: Hugging Face access token
57
- - `TAVILY_API_KEY`: Tavily API key
58
- - `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`: Redis connection details
59
- - `NASA_API_KEY`: NASA API key
60
- - `OPENWEATHER_API_KEY`: OpenWeatherMap API key
61
- 3. Install dependencies: `pip install -r requirements.txt`
62
- 4. Run the application: `python app.py`
 
 
 
 
 
 
 
 
 
 
63
 
64
- ## Deployment
 
 
 
 
 
65
 
66
- Deploy as a Hugging Face Space with the following configuration:
67
- - SDK: Gradio
68
- - Secrets: Configure all API keys as described above
 
 
 
69
 
70
- ## License
 
71
 
72
- Apache 2.0
 
 
1
  ---
 
 
 
 
 
 
2
  title: AI Research Assistant
3
  sdk: gradio
4
  sdk_version: 4.38.1
 
6
  license: apache-2.0
7
  ---
8
 
9
+ # 🧠 AI Research Assistant
10
+
11
+ An advanced AI-powered research assistant that combines web search capabilities with contextual awareness to provide comprehensive answers to complex questions.
12
+
13
+ ## 🌟 Key Features
14
+
15
+ - **Real-time Streaming Output**: See responses as they're generated for immediate feedback
16
+ - **Contextual Awareness**: Incorporates current weather and space weather data
17
+ - **Web Search Integration**: Powered by Tavily API for up-to-date information
18
+ - **Smart Caching**: Redis-based caching for faster repeated queries
19
+ - **Intelligent Server Monitoring**: Clear guidance during model warm-up periods
20
+ - **Accurate Citations**: Real sources extracted from search results
21
+ - **Asynchronous Processing**: Parallel execution for optimal performance
22
+ - **Responsive Interface**: Modern Gradio UI with example queries
23
+
24
+ ## 🏗️ Architecture
25
 
26
+ The application follows a modular architecture for maintainability and scalability:
27
+ myspace134v/
28
+ ├── app.py # Main Gradio interface
29
+ ├── modules/
30
+ │ ├── analyzer.py # LLM interaction with streaming
31
+ │ ├── citation.py # Citation generation and formatting
32
+ │ ├── context_enhancer.py # Weather and space context (async)
33
+ │ ├── formatter.py # Response formatting
34
+ │ ├── input_handler.py # Input validation
35
+ │ ├── retriever.py # Web search with Tavily
36
+ │ ├── server_cache.py # Redis caching
37
+ │ ├── server_monitor.py # Server health monitoring
38
+ │ ├── status_logger.py # Event logging
39
+ │ ├── visualizer.py # Output rendering
40
+ │ └── visualize_uptime.py # System uptime monitoring
41
+ ├── tests/ # Unit tests
42
+ ├── requirements.txt # Dependencies
43
+ └── version.json # Version tracking
44
 
 
45
 
 
 
 
 
 
 
46
 
47
+ ## 🤖 AI Model Information
48
 
49
+ This assistant uses the **DavidAU/OpenAi-GPT-oss-20b-abliterated-uncensored-NEO-Imatrix-gguf** model hosted on Hugging Face Endpoints. This is a powerful open-source language model with:
50
 
51
+ - **20 Billion Parameters**: Capable of handling complex reasoning tasks
52
+ - **Extended Context Window**: Supports up to 8192 tokens per response
53
+ - **Uncensored Capabilities**: Provides comprehensive answers without artificial limitations
54
+ - **Specialized Training**: Optimized for research and analytical tasks
 
 
 
 
 
 
 
55
 
56
+ ## 🔧 API Integrations
57
 
58
+ | Service | Purpose | Usage |
59
+ |---------|---------|-------|
60
+ | **Tavily** | Web Search | Real-time information retrieval |
61
+ | **Hugging Face Inference** | LLM Processing | Natural language understanding |
62
+ | **Redis** | Caching | Performance optimization |
63
+ | **NASA** | Space Data | Astronomical context |
64
+ | **OpenWeatherMap** | Weather Data | Environmental context |
65
 
66
+ ## Enhanced Features
67
 
68
+ ### 🔁 Streaming Output
69
+ Responses stream in real-time, allowing users to start reading before the complete answer is generated. This creates a more natural conversational experience.
70
+
71
+ ### 📚 Dynamic Citations
72
+ All information is properly sourced with clickable links to original content, ensuring transparency and enabling further exploration.
73
+
74
+ ### ⚡ Asynchronous Operations
75
+ Weather data, space weather, and web searches run in parallel, significantly reducing response times.
76
+
77
+ ### 🧠 Contextual Intelligence
78
+ Each query is enhanced with:
79
+ - Current weather conditions
80
+ - Recent space events
81
+ - Accurate timestamps
82
+
83
+ ### 🛡️ Server State Management
84
+ Intelligent monitoring detects when the model server is initializing and provides clear user guidance with estimated wait times.
85
+
86
+ ## 🚀 Getting Started
87
+
88
+ ### Prerequisites
89
+ - Python 3.8+
90
+ - Hugging Face account and token
91
+ - API keys for Tavily, NASA, and OpenWeatherMap
92
+ - Redis instance for caching
93
+
94
+ ### Setup Instructions
95
  1. Clone the repository
96
+ 2. Set up required environment variables:
97
+ ```bash
98
+ export HF_TOKEN="your_hugging_face_token"
99
+ export TAVILY_API_KEY="your_tavily_api_key"
100
+ export REDIS_HOST="your_redis_host"
101
+ export REDIS_PORT="your_redis_port"
102
+ export REDIS_USERNAME="your_redis_username"
103
+ export REDIS_PASSWORD="your_redis_password"
104
+ export NASA_API_KEY="your_nasa_api_key"
105
+ export OPENWEATHER_API_KEY="your_openweather_api_key"
106
+ Install dependencies:
107
+
108
+ pip install -r requirements.txt
109
+ Run the application:
110
+
111
+ python app.py
112
+ 📊 System Monitoring
113
+ The assistant includes built-in monitoring capabilities:
114
 
115
+ Server Health Tracking: Detects and reports server state changes
116
+ Performance Metrics: Logs request processing times
117
+ Uptime Monitoring: Tracks system availability
118
+ Failure Recovery: Automatic handling of transient errors
119
+ 📋 Example Queries
120
+ Try these sample questions to see the assistant in action:
121
 
122
+ "What are the latest developments in fusion energy research?"
123
+ "How does climate change impact global food security?"
124
+ "Explain the significance of recent Mars rover discoveries"
125
+ "What are the economic implications of AI advancement?"
126
+ 📄 License
127
+ This project is licensed under the Apache 2.0 License - see the LICENSE file for details.
128
 
129
+ 🤝 Contributing
130
+ Contributions are welcome! Please feel free to submit a Pull Request.
131
 
132
+ 📞 Support
133
+ For issues, questions, or feedback, please open an issue on the repository.
app.py CHANGED
@@ -1,61 +1,151 @@
1
  import gradio as gr
 
2
  from modules.input_handler import validate_input
3
  from modules.retriever import perform_search
4
  from modules.context_enhancer import add_weather_context, add_space_weather_context
5
  from modules.analyzer import analyze_with_model
6
  from modules.formatter import format_output
7
- from modules.citation import generate_citations
8
- from modules.visualizer import render_output
9
  from modules.server_cache import get_cached_result, cache_result
10
  from modules.status_logger import log_request
 
11
 
12
- def research_assistant(query):
 
 
13
  log_request("Research started", query=query)
14
 
15
- # Check cache first
16
  cached = get_cached_result(query)
17
  if cached:
18
  log_request("Cache hit", query=query)
19
- return cached
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- # Input validation
22
- validated_query = validate_input(query)
 
 
 
 
 
 
23
 
24
- # Context enhancement
25
- weather_data = add_weather_context()
26
- space_weather_data = add_space_weather_context()
27
 
28
- # Web search
29
- search_results = perform_search(validated_query)
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
- # Combine context
32
- enriched_input = f"{validated_query}\n\nWeather: {weather_data}\nSpace Weather: {space_weather_data}\n\nSearch Results:\n{search_results}"
 
33
 
34
- # LLM Analysis
35
- analysis = analyze_with_model(enriched_input)
 
36
 
37
- # Formatting and citations
38
- formatted_output = format_output(analysis)
39
- citations = generate_citations(search_results)
40
 
41
- # Final output
42
- final_output = render_output(formatted_output, citations)
 
43
 
44
- # Cache result
45
- cache_result(query, final_output)
 
46
 
47
- log_request("Research completed", result_length=len(final_output))
48
- return final_output
 
49
 
50
- # Gradio Interface
51
- demo = gr.Interface(
52
- fn=research_assistant,
53
- inputs=gr.Textbox(label="Enter your research question"),
54
- outputs=gr.Markdown(label="Research Summary"),
55
- title="AI Research Assistant",
56
- description="An AI-powered research assistant that gathers and analyzes information with web search, weather, and space weather context.",
57
- allow_flagging="never"
58
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  if __name__ == "__main__":
61
  demo.launch()
 
1
  import gradio as gr
2
+ import asyncio
3
  from modules.input_handler import validate_input
4
  from modules.retriever import perform_search
5
  from modules.context_enhancer import add_weather_context, add_space_weather_context
6
  from modules.analyzer import analyze_with_model
7
  from modules.formatter import format_output
8
+ from modules.citation import generate_citations, format_citations
 
9
  from modules.server_cache import get_cached_result, cache_result
10
  from modules.status_logger import log_request
11
+ from modules.server_monitor import ServerMonitor
12
 
13
+ server_monitor = ServerMonitor()
14
+
15
+ async def research_assistant(query):
16
  log_request("Research started", query=query)
17
 
 
18
  cached = get_cached_result(query)
19
  if cached:
20
  log_request("Cache hit", query=query)
21
+ yield cached
22
+ return
23
+
24
+ try:
25
+ validated_query = validate_input(query)
26
+ except ValueError as e:
27
+ yield f"⚠️ Input Error: {str(e)}"
28
+ return
29
+
30
+ # Run context enhancement and search in parallel
31
+ weather_task = asyncio.create_task(add_weather_context())
32
+ space_weather_task = asyncio.create_task(add_space_weather_context())
33
+ search_task = asyncio.create_task(asyncio.to_thread(perform_search, validated_query))
34
+
35
+ weather_data = await weather_task
36
+ space_weather_data = await space_weather_task
37
+ search_results = await search_task
38
+
39
+ # Handle search errors
40
+ if isinstance(search_results, list) and len(search_results) > 0 and "error" in search_results[0]:
41
+ yield f"🔍 Search Error: {search_results[0]['error']}"
42
+ return
43
 
44
+ # Format search content for LLM
45
+ search_content = ""
46
+ answer_content = ""
47
+ for result in search_results:
48
+ if result.get("type") == "answer":
49
+ answer_content = f"Direct Answer: {result['content']}\n\n"
50
+ elif result.get("type") == "source":
51
+ search_content += f"Source: {result['content']}\n\n"
52
 
53
+ enriched_input = f"{validated_query}\n\n{answer_content}Weather: {weather_data}\nSpace Weather: {space_weather_data}\n\nSearch Results:\n{search_content}"
 
 
54
 
55
+ server_status = server_monitor.check_server_status()
56
+ if not server_status["available"]:
57
+ wait_time = server_status["estimated_wait"]
58
+ yield (
59
+ f"⏳ **Server Initializing** ⏳\n\n"
60
+ f"The AI model server is currently starting up. This happens automatically after periods of inactivity.\n\n"
61
+ f"**Estimated wait time: {wait_time} minutes**\n\n"
62
+ f"**What you can do:**\n"
63
+ f"- Wait for {wait_time} minutes and try again\n"
64
+ f"- Try a simpler query which might process faster\n"
65
+ f"- Check back shortly - the server will be ready soon!\n\n"
66
+ f"*Technical Details: {server_status['message']}*"
67
+ )
68
+ return
69
 
70
+ try:
71
+ stream = analyze_with_model(enriched_input)
72
+ full_response = ""
73
 
74
+ for chunk in stream:
75
+ full_response += chunk
76
+ yield format_output(full_response)
77
 
78
+ citations = generate_citations(search_results)
79
+ citation_text = format_citations(citations)
80
+ full_output = format_output(full_response) + citation_text
81
 
82
+ cache_result(query, full_output)
83
+ server_monitor.report_success()
84
+ log_request("Research completed", result_length=len(full_output))
85
 
86
+ except Exception as e:
87
+ server_monitor.report_failure()
88
+ yield f"🤖 **Unexpected Error** 🤖\n\nAn unexpected error occurred:\n\n{str(e)}"
89
 
90
+ # Wrapper for Gradio
91
+ def research_assistant_wrapper(query):
92
+ return asyncio.run(research_assistant(query))
93
 
94
+ # Gradio Interface for Streaming
95
+ with gr.Blocks(theme=gr.themes.Soft(), title="AI Research Assistant") as demo:
96
+ gr.Markdown("# 🧠 AI Research Assistant")
97
+ gr.Markdown("This advanced AI assistant combines web search with contextual awareness to answer complex questions. "
98
+ "It incorporates current weather and space weather data for richer context.")
99
+
100
+ with gr.Row():
101
+ with gr.Column(scale=1):
102
+ gr.Markdown("## How to Use")
103
+ gr.Markdown("""
104
+ 1. Enter a research question in the input box
105
+ 2. Click Submit or press Enter
106
+ 3. Watch as the response streams in real-time
107
+ 4. Review sources at the end of each response
108
+
109
+ ## Features
110
+ - 🔍 Web search integration
111
+ - 🌤️ Weather context
112
+ - 🌌 Space weather context
113
+ - 📚 Real-time citations
114
+ - ⚡ Streaming output
115
+ """)
116
+
117
+ with gr.Column(scale=2):
118
+ chatbot = gr.Chatbot(height=500, label="Research Conversation")
119
+ msg = gr.Textbox(
120
+ label="Research Question",
121
+ placeholder="Ask a complex research question...",
122
+ lines=3
123
+ )
124
+ submit_btn = gr.Button("Submit Research Query")
125
+ clear_btn = gr.Button("Clear Conversation")
126
+
127
+ examples = gr.Examples(
128
+ examples=[
129
+ "What are the latest developments in quantum computing?",
130
+ "How does climate change affect ocean currents?",
131
+ "Explain the significance of the James Webb Space Telescope findings",
132
+ "What are the economic implications of renewable energy adoption?",
133
+ "How do solar flares affect satellite communications?"
134
+ ],
135
+ inputs=msg,
136
+ label="Example Questions"
137
+ )
138
+
139
+ def respond(message, chat_history):
140
+ bot_response = ""
141
+ for partial_response in research_assistant_wrapper(message):
142
+ bot_response = partial_response
143
+ yield bot_response
144
+
145
+ submit_btn.click(respond, [msg, chatbot], chatbot)
146
+ msg.submit(respond, [msg, chatbot], chatbot)
147
+
148
+ clear_btn.click(lambda: None, None, chatbot, queue=False)
149
 
150
  if __name__ == "__main__":
151
  demo.launch()
modules/analyzer.py CHANGED
@@ -1,5 +1,6 @@
1
  from openai import OpenAI
2
  import os
 
3
 
4
  client = OpenAI(
5
  base_url="https://zxzbfrlg3ssrk7d9.us-east-1.aws.endpoints.huggingface.cloud/v1/",
@@ -7,14 +8,30 @@ client = OpenAI(
7
  )
8
 
9
  def analyze_with_model(prompt):
 
10
  try:
11
  response = client.chat.completions.create(
12
  model="DavidAU/OpenAi-GPT-oss-20b-abliterated-uncensored-NEO-Imatrix-gguf",
13
  messages=[{"role": "user", "content": prompt}],
14
- stream=False,
15
  temperature=0.7,
16
- max_tokens=1000
 
17
  )
18
- return response.choices[0].message.content
 
 
 
 
 
 
19
  except Exception as e:
20
- return f"Error during analysis: {str(e)}"
 
 
 
 
 
 
 
 
 
1
  from openai import OpenAI
2
  import os
3
+ import time
4
 
5
  client = OpenAI(
6
  base_url="https://zxzbfrlg3ssrk7d9.us-east-1.aws.endpoints.huggingface.cloud/v1/",
 
8
  )
9
 
10
  def analyze_with_model(prompt):
11
+ """Analyze prompt with LLM, returning a generator for streaming"""
12
  try:
13
  response = client.chat.completions.create(
14
  model="DavidAU/OpenAi-GPT-oss-20b-abliterated-uncensored-NEO-Imatrix-gguf",
15
  messages=[{"role": "user", "content": prompt}],
16
+ stream=True, # Enable streaming
17
  temperature=0.7,
18
+ max_tokens=8192, # Increased token limit
19
+ timeout=120 # Increased timeout for longer responses
20
  )
21
+
22
+ for chunk in response:
23
+ content = chunk.choices[0].delta.content
24
+ if content:
25
+ yield content
26
+ time.sleep(0.01) # Smooth out the stream
27
+
28
  except Exception as e:
29
+ error_msg = str(e)
30
+ if "503" in error_msg:
31
+ yield f"Error during analysis: Service temporarily unavailable (503). The model server is likely initializing. Please wait 5 minutes and try again. Details: {error_msg}"
32
+ elif "timeout" in error_msg.lower():
33
+ yield f"Error during analysis: Request timed out. The model server may be initializing. Please wait 5 minutes and try again. Details: {error_msg}"
34
+ elif "connection" in error_msg.lower():
35
+ yield f"Error during analysis: Connection error. The model server may be initializing. Please wait 5 minutes and try again. Details: {error_msg}"
36
+ else:
37
+ yield f"Error during analysis: {error_msg}"
modules/citation.py CHANGED
@@ -1,14 +1,14 @@
1
- import json
2
-
3
  def generate_citations(search_results):
4
- """Generate citations from search results"""
5
  try:
6
- # For now, return a placeholder citation
7
- # In a real implementation, this would parse actual search results
8
- return [
9
- {"source": "Web Search Result 1", "url": "https://example.com/1"},
10
- {"source": "Web Search Result 2", "url": "https://example.com/2"}
11
- ]
 
 
12
  except Exception as e:
13
  return [{"error": f"Citation generation failed: {str(e)}"}]
14
 
@@ -16,7 +16,7 @@ def format_citations(citations):
16
  """Format citations for display"""
17
  if not citations:
18
  return ""
19
-
20
  formatted = "\n\n**Sources:**\n"
21
  for i, citation in enumerate(citations, 1):
22
  if "error" in citation:
 
 
 
1
  def generate_citations(search_results):
2
+ """Generate citations from structured search results"""
3
  try:
4
+ citations = []
5
+ for result in search_results:
6
+ if result.get("type") == "source" and result.get("url"):
7
+ citations.append({
8
+ "source": result.get("title", "Unknown Source"),
9
+ "url": result.get("url")
10
+ })
11
+ return citations
12
  except Exception as e:
13
  return [{"error": f"Citation generation failed: {str(e)}"}]
14
 
 
16
  """Format citations for display"""
17
  if not citations:
18
  return ""
19
+
20
  formatted = "\n\n**Sources:**\n"
21
  for i, citation in enumerate(citations, 1):
22
  if "error" in citation:
modules/context_enhancer.py CHANGED
@@ -1,41 +1,37 @@
1
- import requests
2
  import os
3
  from datetime import datetime
4
 
5
- def add_weather_context(location="London"):
6
- """Add current weather context to the query"""
7
  try:
8
  api_key = os.getenv("OPENWEATHER_API_KEY")
9
  if not api_key:
10
  return "Weather data unavailable (API key not configured)"
11
-
12
  url = f"http://api.openweathermap.org/data/2.5/weather?q={location}&appid={api_key}&units=metric"
13
- response = requests.get(url, timeout=5)
14
- response.raise_for_status()
15
- data = response.json()
16
-
17
- return f"Current weather in {location}: {data['weather'][0]['description']}, {data['main']['temp']}°C"
18
  except Exception as e:
19
  return f"Weather data unavailable: {str(e)}"
20
 
21
- def add_space_weather_context():
22
- """Add space weather context to the query"""
23
  try:
24
  api_key = os.getenv("NASA_API_KEY")
25
  if not api_key:
26
  return "Space weather data unavailable (API key not configured)"
27
-
28
- # Using a different NASA endpoint that doesn't require parameters
29
  url = f"https://api.nasa.gov/planetary/apod?api_key={api_key}"
30
- response = requests.get(url, timeout=5)
31
- response.raise_for_status()
32
- data = response.json()
33
-
34
- return f"Space context: Astronomy Picture of the Day - {data.get('title', 'N/A')}"
35
  except Exception as e:
36
  return f"Space weather data unavailable: {str(e)}"
37
 
38
  def add_time_context():
39
- """Add current time context"""
40
  now = datetime.now()
41
  return f"Current date and time: {now.strftime('%Y-%m-%d %H:%M:%S %Z')}"
 
1
+ import aiohttp
2
  import os
3
  from datetime import datetime
4
 
5
+ async def add_weather_context(location="London"):
 
6
  try:
7
  api_key = os.getenv("OPENWEATHER_API_KEY")
8
  if not api_key:
9
  return "Weather data unavailable (API key not configured)"
10
+
11
  url = f"http://api.openweathermap.org/data/2.5/weather?q={location}&appid={api_key}&units=metric"
12
+ async with aiohttp.ClientSession() as session:
13
+ async with session.get(url, timeout=5) as response:
14
+ response.raise_for_status()
15
+ data = await response.json()
16
+ return f"Current weather in {location}: {data['weather'][0]['description']}, {data['main']['temp']}°C"
17
  except Exception as e:
18
  return f"Weather data unavailable: {str(e)}"
19
 
20
+ async def add_space_weather_context():
 
21
  try:
22
  api_key = os.getenv("NASA_API_KEY")
23
  if not api_key:
24
  return "Space weather data unavailable (API key not configured)"
25
+
 
26
  url = f"https://api.nasa.gov/planetary/apod?api_key={api_key}"
27
+ async with aiohttp.ClientSession() as session:
28
+ async with session.get(url, timeout=5) as response:
29
+ response.raise_for_status()
30
+ data = await response.json()
31
+ return f"Space context: Astronomy Picture of the Day - {data.get('title', 'N/A')}"
32
  except Exception as e:
33
  return f"Space weather data unavailable: {str(e)}"
34
 
35
  def add_time_context():
 
36
  now = datetime.now()
37
  return f"Current date and time: {now.strftime('%Y-%m-%d %H:%M:%S %Z')}"
modules/retriever.py CHANGED
@@ -4,25 +4,31 @@ import os
4
  tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
5
 
6
  def perform_search(query):
7
- """Perform web search using Tavily API"""
8
  try:
9
  if not os.getenv("TAVILY_API_KEY"):
10
- return "Web search unavailable (API key not configured)"
11
-
12
  response = tavily.search(
13
- query=query,
14
  max_results=5,
15
  include_answer=True,
16
  include_raw_content=False
17
  )
18
-
19
  results = []
20
  if response.get('answer'):
21
- results.append(f"Direct Answer: {response['answer']}")
22
-
23
  for result in response.get('results', []):
24
- results.append(f"Source: {result['content']}")
25
-
26
- return "\n\n".join(results) if results else "No relevant results found."
 
 
 
 
 
 
27
  except Exception as e:
28
- return f"Search failed: {str(e)}"
 
4
  tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
5
 
6
  def perform_search(query):
7
+ """Perform web search using Tavily API and return structured results"""
8
  try:
9
  if not os.getenv("TAVILY_API_KEY"):
10
+ return [{"error": "API key not configured"}]
11
+
12
  response = tavily.search(
13
+ query=query,
14
  max_results=5,
15
  include_answer=True,
16
  include_raw_content=False
17
  )
18
+
19
  results = []
20
  if response.get('answer'):
21
+ results.append({"type": "answer", "content": response['answer']})
22
+
23
  for result in response.get('results', []):
24
+ results.append({
25
+ "type": "source",
26
+ "title": result.get("title"),
27
+ "url": result.get("url"),
28
+ "content": result.get("content")
29
+ })
30
+
31
+ return results
32
+
33
  except Exception as e:
34
+ return [{"error": f"Search failed: {str(e)}"}]
modules/server_monitor.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import redis
2
+ import os
3
+ import time
4
+ from datetime import datetime, timedelta
5
+
6
+ class ServerMonitor:
7
+ def __init__(self):
8
+ try:
9
+ self.redis_client = redis.Redis(
10
+ host=os.getenv("REDIS_HOST", "localhost"),
11
+ port=int(os.getenv("REDIS_PORT", 6379)),
12
+ username=os.getenv("REDIS_USERNAME"),
13
+ password=os.getenv("REDIS_PASSWORD"),
14
+ decode_responses=True
15
+ )
16
+ # Test connection
17
+ self.redis_client.ping()
18
+ self.connected = True
19
+ except Exception:
20
+ self.redis_client = None
21
+ self.connected = False
22
+
23
+ def report_failure(self):
24
+ """Report a server failure (e.g., 503 error)"""
25
+ if not self.connected:
26
+ return
27
+
28
+ try:
29
+ # Increment failure counter
30
+ key = f"server_failures:{datetime.now().strftime('%Y-%m-%d:%H')}"
31
+ self.redis_client.incr(key)
32
+ self.redis_client.expire(key, 3600) # Expire in 1 hour
33
+
34
+ # Record last failure time
35
+ self.redis_client.set("last_failure", datetime.now().isoformat())
36
+ self.redis_client.expire("last_failure", 86400) # Expire in 24 hours
37
+ except Exception:
38
+ pass # Silently fail to avoid breaking the main app
39
+
40
+ def report_success(self):
41
+ """Report a successful request"""
42
+ if not self.connected:
43
+ return
44
+
45
+ try:
46
+ # Reset failure counter for current hour
47
+ key = f"server_failures:{datetime.now().strftime('%Y-%m-%d:%H')}"
48
+ self.redis_client.delete(key)
49
+
50
+ # Record last success time
51
+ self.redis_client.set("last_success", datetime.now().isoformat())
52
+ self.redis_client.expire("last_success", 86400) # Expire in 24 hours
53
+ except Exception:
54
+ pass # Silently fail to avoid breaking the main app
55
+
56
+ def check_server_status(self):
57
+ """Check if server is likely available based on recent activity"""
58
+ if not self.connected:
59
+ return {"available": True, "message": "Redis not configured, assuming server available"}
60
+
61
+ try:
62
+ # Get recent failures
63
+ now = datetime.now()
64
+ failures_last_hour = 0
65
+
66
+ # Check current and previous hour
67
+ for i in range(2):
68
+ check_time = now - timedelta(hours=i)
69
+ key = f"server_failures:{check_time.strftime('%Y-%m-%d:%H')}"
70
+ failures = self.redis_client.get(key)
71
+ if failures:
72
+ failures_last_hour += int(failures)
73
+
74
+ # Get last failure time
75
+ last_failure_str = self.redis_client.get("last_failure")
76
+ last_success_str = self.redis_client.get("last_success")
77
+
78
+ # If we had recent failures but no recent success, server might be down
79
+ if failures_last_hour > 3:
80
+ if last_success_str:
81
+ last_success = datetime.fromisoformat(last_success_str)
82
+ minutes_since_success = (now - last_success).total_seconds() / 60
83
+ if minutes_since_success < 15:
84
+ return {
85
+ "available": True,
86
+ "message": "Recent success detected, server likely available",
87
+ "estimated_wait": 0
88
+ }
89
+
90
+ # Estimate wait time based on typical warmup
91
+ return {
92
+ "available": False,
93
+ "message": f"High failure rate detected ({failures_last_hour} failures recently)",
94
+ "estimated_wait": 5
95
+ }
96
+
97
+ # If we had a very recent failure (< 5 mins), suggest waiting
98
+ if last_failure_str:
99
+ last_failure = datetime.fromisoformat(last_failure_str)
100
+ minutes_since_failure = (now - last_failure).total_seconds() / 60
101
+ if minutes_since_failure < 5:
102
+ return {
103
+ "available": False,
104
+ "message": f"Recent failure {int(minutes_since_failure)} minutes ago",
105
+ "estimated_wait": max(1, 5 - int(minutes_since_failure))
106
+ }
107
+
108
+ return {
109
+ "available": True,
110
+ "message": "Server appears to be available",
111
+ "estimated_wait": 0
112
+ }
113
+
114
+ except Exception as e:
115
+ # On any Redis error, assume server is available
116
+ return {
117
+ "available": True,
118
+ "message": f"Monitoring check failed: {str(e)}, assuming server available",
119
+ "estimated_wait": 0
120
+ }
121
+
122
+ def get_system_stats(self):
123
+ """Get detailed system statistics"""
124
+ if not self.connected:
125
+ return {"error": "Redis not configured"}
126
+
127
+ try:
128
+ stats = {}
129
+
130
+ # Get recent failures
131
+ now = datetime.now()
132
+ total_failures = 0
133
+ for i in range(24): # Last 24 hours
134
+ check_time = now - timedelta(hours=i)
135
+ key = f"server_failures:{check_time.strftime('%Y-%m-%d:%H')}"
136
+ failures = self.redis_client.get(key)
137
+ if failures:
138
+ total_failures += int(failures)
139
+
140
+ stats["failures_last_24h"] = total_failures
141
+
142
+ # Get last events
143
+ last_failure = self.redis_client.get("last_failure")
144
+ last_success = self.redis_client.get("last_success")
145
+
146
+ stats["last_failure"] = last_failure if last_failure else "None recorded"
147
+ stats["last_success"] = last_success if last_success else "None recorded"
148
+
149
+ # Calculate uptime percentage (approximate)
150
+ if last_failure and last_success:
151
+ failure_time = datetime.fromisoformat(last_failure)
152
+ success_time = datetime.fromisoformat(last_success)
153
+ if success_time > failure_time:
154
+ stats["status"] = "Operational"
155
+ else:
156
+ stats["status"] = "Degraded"
157
+ elif last_success:
158
+ stats["status"] = "Operational"
159
+ elif last_failure:
160
+ stats["status"] = "Issues Detected"
161
+ else:
162
+ stats["status"] = "Unknown"
163
+
164
+ return stats
165
+
166
+ except Exception as e:
167
+ return {"error": str(e)}
requirements.txt CHANGED
@@ -2,5 +2,6 @@ gradio==4.38.1
2
  openai
3
  tavily-python
4
  redis
 
5
  requests
6
  python-dotenv
 
2
  openai
3
  tavily-python
4
  redis
5
+ aiohttp
6
  requests
7
  python-dotenv
version.json CHANGED
@@ -1,4 +1,4 @@
1
  {
2
- "version": "1.0.0",
3
- "description": "Initial modular architecture with Redis, weather, and space weather integration"
4
  }
 
1
  {
2
+ "version": "1.0.0",
3
+ "description": "Initial modular architecture with Redis, weather, and space weather integration"
4
  }