Spaces:
Sleeping
Sleeping
Upload 11 files
Browse files- __init__.py +5 -0
- main.py +48 -0
- requirements.txt +26 -0
- src/__pycache__/agent.cpython-311.pyc +0 -0
- src/__pycache__/config.cpython-311.pyc +0 -0
- src/__pycache__/ui.cpython-311.pyc +0 -0
- src/__pycache__/utils.cpython-311.pyc +0 -0
- src/agent.py +196 -0
- src/config.py +58 -0
- src/ui.py +184 -0
- src/utils.py +147 -0
__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Gemini AI Research Agent
|
| 3 |
+
|
| 4 |
+
An advanced AI assistant powered by Google's Gemini 1.5 Flash model,
|
| 5 |
+
specialized in handling complex research questions and multi
|
main.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Main application entry point for the Gemini AI Agent.
|
| 4 |
+
This script initializes and runs the Gradio interface.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import gradio as gr
|
| 8 |
+
import logging
|
| 9 |
+
from src.agent import GeminiAgent
|
| 10 |
+
from src.config import Config
|
| 11 |
+
from src.ui import create_interface
|
| 12 |
+
|
| 13 |
+
# Configure logging
|
| 14 |
+
logging.basicConfig(
|
| 15 |
+
level=logging.INFO,
|
| 16 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 17 |
+
)
|
| 18 |
+
logger = logging.getLogger(__name__)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def main():
|
| 22 |
+
"""
|
| 23 |
+
Main function to initialize and launch the application.
|
| 24 |
+
"""
|
| 25 |
+
try:
|
| 26 |
+
# Load configuration
|
| 27 |
+
config = Config()
|
| 28 |
+
|
| 29 |
+
# Initialize the Gemini agent
|
| 30 |
+
agent = GeminiAgent(config)
|
| 31 |
+
|
| 32 |
+
# Create and launch the Gradio interface
|
| 33 |
+
interface = create_interface(agent)
|
| 34 |
+
|
| 35 |
+
logger.info("Starting Gemini AI Agent application...")
|
| 36 |
+
interface.launch(
|
| 37 |
+
server_name="0.0.0.0",
|
| 38 |
+
server_port=7860,
|
| 39 |
+
share=False
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
except Exception as e:
|
| 43 |
+
logger.error(f"Failed to start application: {e}")
|
| 44 |
+
raise
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
if __name__ == "__main__":
|
| 48 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core dependencies
|
| 2 |
+
gradio>=4.0.0
|
| 3 |
+
google-generativeai>=0.3.0
|
| 4 |
+
python-dotenv>=1.0.0
|
| 5 |
+
|
| 6 |
+
# Utility dependencies
|
| 7 |
+
requests>=2.31.0
|
| 8 |
+
aiohttp>=3.8.0
|
| 9 |
+
asyncio-throttle>=1.0.2
|
| 10 |
+
|
| 11 |
+
# Data processing
|
| 12 |
+
pandas>=2.0.0
|
| 13 |
+
numpy>=1.24.0
|
| 14 |
+
Pillow>=10.0.0
|
| 15 |
+
|
| 16 |
+
# Development and testing
|
| 17 |
+
pytest>=7.4.0
|
| 18 |
+
pytest-asyncio>=0.21.0
|
| 19 |
+
black>=23.0.0
|
| 20 |
+
flake8>=6.0.0
|
| 21 |
+
|
| 22 |
+
# Optional: For enhanced functionality
|
| 23 |
+
youtube-dl>=2021.12.17 # For video analysis
|
| 24 |
+
beautifulsoup4>=4.12.0 # For web scraping
|
| 25 |
+
python-chess>=1.999 # For chess analysis
|
| 26 |
+
sympy>=1.12 # For mathematical computations
|
src/__pycache__/agent.cpython-311.pyc
ADDED
|
Binary file (9.07 kB). View file
|
|
|
src/__pycache__/config.cpython-311.pyc
ADDED
|
Binary file (2.42 kB). View file
|
|
|
src/__pycache__/ui.cpython-311.pyc
ADDED
|
Binary file (9.35 kB). View file
|
|
|
src/__pycache__/utils.cpython-311.pyc
ADDED
|
Binary file (5.43 kB). View file
|
|
|
src/agent.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Gemini AI Agent implementation.
|
| 3 |
+
Handles communication with Google's Gemini API and manages conversation context.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
import asyncio
|
| 8 |
+
from typing import List, Dict, Any, Optional
|
| 9 |
+
import google.generativeai as genai
|
| 10 |
+
from google.generativeai.types import HarmCategory, HarmBlockThreshold
|
| 11 |
+
from src.config import Config
|
| 12 |
+
from src.utils import sanitize_input, format_response
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
logger = logging.getLogger(__name__)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class GeminiAgent:
|
| 19 |
+
"""
|
| 20 |
+
AI Agent powered by Google's Gemini 1.5 Flash model.
|
| 21 |
+
|
| 22 |
+
This agent is designed to handle complex, multi-modal questions including:
|
| 23 |
+
- Text analysis and reasoning
|
| 24 |
+
- Mathematical computations
|
| 25 |
+
- Research tasks requiring web search
|
| 26 |
+
- Data analysis and interpretation
|
| 27 |
+
- Creative problem solving
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(self, config: Config):
|
| 31 |
+
"""
|
| 32 |
+
Initialize the Gemini Agent.
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
config: Configuration object containing API keys and settings
|
| 36 |
+
"""
|
| 37 |
+
self.config = config
|
| 38 |
+
self.conversation_history: List[Dict[str, str]] = []
|
| 39 |
+
self._initialize_client()
|
| 40 |
+
|
| 41 |
+
def _initialize_client(self):
|
| 42 |
+
"""
|
| 43 |
+
Initialize the Gemini API client with safety settings.
|
| 44 |
+
"""
|
| 45 |
+
try:
|
| 46 |
+
genai.configure(api_key=self.config.gemini_api_key)
|
| 47 |
+
|
| 48 |
+
# Configure safety settings for research and analysis tasks
|
| 49 |
+
safety_settings = {
|
| 50 |
+
HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_NONE,
|
| 51 |
+
HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_NONE,
|
| 52 |
+
HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE,
|
| 53 |
+
HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE,
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
# Initialize the model
|
| 57 |
+
self.model = genai.GenerativeModel(
|
| 58 |
+
model_name=self.config.model_name,
|
| 59 |
+
safety_settings=safety_settings
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
logger.info(f"Gemini client initialized with model: {self.config.model_name}")
|
| 63 |
+
|
| 64 |
+
except Exception as e:
|
| 65 |
+
logger.error(f"Failed to initialize Gemini client: {e}")
|
| 66 |
+
raise
|
| 67 |
+
|
| 68 |
+
def _build_system_prompt(self) -> str:
|
| 69 |
+
"""
|
| 70 |
+
Build the system prompt that defines the agent's capabilities and role.
|
| 71 |
+
|
| 72 |
+
Returns:
|
| 73 |
+
str: System prompt for the agent
|
| 74 |
+
"""
|
| 75 |
+
return """You are an advanced AI research assistant specialized in answering complex, multi-faceted questions that may require:
|
| 76 |
+
|
| 77 |
+
- Deep research and fact-checking
|
| 78 |
+
- Mathematical calculations and logical reasoning
|
| 79 |
+
- Analysis of multimedia content (images, videos, audio)
|
| 80 |
+
- Data interpretation and statistical analysis
|
| 81 |
+
- Cross-referencing multiple sources
|
| 82 |
+
- Handling ambiguous or challenging queries
|
| 83 |
+
|
| 84 |
+
Key capabilities:
|
| 85 |
+
1. **Research Excellence**: Thoroughly investigate topics using available information
|
| 86 |
+
2. **Multi-modal Analysis**: Process and analyze images, videos, and audio content
|
| 87 |
+
3. **Mathematical Reasoning**: Perform calculations and logical deductions
|
| 88 |
+
4. **Data Analysis**: Interpret tables, charts, and datasets
|
| 89 |
+
5. **Fact Verification**: Cross-check information for accuracy
|
| 90 |
+
6. **Creative Problem Solving**: Approach unusual questions with innovative methods
|
| 91 |
+
|
| 92 |
+
Guidelines:
|
| 93 |
+
- Provide detailed, well-researched answers
|
| 94 |
+
- Show your reasoning process step-by-step
|
| 95 |
+
- If information is incomplete, clearly state assumptions
|
| 96 |
+
- For complex calculations, break down the steps
|
| 97 |
+
- When analyzing media, describe what you observe
|
| 98 |
+
- Always strive for accuracy over speed
|
| 99 |
+
- If uncertain, express confidence levels
|
| 100 |
+
|
| 101 |
+
Remember: You excel at handling challenging questions that require deep thinking and research."""
|
| 102 |
+
|
| 103 |
+
async def process_question(self, question: str, context: Optional[str] = None) -> str:
|
| 104 |
+
"""
|
| 105 |
+
Process a question and generate a response using Gemini.
|
| 106 |
+
|
| 107 |
+
Args:
|
| 108 |
+
question: The user's question
|
| 109 |
+
context: Optional additional context
|
| 110 |
+
|
| 111 |
+
Returns:
|
| 112 |
+
str: The agent's response
|
| 113 |
+
"""
|
| 114 |
+
try:
|
| 115 |
+
# Sanitize input
|
| 116 |
+
clean_question = sanitize_input(question)
|
| 117 |
+
|
| 118 |
+
# Build the full prompt
|
| 119 |
+
system_prompt = self._build_system_prompt()
|
| 120 |
+
|
| 121 |
+
# Prepare conversation context
|
| 122 |
+
context_str = ""
|
| 123 |
+
if self.conversation_history:
|
| 124 |
+
context_str = "\n\nPrevious conversation context:\n"
|
| 125 |
+
for entry in self.conversation_history[-3:]: # Last 3 exchanges
|
| 126 |
+
context_str += f"Q: {entry['question']}\nA: {entry['answer'][:200]}...\n"
|
| 127 |
+
|
| 128 |
+
# Build final prompt
|
| 129 |
+
full_prompt = f"{system_prompt}\n\n"
|
| 130 |
+
if context:
|
| 131 |
+
full_prompt += f"Additional Context: {context}\n\n"
|
| 132 |
+
full_prompt += f"{context_str}\nCurrent Question: {clean_question}\n\nResponse:"
|
| 133 |
+
|
| 134 |
+
# Generate response
|
| 135 |
+
response = self.model.generate_content(
|
| 136 |
+
full_prompt,
|
| 137 |
+
generation_config=genai.types.GenerationConfig(
|
| 138 |
+
max_output_tokens=self.config.max_tokens,
|
| 139 |
+
temperature=self.config.temperature,
|
| 140 |
+
)
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
# Extract and format response
|
| 144 |
+
if response.text:
|
| 145 |
+
formatted_response = format_response(response.text)
|
| 146 |
+
|
| 147 |
+
# Update conversation history
|
| 148 |
+
self._update_history(clean_question, formatted_response)
|
| 149 |
+
|
| 150 |
+
logger.info(f"Successfully processed question: {clean_question[:50]}...")
|
| 151 |
+
return formatted_response
|
| 152 |
+
else:
|
| 153 |
+
logger.warning("Received empty response from Gemini")
|
| 154 |
+
return "I apologize, but I couldn't generate a response to your question. Please try rephrasing it."
|
| 155 |
+
|
| 156 |
+
except Exception as e:
|
| 157 |
+
logger.error(f"Error processing question: {e}")
|
| 158 |
+
return f"I encountered an error while processing your question: {str(e)}"
|
| 159 |
+
|
| 160 |
+
def _update_history(self, question: str, answer: str):
|
| 161 |
+
"""
|
| 162 |
+
Update the conversation history.
|
| 163 |
+
|
| 164 |
+
Args:
|
| 165 |
+
question: The user's question
|
| 166 |
+
answer: The agent's response
|
| 167 |
+
"""
|
| 168 |
+
self.conversation_history.append({
|
| 169 |
+
'question': question,
|
| 170 |
+
'answer': answer
|
| 171 |
+
})
|
| 172 |
+
|
| 173 |
+
# Keep only recent history
|
| 174 |
+
if len(self.conversation_history) > self.config.max_history_length:
|
| 175 |
+
self.conversation_history = self.conversation_history[-self.config.max_history_length:]
|
| 176 |
+
|
| 177 |
+
def clear_history(self):
|
| 178 |
+
"""
|
| 179 |
+
Clear the conversation history.
|
| 180 |
+
"""
|
| 181 |
+
self.conversation_history.clear()
|
| 182 |
+
logger.info("Conversation history cleared")
|
| 183 |
+
|
| 184 |
+
def get_stats(self) -> Dict[str, Any]:
|
| 185 |
+
"""
|
| 186 |
+
Get agent statistics.
|
| 187 |
+
|
| 188 |
+
Returns:
|
| 189 |
+
Dict containing agent statistics
|
| 190 |
+
"""
|
| 191 |
+
return {
|
| 192 |
+
'model': self.config.model_name,
|
| 193 |
+
'conversation_length': len(self.conversation_history),
|
| 194 |
+
'max_tokens': self.config.max_tokens,
|
| 195 |
+
'temperature': self.config.temperature
|
| 196 |
+
}
|
src/config.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Configuration management for the Gemini AI Agent.
|
| 3 |
+
Handles environment variables and application settings.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
from typing import Optional
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class Config:
|
| 13 |
+
"""
|
| 14 |
+
Configuration class for the Gemini AI Agent.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
# API Configuration
|
| 18 |
+
gemini_api_key: str
|
| 19 |
+
model_name: str = "gemini-1.5-flash"
|
| 20 |
+
max_tokens: int = 2048
|
| 21 |
+
temperature: float = 0.7
|
| 22 |
+
|
| 23 |
+
# Application Configuration
|
| 24 |
+
max_history_length: int = 10
|
| 25 |
+
timeout_seconds: int = 30
|
| 26 |
+
|
| 27 |
+
def __init__(self):
|
| 28 |
+
"""
|
| 29 |
+
Initialize configuration from environment variables.
|
| 30 |
+
"""
|
| 31 |
+
self.gemini_api_key = self._get_api_key()
|
| 32 |
+
|
| 33 |
+
def _get_api_key(self) -> str:
|
| 34 |
+
"""
|
| 35 |
+
Get the Gemini API key from environment variables.
|
| 36 |
+
|
| 37 |
+
Returns:
|
| 38 |
+
str: The API key
|
| 39 |
+
|
| 40 |
+
Raises:
|
| 41 |
+
ValueError: If API key is not found
|
| 42 |
+
"""
|
| 43 |
+
api_key = os.getenv('GEMINI_API_KEY')
|
| 44 |
+
if not api_key:
|
| 45 |
+
raise ValueError(
|
| 46 |
+
"GEMINI_API_KEY environment variable is required. "
|
| 47 |
+
"Please set it with your Google AI Studio API key."
|
| 48 |
+
)
|
| 49 |
+
return api_key
|
| 50 |
+
|
| 51 |
+
def validate(self) -> bool:
|
| 52 |
+
"""
|
| 53 |
+
Validate the configuration.
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
bool: True if configuration is valid
|
| 57 |
+
"""
|
| 58 |
+
return bool(self.gemini_api_key and self.model_name)
|
src/ui.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Gradio user interface for the Gemini AI Agent.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import gradio as gr
|
| 6 |
+
import asyncio
|
| 7 |
+
import logging
|
| 8 |
+
from typing import List, Tuple, Optional
|
| 9 |
+
from src.agent import GeminiAgent
|
| 10 |
+
from src.utils import validate_question, format_error_message
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def create_interface(agent: GeminiAgent) -> gr.Blocks:
|
| 17 |
+
"""
|
| 18 |
+
Create the Gradio interface for the AI agent.
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
agent: The GeminiAgent instance
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
gr.Blocks: The Gradio interface
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
async def process_question_async(question: str, context: str = "") -> str:
|
| 28 |
+
"""
|
| 29 |
+
Async wrapper for question processing.
|
| 30 |
+
"""
|
| 31 |
+
return await agent.process_question(question, context or None)
|
| 32 |
+
|
| 33 |
+
def process_question_sync(question: str, context: str = "") -> str:
|
| 34 |
+
"""
|
| 35 |
+
Synchronous wrapper for question processing (required by Gradio).
|
| 36 |
+
"""
|
| 37 |
+
try:
|
| 38 |
+
# Validate question
|
| 39 |
+
is_valid, error_msg = validate_question(question)
|
| 40 |
+
if not is_valid:
|
| 41 |
+
return f"❌ **Error**: {error_msg}"
|
| 42 |
+
|
| 43 |
+
# Process question
|
| 44 |
+
loop = asyncio.new_event_loop()
|
| 45 |
+
asyncio.set_event_loop(loop)
|
| 46 |
+
try:
|
| 47 |
+
result = loop.run_until_complete(process_question_async(question, context))
|
| 48 |
+
return f"🤖 **AI Response**:\n\n{result}"
|
| 49 |
+
finally:
|
| 50 |
+
loop.close()
|
| 51 |
+
|
| 52 |
+
except Exception as e:
|
| 53 |
+
error_msg = format_error_message(e)
|
| 54 |
+
return f"❌ **Error**: {error_msg}"
|
| 55 |
+
|
| 56 |
+
def clear_conversation():
|
| 57 |
+
"""
|
| 58 |
+
Clear the conversation history.
|
| 59 |
+
"""
|
| 60 |
+
agent.clear_history()
|
| 61 |
+
return "", "Conversation history cleared! ✨"
|
| 62 |
+
|
| 63 |
+
def get_agent_info() -> str:
|
| 64 |
+
"""
|
| 65 |
+
Get information about the agent.
|
| 66 |
+
"""
|
| 67 |
+
stats = agent.get_stats()
|
| 68 |
+
return f"""
|
| 69 |
+
## 🤖 Agent Information
|
| 70 |
+
|
| 71 |
+
**Model**: {stats['model']}
|
| 72 |
+
**Conversation Length**: {stats['conversation_length']} exchanges
|
| 73 |
+
**Max Tokens**: {stats['max_tokens']}
|
| 74 |
+
**Temperature**: {stats['temperature']}
|
| 75 |
+
|
| 76 |
+
### 🎯 Specialized Capabilities
|
| 77 |
+
- **Complex Research**: Multi-source fact-checking and analysis
|
| 78 |
+
- **Mathematical Reasoning**: Step-by-step problem solving
|
| 79 |
+
- **Multi-modal Analysis**: Processing images, videos, and audio
|
| 80 |
+
- **Data Interpretation**: Tables, charts, and statistical analysis
|
| 81 |
+
- **Creative Problem Solving**: Innovative approaches to unusual questions
|
| 82 |
+
"""
|
| 83 |
+
|
| 84 |
+
# Create the interface
|
| 85 |
+
with gr.Blocks(
|
| 86 |
+
title="Gemini AI Research Agent",
|
| 87 |
+
theme=gr.themes.Soft(),
|
| 88 |
+
css="""
|
| 89 |
+
.container {
|
| 90 |
+
max-width: 1200px;
|
| 91 |
+
margin: auto;
|
| 92 |
+
}
|
| 93 |
+
.question-box {
|
| 94 |
+
border-left: 4px solid #4CAF50;
|
| 95 |
+
padding-left: 16px;
|
| 96 |
+
}
|
| 97 |
+
.response-box {
|
| 98 |
+
background-color: #f8f9fa;
|
| 99 |
+
border-radius: 8px;
|
| 100 |
+
padding: 16px;
|
| 101 |
+
}
|
| 102 |
+
"""
|
| 103 |
+
) as interface:
|
| 104 |
+
|
| 105 |
+
gr.Markdown("""
|
| 106 |
+
# 🧠 Gemini AI Research Agent
|
| 107 |
+
|
| 108 |
+
An advanced AI assistant powered by Google's Gemini 1.5 Flash, specialized in handling complex research questions, data analysis, and multi-modal content processing.
|
| 109 |
+
|
| 110 |
+
**Perfect for**: Academic research, fact-checking, mathematical problems, data analysis, and challenging multi-step questions.
|
| 111 |
+
""")
|
| 112 |
+
|
| 113 |
+
with gr.Row():
|
| 114 |
+
with gr.Column(scale=2):
|
| 115 |
+
with gr.Group():
|
| 116 |
+
gr.Markdown("## 💬 Ask Your Question")
|
| 117 |
+
|
| 118 |
+
question_input = gr.Textbox(
|
| 119 |
+
label="Question",
|
| 120 |
+
placeholder="Enter your research question here... (e.g., 'How many studio albums were published by Mercedes Sosa between 2000 and 2009?')",
|
| 121 |
+
lines=4,
|
| 122 |
+
elem_classes=["question-box"]
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
context_input = gr.Textbox(
|
| 126 |
+
label="Additional Context (Optional)",
|
| 127 |
+
placeholder="Provide any additional context, constraints, or specific requirements...",
|
| 128 |
+
lines=2
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
with gr.Row():
|
| 132 |
+
submit_btn = gr.Button("🔍 Ask Question", variant="primary", size="lg")
|
| 133 |
+
clear_btn = gr.Button("🗑️ Clear History", variant="secondary")
|
| 134 |
+
|
| 135 |
+
with gr.Group():
|
| 136 |
+
gr.Markdown("## 📝 Response")
|
| 137 |
+
response_output = gr.Textbox(
|
| 138 |
+
label="AI Response",
|
| 139 |
+
lines=15,
|
| 140 |
+
interactive=False,
|
| 141 |
+
elem_classes=["response-box"]
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
with gr.Column(scale=1):
|
| 145 |
+
with gr.Group():
|
| 146 |
+
gr.Markdown("## ℹ️ Agent Status")
|
| 147 |
+
agent_info = gr.Markdown(get_agent_info())
|
| 148 |
+
refresh_info_btn = gr.Button("🔄 Refresh Info", size="sm")
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
gr.Markdown("""
|
| 152 |
+
---
|
| 153 |
+
### 🔧 Tips for Best Results:
|
| 154 |
+
- **Be Specific**: Include all relevant details and constraints
|
| 155 |
+
- **Multi-step Questions**: Break complex questions into clear parts
|
| 156 |
+
- **Context Matters**: Use the context field for additional information
|
| 157 |
+
- **Iterative Approach**: Build on previous questions for deeper analysis
|
| 158 |
+
""")
|
| 159 |
+
|
| 160 |
+
# Event handlers
|
| 161 |
+
submit_btn.click(
|
| 162 |
+
fn=process_question_sync,
|
| 163 |
+
inputs=[question_input, context_input],
|
| 164 |
+
outputs=[response_output]
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
clear_btn.click(
|
| 168 |
+
fn=clear_conversation,
|
| 169 |
+
outputs=[question_input, response_output]
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
refresh_info_btn.click(
|
| 173 |
+
fn=get_agent_info,
|
| 174 |
+
outputs=[agent_info]
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
# Allow Enter key to submit
|
| 178 |
+
question_input.submit(
|
| 179 |
+
fn=process_question_sync,
|
| 180 |
+
inputs=[question_input, context_input],
|
| 181 |
+
outputs=[response_output]
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
return interface
|
src/utils.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Utility functions for the Gemini AI Agent.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import re
|
| 6 |
+
import logging
|
| 7 |
+
from typing import Optional
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def sanitize_input(text: str) -> str:
|
| 14 |
+
"""
|
| 15 |
+
Sanitize user input by removing potentially harmful content.
|
| 16 |
+
|
| 17 |
+
Args:
|
| 18 |
+
text: Raw input text
|
| 19 |
+
|
| 20 |
+
Returns:
|
| 21 |
+
str: Sanitized text
|
| 22 |
+
"""
|
| 23 |
+
if not isinstance(text, str):
|
| 24 |
+
return str(text)
|
| 25 |
+
|
| 26 |
+
# Remove excessive whitespace
|
| 27 |
+
text = re.sub(r'\s+', ' ', text.strip())
|
| 28 |
+
|
| 29 |
+
# Remove potential injection attempts (basic protection)
|
| 30 |
+
text = text.replace('\\n', '\n').replace('\\t', '\t')
|
| 31 |
+
|
| 32 |
+
return text
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def format_response(response: str) -> str:
|
| 36 |
+
"""
|
| 37 |
+
Format the AI response for better readability.
|
| 38 |
+
|
| 39 |
+
Args:
|
| 40 |
+
response: Raw response from the AI model
|
| 41 |
+
|
| 42 |
+
Returns:
|
| 43 |
+
str: Formatted response
|
| 44 |
+
"""
|
| 45 |
+
if not response:
|
| 46 |
+
return "No response generated."
|
| 47 |
+
|
| 48 |
+
# Clean up the response
|
| 49 |
+
response = response.strip()
|
| 50 |
+
|
| 51 |
+
# Ensure proper spacing after periods
|
| 52 |
+
response = re.sub(r'\.([A-Z])', r'. \1', response)
|
| 53 |
+
|
| 54 |
+
# Fix common formatting issues
|
| 55 |
+
response = re.sub(r'\n\s*\n\s*\n', '\n\n', response) # Multiple newlines
|
| 56 |
+
response = re.sub(r'([.!?])\s*([A-Z])', r'\1 \2', response) # Spacing after punctuation
|
| 57 |
+
|
| 58 |
+
return response
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def truncate_text(text: str, max_length: int = 1000) -> str:
|
| 62 |
+
"""
|
| 63 |
+
Truncate text to a maximum length while preserving word boundaries.
|
| 64 |
+
|
| 65 |
+
Args:
|
| 66 |
+
text: Text to truncate
|
| 67 |
+
max_length: Maximum length allowed
|
| 68 |
+
|
| 69 |
+
Returns:
|
| 70 |
+
str: Truncated text
|
| 71 |
+
"""
|
| 72 |
+
if len(text) <= max_length:
|
| 73 |
+
return text
|
| 74 |
+
|
| 75 |
+
# Find the last space before the max_length
|
| 76 |
+
truncated = text[:max_length]
|
| 77 |
+
last_space = truncated.rfind(' ')
|
| 78 |
+
|
| 79 |
+
if last_space > max_length * 0.8: # If space is reasonably close to end
|
| 80 |
+
return truncated[:last_space] + "..."
|
| 81 |
+
else:
|
| 82 |
+
return truncated + "..."
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def extract_code_blocks(text: str) -> list:
|
| 86 |
+
"""
|
| 87 |
+
Extract code blocks from markdown-formatted text.
|
| 88 |
+
|
| 89 |
+
Args:
|
| 90 |
+
text: Text containing potential code blocks
|
| 91 |
+
|
| 92 |
+
Returns:
|
| 93 |
+
list: List of code blocks found
|
| 94 |
+
"""
|
| 95 |
+
code_pattern = r'```(\w+)?\n(.*?)\n```'
|
| 96 |
+
matches = re.findall(code_pattern, text, re.DOTALL)
|
| 97 |
+
|
| 98 |
+
return [{'language': match[0] or 'text', 'code': match[1]} for match in matches]
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def validate_question(question: str) -> tuple[bool, Optional[str]]:
|
| 102 |
+
"""
|
| 103 |
+
Validate if a question is appropriate and well-formed.
|
| 104 |
+
|
| 105 |
+
Args:
|
| 106 |
+
question: The question to validate
|
| 107 |
+
|
| 108 |
+
Returns:
|
| 109 |
+
tuple: (is_valid, error_message)
|
| 110 |
+
"""
|
| 111 |
+
if not question or not question.strip():
|
| 112 |
+
return False, "Question cannot be empty."
|
| 113 |
+
|
| 114 |
+
if len(question.strip()) < 3:
|
| 115 |
+
return False, "Question is too short. Please provide more detail."
|
| 116 |
+
|
| 117 |
+
if len(question) > 5000:
|
| 118 |
+
return False, "Question is too long. Please keep it under 5000 characters."
|
| 119 |
+
|
| 120 |
+
return True, None
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def format_error_message(error: Exception) -> str:
|
| 124 |
+
"""
|
| 125 |
+
Format error messages for user display.
|
| 126 |
+
|
| 127 |
+
Args:
|
| 128 |
+
error: The exception that occurred
|
| 129 |
+
|
| 130 |
+
Returns:
|
| 131 |
+
str: User-friendly error message
|
| 132 |
+
"""
|
| 133 |
+
error_type = type(error).__name__
|
| 134 |
+
|
| 135 |
+
# Map common errors to user-friendly messages
|
| 136 |
+
error_messages = {
|
| 137 |
+
'ConnectionError': 'Unable to connect to the AI service. Please check your internet connection.',
|
| 138 |
+
'TimeoutError': 'The request timed out. Please try again with a shorter question.',
|
| 139 |
+
'ValueError': 'Invalid input provided. Please check your question format.',
|
| 140 |
+
'KeyError': 'Configuration error. Please check your API settings.',
|
| 141 |
+
'PermissionError': 'Access denied. Please check your API key permissions.'
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
user_message = error_messages.get(error_type, f"An unexpected error occurred: {str(error)}")
|
| 145 |
+
|
| 146 |
+
logger.error(f"Error formatted for user: {error_type} - {str(error)}")
|
| 147 |
+
return user_message
|