This commit is contained in:
2026-02-20 04:38:32 +00:00
parent 14ed21a1f9
commit bcdc41ecd5
20 changed files with 309 additions and 283 deletions

View File

@@ -1,21 +1,21 @@
import logging
from uuid import uuid4
from datetime import datetime, timedelta
from uuid import uuid4
from ..config import Settings
from ..models import (
from capa_de_integracion.config import Settings
from capa_de_integracion.models import (
ConversationEntry,
ConversationRequest,
ConversationSession,
DetectIntentResponse,
QueryResult,
ConversationSession,
ConversationEntry,
)
from .redis_service import RedisService
from .firestore_service import FirestoreService
from .dlp_service import DLPService
from .rag_service import RAGService
from .quick_reply_content import QuickReplyContentService
from .dlp_service import DLPService
from .firestore_service import FirestoreService
from .quick_reply_content import QuickReplyContentService
from .rag_service import RAGService
from .redis_service import RedisService
logger = logging.getLogger(__name__)
@@ -35,7 +35,7 @@ class ConversationManagerService:
redis_service: RedisService,
firestore_service: FirestoreService,
dlp_service: DLPService,
):
) -> None:
"""Initialize conversation manager."""
self.settings = settings
self.rag_service = rag_service
@@ -47,16 +47,16 @@ class ConversationManagerService:
logger.info("ConversationManagerService initialized successfully")
async def manage_conversation(
self, request: ConversationRequest
self, request: ConversationRequest,
) -> DetectIntentResponse:
"""
Main entry point for managing conversations.
"""Main entry point for managing conversations.
Args:
request: External conversation request from client
Returns:
Detect intent response from Dialogflow
"""
try:
# Step 1: DLP obfuscation
@@ -75,7 +75,7 @@ class ConversationManagerService:
session_id = str(uuid4())
user_id = f"user_by_phone_{telefono.replace(' ', '').replace('-', '')}"
session = await self.firestore_service.create_session(
session_id, user_id, telefono
session_id, user_id, telefono,
)
await self.redis_service.save_session(session)
@@ -85,10 +85,10 @@ class ConversationManagerService:
if self._is_pantalla_context_valid(session.lastModified):
logger.info(
f"Detected 'pantallaContexto' in session: {session.pantallaContexto}. "
f"Delegating to QuickReplies flow."
f"Delegating to QuickReplies flow.",
)
response = await self._manage_quick_reply_conversation(
request, session.pantallaContexto
request, session.pantallaContexto,
)
if response:
# Save user message to Firestore
@@ -101,7 +101,7 @@ class ConversationManagerService:
canal=getattr(request, "canal", None),
)
await self.firestore_service.save_entry(
session.sessionId, user_entry
session.sessionId, user_entry,
)
# Save quick reply response to Firestore
@@ -119,7 +119,7 @@ class ConversationManagerService:
canal=getattr(request, "canal", None),
)
await self.firestore_service.save_entry(
session.sessionId, assistant_entry
session.sessionId, assistant_entry,
)
# Update session with last message and timestamp
@@ -131,14 +131,14 @@ class ConversationManagerService:
return response
else:
logger.info(
"Detected STALE 'pantallaContexto'. Ignoring and proceeding with normal flow."
"Detected STALE 'pantallaContexto'. Ignoring and proceeding with normal flow.",
)
# Step 3: Continue with standard conversation flow
nickname = request.usuario.nickname
logger.info(
f"Primary Check (Redis): Looking up session for phone: {telefono}"
f"Primary Check (Redis): Looking up session for phone: {telefono}",
)
# Step 3a: Load conversation history from Firestore
@@ -165,7 +165,7 @@ class ConversationManagerService:
logger.info("Sending query to RAG service")
assistant_response = await self.rag_service.query(messages)
logger.info(
f"Received response from RAG service: {assistant_response[:100]}..."
f"Received response from RAG service: {assistant_response[:100]}...",
)
# Step 3e: Save user message to Firestore
@@ -205,7 +205,7 @@ class ConversationManagerService:
logger.info(f"Marked {len(notifications)} notifications as processed")
# Step 3i: Return response object
response = DetectIntentResponse(
return DetectIntentResponse(
responseId=str(uuid4()),
queryResult=QueryResult(
responseText=assistant_response,
@@ -214,10 +214,9 @@ class ConversationManagerService:
quick_replies=None,
)
return response
except Exception as e:
logger.error(f"Error managing conversation: {str(e)}", exc_info=True)
logger.error(f"Error managing conversation: {e!s}", exc_info=True)
raise
def _is_pantalla_context_valid(self, last_modified: datetime) -> bool:
@@ -252,17 +251,16 @@ class ConversationManagerService:
# If no match, use first question as default or delegate to normal flow
if not matched_answer:
logger.warning(
f"No matching quick reply found for message: '{request.mensaje}'."
f"No matching quick reply found for message: '{request.mensaje}'.",
)
# Create response with the matched quick reply answer
response = DetectIntentResponse(
return DetectIntentResponse(
responseId=str(uuid4()),
queryResult=QueryResult(responseText=matched_answer, parameters=None),
quick_replies=quick_reply_screen,
)
return response
async def _get_active_notifications(self, telefono: str) -> list:
"""Retrieve active notifications for a user from Redis or Firestore.
@@ -272,20 +270,21 @@ class ConversationManagerService:
Returns:
List of active Notification objects
"""
try:
# Try Redis first
notification_session = await self.redis_service.get_notification_session(
telefono
telefono,
)
# If not in Redis, try Firestore
if not notification_session:
# Firestore uses phone as document ID for notifications
from ..models.notification import NotificationSession
from capa_de_integracion.models.notification import NotificationSession
doc_ref = self.firestore_service.db.collection(
self.firestore_service.notifications_collection
self.firestore_service.notifications_collection,
).document(telefono)
doc = await doc_ref.get()
@@ -295,18 +294,17 @@ class ConversationManagerService:
# Filter for active notifications only
if notification_session and notification_session.notificaciones:
active_notifications = [
return [
notif
for notif in notification_session.notificaciones
if notif.status == "active"
]
return active_notifications
return []
except Exception as e:
logger.error(
f"Error retrieving notifications for {telefono}: {str(e)}",
f"Error retrieving notifications for {telefono}: {e!s}",
exc_info=True,
)
return []
@@ -330,6 +328,7 @@ class ConversationManagerService:
Returns:
List of messages in OpenAI format [{"role": "...", "content": "..."}]
"""
messages = []
@@ -341,7 +340,7 @@ class ConversationManagerService:
{
"role": "system",
"content": f"Historial de conversación:\n{conversation_context}",
}
},
)
# Add system message with notifications if available
@@ -355,7 +354,7 @@ class ConversationManagerService:
{
"role": "system",
"content": f"Notificaciones pendientes para el usuario:\n{notifications_text}",
}
},
)
# Add system message with user context
@@ -372,11 +371,12 @@ class ConversationManagerService:
Args:
telefono: User phone number
"""
try:
# Update status in Firestore
await self.firestore_service.update_notification_status(
telefono, "processed"
telefono, "processed",
)
# Update or delete from Redis
@@ -386,7 +386,7 @@ class ConversationManagerService:
except Exception as e:
logger.error(
f"Error marking notifications as processed for {telefono}: {str(e)}",
f"Error marking notifications as processed for {telefono}: {e!s}",
exc_info=True,
)
@@ -408,13 +408,14 @@ class ConversationManagerService:
Returns:
Formatted conversation text
"""
if not entries:
return ""
# Filter by date (30 days)
cutoff_date = datetime.now() - timedelta(
days=self.settings.conversation_context_days_limit
days=self.settings.conversation_context_days_limit,
)
recent_entries = [
e for e in entries if e.timestamp and e.timestamp >= cutoff_date
@@ -439,6 +440,7 @@ class ConversationManagerService:
Returns:
Formatted text, truncated if necessary
"""
if not entries:
return ""
@@ -470,6 +472,7 @@ class ConversationManagerService:
Returns:
Formatted string (e.g., "User: hello", "Assistant: hi there")
"""
# Map entity to prefix (fixed bug from Java port!)
prefix = "User: " if entry.entity == "user" else "Assistant: "