Los Modelos de Lenguaje Grandes (LLM) pueden ser más confiables en cuanto a veracidad cuando se les proporcionan algunos contextos recuperados de una base de conocimientos, lo que se conoce como Retrieval Augmented Generation (RAG). Nuestros blogs anteriores discutieron el aumento de rendimiento (opens new window) y la viabilidad en términos de costo y latencia (opens new window). En este blog, te presentaremos un uso avanzado del pipeline de RAG: construir un chatbot utilizando Retrieval Augmented Generation con MyScale. También puedes probarlo en nuestro espacio de HuggingFace (opens new window)
Nota:
Visita el espacio de HuggingFace de MyScale (opens new window) para probar nuestro chatbot.
Los chatbots difieren de las tareas de pregunta y respuesta de una sola vuelta. Así es como:
Tareas de pregunta y respuesta de una sola vuelta:
En las tareas de pregunta y respuesta de una sola vuelta, la interacción entre el usuario y el sistema generalmente consiste en una sola pregunta formulada por el usuario y una respuesta directa proporcionada por el sistema. Estos se conocen como pares de pregunta y respuesta.
Chatbots:
Sin embargo, la conversación entre un chatbot y un usuario es más compleja y se extiende con discusiones de múltiples vueltas. Los chatbots pueden manejar diálogos en curso y preguntas de seguimiento, siguiendo el contexto de la conversación a lo largo de múltiples interacciones.
Para lograr esto, el chatbot necesita almacenar el historial completo de chat de un usuario, incluyendo sus conversaciones anteriores y las acciones (o resultados) de sus últimas llamadas de función. En segundo lugar, la memoria del chatbot debe ser capaz de atender a diferentes usuarios simultáneamente, manteniendo sus conversaciones separadas entre sí. Esto puede ser un desafío significativo si no se configura correctamente. La buena noticia es que MyScale proporciona una solución perfecta a este desafío a través de su compatibilidad con SQL y su funcionalidad de control de acceso basado en roles (opens new window), lo que te permite gestionar fácilmente los historiales de chat de millones de usuarios.
Los chatbots también pueden beneficiarse de RAG, pero no todos los chats necesitan RAG. Por ejemplo, cuando el usuario solicita una traducción de un idioma a otro, agregar RAG a la mezcla no agregará valor a esta solicitud. En consecuencia, debemos permitir que el chatbot decida cuándo y dónde usar RAG como parte de su consulta de búsqueda.
¿Cómo logramos esto?
Afortunadamente, OpenAI tiene una API de llamada de función que podemos utilizar para insertar un pipeline de recuperación con MyScale como una llamada de función externa (opens new window).
Además, MyScale es capaz de realizar todas las tareas de alojamiento de datos, desde la búsqueda de vectores hasta la gestión del historial de chat. Como muestra el diagrama anterior, puedes construir un chatbot utilizando MyScale como única fuente de datos. No necesitas preocuparte de que tus datos estén dispersos en diferentes bases de datos y motores.
¡Veamos cómo se hace!
# Recuperador como una herramienta
RAG puede ser simbolizado como una función externa. Consulta nuestros documentos de llamada de función de OpenAI (opens new window) para obtener más información sobre cómo crear un prompt para enseñar a GPT a utilizar el vector store de MyScale como una herramienta.
Hoy utilizaremos las API de recuperador de LangChain en lugar del vectorstore para mejorar tu consulta utilizando la búsqueda avanzada de filtros de MyScale. Anteriormente, mostramos cómo los recuperadores de autoconsulta (opens new window) pueden traducir tus preguntas en consultas de búsqueda de vectores utilizando filtros. También describimos cómo los recuperadores construidos a partir de una cadena de bases de datos vectoriales SQL (opens new window) hacen lo mismo que los recuperadores de autoconsulta, pero en una interfaz SQL.
Nota:
Estos dos recuperadores solo toman el texto de la consulta como entrada, por lo que es fácil convertirlos en herramientas de chatbot.
De hecho, solo se necesitan unas pocas líneas de código para transformar un recuperador en una herramienta:
from langchain.agents.agent_toolkits import create_retriever_tool
retriever = ... # recuperador de autoconsulta / recuperador de base de datos vectorial SQL
# Crea una herramienta con una descripción adecuada, tomando la recuperación de Wikipedia como ejemplo:
tool = create_retriever_tool(retriever,
"buscar_entre_wikipedia",
"Busca entre Wikipedia y devuelve páginas wiki relacionadas")
# crea un conjunto de herramientas
tools = [tool]
Por lo tanto, puedes crear múltiples herramientas y alimentarlas a un solo chatbot. Por ejemplo, si tienes numerosas bases de conocimientos para buscar, puedes desarrollar herramientas para cada base de conocimientos y permitir que el chatbot decida qué herramienta utilizar.
# Recordando los Chats
Los recuerdos de chat son cruciales para los chatbots. Debido a que proporcionamos a nuestro chatbot múltiples herramientas, también necesitamos proporcionarle memoria para almacenar los resultados intermedios de estas herramientas. Esto requiere un tipo de datos rico y un soporte avanzado de multiarrendamiento, en lo que MyScale es bueno.
El siguiente script de Python describe cómo crear memoria para el chatbot:
from langchain.memory import SQLChatMessageHistory
from langchain.memory.chat_message_histories.sql import BaseMessageConverter, DefaultMessageConverter
from langchain.agents.openai_functions_agent.agent_token_buffer_memory import AgentTokenBufferMemory
# Credenciales de MyScale
MYSCALE_USER = ...
MYSCALE_PASSWORD = ...
MYSCALE_HOST = ...
MYSCALE_PORT = ...
database = 'chat'
# MyScale admite sqlalchemy a través del paquete `clickhouse-sqlalchemy`
conn_str = f'clickhouse://{MYSCALE_USER}:{MYSCALE_PASSWORD}@{MYSCALE_HOST}:{MYSCALE_PORT}'
# LangChain tiene soporte nativo para bases de datos SQL como backend de historial de chat
chat_memory = SQLChatMessageHistory(
# el ID de sesión debe ser específico del usuario, aísla las sesiones
session_id,
# MyScale SaaS utiliza una conexión HTTPS
connection_string=f'{conn_str}/{database}?protocol=https',
# Aquí personalizamos el convertidor de mensajes y el esquema de la tabla
custom_message_converter=DefaultClickhouseMessageConverter(name))
# AgentTokenBufferMemory nos ayudará a almacenar todos los mensajes intermedios de usuarios, bots y herramientas
memory = AgentTokenBufferMemory(llm=llm, chat_memory=chat_memory)
MyScale también funciona como una base de datos relacional. Y gracias a la SQLChatMessageHistory
de LangChain, puedes utilizar MyScale como tu backend de memoria a través de clickhouse-sqlalchemy
. Se necesita un convertidor de mensajes personalizado para almacenar más información en la base de datos.
Así es como puedes definir el esquema de la tabla de la memoria:
import time
import json
import hashlib
from sqlalchemy import Column, Text
try:
from sqlalchemy.orm import declarative_base
except ImportError:
from sqlalchemy.ext.declarative import declarative_base
from clickhouse_sqlalchemy import types, engines
from langchain.schema.messages import BaseMessage, _message_to_dict, messages_from_dict
def create_message_model(table_name, DynamicBase): # type: ignore
# El modelo se declara dentro de una función para tener un nombre de tabla dinámico
class Message(DynamicBase):
__tablename__ = table_name
# SQLChatMessageHistory ordenará los mensajes por id
# Por lo tanto, aquí almacenamos la marca de tiempo como id
id = Column(types.Float64)
# el ID de sesión sirve para aislar las sesiones
session_id = Column(Text)
# Este es el verdadero clave primaria para el mensaje
msg_id = Column(Text, primary_key=True)
# tipo de este mensaje, podría ser HumanMessage / AIMessage u otros
type = Column(Text)
# Mensaje adicional en cadena JSON
addtionals = Column(Text)
# mensaje en texto
message = Column(Text)
__table_args__ = (
# ReplacingMergeTree deduplicará con respecto a la clave primaria
engines.ReplacingMergeTree(
partition_by='session_id',
order_by=('id', 'msg_id')),
{'comment': 'Almacenar historial de chat'}
)
return Message
class DefaultClickhouseMessageConverter(DefaultMessageConverter):
"""Un convertidor de mensajes ClickHouse para SQLChatMessageHistory."""
def __init__(self, table_name: str):
# crea el esquema de tabla para la memoria de chat
self.model_class = create_message_model(table_name, declarative_base())
def to_sql_model(self, message: BaseMessage, session_id: str) -> Any:
tstamp = time.time()
msg_id = hashlib.sha256(f"{session_id}_{message}_{tstamp}".encode('utf-8')).hexdigest()
# rellena los espacios en blanco
return self.model_class(
id=tstamp,
msg_id=msg_id,
session_id=session_id,
type=message.type,
addtionals=json.dumps(message.additional_kwargs),
message=json.dumps({
"type": message.type,
"additional_kwargs": {"timestamp": tstamp},
"data": message.dict()})
)
def from_sql_model(self, sql_message: Any) -> BaseMessage:
# convierte el historial recuperado en un objeto de mensaje
msg_dump = json.loads(sql_message.message)
msg = messages_from_dict([msg_dump])[0]
msg.additional_kwargs = msg_dump["additional_kwargs"]
return msg
¡Ahora tienes una memoria de chat completamente funcional respaldada por MyScale! ¡Hurra!
# Gestión de la Memoria de Chat
Los historiales de conversación de los usuarios son activos y deben mantenerse seguros. Las memorias de chat de LangChain ya tienen aislamiento de sesión controlado por session_id
(opens new window).
Millones de usuarios pueden interactuar con tu chatbot, lo que dificulta la gestión de la memoria. Afortunadamente, tenemos varios "trucos" para ayudar a gestionar los historiales de chat de todos estos usuarios.
MyScale admite el aislamiento de datos mediante la creación de diferentes tablas, particiones o claves primarias para los usuarios. Como tener demasiadas tablas en la base de datos sobrecargará el sistema, te recomendamos adoptar una estrategia de multiarrendamiento orientada a la filtración de metadatos (opens new window) en su lugar. Para ser más concretos, puedes crear particiones en lugar de tablas para tus usuarios o ordenarlos utilizando una clave primaria. Esto te ayudará a realizar una recuperación rápida desde tu base de datos, lo cual es más eficiente que buscar y almacenar.
En este escenario, recomendamos utilizar la solución basada en claves primarias. Agregar session_id
a la lista de claves primarias mejorará la velocidad al recuperar el historial de chat de un usuario específico.
# Aquí modificamos el modelo de SQLAlchemy
def create_message_model(table_name, DynamicBase): # type: ignore
class Message(DynamicBase):
__tablename__ = table_name
id = Column(types.Float64)
session_id = Column(Text, primary_key=True)
msg_id = Column(Text, primary_key=True)
type = Column(Text)
addtionals = Column(Text)
message = Column(Text)
__table_args__ = (
engines.ReplacingMergeTree(
# ||| Esto creará particiones para cada 1,000 sesiones
# vvv (demasiadas particiones sobrecargarán el sistema)
partition_by='sipHash64(session_id) % 1000',
# Aquí ordenamos por ID de sesión e ID de mensaje
# para acelerar la recuperación
order_by=('session_id', 'msg_id')),
{'comment': 'Almacenar historial de chat'}
)
return Message
Nota:
Consulta nuestra documentación si deseas obtener más información sobre las estrategias de multiarrendamiento (opens new window).
# Poniéndolos Juntos
Ahora tenemos todos los componentes necesarios para construir un chatbot con RAG. Vamos a unirlos, como se describe en el siguiente fragmento de código:
from langchain.agents import AgentExecutor
from langchain.schema import SystemMessage
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import MessagesPlaceholder
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
# inicializa el LLM de OpenAI
chat_model_name = "gpt-3.5-turbo"
OPENAI_API_BASE = ...
OPENAI_API_KEY = ...
# crea el LLM
chat_llm = ChatOpenAI(model_name=chat_model_name, temperature=0.6, openai_api_base=OPENAI_API_BASE, openai_api_key=OPENAI_API_KEY)
# prompts iniciales para animar al chatbot a utilizar funciones de búsqueda
_system_message = SystemMessage(
content=(
"Haz lo posible por responder las preguntas. "
"Siéntete libre de utilizar todas las herramientas disponibles para buscar "
"información relevante. Por favor, mantén todos los detalles en la consulta "
"cuando llames a las funciones de búsqueda."
)
)
# crea los prompts de llamada de función
prompt = OpenAIFunctionsAgent.create_prompt(
system_message=_system_message,
# Aquí es donde colocas el historial de chat de la base de datos
extra_prompt_messages=[MessagesPlaceholde(variable_name="history")],
)
# Utilizamos el agente de funciones de OpenAI
agent = OpenAIFunctionsAgent(llm=chat_llm, tools=tools, prompt=prompt)
# combina todos los componentes juntos
executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
verbose=True,
# Almacenamos todos esos pasos intermedios en la base de datos, por lo que necesitamos esto
return_intermediate_steps=True,
)
Ahí lo tienes: un chatbot habilitado para RAG con un AgentExecutor
. Puedes hablar con él con una simple línea de código:
response = executor({"input": "¡Hola!"})
Nota: Todos los historiales de chat se almacenan en executor.memory.chat_memory.messages
. Si deseas una referencia sobre cómo renderizar mensajes desde la memoria, consulta nuestra implementación en GitHub (opens new window).
# Para Concluir...
MyScale es realmente bueno en la búsqueda de vectores de alto rendimiento y proporciona todas las funcionalidades que ofrecen las bases de datos SQL. Puedes utilizarlo como una base de datos de vectores y una base de datos SQL. Además, tiene características avanzadas como el control de acceso para gestionar tus usuarios y aplicaciones.
Este blog demuestra cómo construir un chatbot con MyScale, utilizando como única fuente de datos. Integrar tu chatbot con una sola base de datos garantiza la integridad, seguridad y consistencia de los datos. También reduce la redundancia de datos almacenando referencias a registros, mejorando el acceso a los datos y compartiéndolos con un control de acceso avanzado. Esto puede mejorar significativamente la confiabilidad y la calidad, convirtiendo tu chatbot en un servicio modernizado que puede escalar según las necesidades de tu negocio.
¡Prueba nuestro chatbot en HuggingFace (opens new window), o ejecútalo tú mismo utilizando el código de GitHub (opens new window)! ¡También únete a nosotros para compartir tus ideas en Twitter (opens new window) y Discord (opens new window)!
# Referencias:
- https://myscale.com/blog/es/teach-your-llm-vector-sql/ (opens new window)
- https://myscale.com/docs/es/advanced-applications/chatdata/ (opens new window)
- https://myscale.com/docs/es/sample-applications/openai-function-call/ (opens new window)
- https://python.langchain.com/docs/modules/memory/ (opens new window)
- https://python.langchain.com/docs/integrations/memory/sql_chat_message_history (opens new window)
- https://python.langchain.com/docs/use_cases/chatbots (opens new window)
- https://python.langchain.com/docs/use_cases/question_answering/how_to/conversational_retrieval_agents (opens new window)