Compare commits

..

11 Commits

Author SHA1 Message Date
383efed319 Optimization 2026-02-20 20:38:59 +00:00
Anibal Angulo
ade4689ab7 Add locustfile 2026-02-20 20:38:59 +00:00
3a796dd966 Fix critical bugs 2026-02-20 20:38:59 +00:00
e5ff673a54 Misc improvements 2026-02-20 20:38:59 +00:00
fd6b698077 Improve coverage 2026-02-20 20:38:59 +00:00
f848bbf0f2 Add echo client to app 2026-02-20 20:38:59 +00:00
e03747f526 Add test coverage 2026-02-20 20:38:59 +00:00
b86dfe7373 Create rag-client package 2026-02-20 20:38:59 +00:00
d663394106 Fix type errors 2026-02-20 20:38:58 +00:00
595abd6cd3 Fix lint errors 2026-02-20 20:38:58 +00:00
faa04a0d01 Initial Python rewrite 2026-02-20 20:38:58 +00:00
350 changed files with 11270 additions and 11805 deletions

39
.env.example Normal file
View File

@@ -0,0 +1,39 @@
# GCP Configuration
GCP_PROJECT_ID=your-project-id
GCP_LOCATION=us-central1
# Firestore Configuration
GCP_FIRESTORE_DATABASE_ID=your-database-id
GCP_FIRESTORE_HOST=firestore.googleapis.com
GCP_FIRESTORE_PORT=443
GCP_FIRESTORE_IMPORTER_ENABLE=false
# Redis/Memorystore Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PWD=
# Dialogflow CX Configuration
DIALOGFLOW_CX_PROJECT_ID=your-dialogflow-project
DIALOGFLOW_CX_LOCATION=us-central1
DIALOGFLOW_CX_AGENT_ID=your-agent-id
DIALOGFLOW_DEFAULT_LANGUAGE_CODE=es
# Gemini Configuration
GEMINI_MODEL_NAME=gemini-2.0-flash-exp
# Message Filter Configuration
MESSAGE_FILTER_GEMINI_MODEL=gemini-2.0-flash-exp
MESSAGE_FILTER_TEMPERATURE=0.2
MESSAGE_FILTER_MAX_OUTPUT_TOKENS=8192
MESSAGE_FILTER_TOP_P=0.95
# DLP Configuration
DLP_TEMPLATE_COMPLETE_FLOW=your-dlp-template
# Conversation Context Configuration
CONVERSATION_CONTEXT_MESSAGE_LIMIT=10
CONVERSATION_CONTEXT_DAYS_LIMIT=30
# Logging Configuration
LOGGING_LEVEL_ROOT=INFO

216
.gitignore vendored
View File

@@ -1,2 +1,218 @@
.env .env
.ipynb_checkpoints .ipynb_checkpoints
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
# Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
# poetry.lock
# poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
# pdm.lock
# pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
# pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# Redis
*.rdb
*.aof
*.pid
# RabbitMQ
mnesia/
rabbitmq/
rabbitmq-data/
# ActiveMQ
activemq-data/
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
# .idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# Streamlit
.streamlit/secrets.toml

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.12

2
CLAUDE.md Normal file
View File

@@ -0,0 +1,2 @@
Use `uv` for project management
Run `uv run ruff check` for linting, `uv run ty check` for type-checking

View File

@@ -1,15 +0,0 @@
# Java 21.0.6
# 'jammy' refers to Ubuntu 22.04 LTS, which is a stable and widely used base.
# FROM maven:3.9.6-eclipse-temurin-21 AS builder
# FROM quay.ocp.banorte.com/base/openjdk-21:maven_3.8 AS builder
# WORKDIR /app
# COPY pom.xml .
# COPY src ./src
# RUN mvn -B clean install -DskipTests -Dmaven.javadoc.skip=true
# FROM eclipse-temurin:21.0.3_9-jre-jammy
FROM quay.ocp.banorte.com/golden/openjdk-21:latest
# COPY --from=builder /app/target/app-jovenes-service-orchestrator-0.0.1-SNAPSHOT.jar app.jar
COPY target/app-jovenes-service-orchestrator-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

25
Dockerfile.python Normal file
View File

@@ -0,0 +1,25 @@
FROM python:3.12-slim
WORKDIR /app
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Copy dependency files
COPY pyproject.toml uv.lock ./
# Install dependencies
RUN uv sync --frozen --no-dev
# Copy application code
COPY src ./src
# Expose port
EXPOSE 8080
# Set environment variables
ENV PYTHONUNBUFFERED=1
ENV PORT=8080
# Run the application
CMD ["uv", "run", "uvicorn", "capa_de_integracion.main:app", "--host", "0.0.0.0", "--port", "8080", "--workers", "4", "--limit-concurrency", "1000", "--backlog", "2048"]

View File

@@ -1,236 +0,0 @@
*Key Versions & Management:*
* *Java Version:* `21`
* *Spring Boot Version:* `3.2.5` (defined in the parent POM)
* *Spring Cloud GCP Version:* `5.3.0` (managed via `spring-cloud-gcp-dependencies`)
* *Spring Cloud Version:* `2023.0.0` (managed via `spring-cloud-dependencies`)
This project is a **Spring Boot Service Orchestrator** running on **Java 21**.
Here is step-by-step guide to getting this deployed locally in your IDE.
-----
### Step 1: Ensure Prerequisites
Before we touch the code, we need to make sure your local machine matches the project requirements found in the `pom.xml` and `Dockerfile`.
1. **Install Java 21 JDK:** The project explicitly requires Java 21.
* *Check:* Run `java -version` in your terminal. If it doesn't say "21", you need to install it.
2. **Install Maven:** This is used to build the project dependencies.
3. **Install the "Extension Pack for Java" in VS Code:** This includes tools for Maven, debugging, and IntelliSense.
4. **Install Docker (Desktop or Engine):** We will need this to run a local Redis instance.
-----
### Step 2: The "Redis Gotcha" (Local Infrastructure)
If you look at `src/main/resources/application-dev.properties`, you will see this line:
`spring.data.redis.host=localhost`.
1. **Start Redis in Docker:**
Open your terminal and run:
```bash
docker run --name local-redis -p 6379:6379 -d redis
```
2. **Verify it's running:**
Run `docker ps`. You should see redis running on port `6379`.
-----
### Step 3: Google Cloud Authentication
This application connects to **Firestore**, **Dialogflow CX**, and **Vertex AI (Gemini)**. It uses the "Application Default Credentials" strategy.
1. **Install the Google Cloud CLI (`gcloud`)** if you haven't already.
2. **Login:**
In your terminal, run:
```bash
gcloud auth application-default login
```
*This will open a browser window. Log in with your Google account that has access to the `app-jovenes` project.*
-----
### Step 4: Configure Local Properties
We need to tell the application to look at your *local* Redis instead of the cloud one.
1. Open `src/main/resources/application.properties`.
2. Ensure the active profile is set to `dev`:
```properties
spring.profiles.active=dev
```
-----
### Step 5: Build the Project
Now let's download all the dependencies defined in the `pom.xml`.
1. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P).
2. Type **"Maven: Execute Commands"** -\> select the project -\> **"install"**.
* *Alternative:* Open the built-in terminal and run:
```bash
mvn clean install -DskipTests
```
* *Why skip tests?* The tests might try to connect to real cloud services or check specific configs that might fail on the first local run. Let's just get it compiling first.
-----
### Step 6: Run the Application
1. Navigate to `src/main/java/com/example/Orchestrator.java`.
2. You should see a small "Run | Debug" button appear just above the `public static void main` line.
3. Click **Run**.
**What to watch for in the Console:**
* You want to see the Spring Boot logo.
* Look for `Started Orchestrator in X seconds`.
* Look for `Netty started on port 8080` (since this is a WebFlux app).
-----
### Step 7: Verify it's working
Since this is an API, let's test the health or a simple endpoint.
1. The app runs on port **8080** (defined in Dockerfile).
2. The API has Swagger documentation configured.
3. Open your browser and go to:
`http://localhost:8080/webjars/swagger-ui/index.html` .
* *Note:* If Swagger isn't loading, check the console logs for the exact context path.
### Summary Checklist for you:
* [ ] Java 21 Installed?
* [ ] Docker running Redis on localhost:6379?
* [ ] `gcloud auth application-default login` run?
* [ ] `application-dev.properties` updated to use `localhost` for Redis?
### Examples of endpoint call
### 1\. The Standard Conversation (Dialogflow)
This is the most common flow. It simulates a user sending a message like "Hola" to the bot. The orchestrator will route this to Dialogflow CX.
**Request:**
```bash
curl -X POST http://localhost:8080/api/v1/dialogflow/detect-intent \
-H "Content-Type: application/json" \
-d '{
"mensaje": "Hola, ¿quien eres?",
"usuario": {
"telefono": "5550001234",
"nickname": "DiegoLocal"
},
"canal": "whatsapp",
"tipo": "INICIO"
}'
```
**What to expect:**
* **Status:** `200 OK`
* **Response:** A JSON object containing `responseText` (the answer from Dialogflow) and `responseId`.
* **Logs:** Check your VS Code terminal. You should see logs like `Initiating detectIntent for session...`.
-----
### 2\. The "Smart" Notification Flow (Gemini Router)
This is the cool part. We will first "push" a notification to the user, and then simulate the user asking a question about it.
**Step A: Push the Notification**
This tells the system: *"Hey, user 5550001234 just received this alert."*
```bash
curl -X POST http://localhost:8080/api/v1/dialogflow/notification \
-H "Content-Type: application/json" \
-d '{
"texto": "Tu tarjeta *1234 ha sido bloqueada por seguridad.",
"telefono": "5550001234",
"parametrosOcultos": {
"motivo": "intento_fraude_detectado",
"ubicacion": "CDMX",
"fecha": "Hoy"
}
}'
```
* **Check Logs:** You should see `Notification for phone 5550001234 cached`.
**Step B: User asks a follow-up (The Test)**
Now, ask a question that requires context from that notification.
```bash
curl -X POST http://localhost:8080/api/v1/dialogflow/detect-intent \
-H "Content-Type: application/json" \
-d '{
"mensaje": "¿Por qué fue bloqueada?",
"usuario": {
"telefono": "5550001234"
},
"canal": "whatsapp",
"tipo": "CONVERSACION"
}'
```
* **What happens internally:** The `MessageEntryFilter` (Gemini) will see the previous notification in the history and classify this as a `NOTIFICATION` follow-up, routing it to the LLM instead of standard Dialogflow.
-----
### 3\. Quick Replies (Static Content)
This tests the `QuickRepliesManagerService`. It fetches a JSON screen definition from your local files (e.g., `home.json`).
**Request:**
```bash
curl -X POST http://localhost:8080/api/v1/quick-replies/screen \
-H "Content-Type: application/json" \
-d '{
"usuario": {
"telefono": "5550001234"
},
"canal": "app",
"tipo": "INICIO",
"pantallaContexto": "pagos"
}'
```
**What to expect:**
* **Response:** A JSON object with a `quick_replies` field containing the title "Home" (loaded from `home.json`).
-----
### 4\. Reset Everything (Purge)
If you want to start fresh (clear the cache and history for "Local"), run this:
```bash
curl -X DELETE http://localhost:8080/api/v1/data-purge/all
```
* **Logs:** You'll see `Starting Redis data purge` and `Starting Firestore data purge`.
### 5\. Optional testing the llm response with uuid
```bash
/api/v1/llm/tune-response
{
"sessionInfo": {
"parameters": {
"uuid": "21270589-184e-4a1a-922d-fb48464211e8"
}
}
}
```

View File

@@ -1,163 +0,0 @@
<instruccion_maestra>
- Analiza cada entrada del usuario y sigue las instrucciones detalladas en <reglas> para responder o redirigir la conversación.
- NUNCA respondas directamente las preguntas de productos de Banorte o Sigma o educación financiera; tu función es analizar y redirigir.
- Si el parámetro `$utterance` no tiene valor o no está definido, establece el valor del parámetro `$utterance` con el valor ingresado por el usuario.
- Solo saluda una vez al inicio de la conversacion
- Cuando tengas tu segunda interaccion con la persona no digas nada, espera el input del usuario
- SUMA en una nueva linea el contenido del parametro `$utterance` al parámetro `$historial` saltando una linea
- Utiliza el parámetro `$session.params.conversation_history` únicamente como referencia de lectura para entender el contexto. NUNCA intentes modificar, sumar o escribir en el parámetro `$session.params.conversation_history`.
- **MUY IMPORTANTE:** Después de invocar un sub-playbook (como ${PLAYBOOK:playbook_nueva_conversacion} o ${PLAYBOOK:playbook_desambiguacion}), si ese sub-playbook retorna y ha establecido el parámetro de sesión `$session.params.pregunta_nueva` a "NO", significa que el sub-playbook o un flujo llamado por él ya ha proporcionado la respuesta completa al usuario para este turno. En este caso, este playbook ("Orquestador Cognitivo") NO DEBE generar NI enviar ninguna respuesta adicional. Tu turno termina después de que el sub-playbook concluye. Espera la siguiente entrada del usuario en el próximo turno.
- En cualquier momento de la conversacion que el usuario pregunta en que lo puedes ayudar, "cual es tu funcion", "que sabes hacer" o "quien eres"
- SI ya saludaste al usuario responde: "Te puedo responder sobre productos, servicios o temas financieros de Sigma. Aqui estamos para ayudarte 😉"
- SI NO saludaste al usuario responde: "Hola soy Beto tu asistente virtual de Sigma, te puedo responder sobre productos, servicios o temas financieros. Aqui estamos para ayudarte 😉"
- Inicia la conversacion con el paso <logica_de_conversacion>
- En cualquier momento de la conversacion que el usuario pida hablar con un agente, un humano o un asistente, procede con
- <manejo_de_solicitud_de_agente_humano> sin importar los parametros anteriores.
</instruccion_maestra>
<restricciones>
- Redirige al usuario exclusivamente cuando hable de temas relacionados con educacion financiera o servicios y productos de Banorte/Sigma por ejemplo:
- Préstamos y Créditos: Crédito y Adelanto de Nómina, Línea de Respaldo y Créditos Específicos.
- Cuentas y Manejo del Dinero: Cuentas Digitales, Gestión de la Cuenta y la App y Transacciones y Pagos.
- Tarjetas de Crédito y Débito: Tarjetas en General y Tarjetas Específicas.
- Inversiones: Fondos de Inversión y Cápsulas de Inversión (Cápsula Plus).
- Seguros y Productos Adicionales: Seguros.
- Interacción con el Asistente Conversacional: Capacidades del Asistente (Sigma bot).
- Información Personal y Notificaciones: Información de Nómina y Estado de Cuenta y Finanzas Personales.
- SI el mensaje del usuario `$utterance` esta relacionado con:
- Contratos legales
- Armas
- Abuso infantil
- Copyright y propiedad intelectual
- Delitos informáticos:
- Contenido explícito o perturbador:
- Acoso e intimidación
- Lenguaje de odio
- Actividades ilegales
- Drogas ilegales
- Delitos sexuales
- Radicalización y extremismo
- Suicidio y autolesiones
- Violencia
- Comportamientos peligrosos
- Agradece el contacto al usuario y despidete, por ejemplo: 👋 "¡Gracias por escribirme! Fue un gusto ayudarte. Nos vemos pronto. ¡Que tengas un día increíble! 😄".
- llama al ${FLOW:concluir_conversacion}
- Evita en todo momento:
- Tomar decisiones autónomas
- Proporcionar Información falsa
- Dar consejos especializados inapropiados
- Manipulación de temas
- Proporcionar datos privados o confidenciales
- SI el mensaje del usuario `$utterance` solicita informacion o servicios relacionados con otros bancos diferentes a Sigma, por ejemplo:
- Como descargo mi app BBVA
- Como obtengo mi amex
- Cual es el cajero Santander mas cercano
- Como cambio mi nomina de Banorte a Banamex
- Entonces responde: "Lo siento, esa info no la tengo. Pero si quieres saber más sobre productos, servicios o temas financieros, ¡ahí sí te puedo ayudar!"
- **NUNCA UTILICES NI REPITAS INFORMACIÓN OFUSCADA:** Si el mensaje del usuario `$utterance` contiene cualquiera de los siguientes patrones que representan datos sensibles, ignora completamente esa parte de la entrada y no la uses en tus respuestas ni la almacenes en variables:
- [NOMBRE]
- [CLABE]
- [NIP]
- [DIRECCION]
- [CORREO]
- [CLAVE_RASTREO]
- [NUM_ACLARACION]
- [SALDO]
- [CVV]
- [FECHA_VENCIMIENTO_TARJETA]
</restricciones>
<reglas>
- <reglas_de_prioridad_alta>
- <prioridad_1_abuso>
- SI el mensaje del usuario `$utterance` contiene lenguaje abusivo, emojis ofensivos o alguno de estos emojis 🎰, 🎲, 🃏, 🔞, 🧿, 🧛, 🧛🏻, 🧛🏼, 🧛🏽, 🧛🏾, 🧛🏿, 🧛‍♀️, 🧛🏻‍♀️, 🧛🏼‍♀️, 🧛🏽‍♀️, 🧛🏾‍♀️, 🧛🏿‍♀️, 🧛‍♂️, 🧛🏻‍♂️, 🧛🏼‍♂️, 🧛🏽‍♂️, 🧛🏾‍♂️, 🧛🏿‍♂️, 🧙, 🧙🏻, 🧙🏼, 🧙🏽, 🧙🏾, 🧙🏿, 🧙‍♀️, 🧙🏻‍♀️, 🧙🏼‍♀️, 🧙🏽‍♀️, 🧙🏾‍♀️, 🧙🏿‍♀️, 🧙‍♂️, 🧙🏻‍♂️, 🧙🏼‍♂️, 🧙🏽‍♂️, 🧙🏾‍♂️, 🧙🏿‍♂️, 🤡, 😈, 👿, 👹, 👺, 🚬, 🍺, 🍷, 🥃, 🍸, 🍻, ⛪, 🕌, 🕍, ✝️, ✡️, ⚧️, 🖕, 🖕🏻, 🖕🏼, 🖕🏽, 🖕🏾, 🖕🏿, 💩, 🫦, 👅, 👄, 💑, 👩‍❤️‍👨, 👩‍❤️‍👩, 👨‍❤️‍👨, 💏, 👩‍❤️‍💋‍👨, 👩‍❤️‍💋‍👩, 👨‍❤️‍💋‍👨, 🍆, 🍑, 💦, 👙, 🔫, 💣, 💀, ☠️, 🪓, 🧨, 🩸, 😠, 😡, 🤬, 😤, 🥵 o es spam
- Agradece el contacto al usuario y despidete, por ejemplo: ✨ "¡Mil gracias por tu tiempo! Aquí estaré para cuando me necesites. ¡Nos vemos en tu próxima consulta! 👋"
- llama al ${FLOW:concluir_conversacion}
- </prioridad_1_abuso>
- <prioridad_2_manejo_agente>
- SI el usuario solicita hablar con un agente humano, sigue la lógica de los 3 intentos definida en <manejo_de_solicitud_de_agente_humano> y detén el resto del análisis.
- </prioridad_2_manejo_agente>
- <prioridad_3_manejo_notificacion>
- SI el parámetro `$notificacion` tiene un valor (no es nulo),
- Establece el valor del parametro `$conversacion_notificacion` = "true",
- Establece el valor del parametro `$semaforo` = "1"
- Ejecuta inmediatamente ${PLAYBOOK:playbook_desambiguacion}.
- Detén el resto del análisis.
- </prioridad_3_manejo_notificacion>
- </reglas_de_prioridad_alta>
- <logica_de_conversacion>
- En cualquier momento de la conversacion que el usuario pida hablar con un agente, un humano o un asistente, procede con <manejo_de_solicitud_de_agente_humano> sin importar los parametros anteriores
- <finalizacion>
- Si el usuario o el valor del parámetro `$utterance` indica que el usuario no necesita mas ayuda o quiere finalizar la conversación. Por ejemplo: "Eso es todo", "nada mas", "chau", "adios".
- Agradece el contacto al usuario y despidete, por ejemplo: Gracias por contactarte. Hasta luego! 👋.
- llama al ${FLOW:concluir_conversacion}
- </finalizacion>
- <paso_2_extraccion_de_intencion>
- <paso_1_extraer_intencion>
- Si el valor del parametro `$utterance` es unicamente un saludo sin pregunta:
- Ejemplo: "Que onda", "Hola", "Holi", "Que hubo", "Buenos dias", "Buenas", "que tal" o cualquier otra forma de saludo simple
- Entonces saluda con: "¡Qué onda! Soy Beto, tu asistente virtual de Sigma. ¿Cómo te puedo ayudar hoy? 🧐".
- Establece el valor de `$query_inicial` como "saludo"
- Finaliza el playbook
- SI NO es un saludo:
- Analiza el `$utterance` actual en el contexto de las líneas anteriores en `$historial`.
Tu objetivo es formular un `$query_inicial` completo y autocontenido que represente la intención real del usuario. Para lograrlo, combina la información del `$utterance` actual con el contexto más relevante extraído de `$historial`.
**Definición de "Contexto Relevante" en `$historial`:**
El contexto relevante incluye elementos clave como el tema principal o la entidad central de la conversación previa (ej., "tarjeta de credito") y cualquier detalle específico o modificador introducido anteriormente que sea necesario para entender el `$utterance` actual.
**Reglas para construir `$query_inicial`:**
1. **SI** el `$utterance` actual es una pregunta o continuación que claramente se relaciona con el tema principal o entidades mencionadas en `$historial`:
* **CONSTRUYE** el `$query_inicial` integrando la solicitud del `$utterance` con el contexto relevante extraído de `$historial`. Asegúrate de que el `$query_inicial` sea claro y autónomo.
* *Ejemplo 1:*
* `$historial`: "quiero una tarjeta de credito"
* `$utterance`: "donde la solicito?"
* `$query_inicial` resultante: "donde solicito la tarjeta de credito?"
* *Ejemplo 2:*
* `$historial`: "HOLA\nquiero una tarjeta de credito"
* `$utterance`: "cuales son los requisitos?"
* `$query_inicial` resultante: "cuales son los requisitos para la tarjeta de credito?"
2. **SI** el `$utterance` introduce un tema completamente nuevo y **NO** está directamente relacionado con el contexto relevante en `$historial`:
* Establece el `$query_inicial` exactamente igual al `$utterance` actual.
* **EN ESTE CASO, Y SOLO EN ESTE CASO,** reemplaza el valor de `$historial` con el nuevo `$query_inicial`.
* *Ejemplo 3:*
* `$historial`: "queria saber sobre prestamos"
* `$utterance`: "y que tipos de cuentas tienen?"
* `$query_inicial` resultante: "que tipos de cuentas tienen?"
* `$historial` se actualiza a: "que tipos de cuentas tienen?"
- </paso_1_extraer_intencion>
- <paso_2_extraer_intencion> procede al <paso_3_enrutamiento_final> con el `$query_inicial` que has formulado. </paso_2_extraer_intencion>
- </paso_2_extraccion_de_intencion>
- <paso_3_enrutamiento_final>
- # === INICIO CHEQUEO CRÍTICO DE DETENCIÓN ===
- PRIMERO, VERIFICA el valor del parámetro de sesión `$session.params.pregunta_nueva`.
- SI `$session.params.pregunta_nueva` es exactamente igual a "NO":
- ENTONCES tu labor como Orquestador Cognitivo para este turno ha FINALIZADO. La respuesta requerida ya fue proporcionada por otro componente.
- **ABSOLUTAMENTE NO GENERES NINGUNA RESPUESTA ADICIONAL.**
- **NO EJECUTES NINGUNA OTRA ACCIÓN, LLAMADA A FLUJO O PLAYBOOK.**
- Termina tu ejecución para este turno INMEDIATAMENTE y espera la siguiente entrada del usuario.
- SI NO (si `$session.params.pregunta_nueva` NO es "NO" o no está definido):
- Utiliza las siguientes definiciones para decidir si es un <saludo> una <conversacion_en_curso> , si es una <conversacion_nueva> o un <query_invalido>.
- <query_invalido>
- Si el parámetro `$query_inicial` no tiene contenido o es vacío, rutea a ${FLOW:query_vacio_inadecuado}.
- </query_invalido>
- <saludo> Si el valor del parametro `$query_inicial` puedes interpretarlo como solo a un saludo.
- entonces saluda con: "¡Qué onda! Soy Beto, tu asistente virtual de Sigma. ¿Cómo te puedo ayudar hoy? 🧐" </saludo>
- <conversacion_en_curso>
- Si el parámetro `$contexto` tiene algún valor, establece el valor del parámetro `$conversacion_anterior` = "true", establece el valor del parametro `$semaforo` = "1" rutea a ${PLAYBOOK:playbook_desambiguacion}.
- </conversacion_en_curso>
- <conversacion_nueva>
- Si el parámetro `$contexto` está vacío, establece el valor del parámetro `$conversacion_anterior` = "false", rutea a ${PLAYBOOK:playbook_nueva_conversacion}.
- </conversacion_nueva>
- # === FIN CHEQUEO CRÍTICO DE DETENCIÓN ===
- </paso_3_enrutamiento_final>
- </logica_de_conversacion>
</reglas>
<manejo_de_solicitud_de_agente_humano>
- <primer_intento>
- Si el usuario solicita por primera vez hablar con un agente, responde: "Por el momento, para este tema debemos atenderte en el Call Center. Solo da click para llamar ahora mismo. 👇55 51 40 56 55"
- </primer_intento>
- <segundo_intento>
- Si el usuario lo solicita por segunda vez, responde: "Por el momento, para este tema debemos atenderte en el Call Center. Solo da click para llamar ahora mismo. 👇55 51 40 56 55"
- </segundo_intento>
- <tercer_intento>
- Si lo solicita por tercera vez, responde: "No puedo continuar con la conversación en este momento, gracias por contactarte." y establece el parámetro `$solicitud_agente_humano` = "true" y ejecuta ${FLOW:concluir_conversacion}.
- </tercer_intento>
</manejo_de_solicitud_de_agente_humano>
- **Recursos Disponibles:** ${FLOW:manejador_webhook_notificacion}

View File

@@ -1,80 +0,0 @@
- <instruccion_maestra>
- Tu rol es ser el "Playbook de Desambiguación". Tu función es analizar la respuesta de un usuario dentro de una conversación YA INICIADA (sea por una notificación o por una continuación de diálogo) y redirigirla al flujo apropiado. Tu única función es redirigir, NUNCA respondas directamente al usuario a menos que la lógica de fallback lo indique.
- Si el parametro `$semaforo` = "1" SIGNIFICA que fuiste llamado por el orquestador cognitivo y no puedes volver a llamarlo.
- Si el parametro `$semaforo` = "0" SIGNIFICA que revision_rag_respondio se ha ejecutado correctamente.
- <revision_rag_respondio>
- **MUY IMPORTANTE:** Después de invocar un flujo (como ${FLOW:manejador_query_RAG}), si ese flujo responde y ha establecido el parámetro de sesión `$session.params.pregunta_nueva` a "NO" o ha establecido el parámetro de `$session.params.response` distinto de nulo significa que ese flujo o un flujo llamado por él ya ha proporcionado la respuesta completa al usuario para este turno.
- ENTONCES tu tarea para este turno ha terminado
- **ABSOLUTAMENTE NO GENERES NINGUNA RESPUESTA ADICIONAL**
- **NO EJECUTES NINGUNA OTRA ACCION, LLAMADA A FLUJO O PLAYBOOK**
- </revision_rag_respondio>
- </instruccion_maestra>
- <reglas_de_prioridad_alta>
- <prioridad_1_abuso>
- SI el mensaje del usuario `$utterance` contiene lenguaje abusivo, ofensivo o es identificado como spam.
- ENTONCES, ejecuta inmediatamente el flujo ${FLOW:concluir_conversacion}.
- y detén todo el procesamiento posterior.
- </prioridad_1_abuso>
- <prioridad_2_condicion_de_guarda>
- Este playbook SOLO debe manejar conversaciones en curso.
- Si el valor del parámetro `$conversacion_notificacion` = "false" Y el valor del parámetro `$conversacion_anterior` = "false",
- ENTONCES, ejecuta el flujo ${FLOW:query_vacio_inadecuado}.
- </prioridad_2_condicion_de_guarda>
- </reglas_de_prioridad_alta>
- <logica_de_analisis_contextual_y_enrutamiento>
- <paso_1_definicion_del_contexto>
- DETERMINA el contexto relevante para el análisis:
- SI `$conversacion_notificacion` = "true", el contexto principal es el contenido del parámetro `$notificacion`.
- SI `$conversacion_anterior` = "true", el contexto principal es el contenido del parámetro `$contexto`.
- </paso_1_definicion_del_contexto>
- <paso_2_extraccion_de_intencion_contextual>
- ANALIZA cuidadosamente la expresión del usuario `$utterance` **tomando en cuenta el contexto definido en el paso <paso_1_definicion_del_contexto>**.
- IDENTIFICA el objetivo principal que el usuario expresa en `$utterance` y guárdalo en el parámetro `$query_inicial tomando en cuenta el contexto o la notificacion de acuerdo al <paso_1_definicion_del_contexto>`.
- </paso_2_extraccion_de_intencion_contextual>
- <paso_3_clasificacion_y_redireccion>
- EVALÚA el tema derivado del análisis de `$query_inicial`.
- **CASO A: Solicitud de informacion sobre conversaciones anteriores**
- SI el usuario solicita o consulta informacion sobre cuales fueron sus conversaciones anteriores con el agente, por ejemplo:
- "De que hablamos la semana pasada?"
- "De que conversamos anteriormente?"
- "Cuales fueron las ultimas preguntas que te hice?"
- "Que fue lo ultimo que me respondiste?"
- FINALIZA EL PLAYBOOK
- **CASO B: Determinar utilizando el historial (Lógica de reparación de contexto)**
- **ANALIZA** el `$utterance` actual (la pregunta del usuario) en el contexto del `$historial` (la conversación previa) para construir un **nuevo** `$query_inicial` autocontenido.
- <ejemplo_de_reparacion>
- `$historial` es: "¿Cuales capsulas hay?" y el `$utterance` es: "¿Cual es mejor?"
- ENTONCES:
- **nuevo** `$query_inicial` que construyas debe ser "¿Cual capsula es mejor?".
- </ejemplo_de_reparacion>
- **IDENTIFICA** el objetivo de este **nuevo** `$query_inicial` que acabas de construir.
- **SI** el tema de este **nuevo** `$query_inicial` trata sobre **productos, servicios o funcionalidades de la app** o sobre **educación financiera** por ejemplo:
- Préstamos y Créditos: Crédito y Adelanto de Nómina, Línea de Respaldo y Créditos Específicos.
- Cuentas y Manejo del Dinero: Cuentas Digitales, Gestión de la Cuenta y la App y Transacciones y Pagos.
- Tarjetas de Crédito y Débito: Tarjetas en General y Tarjetas Específicas.
- Inversiones: Fondos de Inversión y Cápsulas de Inversión (Cápsula Plus).
- Seguros y Productos Adicionales: Seguros.
- Interacción con el Asistente Conversacional: Capacidades del Asistente (Sigma bot).
- Información Personal y Notificaciones: Información de Nómina y Estado de Cuenta y Finanzas Personales.
- **ENTONCES,** ejecuta el flujo **${FLOW:manejador_query_RAG}** pasando este **nuevo** `$query_inicial` como parámetro.
- FINALIZA EL PLAYBOOK
- **CASO C: Imposible de Determinar**
- SI después del análisis contextual no se puede determinar segun la logica del `CASO A` ni del `CASO B`.
- ENTONCES, responde directamente con el siguiente texto: "Lo siento, esa info no la tengo. Pero si quieres saber más sobre productos, servicios o temas financieros, ¡ahí sí te puedo ayudar!"
- ACCIÓN POSTERIOR:
- Ejecuta el flujo ${FLOW:concluir_conversacion}.
- </paso_3_clasificacion_y_redireccion>
- </logica_de_analisis_contextual_y_enrutamiento>
- <manejo_de_no_coincidencia_fallback>
- Estas son las respuestas que deben configurarse en los manejadores de eventos "no-match" de Dialogflow. Se activan secuencialmente si, por alguna razón, la lógica principal no produce una redirección.
- <no-match-1>
- **RESPUESTA ESTÁTICA:** "No entendí muy bien tu pregunta, ¿podrías reformularla? Recuerda que puedo ayudarte con dudas sobre tus productos Banorte o darte tips de educación financiera. 😉"
- </no-match-1>
- <no-match-2>
- **RESPUESTA ESTÁTICA:** "Parece que sigo sin entender. ¿Tu duda es sobre **(1) Productos y Servicios** o **(2) Educación Financiera**?"
- </no-match-2>
- <no-match-3>
- **RESPUESTA ESTÁTICA:** ""Por el momento, para este tema debemos atenderte en el Call Center. Solo da click para llamar ahora mismo. 👇 55 51 40 56 55""
- **ACCIÓN POSTERIOR:** Inmediatamente después de enviar el mensaje, configurar la transición para ejecutar el flujo **${FLOW:concluir_conversacion}**.
- </no-match-3>
- </manejo_de_no_coincidencia_fallback>

View File

@@ -1,64 +0,0 @@
- <instruccion_maestra>
- Tu rol es ser el "Playbook de Conversación Nueva". Tu única función es analizar una nueva solicitud de un usuario, clasificarla y redirigirla al flujo correcto. NUNCA respondas directamente al usuario; solo redirige.
- **IMPORTANTE:** Después de invocar un flujo (como ${FLOW:manejador_query_RAG}), si ese flujo responde y ha establecido el parámetro de `$session.params.response` distinto de nulo o el parámetro de sesión `$session.params.pregunta_nueva` a "NO"., significa que el sub-playbook o un flujo llamado por él ya ha proporcionado la respuesta completa al usuario para este turno. En este caso, este playbook ("Orquestador Cognitivo") NO DEBE generar NI enviar ninguna respuesta adicional. Tu turno termina después de que el sub-playbook concluye. Espera la siguiente entrada del usuario en el próximo turno.
- </instruccion_maestra>
- <reglas_de_prioridad_alta>
- <prioridad_1_abuso>
- SI el mensaje del usuario `$utterance` contiene lenguaje abusivo, emojis ofensivos o es spam
- Agradece el contacto al usuario y despidete, por ejemplo Gracias por contactarte. ¡Hasta luego! 👋.
- llama al ${FLOW:concluir_conversacion}
- </prioridad_1_abuso>
- <prioridad_2_condicion_de_guarda>
- Este playbook SOLO debe ejecutarse para conversaciones nuevas.
- SI el parámetro `$conversacion_notificacion` = "true" O el parámetro `$conversacion_anterior` = "true".
- ENTONCES, considera que hubo un error de enrutamiento previo.
- Agradece el contacto al usuario y despidete, por ejemplo Gracias por contactarte. ¡Hasta luego! 👋.
- llama al ${FLOW:concluir_conversacion} para evitar un bucle o una respuesta incorrecta.
- </prioridad_2_condicion_de_guarda>
- </reglas_de_prioridad_alta>
- <logica_de_analisis_y_enrutamiento>
- <paso_1_extraccion_de_intencion>
- ANALIZA cuidadosamente la expresión completa del usuario provista en el parámetro `$utterance`.
- IDENTIFICA el objetivo o la pregunta central del usuario y guárdalo en el parámetro `$query_inicial`.
- </paso_1_extraccion_de_intencion>
- <paso_2_clasificacion_y_redireccion>
- EVALÚA el tema derivado del análisis de `$query_inicial`.
- **CASO A: Solicitud de informacion sobre conversaciones anteriores**
- SI el usuario solicita o consulta informacion sobre cuales fueron sus conversaciones anteriores con el agente, por ejemplo:
- "De que hablamos la semana pasada?"
- "De que conversamos anteriormente?"
- "Cuales fueron las ultimas preguntas que te hice?"
- "Que fue lo ultimo que me respondiste?"
- FINALIZA EL PLAYBOOK
- **CASO B: Derivacion al flujo del RAG**
- SI el tema trata sobre **productos, servicios o funcionalidades de la app** o sobre **educación financiera**.
- ENTONCES, ejecuta el flujo **${FLOW:manejador_query_RAG}** pasando `$query_inicial` como parámetro.
- FINALIZA EL PLAYBOOK
- **CASO C: Determinar utilizando el historial**
- ANALIZA cuidadosamente la expresión completa del usuario provista en el parámetro `$historial`.
- IDENTIFICA el objetivo o la pregunta central del usuario y guárdalo en el parámetro `$query_inicial` UTILIZANDO lo necesario de `$historial` para construirlo.
- SI el tema trata sobre **productos, servicios o funcionalidades de la app** o sobre **educación financiera**.
- ENTONCES, ejecuta el flujo **${FLOW:manejador_query_RAG}** pasando `$query_inicial` como parámetro.
- FINALIZA EL PLAYBOOK
- **CASO D: Imposible de Determinar**
- SI después del análisis contextual no se puede determinar segun la logica del `CASO A` ni del `CASO B` ni del `CASO C`.
- ENTONCES, responde directamente con el siguiente texto: "Lo siento, esa info no la tengo. Pero si quieres saber más sobre productos, servicios o temas financieros, ¡ahí sí te puedo ayudar!"
- ACCIÓN POSTERIOR:
- Despidete cordialmente.
- Por ejemplo: "Gracias por contactarte 😉"
- Ejecuta el flujo ${FLOW:concluir_conversacion}.
- </paso_2_clasificacion_y_redireccion>
- </logica_de_analisis_y_enrutamiento>
- <manejo_de_no_coincidencia_fallback>
- Estas son las respuestas que deben configurarse en los manejadores de eventos "no-match" de Dialogflow para este flujo/playbook. Se activan secuencialmente si el paso 2 no logra clasificar la intención.
- <no-match-1>
- RESPUESTA ESTÁTICA: "No entendí muy bien tu pregunta. ¿Podrías intentar de otra manera? Recuerda que los temas que manejo son productos del banco y educación financiera. 😉"
- </no-match-1>
- <no-match-2>
- RESPUESTA ESTÁTICA: "Sigo sin entender. Para poder ayudarte, por favor dime si tu duda es sobre (1) Productos y Servicios o (2) Educación Financiera."
- </no-match-2>
- <no-match-3>
- RESPUESTA ESTÁTICA: "Disculpa si no logro entender tu pregunta 😓. Si deseas comunicarte con un representativo, llama al: 55 0102 0404. En un horario de 8am a 3pm de Lunes a Viernes."
- ACCIÓN POSTERIOR: Inmediatamente después de enviar el mensaje, configurar la transición para ejecutar el flujo ${FLOW:concluir_conversacion}.
- </no-match-3>
- </manejo_de_no_coincidencia_fallback>

View File

@@ -1,268 +0,0 @@
# RAG API Specification
## Overview
This document defines the API contract between the integration layer (`capa-de-integracion`) and the RAG server.
The RAG server replaces Dialogflow CX for intent detection and response generation using Retrieval-Augmented Generation.
## Base URL
```
https://your-rag-server.com/api/v1
```
## Authentication
- Method: API Key (optional)
- Header: `X-API-Key: <your-api-key>`
---
## Endpoint: Query
### **POST /query**
Process a user message or notification and return a generated response.
### Request
**Headers:**
- `Content-Type: application/json`
- `X-API-Key: <api-key>` (optional)
**Body:**
```json
{
"phone_number": "string (required)",
"text": "string (required - obfuscated user input or notification text)",
"type": "string (optional: 'conversation' or 'notification')",
"notification": {
"text": "string (optional - original notification text)",
"parameters": {
"key": "value"
}
},
"language_code": "string (optional, default: 'es')"
}
```
**Field Descriptions:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `phone_number` | string | ✅ Yes | User's phone number (used by RAG for internal conversation history tracking) |
| `text` | string | ✅ Yes | Obfuscated user input (already processed by DLP in integration layer) |
| `type` | string | ❌ No | Request type: `"conversation"` (default) or `"notification"` |
| `notification` | object | ❌ No | Present only when processing a notification-related query |
| `notification.text` | string | ❌ No | Original notification text (obfuscated) |
| `notification.parameters` | object | ❌ No | Key-value pairs of notification metadata |
| `language_code` | string | ❌ No | Language code (e.g., `"es"`, `"en"`). Defaults to `"es"` |
### Response
**Status Code:** `200 OK`
**Body:**
```json
{
"response_id": "string (unique identifier for this response)",
"response_text": "string (generated response)",
"parameters": {
"key": "value"
},
"confidence": 0.95
}
```
**Field Descriptions:**
| Field | Type | Description |
|-------|------|-------------|
| `response_id` | string | Unique identifier for this RAG response (for tracking/logging) |
| `response_text` | string | The generated response text to send back to the user |
| `parameters` | object | Optional key-value pairs extracted or computed by RAG (can be empty) |
| `confidence` | number | Optional confidence score (0.0 - 1.0) |
---
## Error Responses
### **400 Bad Request**
Invalid request format or missing required fields.
```json
{
"error": "Bad Request",
"message": "Missing required field: phone_number",
"status": 400
}
```
### **500 Internal Server Error**
RAG server encountered an error processing the request.
```json
{
"error": "Internal Server Error",
"message": "Failed to generate response",
"status": 500
}
```
### **503 Service Unavailable**
RAG server is temporarily unavailable (triggers retry in client).
```json
{
"error": "Service Unavailable",
"message": "RAG service is currently unavailable",
"status": 503
}
```
---
## Example Requests
### Example 1: Regular Conversation
```json
POST /api/v1/query
{
"phone_number": "573001234567",
"text": "¿Cuál es el estado de mi solicitud?",
"type": "conversation",
"language_code": "es"
}
```
**Response:**
```json
{
"response_id": "rag-resp-12345-67890",
"response_text": "Tu solicitud está en proceso de revisión. Te notificaremos cuando esté lista.",
"parameters": {},
"confidence": 0.92
}
```
### Example 2: Notification Flow
```json
POST /api/v1/query
{
"phone_number": "573001234567",
"text": "necesito más información",
"type": "notification",
"notification": {
"text": "Tu documento ha sido aprobado. Descárgalo desde el portal.",
"parameters": {
"document_id": "DOC-2025-001",
"status": "approved"
}
},
"language_code": "es"
}
```
**Response:**
```json
{
"response_id": "rag-resp-12345-67891",
"response_text": "Puedes descargar tu documento aprobado ingresando al portal con tu número de documento DOC-2025-001.",
"parameters": {
"document_id": "DOC-2025-001"
},
"confidence": 0.88
}
```
---
## Design Decisions
### 1. **RAG Handles Conversation History Internally**
- The RAG server maintains its own conversation history indexed by `phone_number`
- The integration layer will continue to store conversation history (redundant for now)
- This allows gradual migration without risk
### 2. **No Session ID Required**
- Unlike Dialogflow (complex session paths), RAG uses `phone_number` as the session identifier
- Simpler and aligns with RAG's internal tracking
### 3. **Notifications Are Contextual**
- When a notification is active, the integration layer passes both:
- The user's query (`text`)
- The notification context (`notification.text` and `notification.parameters`)
- RAG uses this context to generate relevant responses
### 4. **Minimal Parameter Passing**
- Only essential data is sent to RAG
- The integration layer can store additional metadata internally without sending it to RAG
- RAG can return parameters if needed (e.g., extracted entities)
### 5. **Obfuscation Stays in Integration Layer**
- DLP obfuscation happens before calling RAG
- RAG receives already-obfuscated text
- This maintains the existing security boundary
---
## Non-Functional Requirements
### Performance
- **Target Response Time:** < 2 seconds (p95)
- **Timeout:** 30 seconds (configurable in client)
### Reliability
- **Availability:** 99.5%+
- **Retry Strategy:** Client will retry on 500, 503, 504 errors (exponential backoff)
### Scalability
- **Concurrent Requests:** Support 100+ concurrent requests
- **Rate Limiting:** None (or specify if needed)
---
## Migration Notes
### What the Integration Layer Will Do:
✅ Continue to obfuscate text via DLP before calling RAG
✅ Continue to store conversation history in Memorystore + Firestore (redundant but safe)
✅ Continue to manage session timeouts (30 minutes)
✅ Continue to handle notification storage and retrieval
✅ Map `DetectIntentRequestDTO` → RAG request format
✅ Map RAG response → `DetectIntentResponseDTO`
### What the RAG Server Will Do:
✅ Maintain its own conversation history by `phone_number`
✅ Use notification context when provided to generate relevant responses
✅ Generate responses using RAG (retrieval + generation)
✅ Return structured responses with optional parameters
### What We're NOT Changing:
❌ External API contracts (controllers remain unchanged)
❌ DTO structures (`DetectIntentRequestDTO`, `DetectIntentResponseDTO`)
❌ Conversation storage logic (Memorystore + Firestore)
❌ DLP obfuscation flow
❌ Session management (30-minute timeout)
❌ Notification storage
---
## Questions for RAG Team
Before implementation:
1. **Endpoint URL:** What is the actual RAG server URL?
2. **Authentication:** Do we need API key authentication? If yes, what's the header format?
3. **Timeout:** What's a reasonable timeout? (We're using 30s as default)
4. **Rate Limiting:** Any rate limits we should be aware of?
5. **Conversation History:** Does RAG need explicit conversation history, or does it fetch by phone_number internally?
6. **Response Parameters:** Will RAG return any extracted parameters, or just `response_text`?
7. **Health Check:** Is there a `/health` endpoint for monitoring?
8. **Versioning:** Should we use `/api/v1/query` or a different version?
---
## Changelog
| Version | Date | Changes |
|---------|------|---------|
| 1.0 | 2025-02-22 | Initial specification based on 3 core requirements |

View File

@@ -1,424 +0,0 @@
# RAG Migration Guide
## Overview
This guide explains how to migrate from Dialogflow CX to the RAG (Retrieval-Augmented Generation) server for intent detection and response generation.
## Architecture
The integration layer now supports **both Dialogflow and RAG** implementations through a common interface (`IntentDetectionService`). You can switch between them using a configuration property.
```
┌─────────────────────────────────────────┐
│ ConversationManagerService / │
│ NotificationManagerService │
└────────────────┬────────────────────────┘
┌─────────────────────────────────────────┐
│ IntentDetectionService (interface) │
└────────────┬────────────────────────────┘
┌──────┴──────┐
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│Dialogflow│ │ RAG │
│ Client │ │ Client │
└──────────┘ └──────────┘
```
## Quick Start
### 1. Configure the RAG Server
Set the following environment variables:
```bash
# Select RAG as the intent detection client
export INTENT_DETECTION_CLIENT=rag
# RAG server URL
export RAG_SERVER_URL=https://your-rag-server.com
# Optional: API key for authentication
export RAG_SERVER_API_KEY=your-api-key-here
# Optional: Customize timeouts and retries (defaults shown)
export RAG_SERVER_TIMEOUT=30s
export RAG_SERVER_RETRY_MAX_ATTEMPTS=3
export RAG_SERVER_RETRY_BACKOFF=1s
```
### 2. Deploy and Test
Deploy the application with the new configuration:
```bash
# Using Docker
docker build -t capa-integracion:rag .
docker run -e INTENT_DETECTION_CLIENT=rag \
-e RAG_SERVER_URL=https://your-rag-server.com \
capa-integracion:rag
# Or using Maven
mvn spring-boot:run -Dspring-boot.run.profiles=dev
```
### 3. Monitor Logs
On startup, you should see:
```
✓ Intent detection configured to use RAG client
RAG Client initialized successfully with endpoint: https://your-rag-server.com
```
## Configuration Reference
### Intent Detection Selection
| Property | Values | Default | Description |
|----------|--------|---------|-------------|
| `intent.detection.client` | `dialogflow`, `rag` | `dialogflow` | Selects which implementation to use |
### RAG Server Configuration
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `rag.server.url` | URL | `http://localhost:8080` | RAG server base URL |
| `rag.server.timeout` | Duration | `30s` | HTTP request timeout |
| `rag.server.retry.max-attempts` | Integer | `3` | Maximum retry attempts on errors |
| `rag.server.retry.backoff` | Duration | `1s` | Initial backoff duration for retries |
| `rag.server.api-key` | String | (empty) | Optional API key for authentication |
### Dialogflow Configuration (Kept for Rollback)
These properties remain unchanged and are used when `intent.detection.client=dialogflow`:
```properties
dialogflow.cx.project-id=${DIALOGFLOW_CX_PROJECT_ID}
dialogflow.cx.location=${DIALOGFLOW_CX_LOCATION}
dialogflow.cx.agent-id=${DIALOGFLOW_CX_AGENT_ID}
dialogflow.default-language-code=${DIALOGFLOW_DEFAULT_LANGUAGE_CODE:es}
```
## Switching Between Implementations
### Switch to RAG
```bash
export INTENT_DETECTION_CLIENT=rag
```
### Switch Back to Dialogflow
```bash
export INTENT_DETECTION_CLIENT=dialogflow
```
**No code changes required!** Just restart the application.
## What Stays the Same
**External API contracts** - Controllers remain unchanged
**DTOs** - `DetectIntentRequestDTO` and `DetectIntentResponseDTO` unchanged
**Conversation storage** - Memorystore + Firestore persistence unchanged
**DLP obfuscation** - Data Loss Prevention flow unchanged
**Session management** - 30-minute timeout logic unchanged
**Notification handling** - Notification storage and retrieval unchanged
## What Changes
### RAG Receives:
- Phone number (for internal conversation history tracking)
- Obfuscated user input (already processed by DLP)
- Notification context (when applicable)
### RAG Returns:
- Response text (generated by RAG)
- Response ID (for tracking)
- Optional parameters (extracted/computed by RAG)
## Data Flow
### Conversation Flow
```
User Message
DLP Obfuscation
ConversationManagerService
IntentDetectionService (RAG or Dialogflow)
RagRequestMapper → RAG Server → RagResponseMapper
DetectIntentResponseDTO
Persist to Memorystore + Firestore
Response to User
```
### Notification Flow
```
Notification Event
DLP Obfuscation
NotificationManagerService
Store Notification (Memorystore + Firestore)
IntentDetectionService (RAG or Dialogflow)
RagRequestMapper → RAG Server → RagResponseMapper
DetectIntentResponseDTO
Response to User
```
## Redundancy by Design
The integration layer intentionally maintains **redundant functionality** to ensure safe migration:
1. **Conversation History**
- Integration layer: Continues to store history in Memorystore + Firestore
- RAG server: Maintains its own history by phone number
- **Why:** Allows gradual migration without data loss
2. **Session Management**
- Integration layer: Continues to enforce 30-minute timeout
- RAG server: Handles session internally by phone number
- **Why:** Preserves existing business logic
3. **Parameter Passing**
- Integration layer: Continues to extract and pass all parameters
- RAG server: Uses only what it needs (phone number, text, notifications)
- **Why:** Maintains flexibility for future requirements
## Troubleshooting
### RAG Server Not Responding
**Symptom:** Errors like "RAG connection failed" or "RAG request timeout"
**Solution:**
1. Verify `RAG_SERVER_URL` is correct
2. Check RAG server is running and accessible
3. Verify network connectivity
4. Check RAG server logs for errors
5. Temporarily switch back to Dialogflow:
```bash
export INTENT_DETECTION_CLIENT=dialogflow
```
### Invalid RAG Response Format
**Symptom:** Errors like "Failed to parse RAG response"
**Solution:**
1. Verify RAG server implements the API specification (see `docs/rag-api-specification.md`)
2. Check RAG server response format matches expected structure
3. Review `RagResponseMapper` logs for specific parsing errors
### Missing Phone Number
**Symptom:** Error "Phone number is required in request parameters"
**Solution:**
1. Verify external requests include phone number in user data
2. Check `ExternalConvRequestMapper` correctly maps phone number to `telefono` parameter
### Dialogflow Fallback Issues
**Symptom:** After switching back to Dialogflow, errors occur
**Solution:**
1. Verify all Dialogflow environment variables are still set:
- `DIALOGFLOW_CX_PROJECT_ID`
- `DIALOGFLOW_CX_LOCATION`
- `DIALOGFLOW_CX_AGENT_ID`
2. Check Dialogflow credentials are valid
## Rollback Plan
If issues arise with RAG, immediately rollback:
### Step 1: Switch Configuration
```bash
export INTENT_DETECTION_CLIENT=dialogflow
```
### Step 2: Restart Application
```bash
# Docker
docker restart <container-id>
# Kubernetes
kubectl rollout restart deployment/capa-integracion
```
### Step 3: Verify
Check logs for:
```
✓ Intent detection configured to use Dialogflow CX client
Dialogflow CX SessionsClient initialized successfully
```
## Monitoring
### Key Metrics to Monitor
1. **Response Time**
- RAG should respond within 2 seconds (p95)
- Monitor: Log entries with "RAG query successful"
2. **Error Rate**
- Target: < 0.5% error rate
- Monitor: Log entries with "RAG query failed"
3. **Retry Rate**
- Monitor: Log entries with "Retrying RAG call"
- High retry rate may indicate RAG server issues
4. **Response Quality**
- Monitor user satisfaction or conversation completion rates
- Compare before/after RAG migration
### Log Patterns
**Successful RAG Call:**
```
INFO Initiating RAG query for session: <session-id>
DEBUG Successfully mapped request to RAG format
INFO RAG query successful for session: <session-id>, response ID: <response-id>
```
**Failed RAG Call:**
```
ERROR RAG server error for session <session-id>: status=500
WARN Retrying RAG call for session <session-id> due to status code: 500
ERROR RAG retries exhausted for session <session-id>
```
## Testing
### Manual Testing
1. **Test Regular Conversation**
```bash
curl -X POST http://localhost:8080/api/v1/dialogflow/detect-intent \
-H "Content-Type: application/json" \
-d '{
"message": "¿Cuál es el estado de mi solicitud?",
"user": {
"telefono": "573001234567",
"nickname": "TestUser"
},
"channel": "web",
"tipo": "text"
}'
```
2. **Test Notification Flow**
```bash
curl -X POST http://localhost:8080/api/v1/dialogflow/notification \
-H "Content-Type: application/json" \
-d '{
"text": "Tu documento ha sido aprobado",
"phoneNumber": "573001234567",
"hiddenParameters": {
"document_id": "DOC-2025-001"
}
}'
```
### Expected Behavior
- RAG should return relevant responses based on conversation context
- Response time should be similar to or better than Dialogflow
- All parameters should be preserved in conversation history
- Notification context should be used in RAG responses
## Migration Phases (Recommended)
### Phase 1: Development Testing (1 week)
- Deploy RAG to dev environment
- Set `INTENT_DETECTION_CLIENT=rag`
- Test all conversation flows manually
- Verify notification handling
### Phase 2: QA Environment (1 week)
- Deploy to QA with RAG enabled
- Run automated test suite
- Perform load testing
- Compare responses with Dialogflow baseline
### Phase 3: Production Pilot (1-2 weeks)
- Deploy to production with `INTENT_DETECTION_CLIENT=dialogflow` (Dialogflow still active)
- Gradually switch to RAG:
- Week 1: 10% of traffic
- Week 2: 50% of traffic
- Week 3: 100% of traffic
- Monitor metrics closely
### Phase 4: Full Migration
- Set `INTENT_DETECTION_CLIENT=rag` for all environments
- Keep Dialogflow config for potential rollback
- Monitor for 2 weeks before considering removal of Dialogflow dependencies
## Future Cleanup (Optional)
After RAG is stable in production for 1+ month:
### Phase 1: Deprecate Dialogflow
1. Add `@Deprecated` annotation to `DialogflowClientService`
2. Update documentation to mark Dialogflow as legacy
### Phase 2: Remove Dependencies (Optional)
Edit `pom.xml` and remove:
```xml
<!-- Can be removed after RAG is stable -->
<!--
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-dialogflow-cx</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>com.google.api</groupId>
<artifactId>gax</artifactId>
</dependency>
-->
```
### Phase 3: Code Cleanup
1. Remove `DialogflowClientService.java`
2. Remove `DialogflowRequestMapper.java`
3. Remove `DialogflowResponseMapper.java`
4. Remove Dialogflow-specific tests
5. Update documentation
**Note:** Only proceed with cleanup after confirming no rollback will be needed.
## Support
For issues or questions:
1. Check this guide and `docs/rag-api-specification.md`
2. Review application logs
3. Contact the RAG server team for API issues
4. Contact the integration layer team for mapping/configuration issues
## Summary
- **Minimal Code Changes:** Only configuration needed to switch
- **Safe Rollback:** Can switch back to Dialogflow instantly
- **Redundancy:** Both systems store data for safety
- **Gradual Migration:** Supports phased rollout
- **No External Impact:** API contracts unchanged

View File

@@ -1,440 +0,0 @@
# RAG Migration - Implementation Summary
## ✅ **Migration Complete**
All components for the Dialogflow → RAG migration have been successfully implemented and tested.
---
## 📦 **What Was Delivered**
### 1. Core Implementation (7 new files)
| File | Purpose | Lines | Status |
|------|---------|-------|--------|
| `IntentDetectionService.java` | Common interface for both implementations | 20 | ✅ Complete |
| `RagClientService.java` | HTTP client for RAG server | 180 | ✅ Complete |
| `RagRequestMapper.java` | DTO → RAG format conversion | 140 | ✅ Complete |
| `RagResponseMapper.java` | RAG → DTO conversion | 60 | ✅ Complete |
| `RagQueryRequest.java` | RAG request DTO | 25 | ✅ Complete |
| `RagQueryResponse.java` | RAG response DTO | 20 | ✅ Complete |
| `RagClientException.java` | Custom exception | 15 | ✅ Complete |
| `IntentDetectionConfig.java` | Feature flag configuration | 50 | ✅ Complete |
**Total:** ~510 lines of production code
### 2. Configuration Files (3 updated)
| File | Changes | Status |
|------|---------|--------|
| `application-dev.properties` | Added RAG configuration | ✅ Updated |
| `application-prod.properties` | Added RAG configuration | ✅ Updated |
| `application-qa.properties` | Added RAG configuration | ✅ Updated |
### 3. Service Integration (2 updated)
| File | Changes | Status |
|------|---------|--------|
| `ConversationManagerService.java` | Uses `IntentDetectionService` | ✅ Updated |
| `NotificationManagerService.java` | Uses `IntentDetectionService` | ✅ Updated |
| `DialogflowClientService.java` | Implements interface | ✅ Updated |
### 4. Test Suite (4 new test files)
| Test File | Tests | Coverage | Status |
|-----------|-------|----------|--------|
| `RagRequestMapperTest.java` | 15 tests | Request mapping | ✅ Complete |
| `RagResponseMapperTest.java` | 10 tests | Response mapping | ✅ Complete |
| `RagClientServiceTest.java` | 7 tests | Service unit tests | ✅ Complete |
| `RagClientIntegrationTest.java` | 12 tests | End-to-end with mock server | ✅ Complete |
**Total:** 44 comprehensive tests (~1,100 lines)
### 5. Documentation (3 new docs)
| Document | Purpose | Pages | Status |
|----------|---------|-------|--------|
| `rag-api-specification.md` | RAG API contract | 8 | ✅ Complete |
| `rag-migration-guide.md` | Migration instructions | 12 | ✅ Complete |
| `rag-testing-guide.md` | Testing documentation | 10 | ✅ Complete |
**Total:** ~30 pages of documentation
### 6. Dependency Updates
Added to `pom.xml`:
```xml
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.12.0</version>
<scope>test</scope>
</dependency>
```
---
## 🎯 **Key Features**
### ✅ **Zero-Downtime Migration**
- Switch between Dialogflow and RAG with a single environment variable
- No code deployment required to switch
- Instant rollback capability
### ✅ **Backward Compatible**
- All external APIs unchanged
- All DTOs preserved
- All existing services work without modification
### ✅ **Redundant Safety**
- Conversation history stored in both systems
- Session management preserved
- DLP obfuscation maintained
### ✅ **Production-Ready**
- Retry logic: 3 attempts with exponential backoff
- Timeout handling: 30-second default
- Error mapping: Comprehensive exception handling
- Logging: Detailed info, debug, and error logs
### ✅ **Fully Reactive**
- Native WebClient integration
- Project Reactor patterns
- Non-blocking I/O throughout
### ✅ **Comprehensive Testing**
- 44 tests across unit and integration levels
- Mock HTTP server for realistic testing
- Retry scenarios validated
- Edge cases covered
---
## 🔄 **How It Works**
### Configuration-Based Switching
**Use RAG:**
```bash
export INTENT_DETECTION_CLIENT=rag
export RAG_SERVER_URL=https://your-rag-server.com
export RAG_SERVER_API_KEY=your-api-key
```
**Use Dialogflow:**
```bash
export INTENT_DETECTION_CLIENT=dialogflow
```
### Request Flow
```
User Request
DLP Obfuscation
ConversationManagerService / NotificationManagerService
IntentDetectionService (interface)
├─→ DialogflowClientService (if client=dialogflow)
└─→ RagClientService (if client=rag)
RagRequestMapper
WebClient → RAG Server
RagResponseMapper
DetectIntentResponseDTO
Persist to Memorystore + Firestore
Response to User
```
---
## 📊 **Test Coverage**
### Unit Tests (32 tests)
**RagRequestMapper (15 tests):**
- ✅ Text input mapping
- ✅ Event input mapping
- ✅ Notification parameter extraction
- ✅ Phone number validation
- ✅ Parameter prefix removal
- ✅ Type determination
- ✅ Null/empty handling
**RagResponseMapper (10 tests):**
- ✅ Complete response mapping
- ✅ Response ID generation
- ✅ Null field handling
- ✅ Complex parameter types
- ✅ Long text handling
**RagClientService (7 tests):**
- ✅ Mapper integration
- ✅ Null validation
- ✅ Exception propagation
- ✅ Configuration variants
### Integration Tests (12 tests)
**RagClientIntegrationTest:**
- ✅ Full HTTP request/response cycle
- ✅ Request headers validation
- ✅ Notification context transmission
- ✅ Event-based inputs
- ✅ Retry logic (500, 503, 504)
- ✅ No retry on 4xx errors
- ✅ Timeout handling
- ✅ Complex parameter types
- ✅ Empty/missing field handling
---
## 🚀 **Ready to Deploy**
### Prerequisites
1. **RAG Server Running**
- Implement API per `docs/rag-api-specification.md`
- Endpoint: `POST /api/v1/query`
2. **Environment Variables Set**
```bash
INTENT_DETECTION_CLIENT=rag
RAG_SERVER_URL=https://your-rag-server.com
RAG_SERVER_API_KEY=your-api-key # optional
```
### Deployment Steps
1. **Build Application**
```bash
mvn clean package
```
2. **Run Tests**
```bash
mvn test
```
3. **Deploy to Dev**
```bash
# Deploy with RAG enabled
kubectl apply -f deployment-dev.yaml
```
4. **Verify Logs**
```
✓ Intent detection configured to use RAG client
RAG Client initialized successfully with endpoint: https://...
```
5. **Test Endpoints**
```bash
# Test conversation
curl -X POST http://localhost:8080/api/v1/dialogflow/detect-intent \
-H "Content-Type: application/json" \
-d '{"message": "Hola", "user": {"telefono": "123"}}'
```
---
## 📈 **Migration Phases**
### Phase 1: Development (1 week) - **READY NOW**
- ✅ Code complete
- ✅ Tests passing
- ✅ Documentation ready
- 🎯 Deploy to dev environment with `INTENT_DETECTION_CLIENT=rag`
### Phase 2: QA Testing (1 week)
- 🎯 Run automated test suite
- 🎯 Manual testing of all flows
- 🎯 Load testing
- 🎯 Compare responses with Dialogflow
### Phase 3: Production Pilot (2-3 weeks)
- 🎯 Deploy with feature flag
- 🎯 Gradual rollout: 10% → 50% → 100%
- 🎯 Monitor metrics (response time, errors)
- 🎯 Keep Dialogflow as fallback
### Phase 4: Full Migration
- 🎯 Set `INTENT_DETECTION_CLIENT=rag` for all environments
- 🎯 Monitor for 2 weeks
- 🎯 Remove Dialogflow dependencies (optional)
---
## 🔍 **Monitoring**
### Key Metrics
| Metric | Target | How to Monitor |
|--------|--------|----------------|
| Response Time (p95) | < 2s | Log entries: "RAG query successful" |
| Error Rate | < 0.5% | Log entries: "RAG query failed" |
| Retry Rate | < 5% | Log entries: "Retrying RAG call" |
| Success Rate | > 99.5% | Count successful vs failed requests |
### Log Patterns
**Success:**
```
INFO Initiating RAG query for session: <session-id>
INFO RAG query successful for session: <session-id>
```
**Failure:**
```
ERROR RAG server error for session <session-id>: status=500
ERROR RAG retries exhausted for session <session-id>
```
---
## 🛡️ **Rollback Plan**
If issues occur:
### Step 1: Switch Configuration (< 1 minute)
```bash
export INTENT_DETECTION_CLIENT=dialogflow
```
### Step 2: Restart Application
```bash
kubectl rollout restart deployment/capa-integracion
```
### Step 3: Verify
```
✓ Intent detection configured to use Dialogflow CX client
```
**No code changes needed. No data loss.**
---
## 📁 **File Structure**
```
capa-de-integracion/
├── docs/
│ ├── rag-api-specification.md [NEW - 250 lines]
│ ├── rag-migration-guide.md [NEW - 400 lines]
│ ├── rag-testing-guide.md [NEW - 350 lines]
│ └── rag-migration-summary.md [NEW - this file]
├── src/main/java/com/example/
│ ├── config/
│ │ └── IntentDetectionConfig.java [NEW - 50 lines]
│ ├── dto/rag/
│ │ ├── RagQueryRequest.java [NEW - 25 lines]
│ │ └── RagQueryResponse.java [NEW - 20 lines]
│ ├── exception/
│ │ └── RagClientException.java [NEW - 15 lines]
│ ├── mapper/rag/
│ │ ├── RagRequestMapper.java [NEW - 140 lines]
│ │ └── RagResponseMapper.java [NEW - 60 lines]
│ ├── service/base/
│ │ ├── IntentDetectionService.java [NEW - 20 lines]
│ │ ├── RagClientService.java [NEW - 180 lines]
│ │ └── DialogflowClientService.java [UPDATED]
│ ├── service/conversation/
│ │ └── ConversationManagerService.java [UPDATED]
│ └── service/notification/
│ └── NotificationManagerService.java [UPDATED]
├── src/main/resources/
│ ├── application-dev.properties [UPDATED]
│ ├── application-prod.properties [UPDATED]
│ └── application-qa.properties [UPDATED]
├── src/test/java/com/example/
│ ├── mapper/rag/
│ │ ├── RagRequestMapperTest.java [NEW - 280 lines]
│ │ └── RagResponseMapperTest.java [NEW - 220 lines]
│ ├── service/unit_testing/
│ │ └── RagClientServiceTest.java [NEW - 150 lines]
│ └── service/integration_testing/
│ └── RagClientIntegrationTest.java [NEW - 450 lines]
└── pom.xml [UPDATED]
```
---
## 🎉 **Benefits Achieved**
### Technical Benefits
- ✅ Cleaner architecture with interface abstraction
- ✅ Easier to switch implementations
- ✅ Better testability
- ✅ Simpler HTTP-based protocol vs gRPC
- ✅ No Protobuf complexity
### Operational Benefits
- ✅ Instant rollback capability
- ✅ No downtime during migration
- ✅ Gradual rollout support
- ✅ Better monitoring and debugging
### Business Benefits
- ✅ Freedom from Dialogflow limitations
- ✅ Custom RAG implementation control
- ✅ Cost optimization potential
- ✅ Better response quality (once RAG is tuned)
---
## 📞 **Support & Resources**
### Documentation
- **API Specification:** `docs/rag-api-specification.md`
- **Migration Guide:** `docs/rag-migration-guide.md`
- **Testing Guide:** `docs/rag-testing-guide.md`
### Key Commands
**Run All Tests:**
```bash
mvn test
```
**Run RAG Tests Only:**
```bash
mvn test -Dtest="**/rag/**/*Test"
```
**Build Application:**
```bash
mvn clean package
```
**Run Locally:**
```bash
mvn spring-boot:run -Dspring-boot.run.profiles=dev
```
---
## ✨ **Summary**
The RAG migration implementation is **production-ready** and includes:
-**~510 lines** of production code
-**~1,100 lines** of test code
-**~1,000 lines** of documentation
-**44 comprehensive tests**
-**Zero breaking changes**
-**Instant rollback support**
**Next Action:** Deploy to dev environment and test with real RAG server.
---
*Generated: 2025-02-22*
*Status: ✅ Ready for Deployment*

View File

@@ -1,412 +0,0 @@
# RAG Client Testing Guide
## Overview
This document describes the comprehensive test suite for the RAG client implementation, including unit tests and integration tests.
## Test Structure
```
src/test/java/com/example/
├── mapper/rag/
│ ├── RagRequestMapperTest.java (Unit tests for request mapping)
│ └── RagResponseMapperTest.java (Unit tests for response mapping)
├── service/unit_testing/
│ └── RagClientServiceTest.java (Unit tests for RAG client service)
└── service/integration_testing/
└── RagClientIntegrationTest.java (Integration tests with mock server)
```
## Test Coverage Summary
### 1. RagRequestMapperTest (15 tests)
**Purpose:** Validates conversion from `DetectIntentRequestDTO` to `RagQueryRequest`.
| Test | Description |
|------|-------------|
| `mapToRagRequest_withTextInput_shouldMapCorrectly` | Text input mapping |
| `mapToRagRequest_withEventInput_shouldMapCorrectly` | Event input mapping (LLM flow) |
| `mapToRagRequest_withNotificationParameters_shouldMapAsNotificationType` | Notification detection |
| `mapToRagRequest_withNotificationTextOnly_shouldMapNotificationContext` | Notification context |
| `mapToRagRequest_withMissingPhoneNumber_shouldThrowException` | Phone validation |
| `mapToRagRequest_withNullTextAndEvent_shouldThrowException` | Input validation |
| `mapToRagRequest_withEmptyTextInput_shouldThrowException` | Empty text validation |
| `mapToRagRequest_withNullRequestDTO_shouldThrowException` | Null safety |
| `mapToRagRequest_withNullQueryParams_shouldUseEmptyParameters` | Empty params handling |
| `mapToRagRequest_withMultipleNotificationParameters_shouldExtractAll` | Parameter extraction |
| `mapToRagRequest_withDefaultLanguageCode_shouldUseNull` | Language code handling |
**Key Scenarios Covered:**
- ✅ Text input mapping
- ✅ Event input mapping (for LLM hybrid flow)
- ✅ Notification parameter detection and extraction
- ✅ Phone number validation
- ✅ Parameter prefix removal (`notification_po_*` → clean keys)
- ✅ Request type determination (conversation vs notification)
- ✅ Null and empty input handling
### 2. RagResponseMapperTest (10 tests)
**Purpose:** Validates conversion from `RagQueryResponse` to `DetectIntentResponseDTO`.
| Test | Description |
|------|-------------|
| `mapFromRagResponse_withCompleteResponse_shouldMapCorrectly` | Full response mapping |
| `mapFromRagResponse_withNullResponseId_shouldGenerateOne` | Response ID generation |
| `mapFromRagResponse_withEmptyResponseId_shouldGenerateOne` | Empty ID handling |
| `mapFromRagResponse_withNullResponseText_shouldUseEmptyString` | Null text handling |
| `mapFromRagResponse_withNullParameters_shouldUseEmptyMap` | Null params handling |
| `mapFromRagResponse_withNullConfidence_shouldStillMapSuccessfully` | Confidence optional |
| `mapFromRagResponse_withEmptyParameters_shouldMapEmptyMap` | Empty params |
| `mapFromRagResponse_withComplexParameters_shouldMapCorrectly` | Complex types |
| `mapFromRagResponse_withMinimalResponse_shouldMapSuccessfully` | Minimal valid response |
| `mapFromRagResponse_withLongResponseText_shouldMapCorrectly` | Long text handling |
**Key Scenarios Covered:**
- ✅ Complete response mapping
- ✅ Response ID generation when missing
- ✅ Null/empty field handling
- ✅ Complex parameter types (strings, numbers, booleans, nested objects)
- ✅ Minimal valid responses
- ✅ Long text handling
### 3. RagClientServiceTest (7 tests)
**Purpose:** Unit tests for RagClientService behavior.
| Test | Description |
|------|-------------|
| `detectIntent_withValidRequest_shouldReturnMappedResponse` | Mapper integration |
| `detectIntent_withNullSessionId_shouldThrowException` | Session ID validation |
| `detectIntent_withNullRequest_shouldThrowException` | Request validation |
| `detectIntent_withMapperException_shouldPropagateAsIllegalArgumentException` | Error propagation |
| `constructor_withApiKey_shouldInitializeSuccessfully` | API key configuration |
| `constructor_withoutApiKey_shouldInitializeSuccessfully` | No API key |
| `constructor_withCustomConfiguration_shouldInitializeCorrectly` | Custom config |
**Key Scenarios Covered:**
- ✅ Mapper integration
- ✅ Null validation
- ✅ Exception propagation
- ✅ Configuration variants
- ✅ Initialization with/without API key
### 4. RagClientIntegrationTest (12 tests)
**Purpose:** End-to-end tests with mock HTTP server using OkHttp MockWebServer.
| Test | Description |
|------|-------------|
| `detectIntent_withSuccessfulResponse_shouldReturnMappedDTO` | Successful HTTP call |
| `detectIntent_withNotificationFlow_shouldSendNotificationContext` | Notification request |
| `detectIntent_withEventInput_shouldMapEventAsText` | Event handling |
| `detectIntent_with500Error_shouldRetryAndFail` | Retry on 500 |
| `detectIntent_with503Error_shouldRetryAndSucceed` | Retry success |
| `detectIntent_with400Error_shouldFailImmediatelyWithoutRetry` | No retry on 4xx |
| `detectIntent_withTimeout_shouldFailWithTimeoutError` | Timeout handling |
| `detectIntent_withEmptyResponseText_shouldMapSuccessfully` | Empty response |
| `detectIntent_withMissingResponseId_shouldGenerateOne` | Missing ID |
| `detectIntent_withComplexParameters_shouldMapCorrectly` | Complex params |
**Key Scenarios Covered:**
- ✅ Full HTTP request/response cycle
- ✅ Request headers validation (API key, session ID)
- ✅ Notification context in request body
- ✅ Event-based inputs
- ✅ Retry logic (exponential backoff on 500, 503, 504)
- ✅ No retry on client errors (4xx)
- ✅ Timeout handling
- ✅ Empty and missing field handling
- ✅ Complex parameter types
## Running Tests
### Run All Tests
```bash
mvn test
```
### Run Specific Test Class
```bash
mvn test -Dtest=RagRequestMapperTest
mvn test -Dtest=RagResponseMapperTest
mvn test -Dtest=RagClientServiceTest
mvn test -Dtest=RagClientIntegrationTest
```
### Run RAG-Related Tests Only
```bash
mvn test -Dtest="**/rag/**/*Test"
```
### Run with Coverage
```bash
mvn test jacoco:report
```
## Test Dependencies
The following dependencies are required for testing:
```xml
<!-- JUnit 5 (included in spring-boot-starter-test) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Reactor Test (for reactive testing) -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<!-- OkHttp MockWebServer (for integration tests) -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.12.0</version>
<scope>test</scope>
</dependency>
```
## Integration Test Details
### MockWebServer Usage
The integration tests use OkHttp's MockWebServer to simulate the RAG server:
```java
@BeforeEach
void setUp() throws IOException {
mockWebServer = new MockWebServer();
mockWebServer.start();
String baseUrl = mockWebServer.url("/").toString();
ragClientService = new RagClientService(baseUrl, ...);
}
@Test
void testExample() {
// Enqueue mock response
mockWebServer.enqueue(new MockResponse()
.setBody("{...}")
.setHeader("Content-Type", "application/json")
.setResponseCode(200));
// Make request and verify
StepVerifier.create(ragClientService.detectIntent(...))
.assertNext(response -> { /* assertions */ })
.verifyComplete();
// Verify request was sent correctly
RecordedRequest recordedRequest = mockWebServer.takeRequest();
assertEquals("/api/v1/query", recordedRequest.getPath());
}
```
### Retry Testing
The integration tests verify retry behavior:
**Scenario 1: Retry and Fail**
- Request 1: 500 error
- Request 2: 500 error (retry)
- Request 3: 500 error (retry)
- Result: Fails with `RagClientException`
**Scenario 2: Retry and Succeed**
- Request 1: 503 error
- Request 2: 503 error (retry)
- Request 3: 200 success (retry)
- Result: Success
**Scenario 3: No Retry on 4xx**
- Request 1: 400 error
- Result: Immediate failure (no retries)
## Reactive Testing with StepVerifier
All tests use `StepVerifier` for reactive stream testing:
```java
// Test successful flow
StepVerifier.create(ragClientService.detectIntent(...))
.assertNext(response -> {
assertEquals("expected", response.responseText());
})
.verifyComplete();
// Test error flow
StepVerifier.create(ragClientService.detectIntent(...))
.expectErrorMatches(throwable ->
throwable instanceof RagClientException)
.verify();
```
## Test Data
### Sample Phone Numbers
- `573001234567` - Standard test phone
### Sample Session IDs
- `test-session-123` - Standard test session
### Sample Request DTOs
**Text Input:**
```java
TextInputDTO textInputDTO = new TextInputDTO("¿Cuál es el estado de mi solicitud?");
QueryInputDTO queryInputDTO = new QueryInputDTO(textInputDTO, null, "es");
Map<String, Object> parameters = Map.of("telefono", "573001234567");
QueryParamsDTO queryParamsDTO = new QueryParamsDTO(parameters);
DetectIntentRequestDTO requestDTO = new DetectIntentRequestDTO(queryInputDTO, queryParamsDTO);
```
**Event Input:**
```java
EventInputDTO eventInputDTO = new EventInputDTO("LLM_RESPONSE_PROCESSED");
QueryInputDTO queryInputDTO = new QueryInputDTO(null, eventInputDTO, "es");
```
**Notification Flow:**
```java
Map<String, Object> parameters = new HashMap<>();
parameters.put("telefono", "573001234567");
parameters.put("notification_text", "Tu documento ha sido aprobado");
parameters.put("notification_po_document_id", "DOC-2025-001");
```
### Sample RAG Responses
**Success Response:**
```json
{
"response_id": "rag-resp-12345",
"response_text": "Tu solicitud está en proceso de revisión.",
"parameters": {
"extracted_entity": "solicitud",
"status": "en_proceso"
},
"confidence": 0.92
}
```
**Minimal Response:**
```json
{
"response_text": "OK",
"parameters": {}
}
```
## Debugging Tests
### Enable Debug Logging
Add to `src/test/resources/application-test.properties`:
```properties
logging.level.com.example.service.base.RagClientService=DEBUG
logging.level.com.example.mapper.rag=DEBUG
logging.level.okhttp3.mockwebserver=DEBUG
```
### View HTTP Requests/Responses
```java
@Test
void debugTest() throws Exception {
// ... test code ...
RecordedRequest request = mockWebServer.takeRequest();
System.out.println("Request path: " + request.getPath());
System.out.println("Request headers: " + request.getHeaders());
System.out.println("Request body: " + request.getBody().readUtf8());
}
```
## Test Maintenance
### When to Update Tests
- **RAG API changes:** Update `RagClientIntegrationTest` mock responses
- **DTO changes:** Update all mapper tests
- **New features:** Add corresponding test cases
- **Bug fixes:** Add regression tests
### Adding New Tests
1. **Identify test type:** Unit or integration?
2. **Choose test class:** Use existing or create new
3. **Follow naming convention:** `methodName_withCondition_shouldExpectedBehavior`
4. **Use AAA pattern:** Arrange, Act, Assert
5. **Add documentation:** Update this guide
## Continuous Integration
These tests should run automatically in CI/CD:
```yaml
# Example GitHub Actions workflow
- name: Run Tests
run: mvn test
- name: Generate Coverage Report
run: mvn jacoco:report
- name: Upload Coverage
uses: codecov/codecov-action@v3
```
## Test Coverage Goals
| Component | Target Coverage | Current Status |
|-----------|----------------|----------------|
| RagRequestMapper | 95%+ | ✅ Achieved |
| RagResponseMapper | 95%+ | ✅ Achieved |
| RagClientService | 85%+ | ✅ Achieved |
| Integration Tests | All critical paths | ✅ Complete |
## Common Issues and Solutions
### Issue: MockWebServer Port Conflict
**Problem:** Tests fail with "Address already in use"
**Solution:** Ensure `mockWebServer.shutdown()` is called in `@AfterEach`
### Issue: Timeout in Integration Tests
**Problem:** Tests hang or timeout
**Solution:**
- Check `mockWebServer.enqueue()` is called before request
- Verify timeout configuration in RagClientService
- Use shorter timeouts in tests
### Issue: Flaky Retry Tests
**Problem:** Retry tests sometimes fail
**Solution:**
- Don't rely on timing-based assertions
- Use deterministic mock responses
- Verify request count instead of timing
## Summary
The RAG client test suite provides comprehensive coverage:
-**44 total tests** across 4 test classes
-**Unit tests** for all mapper logic
-**Integration tests** with mock HTTP server
-**Retry logic** thoroughly tested
-**Error handling** validated
-**Edge cases** covered (null, empty, missing fields)
-**Reactive patterns** tested with StepVerifier
All tests use industry-standard testing libraries and patterns, ensuring maintainability and reliability.

207
locustfile.py Normal file
View File

@@ -0,0 +1,207 @@
"""Locust load testing for capa-de-integracion service.
Usage:
# Run with web UI (default port 8089)
locust --host http://localhost:8080
# Run headless with specific users and spawn rate
locust --host http://localhost:8080 --headless -u 100 -r 10
# Run for specific duration
locust --host http://localhost:8080 --headless -u 50 -r 5 --run-time 5m
"""
import random
from locust import HttpUser, between, task
class ConversationUser(HttpUser):
"""Simulate users interacting with the conversation API."""
wait_time = between(1, 3)
phone_numbers = [
f"555-{1000 + i:04d}" for i in range(100)
]
conversation_messages = [
"Hola",
"¿Cuál es mi saldo?",
"Necesito ayuda con mi tarjeta",
"¿Dónde está mi sucursal más cercana?",
"Quiero hacer una transferencia",
"¿Cómo puedo activar mi tarjeta?",
"Tengo un problema con mi cuenta",
"¿Cuáles son los horarios de atención?",
]
notification_messages = [
"Tu tarjeta fue bloqueada por seguridad",
"Se detectó un cargo de $1,500 en tu cuenta",
"Tu préstamo fue aprobado",
"Transferencia recibida: $5,000",
"Recordatorio: Tu pago vence mañana",
]
screen_contexts = [
"home",
"pagos",
"transferencia",
"prestamos",
"inversiones",
"lealtad",
"finanzas",
"capsulas",
"descubre",
"retiro-sin-tarjeta",
"detalle-tdc",
"detalle-tdd",
]
def on_start(self):
"""Called when a simulated user starts."""
self.phone = random.choice(self.phone_numbers)
self.nombre = f"Usuario_{self.phone.replace('-', '')}"
@task(5)
def health_check(self):
"""Health check endpoint - most frequent task."""
with self.client.get("/health", catch_response=True) as response:
if response.status_code == 200:
data = response.json()
if data.get("status") == "healthy":
response.success()
else:
response.failure("Health check returned unhealthy status")
else:
response.failure(f"Got status code {response.status_code}")
@task(10)
def detect_intent(self):
"""Test the main conversation endpoint."""
payload = {
"mensaje": random.choice(self.conversation_messages),
"usuario": {
"telefono": self.phone,
"nickname": self.nombre,
},
"canal": "web",
"pantallaContexto": random.choice(self.screen_contexts),
}
with self.client.post(
"/api/v1/dialogflow/detect-intent",
json=payload,
catch_response=True,
) as response:
if response.status_code == 200:
data = response.json()
if "responseId" in data or "queryResult" in data:
response.success()
else:
response.failure("Response missing expected fields")
elif response.status_code == 400:
response.failure(f"Validation error: {response.text}")
elif response.status_code == 500:
response.failure(f"Internal server error: {response.text}")
else:
response.failure(f"Unexpected status code: {response.status_code}")
@task(3)
def send_notification(self):
"""Test the notification endpoint."""
payload = {
"texto": random.choice(self.notification_messages),
"telefono": self.phone,
"parametrosOcultos": {
"transaction_id": f"TXN{random.randint(10000, 99999)}",
"amount": random.randint(100, 10000),
},
}
with self.client.post(
"/api/v1/dialogflow/notification",
json=payload,
catch_response=True,
) as response:
if response.status_code == 200:
response.success()
elif response.status_code == 400:
response.failure(f"Validation error: {response.text}")
elif response.status_code == 500:
response.failure(f"Internal server error: {response.text}")
else:
response.failure(f"Unexpected status code: {response.status_code}")
@task(4)
def quick_reply_screen(self):
"""Test the quick reply screen endpoint."""
payload = {
"usuario": {
"telefono": self.phone,
"nombre": self.nombre,
},
"pantallaContexto": random.choice(self.screen_contexts),
}
with self.client.post(
"/api/v1/quick-replies/screen",
json=payload,
catch_response=True,
) as response:
if response.status_code == 200:
data = response.json()
if "responseId" in data and "quick_replies" in data:
response.success()
else:
response.failure("Response missing expected fields")
elif response.status_code == 400:
response.failure(f"Validation error: {response.text}")
elif response.status_code == 500:
response.failure(f"Internal server error: {response.text}")
else:
response.failure(f"Unexpected status code: {response.status_code}")
class ConversationFlowUser(HttpUser):
"""Simulate realistic conversation flows with multiple interactions."""
wait_time = between(2, 5)
weight = 2
def on_start(self):
"""Initialize user session."""
self.phone = f"555-{random.randint(2000, 2999):04d}"
self.nombre = f"Flow_User_{random.randint(1000, 9999)}"
@task
def complete_conversation_flow(self):
"""Simulate a complete conversation flow."""
screen_payload = {
"usuario": {
"telefono": self.phone,
"nombre": self.nombre,
},
"pantallaContexto": "home",
}
self.client.post("/api/v1/quick-replies/screen", json=screen_payload)
conversation_steps = [
"Hola, necesito ayuda",
"¿Cómo puedo verificar mi saldo?",
"Gracias por la información",
]
for mensaje in conversation_steps:
payload = {
"mensaje": mensaje,
"usuario": {
"telefono": self.phone,
"nickname": self.nombre,
},
"canal": "mobile",
"pantallaContexto": "home",
}
self.client.post("/api/v1/dialogflow/detect-intent", json=payload)
self.wait()

View File

@@ -1,28 +0,0 @@
```mermaid
sequenceDiagram
participant U as Usuario
participant O as Orquestador (Spring Boot)
participant DB as Caché (Redis/Firestore)
participant DFCX as Dialogflow CX Agent
participant LLM as Vertex AI (Gemini)
Note over O: Recepción de Notificación Externa
O->>DB: Almacena sesión de notificación (NotificationSessionDTO)
O->>DFC_X: Envía texto "NOTIFICACION" + parámetros (notification_text)
U->>O: Hace pregunta: "¿Por qué fue rechazada?"
O->>LLM: Clasifica entrada (MessageEntryFilter)
LLM-->>O: Resultado: "NOTIFICATION" (Seguimiento)
O->>LLM: Resuelve contexto (NotificationContextResolver)
Note right of LLM: Usa HISTORIAL + METADATOS + PREGUNTA
LLM-->>O: Respuesta específica (ej: "Tu INE está vencida")
O->>DB: Guarda respuesta temporal con UUID
O->>DFC_X: Dispara evento 'LLM_RESPONSE_PROCESSED'
Note over DFCX: Orquestador Cognitivo (Playbook)
DFCX->>O: Webhook call: /api/v1/llm/tune-response (envía UUID)
O-->>DFCX: Devuelve respuesta formateada
DFCX-->>U: Muestra respuesta final amigable
```

View File

@@ -1,171 +0,0 @@
<instruccion_maestra>
- Analiza cada entrada del usuario y sigue las instrucciones detalladas en <reglas> para responder o redirigir la conversación.
- NUNCA respondas directamente las preguntas de productos de Banorte o Sigma o educación financiera; tu función es analizar y redirigir.
- Si el parámetro `$utterance` no tiene valor o no está definido, establece el valor del parámetro `$utterance` con el valor ingresado por el usuario.
- Solo saluda una vez al inicio de la conversacion
- Cuando tengas tu segunda interaccion con la persona no digas nada, espera el input del usuario
- SUMA en una nueva linea el contenido del parametro `$utterance` al parámetro `$historial` saltando una linea
- Utiliza el parámetro `$session.params.historial` y `$session.params.conversation_history` únicamente como referencia de lectura para entender el contexto. NUNCA intentes modificar, sumar o escribir en el parámetro `conversation_history`. Si el historial muestra una pregunta de seguimiento, usa esa información para identificar el `$query_inicial` más completo posible.
- **MUY IMPORTANTE:** Después de invocar un sub-playbook (como `playbook_nueva_conversacion` o `playbook_desambiguacion`), si ese sub-playbook retorna y ha establecido el parámetro de sesión `$session.params.pregunta_nueva` a "NO", significa que el sub-playbook o un flujo llamado por él ya ha proporcionado la respuesta completa al usuario para este turno. En este caso, este playbook ("Orquestador Cognitivo") NO DEBE generar NI enviar ninguna respuesta adicional. Tu turno termina después de que el sub-playbook concluye. Espera la siguiente entrada del usuario en el próximo turno.
- En cualquier momento de la conversacion que el usuario pregunta en que lo puedes ayudar, "cual es tu funcion", "que sabes hacer" o "quien eres"
- SI ya saludaste al usuario responde: "Te puedo responder sobre productos, servicios o temas financieros de Sigma. Aqui estamos para ayudarte 😉"
- SI NO saludaste al usuario responde: "Hola soy Beto tu asistente virtual de Sigma, te puedo responder sobre productos, servicios o temas financieros. Aqui estamos para ayudarte 😉"
- Inicia la conversacion con el paso <logica_de_conversacion>
- En cualquier momento de la conversacion que el usuario pida hablar con un agente, un humano o un asistente, procede con
- <manejo_de_solicitud_de_agente_humano> sin importar los parametros anteriores.
</instruccion_maestra>
<restricciones>
- Redirige al usuario exclusivamente cuando hable de temas relacionados con educacion financiera o servicios y productos de Banorte/Sigma por ejemplo:
- Préstamos y Créditos: Crédito y Adelanto de Nómina, Línea de Respaldo y Créditos Específicos.
- Cuentas y Manejo del Dinero: Cuentas Digitales, Gestión de la Cuenta y la App y Transacciones y Pagos.
- Tarjetas de Crédito y Débito: Tarjetas en General y Tarjetas Específicas.
- Inversiones: Fondos de Inversión y Cápsulas de Inversión (Cápsula Plus).
- Seguros y Productos Adicionales: Seguros.
- Interacción con el Asistente Conversacional: Capacidades del Asistente (Sigma bot).
- Información Personal y Notificaciones: Información de Nómina y Estado de Cuenta y Finanzas Personales.
- SI el mensaje del usuario `$utterance` esta relacionado con:
- Contratos legales
- Armas
- Abuso infantil
- Copyright y propiedad intelectual
- Delitos informáticos:
- Contenido explícito o perturbador:
- Acoso e intimidación
- Lenguaje de odio
- Actividades ilegales
- Drogas ilegales
- Delitos sexuales
- Radicalización y extremismo
- Suicidio y autolesiones
- Violencia
- Comportamientos peligrosos
- llama al ${FLOW:concluir_conversacion}
- Evita en todo momento:
- Tomar decisiones autónomas
- Proporcionar Información falsa
- Dar consejos especializados inapropiados
- Manipulación de temas
- Proporcionar datos privados o confidenciales
- SI el mensaje del usuario `$utterance` solicita informacion o servicios relacionados con otros bancos diferentes a Sigma, por ejemplo:
- Como descargo mi app BBVA
- Como obtengo mi amex
- Cual es el cajero Santander mas cercano
- Como cambio mi nomina de Banorte a Banamex
- Entonces responde: "Lo siento, esa info no la tengo. Pero si quieres saber más sobre productos, servicios o temas financieros, ¡ahí sí te puedo ayudar!"
- **NUNCA UTILICES NI REPITAS INFORMACIÓN OFUSCADA:** Si el mensaje del usuario `$utterance` contiene cualquiera de los siguientes patrones que representan datos sensibles, ignora completamente esa parte de la entrada y no la uses en tus respuestas ni la almacenes en variables:
- [NOMBRE]
- [CLABE]
- [NIP]
- [DIRECCION]
- [CORREO]
- [CLAVE_RASTREO]
- [NUM_ACLARACION]
- [SALDO]
- [CVV]
- [FECHA_VENCIMIENTO_TARJETA]
</restricciones>
<reglas>
- <reglas_de_prioridad_alta>
- <prioridad_1_abuso>
- SI el mensaje del usuario `$utterance` contiene lenguaje abusivo, emojis ofensivos o alguno de estos emojis 🎰, 🎲, 🃏, 🔞, 🧿, 🧛, 🧛🏻, 🧛🏼, 🧛🏽, 🧛🏾, 🧛🏿, 🧛‍♀️, 🧛🏻‍♀️, 🧛🏼‍♀️, 🧛🏽‍♀️, 🧛🏾‍♀️, 🧛🏿‍♀️, 🧛‍♂️, 🧛🏻‍♂️, 🧛🏼‍♂️, 🧛🏽‍♂️, 🧛🏾‍♂️, 🧛🏿‍♂️, 🧙, 🧙🏻, 🧙🏼, 🧙🏽, 🧙🏾, 🧙🏿, 🧙‍♀️, 🧙🏻‍♀️, 🧙🏼‍♀️, 🧙🏽‍♀️, 🧙🏾‍♀️, 🧙🏿‍♀️, 🧙‍♂️, 🧙🏻‍♂️, 🧙🏼‍♂️, 🧙🏽‍♂️, 🧙🏾‍♂️, 🧙🏿‍♂️, 🤡, 😈, 👿, 👹, 👺, 🚬, 🍺, 🍷, 🥃, 🍸, 🍻, ⛪, 🕌, 🕍, ✝️, ✡️, ⚧️, 🖕, 🖕🏻, 🖕🏼, 🖕🏽, 🖕🏾, 🖕🏿, 💩, 🫦, 👅, 👄, 💑, 👩‍❤️‍👨, 👩‍❤️‍👩, 👨‍❤️‍👨, 💏, 👩‍❤️‍💋‍👨, 👩‍❤️‍💋‍👩, 👨‍❤️‍💋‍👨, 🍆, 🍑, 💦, 👙, 🔫, 💣, 💀, ☠️, 🪓, 🧨, 🩸, 😠, 😡, 🤬, 😤, 🥵 o es spam
- Agradece el contacto al usuario y despidete, por ejemplo: ✨ "¡Mil gracias por tu tiempo! Aquí estaré para cuando me necesites. ¡Nos vemos en tu próxima consulta! 👋"
- llama al ${FLOW:concluir_conversacion}
- </prioridad_1_abuso>
- <prioridad_2_manejo_agente>
- SI el usuario solicita hablar con un agente humano, sigue la lógica de los 3 intentos definida en <manejo_de_solicitud_de_agente_humano> y detén el resto del análisis.
- </prioridad_2_manejo_agente>
- <prioridad_3_manejo_notificacion>
- SI el parámetro `$notificacion` tiene un valor (no es nulo),
- Establece el valor del parametro `$conversacion_notificacion` = "true",
- Establece el valor del parametro `$semaforo` = "1" y Ejecuta inmediatamente ${PLAYBOOK:playbook_desambiguacion}.
- Detén el resto del análisis.
- </prioridad_3_manejo_notificacion>
- </reglas_de_prioridad_alta>
- <logica_de_conversacion>
- En cualquier momento de la conversacion que el usuario pida hablar con un agente, un humano o un asistente, procede con <manejo_de_solicitud_de_agente_humano> sin importar los parametros anteriores
- <finalizacion>
- Si el usuario o el valor del parámetro `$utterance` indica que el usuario no necesita mas ayuda o quiere finalizar la conversación. Por ejemplo: "Eso es todo", "nada mas", "chau", "adios".
- Agradece el contacto al usuario y despidete, por ejemplo: Gracias por contactarte. Hasta luego! 👋.
- llama al ${FLOW:concluir_conversacion}
- </finalizacion>
- <paso_2_extraccion_de_intencion>
- <paso_1_extraer_intencion>
- Si el valor del parametro `$utterance` es unicamente un saludo sin pregunta:
- Ejemplo: "Que onda", "Hola", "Holi", "Que hubo", "Buenos dias", "Buenas", "que tal" o cualquier otra forma de saludo simple
- Entonces saluda con: "¡Qué onda! Soy Beto, tu asistente virtual de Sigma. ¿Cómo te puedo ayudar hoy? 🧐".
- Establece el valor de `$query_inicial` como "saludo"
- Finaliza el playbook
- SI NO es un saludo:
- Analiza el `$utterance` actual en el contexto de las líneas anteriores en:
- 1. Revisa `$historial` para el contexto de la sesión actual.
- 2. Revisa el parametro `$session.params.conversation_history` (si existe) para contexto de sesiones pasadas.
- 3. Usa ambas fuentes para desambiguar la solicitud.
Tu objetivo es formular un `$query_inicial` completo y autocontenido que represente la intención real del usuario. Para lograrlo, combina la información del `$utterance` actual con el contexto más relevante extraído de `$historial` y `$conversation_history` (si este último contiene datos de sesiones previas).
**Definición de "Contexto Relevante" en `$historial`:**
El contexto relevante incluye elementos clave como el tema principal o la entidad central de la conversación previa (ej., "tarjeta de credito") y cualquier detalle específico o modificador introducido anteriormente que sea necesario para entender el `$utterance` actual.
**Reglas para construir `$query_inicial`:**
1. **SI** el `$utterance` actual parece una continuación, una pregunta de seguimiento, o una frase corta e incompleta que probablemente depende del contexto previo en `$historial`:
* **CONSTRUYE** el `$query_inicial` integrando la solicitud del `$utterance` con el contexto relevante extraído de `$historial`. Asegúrate de que el `$query_inicial` sea claro y autónomo.
* *Ejemplo 1:*
* `$historial`: "quiero una tarjeta de credito"
* `$utterance`: "donde la solicito?"
* `$query_inicial` resultante: "donde solicito la tarjeta de credito?"
* *Ejemplo 2:*
* `$historial`: "HOLA\nquiero una tarjeta de credito"
* `$utterance`: "cuales son los requisitos?"
* `$query_inicial` resultante: "cuales son los requisitos para la tarjeta de credito?"
* *Ejemplo 3:*
* `$historial`: "HOLA\nque son las capsulas?"
* `$utterance`: "cual es la mejor?"
* `$query_inicial` resultante: "cual es la mejor capsula?"
* `$historial`: "HOLA\nque son las capsulas?\ncual es la mejor?"
* `$utterance`: "como la contrato?"
* `$query_inicial` resultante: "como contrato la mejor capsula?"
2. **SI** el `$utterance` introduce un tema completamente nuevo y **NO** está directamente relacionado con el contexto relevante en `$historial`:
* Establece el `$query_inicial` exactamente igual al `$utterance` actual.
* **EN ESTE CASO, Y SOLO EN ESTE CASO,** reemplaza el valor de `$historial` con el nuevo `$query_inicial`.
* *Ejemplo 3:*
* `$historial`: "queria saber sobre prestamos"
* `$utterance`: "y que tipos de cuentas tienen?"
* `$query_inicial` resultante: "que tipos de cuentas tienen?"
* `$historial` se actualiza a: "que tipos de cuentas tienen?"
- </paso_1_extraer_intencion>
- <paso_2_extraer_intencion> procede al <paso_3_enrutamiento_final> con el `$query_inicial` que has formulado. </paso_2_extraer_intencion>
- </paso_2_extraccion_de_intencion>
- <paso_3_enrutamiento_final>
- # === INICIO CHEQUEO CRÍTICO DE DETENCIÓN ===
- PRIMERO, VERIFICA el valor del parámetro de sesión `$session.params.pregunta_nueva`.
- SI `$session.params.pregunta_nueva` es exactamente igual a "NO":
- ENTONCES tu labor como Orquestador Cognitivo para este turno ha FINALIZADO. La respuesta requerida ya fue proporcionada por otro componente.
- **ABSOLUTAMENTE NO GENERES NINGUNA RESPUESTA ADICIONAL.**
- **NO EJECUTES NINGUNA OTRA ACCIÓN, LLAMADA A FLUJO O PLAYBOOK.**
- Termina tu ejecución para este turno INMEDIATAMENTE y espera la siguiente entrada del usuario.
- SI NO (si `$session.params.pregunta_nueva` NO es "NO" o no está definido):
- Utiliza las siguientes definiciones para decidir si es un <saludo> una <conversacion_en_curso> , si es una <conversacion_nueva> o un <query_invalido>.
- <query_invalido>
- Si el parámetro `$query_inicial` no tiene contenido o es vacío, rutea a ${FLOW:query_vacio_inadecuado}.
- </query_invalido>
- <saludo> Si el valor del parametro `$query_inicial` puedes interpretarlo como solo a un saludo.
- entonces saluda con: "¡Qué onda! Soy Beto, tu asistente virtual de Sigma. ¿Cómo te puedo ayudar hoy? 🧐" </saludo>
- <conversacion_en_curso>
- Si el parámetro `$contexto` tiene algún valor, establece el valor del parámetro `$conversacion_anterior` = "true", establece el valor del parametro `$semaforo` = "1" rutea a ${PLAYBOOK:playbook_desambiguacion}.
- </conversacion_en_curso>
- <conversacion_nueva>
- Si el parámetro `$contexto` está vacío, establece el valor del parámetro `$conversacion_anterior` = "false", rutea a ${PLAYBOOK:playbook_nueva_conversacion}.
- </conversacion_nueva>
- # === FIN CHEQUEO CRÍTICO DE DETENCIÓN ===
- </paso_3_enrutamiento_final>
- </logica_de_conversacion>
</reglas>
<manejo_de_solicitud_de_agente_humano>
- <primer_intento>
- Si el usuario solicita por primera vez hablar con un agente, responde: "Por el momento, para este tema debemos atenderte en el Call Center. Solo da click para llamar ahora mismo. 👇55 51 40 56 55"
- </primer_intento>
- <segundo_intento>
- Si el usuario lo solicita por segunda vez, responde: "Por el momento, para este tema debemos atenderte en el Call Center. Solo da click para llamar ahora mismo. 👇55 51 40 56 55"
- </segundo_intento>
- <tercer_intento>
- Si lo solicita por tercera vez, responde: "No puedo continuar con la conversación en este momento, gracias por contactarte." y establece el parámetro `$solicitud_agente_humano` = "true" y ejecuta ${FLOW:concluir_conversacion}.
- </tercer_intento>
</manejo_de_solicitud_de_agente_humano>
- **Recursos Disponibles:** ${FLOW:manejador_webhook_notificacion}

View File

@@ -1,80 +0,0 @@
- <instruccion_maestra>
- Tu rol es ser el "Playbook de Desambiguación". Tu función es analizar la respuesta de un usuario dentro de una conversación YA INICIADA (sea por una notificación o por una continuación de diálogo) y redirigirla al flujo apropiado. Tu única función es redirigir, NUNCA respondas directamente al usuario a menos que la lógica de fallback lo indique.
- Si el parametro `$semaforo` = "1" SIGNIFICA que fuiste llamado por el orquestador cognitivo y no puedes volver a llamarlo.
- Si el parametro `$semaforo` = "0" SIGNIFICA que revision_rag_respondio se ha ejecutado correctamente.
- <revision_rag_respondio>
- **MUY IMPORTANTE:** Después de invocar un flujo (como `manejador_query_RAG`), si ese flujo responde y ha establecido el parámetro de sesión `$session.params.pregunta_nueva` a "NO" o ha establecido el parámetro de `$session.params.response` distinto de nulo significa que ese flujo o un flujo llamado por él ya ha proporcionado la respuesta completa al usuario para este turno.
- ENTONCES tu tarea para este turno ha terminado
- **ABSOLUTAMENTE NO GENERES NINGUNA RESPUESTA ADICIONAL**
- **NO EJECUTES NINGUNA OTRA ACCION, LLAMADA A FLUJO O PLAYBOOK**
- </revision_rag_respondio>
- </instruccion_maestra>
- <reglas_de_prioridad_alta>
- <prioridad_1_abuso>
- SI el mensaje del usuario `$utterance` contiene lenguaje abusivo, ofensivo o es identificado como spam.
- ENTONCES, ejecuta inmediatamente el flujo ${FLOW:concluir_conversacion}.
- y detén todo el procesamiento posterior.
- </prioridad_1_abuso>
- <prioridad_2_condicion_de_guarda>
- Este playbook SOLO debe manejar conversaciones en curso.
- Si el valor del parámetro `$conversacion_notificacion` = "false" Y el valor del parámetro `$conversacion_anterior` = "false",
- ENTONCES, ejecuta el flujo ${FLOW:query_vacio_inadecuado}.
- </prioridad_2_condicion_de_guarda>
- </reglas_de_prioridad_alta>
- <logica_de_analisis_contextual_y_enrutamiento>
- <paso_1_definicion_del_contexto>
- DETERMINA el contexto relevante para el análisis:
- SI `$conversacion_notificacion` = "true", el contexto principal es el contenido del parámetro `$notificacion`.
- SI `$conversacion_anterior` = "true", el contexto principal es el contenido del parámetro `$contexto`.
- </paso_1_definicion_del_contexto>
- <paso_2_extraccion_de_intencion_contextual>
- ANALIZA cuidadosamente la expresión del usuario `$utterance` **tomando en cuenta el contexto definido en el paso <paso_1_definicion_del_contexto>**.
- IDENTIFICA el objetivo principal que el usuario expresa en `$utterance` y guárdalo en el parámetro `$query_inicial tomando en cuenta el contexto o la notificacion de acuerdo al <paso_1_definicion_del_contexto>`.
- </paso_2_extraccion_de_intencion_contextual>
- <paso_3_clasificacion_y_redireccion>
- EVALÚA el tema derivado del análisis de `$query_inicial`.
- **CASO A: Solicitud de informacion sobre conversaciones anteriores**
- SI el usuario solicita o consulta informacion sobre cuales fueron sus conversaciones anteriores con el agente, por ejemplo:
- "De que hablamos la semana pasada?"
- "De que conversamos anteriormente?"
- "Cuales fueron las ultimas preguntas que te hice?"
- "Que fue lo ultimo que me respondiste?"
- FINALIZA EL PLAYBOOK
- **CASO B: Determinar utilizando el historial (Lógica de reparación de contexto)**
- **ANALIZA** el `$utterance` actual (la pregunta del usuario) en el contexto del `$historial` (la conversación previa) para construir un **nuevo** `$query_inicial` autocontenido.
- <ejemplo_de_reparacion>
- `$historial` es: "¿Cuales capsulas hay?" y el `$utterance` es: "¿Cual es mejor?"
- ENTONCES:
- **nuevo** `$query_inicial` que construyas debe ser "¿Cual capsula es mejor?".
- </ejemplo_de_reparacion>
- **IDENTIFICA** el objetivo de este **nuevo** `$query_inicial` que acabas de construir.
- **SI** el tema de este **nuevo** `$query_inicial` trata sobre **productos, servicios o funcionalidades de la app** o sobre **educación financiera** por ejemplo:
- Préstamos y Créditos: Crédito y Adelanto de Nómina, Línea de Respaldo y Créditos Específicos.
- Cuentas y Manejo del Dinero: Cuentas Digitales, Gestión de la Cuenta y la App y Transacciones y Pagos.
- Tarjetas de Crédito y Débito: Tarjetas en General y Tarjetas Específicas.
- Inversiones: Fondos de Inversión y Cápsulas de Inversión (Cápsula Plus).
- Seguros y Productos Adicionales: Seguros.
- Interacción con el Asistente Conversacional: Capacidades del Asistente (Sigma bot).
- Información Personal y Notificaciones: Información de Nómina y Estado de Cuenta y Finanzas Personales.
- **ENTONCES,** ejecuta el flujo **${FLOW:manejador_query_RAG}** pasando este **nuevo** `$query_inicial` como parámetro.
- FINALIZA EL PLAYBOOK
- **CASO C: Imposible de Determinar**
- SI después del análisis contextual no se puede determinar segun la logica del `CASO A` ni del `CASO B`.
- ENTONCES, responde directamente con el siguiente texto: "Lo siento, esa info no la tengo. Pero si quieres saber más sobre productos, servicios o temas financieros, ¡ahí sí te puedo ayudar!"
- ACCIÓN POSTERIOR:
- Ejecuta el flujo ${FLOW:concluir_conversacion}.
- </paso_3_clasificacion_y_redireccion>
- </logica_de_analisis_contextual_y_enrutamiento>
- <manejo_de_no_coincidencia_fallback>
- Estas son las respuestas que deben configurarse en los manejadores de eventos "no-match" de Dialogflow. Se activan secuencialmente si, por alguna razón, la lógica principal no produce una redirección.
- <no-match-1>
- **RESPUESTA ESTÁTICA:** "No entendí muy bien tu pregunta, ¿podrías reformularla? Recuerda que puedo ayudarte con dudas sobre tus productos Banorte o darte tips de educación financiera. 😉"
- </no-match-1>
- <no-match-2>
- **RESPUESTA ESTÁTICA:** "Parece que sigo sin entender. ¿Tu duda es sobre **(1) Productos y Servicios** o **(2) Educación Financiera**?"
- </no-match-2>
- <no-match-3>
- **RESPUESTA ESTÁTICA:** ""Por el momento, para este tema debemos atenderte en el Call Center. Solo da click para llamar ahora mismo. 👇 55 51 40 56 55""
- **ACCIÓN POSTERIOR:** Inmediatamente después de enviar el mensaje, configurar la transición para ejecutar el flujo **${FLOW:concluir_conversacion}**.
- </no-match-3>
- </manejo_de_no_coincidencia_fallback>

View File

@@ -1,66 +0,0 @@
- <instruccion_maestra>
- Tu rol es ser el "Playbook de Conversación Nueva". Tu única función es analizar una nueva solicitud de un usuario, clasificarla y redirigirla al flujo correcto. NUNCA respondas directamente al usuario; solo redirige.
- <revision_rag_respondio>
- **MUY IMPORTANTE:** Después de invocar un flujo (como `manejador_query_RAG`), si ese flujo responde y ha establecido el parámetro de sesión `$session.params.pregunta_nueva` a "NO" o ha establecido el parámetro de `$session.params.response` distinto de nulo significa que ese flujo o un flujo llamado por él ya ha proporcionado la respuesta completa al usuario para este turno.
- ENTONCES tu tarea para este turno ha terminado
- **ABSOLUTAMENTE NO GENERES NINGUNA RESPUESTA ADICIONAL**
- **NO EJECUTES NINGUNA OTRA ACCION, LLAMADA A FLUJO O PLAYBOOK**
- </revision_rag_respondio>
- </instruccion_maestra>
- <reglas_de_prioridad_alta>
- <prioridad_1_abuso>
- SI el mensaje del usuario `$utterance` contiene lenguaje abusivo, emojis ofensivos o es spam
- llama al ${FLOW:concluir_conversacion}
- </prioridad_1_abuso>
- <prioridad_2_condicion_de_guarda>
- Este playbook SOLO debe ejecutarse para conversaciones nuevas.
- SI el parámetro `$conversacion_notificacion` = "true" O el parámetro `$conversacion_anterior` = "true".
- ENTONCES, considera que hubo un error de enrutamiento previo.
- llama al ${FLOW:concluir_conversacion} para evitar un bucle o una respuesta incorrecta.
- </prioridad_2_condicion_de_guarda>
- </reglas_de_prioridad_alta>
- <logica_de_analisis_y_enrutamiento>
- <paso_1_extraccion_de_intencion>
- ANALIZA cuidadosamente la expresión completa del usuario provista en el parámetro `$utterance`.
- IDENTIFICA el objetivo o la pregunta central del usuario y guárdalo en el parámetro `$query_inicial`.
- </paso_1_extraccion_de_intencion>
- <paso_2_clasificacion_y_redireccion>
- EVALÚA el tema derivado del análisis de `$query_inicial`.
- **CASO A: Solicitud de informacion sobre conversaciones anteriores**
- SI el usuario solicita o consulta informacion sobre cuales fueron sus conversaciones anteriores con el agente, por ejemplo:
- "De que hablamos la semana pasada?"
- "De que conversamos anteriormente?"
- "Cuales fueron las ultimas preguntas que te hice?"
- "Que fue lo ultimo que me respondiste?"
- FINALIZA EL PLAYBOOK
- **CASO B: Determinar utilizando el historial**
- ANALIZA cuidadosamente la expresión completa del usuario provista en el parámetro `$historial`.
- IDENTIFICA el objetivo o la pregunta central del usuario y guárdalo en el parámetro `$query_inicial` UTILIZANDO lo necesario de `$historial` para construirlo
- SI el tema trata sobre **productos, servicios o funcionalidades de la app** o sobre **educación financiera**.
- ENTONCES, ejecuta el flujo **${FLOW:manejador_query_RAG}** pasando `$query_inicial` como parámetro.
- FINALIZA EL PLAYBOOK
- **CASO C: Derivacion al flujo del RAG**
- SI el tema trata sobre **productos, servicios o funcionalidades de la app** o sobre **educación financiera**.
- ENTONCES, ejecuta el flujo **${FLOW:manejador_query_RAG}** pasando `$query_inicial` como parámetro.
- FINALIZA EL PLAYBOOK
- **CASO D: Imposible de Determinar**
- SI después del análisis contextual no se puede determinar segun la logica del `CASO A` ni del `CASO B` ni del `CASO C`.
- ENTONCES, responde directamente con el siguiente texto: "Lo siento, esa info no la tengo. Pero si quieres saber más sobre productos, servicios o temas financieros, ¡ahí sí te puedo ayudar!"
- ACCIÓN POSTERIOR:
- Despidete cordialmente.
- Ejecuta el flujo ${FLOW:concluir_conversacion}.
- </paso_2_clasificacion_y_redireccion>
- </logica_de_analisis_y_enrutamiento>
- <manejo_de_no_coincidencia_fallback>
- Estas son las respuestas que deben configurarse en los manejadores de eventos "no-match" de Dialogflow para este flujo/playbook. Se activan secuencialmente si el paso 2 no logra clasificar la intención.
- <no-match-1>
- RESPUESTA ESTÁTICA: "No entendí muy bien tu pregunta. ¿Podrías intentar de otra manera? Recuerda que los temas que manejo son productos del banco y educación financiera. 😉"
- </no-match-1>
- <no-match-2>
- RESPUESTA ESTÁTICA: "Sigo sin entender. Para poder ayudarte, por favor dime si tu duda es sobre (1) Productos y Servicios o (2) Educación Financiera."
- </no-match-2>
- <no-match-3>
- RESPUESTA ESTÁTICA: "Por el momento, para este tema debemos atenderte en el Call Center. Solo da click para llamar ahora mismo. 👇55 51 40 56 55"
- ACCIÓN POSTERIOR: Inmediatamente después de enviar el mensaje, configurar la transición para ejecutar el flujo ${FLOW:concluir_conversacion}.
- </no-match-3>
- </manejo_de_no_coincidencia_fallback>

241
pom 2.xml
View File

@@ -1,241 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.11</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>app-jovenes-service-orchestrator</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>app-jovenes-service-orchestrator</name>
<description>This serivce handle conversations over Dialogflow and multiple Storage GCP services</description>
<properties>
<java.version>21</java.version>
<spring-cloud-gcp.version>5.4.0</spring-cloud-gcp.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
<lettuce.version>6.4.0.RELEASE</lettuce.version>
<spring-framework.version>6.1.21</spring-framework.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-dependencies</artifactId>
<version>${spring-cloud-gcp.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.40.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2024.0.8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.5.0</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-firestore</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-data-firestore</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-storage</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-dialogflow-cx</artifactId>
</dependency>
<dependency>
<groupId>com.google.genai</groupId>
<artifactId>google-genai</artifactId>
<version>1.14.0</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>com.google.api</groupId>
<artifactId>gax</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-dlp</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http2</artifactId>
<version>4.1.125.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>4.1.125.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>4.1.125.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
<version>4.1.125.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
<version>4.1.125.Final</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.25.5</version>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<version>2.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.18.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -229,12 +229,6 @@
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
<version>3.18.0</version> <version>3.18.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.12.0</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>

85
pyproject.toml Normal file
View File

@@ -0,0 +1,85 @@
[project]
name = "capa-de-integracion"
version = "0.1.0"
description = "Orchestrator service for conversational AI - Python implementation"
readme = "README.md"
authors = [
{ name = "A8065384", email = "anibal.angulo.cardoza@banorte.com" }
]
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115.0",
"uvicorn[standard]>=0.32.0",
"pydantic>=2.10.0",
"pydantic-settings>=2.6.0",
"google-cloud-dialogflow-cx>=1.45.0",
"google-cloud-firestore>=2.20.0",
"google-cloud-aiplatform>=1.75.0",
"google-generativeai>=0.8.0",
"google-cloud-dlp>=3.30.0",
"redis[hiredis]>=5.2.0",
"tenacity>=9.0.0",
"python-multipart>=0.0.12",
"httpx>=0.27.0",
]
[project.scripts]
capa-de-integracion = "capa_de_integracion:main"
[build-system]
requires = ["uv_build>=0.9.22,<0.10.0"]
build-backend = "uv_build"
[dependency-groups]
dev = [
"fakeredis>=2.34.0",
"inline-snapshot>=0.32.1",
"locust>=2.43.3",
"pytest>=9.0.2",
"pytest-asyncio>=1.3.0",
"pytest-cov>=7.0.0",
"pytest-env>=1.5.0",
"pytest-recording>=0.13.4",
"ruff>=0.15.1",
"ty>=0.0.17",
]
[tool.ruff]
exclude = ["tests", "scripts"]
[tool.ruff.lint]
select = ['ALL']
ignore = ['D203', 'D213', 'COM812']
[tool.ty.src]
include = ["src"]
exclude = ["tests"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
testpaths = ["tests"]
addopts = [
"--cov=capa_de_integracion",
"--cov-report=term-missing",
"--cov-report=html",
"--cov-branch",
]
filterwarnings = [
"ignore:Call to '__init__' function with deprecated usage:DeprecationWarning:fakeredis",
"ignore:.*retry_on_timeout.*:DeprecationWarning",
"ignore:.*lib_name.*:DeprecationWarning",
"ignore:.*lib_version.*:DeprecationWarning",
]
env = [
"FIRESTORE_EMULATOR_HOST=[::1]:8462",
"GCP_PROJECT_ID=test-project",
"GCP_LOCATION=us-central1",
"GCP_FIRESTORE_DATABASE_ID=(default)",
"RAG_ENDPOINT_URL=http://localhost:8000/rag",
"REDIS_HOST=localhost",
"REDIS_PORT=6379",
"DLP_TEMPLATE_COMPLETE_FLOW=projects/test/dlpJobTriggers/test",
]

View File

@@ -18,7 +18,6 @@ import com.google.cloud.dialogflow.cx.v3.SessionName;
import com.google.cloud.dialogflow.cx.v3.SessionsSettings; import com.google.cloud.dialogflow.cx.v3.SessionsSettings;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@@ -33,8 +32,7 @@ import reactor.util.retry.Retry;
* all within a reactive programming context. * all within a reactive programming context.
*/ */
@Service @Service
@Qualifier("dialogflowClientService") public class DialogflowClientService {
public class DialogflowClientService implements IntentDetectionService {
private static final Logger logger = LoggerFactory.getLogger(DialogflowClientService.class); private static final Logger logger = LoggerFactory.getLogger(DialogflowClientService.class);
@@ -83,7 +81,6 @@ public class DialogflowClientService implements IntentDetectionService {
} }
} }
@Override
public Mono<DetectIntentResponseDTO> detectIntent( public Mono<DetectIntentResponseDTO> detectIntent(
String sessionId, String sessionId,
DetectIntentRequestDTO request) { DetectIntentRequestDTO request) {

View File

@@ -14,7 +14,7 @@ import com.example.mapper.conversation.ConversationEntryMapper;
import com.example.mapper.conversation.ExternalConvRequestMapper; import com.example.mapper.conversation.ExternalConvRequestMapper;
import com.example.mapper.messagefilter.ConversationContextMapper; import com.example.mapper.messagefilter.ConversationContextMapper;
import com.example.mapper.messagefilter.NotificationContextMapper; import com.example.mapper.messagefilter.NotificationContextMapper;
import com.example.service.base.IntentDetectionService; import com.example.service.base.DialogflowClientService;
import com.example.service.base.MessageEntryFilter; import com.example.service.base.MessageEntryFilter;
import com.example.service.base.NotificationContextResolver; import com.example.service.base.NotificationContextResolver;
import com.example.service.notification.MemoryStoreNotificationService; import com.example.service.notification.MemoryStoreNotificationService;
@@ -67,7 +67,7 @@ public class ConversationManagerService {
private static final String CONV_HISTORY_PARAM = "conversation_history"; private static final String CONV_HISTORY_PARAM = "conversation_history";
private static final String HISTORY_PARAM = "historial"; private static final String HISTORY_PARAM = "historial";
private final ExternalConvRequestMapper externalRequestToDialogflowMapper; private final ExternalConvRequestMapper externalRequestToDialogflowMapper;
private final IntentDetectionService intentDetectionService; private final DialogflowClientService dialogflowServiceClient;
private final FirestoreConversationService firestoreConversationService; private final FirestoreConversationService firestoreConversationService;
private final MemoryStoreConversationService memoryStoreConversationService; private final MemoryStoreConversationService memoryStoreConversationService;
private final QuickRepliesManagerService quickRepliesManagerService; private final QuickRepliesManagerService quickRepliesManagerService;
@@ -83,7 +83,7 @@ public class ConversationManagerService {
private final ConversationEntryMapper conversationEntryMapper; private final ConversationEntryMapper conversationEntryMapper;
public ConversationManagerService( public ConversationManagerService(
IntentDetectionService intentDetectionService, DialogflowClientService dialogflowServiceClient,
FirestoreConversationService firestoreConversationService, FirestoreConversationService firestoreConversationService,
MemoryStoreConversationService memoryStoreConversationService, MemoryStoreConversationService memoryStoreConversationService,
ExternalConvRequestMapper externalRequestToDialogflowMapper, ExternalConvRequestMapper externalRequestToDialogflowMapper,
@@ -97,7 +97,7 @@ public class ConversationManagerService {
LlmResponseTunerService llmResponseTunerService, LlmResponseTunerService llmResponseTunerService,
ConversationEntryMapper conversationEntryMapper, ConversationEntryMapper conversationEntryMapper,
@Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) { @Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) {
this.intentDetectionService = intentDetectionService; this.dialogflowServiceClient = dialogflowServiceClient;
this.firestoreConversationService = firestoreConversationService; this.firestoreConversationService = firestoreConversationService;
this.memoryStoreConversationService = memoryStoreConversationService; this.memoryStoreConversationService = memoryStoreConversationService;
this.externalRequestToDialogflowMapper = externalRequestToDialogflowMapper; this.externalRequestToDialogflowMapper = externalRequestToDialogflowMapper;
@@ -305,7 +305,7 @@ public class ConversationManagerService {
finalSessionId)) finalSessionId))
.doOnError(e -> logger.error("Error during user entry persistence for session {}: {}", finalSessionId, .doOnError(e -> logger.error("Error during user entry persistence for session {}: {}", finalSessionId,
e.getMessage(), e)) e.getMessage(), e))
.then(Mono.defer(() -> intentDetectionService.detectIntent(finalSessionId, request) .then(Mono.defer(() -> dialogflowServiceClient.detectIntent(finalSessionId, request)
.flatMap(response -> { .flatMap(response -> {
logger.debug( logger.debug(
"RTest eceived Dialogflow CX response for session {}. Initiating agent response persistence.", "RTest eceived Dialogflow CX response for session {}. Initiating agent response persistence.",
@@ -366,7 +366,7 @@ public class ConversationManagerService {
request.queryParams()) request.queryParams())
.withParameter("llm_reponse_uuid", uuid); .withParameter("llm_reponse_uuid", uuid);
return intentDetectionService.detectIntent(sessionId, newRequest) return dialogflowServiceClient.detectIntent(sessionId, newRequest)
.flatMap(response -> { .flatMap(response -> {
ConversationEntryDTO agentEntry = ConversationEntryDTO ConversationEntryDTO agentEntry = ConversationEntryDTO
.forAgent(response.queryResult()); .forAgent(response.queryResult());
@@ -387,7 +387,7 @@ public class ConversationManagerService {
.withParameters(notification.parametros()); .withParameters(notification.parametros());
} }
return persistConversationTurn(session, conversationEntryMapper.toConversationMessageDTO(userEntry)) return persistConversationTurn(session, conversationEntryMapper.toConversationMessageDTO(userEntry))
.then(intentDetectionService.detectIntent(sessionId, finalRequest) .then(dialogflowServiceClient.detectIntent(sessionId, finalRequest)
.flatMap(response -> { .flatMap(response -> {
ConversationEntryDTO agentEntry = ConversationEntryDTO ConversationEntryDTO agentEntry = ConversationEntryDTO
.forAgent(response.queryResult()); .forAgent(response.queryResult());

View File

@@ -14,7 +14,7 @@ import com.example.dto.dialogflow.conversation.ConversationSessionDTO;
import com.example.dto.dialogflow.notification.NotificationDTO; import com.example.dto.dialogflow.notification.NotificationDTO;
import com.example.mapper.conversation.ConversationEntryMapper; import com.example.mapper.conversation.ConversationEntryMapper;
import com.example.mapper.notification.ExternalNotRequestMapper; import com.example.mapper.notification.ExternalNotRequestMapper;
import com.example.service.base.IntentDetectionService; import com.example.service.base.DialogflowClientService;
import com.example.service.conversation.DataLossPrevention; import com.example.service.conversation.DataLossPrevention;
import com.example.service.conversation.FirestoreConversationService; import com.example.service.conversation.FirestoreConversationService;
import com.example.service.conversation.MemoryStoreConversationService; import com.example.service.conversation.MemoryStoreConversationService;
@@ -36,7 +36,7 @@ public class NotificationManagerService {
private static final String eventName = "notificacion"; private static final String eventName = "notificacion";
private static final String PREFIX_PO_PARAM = "notification_po_"; private static final String PREFIX_PO_PARAM = "notification_po_";
private final IntentDetectionService intentDetectionService; private final DialogflowClientService dialogflowClientService;
private final FirestoreNotificationService firestoreNotificationService; private final FirestoreNotificationService firestoreNotificationService;
private final MemoryStoreNotificationService memoryStoreNotificationService; private final MemoryStoreNotificationService memoryStoreNotificationService;
private final ExternalNotRequestMapper externalNotRequestMapper; private final ExternalNotRequestMapper externalNotRequestMapper;
@@ -50,7 +50,7 @@ public class NotificationManagerService {
private String defaultLanguageCode; private String defaultLanguageCode;
public NotificationManagerService( public NotificationManagerService(
IntentDetectionService intentDetectionService, DialogflowClientService dialogflowClientService,
FirestoreNotificationService firestoreNotificationService, FirestoreNotificationService firestoreNotificationService,
MemoryStoreNotificationService memoryStoreNotificationService, MemoryStoreNotificationService memoryStoreNotificationService,
MemoryStoreConversationService memoryStoreConversationService, MemoryStoreConversationService memoryStoreConversationService,
@@ -60,8 +60,8 @@ public class NotificationManagerService {
DataLossPrevention dataLossPrevention, DataLossPrevention dataLossPrevention,
ConversationEntryMapper conversationEntryMapper, ConversationEntryMapper conversationEntryMapper,
@Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) { @Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) {
this.intentDetectionService = intentDetectionService; this.dialogflowClientService = dialogflowClientService;
this.firestoreNotificationService = firestoreNotificationService; this.firestoreNotificationService = firestoreNotificationService;
this.memoryStoreNotificationService = memoryStoreNotificationService; this.memoryStoreNotificationService = memoryStoreNotificationService;
this.externalNotRequestMapper = externalNotRequestMapper; this.externalNotRequestMapper = externalNotRequestMapper;
@@ -147,7 +147,7 @@ public class NotificationManagerService {
DetectIntentRequestDTO detectIntentRequest = externalNotRequestMapper.map(obfuscatedRequest); DetectIntentRequestDTO detectIntentRequest = externalNotRequestMapper.map(obfuscatedRequest);
return intentDetectionService.detectIntent(sessionId, detectIntentRequest); return dialogflowClientService.detectIntent(sessionId, detectIntentRequest);
}) })
.doOnSuccess(response -> logger .doOnSuccess(response -> logger
.info("Finished processing notification. Dialogflow response received for phone {}.", telefono)) .info("Finished processing notification. Dialogflow response received for phone {}.", telefono))

View File

@@ -38,27 +38,12 @@ spring.data.redis.port=${REDIS_PORT}
# spring.data.redis.ssl.key-store=classpath:keystore.p12 # spring.data.redis.ssl.key-store=classpath:keystore.p12
# spring.data.redis.ssl.key-store-password=${REDIS_KEY_PWD} # spring.data.redis.ssl.key-store-password=${REDIS_KEY_PWD}
# ========================================================= # =========================================================
# Intent Detection Client Selection # Google Conversational Agents Configuration
# =========================================================
# Options: 'dialogflow' or 'rag'
# Set to 'dialogflow' to use Dialogflow CX (default)
# Set to 'rag' to use RAG server
intent.detection.client=${INTENT_DETECTION_CLIENT:dialogflow}
# =========================================================
# Google Conversational Agents Configuration (Dialogflow)
# ========================================================= # =========================================================
dialogflow.cx.project-id=${DIALOGFLOW_CX_PROJECT_ID} dialogflow.cx.project-id=${DIALOGFLOW_CX_PROJECT_ID}
dialogflow.cx.location=${DIALOGFLOW_CX_LOCATION} dialogflow.cx.location=${DIALOGFLOW_CX_LOCATION}
dialogflow.cx.agent-id=${DIALOGFLOW_CX_AGENT_ID} dialogflow.cx.agent-id=${DIALOGFLOW_CX_AGENT_ID}
dialogflow.default-language-code=${DIALOGFLOW_DEFAULT_LANGUAGE_CODE:es} dialogflow.default-language-code=${DIALOGFLOW_DEFAULT_LANGUAGE_CODE}
# =========================================================
# RAG Server Configuration
# =========================================================
rag.server.url=${RAG_SERVER_URL:http://localhost:8080}
rag.server.timeout=${RAG_SERVER_TIMEOUT:30s}
rag.server.retry.max-attempts=${RAG_SERVER_RETRY_MAX_ATTEMPTS:3}
rag.server.retry.backoff=${RAG_SERVER_RETRY_BACKOFF:1s}
rag.server.api-key=${RAG_SERVER_API_KEY:}
# ========================================================= # =========================================================
# Google Generative AI (Gemini) Configuration # Google Generative AI (Gemini) Configuration
# ========================================================= # =========================================================

View File

@@ -38,27 +38,12 @@ spring.data.redis.port=${REDIS_PORT}
# spring.data.redis.ssl.key-store=classpath:keystore.p12 # spring.data.redis.ssl.key-store=classpath:keystore.p12
# spring.data.redis.ssl.key-store-password=${REDIS_KEY_PWD} # spring.data.redis.ssl.key-store-password=${REDIS_KEY_PWD}
# ========================================================= # =========================================================
# Intent Detection Client Selection # Google Conversational Agents Configuration
# =========================================================
# Options: 'dialogflow' or 'rag'
# Set to 'dialogflow' to use Dialogflow CX (default)
# Set to 'rag' to use RAG server
intent.detection.client=${INTENT_DETECTION_CLIENT:dialogflow}
# =========================================================
# Google Conversational Agents Configuration (Dialogflow)
# ========================================================= # =========================================================
dialogflow.cx.project-id=${DIALOGFLOW_CX_PROJECT_ID} dialogflow.cx.project-id=${DIALOGFLOW_CX_PROJECT_ID}
dialogflow.cx.location=${DIALOGFLOW_CX_LOCATION} dialogflow.cx.location=${DIALOGFLOW_CX_LOCATION}
dialogflow.cx.agent-id=${DIALOGFLOW_CX_AGENT_ID} dialogflow.cx.agent-id=${DIALOGFLOW_CX_AGENT_ID}
dialogflow.default-language-code=${DIALOGFLOW_DEFAULT_LANGUAGE_CODE:es} dialogflow.default-language-code=${DIALOGFLOW_DEFAULT_LANGUAGE_CODE}
# =========================================================
# RAG Server Configuration
# =========================================================
rag.server.url=${RAG_SERVER_URL:http://localhost:8080}
rag.server.timeout=${RAG_SERVER_TIMEOUT:30s}
rag.server.retry.max-attempts=${RAG_SERVER_RETRY_MAX_ATTEMPTS:3}
rag.server.retry.backoff=${RAG_SERVER_RETRY_BACKOFF:1s}
rag.server.api-key=${RAG_SERVER_API_KEY:}
# ========================================================= # =========================================================
# Google Generative AI (Gemini) Configuration # Google Generative AI (Gemini) Configuration
# ========================================================= # =========================================================

Some files were not shown because too many files have changed in this diff Show More