Fix lint errors
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
"""Conversation manager service for orchestrating user conversations."""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
import re
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from uuid import uuid4
|
||||
|
||||
from capa_de_integracion.config import Settings
|
||||
@@ -10,6 +13,7 @@ from capa_de_integracion.models import (
|
||||
DetectIntentResponse,
|
||||
QueryResult,
|
||||
)
|
||||
from capa_de_integracion.models.notification import NotificationSession
|
||||
|
||||
from .dlp_service import DLPService
|
||||
from .firestore_service import FirestoreService
|
||||
@@ -46,10 +50,10 @@ class ConversationManagerService:
|
||||
|
||||
logger.info("ConversationManagerService initialized successfully")
|
||||
|
||||
async def manage_conversation(
|
||||
async def manage_conversation( # noqa: PLR0915
|
||||
self, request: ConversationRequest,
|
||||
) -> DetectIntentResponse:
|
||||
"""Main entry point for managing conversations.
|
||||
"""Manage conversation flow and return response.
|
||||
|
||||
Args:
|
||||
request: External conversation request from client
|
||||
@@ -82,10 +86,11 @@ class ConversationManagerService:
|
||||
# Step 2: Check for pantallaContexto in existing session
|
||||
if session.pantallaContexto:
|
||||
# Check if pantallaContexto is stale (10 minutes)
|
||||
if self._is_pantalla_context_valid(session.lastModified):
|
||||
if self._is_pantalla_context_valid(session.last_modified):
|
||||
logger.info(
|
||||
f"Detected 'pantallaContexto' in session: {session.pantallaContexto}. "
|
||||
f"Delegating to QuickReplies flow.",
|
||||
"Detected 'pantallaContexto' in session: %s. "
|
||||
"Delegating to QuickReplies flow.",
|
||||
session.pantallaContexto,
|
||||
)
|
||||
response = await self._manage_quick_reply_conversation(
|
||||
request, session.pantallaContexto,
|
||||
@@ -95,62 +100,64 @@ class ConversationManagerService:
|
||||
user_entry = ConversationEntry(
|
||||
entity="user",
|
||||
type="CONVERSACION",
|
||||
timestamp=datetime.now(),
|
||||
timestamp=datetime.now(UTC),
|
||||
text=request.mensaje,
|
||||
parameters=None,
|
||||
canal=getattr(request, "canal", None),
|
||||
)
|
||||
await self.firestore_service.save_entry(
|
||||
session.sessionId, user_entry,
|
||||
session.session_id, user_entry,
|
||||
)
|
||||
|
||||
# Save quick reply response to Firestore
|
||||
response_text = (
|
||||
response.queryResult.responseText
|
||||
if response.queryResult
|
||||
response.query_result.response_text
|
||||
if response.query_result
|
||||
else ""
|
||||
) or ""
|
||||
assistant_entry = ConversationEntry(
|
||||
entity="assistant",
|
||||
type="CONVERSACION",
|
||||
timestamp=datetime.now(),
|
||||
timestamp=datetime.now(UTC),
|
||||
text=response_text,
|
||||
parameters=None,
|
||||
canal=getattr(request, "canal", None),
|
||||
)
|
||||
await self.firestore_service.save_entry(
|
||||
session.sessionId, assistant_entry,
|
||||
session.session_id, assistant_entry,
|
||||
)
|
||||
|
||||
# Update session with last message and timestamp
|
||||
session.lastMessage = response_text
|
||||
session.lastModified = datetime.now()
|
||||
session.last_message = response_text
|
||||
session.last_modified = datetime.now(UTC)
|
||||
await self.firestore_service.save_session(session)
|
||||
await self.redis_service.save_session(session)
|
||||
|
||||
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}",
|
||||
"Primary Check (Redis): Looking up session for phone: %s",
|
||||
telefono,
|
||||
)
|
||||
|
||||
# Step 3a: Load conversation history from Firestore
|
||||
entries = await self.firestore_service.get_entries(
|
||||
session.sessionId,
|
||||
session.session_id,
|
||||
limit=self.settings.conversation_context_message_limit,
|
||||
)
|
||||
logger.info(f"Loaded {len(entries)} conversation entries from Firestore")
|
||||
logger.info("Loaded %s conversation entries from Firestore", len(entries))
|
||||
|
||||
# Step 3b: Retrieve active notifications for this user
|
||||
notifications = await self._get_active_notifications(telefono)
|
||||
logger.info(f"Retrieved {len(notifications)} active notifications")
|
||||
logger.info("Retrieved %s active notifications", len(notifications))
|
||||
|
||||
# Step 3c: Prepare context for RAG service
|
||||
messages = await self._prepare_rag_messages(
|
||||
@@ -165,36 +172,37 @@ 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]}...",
|
||||
"Received response from RAG service: %s...",
|
||||
assistant_response[:100],
|
||||
)
|
||||
|
||||
# Step 3e: Save user message to Firestore
|
||||
user_entry = ConversationEntry(
|
||||
entity="user",
|
||||
type="CONVERSACION",
|
||||
timestamp=datetime.now(),
|
||||
timestamp=datetime.now(UTC),
|
||||
text=request.mensaje,
|
||||
parameters=None,
|
||||
canal=getattr(request, "canal", None),
|
||||
)
|
||||
await self.firestore_service.save_entry(session.sessionId, user_entry)
|
||||
await self.firestore_service.save_entry(session.session_id, user_entry)
|
||||
logger.info("Saved user message to Firestore")
|
||||
|
||||
# Step 3f: Save assistant response to Firestore
|
||||
assistant_entry = ConversationEntry(
|
||||
entity="assistant",
|
||||
type="LLM",
|
||||
timestamp=datetime.now(),
|
||||
timestamp=datetime.now(UTC),
|
||||
text=assistant_response,
|
||||
parameters=None,
|
||||
canal=getattr(request, "canal", None),
|
||||
)
|
||||
await self.firestore_service.save_entry(session.sessionId, assistant_entry)
|
||||
await self.firestore_service.save_entry(session.session_id, assistant_entry)
|
||||
logger.info("Saved assistant response to Firestore")
|
||||
|
||||
# Step 3g: Update session with last message and timestamp
|
||||
session.lastMessage = assistant_response
|
||||
session.lastModified = datetime.now()
|
||||
session.last_message = assistant_response
|
||||
session.last_modified = datetime.now(UTC)
|
||||
await self.firestore_service.save_session(session)
|
||||
await self.redis_service.save_session(session)
|
||||
logger.info("Updated session in Firestore and Redis")
|
||||
@@ -202,26 +210,26 @@ class ConversationManagerService:
|
||||
# Step 3h: Mark notifications as processed if any were included
|
||||
if notifications:
|
||||
await self._mark_notifications_as_processed(telefono)
|
||||
logger.info(f"Marked {len(notifications)} notifications as processed")
|
||||
logger.info("Marked %s notifications as processed", len(notifications))
|
||||
|
||||
# Step 3i: Return response object
|
||||
return DetectIntentResponse(
|
||||
responseId=str(uuid4()),
|
||||
queryResult=QueryResult(
|
||||
responseText=assistant_response,
|
||||
response_id=str(uuid4()),
|
||||
query_result=QueryResult(
|
||||
response_text=assistant_response,
|
||||
parameters=None,
|
||||
),
|
||||
quick_replies=None,
|
||||
)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error managing conversation: {e!s}", exc_info=True)
|
||||
except Exception:
|
||||
logger.exception("Error managing conversation")
|
||||
raise
|
||||
|
||||
def _is_pantalla_context_valid(self, last_modified: datetime) -> bool:
|
||||
"""Check if pantallaContexto is still valid (not stale)."""
|
||||
time_diff = datetime.now() - last_modified
|
||||
time_diff = datetime.now(UTC) - last_modified
|
||||
return time_diff < timedelta(minutes=self.SCREEN_CONTEXT_TIMEOUT_MINUTES)
|
||||
|
||||
async def _manage_quick_reply_conversation(
|
||||
@@ -234,7 +242,7 @@ class ConversationManagerService:
|
||||
|
||||
# If no questions available, delegate to normal conversation flow
|
||||
if not quick_reply_screen.preguntas:
|
||||
logger.warning(f"No quick replies found for screen: {screen_id}.")
|
||||
logger.warning("No quick replies found for screen: %s.", screen_id)
|
||||
return None
|
||||
|
||||
# Match user message to a quick reply question
|
||||
@@ -245,19 +253,20 @@ class ConversationManagerService:
|
||||
# Simple matching: check if question title matches user message
|
||||
if pregunta.titulo.lower().strip() == user_message_lower:
|
||||
matched_answer = pregunta.respuesta
|
||||
logger.info(f"Matched quick reply: {pregunta.titulo}")
|
||||
logger.info("Matched quick reply: %s", pregunta.titulo)
|
||||
break
|
||||
|
||||
# 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}'.",
|
||||
"No matching quick reply found for message: '%s'.",
|
||||
request.mensaje,
|
||||
)
|
||||
|
||||
# Create response with the matched quick reply answer
|
||||
return DetectIntentResponse(
|
||||
responseId=str(uuid4()),
|
||||
queryResult=QueryResult(responseText=matched_answer, parameters=None),
|
||||
response_id=str(uuid4()),
|
||||
query_result=QueryResult(response_text=matched_answer, parameters=None),
|
||||
quick_replies=quick_reply_screen,
|
||||
)
|
||||
|
||||
@@ -281,8 +290,6 @@ class ConversationManagerService:
|
||||
# If not in Redis, try Firestore
|
||||
if not notification_session:
|
||||
# Firestore uses phone as document ID for notifications
|
||||
from capa_de_integracion.models.notification import NotificationSession
|
||||
|
||||
doc_ref = self.firestore_service.db.collection(
|
||||
self.firestore_service.notifications_collection,
|
||||
).document(telefono)
|
||||
@@ -294,20 +301,19 @@ class ConversationManagerService:
|
||||
|
||||
# Filter for active notifications only
|
||||
if notification_session and notification_session.notificaciones:
|
||||
return [
|
||||
active_notifications = [
|
||||
notif
|
||||
for notif in notification_session.notificaciones
|
||||
if notif.status == "active"
|
||||
]
|
||||
else:
|
||||
active_notifications = []
|
||||
|
||||
except Exception:
|
||||
logger.exception("Error retrieving notifications for %s", telefono)
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error retrieving notifications for {telefono}: {e!s}",
|
||||
exc_info=True,
|
||||
)
|
||||
return []
|
||||
else:
|
||||
return active_notifications
|
||||
|
||||
async def _prepare_rag_messages(
|
||||
self,
|
||||
@@ -339,7 +345,10 @@ class ConversationManagerService:
|
||||
messages.append(
|
||||
{
|
||||
"role": "system",
|
||||
"content": f"Historial de conversación:\n{conversation_context}",
|
||||
"content": (
|
||||
f"Historial de conversación:\n"
|
||||
f"{conversation_context}"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -353,7 +362,10 @@ class ConversationManagerService:
|
||||
messages.append(
|
||||
{
|
||||
"role": "system",
|
||||
"content": f"Notificaciones pendientes para el usuario:\n{notifications_text}",
|
||||
"content": (
|
||||
f"Notificaciones pendientes para el usuario:\n"
|
||||
f"{notifications_text}"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -382,17 +394,17 @@ class ConversationManagerService:
|
||||
# Update or delete from Redis
|
||||
await self.redis_service.delete_notification_session(telefono)
|
||||
|
||||
logger.info(f"Marked notifications as processed for {telefono}")
|
||||
logger.info("Marked notifications as processed for %s", telefono)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error marking notifications as processed for {telefono}: {e!s}",
|
||||
exc_info=True,
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Error marking notifications as processed for %s",
|
||||
telefono,
|
||||
)
|
||||
|
||||
def _format_conversation_history(
|
||||
self,
|
||||
session: ConversationSession,
|
||||
session: ConversationSession, # noqa: ARG002
|
||||
entries: list[ConversationEntry],
|
||||
) -> str:
|
||||
"""Format conversation history with business rule limits.
|
||||
@@ -414,7 +426,7 @@ class ConversationManagerService:
|
||||
return ""
|
||||
|
||||
# Filter by date (30 days)
|
||||
cutoff_date = datetime.now() - timedelta(
|
||||
cutoff_date = datetime.now(UTC) - timedelta(
|
||||
days=self.settings.conversation_context_days_limit,
|
||||
)
|
||||
recent_entries = [
|
||||
@@ -445,7 +457,7 @@ class ConversationManagerService:
|
||||
if not entries:
|
||||
return ""
|
||||
|
||||
MAX_BYTES = 50 * 1024 # 50KB
|
||||
max_bytes = 50 * 1024 # 50KB
|
||||
formatted_messages = [self._format_entry(entry) for entry in entries]
|
||||
|
||||
# Build from newest to oldest
|
||||
@@ -456,7 +468,7 @@ class ConversationManagerService:
|
||||
message_line = message + "\n"
|
||||
message_bytes = len(message_line.encode("utf-8"))
|
||||
|
||||
if current_size + message_bytes > MAX_BYTES:
|
||||
if current_size + message_bytes > max_bytes:
|
||||
break
|
||||
|
||||
text_block.insert(0, message_line)
|
||||
@@ -481,8 +493,6 @@ class ConversationManagerService:
|
||||
content = entry.text
|
||||
if entry.entity == "assistant":
|
||||
# Remove trailing JSON artifacts like {...}
|
||||
import re
|
||||
|
||||
content = re.sub(r"\s*\{.*\}\s*$", "", content).strip()
|
||||
|
||||
return prefix + content
|
||||
|
||||
Reference in New Issue
Block a user