inteligencia_artificial:langchain
Diferencias
Muestra las diferencias entre dos versiones de la página.
| Ambos lados, revisión anteriorRevisión previaPróxima revisión | Revisión previa | ||
| inteligencia_artificial:langchain [2024/12/18 16:22] – [Depuración] alberto | inteligencia_artificial:langchain [2025/01/09 00:41] (actual) – alberto | ||
|---|---|---|---|
| Línea 9: | Línea 9: | ||
| * Agentes: sistemas automatizados que pueden interactuar con el entorno y tomar decisiones usando modelos de lenguaje. | * Agentes: sistemas automatizados que pueden interactuar con el entorno y tomar decisiones usando modelos de lenguaje. | ||
| - | La información acerca de langchain y uso se puede encontrar [[https:// | + | La información acerca de langchain y uso se puede encontrar [[https:// |
| - | ===== Modelos, Prompts y Parsers de salida ===== | + | Vamos a ver langchain en Python. Para ello vamos a dividir este tema en varias partes: |
| - | ==== Modelos ==== | + | |
| - | En primer lugar hay que tener el modelo correspondiente instalado y ejecutándose. \\ | + | |
| - | Lo modelos disponibles para funcionar con langchain | + | |
| - | En mi caso trabajo habitualmente con Ollama en local, usando el modelo Llama 3.2 de 3 mil millones de parámetros, | + | * [[inteligencia artificial: |
| + | * [[inteligencia artificial: | ||
| + | * [[inteligencia artificial: | ||
| + | * [[inteligencia artificial: | ||
| + | * [[inteligencia artificial: | ||
| + | * [[inteligencia artificial: | ||
| - | <code Python> | ||
| - | # Cargamos las librerías de langchain del modelo | ||
| - | from langchain_ollama import ChatOllama | ||
| - | from langchain_core.prompts import ChatPromptTemplate | ||
| - | |||
| - | # Instanciamos el chat del modelo definiendo sus opciones | ||
| - | chat = ChatOllama ( | ||
| - | model = " | ||
| - | temperature = 0.1 | ||
| - | ) | ||
| - | |||
| - | </ | ||
| - | |||
| - | ==== Plantillas ==== | ||
| - | Las plantillas definen prompts por defecto que tienen algunas " | ||
| - | |||
| - | <code Python> | ||
| - | prompt = """ | ||
| - | de cocina para {hora_comida} que tenga al menos \ | ||
| - | los siguientes ingredientes: | ||
| - | |||
| - | # Definimos la plantilla de prompt | ||
| - | plantilla_prompt = ChatPromptTemplate.from_template(prompt) | ||
| - | |||
| - | # Como el programa va de obtener recetas, vamos a incluir en las variables un código numérico | ||
| - | # que indique la fecha para la que estamos calculando la receta | ||
| - | hora_comida_111024 = " | ||
| - | ingredientes_111024 = " | ||
| - | |||
| - | # Creamos el mensaje a pasar al modelo, indicando en la plantilla las variables correspondientes | ||
| - | mensaje = plantilla_prompt.format_messages( | ||
| - | hora_comida = hora_comida_111024, | ||
| - | ingredientes = ingredientes_111024) | ||
| - | | ||
| - | # Obtenemos y visualizamos la receta | ||
| - | respuesta = chat.invoke(mensaje) | ||
| - | receta_111024 = respuesta.content | ||
| - | print(receta_111024) | ||
| - | </ | ||
| - | |||
| - | ==== Parseo de salida ==== | ||
| - | Se entiende por //parser// la capacidad de devolver respuestas estructuradas, | ||
| - | |||
| - | Langchain tiene sus propios //parsers// que pueden consultarse en su documentación, | ||
| - | |||
| - | <code Python> | ||
| - | from langchain_core.prompts import ChatPromptTemplate | ||
| - | |||
| - | # En primer lugar podríamos hacer que el modelo nos diese directamente una cadena de | ||
| - | # texto //string// en formato JSON y convertirla a través de un parser predefinido para | ||
| - | # este tipo de archivos, en una variable de tipo diccionario | ||
| - | import JSON | ||
| - | |||
| - | plantilla_instrucciones = """ | ||
| - | De la siguiente receta extrae la siguiente información y no indiques nada más: | ||
| - | |||
| - | nombre: extrae el título de la receta. | ||
| - | ingredientes: | ||
| - | pasos: extrae el número de pasos necesarios para cocinar la receta. | ||
| - | comensales: extrae el número de comensales para los que está preparada la receta, si no se especifica, indicar -1. | ||
| - | |||
| - | Formatea la salida como JSON con las siguientes keys, sólo el contenido del JSON: | ||
| - | nombre | ||
| - | ingredientes | ||
| - | pasos | ||
| - | comensales | ||
| - | |||
| - | receta = {receta} | ||
| - | """ | ||
| - | |||
| - | # Suponemos que tenemos la variable receta_111024 del apartado anterior, copiarla para la realización del ejemplo. | ||
| - | # Trabajamos como vimos en el apartado anterior | ||
| - | plantilla_prompt = ChatPromptTemplate.from_template(plantilla_instrucciones) | ||
| - | mensaje = plantilla_prompt.format_messages(receta=receta_111024) | ||
| - | chat = ChatOllama ( | ||
| - | model = " | ||
| - | temperature = 0.0 | ||
| - | ) | ||
| - | |||
| - | respuesta = chat.invoke(mensaje) | ||
| - | datos_json = respuesta.content | ||
| - | |||
| - | try: | ||
| - | dato_dict = json.loads(datos_json) | ||
| - | print(dato_dict) | ||
| - | print(type(dato_dict)) | ||
| - | print(dato_dict[" | ||
| - | print(type(dato_dict[" | ||
| - | except json.JSONDecodeError: | ||
| - | print(" | ||
| - | except Exception as e: | ||
| - | print(f" | ||
| - | |||
| - | |||
| - | </ | ||
| - | |||
| - | Puede que en ocasiones necesitemos crear nuestros parsers personalizados, | ||
| - | |||
| - | <code Python> | ||
| - | from langchain_core.prompts import ChatPromptTemplate | ||
| - | from langchain.output_parsers import ResponseSchema | ||
| - | from langchain.output_parsers import StructuredOutputParser | ||
| - | |||
| - | # De nuevo, suponemos que tenemos la variable receta_111024 del apartado anterior, copiarla para la realización del ejemplo. | ||
| - | # Definimos la estructura que tendrá el JSON, a patir de la información contenida en el texto de la receta | ||
| - | esquema_nombre = ResponseSchema(name = " | ||
| - | description = " | ||
| - | esquema_ingredientes = ResponseSchema(name = " | ||
| - | description = " | ||
| - | esquema_pasos = ResponseSchema(name = " | ||
| - | | ||
| - | esquema_comensales = ResponseSchema(name = " | ||
| - | description = " | ||
| - | Si no se especifican, | ||
| - | esquema_respuesta = [esquema_nombre, | ||
| - | | ||
| - | | ||
| - | | ||
| - | |||
| - | # Una vez tenemos el esquema del JSON definido, creamos el parser de salida | ||
| - | parser_de_salida = StructuredOutputParser.from_response_schemas(esquema_respuesta) | ||
| - | instrucciones_de_formato = parser_de_salida.get_format_instructions() | ||
| - | |||
| - | # Definimos las instrucciones para extraer la información, | ||
| - | plantilla_instrucciones = """ | ||
| - | De la siguiente receta extrae la siguiente información: | ||
| - | |||
| - | nombre: extrae el título de la receta. | ||
| - | ingredientes: | ||
| - | pasos: extrae el número de pasos necesarios para cocinar la receta. | ||
| - | comensales: extrae el número de comensales para los que está preparada la receta, si no se especifica, indicar -1. | ||
| - | |||
| - | receta = {receta} | ||
| - | |||
| - | {instrucciones_de_formato} | ||
| - | """ | ||
| - | |||
| - | # Creamos como hemos hecho antes, la plantilla del prompt y el mensaje a enviar al modelo | ||
| - | prompt = ChatPromptTemplate.from_template(template=plantilla_instrucciones) | ||
| - | mensaje = prompt.format_messages(receta=receta_111024, | ||
| - | respuesta = chat.invoke(mensaje) | ||
| - | |||
| - | # Por último pasamos a una variable el parseo de la respuesta obtenida, ya que gracias | ||
| - | # al esquema que hicimos en un principio, puede interpretar de la forma adecuada, que en | ||
| - | # este caso será un diccionario Python, en el que además el elemento " | ||
| - | diccionario_de_salida = parser_de_salida.parse(respuesta.content) | ||
| - | print(diccionario_de_salida) | ||
| - | print (type(diccionario_de_salida)) | ||
| - | |||
| - | print(diccionario_de_salida.get(" | ||
| - | print(type(diccionario_de_salida.get(" | ||
| - | </ | ||
| - | |||
| - | ===== Memoria ===== | ||
| - | El modo en que se gestiona la memoria en este apartado a través de langchain, está obsoleto // | ||
| - | Se debe de hacer a través de // | ||
| - | |||
| - | |||
| - | Si a un modelo le hacemos varias consultas seguidas, al responder cada una, no recuerda qué respondió las anteriores, por lo que es complicado realizar con él una conversación coherente. | ||
| - | |||
| - | <code Python> | ||
| - | from langchain_ollama import ChatOllama | ||
| - | |||
| - | chat = ChatOllama( | ||
| - | model = " | ||
| - | temperature = 0.0, | ||
| - | verbose = False | ||
| - | ) | ||
| - | |||
| - | | ||
| - | | ||
| - | | ||
| - | | ||
| - | |||
| - | </ | ||
| - | ==== Buffer de conversación ==== | ||
| - | Cuando instanciamos la clase **ConversationBufferMemory**, | ||
| - | Si le pasamos esta información con una plantilla de prompt en cada iteración, le estaremos proporcionando al modelo una memoria a corto plazo a costa de una gran cantidad de tokens. | ||
| - | |||
| - | <code Python> | ||
| - | from langchain_ollama import ChatOllama | ||
| - | from langchain.memory import ConversationBufferMemory | ||
| - | from langchain.chains.llm import LLMChain | ||
| - | from langchain.prompts import PromptTemplate | ||
| - | |||
| - | chat = ChatOllama( | ||
| - | model = " | ||
| - | temperature = 0.0, | ||
| - | verbose = False | ||
| - | ) | ||
| - | |||
| - | # Definimos el prompt con una plantilla, en el prompt especificaremos el historial | ||
| - | # de la conversación (transcripción) que pasaremos en cada nueva llamada | ||
| - | prompt = PromptTemplate( | ||
| - | input_variables=[" | ||
| - | template=" | ||
| - | ) | ||
| - | |||
| - | # Creamos la memoria de la conversación | ||
| - | memoria = ConversationBufferMemory(memory_key=" | ||
| - | |||
| - | # Cadena LLMChain con el modelo, el prompt y la memoria, hay que hacerlo así | ||
| - | chat_chain = LLMChain( | ||
| - | llm = chat, | ||
| - | prompt=prompt, | ||
| - | memory=memoria, | ||
| - | verbose=False | ||
| - | ) | ||
| - | # Obtenemos las respuestas del modelo | ||
| - | respuesta = chat_chain.run(" | ||
| - | # | ||
| - | |||
| - | respuesta = chat_chain.run(" | ||
| - | # | ||
| - | |||
| - | respuesta = chat_chain.run(" | ||
| - | # | ||
| - | |||
| - | # Podemos añadir contexto a la memoria | ||
| - | memoria.save_context( | ||
| - | {" | ||
| - | {" | ||
| - | ) | ||
| - | |||
| - | # Muestro la conversación completa | ||
| - | print(memoria.buffer_as_str) | ||
| - | </ | ||
| - | ==== Ventana del Buffer de conversación ==== | ||
| - | Podemos seleccionar el número de interacciones (pregunta-respuesta) que queremos que el modelo " | ||
| - | |||
| - | <code Python> | ||
| - | from langchain.memory import ConversationBufferWindowMemory | ||
| - | |||
| - | # Instanciamos la memoria con una ventana de contexto de 2 iteraciones: | ||
| - | memoria = ConversationBufferWindowMemory(k = 2) | ||
| - | |||
| - | # En lugar de preguntar a un modelo, le introducimos las interacciones directamente en memoria | ||
| - | memoria.save_context( | ||
| - | {" | ||
| - | {" | ||
| - | ) | ||
| - | |||
| - | memoria.save_context( | ||
| - | {" | ||
| - | {" | ||
| - | ) | ||
| - | |||
| - | memoria.save_context( | ||
| - | {" | ||
| - | {" | ||
| - | ) | ||
| - | |||
| - | print(memoria.load_memory_variables({})) | ||
| - | </ | ||
| - | ==== Ventana de tokens del buffer de conversación ==== | ||
| - | Del mismo modo que hicimos anteriormente, | ||
| - | De esta manera se recordarán los últimos tokens en el número definido en la clase **ConversationTokenBufferMemory** | ||
| - | |||
| - | <code Python> | ||
| - | # CUARTO EJEMPLO: Definimos el número máximo de tokens a recordar | ||
| - | # Se necesita instalar el módulo " | ||
| - | # Tembién he tenido que instalar el módulo " | ||
| - | from langchain.memory import ConversationTokenBufferMemory | ||
| - | from langchain_ollama import ChatOllama | ||
| - | |||
| - | chat = ChatOllama( | ||
| - | model = " | ||
| - | temperature = 0.0, | ||
| - | verbose = False | ||
| - | ) | ||
| - | |||
| - | # Definimos la memoria, indicando el modelo y el número máximo de tokens a recordar | ||
| - | memoria = ConversationTokenBufferMemory(llm=chat, | ||
| - | |||
| - | memoria.save_context ( | ||
| - | {" | ||
| - | {" | ||
| - | ) | ||
| - | |||
| - | memoria.save_context ( | ||
| - | {" | ||
| - | {" | ||
| - | ) | ||
| - | |||
| - | memoria.save_context ( | ||
| - | {" | ||
| - | {" | ||
| - | ) | ||
| - | |||
| - | print(memoria.load_memory_variables({})) | ||
| - | </ | ||
| - | {{gallery>: | ||
| - | Con el fin de " | ||
| - | |||
| - | <code Python> | ||
| - | from langchain_ollama import ChatOllama | ||
| - | from langchain.memory import ConversationSummaryBufferMemory | ||
| - | from langchain.chains.llm import LLMChain | ||
| - | from langchain.prompts import PromptTemplate | ||
| - | |||
| - | # Definimos el chat del modelo | ||
| - | chat = ChatOllama( | ||
| - | model = " | ||
| - | temperature = 0.0, | ||
| - | verbose = False # Podemos añadir como parámetro ' | ||
| - | ) | ||
| - | |||
| - | # Texto con el que vamos a trabajar | ||
| - | with open(' | ||
| - | texto = articulo.read() | ||
| - | |||
| - | # Definimos la memoria | ||
| - | memoria = ConversationSummaryBufferMemory(llm=chat, | ||
| - | |||
| - | # Añadimos contexto a la conversación, | ||
| - | memoria.save_context ( | ||
| - | {" | ||
| - | {" | ||
| - | ) | ||
| - | |||
| - | memoria.save_context ( | ||
| - | {" | ||
| - | {" | ||
| - | ) | ||
| - | |||
| - | memoria.save_context ( | ||
| - | {" | ||
| - | {" | ||
| - | ) | ||
| - | |||
| - | # | ||
| - | # | ||
| - | |||
| - | # Creamos el prompt sólo con la entrada | ||
| - | prompt = PromptTemplate( | ||
| - | input_variables=[" | ||
| - | template=" | ||
| - | ) | ||
| - | |||
| - | # Cadena (chain) de conversación | ||
| - | conversacion = LLMChain( | ||
| - | llm = chat, | ||
| - | prompt = prompt, | ||
| - | memory=memoria, | ||
| - | verbose=True | ||
| - | ) | ||
| - | |||
| - | respuesta = conversacion.invoke(" | ||
| - | |||
| - | print(respuesta) | ||
| - | # | ||
| - | # | ||
| - | </ | ||
| - | |||
| - | |||
| - | ===== Chains ===== | ||
| - | ==== Chain simple ==== | ||
| - | Un //chain// o " | ||
| - | |||
| - | Por ejemplo, el //chain// más básico entrega a un modelo una pregunta (pueden usarse plantillas de prompts) y éste devuelve una respuesta. \\ | ||
| - | |||
| - | Pueden combinarse diferentes, de forma que la salida de uno sea la entrada del siguiente y de este modo crear flujos más o menos complejos. \\ | ||
| - | |||
| - | A continuación un ejemplo de un //chain// simple. | ||
| - | |||
| - | <code python> | ||
| - | # Importamos dependencias | ||
| - | # Cuando no son plantillas estáticas, si no que pueden variar como resultado de una | ||
| - | # conversación de chat, usamos ' | ||
| - | from langchain_ollama import ChatOllama | ||
| - | from langchain.prompts import ChatPromptTemplate | ||
| - | from langchain.chains.llm import LLMChain | ||
| - | |||
| - | # Definimos el modelo con el que trabajaremos | ||
| - | modelo = ChatOllama( | ||
| - | model = " | ||
| - | temperature = 0.9, | ||
| - | verbose = False | ||
| - | ) | ||
| - | |||
| - | #Definimos el prompt | ||
| - | prompt = """ | ||
| - | del género: {genero}.""" | ||
| - | |||
| - | plantilla_prompt = ChatPromptTemplate.from_template(prompt) | ||
| - | |||
| - | # Definimos el " | ||
| - | cadena = LLMChain(llm=modelo, | ||
| - | |||
| - | # Ya sólo nos queda definir los datos de entrada y " | ||
| - | genero = " | ||
| - | titulo = cadena.run(genero) | ||
| - | print(titulo) | ||
| - | </ | ||
| - | |||
| - | ==== Chain compuesto ==== | ||
| - | La unión de diferentes chains, puede dar lugar a flujos, de modo que la salida de uno sea la entrada de otro. \\ | ||
| - | A continuación vemos un ejemplo de hasta 4 //chains// seguidos.\\ | ||
| - | |||
| - | <code python> | ||
| - | # A partir de la transcripción de una reunión entre 3 profesores que pasamos en un archivo txt, realizamos varias acciones: | ||
| - | # 1. Chain 1: Resumimos el contenido de la transcripción. | ||
| - | # 2. Chain 2: Lo pasamos a inglés. | ||
| - | # 3. Chain 3: El director responde en inglés a los profesores que mantuvieron la reunión. | ||
| - | # 4. Chain 4: Se traduce a español la respuesta del director. | ||
| - | |||
| - | # En primer lugar cargamos las dependencias | ||
| - | from langchain_ollama import ChatOllama | ||
| - | from langchain.prompts import ChatPromptTemplate | ||
| - | from langchain.chains.llm import LLMChain | ||
| - | from langchain.chains.sequential import SequentialChain | ||
| - | import pprint | ||
| - | |||
| - | # Definimos el modelo con el que trabajaremos | ||
| - | modelo = ChatOllama( | ||
| - | model = " | ||
| - | temperature = 0.9, | ||
| - | verbose = False | ||
| - | ) | ||
| - | |||
| - | # La transcripción se puede poner aquí, aunque también se podría poner al final si el código se va a reutilizar | ||
| - | #with open(" | ||
| - | # reunion = transcripcion.read() | ||
| - | |||
| - | |||
| - | # Primera plantilla de prompt: Crea un resumen a partir de la transcripción de la reunión | ||
| - | prompt1 = ChatPromptTemplate.from_template( | ||
| - | "Crea un resumen de la reunión mantenida por tres profesores, \ | ||
| - | de la cual tenemos la siguiente transcripción: | ||
| - | " | ||
| - | ) | ||
| - | |||
| - | # Eslabón 1: input = transcripcion de la reunión, output = acta | ||
| - | eslabon1 = LLMChain(llm=modelo, | ||
| - | |||
| - | # Segundo prompt: Traducción del resumen de la reunión del español al inglés | ||
| - | prompt2 = ChatPromptTemplate.from_template( | ||
| - | " | ||
| - | " | ||
| - | ) | ||
| - | |||
| - | # Segundo eslabón: input = resumen en español, output = resumen en inglés | ||
| - | eslabon2 = LLMChain(llm=modelo, | ||
| - | |||
| - | # tercer prompt: Respuesta del director en inglés | ||
| - | prompt3 = ChatPromptTemplate.from_template( | ||
| - | "El director del instituto, que habla inglés, lee el resumen de la reunión mantenida por los profesores: " | ||
| - | " | ||
| - | " | ||
| - | ) | ||
| - | |||
| - | # tercer eslabón: input = resumen en inglés, output = respuesta en inglés | ||
| - | eslabon3 = LLMChain(llm=modelo, | ||
| - | |||
| - | # cuarto prompt: Traducción de la respuesta del inglés al español | ||
| - | prompt4 = ChatPromptTemplate.from_template( | ||
| - | " | ||
| - | " | ||
| - | ) | ||
| - | |||
| - | # cuarto eslabón: input = acta en español, output = acta en inglés | ||
| - | eslabon4 = LLMChain(llm=modelo, | ||
| - | |||
| - | |||
| - | # Creamos el flujo, esto es la cadena total uniendo todos los eslabones | ||
| - | cadena = SequentialChain( | ||
| - | chains = [eslabon1, eslabon2, eslabon3, eslabon4], | ||
| - | input_variables=[" | ||
| - | output_variables=[" | ||
| - | verbose=True | ||
| - | ) | ||
| - | |||
| - | # Obtenemos la transcripción de la reunión | ||
| - | with open(" | ||
| - | reunion = transcripcion.read() | ||
| - | |||
| - | # Ejecutamos el flujo, pasando al flujo creado la entrada (trnacripción), | ||
| - | contestacion = cadena(reunion) | ||
| - | pprint.pprint(contestacion) | ||
| - | </ | ||
| - | |||
| - | ==== Cadena ruteada ==== | ||
| - | En este caso la cadena tendrá diferentes ramas en paralelo y el sistema deberá rutear el flujo por una o por otra, en función del contexto, según los datos de entrada. \\ | ||
| - | |||
| - | Vamos a ver un ejemplo de cadena con ruteo. Para construir el flujo, seguimos los sigientes pasos: | ||
| - | - Creamos un diccionario en que cada elemento tiene el nombre del prompt correspondiente a una de las ramas en paralelo, y el contenido es un chain básico que responderá a la entrada. | ||
| - | - Definimos un chain básico "por defecto", | ||
| - | - Definimos una " | ||
| - | - Creamos el prompt de ruteo a partir de la plantilla anterior, especificamos la entrada (en nuestro caso será " | ||
| - | - Creamos el " | ||
| - | - Creamos el flujo final, que es un " | ||
| - | - Ejecutamos el flujo anterior, que debe devolver una respuesta en la que podemos ver los pasos intermedios. | ||
| - | |||
| - | <code python> | ||
| - | # Como ejemplo, tendremos como entrada una consulta a una empresa de | ||
| - | # informática y electrónica que se ocupa tanto de servicio técnico | ||
| - | # como de venta de artículos. El sistema deberá pasar la consulta | ||
| - | # al departamento técnico o comercial, según proceda | ||
| - | |||
| - | from langchain_ollama import ChatOllama | ||
| - | from langchain.prompts import ChatPromptTemplate | ||
| - | from langchain.prompts import PromptTemplate | ||
| - | from langchain.chains.llm import LLMChain | ||
| - | from langchain.chains.router import MultiPromptChain | ||
| - | from langchain.chains.router.llm_router import LLMRouterChain, | ||
| - | import pprint | ||
| - | |||
| - | # Definimos el modelo con el que trabajaremos | ||
| - | # En este caso una temperatura alta nos entorpece la salida, que debe de ser | ||
| - | # un JSON. Al tener una temperatura alta, el modelo se vuelve " | ||
| - | # añade texto explicativo innecesario que genera un error | ||
| - | modelo = ChatOllama( | ||
| - | model = " | ||
| - | temperature = 0.0, | ||
| - | verbose = False | ||
| - | ) | ||
| - | |||
| - | # Definimos las plantillas de los distintos departamentos | ||
| - | plantilla_comercial = """ | ||
| - | y divertido. Estás encantado de responder a las consultas que te formulan \ | ||
| - | los clientes, siempre encuentras una salida a las preguntas difíciles y \ | ||
| - | sueles conseguir vender lo que te propones. | ||
| - | |||
| - | Responde a la siguiente consulta de un cliente: | ||
| - | {input}""" | ||
| - | |||
| - | plantilla_tecnico = """ | ||
| - | de electrónica. Eres serio, conciso, y que le gusta ir al grano. \ | ||
| - | Respondes a las cuestiones técnicas que te plantean los clientes de forma profesional, | ||
| - | y precisa. Sabes adaptar las explicaciones cuando el cliente parace tener pocos \ | ||
| - | conocimientos técnicos. | ||
| - | |||
| - | Responde a la siguiente consulta de un cliente: | ||
| - | {input}""" | ||
| - | |||
| - | |||
| - | # Definimos una lista con información de los departamentos | ||
| - | info_departamentos = [ | ||
| - | { | ||
| - | " | ||
| - | " | ||
| - | " | ||
| - | }, | ||
| - | { | ||
| - | " | ||
| - | " | ||
| - | " | ||
| - | } | ||
| - | ] | ||
| - | |||
| - | # 1. Ahora definimos la variable " | ||
| - | # con uno de los posibles ramales de ruteo. Cada elemento tendrá como nombre el especificado en | ||
| - | # info_departamentos, | ||
| - | # y el modelo de IA a usar | ||
| - | ramales = {} | ||
| - | for info_dpto in info_departamentos: | ||
| - | nombre = info_dpto[" | ||
| - | plantilla = info_dpto[" | ||
| - | prompt = ChatPromptTemplate.from_template(template=plantilla) | ||
| - | eslabon = LLMChain(llm=modelo, | ||
| - | ramales[nombre] = eslabon | ||
| - | |||
| - | # destinos: lista (también otra variable destinos_str tipo cadena de caracteres) que tiene en cada | ||
| - | # elemento cadena " | ||
| - | destinos = [f" | ||
| - | destinos_str = " | ||
| - | |||
| - | # | ||
| - | # | ||
| - | |||
| - | # 2. Definimos un prompt "por defecto" | ||
| - | # de los dos departamentos | ||
| - | default_prompt = ChatPromptTemplate.from_template(" | ||
| - | default_chain = LLMChain(llm=modelo, | ||
| - | |||
| - | # 3. Escribimos el prompt que seleccionará el ruteo (ramal) correspondiente | ||
| - | plantilla_multiprompt_ruteo = """ | ||
| - | para una empresa de informática y electrónica, | ||
| - | la naturaleza de dicha consulta. Se proporcionarán los nombres \ | ||
| - | de las opciones disponibles y una descripción para discernir cuál es la más adecuada. \ | ||
| - | También puedes revisar la entrada original si crees que al revisarla, se obtendrá \ | ||
| - | una mejor respuesta del modelo. | ||
| - | |||
| - | << | ||
| - | Devuelve SÓLO y ÚNICAMENTE un código en formato JSON de la siguiente manera: | ||
| - | ''' | ||
| - | {{{{ | ||
| - | " | ||
| - | " | ||
| - | }}}} | ||
| - | ''' | ||
| - | |||
| - | RECUERDA: " | ||
| - | O puede ser " | ||
| - | |||
| - | RECUERDA: " | ||
| - | que se necesiten modificaciones. | ||
| - | |||
| - | RECUERDA: Devuelve SÓLO y ÚNICAMENTE el output en formato JSON | ||
| - | |||
| - | << | ||
| - | {destinos} | ||
| - | |||
| - | << | ||
| - | {{input}} | ||
| - | |||
| - | << | ||
| - | """ | ||
| - | |||
| - | # Sustituimos en la plantilla multiprompt, | ||
| - | plantilla_ruteo = plantilla_multiprompt_ruteo.format(destinos=destinos_str) | ||
| - | |||
| - | # 4. Creamos el prompt de ruteo a partir de las plantilla multiprompt | ||
| - | prompt_ruteo = PromptTemplate( | ||
| - | template=plantilla_ruteo, | ||
| - | input_variables=[" | ||
| - | output_parser=RouterOutputParser(), | ||
| - | ) | ||
| - | |||
| - | # 5. Creamos el " | ||
| - | cadena_ruteo = LLMRouterChain.from_llm(modelo, | ||
| - | |||
| - | # 6. Creamos el flujo completo definiendo los posibles destinos | ||
| - | cadena_consulta = MultiPromptChain(router_chain=cadena_ruteo, | ||
| - | destination_chains=ramales, | ||
| - | default_chain=default_chain, | ||
| - | verbose=True | ||
| - | ) | ||
| - | # 7. Ejecutamos | ||
| - | #respuesta = cadena_consulta.run(" | ||
| - | #respuesta = cadena_consulta.run(" | ||
| - | respuesta = cadena_consulta.run(" | ||
| - | |||
| - | pprint.pprint(respuesta) | ||
| - | </ | ||
| - | |||
| - | ===== Preguntas y respuestas acerca de documentos ===== | ||
| - | Vamos a ver el modo en que los LLM gestionan la información de documentos, de modo que podamos preguntar a un modelo acerca de la información contenida en los mismos. \\ | ||
| - | |||
| - | Para ello aprenderemos qué son los // | ||
| - | |||
| - | De momento, no se va a documentar ningún caso práctico, ya que toda esta información está extraída del curso de Langchain " | ||
| - | |||
| - | He implementado los ejemplos que ofrece, y aunque se ejecutan sin dificultad, los resultados son erróneos y parece que no es capaz de relacionar bien el contenido de un documento CSV con lo que le pregunto. Por ello la parte práctica quedará pendiente. \\ | ||
| - | |||
| - | En cuanto a la parte teórica es de gran valor, ya que es común a todos los LLM y es lo que pasaremos a ver. | ||
| - | |||
| - | ==== Embeddings ==== | ||
| - | Los modelos de lenguaje, o LLM, sólo pueden examinar unos pocos miles de palabras de una vez, por lo que tenemos que buscar una solución para documentos que sean largos. | ||
| - | {{ : | ||
| - | |||
| - | |||
| - | Para almacenar la información de los documentos usamos // | ||
| - | |||
| - | {{ : | ||
| - | {{ : | ||
| - | |||
| - | En el ejemplo de la imagen, los dos primeros casos hablan de mascotas, mientras que el tercero habla de un coche, por lo que los vectores entre los dos primeros son similares (cerca espacialmente, | ||
| - | |||
| - | |||
| - | ==== Base de datos de vectores ==== | ||
| - | A la base de datos de vectores le pasamos los vectores resultantes de aplicar los embeddings al texto de nuestros documentos. \\ | ||
| - | |||
| - | Si los documentos son muy grandes, hay que " | ||
| - | |||
| - | {{ : | ||
| - | |||
| - | Para pasar al modelo las partes más importantes del texto que requiera la consulta, necesitaremos crear un índice **index** que busque entre los vectores de las diferentes porciones de información. | ||
| - | |||
| - | {{ : | ||
| - | |||
| - | La consulta se pasa a un vector a través de un embedding, y se compara con los diferentes vectores de cada porción de información. Se devuelven al modelo los más parecidos para poder devolver la respuesta definitiva. | ||
| - | |||
| - | {{ : | ||
| - | |||
| - | ==== Métodos de extracción de información ==== | ||
| - | Hay varios métodos a partir de los cuales se puede extraer la información más importante (la más similar vectorialmente) de las diferentes partes en que se dividió la información. | ||
| - | |||
| - | === Stuff === | ||
| - | Este método es el más sencillo y consiste en pasarle TODA la información a la consulta como contexto y de ahí al modelo. | ||
| - | * **Pros**: Una única llamada al LLM. Se tiene acceso a todos los datos. | ||
| - | * **Contras**: | ||
| - | |||
| - | Es el método más usado, junto con //Map reduce//. | ||
| - | |||
| - | {{ : | ||
| - | |||
| - | === Map reduce === | ||
| - | Cada trozo de texto se pasa junto con la consulta a un LLM que da respuestas individuales, | ||
| - | Es muy potente y permite la inspección de muchos " | ||
| - | |||
| - | {{ : | ||
| - | |||
| - | Es el método más usado, después de //Stuff//. | ||
| - | También viene bien para realizar resúmenes. | ||
| - | |||
| - | === Refine === | ||
| - | Va construyendo la respuesta en función del " | ||
| - | Muy bueno para combinar información e ir construyendo una respuesta en el tiempo. \\ | ||
| - | Da lugar a respuestas largas, pero es muy lento. | ||
| - | |||
| - | {{ : | ||
| - | |||
| - | |||
| - | === Map rerank === | ||
| - | Se llama al LLM por cada documento, y éste asigna una puntuación por la relevancia del mismo para la consulta. Se selecciona e más adecuado y a partir de él se genera la respuesta final. \\ | ||
| - | Es algo experimental, | ||
| - | |||
| - | {{ : | ||
| - | |||
| - | |||
| - | ===== Evaluación | ||
| - | Es importante saber cómo evaluar una aplicación con modelos LLM, para poder medir de algún modo con qué modelo funciona mejor, o con que // | ||
| - | |||
| - | Estas aplicaciones que usan LLM, en realidad son " | ||
| - | |||
| - | ==== Evaluación manual ==== | ||
| - | Consiste en crear por mi parte unos ejemplos de pregunta - respuesta, y después hago que el modelo cree otros ejemplos de forma automática. \\ | ||
| - | La idea es que el modelo responda a estas preguntas y ver si su respuesta es correcta: en las pruebas que he hecho deben ser igual que las mías, y en las generadas de forma automática, | ||
| - | |||
| - | Partimos del ejempo en el que obtenemos los datos de un CSV: | ||
| - | <code python> | ||
| - | # Ejemplo en el que cargaremos información de un documento CSV y le preguntaremos al modelo | ||
| - | # cuestiones acerca de esta información | ||
| - | |||
| - | from langchain_ollama import ChatOllama | ||
| - | from langchain_ollama import OllamaEmbeddings # Modelo embedding para crear las representaciones numéricas de los datos. | ||
| - | from langchain.chains.retrieval_qa.base import RetrievalQA | ||
| - | from langchain_community.document_loaders import CSVLoader | ||
| - | from langchain_community.vectorstores import DocArrayInMemorySearch | ||
| - | from langchain.indexes import VectorstoreIndexCreator | ||
| - | import pprint | ||
| - | |||
| - | # Cargamos el documento CSV | ||
| - | file = ' | ||
| - | loader = CSVLoader(file_path=file, | ||
| - | data = loader.load() | ||
| - | |||
| - | # Especificamos el modelo que generará los " | ||
| - | embeddings = OllamaEmbeddings (model=" | ||
| - | |||
| - | # Creamos el índice especificando su clase (tipo de almacén de datos) y la lista de " | ||
| - | index = VectorstoreIndexCreator (vectorstore_cls=DocArrayInMemorySearch, | ||
| - | |||
| - | # Definimos el modelo con el que vamos a trabajar | ||
| - | llm = ChatOllama( | ||
| - | model = " | ||
| - | temperature = 0.3, | ||
| - | verbose = True | ||
| - | ) | ||
| - | |||
| - | # Definimos la interfaz con el vectorstore | ||
| - | qa = RetrievalQA.from_chain_type( | ||
| - | llm = llm, | ||
| - | chain_type=" | ||
| - | retriever=index.vectorstore.as_retriever(), | ||
| - | verbose=True, | ||
| - | chain_type_kwargs={ | ||
| - | " | ||
| - | } | ||
| - | ) | ||
| - | </ | ||
| - | |||
| - | A continuación realizamos la comprobación " | ||
| - | - Generamos nuestros propios ejemplos. | ||
| - | - Usamos el chain **QAGenerateChain** que genera ejemplos de pregunta - respuesta a partir del LLM indicado. | ||
| - | - Añadimos a nuestros ejemplos hechos de forma manual, los generados automáticamente. | ||
| - | - A partir del retriever (RetrievarQA), | ||
| - | - Mostramos las respuestas para comprobar si el modelo a resuelto las consultas correctamente, | ||
| - | <code python> | ||
| - | # Evaluación manual: Creo unos ejemplos de pregunta - respuesta y después creo otros de forma automática con LLM. | ||
| - | # Hago que el modelo responda a la misma pregunta (mía o de las que ha creado él) para ver directamente si acierta o no. | ||
| - | |||
| - | ejemplos = [ | ||
| - | { | ||
| - | " | ||
| - | el 2/ | ||
| - | " | ||
| - | }, | ||
| - | { | ||
| - | " | ||
| - | " | ||
| - | } | ||
| - | ] | ||
| - | |||
| - | from langchain.evaluation.qa import QAGenerateChain | ||
| - | import langchain | ||
| - | |||
| - | langchain.debug = True | ||
| - | |||
| - | chain_generador_ejemplos = QAGenerateChain.from_llm(llm) | ||
| - | nuevos_ejemplos = chain_generador_ejemplos.apply_and_parse( | ||
| - | [{" | ||
| - | ) | ||
| - | |||
| - | ejemplos += nuevos_ejemplos | ||
| - | |||
| - | pprint.pprint(ejemplos[0]) | ||
| - | # | ||
| - | respuesta=qa.run(ejemplos[0][" | ||
| - | |||
| - | langchain.debug = False | ||
| - | |||
| - | pprint.pprint(respuesta) | ||
| - | </ | ||
| - | |||
| - | |||
| - | |||
| - | ==== Evaluación automática ==== | ||
| - | Podemos programar la automatización de la comprobación de las respuestas. \\ | ||
| - | Partimos de nuevo de la carga del documento CSV y la creación del vectorstore y el // | ||
| - | <code python> | ||
| - | # Ejemplo en el que cargaremos información de un documento CSV y le preguntaremos al modelo | ||
| - | # cuestiones acerca de esta información | ||
| - | |||
| - | from langchain_ollama import ChatOllama | ||
| - | from langchain_ollama import OllamaEmbeddings # Modelo embedding para crear las representaciones numéricas de los datos. | ||
| - | from langchain.chains.retrieval_qa.base import RetrievalQA | ||
| - | from langchain_community.document_loaders import CSVLoader | ||
| - | from langchain_community.vectorstores import DocArrayInMemorySearch | ||
| - | from langchain.indexes import VectorstoreIndexCreator | ||
| - | import pprint | ||
| - | |||
| - | # Cargamos el documento CSV | ||
| - | file = ' | ||
| - | loader = CSVLoader(file_path=file, | ||
| - | data = loader.load() | ||
| - | |||
| - | # Especificamos el modelo que generará los " | ||
| - | embeddings = OllamaEmbeddings (model=" | ||
| - | |||
| - | # Creamos el índice especificando su clase (tipo de almacén de datos) y la lista de " | ||
| - | index = VectorstoreIndexCreator (vectorstore_cls=DocArrayInMemorySearch, | ||
| - | |||
| - | # Definimos el modelo con el que vamos a trabajar | ||
| - | llm = ChatOllama( | ||
| - | model = " | ||
| - | temperature = 0.3, | ||
| - | verbose = True | ||
| - | ) | ||
| - | |||
| - | # Definimos la interfaz con el vectorstore | ||
| - | qa = RetrievalQA.from_chain_type( | ||
| - | llm = llm, | ||
| - | chain_type=" | ||
| - | retriever=index.vectorstore.as_retriever(), | ||
| - | verbose=True, | ||
| - | chain_type_kwargs={ | ||
| - | " | ||
| - | } | ||
| - | ) | ||
| - | </ | ||
| - | |||
| - | Ahora vamos a realizar la comprobación automática del software: | ||
| - | - En primer lugar volveremos a generar nuestros propios ejemplos. | ||
| - | - Volvemos a generar de forma automática ejemplos pregunta-respuesta con QAGenerateChain | ||
| - | - Ahora deberíamos juntar todos los ejemplos, como necesitamos que tengan un formato concreto, lo ajustamos. | ||
| - | - A partir del retriever, usamos la función **apply()**, | ||
| - | * query: consulta realizada. | ||
| - | * answer: respuesta que se generó anteriormente, | ||
| - | * result: respuesta gnerada ahora por el modelo especificado. | ||
| - | - Ahora con la función **evaluate()** del chain **QAEvalChain** le metemos los datos obtenidos, el LLM especificado compara los //answer// con los //result//, determinando que la respuesta es correcta //correct// si son los mismos, o incorrecta // | ||
| - | |||
| - | <code python> | ||
| - | # Evaluación automática: | ||
| - | # El modelo responda a la mismas preguntas (mías o de las que ha creado él) y deberá clasificar las respuestas en correctas o incorrectas. | ||
| - | |||
| - | # Creo mis propios ejemlos de forma manual | ||
| - | ejemplos = [ | ||
| - | { | ||
| - | " | ||
| - | " | ||
| - | }, | ||
| - | { | ||
| - | " | ||
| - | " | ||
| - | } | ||
| - | ] | ||
| - | |||
| - | # Dependencias necesarias para la evaluación | ||
| - | from langchain.evaluation.qa import QAGenerateChain # Generación automática de pares pregunta-respuesta | ||
| - | from langchain.evaluation.qa import QAEvalChain | ||
| - | |||
| - | #Definimos el chain QAGenerateChain y lo usamos para definir pares pregunta-respuestas de forma automática | ||
| - | chain_generador_ejemplos = QAGenerateChain.from_llm(llm) | ||
| - | nuevos_ejemplos = chain_generador_ejemplos.apply_and_parse( | ||
| - | [{" | ||
| - | ) | ||
| - | |||
| - | # Junto todos los ejemplos, ajustándolos a la forma query: | ||
| - | ejemplos = ejemplos + [ | ||
| - | { | ||
| - | " | ||
| - | " | ||
| - | } for ejemplo in nuevos_ejemplos | ||
| - | ] | ||
| - | |||
| - | # Ejecuto las consultas de los ejemplos y obtnego: la consulta (query), la respuesta del ejemlop, ya sea la mía o la que generó el modelo (answwer) y la que ha generado ahora (result) | ||
| - | predicciones = qa.apply(ejemplos) | ||
| - | |||
| - | # Evaluamos las respuestas, ' | ||
| - | eval_chain = QAEvalChain.from_llm(llm) | ||
| - | salidas_clasificadas = eval_chain.evaluate(ejemplos, | ||
| - | |||
| - | # Mostramos las conclusiones de la evaluación automática | ||
| - | for i, eg in enumerate (ejemplos): | ||
| - | print(f" | ||
| - | print(" | ||
| - | print(" | ||
| - | print(" | ||
| - | print(" | ||
| - | print() | ||
| - | |||
| - | print(salidas_clasificadas[0]) | ||
| - | </ | ||
| - | |||
| - | En nuestro caso es siempre incorrecto, por el problema que arrastramos cuando vimos los chains QA para preguntar acerca del contenido de documentos: Como cargamos los datos de un CSV, los registros se pasan como " | ||
| - | * usaremos el chain **QAEvalChain** | ||
| - | ==== Depuración ==== | ||
| - | También es importante saber que hay varias maneras de ver qué está pasando "tras bambalinas", | ||
| - | Esto se puede hacer de dos Maneras: | ||
| - | * **verbose = true**, como parámetro del chain, de modo que se podrá observar qué ocurre " | ||
| - | <code python> | ||
| - | # Anteriormente se define qa como un QAChain y data como el contenido de un loaderCSV | ||
| - | from langchain.evaluation.qa import QAGenerateChain | ||
| - | |||
| - | chain_generador_ejemplos = QAGenerateChain.from_llm(llm, | ||
| - | ejemplos = chain_generador_ejemplos.apply_and_parse( | ||
| - | [{" | ||
| - | ) | ||
| - | |||
| - | pprint.pprint(ejemplos[0]) | ||
| - | respuesta=qa.run(ejemplos[0][" | ||
| - | |||
| - | pprint.pprint(respuesta) | ||
| - | </ | ||
| - | * **Debug Mode**: importamos directamente langchain, para poder activar (y desactivar) el modo depuración, | ||
| - | <code python> | ||
| - | # Anteriormente se define qa como un QAChain y data como el contenido de un loaderCSV | ||
| - | from langchain.evaluation.qa import QAGenerateChain | ||
| - | import langchain | ||
| - | |||
| - | langchain.debug = True | ||
| - | chain_generador_ejemplos = QAGenerateChain.from_llm(llm) | ||
| - | ejemplos = chain_generador_ejemplos.apply_and_parse( | ||
| - | [{" | ||
| - | ) | ||
| - | |||
| - | pprint.pprint(ejemplos[0]) | ||
| - | respuesta=qa.run(ejemplos[0][" | ||
| - | |||
| - | langchain.debug = False | ||
| - | |||
| - | pprint.pprint(respuesta) | ||
| - | </ | ||
| - | |||
| - | ===== Agentes ===== | ||
inteligencia_artificial/langchain.1734535370.txt.gz · Última modificación: por alberto
