Pranesh64's picture
Create backend/llm.py
170ccaf verified
"""
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)}"
}