import logging from typing import List, Dict, Any from sqlalchemy.orm import Session from src.database.models import VerifiedNews from src.analysis.llm_analyzer import LLMAnalyzer import openai from src.config.settings import OPENAI_API_KEY from loguru import logger # logger = logging.getLogger(__name__) class NewsChatEngine: def __init__(self): self.api_key = OPENAI_API_KEY if self.api_key: self.client = openai.OpenAI(api_key=self.api_key) else: self.client = None def get_response(self, session: Session, query: str) -> str: """ Simple RAG: Find relevant news and answer based on it. """ if not self.client: return "I'm sorry, I cannot answer questions right now as no AI API key is configured." # 1. Search DB for relevant keywords (Naive search for now) # In a real app, use Vector Search (FAISS) which is in requirements keywords = query.split() if not keywords: results = session.query(VerifiedNews).order_by(VerifiedNews.published_at.desc()).limit(3).all() else: # Simple keyword search with multiple words filters = [VerifiedNews.title.contains(k) | VerifiedNews.content.contains(k) for k in keywords[:3]] from sqlalchemy import or_ results = session.query(VerifiedNews).filter(or_(*filters)).limit(5).all() if not results: # Fallback to general latest news results = session.query(VerifiedNews).order_by(VerifiedNews.published_at.desc()).limit(3).all() context = "\n---\n".join([ f"Title: {n.title}\nSummary: {n.summary_bullets}\nWhy it matters: {n.why_it_matters}\nWho is affected: {n.who_is_affected}" for n in results ]) system_prompt = """ You are a Conversational News Intelligence Assistant. Answer user questions ONLY based on the provided news context. Always cite the source/title. CRITICAL SAFETY RULES: 1. NEVER claim absolute accuracy. 2. NO hallucinated facts. If information is missing, say so politely. 3. Maintain a neutral, factual tone. 4. If the information is not in the context, state "Based on current data, I do not have information on this." """ user_prompt = f"User Question: {query}\n\nContext:\n{context}" try: # Check if API key is a placeholder if not self.api_key or self.api_key.startswith("your_"): return self._mock_response(query, results) response = self.client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.4 ) return response.choices[0].message.content except Exception as e: logger.error(f"Chat failed: {e}") return self._mock_response(query, results) def chat_with_article(self, session: Session, article_id: int, query: str) -> str: """ Focused chat about a single article. """ if not self.client: return "I'm sorry, I cannot answer questions right now as no AI API key is configured." article = session.query(VerifiedNews).filter(VerifiedNews.id == article_id).first() if not article: return "I couldn't find the article you're referring to." context = f""" Title: {article.title} Summary: {article.summary_bullets} Why it matters: {article.why_it_matters} Who is affected: {article.who_is_affected} Full Content: {article.content[:4000]} """ system_prompt = """ You are an AI News Analyst. Answer the user's question explicitly based on the provided article context. If the information is not present, say so. Be concise, professional, and helpful. """ user_prompt = f"Article Context:\n{context}\n\nUser Question: {query}" try: # Check for mock mode if not self.api_key or self.api_key.startswith("your_"): return f"Based on the article '{article.title}', here is a simulated response to your question: '{query}'. (Real-time AI is in mock mode)." response = self.client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.3 ) return response.choices[0].message.content except Exception as e: logger.error(f"Article chat failed: {e}") if "quota" in str(e).lower() or "429" in str(e): return "The AI Analysis node has reached its usage limit for today. Please try again tomorrow or upgrade your plan." return f"I encountered an error while processing your request. Please try again later." def _mock_response(self, query: str, results: List[VerifiedNews]) -> str: """ Fallback response using keyword matching and context. """ if not results: return "I couldn't find any news related to your query. Try asking something else!" response = f"I found several relevant stories in our system:\n\n" for n in results: response += f"• {n.title} (Category: {n.category})\n" response += f" - Impact: {n.why_it_matters}\n" response += f" - Affected: {n.who_is_affected}\n\n" response += "Note: Real-time conversational analysis is currently in mock mode due to API configuration." return response