Resolviendo Preguntas Determinísticas con IA Generativa: Un Enfoque Práctico
Gerardo Arroyo
Posted on November 30, 2024
Introducción
En el dinámico panorama actual de la inteligencia artificial generativa, los modelos de lenguaje de gran escala (LLM) han transformado radicalmente nuestra interacción con la tecnología. Estos modelos han demostrado capacidades excepcionales en tareas como la generación de texto, análisis de sentimientos y comprensión contextual. Sin embargo, cuando nos enfrentamos a escenarios que requieren precisión absoluta y resultados determinísticos, nos encontramos con limitaciones inherentes que necesitan ser abordadas de manera innovadora.
El Desafío de los Modelos No Determinísticos
Fundamentos del Funcionamiento de LLMs
Los modelos de lenguaje de gran escala operan mediante un sistema probabilístico sofisticado. En su núcleo, estos modelos:
- Predicción Contextual: Analizan el contexto previo para predecir la siguiente palabra o secuencia más probable.
- Distribución de Probabilidad: Generan una distribución de probabilidades para diferentes opciones de respuesta.
- Temperatura y Aleatoriedad: Utilizan parámetros como la temperatura para controlar la creatividad vs. determinismo en sus respuestas.
Esta naturaleza probabilística es precisamente lo que hace a los LLMs tan versátiles para tareas creativas y de análisis, pero también lo que los hace menos confiables para consultas que requieren exactitud numérica o precisión absoluta.
Contexto: De una POC Fallida a una Solución Innovadora
Durante los últimos meses, mientras realizaba múltiples charlas sobre IA Generativa, una conversación particular captó mi atención. Un equipo de desarrollo me compartió su frustración con una prueba de concepto (POC) que consideraban fallida. El problema: su implementación de IA generativa para análisis de tickets de soporte producía resultados inconsistentes.
Al profundizar en el caso, emergió un patrón interesante:
Lo que Funcionaba Bien:
- "Analiza el ticket de soporte X"
- "¿Cuál es el sumario del caso Y?"
- "¿Qué sugiere este reporte de incidente?"
Estas preguntas, que requerían comprensión contextual y análisis cualitativo, recibían respuestas precisas y útiles.
Lo que Fallaba Consistentemente:
- "¿Cuál departamento tiene más tickets abiertos?"
- "¿Cuántos tickets fueron atendidos el mes pasado?"
- "¿Cuál es el tiempo promedio de resolución?"
Las preguntas que requerían precisión numérica y cálculos exactos nunca proporcionaban resultados confiables.
La Revelación Clave
La razón del fracaso se volvió evidente una vez que entendimos la naturaleza fundamental de los LLMs: son inherentemente no determinísticos. Su fortaleza radica en el procesamiento de lenguaje natural y la generación de contenido basado en probabilidades, no en realizar cálculos precisos o consultas exactas sobre datos estructurados.
Esta visión me llevó a reformular la pregunta clave:
¿Cómo podemos responder preguntas determinísticas cuando un LLM, por su naturaleza, no está diseñado para hacerlo?
La respuesta surgió al reconocer que no necesitábamos forzar al LLM a hacer algo para lo que no está diseñado. En su lugar, podíamos:
- Usar el LLM para lo que hace mejor: entender la intención de la pregunta.
- Traducir esa intención a consultas estructuradas cuando sea necesario.
- Utilizar herramientas especializadas para los cálculos precisos.
- Presentar los resultados de manera coherente y natural.
La Brecha entre Precisión y Probabilidad: Implementación de la Solución
Una vez identificado el núcleo del problema, desarrollé una propuesta que primero determina la naturaleza de la consulta y luego aplica el procesamiento apropiado.
Clasificación de Consultas
Consultas Determinísticas:
Características:
- Requieren conteos exactos y reproducibles.
- Involucran agregaciones sobre campos específicos del ticket.
- Operan sobre el esquema definido en Athena.
Ejemplos Reales:
- "¿Cuál departamento tiene más tickets abiertos?" SQL generado:
SELECT departamento, COUNT(*) as total
FROM tickets
WHERE estado != 'CLOSED'
GROUP BY departamento
ORDER BY total DESC
- "¿Cuál es el mayor causante de incidentes registrados?" SQL generado:
SELECT causante, COUNT(*) as total_incidentes
FROM tickets
WHERE solicitudes = 'Incidentes'
GROUP BY causante
ORDER BY total_incidentes DESC
LIMIT 1
Consultas No Determinísticas:
Características:
- Requieren análisis contextual del contenido del ticket.
- Se benefician del procesamiento de lenguaje natural.
- Son manejadas por el Knowledge Base de Bedrock.
Ejemplos:
- Análisis de contenido específico de tickets.
- Resúmenes de casos.
- Interpretación de patrones en reportes.
Flujo de Procesamiento
El flujo que decidí tomar para poder afrontar el reto se divide en tres pasos sencillos.
-
Evaluación Inicial
- Utiliza el prompt definido para determinar si la consulta es determinística. En este paso, y como veremos más adelante, uso un LLM para saber si lo que pregunta el usuario es por naturaleza determinístico o no.
- Cuando es determinística, el LLM genera el SQL apropiado dentro de tags
<SQL>
. Esto se basa en una tabla de Athena y un diccionario de datos.
-
Procesamiento
- Consultas determinísticas: Se ejecutan a través de Athena, enviamos un SQL creado por un LLM que satisface la consulta del usuario.
- Consultas no determinísticas: Se procesan mediante Amazon Bedrock - Knowledge Base. Esta base de conocimiento contiene el mismo archivo CSV que empleamos en Athena.
-
Formateo de Respuesta
- Los resultados de Athena se limitan a 25 registros (esto porque no deseamos que una pregunta sea capaz de retornar toda la base de datos).
- Se utiliza el LLM para convertir los resultados en respuestas naturales.
- Se mantiene la consistencia del idioma de la pregunta original.
Arquitectura de la Solución
La arquitectura implementada resuelve el desafío de las consultas determinísticas mediante una combinación estratégica de servicios AWS y procesamiento LLM. Analicemos cada componente y su implementación detallada.
1. Capa de Almacenamiento y Preparación de Datos
1.1 Estructura de Datos Base
El sistema opera sobre un archivo CSV alojado en S3 que contiene los registros de tickets. La preparación de estos datos es crucial y requiere:
CREATE EXTERNAL TABLE IF NOT EXISTS `default`.`tickets` (
`fechaResolucion` string,
`asignado` string,
`solicitudes` string,
`producto` string,
`departamento` string,
-- [resto de campos]
)
COMMENT "Tabla de tiquetes de Ejemplo"
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES ('field.delim' = ';')
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://MiBucket/'
TBLPROPERTIES ('classification' = 'csv');
Este DDL es fundamental porque:
- Define la estructura exacta que Athena utilizará para las consultas.
- Especifica el delimitador
;
para la correcta interpretación del CSV. - Establece la ubicación en S3 donde residen los datos.
- Configura el formato de entrada/salida para optimizar el procesamiento.
1.2 Diccionario de Datos
Junto con la estructura, mantenemos un diccionario de datos detallado que el LLM utilizará para entender el contexto de cada campo. Por ejemplo:
fechaResolucion: Campo que indica la fecha y hora de resolución del ticket.
Formato: mes/dia/anno hora:minuto
causante: Campo categórico que indica si el ticket fue levantado por A o B
departamento: Campo calculado descriptivo del departamento que atendió
2. Sistema de Clasificación de Consultas
2.1 Prompt de Clasificación
El primer paso crucial es determinar si una consulta es determinística. Implementamos esto mediante un prompt específico:
StringBuilder prompt = new StringBuilder(
"Eres un experto en análisis de tiquetes, necesito que analices " +
"la pregunta que te indico y si esa pregunta no puede ser respondida " +
"por un LLM (ya que es determinística) respondas solamente la frase " +
"'DETERMINISTICA' seguido de un SQL dentro de un tag <SQL> que cumpla " +
"con la definición de la siguiente tabla de Athena y su glosario..."
);
Este prompt es crítico porque:
- Define el rol específico del modelo.
- Establece el formato exacto de respuesta esperado.
- Incluye el contexto del schema y diccionario de datos.
- Fuerza una respuesta estructurada y procesable.
2.2 Generación de SQL mediante LLM
Una vez que el sistema ha identificado que la consulta es determinística, nos retorna el SQL a ser enviado a Athena para su ejecución. Esto se logra gracias a que indicamos en el prompt previo la definición de la tabla y el diccionario de datos.
En un artículo previo sobre el uso de Bedrock con RDS{:target="_blank"} expliqué cómo se puede usar un LLM para generar SQL; y esa experiencia previa forma parte de esta solución.
2.2.1 Configuración e Invocación del Modelo
var message = Message.builder()
.content(ContentBlock.fromText(prompt.toString()))
.role(ConversationRole.USER)
.build();
try {
var client = BedrockRuntimeClient.builder()
.credentialsProvider(DefaultCredentialsProvider.create())
.region(Region.US_EAST_1)
.build();
// Send the message with a basic inference configuration.
ConverseResponse response = client.converse(request -> request
.modelId(FOUNDATIONAL_MODEL)
.messages(message)
.inferenceConfig(config -> config
.maxTokens(512) // Suficiente para consultas SQL complejas
.temperature(0.5F) // Bajo para mayor precisión
.topP(0.9F))); // Alta coherencia en la estructura
// Retrieve the generated text from Bedrock's response object.
var responseText = response.output().message().content().get(0).text();
client.close();
return responseText;
} catch (SdkClientException e) {
System.err.printf("ERROR: Can't invoke '%s'. Reason: %s", FOUNDATIONAL_MODEL, e.getMessage());
return "No se puede responder esa pregunta";
}
2.2.2 Ejemplo de Flujo Completo
Para ilustrar el proceso, consideremos la pregunta: "¿Cuál departamento tiene más tickets abiertos?"
- Input Procesado por el Modelo:
[Todo el contexto anterior + schema + diccionario]
Pregunta: ¿Cuál departamento tiene más tickets abiertos?
- SQL Generado:
SELECT
departamento,
COUNT(*) as total_tickets
FROM tickets
WHERE fechaResolucion IS NULL
GROUP BY departamento
ORDER BY total_tickets DESC
LIMIT 25
El SQL generado se envía directamente a Athena para su ejecución, aprovechando que el modelo ya conoce la estructura exacta de la tabla y el significado de cada campo gracias al contexto proporcionado.
La clave del éxito de este enfoque radica en la precisión del contexto proporcionado al modelo y la consistencia en el formato de respuesta solicitado, permitiendo una generación confiable de consultas SQL que se ajusten exactamente a nuestro schema.
3. Procesamiento de Consultas Determinísticas
3.1 Ejecución de Consultas Athena
Una vez identificada una consulta determinística, el sistema ejecuta el SQL generado:
public String executeAthenaQuery(String query, String database) {
try (AthenaClient athenaClient = AthenaClient.builder()
.region(Region.US_EAST_1) // Ajusta la región según tu configuración
.credentialsProvider(DefaultCredentialsProvider.create())
.build()) {
// Configurar la solicitud de consulta
StartQueryExecutionRequest startQueryExecutionRequest = StartQueryExecutionRequest.builder()
.queryString(query)
.queryExecutionContext(QueryExecutionContext.builder()
.database(database)
.build())
.resultConfiguration(ResultConfiguration.builder()
.build())
.build();
// Iniciar la consulta
StartQueryExecutionResponse startQueryExecutionResponse = athenaClient.startQueryExecution(startQueryExecutionRequest);
String queryExecutionId = startQueryExecutionResponse.queryExecutionId();
// Esperar a que la consulta termine
waitForQueryToComplete(athenaClient, queryExecutionId);
// Obtener los resultados de la consulta
return getQueryResults(athenaClient, queryExecutionId);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error executing Athena query", e);
}
}
Este código:
- Establece una conexión segura con Athena.
- Ejecuta la consulta de manera asíncrona.
- Maneja el ID de ejecución para seguimiento.
4. Formateo de Respuestas
El último paso involucra transformar los resultados técnicos en respuestas comprensibles:
StringBuilder prompt = new StringBuilder(
"Eres un experto en atención a consultas, debes responder " +
"de manera profesional, concisa y clara. La pregunta realizada fue " +
preguntaUsuario + " y la respuesta de la base de datos es: " +
respuestaBD);
Este formateo:
- Mantiene el contexto de la pregunta original.
- Estructura la respuesta de manera natural.
- Preserva la precisión de los datos obtenidos.
5. Manejo de Consultas No Determinísticas
Cuando el sistema identifica una consulta como no determinística, significa que requiere análisis contextual o interpretativo que no puede resolverse mediante una consulta SQL directa. En este caso, el sistema utiliza directamente el modelo de Anthropic para procesar la consulta.
5.1 Identificación y Procesamiento
La identificación ocurre en el primer paso del proceso, cuando el modelo no retorna la palabra "DETERMINISTICA" seguida de un SQL. En este caso, el sistema procede a procesar la consulta utilizando directamente el modelo de Bedrock.
5.2 Configuración del Modelo
Para estas consultas, utilizamos la configuración base del modelo Anthropic Sonnet 3.5 v2:
RetrieveAndGenerateInput input = RetrieveAndGenerateInput.builder()
.text(prompt)
.build();
KnowledgeBaseRetrieveAndGenerateConfiguration knowledgeConfig = KnowledgeBaseRetrieveAndGenerateConfiguration
.builder()
.knowledgeBaseId(KNOWLEDGE_BASE_ID)
.modelArn(MODEL_ARN)
.build();
RetrieveAndGenerateConfiguration retrieveConfig = RetrieveAndGenerateConfiguration.builder()
.knowledgeBaseConfiguration(knowledgeConfig)
.type("KNOWLEDGE_BASE")
.build();
RetrieveAndGenerateRequest request1 = RetrieveAndGenerateRequest.builder()
.retrieveAndGenerateConfiguration(retrieveConfig)
.input(input)
.build();
RetrieveAndGenerateResponse response1 = bedrockAgentRuntimeClient.retrieveAndGenerate(request1);
5.3 Ejemplos de Consultas No Determinísticas
Las siguientes consultas son ejemplos típicos que el sistema procesa de manera interpretativa:
- Análisis de Contenido:
Pregunta: "¿Cuáles son los patrones comunes en los tickets de error de conexión?"
- Interpretación de Casos:
Pregunta: "¿Cómo se resolvió un caso similar la última vez?"
- Resúmenes Contextuales:
Pregunta: "Resume el problema principal del ticket #12345"
En estos casos, el sistema:
- No intenta generar un SQL.
- Procesa la consulta directamente a través del modelo.
- Proporciona una respuesta basada en el contexto y la información disponible.
- Mantiene el formato y tono consistente con la pregunta original.
La respuesta se entrega directamente al usuario, manteniendo la naturaleza conversacional y el contexto de la pregunta original.
Conclusiones y Próximos Pasos
La implementación de este sistema híbrido, que combina la precisión de las consultas SQL con la capacidad interpretativa de los modelos de lenguaje, representa apenas el inicio de lo que es posible lograr con la Inteligencia Artificial Generativa en el análisis de datos empresariales.
Reflexiones Clave
- La distinción automática entre consultas determinísticas y no determinísticas nos permite aprovechar lo mejor de ambos mundos: la exactitud de las bases de datos relacionales y la comprensión contextual de los LLMs.
- La arquitectura implementada demuestra que es posible mantener la precisión necesaria en entornos empresariales mientras se mejora significativamente la experiencia del usuario.
- El uso de servicios modernos como Amazon Bedrock nos permite implementar soluciones de IA avanzadas sin necesidad de gestionar infraestructura compleja y teniendo acceso a LLM de última generación.
Los invito a tomar este ejemplo como punto de partida para sus propias exploraciones. Ya sea que estén buscando mejorar sus sistemas de análisis de tickets, o que quieran aplicar estos conceptos a otros dominios completamente diferentes, las posibilidades son enormes.
La GenAI está transformando la manera en que interactuamos con los datos, y me emociona ser parte de esta transformación. ¿Te animas a ser parte de ella también?
Posted on November 30, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.