Comprensión del código en tu propio hardware
'Understanding code on your own hardware.'
Configuración de un LLM para hablar sobre tu código — con LangChain y hardware local
Entre las diversas tareas que los Modelos de Lenguaje de Gran Tamaño (LLM) pueden realizar hoy en día, el entendimiento de código puede ser de particular interés para ti, si trabajas con código fuente como desarrollador de software o científico de datos. ¿No sería genial tener un chatbot al que puedas hacerle preguntas sobre tu código? ¿Dónde se implementa el preprocesamiento de datos? ¿Ya existe una función para verificar la autenticación del usuario? ¿Cuál es la diferencia entre la función calculate_vector_dim y calculate_vector_dimension? En lugar de buscar el archivo correcto por ti mismo, simplemente pregúntale al bot y te dará una respuesta, junto con un enlace a los archivos que contienen los fragmentos de código relevantes. Ese mecanismo se llama búsqueda semántica y puedes imaginar lo útil que es.
En este tutorial, te mostraré cómo implementar un bot de LangChain que haga exactamente eso. Además, me centraré en el problema específico relacionado con la privacidad de datos de no entregar tu código a manos ajenas. El código que tú o tu empresa hayan producido es propiedad privada y puede contener información confidencial o conocimientos valiosos. Es posible que no desees o que las políticas de tu empresa no te permitan enviarlo a un LLM alojado por otra empresa, que puede estar ubicada en otro país. Por lo tanto, en este tutorial, te mostraré cómo configurar un bot de entendimiento de código que se ejecuta en tu hardware local, para que tu código nunca salga de tu infraestructura.
¡Comencemos! Primero, te daré una breve introducción al proceso general de búsqueda semántica antes de implementar un bot para el entendimiento de código.
Introducción a la búsqueda semántica
En primer lugar, permíteme explicarte brevemente la idea general de la búsqueda semántica. Este enfoque consta de dos pasos principales, que son la recuperación y la generación de respuestas por parte del LLM mismo. En el paso de recuperación, se seleccionan documentos que contienen información relevante y se alimentan al LLM para crear una respuesta en lenguaje natural. Por ejemplo, si haces una pregunta sobre una función llamada transform_vectors, la recuperación seleccionará aquellos archivos que son relevantes para responder esa pregunta. Eso puede incluir el archivo donde se implementa la función transform_vectors, pero también archivos que la utilizan o partes de la documentación que la mencionan. En el segundo paso, el contenido de esos archivos se entrega al LLM en un formato que puede verse algo así:
- Investigadores de Princeton presentan InterCode un revolucionario marco ligero que simplifica la interacción del modelo de lenguaje para generar código de manera similar a como lo haría un humano.
- Cómo automatizar la extracción de entidades de un PDF utilizando LLMs
- Evolucionando un Plan de Pruebas para una Canalización de Datos
"""Responde la siguiente pregunta dada la información. <documento 1><documento 2>...<documento n>Pregunta: <pregunta del usuario>Respuesta:"""
El LLM crea una respuesta en lenguaje natural a la pregunta utilizando la información de los documentos que se le proporcionaron.
Esa es la idea principal de la búsqueda semántica. ¡Ahora comencemos a implementar! En primer lugar, tenemos que instalar los requisitos y leer nuestros datos.
Instalar requisitos
Antes de comenzar, asegúrate de tener configurado un entorno que ejecute Python e instala los siguientes paquetes:
pip install langchain==0.0.191pip install transformers
Leer los documentos
Ahora necesitamos leer los datos y convertirlos en un formato en el que LangChain pueda trabajar. Para esta demostración, descargaré el código de LangChain en sí, pero puedes usar tu propio código base, por supuesto:
import osfolder_name = "sample_code"os.system(f"git clone https://github.com/hwchase17/langchain {folder_name}")
Cargamos todos los archivos y los convertimos en un Documento cada uno, es decir, cada Documento contendrá exactamente un archivo del código base.
from langchain.docstore.document import Document
documents = []
for root, dirs, files in os.walk(folder_name):
for file in files:
try:
with open(os.path.join(root, file), "r", encoding="utf-8") as o:
code = o.readlines()
d = Document(page_content="\n".join(code), metadata={"source": os.path.join(root, file)})
documents.append(d)
except UnicodeDecodeError:
# algunos archivos no están codificados en utf-8; los ignoraremos por ahora
pass
Recuperación
Ahora que hemos creado nuestros Documentos, necesitamos indexarlos para que sean buscables. Indexar un Documento significa calcular un vector numérico que capture la información más relevante del Documento. A diferencia del propio texto, un vector de números se puede utilizar para realizar cálculos numéricos, lo que significa que podemos calcular fácilmente una similitud en él, que luego se utiliza para determinar qué Documentos son relevantes para responder una determinada pregunta.
En un nivel técnico, este índice que crearemos con la ayuda de un embedding y lo almacenaremos en un VectorStore. Hay VectorStores disponibles como servicio (por ejemplo, DeepLake), que vienen con algunas ventajas útiles, pero en nuestro escenario, no queremos dar el código fuera de nuestras manos, así que creamos un VectorStore localmente en nuestra máquina. La forma más sencilla de hacerlo es usando Chroma, que crea un VectorStore en memoria y nos permite persistirlo.
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
hfemb = HuggingFaceEmbeddings(model_name="krlvi/sentence-t5-base-nlpl-code-x-glue")
persist_directory = "db"
db = Chroma.from_documents(documents, hfemb, persist_directory=persist_directory)
db.persist()
Dentro de la función from_documents, se calculan los índices y se almacenan en la base de datos Chroma. La próxima vez, en lugar de llamar a la función from_documents nuevamente, podemos cargar la base de datos Chroma persistida en sí:
db = Chroma(persist_directory=persist_directory, embedding_function=hfemb)
Como viste anteriormente, como embedding utilicé krlvi/sentence-t5-base-nlpl-code-x-glue, que es un embedding que se entrenó en código de bibliotecas de GitHub de código abierto. Como puedes imaginar, es crucial que el embedding que utilicemos se haya entrenado en código (entre otros datos), para que pueda usar los datos que le proporcionamos. Es probable que un embedding que se haya entrenado solo en lenguaje natural funcione peor.
Ahora que tenemos nuestro VectorStore y nuestro embedding, podemos crear el recuperador directamente desde la base de datos Chroma:
retriever = db.as_retriever()
LLM
El último componente que necesitamos es un LLM. La solución más sencilla sería utilizar un LLM alojado, por ejemplo, utilizando la interfaz de OpenAI. Sin embargo, no queremos enviar nuestro código a un servicio alojado. En su lugar, ejecutaremos un LLM en nuestro propio hardware. Para hacerlo, utilizamos HuggingFacePipeline, que nos permite utilizar un modelo de HuggingFace en el framework de LangChain.
from langchain import HuggingFacePipeline
import transformers
model_id = "mosaicml/mpt-7b-instruct"
config = transformers.AutoConfig.from_pretrained(model_id,trust_remote_code=True)
tokenizer = transformers.AutoTokenizer.from_pretrained(model_id)
model = transformers.AutoModelForCausalLM.from_pretrained(model_id, config=config, trust_remote_code=True)
pipe = transformers.pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=100)
llm = HuggingFacePipeline(pipeline=pipe)
Como puedes ver, utilicé el modelo mpt-7b de mosaico, que solo necesita ~16GB de memoria en una GPU. Creé un AutoModelForCausalLM, que se pasa a transformers.pipeline, que finalmente se transforma en un HuggingFacePipeline. El HuggingFacePipeline implementa la misma interfaz que los objetos típicos de LLM en LangChain. Es decir, puedes usarlo de la misma manera que usarías la interfaz de LLM de OpenAI, por ejemplo.
Si tienes varias GPU en tu máquina, debes especificar cuál usar. En este caso, quiero usar la GPU con índice 0:
config.init_device="cuda:0"model.to(device='cuda:0')pipe = transformers.pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=100, device=0)
Algunos parámetros adicionales que he configurado arriba se pueden explicar de la siguiente manera:
- trust_remote_code: Esto debe establecerse en true para permitir ejecutar un modelo proveniente de fuera de LangChain.
- max_new_tokens: Esto define el número máximo de tokens que el modelo puede producir en su respuesta. Si este valor es demasiado bajo, la respuesta del modelo puede ser truncada antes de que pueda responder la pregunta por completo.
Conectar todo
Ahora tenemos todos los componentes que necesitamos y podemos combinarlos en una ConversationalRetrievalChain.
from langchain.chains import ConversationalRetrievalChainqa_chain = ConversationalRetrievalChain.from_llm(llm=llm, retriever=retriever, return_source_documents=True)
Finalmente, podemos consultar la cadena para responder nuestras preguntas. El objeto de resultado incluirá una respuesta en lenguaje natural y una lista de source_documents que se consultaron para llegar a esa respuesta.
result = qa_chain({"question":"¿Cuál es el tipo de retorno de la función create_index en KNNRetriever?", "chat_history":[]})print(f"Respuesta: {result['answer']}")print(f"Fuentes: {[x.metadata['source'] for x in result['source_documents']]}")
Aquí está la respuesta:
Respuesta: El tipo de retorno de la función create_index en KNNRetriever es np.ndarray.Fuentes: ['sample_code/langchain/retrievers/knn.py', 'sample_code/langchain/vectorstores/elastic_vector_search.py', 'sample_code/langchain/vectorstores/elastic_vector_search.py', 'sample_code/langchain/vectorstores/opensearch_vector_search.py']
Resumen
¡Hemos terminado! Bueno, más o menos. Con el código anterior, ahora podemos hacer preguntas sobre el código fuente. Sin embargo, hay algunos pasos que quizás desees modificar según tus necesidades:
- Usar tu propio código fuente como documentos en lugar del código de LangChain.
- Probar un embedding diferente. Si el embedding no encaja, el recuperador no podrá encontrar los documentos correctos y, al final, las preguntas no podrán responderse con precisión.
- Probar un modelo diferente. Hay modelos más grandes y más potentes disponibles, pero algunos pueden ser demasiado grandes para ejecutar en tu hardware. Debes encontrar el punto óptimo donde tengas un rendimiento decente pero aún puedas ejecutar el modelo de manera satisfactoria.
- Probar diferentes formas de preprocesar los documentos para facilitar el paso de recuperación. Un ejemplo común sería dividirlos en fragmentos de igual longitud.
Estoy seguro de que hay mucho más que probar para obtener un mejor rendimiento. Solo experimenta y adapta el bot a tus necesidades.
Lectura adicional
Para obtener más ejemplos de comprensión de código con LangChain, echa un vistazo a su documentación aquí:
- https://python.langchain.com/docs/use_cases/code/
En HuggingFace puedes encontrar modelos y embeddings que puedes usar fácilmente en LangChain:
- https://huggingface.co/models
¿Te ha gustado este artículo? Sígueme para recibir notificaciones sobre mis futuras publicaciones.
We will continue to update Zepes; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles
- Cómo llamar a Hugging Face AI desde una base de datos Oracle usando JavaScript
- Agentes Orientados a Documentos Un Viaje con Bases de Datos Vectoriales, LLMs, Langchain, FastAPI y Docker
- Potencia tu Python Asyncio con Aiomultiprocess Una guía completa
- Cómo convertí una base de datos relacional regular en una base de datos vectorial para almacenar incrustaciones
- Investigadores de la Universidad de Pekín presentan ChatLaw un modelo de lenguaje legal de código abierto de gran tamaño con bases de conocimientos externas integradas.
- 5 Idiomas mejor pagados para aprender este año
- Investigadores de Stanford presentan HyenaDNA un modelo genómico de base de largo alcance con longitudes de contexto de hasta 1 millón de tokens a una resolución de nucleótido único.