Deltacorvi commited on
Commit
7224b0b
·
verified ·
1 Parent(s): 86379d4

Upload 11 files

Browse files
__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