.
This commit is contained in:
285
src/capa_de_integracion/services/dialogflow_client.py
Normal file
285
src/capa_de_integracion/services/dialogflow_client.py
Normal file
@@ -0,0 +1,285 @@
|
||||
"""
|
||||
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")
|
||||
Reference in New Issue
Block a user