DEV Community

Cover image for Integrando Azure ContentSafety para el análisis de feedback: Una prueba de concepto
Daniel J. Saldaña
Daniel J. Saldaña

Posted on • Originally published at danieljsaldana.dev on

Integrando Azure ContentSafety para el análisis de feedback: Una prueba de concepto

Bienvenidos a una nueva entrada en nuestro blog, donde exploraremos cómo integrar Azure ContentSafety en una aplicación de análisis de feedback. Esta guía paso a paso te llevará a través del proceso de establecer una arquitectura robusta que involucra frontend, backend y una base de datos, garantizando un entorno seguro para el manejo del contenido generado por los usuarios.

Paso 1: Configurar el entorno de desarrollo

Antes de comenzar, asegúrate de tener las siguientes herramientas y servicios configurados:

  • Node.js y npm instalados en tu máquina local.
  • Una cuenta de Azure con acceso al servicio Text Analytics.
  • Un entorno de base de datos PostgreSQL.

Paso 2: Preparar el frontend

Nuestra aplicación de feedback se construirá usando React. Crearemos un componente para enviar feedback y visualizar el análisis de sentimientos.

  1. Crea un nuevo proyecto React : Utiliza create-react-app para configurar tu entorno de frontend.

  2. Integra el componente AnalyzeFeedback : Este componente es responsable de enviar el título del feedback y mostrar los resultados del análisis.

'use client'

import { useMemo } from 'react'
import { useQuery } from 'react-query'
import { CompositeScore } from '@/core/feedback/domain/CompositeScore'
import request from '@/utils/request'
import s from './AnalyzeFeedback.module.scss'
import { AnalyzeFeedbackProps } from './AnalyzeFeedback.types'
import { CIRCUMFERENCE, getBackgroundColor, getOffset } from './AnalyzeFeedback.utils'

const AnalyzeFeedback = ({ title, className }: AnalyzeFeedbackProps) => {
  const { data, isLoading } = useQuery('getScore', async () =>
    request<CompositeScore>('api/getfeedbackscores', {
      method: 'POST',
      body: { title },
    })
  )

  const score = useMemo(() => data?.compositeScore ?? null, [data])
  const [backgroundColor, offset] = useMemo(() => [getBackgroundColor(score), getOffset(score)], [score])

  return (
    <div className={`rounded-full h-[24px] w-[22px] ${className ?? ''}`}>
      <svg width="24" height="24" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
        <g shapeRendering="geometricPrecision">
          <circle cx="64" cy="64" fill={backgroundColor} r="64" />
          <circle cx="64" cy="64" fill="none" r="48" stroke="rgba(0,0,0,.1)" strokeWidth="10" />
          {(isLoading || !score) && (
            <svg
              className={s.fadeIn}
              data-testid="geist-icon"
              fill="none"
              height="70"
              shapeRendering="geometricPrecision"
              stroke="#999"
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth="2"
              viewBox="0 0 24 24"
              width="70"
              x="29"
              y="29"
            >
              <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
            </svg>
          )}
          {!isLoading && score !== null && (
            <>
              <circle
                cx="64"
                cy="64"
                fill="none"
                r="48"
                stroke="white"
                strokeDasharray={`${CIRCUMFERENCE} ${CIRCUMFERENCE}`}
                strokeDashoffset={offset}
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth="10"
                className="score_progress__quTNG"
              />
              <text fill="white" fontSize="42" fontWeight="500" textAnchor="middle" x="64" y="79">
                {score}
              </text>
            </>
          )}
        </g>
      </svg>
    </div>
  )
}

export default AnalyzeFeedback

Enter fullscreen mode Exit fullscreen mode
  1. Estiliza tu componente : Usa los estilos predefinidos para hacer que tu componente sea visualmente atractivo y claro para los usuarios.
@keyframes fadeIn {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

.fadeIn {
    animation: fadeIn ease-in 1s;
    animation-fill-mode: forwards;
}

Enter fullscreen mode Exit fullscreen mode
  1. Define los tipos y utilidades : Establece las propiedades de tu componente y las funciones de utilidad para calcular la visualización de los datos.
import ClassName from '@/types/ClassName'

export type AnalyzeFeedbackProps = ClassName & {
  title: string
}

Enter fullscreen mode Exit fullscreen mode

y

export const CIRCUMFERENCE = 301.59289474462014 as const

export const getBackgroundColor = (score: number | null): string => {
  if (!score) {
    return '#d3d3d3'
  }

  if (score > 80) {
    return '#0ac769'
  }

  if (score >= 50 && score <= 79) {
    return '#ffad00'
  }

  return '#ea001e'
}

export const getOffset = (score: number | null): number => {
  if (!score) {
    return 0
  }

  return ((100 - score) / 100) * CIRCUMFERENCE
}

Enter fullscreen mode Exit fullscreen mode

Paso 3: Configurar el backend

El backend de nuestra aplicación se construirá con Node.js y se comunicará con los servicios de Azure para analizar el feedback.

  1. Establece la API de análisis de feedback : Crea una API que reciba el feedback, analice el sentimiento y las frases clave usando Azure Text Analytics, e inserte los resultados en la base de datos.
import { TextAnalyticsClient, AzureKeyCredential } from '@azure/ai-text-analytics';
import { enableCors } from "@/src/middleware/enableCors";
import { methodValidator } from "@/src/utils/methodValidator";
import { insertFeedback } from "@/src/core/insertFeedback"; 
import dotenv from 'dotenv';

dotenv.config();

const textAnalyticsClient = new TextAnalyticsClient(
    process.env.AZURE_TEXT_ANALYTICS_ENDPOINT, 
    new AzureKeyCredential(process.env.AZURE_TEXT_ANALYTICS_KEY)
);

async function analyzeFeedback(req, res) {
    try {
        await methodValidator(req, res, 'POST');
        if (res.headersSent) return;

        const { title, text, user } = req.body;
        if (!title || !text || !user) {
            return res.status(400).json({ error: 'Faltan datos requeridos para el análisis' });
        }

        const sentimentResult = await textAnalyticsClient.analyzeSentiment([text]);
        const sentiment = sentimentResult[0];

        const keyPhrasesResult = await textAnalyticsClient.extractKeyPhrases([text]);
        const keyPhrases = keyPhrasesResult[0];

        await insertFeedback(
            title, 
            sentiment.sentiment, 
            sentiment.confidenceScores.positive, 
            sentiment.confidenceScores.neutral, 
            sentiment.confidenceScores.negative, 
            user
        );

        res.status(200).json({
            title,
            user,
            sentiment: sentiment.sentiment,
            confidenceScores: sentiment.confidenceScores,
            keyPhrases: keyPhrases.keyPhrases,
        });
    } catch (error) {
        console.error('Error al analizar el feedback:', error);
        res.status(500).json({ error: `Error al analizar el feedback: ${error.message}` });
    }
}

export default enableCors(analyzeFeedback);

Enter fullscreen mode Exit fullscreen mode
  1. Desarrolla la API para obtener puntajes de feedback : Esta API recupera los promedios de los puntajes de feedback basados en el título proporcionado.
import { enableCors } from "@/src/middleware/enableCors";
import { methodValidator } from "@/src/utils/methodValidator";
import { sanitizeTitleForFilename } from "@/src/utils/sanitizeTitleForFilename";
import { getAverageFeedbackScores } from "@/src/core/getAverageFeedbackScores";
import dotenv from 'dotenv';

dotenv.config();

export async function getFeedbackScores(req, res) {
    await methodValidator(req, res, 'POST'); 
    if (res.headersSent) return; 

    const { title } = req.body; 
    if (!title) { 
        return res.status(400).json({ error: 'Falta el título para el análisis' });
    }

    const sanitizedTitle = sanitizeTitleForFilename(title); 
    console.log('Título sanitizado:', sanitizedTitle);

    try {
        const averages = await getAverageFeedbackScores(sanitizedTitle);

        if (!averages || averages.average_positive === 0 && averages.average_neutral === 0 && averages.average_negative === 0) {
            return res.status(200).json({ error: 'No se encontraron datos para el título proporcionado', compositeScore: null });
        }

        const avgPositive = parseFloat(averages.average_positive);
        const avgNeutral = parseFloat(averages.average_neutral);
        const avgNegative = parseFloat(averages.average_negative);

        console.log('Promedios convertidos:', avgPositive, avgNeutral, avgNegative);

        let compositeScore = (avgPositive + 0.5 * avgNeutral - avgNegative);
        compositeScore = Math.max(0, Math.min(1, compositeScore)) * 100;

        compositeScore = Math.round(compositeScore);

        console.log('Puntuación compuesta:', compositeScore);
        res.status(200).json({ compositeScore: compositeScore });
    } catch (error) {
        console.error('Error al obtener los promedios del feedback:', error);
        res.status(500).json({ error: `Error al obtener los promedios del feedback: ${error.message}` });
    }
}

export default enableCors(getFeedbackScores);

Enter fullscreen mode Exit fullscreen mode
  1. Implementa funciones de base de datos : Usa estas funciones para interactuar con tu base de datos PostgreSQL y manejar la inserción y recuperación de datos de feedback.
import { sql } from '@vercel/postgres'; 

export async function getAverageFeedbackScores(title) {
    try {
        const result = await sql`
            SELECT 
                AVG(positive) AS average_positive, 
                AVG(neutral) AS average_neutral, 
                AVG(negative) AS average_negative 
            FROM post_feedback 
            WHERE title = ${title}
            GROUP BY title;
        `;

        if (result.rows && result.rows.length > 0) {
            return result.rows[0];
        } else {
            return { average_positive: 0, average_neutral: 0, average_negative: 0 };
        }
    } catch (error) {
        console.error(`Error al obtener los promedios de feedback para el título: ${title}`, error);
        throw error;
    }
}

Enter fullscreen mode Exit fullscreen mode

y

import { sql } from '@vercel/postgres';

export async function insertFeedback(title, sentiment, positive, neutral, negative, user) {
    console.log(`Insertando feedback para el título: ${title}`);

    try {
        await sql`
            INSERT INTO post_feedback
            (title, sentiment, positive, neutral, negative, "user", last_updated)
            VALUES 
            (${title}, ${sentiment}, ${positive}, ${neutral}, ${negative}, ${user}, CURRENT_TIMESTAMP)
            ON CONFLICT (title, "user") -- Actualiza esto para que coincida con la nueva restricción única
            DO UPDATE SET 
                sentiment = EXCLUDED.sentiment,
                positive = EXCLUDED.positive,
                neutral = EXCLUDED.neutral,
                negative = EXCLUDED.negative,
                last_updated = CURRENT_TIMESTAMP;
        `;
        console.log(`Feedback insertado con éxito para el título: ${title}`);
    } catch (error) {
        console.error(`Error al insertar feedback para el título: ${title}`, error);
        throw new Error(`Error al insertar feedback: ${error.message}`);
    }
}

Enter fullscreen mode Exit fullscreen mode

Paso 4: Configuración de la base de datos

Antes de que puedas comenzar a almacenar y recuperar datos de feedback, necesitas configurar tu base de datos PostgreSQL. Usa la siguiente query SQL para crear la tabla necesaria para almacenar los datos de feedback:

CREATE TABLE post_feedback (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    sentiment VARCHAR(50) NOT NULL,
    positive NUMERIC NOT NULL,
    neutral NUMERIC NOT NULL,
    negative NUMERIC NOT NULL,
    "user" VARCHAR(255) NOT NULL,
    last_updated TIMESTAMP NOT NULL,
    UNIQUE(title, "user")
);

Enter fullscreen mode Exit fullscreen mode

Esta tabla almacenará cada pieza de feedback junto con el análisis de sentimientos y la marca de tiempo de la última actualización. Asegúrate de que tu base de datos esté en funcionamiento y accesible desde tu backend antes de continuar.

Paso 5: Prueba de concepto

Para probar la integración completa de tu sistema, utiliza Postman para simular solicitudes de cliente a tu backend. A continuación, te proporciono una guía para configurar las solicitudes en Postman:

Configuración en Postman:

  1. POST para analizar feedback :

  2. POST para obtener puntajes de feedback :

Asegúrate de tener tu servidor backend en ejecución antes de enviar las solicitudes desde Postman. Estas solicitudes simularán la interacción entre el frontend y el backend, permitiéndote evaluar la funcionalidad completa de tu aplicación de análisis de feedback.

Después de configurar y ejecutar estas pruebas, deberías poder ver cómo tu aplicación procesa y responde al feedback, cómo se almacena en la base de datos y cómo se recuperan y calculan los promedios de los puntajes de sentimiento. Esto completará tu prueba de concepto, demostrando la funcionalidad y la integración entre todas las partes de tu aplicación.

Paso 6: Configuración de variables de entorno

Para asegurar que tu aplicación maneje la información sensible de forma segura, como las URL de tu backend y las claves de API, debes configurar variables de entorno. Estas variables mantendrán tus datos seguros y facilitarán la configuración de tu aplicación en diferentes entornos (desarrollo, prueba, producción, etc.).

  1. Crear archivo .env : En la raíz de tu proyecto de backend, crea un archivo .env. Este archivo no debe subirse a tu repositorio de código (asegúrate de que .env esté listado en tu .gitignore).

  2. Agregar variables de entorno : Dentro del archivo .env, agrega las siguientes líneas:

  3. Acceder a las variables en tu aplicación : En tu código de backend, puedes acceder a estas variables de entorno utilizando process.env, por ejemplo:

Al usar variables de entorno, puedes cambiar fácilmente tus configuraciones entre diferentes entornos sin tener que cambiar tu código fuente. Además, ayuda a proteger tu información sensible, como las claves de API, manteniéndolas fuera de tu base de código público.

Después de configurar estas variables de entorno, tu aplicación backend debería poder utilizar estos valores para realizar operaciones, como conectarse a servicios externos o configurar rutas de API. Recuerda reiniciar tu servidor de backend si estaba ejecutándose cuando hiciste estos cambios para asegurarte de que las nuevas variables de entorno se carguen correctamente.

Top comments (0)