Athrun Data Intelligence


Mejorar la calidad de la respuesta para las consultas de los usuarios es esencial para las aplicaciones impulsadas por la IA, especialmente aquellas que se centran en la satisfacción del favorecido. Por ejemplo, un asistente basado en el chat de bienes humanos debe seguir estrictamente las políticas de la empresa y objetar usando un tono determinado. Una desviación de eso puede ser corregida por los comentarios de los usuarios. Esta publicación demuestra cómo Roca principio de Amazoncombinado con un conjunto de datos de feedback de usuarios y pocas solicitudes de disparo, puede refinar las respuestas para una longevo satisfacción del favorecido. Utilizando Amazon Titan Text Increddings v2demostramos una restablecimiento estadísticamente significativa en la calidad de la respuesta, por lo que es una utensilio valiosa para aplicaciones que buscan respuestas precisas y personalizadas.

Estudios recientes han destacado el valía de la feedback y la incorporación en la refinación de las respuestas de AI. Optimización rápida con comentarios humanos Propone un enfoque sistemático para asimilar de los comentarios de los usuarios, utilizándolo para ajustar los modelos iterativamente para mejorar la columna y la robustez. Similarmente, Optimización de solicitud de caja negra: alineando modelos de idiomas grandes sin capacitación en modelos Demuestra cómo la recuperación aumentada de la dependencia de pensamiento, la impulso restablecimiento el formación de pocos disparos al integrar el contexto relevante, permitiendo un mejor razonamiento y la calidad de la respuesta. Sobre la cojín de estas ideas, nuestro trabajo usa el Amazon Titan Text Increddings v2 Maniquí para optimizar las respuestas utilizando la feedback de los usuarios adecuado y la solicitud de pocos disparos, logrando mejoras estadísticamente significativas en la satisfacción del favorecido. Amazon Bedrock ya ofrece un optimización cibernética de inmediato La función para adaptar y optimizar automáticamente las indicaciones sin entrada adicional del favorecido. En esta publicación de blog, mostramos cómo usar las bibliotecas OSS para una optimización más personalizada basada en los comentarios de los usuarios y la solicitud de pocos disparos.

Hemos desarrollado una alternativa maña utilizando Amazon Bedrock que restablecimiento automáticamente las respuestas del asistente de chat en función de los comentarios de los usuarios. Esta alternativa utiliza incrustaciones y pocas solicitudes de disparo. Para demostrar la efectividad de la alternativa, utilizamos un conjunto de datos de comentarios de los usuarios disponibles públicamente. Sin secuestro, al aplicarlo en el interior de una empresa, el maniquí puede usar sus propios datos de feedback proporcionados por sus usuarios. Con nuestro conjunto de datos de prueba, muestra un aumento del 3.67% en los puntajes de satisfacción del favorecido. Los pasos secreto incluyen:

  1. Recupere un conjunto de datos de comentarios de los usuarios disponibles públicamente (para este ejemplo, Conjunto de datos de feedback unificada en la cara de sobo).
  2. Cree incrustaciones para consultas para capturar ejemplos similares semánticos, utilizando embedidas de texto de Amazon Titan.
  3. Use consultas similares como ejemplos en un mensaje de pocos disparos para gestar indicaciones optimizadas.
  4. Compare las indicaciones optimizadas con directo maniquí de idioma ilustre (LLM) llamadas.
  5. Valide la restablecimiento en la calidad de la respuesta utilizando una prueba t de muestra pareada.

El ulterior diagrama es una descripción militar del sistema.

Diagrama de flujo de trabajo de extremo a extremo que muestra cómo se procesan la retroalimentación y las consultas de los usuarios mediante la incrustación, la búsqueda semántica y la optimización de LLM

Los beneficios secreto del uso de la roca principio de Amazon son:

  • Papeleo de infraestructura cero – Implementación y escalera sin establecer la infraestructura de formación mecánico enredado (ML)
  • Rentable – Pague solo por lo que usa con la roca principio de Amazon cuota maniquí de precios
  • Seguridad de porción empresarial -Utilice las funciones de seguridad y cumplimiento incorporadas de AWS
  • Integración sencilla – Integre las aplicaciones existentes sin problemas y las herramientas de código libre
  • Opciones de maniquí múltiple – Acceda a varios modelos de cojín (FMS) para diferentes casos de uso

Las siguientes secciones se sumergen más profundamente en estos pasos, proporcionando fragmentos de código desde el cuaderno para ilustrar el proceso.

Requisitos previos

Los requisitos previos para la implementación incluyen una cuenta de AWS con Amazon Bedrock Access, Python 3.8 o posterior, y las credenciales de Amazon configuradas.

Compilación de datos

Descargamos un conjunto de datos de comentarios de los usuarios de Hugging Face, LLM-Blender/Unified-Feedback. El conjunto de datos contiene campos como conv_A_user (la consulta de favorecido) y conv_A_rating (una calificación binaria; 0 significa que al favorecido no le gusta y 1 significa que al favorecido le gusta). El ulterior código recupera el conjunto de datos y se centra en los campos necesarios para integrar la engendramiento y el investigación de feedback. Se puede ejecutar en un Amazon Sagemaker cuaderno o un cuaderno Jupyter que tiene entrada a Amazon Bedrock.

# Load the dataset and specify the subset
dataset = load_dataset("llm-blender/Unified-Feedback", "synthetic-instruct-gptj-pairwise")

# Access the 'train' split
train_dataset = dataset("train")

# Convert the dataset to Pandas DataFrame
df = train_dataset.to_pandas()

# Flatten the nested conversation structures for conv_A and conv_B safely
df('conv_A_user') = df('conv_A').apply(lambda x: x(0)('content') if len(x) > 0 else None)
df('conv_A_assistant') = df('conv_A').apply(lambda x: x(1)('content') if len(x) > 1 else None)

# Drop the llamativo nested columns if they are no longer needed
df = df.drop(columns=('conv_A', 'conv_B'))

Gestación de muestreo de datos e incrustación

Para establecer el proceso de forma efectiva, probamos 6,000 consultas del conjunto de datos. Utilizamos Amazon Titan Text Increddings V2 para crear incrustaciones para estas consultas, transformando el texto en representaciones de entrada dimensión que permiten comparaciones de similitud. Vea el ulterior código:

import random import bedrock # Take a sample of 6000 queries 
df = df.shuffle(seed=42).select(range(6000)) 
# AWS credentials
session = boto3.Session()
region = 'us-east-1'
# Initialize the S3 client
s3_client = boto3.client('s3')

boto3_bedrock = boto3.client('bedrock-runtime', region)
titan_embed_v2 = BedrockEmbeddings(
    client=boto3_bedrock, model_id="amazon.titan-embed-text-v2:0")
    
# Function to convert text to embeddings
def get_embeddings(text):
    response = titan_embed_v2.embed_query(text)
    return response  # This should return the embedding vector

# Apply the function to the 'prompt' column and store in a new column
df_test('conv_A_user_vec') = df_test('conv_A_user').apply(get_embeddings)

Pequeños de disparos con búsqueda de similitud

Para esta parte, tomamos los siguientes pasos:

  1. Muestra 100 consultas del conjunto de datos para las pruebas. El muestreo de 100 consultas nos ayuda a ejecutar múltiples pruebas para validar nuestra alternativa.
  2. Calcular similitud de coseno (Medida de similitud entre dos vectores distintos de cero) entre los incrustaciones de estas consultas de prueba y las 6,000 incrustaciones almacenadas.
  3. Seleccione las consultas similares K similares a las consultas de prueba para servir como pocos ejemplos de disparos. Establecemos K = 10 para equilibrar entre la eficiencia computacional y la riqueza de los ejemplos.

Vea el ulterior código:

# Step 2: Define cosine similarity function
def compute_cosine_similarity(embedding1, embedding2):
embedding1 = np.array(embedding1).reshape(1, -1) # Reshape to 2D array
embedding2 = np.array(embedding2).reshape(1, -1) # Reshape to 2D array
return cosine_similarity(embedding1, embedding2)(0)(0)

# Sample query embedding
def get_matched_convo(query, df):
    query_embedding = get_embeddings(query)
    
    # Step 3: Compute similarity with each row in the DataFrame
    df('similarity') = df('conv_A_user_vec').apply(lambda x: compute_cosine_similarity(query_embedding, x))
    
    # Step 4: Sort rows based on similarity score (descending order)
    df_sorted = df.sort_values(by='similarity', ascending=False)
    
    # Step 5: Filter or get top matching rows (e.g., top 10 matches)
    top_matches = df_sorted.head(10) 
    
    # Print top matches
    return top_matches(('conv_A_user', 'conv_A_assistant','conv_A_rating','similarity'))

Este código proporciona un contexto de pocos disparos para cada consulta de prueba, utilizando una similitud de coseno para recuperar las coincidencias más cercanas. Estas consultas de ejemplo y comentarios sirven como contexto adicional para gobernar la optimización de inmediato. La ulterior función genera el mensaje de pocos disparos:

import boto3
from langchain_aws import ChatBedrock
from pydantic import BaseModel

# Initialize Amazon Bedrock client
bedrock_runtime = boto3.client(service_name="bedrock-runtime", region_name="us-east-1")

# Configure the model to use
model_id = "us.anthropic.claude-3-5-haiku-20241022-v1:0"
model_kwargs = {
"max_tokens": 2048,
"temperature": 0.1,
"top_k": 250,
"top_p": 1,
"stop_sequences": ("nnHuman"),
}

# Create the LangChain Chat object for Bedrock
llm = ChatBedrock(
client=bedrock_runtime,
model_id=model_id,
model_kwargs=model_kwargs,
)

# Pydantic model to validate the output prompt
class OptimizedPromptOutput(BaseModel):
optimized_prompt: str

# Function to generate the few-shot prompt
def generate_few_shot_prompt_only(user_query, nearest_examples):
    # Ensure that df_examples is a DataFrame
    if not isinstance(nearest_examples, pd.DataFrame):
    raise ValueError("Expected df_examples to be a DataFrame")
    # Construct the few-shot prompt using nearest matching examples
    few_shot_prompt = "Here are examples of user queries, LLM responses, and feedback:nn"
    for i in range(len(nearest_examples)):
    few_shot_prompt += f"User Query: {nearest_examples.loc(i,'conv_A_user')}n"
    few_shot_prompt += f"LLM Response: {nearest_examples.loc(i,'conv_A_assistant')}n"
    few_shot_prompt += f"User Feedback: {'👍' if nearest_examples.loc(i,'conv_A_rating') == 1.0 else '👎'}nn"
    
    # Add the user query for which the optimized prompt is required
    few_shot_prompt += f"Based on these examples, generate a militar optimized prompt for the following user query:nn"
    few_shot_prompt += f"User Query: {user_query}n"
    few_shot_prompt += "Optimized Prompt: Provide a clear, well-researched response based on accurate data and credible sources. Avoid unnecessary information or speculation."
    
    return few_shot_prompt

El get_optimized_prompt La función realiza las siguientes tareas:

  1. La consulta de favorecido y los ejemplos similares generan un mensaje de pocos disparos.
  2. Usamos el mensaje de pocos disparos en una emplazamiento LLM para gestar un aviso optimizado.
  3. Asegúrese de que la salida esté en el ulterior formato usando Pydantic.

Vea el ulterior código:

# Function to generate an optimized prompt using Bedrock and return only the prompt using Pydantic
def get_optimized_prompt(user_query, nearest_examples):
    # Generate the few-shot prompt
    few_shot_prompt = generate_few_shot_prompt_only(user_query, nearest_examples)
    
    # Call the LLM to generate the optimized prompt
    response = llm.invoke(few_shot_prompt)
    
    # Extract and validate only the optimized prompt using Pydantic
    optimized_prompt = response.content # Fixed to access the 'content' attribute of the AIMessage object
    optimized_prompt_output = OptimizedPromptOutput(optimized_prompt=optimized_prompt)
    
    return optimized_prompt_output.optimized_prompt

# Example usage
query = "Is the US dollar weakening over time?"
nearest_examples = get_matched_convo(query, df_test)
nearest_examples.reset_index(drop=True, inplace=True)

# Generate optimized prompt
optimized_prompt = get_optimized_prompt(query, nearest_examples)
print("Optimized Prompt:", optimized_prompt)

El make_llm_call_with_optimized_prompt La función utiliza un aviso optimizado y una consulta de favorecido para hacer la emplazamiento LLM (Claude Haiku 3.5) LLM para obtener la respuesta final:

# Function to make the LLM call using the optimized prompt and user query
def make_llm_call_with_optimized_prompt(optimized_prompt, user_query):
    start_time = time.time()
    # Combine the optimized prompt and user query to form the input for the LLM
    final_prompt = f"{optimized_prompt}nnUser Query: {user_query}nResponse:"

    # Make the call to the LLM using the combined prompt
    response = llm.invoke(final_prompt)
    
    # Extract only the content from the LLM response
    final_response = response.content  # Extract the response content without adding any labels
    time_taken = time.time() - start_time
    return final_response,time_taken

# Example usage
user_query = "How to grow avocado indoor?"
# Assume 'optimized_prompt' has already been generated from the previous step
final_response,time_taken = make_llm_call_with_optimized_prompt(optimized_prompt, user_query)
print("LLM Response:", final_response)

Evaluación comparativa de indicaciones optimizadas y no optimizadas

Para comparar la solicitud optimizada con la carrera de cojín (en este caso, el aviso no optimizado), definimos una función que devolvió un resultado sin una solicitud optimizada para todas las consultas en el conjunto de datos de evaluación:

def get_unoptimized_prompt_response(df_eval):
    # Iterate over the dataframe and make LLM calls
    for index, row in tqdm(df_eval.iterrows()):
        # Get the user query from 'conv_A_user'
        user_query = row('conv_A_user')
        
        # Make the Bedrock LLM call
        response = llm.invoke(user_query)
        
        # Store the response content in a new column 'unoptimized_prompt_response'
        df_eval.at(index, 'unoptimized_prompt_response') = response.content  # Extract 'content' from the response object
    
    return df_eval

La ulterior función genera la respuesta de consulta utilizando la búsqueda de similitud y la engendramiento de aviso optimizado intermedio para todas las consultas en el conjunto de datos de evaluación:

def get_optimized_prompt_response(df_eval):
    # Iterate over the dataframe and make LLM calls
    for index, row in tqdm(df_eval.iterrows()):
        # Get the user query from 'conv_A_user'
        user_query = row('conv_A_user')
        nearest_examples = get_matched_convo(user_query, df_test)
        nearest_examples.reset_index(drop=True, inplace=True)
        optimized_prompt = get_optimized_prompt(user_query, nearest_examples)
        # Make the Bedrock LLM call
        final_response,time_taken = make_llm_call_with_optimized_prompt(optimized_prompt, user_query)
        
        # Store the response content in a new column 'unoptimized_prompt_response'
        df_eval.at(index, 'optimized_prompt_response') = final_response  # Extract 'content' from the response object
    
    return df_eval

Este código compara las respuestas generadas con y sin optimización de pocas disparos, configurando los datos para la evaluación.

LLM como magistrado y evaluación de respuestas

Para cuantificar la calidad de la respuesta, utilizamos un LLM como magistrado para adscribir las respuestas optimizadas y no optimizadas para la columna con la consulta del favorecido. Utilizamos Pydantic aquí para asegurarnos de que la salida se adhiera al patrón deseado de 0 (LLM predice que el favorecido no le gusta la respuesta) o 1 (LLM predice que el favorecido le gustará la respuesta):

# Define Pydantic model to enforce predicted feedback as 0 or 1
class FeedbackPrediction(BaseModel):
    predicted_feedback: conint(ge=0, le=1)  # Only allow values 0 or 1

# Function to generate few-shot prompt
def generate_few_shot_prompt(df_examples, unoptimized_response):
    few_shot_prompt = (
        "You are an impartial judge evaluating the quality of LLM responses. "
        "Based on the user queries and the LLM responses provided below, your task is to determine whether the response is good or bad, "
        "using the examples provided. Return 1 if the response is good (thumbs up) or 0 if the response is bad (thumbs down).nn"
    )
    few_shot_prompt += "Below are examples of user queries, LLM responses, and user feedback:nn"
    
    # Iterate over few-shot examples
    for i, row in df_examples.iterrows():
        few_shot_prompt += f"User Query: {row('conv_A_user')}n"
        few_shot_prompt += f"LLM Response: {row('conv_A_assistant')}n"
        few_shot_prompt += f"User Feedback: {'👍' if row('conv_A_rating') == 1 else '👎'}nn"
    
    # Provide the unoptimized response for feedback prediction
    few_shot_prompt += (
        "Now, evaluate the following LLM response based on the examples above. Return 0 for bad response or 1 for good response.nn"
        f"User Query: {unoptimized_response}n"
        f"Predicted Feedback (0 for 👎, 1 for 👍):"
    )
    return few_shot_prompt

LLM-AS-A-Judge es una funcionalidad en la que un LLM puede dictaminar la precisión de un texto utilizando ciertos ejemplos de cojín. Hemos utilizado esa funcionalidad aquí para dictaminar la diferencia entre el resultado recibido de la solicitud optimizada y no optimizada. Amazon Bedrock lanzó un LLM-as-a-Judge Funcionalidad en diciembre de 2024 que puede estilarse para tales casos de uso. En la ulterior función, demostramos cómo el LLM actúa como evaluador, calificando las respuestas basadas en su columna y satisfacción para el conjunto de datos de evaluación completo:

# Function to predict feedback using few-shot examples
def predict_feedback(df_examples, df_to_rate, response_column, target_col):
    # Create a new column to store predicted feedback
    df_to_rate(target_col) = None
    
    # Iterate over each row in the dataframe to rate
    for index, row in tqdm(df_to_rate.iterrows(), total=len(df_to_rate)):
        # Get the unoptimized prompt response
        try:
            time.sleep(2)
            unoptimized_response = row(response_column)

            # Generate few-shot prompt
            few_shot_prompt = generate_few_shot_prompt(df_examples, unoptimized_response)

            # Call the LLM to predict the feedback
            response = llm.invoke(few_shot_prompt)

            # Extract the predicted feedback (assuming the model returns '0' or '1' as feedback)
            predicted_feedback_str = response.content.strip()  # Clean and extract the predicted feedback

            # Validate the feedback using Pydantic
            try:
                feedback_prediction = FeedbackPrediction(predicted_feedback=int(predicted_feedback_str))
                # Store the predicted feedback in the dataframe
                df_to_rate.at(index, target_col) = feedback_prediction.predicted_feedback
            except (ValueError, ValidationError):
                # In case of invalid data, assign default value (e.g., 0)
                df_to_rate.at(index, target_col) = 0
        except:
            pass

    return df_to_rate

En el ulterior ejemplo, repitimos este proceso para 20 pruebas, capturando los puntajes de satisfacción del favorecido cada vez. La puntuación militar para el conjunto de datos es la suma de la puntuación de satisfacción del favorecido.

df_eval = df.drop(df_test.index).sample(100)
df_eval('unoptimized_prompt_response') = "" # Create an empty column to store responses
df_eval = get_unoptimized_prompt_response(df_eval)
df_eval('optimized_prompt_response') = "" # Create an empty column to store responses
df_eval = get_optimized_prompt_response(df_eval)
Call the function to predict feedback
df_with_predictions = predict_feedback(df_eval, df_eval, 'unoptimized_prompt_response', 'predicted_unoptimized_feedback')
df_with_predictions = predict_feedback(df_with_predictions, df_with_predictions, 'optimized_prompt_response', 'predicted_optimized_feedback')

# Calculate accuracy for unoptimized and optimized responses
original_success = df_with_predictions.conv_A_rating.sum()*100.0/len(df_with_predictions)
unoptimized_success  = df_with_predictions.predicted_unoptimized_feedback.sum()*100.0/len(df_with_predictions) 
optimized_success = df_with_predictions.predicted_optimized_feedback.sum()*100.0/len(df_with_predictions) 

# Display results
print(f"Llamativo success: {original_success:.2f}%")
print(f"Unoptimized Prompt success: {unoptimized_success:.2f}%")
print(f"Optimized Prompt success: {optimized_success:.2f}%")

Estudio de resultados

El ulterior cuadro de carrera muestra la restablecimiento del rendimiento de la alternativa optimizada sobre la no optimizada. Las áreas verdes indican mejoras positivas, mientras que las áreas rojas muestran cambios negativos.

Gráfico de análisis de rendimiento detallado Comparación de soluciones optimizadas vs no optimizadas, destacando la mejora pico del 12% en el caso de prueba 7.5

Al reunir el resultado de 20 ensayos, vimos que la media de los puntajes de satisfacción del aviso no optimizado fue de 0.8696, mientras que la media de los puntajes de satisfacción del aviso optimizado fue de 0.9063. Por lo tanto, nuestro método supera la carrera de cojín en un 3,67%.

Finalmente, ejecutamos una prueba t de muestra emparejada para comparar los puntajes de satisfacción a partir de las indicaciones optimizadas y no optimizadas. Esta prueba estadística validó si la optimización rápida mejoró significativamente la calidad de la respuesta. Vea el ulterior código:

from scipy import stats
# Sample user satisfaction scores from the notebook
unopt = () #20 samples of scores for the unoptimized promt
opt = () # 20 samples of scores for the optimized promt)
# Paired sample t-test
t_stat, p_val = stats.ttest_rel(unopt, opt)
print(f"t-statistic: {t_stat}, p-value: {p_val}")

Luego de ejecutar la prueba t, obtuvimos un valía p de 0.000762, que es inferior a 0.05. Por lo tanto, el aumento de rendimiento de las indicaciones optimizadas sobre las indicaciones no optimizadas es estadísticamente significativo.

Control de interruptor

Aprendimos las siguientes conclusiones secreto de esta alternativa:

  • Peque indicación de shot restablecimiento la respuesta de la consulta -El uso de ejemplos de pocos disparos muy similares conduce a mejoras significativas en la calidad de la respuesta.
  • Amazon Titan Text Increddings permite una similitud contextual – El maniquí produce incrustaciones que facilitan búsquedas de similitud efectivas.
  • La empuje estadística confirma la efectividad -Un valía p de 0.000762 indica que nuestro enfoque optimizado restablecimiento significativamente la satisfacción del favorecido.
  • Impacto comercial mejorado – Este enfoque ofrece un valía comercial medible a través del rendimiento mejorado del asistente de IA. El aumento del 3.67% en los puntajes de satisfacción se traduce en resultados tangibles: los departamentos de bienes humanos pueden esperar menos interpretaciones erróneas de las políticas (reduciendo los riesgos de cumplimiento), y los equipos de servicio al cliente pueden ver una reducción significativa en los boletos aumentados. La capacidad de la alternativa para asimilar continuamente de la feedback crea un sistema de distribución personal que aumenta el ROI con el tiempo sin requerir experiencia especializada en ML o inversiones en infraestructura.

Limitaciones

Aunque el sistema es prometedor, su rendimiento depende en gran medida de la disponibilidad y el grosor de la feedback de los usuarios, especialmente en aplicaciones de dominio cerrado. En escenarios en los que solo hay un puñado de ejemplos de feedback disponibles, el maniquí podría tener dificultades para gestar optimizaciones significativas o no capturar los matices de las preferencias del favorecido de forma efectiva. Encima, la implementación coetáneo supone que la feedback del favorecido es confiable y representativa de las evacuación más amplias del favorecido, lo que podría no ser siempre el caso.

Siguientes pasos

El trabajo futuro podría centrarse en expandir este sistema para aposentar consultas y respuestas multilingües, lo que permite una aplicabilidad más amplia en diversas bases de usuarios. Incorporación Gestación aumentada de recuperación Las técnicas (trapo) podrían mejorar aún más el manejo y la precisión del contexto para consultas complejas. Encima, explorar formas de encarar las limitaciones en escenarios de devaluación feedback, como la engendramiento de feedback sintética o el formación de transferencia, podría hacer que el enfoque sea más robusto y versátil.

Conclusión

En esta publicación, demostramos la efectividad de la optimización de consultas utilizando el capa de roca de Amazon, la solicitud de pocos disparos y los comentarios de los usuarios para mejorar significativamente la calidad de la respuesta. Al alinear las respuestas con las preferencias específicas del favorecido, este enfoque alivia la carestia de un maniquí costoso justo, lo que lo hace práctico para las aplicaciones del mundo efectivo. Su flexibilidad lo hace adecuado para asistentes basados ​​en chat en varios dominios, como el comercio electrónico, el servicio al cliente y la hospitalidad, donde las respuestas de entrada calidad y alineadas al favorecido son esenciales.

Para obtener más información, consulte los siguientes bienes:


Sobre los autores

Tanay Chowdhury es un verificado de datos en el Centro de Innovación Generativa de AI en Amazon Web Services.

Parth Patwa es un verificado de datos en el Centro de Innovación Generativa de AI en Amazon Web Services.

Yingwei Yu es jefe de ciencias aplicadas en el Centro de Innovación Generativa de AI en Amazon Web Services.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *