Files
int-layer/src/capa_de_integracion/services/dialogflow_client.py
2026-02-20 08:42:45 +00:00

286 lines
9.3 KiB
Python

"""
Copyright 2025 Google. This software is provided as-is, without warranty or
representation for any use or purpose. Your use of it is subject to your
agreement with Google.
Dialogflow CX client service for intent detection.
"""
import logging
from google.cloud.dialogflowcx_v3 import SessionsAsyncClient
from google.cloud.dialogflowcx_v3.types import (
DetectIntentRequest,
QueryInput,
TextInput,
EventInput,
QueryParameters,
)
from google.api_core.exceptions import (
GoogleAPIError,
InternalServerError,
ServiceUnavailable,
)
from tenacity import (
retry,
stop_after_attempt,
wait_exponential,
retry_if_exception_type,
)
from ..config import Settings
from ..models import DetectIntentRequestDTO, DetectIntentResponseDTO, QueryResultDTO
logger = logging.getLogger(__name__)
class DialogflowClientService:
"""Service for interacting with Dialogflow CX API."""
def __init__(self, settings: Settings):
"""Initialize Dialogflow client."""
self.settings = settings
self.project_id = settings.dialogflow_project_id
self.location = settings.dialogflow_location
self.agent_id = settings.dialogflow_agent_id
self.default_language = settings.dialogflow_default_language
# Initialize async client
endpoint = settings.dialogflow_endpoint
client_options = {"api_endpoint": endpoint}
self.client = SessionsAsyncClient(client_options=client_options)
logger.info(
f"Dialogflow CX SessionsClient initialized for endpoint: {endpoint}"
)
logger.info(f"Agent ID: {self.agent_id}")
def _build_session_path(self, session_id: str) -> str:
"""Build Dialogflow session path."""
return self.client.session_path(
project=self.project_id,
location=self.location,
agent=self.agent_id,
session=session_id,
)
def _map_query_input(self, query_input_dto) -> QueryInput:
"""Map QueryInputDTO to Dialogflow QueryInput."""
language_code = query_input_dto.language_code or self.default_language
if query_input_dto.text and query_input_dto.text.text:
return QueryInput(
text=TextInput(text=query_input_dto.text.text),
language_code=language_code,
)
elif query_input_dto.event and query_input_dto.event.event:
return QueryInput(
event=EventInput(event=query_input_dto.event.event),
language_code=language_code,
)
else:
raise ValueError("Either text or event input must be provided")
def _map_query_params(self, query_params_dto) -> QueryParameters | None:
"""Map QueryParamsDTO to Dialogflow QueryParameters."""
if not query_params_dto or not query_params_dto.parameters:
return None
return QueryParameters(parameters=query_params_dto.parameters)
def _extract_response_text(self, response) -> str:
"""Extract text from Dialogflow response messages."""
texts = []
for msg in response.query_result.response_messages:
if hasattr(msg, "text") and msg.text.text:
texts.extend(msg.text.text)
return " ".join(texts) if texts else ""
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=1, max=10),
retry=retry_if_exception_type((InternalServerError, ServiceUnavailable)),
reraise=True,
)
async def detect_intent(
self, session_id: str, request_dto: DetectIntentRequestDTO
) -> DetectIntentResponseDTO:
"""
Detect intent from user input using Dialogflow CX.
Args:
session_id: Unique session identifier
request_dto: Detect intent request
Returns:
Detect intent response with query results
Raises:
GoogleAPIError: If Dialogflow API call fails
"""
if not session_id:
raise ValueError("Session ID cannot be empty")
if not request_dto:
raise ValueError("Request DTO cannot be None")
logger.info(f"Initiating detectIntent for session: {session_id}")
try:
# Build request
session_path = self._build_session_path(session_id)
query_input = self._map_query_input(request_dto.query_input)
query_params = self._map_query_params(request_dto.query_params)
detect_request = DetectIntentRequest(
session=session_path,
query_input=query_input,
query_params=query_params,
)
# Call Dialogflow
logger.debug(
f"Calling Dialogflow CX detectIntent for session: {session_id}"
)
response = await self.client.detect_intent(request=detect_request)
# Extract response data
query_result = response.query_result
response_text = self._extract_response_text(response)
# Map to DTO
query_result_dto = QueryResultDTO(
responseText=response_text,
parameters=dict(query_result.parameters)
if query_result.parameters
else None,
)
result = DetectIntentResponseDTO(
responseId=response.response_id,
queryResult=query_result_dto,
)
logger.info(
f"Successfully processed detectIntent for session: {session_id}"
)
return result
except GoogleAPIError as e:
logger.error(
f"Dialogflow CX API error for session {session_id}: {e.message}",
exc_info=True,
)
raise
except Exception as e:
logger.error(
f"Unexpected error in detectIntent for session {session_id}: {str(e)}",
exc_info=True,
)
raise
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=1, max=10),
retry=retry_if_exception_type((InternalServerError, ServiceUnavailable)),
reraise=True,
)
async def detect_intent_event(
self,
session_id: str,
event_name: str,
parameters: dict | None = None,
language_code: str | None = None,
) -> DetectIntentResponseDTO:
"""
Trigger Dialogflow event detection.
Used for notification events and system-triggered flows.
Args:
session_id: Unique session identifier
event_name: Dialogflow event name (e.g., "notificacion")
parameters: Event parameters
language_code: Language code (defaults to settings)
Returns:
Detect intent response
Raises:
GoogleAPIError: If Dialogflow API call fails
"""
if not session_id:
raise ValueError("Session ID cannot be empty")
if not event_name:
raise ValueError("Event name cannot be empty")
lang_code = language_code or self.default_language
logger.info(
f"Triggering Dialogflow event '{event_name}' for session: {session_id}"
)
try:
# Build request
session_path = self._build_session_path(session_id)
query_input = QueryInput(
event=EventInput(event=event_name),
language_code=lang_code,
)
query_params = None
if parameters:
query_params = QueryParameters(parameters=parameters)
detect_request = DetectIntentRequest(
session=session_path,
query_input=query_input,
query_params=query_params,
)
# Call Dialogflow
logger.debug(
f"Calling Dialogflow CX for event '{event_name}' in session: {session_id}"
)
response = await self.client.detect_intent(request=detect_request)
# Extract response data
query_result = response.query_result
response_text = self._extract_response_text(response)
# Map to DTO
query_result_dto = QueryResultDTO(
responseText=response_text,
parameters=dict(query_result.parameters)
if query_result.parameters
else None,
)
result = DetectIntentResponseDTO(
responseId=response.response_id,
queryResult=query_result_dto,
)
logger.info(
f"Successfully processed event '{event_name}' for session: {session_id}"
)
return result
except GoogleAPIError as e:
logger.error(
f"Dialogflow CX API error for event '{event_name}' in session {session_id}: {e.message}",
exc_info=True,
)
raise
except Exception as e:
logger.error(
f"Unexpected error triggering event '{event_name}' for session {session_id}: {str(e)}",
exc_info=True,
)
raise
async def close(self):
"""Close the Dialogflow client."""
await self.client.transport.close()
logger.info("Dialogflow CX SessionsClient closed")