Guía completa de LangChain en JavaScript — CodesCode

Aprende acerca de los componentes esenciales de LangChain agentes, modelos, fragmentos y cadenas, y cómo aprovechar el poder de LangChain en JavaScript.

En esta guía completa, profundizaremos en los componentes esenciales de LangChain y demostraremos cómo aprovechar su poder en JavaScript.

LangChainJS es un versátil framework de JavaScript que permite a los desarrolladores e investigadores crear, experimentar y analizar modelos y agentes de lenguaje. Ofrece un conjunto completo de características para los entusiastas del procesamiento del lenguaje natural (NLP), desde la construcción de modelos personalizados hasta la manipulación eficiente de datos de texto. Como framework de JavaScript, también permite a los desarrolladores integrar fácilmente sus aplicaciones de inteligencia artificial en aplicaciones web.

Prerrequisitos

Para seguir este artículo, crea una nueva carpeta e instala el paquete npm de LangChain:

npm install -S langchain

Después de crear una nueva carpeta, crea un nuevo archivo de módulo JS utilizando el sufijo .mjs (como test1.mjs).

Agentes

En LangChain, un agente es una entidad que puede entender y generar texto. Estos agentes se pueden configurar con comportamientos y fuentes de datos específicas y entrenar para realizar diversas tareas relacionadas con el lenguaje, lo que los convierte en herramientas versátiles para una amplia gama de aplicaciones.

Creación de un agente LangChain

Los agentes se pueden configurar para utilizar “herramientas” para recopilar los datos que necesitan y formular una buena respuesta. Echa un vistazo al ejemplo a continuación. Utiliza la API de Serp (una API de búsqueda en Internet) para buscar en Internet información relevante a la pregunta o entrada, y utiliza esa información para dar una respuesta. También utiliza la herramienta llm-math para realizar operaciones matemáticas, por ejemplo, para convertir unidades o encontrar el cambio porcentual entre dos valores:

import { initializeAgentExecutorWithOptions } from "langchain/agents";import { ChatOpenAI } from "langchain/chat_models/openai";import { SerpAPI } from "langchain/tools";import { Calculator } from "langchain/tools/calculator";process.env["OPENAI_API_KEY"] = "TU_CLAVE_DE_OPENAI"process.env["SERPAPI_API_KEY"] = "TU_CLAVE_DE_SERPAPI"const tools = [new Calculator(), new SerpAPI()];const model = new ChatOpenAI({ modelName: "gpt-3.5-turbo", temperature: 0 });const executor = await initializeAgentExecutorWithOptions(tools, model, {  agentType: "openai-functions",  verbose: false,});const result = await executor.run("Al buscar en Internet, encuentra cuántos álbumes ha lanzado Boldy James desde 2010 y cuántos álbumes ha lanzado Nas desde 2010. Encuentra quién ha lanzado más álbumes y muestra la diferencia en porcentaje.");console.log(result);

Después de crear la variable model utilizando modelName: "gpt-3.5-turbo" y temperature: 0, creamos el executor que combina el model creado con las herramientas especificadas (SerpAPI y Calculator). En la entrada, le he pedido al LLM que busque en Internet (usando SerpAPI) y encuentre qué artista ha lanzado más álbumes desde 2010: Nas o Boldy James, y muestre la diferencia porcentual (usando Calculator).

En este ejemplo, tuve que decir explícitamente al LLM “Al buscar en Internet…” para que obtuviera datos hasta el día actual utilizando Internet en lugar de utilizar los datos predeterminados de OpenAI limitados a 2021.

Aquí se muestra la salida:

> node test1.mjsBoldy James ha lanzado 4 álbumes desde 2010. Nas ha lanzado 17 álbumes de estudio desde 2010. Por lo tanto, Nas ha lanzado más álbumes que Boldy James. La diferencia en el número de álbumes es de 13.Para calcular la diferencia en porcentaje, podemos usar la fórmula: (Diferencia / Total) * 100.En este caso, la diferencia es de 13 y el total es de 17.La diferencia en porcentaje es: (13 / 17) * 100 = 76.47%.Por lo tanto, Nas ha lanzado un 76.47% más de álbumes que Boldy James desde 2010.

Modelos

Existen tres tipos de modelos en LangChain: LLMs, modelos de chat y modelos de incrustación de texto. Vamos a explorar cada tipo de modelo con algunos ejemplos.

Modelo de lenguaje

LangChain proporciona una forma de utilizar modelos de lenguaje en JavaScript para producir una salida de texto basada en una entrada de texto. No es tan complejo como un modelo de chat, y se utiliza mejor con tareas simples de lenguaje de entrada y salida. Aquí tienes un ejemplo utilizando OpenAI:

import { OpenAI } from "langchain/llms/openai";const llm = new OpenAI({  openAIApiKey: "TU_CLAVE_DE_OPENAI",  model: "gpt-3.5-turbo",  temperature: 0});const res = await llm.call("Listar todas las bayas rojas");console.log(res);

Como puedes ver, utiliza el modelo gpt-3.5-turbo para listar todas las bayas rojas. En este ejemplo, configuré la temperatura en 0 para que LLM sea precisamente exacto. Salida:

1. Fresas2. Arándanos rojos3. Frambuesas4. Grosellas rojas5. Grosellas espinosas rojas6. Bayas de saúco rojas7. Bayas de huckleberry rojas8. Moras rojas

Modelo de chat

Si deseas respuestas y conversaciones más sofisticadas, necesitas utilizar modelos de chat. ¿En qué se diferencian técnicamente los modelos de chat de los modelos de lenguaje? Bueno, en palabras de la documentación de LangChain:

Los modelos de chat son una variación de los modelos de lenguaje. Aunque los modelos de chat utilizan modelos de lenguaje en su interior, la interfaz que utilizan es un poco diferente. En lugar de utilizar una API de “texto de entrada, texto de salida”, utilizan una interfaz donde los “mensajes de chat” son las entradas y salidas.

Aquí tienes un script simple (bastante inútil pero divertido) de un modelo de chat en JavaScript:

import { ChatOpenAI } from "langchain/chat_models/openai";import { PromptTemplate } from "langchain/prompts";const chat = new ChatOpenAI({  openAIApiKey: "TU_CLAVE_DE_OPENAI",  model: "gpt-3.5-turbo",  temperature: 0});const prompt = PromptTemplate.fromTemplate(`Eres un asistente poético que siempre responde en rimas: {question}`);const runnable = prompt.pipe(chat);const response = await runnable.invoke({ question: "¿Quién es mejor, Djokovic, Federer o Nadal?" });console.log(response);

Como puedes ver, el código primero envía un mensaje del sistema y le dice al chatbot que sea un asistente poético que siempre responda en rimas, y después envía un mensaje humano diciéndole al chatbot que me diga quién es el mejor jugador de tenis: Djokovic, Federer o Nadal. Si ejecutas este modelo de chat, verás algo como esto:

AIMessage.content:'En el reino del tenis, todos brillan intensamente,\n' +'Djokovic, Federer y Nadal, un glorioso espectáculo brillante.\n' +'Cada uno con su estilo y habilidad única,\n' +'Elegir al mejor es un emocionante desafío.\n' +'\n' +'Djokovic, el serbio, un maestro de la precisión,\n' +'Con agilidad y enfoque, juega con decisión.\n' +'Sus golpes poderosos y su implacable manejo,\n' +'Lo convierten en una fuerza difícil de sobrevivir.\n' +'\n' +'Federer, el maestro suizo, un verdadero artista,\n' +'Gracioso y elegante, su juego es el más inteligente.\n' +'Su técnica fluida y su toque mágico,\n' +'Dejan a los espectadores maravillados, tanto como bálsamo.\n' +'\n' +'Nadal, el español, un guerrero en la arcilla,\n' +'Su feroz determinación mantiene a los oponentes en la orilla.\n' +'Con su poder implacable y una lucha interminable,\n' +'Conquista la cancha con toda su fuerza indomable.\n' +'\n' +"Entonces, ¿quién es mejor? Es una cuestión de gusto,\n" +"La grandeza de cada jugador no puede borrarse ni tras un rebote.\n" +"Al final, es el amor por el juego que compartimos,\n" +'Lo que los hace campeones, sin igual y sin detenimientos.'

¡Muy genial!

Incrustaciones

Los modelos de incrustaciones brindan una forma de convertir palabras y números en un texto en vectores, que luego pueden asociarse con otras palabras o números. Esto puede sonar abstracto, así que veamos un ejemplo:

import { OpenAIEmbeddings } from "langchain/embeddings/openai";process.env["OPENAI_API_KEY"] = "TU_CLAVE_DE_API_DE_OPENAI"const embeddings = new OpenAIEmbeddings();const res = await embeddings.embedQuery("¿Quién creó la World Wide Web?");console.log(res)

Esto devolverá una larga lista de números fraccionarios:

[  0.02274114,  -0.012759142,   0.004794503,  -0.009431809,    0.01085313,  0.0019698727,  -0.013649924,   0.014933698, -0.0038185727,  -0.025400387,  0.010794181,   0.018680222,   0.020042595,   0.004303263,   0.019937797,  0.011226473,   0.009268062,   0.016125774,  0.0116391145, -0.0061765253,  -0.0073358514, 0.00021696436,   0.004896026,  0.0034026562,  -0.018365828,  ... 1501 más elementos]

Así es como se ve una incrustación. ¡Todos esos números fraccionarios solo para seis palabras!

Luego, esta incrustación se puede utilizar para asociar el texto de entrada con respuestas potenciales, textos relacionados, nombres y más.

Ahora veamos un caso de uso de los modelos de incrustación…

Aquí hay un script que tomará la pregunta “¿Cuál es el animal más pesado?” y encontrará la respuesta correcta en la lista de posibles respuestas proporcionada utilizando incrustaciones:

import { OpenAIEmbeddings } from "langchain/embeddings/openai";process.env["OPENAI_API_KEY"] = "TU_CLAVE_DE_API_DE_OPENAI"const embeddings = new OpenAIEmbeddings();function cosinesim(A, B) {    var dotproduct = 0;    var mA = 0;    var mB = 0;    for(var i = 0; i < A.length; i++) {        dotproduct += A[i] * B[i];        mA += A[i] * A[i];        mB += B[i] * B[i];    }    mA = Math.sqrt(mA);    mB = Math.sqrt(mB);    var similarity = dotproduct / (mA * mB);    return similarity;}const res1 = await embeddings.embedQuery("La ballena azul es el animal más pesado del mundo");const res2 = await embeddings.embedQuery("George Orwell escribió 1984");const res3 = await embeddings.embedQuery("Cosas aleatorias");const text_arr = ["La ballena azul es el animal más pesado del mundo", "George Orwell escribió 1984", "Cosas aleatorias"]const res_arr = [res1, res2, res3]const question = await embeddings.embedQuery("¿Cuál es el animal más pesado?");const sims = []for (var i=0;i<res_arr.length;i++){    sims.push(cosinesim(question, res_arr[i]))}Array.prototype.max = function() {    return Math.max.apply(null, this);};console.log(text_arr[sims.indexOf(sims.max())])

Este código utiliza la función cosinesim(A, B) para encontrar la relación de cada respuesta con la pregunta. Al encontrar la lista de incrustaciones más relacionadas con la pregunta utilizando la función Array.prototype.max al encontrar el valor máximo en la matriz de índices de relación que se generaron usando cosinesim, el código luego puede encontrar la respuesta correcta encontrando qué texto de text_arr corresponde a la respuesta más relacionada: text_arr[sims.indexOf(sims.max())].

Salida:

La ballena azul es el animal más pesado del mundo

Fragmentos

Los modelos de LangChain no pueden manejar textos largos y usarlos para generar respuestas. Aquí es donde entran en juego los fragmentos y la división de texto. Permíteme mostrarte dos métodos sencillos para dividir tus datos de texto en fragmentos antes de introducirlo en LangChain.

Dividiendo fragmentos por caracteres

Para evitar rupturas abruptas en los fragmentos, puedes dividir tus textos por párrafos al separarlos en cada aparición de un salto de línea:

import { Document } from "langchain/document";import { CharacterTextSplitter } from "langchain/text_splitter";const splitter = new CharacterTextSplitter({  separator: "\n",  chunkSize: 7,  chunkOverlap: 3,});const output = await splitter.createDocuments([your_text]);

Esta es una forma útil de dividir un texto. Sin embargo, puedes utilizar cualquier carácter como separador de fragmentos, no solo \n.

Dividiendo fragmentos de forma recursiva

Si quieres dividir estrictamente tu texto por una longitud determinada de caracteres, puedes hacerlo utilizando RecursiveCharacterTextSplitter:

import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";const splitter = new RecursiveCharacterTextSplitter({  chunkSize: 100,  chunkOverlap: 15,});const output = await splitter.createDocuments([your_text]);

En este ejemplo, el texto se divide cada 100 caracteres, con una superposición de fragmentos de 15 caracteres.

Tamaño y superposición de fragmentos

Al ver esos ejemplos, probablemente te habrás preguntado qué significan exactamente los parámetros de tamaño de fragmento y superposición, y qué implicaciones tienen en el rendimiento. Bueno, permíteme explicarlo de forma sencilla en dos puntos.

  • El tamaño del fragmento determina la cantidad de caracteres que habrá en cada fragmento. Cuanto mayor sea el tamaño del fragmento, más datos contendrá el fragmento, más tiempo llevará a LangChain procesarlo y producir una salida, y viceversa.

  • La superposición de fragmentos es lo que comparte información entre fragmentos para que compartan un contexto. Cuanto mayor sea la superposición de fragmentos, más redundantes serán tus fragmentos; cuanto menor sea la superposición de fragmentos, menos contexto se compartirá entre los fragmentos. En general, una buena superposición de fragmentos se encuentra entre el 10% y el 20% del tamaño del fragmento, aunque la superposición de fragmentos ideal varía según los diferentes tipos de texto y casos de uso.

Cadenas

Cadenas son básicamente múltiples funcionalidades de LLM enlazadas para realizar tareas más complejas que no podrían hacerse fácilmente con una estructura de entrada y salida de LLM simple. Veamos un ejemplo interesante:

import { ChatPromptTemplate } from "langchain/prompts";import { LLMChain } from "langchain/chains";import { ChatOpenAI } from "langchain/chat_models/openai";process.env["OPENAI_API_KEY"] = "TU_CLAVE_DE_API_DE_OPENAI"const wiki_text = `Alexander Stanislavovich 'Sasha' Bublik (Александр Станиславович Бублик; nacido el 17 de junio de 1997) es un tenista profesional kazajo. Ha llegado a ocupar el puesto número 25 del mundo en individuales según la Association of Tennis Professionals (ATP), que logró en julio de 2023, y es el número 1 actual de Kazajistán...Alexander Stanislavovich Bublik nació el 17 de junio de 1997 en Gatchina, Rusia, y comenzó a jugar al tenis a los cuatro años. Fue entrenado por su padre, Stanislav. En el circuito junior, Bublik alcanzó el puesto número 19 y ganó once títulos (seis individuales y cinco dobles) en el circuito junior de la Federación Internacional de Tenis (ITF).[4][5]...`const chat = new ChatOpenAI({ temperature: 0 });const chatPrompt = ChatPromptTemplate.fromMessages([  [    "sistema",    "Eres un asistente útil que {acción} el texto proporcionado",  ],  ["humano", "{texto}"],]);const cadenaB = new LLMChain({  prompt: chatPrompt,  llm: chat,});const resB = await cadenaB.call({  acción: "enumera todos los números importantes de",  texto: wiki_text,});console.log({ resB });

Este código toma una variable en su prompt y formula una respuesta correcta desde el punto de vista factual (temperatura: 0). En este ejemplo, pedí al LLM que enumerara todos los números importantes de una breve biografía de Wikipedia de mi jugador de tenis favorito.

Aquí está la salida de este código:

{  resB: {    texto: 'Números importantes del texto proporcionado:\n' +      '\n' +      "- Fecha de nacimiento de Alexander Stanislavovich 'Sasha' Bublik: 17 de junio de 1997\n" +      "- Mejor ranking de Bublik en individuales: número 25 mundial\n" +      "- Mejor ranking de Bublik en dobles: número 47 mundial\n" +      "- Títulos individuales de Bublik en el ATP Tour: 3\n" +      "- Finalistas individuales de Bublik en el ATP Tour: 6\n" +      "- Altura de Bublik: 1,96 m (6 pies 5 pulgadas)\n" +      "- Número de aces servidos por Bublik en la temporada 2021 del ATP Tour: desconocido\n" +      "- Ranking del circuito junior de Bublik: número 19\n" +      "- Títulos del circuito junior de Bublik: 11 (6 individuales y 5 dobles)\n" +      "- Ciudadanía anterior de Bublik: Rusia\n" +      "- Ciudadanía actual de Bublik: Kazajistán\n" +      "- Rol de Bublik en el equipo Levitov Chess Wizards: miembro suplente"  }}

Muy cool, pero esto realmente no muestra todo el poder de las cadenas. Veamos un ejemplo más práctico:

import { z } from "zod";import { zodToJsonSchema } from "zod-to-json-schema";import { ChatOpenAI } from "langchain/chat_models/openai";import {  ChatPromptTemplate,  SystemMessagePromptTemplate,  HumanMessagePromptTemplate,} from "langchain/prompts";import { JsonOutputFunctionsParser } from "langchain/output_parsers";process.env["OPENAI_API_KEY"] = "YOUR_OPENAI_KEY"const zodSchema = z.object({  albums: z    .array(      z.object({        name: z.string().describe("El nombre del álbum"),        artist: z.string().describe("El/los artista(s) que hizo/el álbum"),        length: z.number().describe("La duración del álbum en minutos"),        genre: z.string().optional().describe("El género del álbum"),      })    )    .describe("Una matriz de álbumes de música mencionados en el texto"),});const prompt = new ChatPromptTemplate({  promptMessages: [    SystemMessagePromptTemplate.fromTemplate(      "Enumera todos los álbumes de música mencionados en el siguiente texto."    ),    HumanMessagePromptTemplate.fromTemplate("{inputText}"),  ],  inputVariables: ["inputText"],});const llm = new ChatOpenAI({ modelName: "gpt-3.5-turbo", temperature: 0 });const functionCallingModel = llm.bind({  functions: [    {      name: "output_formatter",      description: "Siempre se debe usar para formatear correctamente la salida",      parameters: zodToJsonSchema(zodSchema),    },  ],  function_call: { name: "output_formatter" },});const outputParser = new JsonOutputFunctionsParser();const chain = prompt.pipe(functionCallingModel).pipe(outputParser);const response = await chain.invoke({  inputText: "Mis álbumes favoritos son: 2001, To Pimp a Butterfly y Led Zeppelin IV",});console.log(JSON.stringify(response, null, 2));

Este código lee un texto de entrada, identifica todos los álbumes de música mencionados, identifica el nombre, artista, duración y género de cada álbum, y finalmente pone todos los datos en formato JSON. Aquí está la salida dada la entrada “Mis álbumes favoritos son: 2001, To Pimp a Butterfly y Led Zeppelin IV”:

{  "albums": [    {      "name": "2001",      "artist": "Dr. Dre",      "length": 68,      "genre": "Hip Hop"    },    {      "name": "To Pimp a Butterfly",      "artist": "Kendrick Lamar",      "length": 79,      "genre": "Hip Hop"    },    {      "name": "Led Zeppelin IV",      "artist": "Led Zeppelin",      "length": 42,      "genre": "Rock"    }  ]}

Este es solo un ejemplo divertido, pero esta técnica se puede utilizar para estructurar datos de texto no estructurados en innumerables otras aplicaciones.

Yendo más allá de OpenAI

Aunque sigo usando modelos de OpenAI como ejemplos de las diferentes funcionalidades de LangChain, no se limita a modelos de OpenAI. Puedes usar LangChain con una multitud de otros LLMs y servicios de IA. Puedes encontrar la lista completa de LLMs integrables con LangChain y JavaScript en su documentación.

Por ejemplo, puedes usar Cohere con LangChain. Después de instalar Cohere, utilizando npm install cohere-ai, puedes hacer un código simple de pregunta -> respuesta usando LangChain y Cohere de la siguiente manera:

import { Cohere } from "langchain/llms/cohere";const model = new Cohere({  maxTokens: 50,  apiKey: "TU_CLAVE_COHERE", // En Node.js, por defecto se usa process.env.COHERE_API_KEY});const res = await model.call(  "Proponer un nombre para un nuevo álbum de Nas");console.log({ res });

Salida:

{  res: ' Aquí hay algunos posibles nombres para un nuevo álbum de Nas:\n' +    '\n' +    "- King's Landing\n" +    "- God's Son: The Sequel\n" +    "- Street's Disciple\n" +    '- Izzy Free\n' +    '- Nas and the Illmatic Flow\n' +    '\n' +    '¿Algún'}

Conclusión

En esta guía, has visto los diferentes aspectos y funcionalidades de LangChain en JavaScript. Puedes usar LangChain en JavaScript para desarrollar fácilmente aplicaciones web impulsadas por IA y experimentar con LLMs. Asegúrate de consultar la documentación de LangChainJS para obtener más detalles sobre funcionalidades específicas.

¡Feliz codificación y experimentación con LangChain en JavaScript! Si disfrutaste este artículo, es posible que también te guste leer sobre cómo usar LangChain con Python.

Comparte este artículo


Leave a Reply

Your email address will not be published. Required fields are marked *