Optimizar las respuestas de las consultas con comentarios de los usuarios utilizando la incrustación de roca madre de Amazon y pocas solicitudes de disparo

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 usuario. Por ejemplo, un asistente basado en el chat de recursos humanos debe seguir estrictamente las políticas de la empresa y responder 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 madre de Amazoncombinado con un conjunto de datos de retroalimentación de usuarios y pocas solicitudes de disparo, puede refinar las respuestas para una mayor satisfacción del usuario. Utilizando Amazon Titan Text Increddings v2demostramos una mejora estadísticamente significativa en la calidad de la respuesta, por lo que es una herramienta valiosa para aplicaciones que buscan respuestas precisas y personalizadas.

Estudios recientes han destacado el valor de la retroalimentación 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 aprender de los comentarios de los usuarios, utilizándolo para ajustar los modelos iterativamente para mejorar la alineación 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 cadena de pensamiento, la impulso mejora el aprendizaje de pocos disparos al integrar el contexto relevante, permitiendo un mejor razonamiento y la calidad de la respuesta. Sobre la base de estas ideas, nuestro trabajo usa el Amazon Titan Text Increddings v2 Modelo para optimizar las respuestas utilizando la retroalimentación de los usuarios disponible y la solicitud de pocos disparos, logrando mejoras estadísticamente significativas en la satisfacción del usuario. Amazon Bedrock ya ofrece un optimización automática de inmediato La función para adaptar y optimizar automáticamente las indicaciones sin entrada adicional del usuario. 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 solución práctica utilizando Amazon Bedrock que mejora automáticamente las respuestas del asistente de chat en función de los comentarios de los usuarios. Esta solución utiliza incrustaciones y pocas solicitudes de disparo. Para demostrar la efectividad de la solución, utilizamos un conjunto de datos de comentarios de los usuarios disponibles públicamente. Sin embargo, al aplicarlo dentro de una empresa, el modelo puede usar sus propios datos de retroalimentación proporcionados por sus usuarios. Con nuestro conjunto de datos de prueba, muestra un aumento del 3.67% en los puntajes de satisfacción del usuario. Los pasos clave incluyen:

  1. Recupere un conjunto de datos de comentarios de los usuarios disponibles públicamente (para este ejemplo, Conjunto de datos de retroalimentación unificada en la cara de abrazo).
  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 generar indicaciones optimizadas.
  4. Compare las indicaciones optimizadas con directo modelo de lenguaje grande (LLM) llamadas.
  5. Valide la mejora en la calidad de la respuesta utilizando una prueba t de muestra pareada.

El siguiente diagrama es una descripción general del sistema.

Los beneficios clave del uso de la roca madre de Amazon son:

  • Gestión de infraestructura cero – Implementación y escala sin administrar la infraestructura de aprendizaje automático complejo (ML)
  • Rentable – Pague solo por lo que usa con la roca madre de Amazon pago modelo de precios
  • Seguridad de grado 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 abierto
  • Opciones de modelo múltiple – Acceda a varios modelos de base (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.

Recopilació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 usuario) y conv_A_rating (una calificación binaria; 0 significa que al usuario no le gusta y 1 significa que al usuario le gusta). El siguiente código recupera el conjunto de datos y se centra en los campos necesarios para integrar la generación y el análisis de retroalimentación. Se puede ejecutar en un Amazon Sagemaker cuaderno o un cuaderno Jupyter que tiene acceso 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 original nested columns if they are no longer needed
df = df.drop(columns=['conv_A', 'conv_B'])

Generación de muestreo de datos e incrustación

Para administrar el proceso de manera 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 alta dimensión que permiten comparaciones de similitud. Vea el siguiente 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 solución.
  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 diversidad de los ejemplos.

Vea el siguiente 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 guiar la optimización de inmediato. La siguiente 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": ["\n\nHuman"],
}

# 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:\n\n"
    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 '👎'}\n\n"
    
    # Add the user query for which the optimized prompt is required
    few_shot_prompt += f"Based on these examples, generate a general optimized prompt for the following user query:\n\n"
    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 usuario y los ejemplos similares generan un mensaje de pocos disparos.
  2. Usamos el mensaje de pocos disparos en una llamada LLM para generar un aviso optimizado.
  3. Asegúrese de que la salida esté en el siguiente formato usando Pydantic.

Vea el siguiente 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 usuario para hacer la llamada 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}\n\nUser 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 línea de base (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 siguiente función genera la respuesta de consulta utilizando la búsqueda de similitud y la generación 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 juez y evaluación de respuestas

Para cuantificar la calidad de la respuesta, utilizamos un LLM como juez para calificar las respuestas optimizadas y no optimizadas para la alineación con la consulta del usuario. Utilizamos Pydantic aquí para asegurarnos de que la salida se adhiera al patrón deseado de 0 (LLM predice que el usuario no le gusta la respuesta) o 1 (LLM predice que el usuario 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).\n\n"
    )
    few_shot_prompt += "Below are examples of user queries, LLM responses, and user feedback:\n\n"
    
    # 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 '👎'}\n\n"
    
    # 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.\n\n"
        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 juzgar la precisión de un texto utilizando ciertos ejemplos de base. Hemos utilizado esa funcionalidad aquí para juzgar 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 usarse para tales casos de uso. En la siguiente función, demostramos cómo el LLM actúa como evaluador, calificando las respuestas basadas en su alineación 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 siguiente ejemplo, repitimos este proceso para 20 pruebas, capturando los puntajes de satisfacción del usuario cada vez. La puntuación general para el conjunto de datos es la suma de la puntuación de satisfacción del usuario.

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"Original success: {original_success:.2f}%")
print(f"Unoptimized Prompt success: {unoptimized_success:.2f}%")
print(f"Optimized Prompt success: {optimized_success:.2f}%")

Análisis de resultados

El siguiente cuadro de línea muestra la mejora del rendimiento de la solución 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 línea de base 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 siguiente 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}")

Después de ejecutar la prueba t, obtuvimos un valor 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 llave

Aprendimos las siguientes conclusiones clave de esta solución:

  • Peque indicación de shot mejora 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 modelo produce incrustaciones que facilitan búsquedas de similitud efectivas.
  • La validación estadística confirma la efectividad -Un valor p de 0.000762 indica que nuestro enfoque optimizado mejora significativamente la satisfacción del usuario.
  • Impacto comercial mejorado – Este enfoque ofrece un valor 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 recursos 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 solución para aprender continuamente de la retroalimentación crea un sistema de administració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 volumen de la retroalimentación de los usuarios, especialmente en aplicaciones de dominio cerrado. En escenarios en los que solo hay un puñado de ejemplos de retroalimentación disponibles, el modelo podría tener dificultades para generar optimizaciones significativas o no capturar los matices de las preferencias del usuario de manera efectiva. Además, la implementación actual supone que la retroalimentación del usuario es confiable y representativa de las necesidades más amplias del usuario, lo que podría no ser siempre el caso.

Siguientes pasos

El trabajo futuro podría centrarse en expandir este sistema para admitir consultas y respuestas multilingües, lo que permite una aplicabilidad más amplia en diversas bases de usuarios. Incorporación Generació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. Además, explorar formas de abordar las limitaciones en escenarios de baja retroalimentación, como la generación de retroalimentación sintética o el aprendizaje 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 lecho 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 usuario, este enfoque alivia la necesidad de un modelo costoso ajustado, lo que lo hace práctico para las aplicaciones del mundo real. 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 alta calidad y alineadas al usuario son esenciales.

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


Sobre los autores

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

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

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