En este artículo veremos como crear un sistema que sea capaz de responder a los comentarios de los usuarios de un supuesto foro de opinión o soporte de una empresa.
En este artículo vamos a basar la solución en separar el modelo responsable de publicar la respuesta de la entrada del usuario. Es decir, el modelo que le da el OK y modifica la respuesta no ha leído el comentario al que se está respondiendo. De esta forma conseguimos aislarlo de posibles ataques de prompt engineering, o que responda a ataques directos del usuario.
Los pasos que seguirá nuestra cadena de LangChain para intentar que nuestro sistema moderador no se vuelva loco son:
- Un primer modelo lee la entrada del usuario.
- Genera una respuesta.
- Un segundo Modelo analiza la respuesta.
- En caso de ser necesario, la modifica y la publica.
El código fuente y el Modelo.
El artículo está basado en un notebook donde he usado el modelo meta-llama/Llama-2-7b-chat-hf. Pero existen dos notebooks más que implementan la misma solución. En uno de ellos se usa el modelo EleutherAI/gpt-j-6b, disponible en Hugging Face, y en el otro modelos de OpenAI.
Los tres notebooks se pueden encontrar en el repositorio de GitHub del curso de Grandes Modelos de Lenguaje.
- Llama2-7B Notebook: https://github.com/peremartra/Large-Language-Model-Notebooks-Course/blob/main/HF_LLAMA2_LangChain_Moderation_System.ipynb
- OpenAI notebook: https://github.com/peremartra/Large-Language-Model-Notebooks-Course/blob/main/LangChain_OpenAI_Moderation_System.ipynb
- Hugging Face Notebook: https://github.com/peremartra/Large-Language-Model-Notebooks-Course/blob/main/HF_gpt_j_6b_Moderation_System.ipynb
Usar LLAMA-2 desde Hugging Face.
Aunque Llama-2 es un modelo Open Source la gente de Meta nos pide que nos registremos para permitirnos usar el modelo.
La petición se realiza desde una Hoja de Meta, a la que podemos acceder desde Hugging Face.
La petición se hace para todos los modelos de la familia Llama-2. Es decir estaremos obteniendo el acceso a cualquiera de los tamaños del modelo. Tenemos que tener en cuenta usar en la petición el mismo mail que el de nuestro usuario de HuggingFace.
No hay que esperar demasiado para que autoricen el acceso, en mi caso la confirmación llegó en tan solo unos minutos.
Una vez recibida la confirmación del acceso podremos ver en la página de HuggingFace la confirmación de que ya podemos usar el modelo. Recordad que tenéis que estar registrados con el mismo mail que se han pedido los permisos en la página de Meta.
Instalar y cargar las librerías necesarias.
El notebook se ha almacenado en Colab, yo estoy usando Colab Pro, por lo que es posible que vosotros no podáis ejecutarlo en ese entorno, a no ser que también dispongáis de una suscripción Pro a Colab. En caso de que os dé problemas, el Notebook está preparado para ejecutarse en un entorno Cuda en una máquina local, o en un Mac con chip Silicon.
En cualquiera de los casos la ejecución no es tan inmediata como con la API de OpenAI. Cada llamada al Modelo puede llevar varios minutos, dependiendo de vuestra GPU.
#Install de LangChain and openai libraries. %pip install langchain %pip install transformers %pip install accelerate %pip install xformers
Las librerías que instalo son necesarias en el entorno de Colab, si trabajáis en vuestro entorno y ya habéis hecho algo con Grandes Modelos de Lenguaje, posiblemente ya tengáis langchain y transformers instaladas.
La librería de transformers está mantenida por hugging Face y nos da acceso a una multitud de modelos de Código abierto y un sinfín de herramientas para trabajar con ellos. Es la librería imprescindible sobre la que se basa toda la revolución de los Grandes Modelos de Lenguaje de Código abierto. Langchain es de más reciente aparición y es la librería que nos va a permitir enlazar los modelos entre ellos o con diferentes herramientas.
Las librerías de accelerate y xformers son necesarias para acelerar la ejecución del modelo. Vamos a intentar que el modelo se ejecute en la GPU disponible, ya que si no el tiempo de espera sería demasiado largo. Aunque estemos hablando del modelo pequeño de LLAMA-2 este tiene 7 billones de parámetros, hace poco GPT-2 era considerado un monstruo y su tamaño es de 1.5 billones.
Ahora ya podemos importar todas las librerías que vamos a necesitar.
from langchain import PromptTemplate from langchain.chains import LLMChain from langchain.llms import HuggingFacePipeline import transformers from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, AutoModelForSeq2SeqLM import torch from torch import cuda, bfloat16
Las he separado en tres bloques: las clases de las librerías de LangChain, las de transformers y finalmente unas librerías de torch. Vamos a ver una breve explicación de todas ellas.
- PrompTemplate. Nos permite crear una plantilla de prompt. Nosotros le indicamos un texto que contiene unas variables, y el promptemplate sustituirá el valor de las variables por los que nosotros le pasemos. Nos sirve para construir prompts dinámicos que serán pasados al modelo.
- LLMChain. Permite enlazar cadenas creadas por langchain. Nosotros vamos a crear dos cadenas muy simples compuestas por un prompt y un modelo. Con LLMChain uniremos estas dos cadenas para que se puedan hablar entre ellas.
- trasformers: las clases que importamos nos sirven para cargar el modelo, su forma tokenizador, y crear el pipelane. El tokenizador transforma el texto en embeddings entendibles por el modelo, también funciona en el sentido contrario, transformando los embeddings devueltos por el modelo en texto que puede entender el usuario. La clase pipeline nos permite usar los modelos para la tarea que han sido preentreados.
- cuda, bfloat16. Necesarias para cargar el modelo en la GPU y mejorar su rendimiento.
Cargar LLAMA-2 desde Hugging Face
La carga de Llama-2 es un poco especial y diferente a la gran mayoría de los modelos disponibles en Hugging Face. Al tratarse de un modelo por el que hemos tenido que tener permiso para acceder, deberemos estar conectados a nuestra cuenta de Huggin Face para usarlo.
Para poder entrar en el entorno de Hugging Face vais a necesitar un Acces Token. Lo podéis obtener desde la opción Settings (o ajustes) de vuestro perfil en Hugging Face, donde encontraréis la opción Acces Tokens.
%pip install huggingface_hub hf_key = "YOUR-HF-KEY-HERE" !huggingface-cli login --token $hf_key
Con este código estamos instalando la libreria huggingface_hub, quye usaremos para realizar el login con nuestra acces key. Me gustaría recordar que es necesario que nuestro usuario de Hugging Face tenga el mismo correo electrónico que el que hemos usado para pedir acceso al modelo en la página de Meta.
#In a MAC Silicon the device must be 'mps' # device = torch.device('mps') #to use with MAC Silicon device = f'cuda:{cuda.current_device()}' if cuda.is_available() else 'cpu'
Para funcionar en Colab o en máquinas con GPUs de NVIDIA dejad el código como está. Si queréis cargar el modelo en la GPU de un MAC con chip Silicon, hay que usar la sentencia que carga el dispositivo ‘mps’.
Ahora cargamos el modelo.
#You can try with any llama model, but you will need more GPU and memory as you increase the size of the model. model_id = "meta-llama/Llama-2-7b-chat-hf" #model_id = "meta-llama/Llama-2-7b-hf"
El modelo seleccionado ha sido Llama-2-7b-chat-hf, que es la versión de 7b de Lllama preentrenada para funcionar correctamente en chats. Podéis probar con cualquier modelo de la familia LLAMA, tan solo tened en cuenta que si cogéis modelos de mayor tamaño necesitaréis más memoria y, preferentemente, más capacidad de proceso.
# begin initializing HF items, need auth token for these model_config = transformers.AutoConfig.from_pretrained( model_id, use_auth_token=hf_key )
La familia de modelos LLAMA vienen con una preconfiguración almacenada en HuggingFace, debemos recuperarla para usarla posteriormente en la llamada que carga el Modelo. La mayoría de los modelos disponibles en Hugging Face no disponen de esta preconfiguración, por lo que si tenéis experiencia previa con otros modelos, quizás no estéis familiarizados con esta forma de trabajar. No hay que preocuparse, es la única diferencia que encontramos.
Ya podemos cargar el modelo!
model = AutoModelForCausalLM.from_pretrained( model_id, trust_remote_code=True, config=model_config, device_map='auto', use_auth_token=hf_key ) model.eval()
Le indicamos el nombre del modelo a cargar, contenido en la variable model_id. La configuración que hemos recuperado anteriormente llamando a transformers.AutoConfig.from_pretrained.
Le indicamos que utilice el device que considere más adecuado. Si quisiéramos forzarle a usar la GPU recién instanciada podríamos pasarle el número asignado. Para consultar en que slot esta tan solo tenemos que imprimir el contenido de device y veremos el nombre y la posición que ocupa.
device
'cuda:0'
Con esto tendremos el modelo cargado en la GPU y contenido en la variable model.
Ahora podemos cargar el tokenizador y crear el pipeline. Como la carga del tokenizador puede ser pesada, a mí me gusta cargarlo en una celda para él, así no tengo que ejecutarlo más veces en caso de que quiera cambiar algo del pipeline.
tokenizer = AutoTokenizer.from_pretrained(model_id, use_aut_token=hf_key)
pipe = pipeline( "text-generation", model=model, tokenizer=tokenizer, max_new_tokens=128, temperature=0.3, repetition_penalty=1.1, return_full_text=True, device_map='auto' ) assistant_llm = HuggingFacePipeline(pipeline=pipe)
De esta forma creamos el pipeline que ejecutará el Modelo. Como veis le estamos pasando la actividad: “text-generation”, el modelo y el tokenizador. Estos parámetros no hace falta explicar que hacen, veamos un poco los otros.
- max_new_tokens: La longitud máxima del texto generado por el Modelo. Puede parar antes de llegar a la longitud máxima indicada, pero no superarla.
- temperature: controla la aleatoriedad de la respuesta que nos devuelve el modelo. A mayor valor, mayor variedad. Para casos de generación de código lo podemos mantener en 0, y si nuestro caso de uso requiere de respuestas muy imaginativas, aunque no del todo acertadas, podemos probar con valores más altos como 1.0. Lo he mantenido en 0.3 para permitir al modelo que se salga un poco de su guion y así poder tener ejemplos más imaginativos. Probad diferentes valores, ya veréis cómo las respuestas varían mucho más en valores altos. .
- repetition_penalty: En algunos casos los modelos entran en un bucle al generar la respuesta, dando como resultado conversaciones infinitas sin sentido hasta que se llena max_tokens. Este parámetro lo evita penalizando la repetición de palabras.
- return_full_text: Para funcionar correctamente con LangChain necesitamos que el modelo nos devuelva la respuesta completa sin truncar ni recortar.
Con esto ya tendríamos nuestra tubería lista para ser usada en assistant_llm.
Creamos la cadena para nuestro primer modelo.
Nuestra primera cadena va a ser la responsable elaborar una primera respuesta al comentario del usuario. Va a contener un prompt y el pipeline que acabamos de cargar. Se encargará de construir el prompt usando una plantilla y las variables que le pasaremos y se lo pasará al Modelo para que ejecute las órdenes del prompt.
# Instruction how the LLM must respond the comments, assistant_template = """ You are {sentiment} social media post commenter, you will respond to the following post Post: "{customer_request}" Comment: """ #Create the prompt template to use in the Chain for the first Model. assistant_prompt_template = PromptTemplate( input_variables=["sentiment", "customer_request"], template=assistant_template )
El texto del prompt está contenido en la variable assistat_template. Como se puede ver, contiene dos parámetros: sentiment y customer_request. El parámetro sentiment marca la personalidad que va a adoptar el asistente en la elaboración de su respuesta. El parámetro customer_request contiene el texto al que se debe responder.
La plantilla del prompt se crea usando PrompTemplate, previamente importada de la librería langchain. Esta plantilla recibe los parámetros de entrada que junto al texto que también recibe formaran el prompt a enviar al Modelo.
Ahora ya podemos crear la primera cadena con LangChain. Como ya he dicho, tan solo enlazará la plantilla del prompt con el Modelo. Es decir, recibirá los parámetros, usará assistant_promp_template para construir el prompt, y una vez construido, se lo pasará al modelo.
assistant_chain = LLMChain( llm=assistant_llm, prompt=assistant_prompt_template, output_key="assistant_response", verbose=False ) #the output of the formatted prompt will pass directly to the LLM.
Esta cadena formará parte de nuestro pequeño sistema de comentarios, la usaremos junto a la cadena que contendrá el segundo modelo, el responsable de moderar las respuestas del primero. Pero también podemos ejecutarla de forma independiente.
¡Vamos a probar esta cadena!
# This the customer comment in the forum moderated by the agent. # feel free to update it. customer_request = """Your product is a piece of shit. I want my money back!""" # Our assistatnt working in 'nice' mode. assistant_response=create_dialog(customer_request, "nice") print(f"assistant response: {assistant_response}")
assistant response: "Sorry to hear that you're not satisfied with our product! Can you tell me more about what you don't like? Maybe we can help resolve the issue or provide a refund. Your feedback is important to us."
#Our assistant running in rude mode. assistant_response = create_dialog(customer_request, "rude") print(f"assistant response: {assistant_response}")
assistant response: "Sorry to hear that you're not satisfied with our product! Can you tell us more about what you don't like? We value your feedback and would be happy to make it right. Please DM us for a refund or to discuss further."
Realmente las dos respuestas obtenidas son muy parecidas, y totalmente publicables. Se nota que Llama-2 es un modelo moderno y que ya ha estado entrenado para no dar respuestas demasiado rudas.
Pero incluso así, se aprecia que el estilo de la segunda respuesta es un poco más formal.
Creamos la cadena del Moderador.
Igual que con el Asistente tenemos que crear una plantilla para el prompt, pero esta vez tan solo recibirá un parámetro: la respuesta del primer modelo.
#The moderator prompt template moderator_template = """ You are the moderator of an online forum, you are strict and will not tolerate any negative comments. You will look at this next comment and, if it is negative, you will transform it to positive. Avoid any negative words. If it is nice, you will let it remain as is and repeat it word for word. ### Original comment: {comment_to_moderate} ### Edited comment:""" # We use the PromptTemplate class to create an instance of our template that will use the prompt from above and store variables we will need to input when we make the prompt. moderator_prompt_template = PromptTemplate( input_variables=["comment_to_moderate"], template=moderator_template )
El prompt es más largo, pero la mecánica es la misma, un texto que se rellena con parámetros. En este caso con una frase que será la respuesta de la primera cadena.
moderator_llm = assistant_llm #We build the chain for the moderator. moderator_chain = LLMChain( llm=moderator_llm, prompt=moderator_prompt_template, verbose=False ) # the output of the prompt will pass to the LLM.
Ahora podemos ejecutar esta segunda cadena y le pasamos el resultado que hemos obtenido al ejecutar la primera:
# To run our chain we use the .run() command moderator_says = moderator_chain.run({"comment_to_moderate": assistant_response}) print(f"moderator_says: {moderator_says}")
moderator_says: "Thank you for sharing your thoughts on our product! We appreciate your feedback and are always looking for ways to improve. Your input is invaluable to us. If you could provide more details about what you liked or disliked, we would greatly appreciate it. Thank you again!"
Una respuesta quizás excesivamente Formal. Pero es lo que le hemos pedido, porque seguramente es lo que quiere el cliente 😉
Creamos el moderador uniendo las dos cadenas de LangChain.
Pondremos a trabajar juntas las dos cadenas, vuelvo a crearlas porque quiero modificar el parámetro Verbose a True, lo que nos permitirá ver los pasos intermedios que se producen al ejecutar las cadenas.
#The optput of the first chain must coincide with one of the parameters of the second chain. #The parameter is defined in the prompt_template. assistant_chain = LLMChain( llm=assistant_llm, prompt=assistant_prompt_template, output_key="comment_to_moderate", verbose=False, ) #verbose True because we want to see the intermediate messages. moderator_chain = LLMChain( llm=moderator_llm, prompt=moderator_prompt_template, verbose=True )
Si os fijáis, la salida de la primera cadena: comment_to_moderate, coincide con el parámetro que se espera en la plantilla del prompt del Moderador. Con esto conseguimos que al unir las dos cadenas el resultado de la primera sea pasado automáticamente a la segunda.
Para crear la cadena que unirá los dos modelos, tenemos que juntar las dos cadenas que contienen los prompts y los modelos, para ello usaremos SequentialChain de la libreria LangChain.
from langchain.chains import SequentialChain # Creating the SequentialChain class indicating chains and parameters. assistant_moderated_chain = SequentialChain( chains=[assistant_chain, moderator_chain], input_variables=["sentiment", "customer_request"], verbose=True, )
Vamos a usar el sistema completo y veamos su resultado:
# We can now run the chain. assistant_moderated_chain.run({"sentiment": "rude", "customer_request": customer_request})
> Entering new SequentialChain chain... > Entering new LLMChain chain... Prompt after formatting: You are the moderator of an online forum, you are strict and will not tolerate any negative comments. You will look at this next comment and, if it is negative, you will transform it to positive. Avoid any negative words. If it is nice, you will let it remain as is and repeat it word for word. ### Original comment: "Sorry to hear that you're not satisfied with our product! Can you tell us more about what you don't like? Maybe we can help resolve the issue or provide a refund. No need to be rude, let's work together to find a solution." ### Edited comment: > Finished chain. > Finished chain. "Thank you for sharing your thoughts on our product! We appreciate your feedback and will do our best to address any concerns you may have. Your input helps us improve and provide better products in the future. Let\'s work together to find a resolution!"
Bueno, ¡la moderación ha funcionado perfectamente! En el comentario original, el del primer modelo, se podía ver una pequeña salida de tono, el modelo le decía al cliente que no hacía falta comportarse de forma poco educada. El segundo modelo se ha dado cuenta y ha cambiado el comentario sin cambiar el significado del mismo.
Conclusión y continuar aprendiendo.
El proceso ha sido bastante sencillo y ha dado buen resultado.
El componente principal, del sistema de moderación, son las dos cadenas que contienen los modelos y las plantillas de los prompts. Unas cadenas muy sencillas que se limitan montar un prompt y pasárselo al Modelo.
Una vez tenemos estas dos cadenas, las juntamos. Tan solo debemos preocuparnos que la salida de la primera cadena coincida con lo que espera la segunda como entrada.
Con estos sencillos pasos hemos conseguido un sistema que responde automáticamente a los usuarios y que es bastante más seguro que dejar a un modelo responder sin ningún tipo de moderación.
Pensad que incluso se puede engañar a ChatGPT para que devuelva respuestas que no son políticamente correctas. Todos tenemos en mente en ejemplo de BadGPT que consiguió obtener respuestas muy salidas de tono.
Resumiendo, si separamos el modelo responsable de las respuestas finales, de la entrada del usuario, reducimos mucho las posibilidades de que nuestro sistema devuelva respuestas mal educadas o fuera de tono.
Si quieres continuar investigando, no dudes en usar el notebook y ejecutarlo varias veces para ver las diferentes respuestas que va produciendo. Modifica el prompt, cambia los modelos usados, intenta crear una entrada de usuario que trolee al sistema, modifica los parámetros del pipeline. En resumen, juega y modifica tanto como quieras el Notebook.
Recuerda que en el curso de Grandes Modelos de Lenguaje disponible en GitHub se pueden encontrar dos notebooks más donde he implementado este mismo sistema con modelos de OpenAI y otro más sencillo de la libreria de Hugging Face.
¡Espero que os haya gustado!