Spaces:
Sleeping
Sleeping
| """ | |
| LLM module for generating answers using Azure OpenAI. | |
| """ | |
| import os | |
| from typing import List, Dict | |
| from openai import AzureOpenAI | |
| from dotenv import load_dotenv | |
| # Load environment variables | |
| load_dotenv() | |
| class LLMClient: | |
| """Wrapper for Azure OpenAI API.""" | |
| def __init__(self): | |
| """Initialize Azure OpenAI client.""" | |
| self.api_key = os.getenv('AZURE_OPENAI_API_KEY', '') | |
| self.endpoint = os.getenv('AZURE_OPENAI_ENDPOINT', '') | |
| self.api_version = os.getenv('AZURE_OPENAI_VERSION', '2024-02-01') | |
| self.deployment_name = os.getenv('AZURE_OPENAI_DEPLOYMENT', '') | |
| self.client = None | |
| if self.has_token(): | |
| try: | |
| self.client = AzureOpenAI( | |
| api_key=self.api_key, | |
| azure_endpoint=self.endpoint, | |
| api_version=self.api_version, | |
| ) | |
| print("✅ Azure OpenAI client initialized successfully") | |
| except Exception as e: | |
| print(f"❌ Failed to initialize Azure OpenAI client: {str(e)}") | |
| self.client = None | |
| else: | |
| print("⚠️ Azure OpenAI credentials not found. Using extractive fallback.") | |
| def has_token(self) -> bool: | |
| """Check if Azure OpenAI credentials are available.""" | |
| return bool(self.api_key and self.endpoint and self.deployment_name) | |
| def generate_answer(self, question: str, context_chunks: List[Dict], max_tokens: int = 800) -> str: | |
| """ | |
| Generate answer using Azure OpenAI with context. | |
| Args: | |
| question: User question | |
| context_chunks: List of retrieved context chunks | |
| max_tokens: Maximum response length | |
| Returns: | |
| Generated answer text | |
| """ | |
| if not context_chunks: | |
| return "No relevant context found. Please index some documents first." | |
| # Format context from chunks | |
| context_parts = [] | |
| for i, chunk in enumerate(context_chunks, 1): | |
| source = chunk.get('source', 'Unknown') | |
| text = chunk.get('text', '') | |
| context_parts.append(f"[Source {i}: {source}]\n{text}") | |
| context = "\n\n".join(context_parts) | |
| # Generate answer using Azure OpenAI | |
| if self.client: | |
| try: | |
| messages = [ | |
| { | |
| "role": "system", | |
| "content": """You are a helpful research assistant. Use ONLY the provided context to answer questions accurately and comprehensively. | |
| Guidelines: | |
| - Base your answer strictly on the provided context | |
| - If the context doesn't contain enough information, clearly state this | |
| - Cite sources when possible | |
| - Provide detailed, well-structured answers | |
| - If multiple sources contain relevant information, synthesize them coherently""" | |
| }, | |
| { | |
| "role": "user", | |
| "content": f"""Question: {question} | |
| Context: | |
| {context} | |
| Please provide a comprehensive answer based on the context above.""" | |
| } | |
| ] | |
| response = self.client.chat.completions.create( | |
| model=self.deployment_name, | |
| messages=messages, | |
| max_tokens=max_tokens, | |
| temperature=0.3, # Lower temperature for more focused answers | |
| top_p=0.9, | |
| frequency_penalty=0.1, | |
| presence_penalty=0.1 | |
| ) | |
| if response.choices and response.choices[0].message: | |
| answer = response.choices[0].message.content | |
| return answer.strip() if answer else "No answer generated." | |
| else: | |
| return "No response from Azure OpenAI." | |
| except Exception as e: | |
| error_msg = str(e) | |
| if "rate limit" in error_msg.lower(): | |
| return "⚠️ Rate limit exceeded. Please try again in a moment." | |
| elif "content filter" in error_msg.lower(): | |
| return "⚠️ Content filtered by Azure OpenAI. Please try rephrasing your question." | |
| elif "timeout" in error_msg.lower(): | |
| return "⚠️ Request timed out. Please try again." | |
| else: | |
| return f"❌ Error generating answer: {error_msg}" | |
| else: | |
| # Fallback: extractive answer from context | |
| return self._extractive_fallback(question, context_chunks) | |
| def _extractive_fallback(self, question: str, context_chunks: List[Dict]) -> str: | |
| """ | |
| Fallback extractive answer when Azure OpenAI is not available. | |
| Returns the most relevant chunk as answer. | |
| """ | |
| if not context_chunks: | |
| return "No context available. Please configure Azure OpenAI credentials for LLM generation." | |
| # Return the top chunk as answer | |
| top_chunk = context_chunks[0] | |
| source = top_chunk.get('source', 'Unknown') | |
| text = top_chunk.get('text', '') | |
| score = top_chunk.get('score', 0) | |
| answer = f"**Extractive Answer** (Relevance: {score:.3f})\n\n" | |
| answer += f"**Source:** {source}\n\n" | |
| answer += f"**Content:** {text[:800]}" | |
| if len(text) > 800: | |
| answer += "...\n\n*Note: This is an extractive answer. Configure Azure OpenAI for generated responses.*" | |
| else: | |
| answer += "\n\n*Note: This is an extractive answer. Configure Azure OpenAI for generated responses.*" | |
| return answer | |
| def test_connection(self) -> Dict[str, str]: | |
| """ | |
| Test Azure OpenAI connection. | |
| Returns: | |
| Dictionary with status and message | |
| """ | |
| if not self.has_token(): | |
| return { | |
| "status": "error", | |
| "message": "Missing Azure OpenAI credentials. Please check environment variables." | |
| } | |
| if not self.client: | |
| return { | |
| "status": "error", | |
| "message": "Azure OpenAI client not initialized." | |
| } | |
| try: | |
| # Test with a simple query | |
| response = self.client.chat.completions.create( | |
| model=self.deployment_name, | |
| messages=[{"role": "user", "content": "Hello, are you working?"}], | |
| max_tokens=10, | |
| temperature=0.1 | |
| ) | |
| if response.choices and response.choices[0].message: | |
| return { | |
| "status": "success", | |
| "message": "Azure OpenAI connection successful!" | |
| } | |
| else: | |
| return { | |
| "status": "error", | |
| "message": "No response from Azure OpenAI." | |
| } | |
| except Exception as e: | |
| return { | |
| "status": "error", | |
| "message": f"Connection test failed: {str(e)}" | |
| } |