Spaces:
Runtime error
Runtime error
Update with enhanced AI Research Assistant - streaming output, 8192 tokens, improved UI
Browse files- README.md +110 -49
- app.py +124 -34
- modules/analyzer.py +21 -4
- modules/citation.py +10 -10
- modules/context_enhancer.py +15 -19
- modules/retriever.py +17 -11
- modules/server_monitor.py +167 -0
- requirements.txt +1 -0
- version.json +2 -2
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
##
|
| 29 |
|
| 30 |
-
|
| 31 |
|
| 32 |
-
-
|
| 33 |
-
-
|
| 34 |
-
-
|
| 35 |
-
-
|
| 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 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
##
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
1. Clone the repository
|
| 55 |
-
2. Set up
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
-
|
|
|
|
| 71 |
|
| 72 |
-
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
#
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
|
| 25 |
-
weather_data = add_weather_context()
|
| 26 |
-
space_weather_data = add_space_weather_context()
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
|
|
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
|
|
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
|
| 41 |
-
|
| 42 |
-
|
|
|
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
|
|
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
|
|
|
| 49 |
|
| 50 |
-
# Gradio Interface
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 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=
|
| 15 |
temperature=0.7,
|
| 16 |
-
max_tokens=
|
|
|
|
| 17 |
)
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
except Exception as e:
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 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
|
| 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 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 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 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 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 "
|
| 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(
|
| 22 |
-
|
| 23 |
for result in response.get('results', []):
|
| 24 |
-
results.append(
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 3 |
-
|
| 4 |
}
|
|
|
|
| 1 |
{
|
| 2 |
+
"version": "1.0.0",
|
| 3 |
+
"description": "Initial modular architecture with Redis, weather, and space weather integration"
|
| 4 |
}
|