Compare commits

...

10 Commits

Author SHA1 Message Date
734cade8d9 Improve coverage 2026-02-20 08:43:08 +00:00
5ceaadb20c Add echo client to app 2026-02-20 08:43:08 +00:00
59a76fc226 Add test coverage 2026-02-20 08:43:08 +00:00
c01f4d1ab3 Create rag-client package 2026-02-20 08:43:08 +00:00
2c722c1166 Fix type errors 2026-02-20 08:43:08 +00:00
58393a538e Fix lint errors 2026-02-20 08:43:08 +00:00
bcdc41ecd5 . 2026-02-20 08:43:08 +00:00
14ed21a1f9 . 2026-02-20 08:43:00 +00:00
03292a635c . 2026-02-20 08:42:56 +00:00
6f629c53a6 . 2026-02-20 08:42:45 +00:00
178 changed files with 9094 additions and 1144 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"]

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,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>

84
pyproject.toml Normal file
View File

@@ -0,0 +1,84 @@
[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",
"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']
[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]:8911",
"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",
]

231
scripts/load_test.py Executable file
View File

@@ -0,0 +1,231 @@
#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "locust>=2.32.5",
# ]
# ///
"""Locust load testing script for capa-de-integracion service.
Usage:
# Run with web UI (default port 8089)
uv run scripts/load_test.py
# Run headless with specific users and spawn rate
uv run scripts/load_test.py --headless -u 100 -r 10
# Run against specific host
uv run scripts/load_test.py --host http://localhost:8080
# Run for specific duration
uv run scripts/load_test.py --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) # Wait 1-3 seconds between tasks
host = "http://localhost:8080"
# Sample data for realistic load testing
phone_numbers = [
f"555-{1000 + i:04d}" for i in range(100)
] # 100 unique phone numbers
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",
"card_management",
"account_details",
"transfers",
"help_center",
]
def on_start(self):
"""Called when a simulated user starts."""
# Assign a phone number to this user for the session
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)
host = "http://localhost:8080"
weight = 2 # This user class will be 2x more likely to be chosen
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."""
# Step 1: Start with quick replies
screen_payload = {
"usuario": {
"telefono": self.phone,
"nombre": self.nombre,
},
"pantallaContexto": "help_center",
}
self.client.post("/api/v1/quick-replies/screen", json=screen_payload)
# Step 2: Have a conversation
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": "help_center",
}
self.client.post("/api/v1/dialogflow/detect-intent", json=payload)
# Small delay between messages
self.wait()
if __name__ == "__main__":
import os
import sys
# Set default host if not provided via command line
if "--host" not in sys.argv and "HOST" not in os.environ:
os.environ["HOST"] = "http://localhost:8080"
# Import and run locust
from locust import main as locust_main
# Run locust with command line arguments
sys.exit(locust_main.main())

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