Полное руководство по LangChain на JavaScript — CodesCode

Изучите основные компоненты LangChain - агенты, модели, чанки, цепи - и как использовать мощь LangChain в JavaScript.

В этом исчерпывающем руководстве мы глубоко погрузимся в основные компоненты LangChain и продемонстрируем, как использовать его силу в JavaScript.

LangChainJS – это универсальный фреймворк JavaScript, который позволяет разработчикам и исследователям создавать, экспериментировать и анализировать модели и агенты естественного языка. Он предлагает широкий набор функций для энтузиастов в области обработки естественного языка (NLP), начиная от создания пользовательских моделей до эффективной обработки текстовых данных. Благодаря тому, что LangChain является фреймворком JavaScript, он также позволяет разработчикам легко интегрировать свои приложения искусственного интеллекта в веб-приложения.

Необходимые условия

Чтобы следовать за этой статьей, создайте новую папку и установите пакет LangChain через npm:

npm install -S langchain

После создания новой папки, создайте новый JS модульный файл с помощью суффикса .mjs (например, test1.mjs).

Агенты

В LangChain агент – это сущность, способная понимать и генерировать текст. Эти агенты могут быть настроены с определенным поведением и источниками данных и обучены выполнять различные задачи, связанные с языком, что делает их универсальным инструментом для широкого спектра приложений.

Создание агента LangChain

Агенты могут быть настроены для использования “инструментов” для сбора необходимых данных и формулировки хорошего ответа. Взгляните на приведенный ниже пример. Он использует Serp API (API поиска в интернете), чтобы искать в интернете информацию, соответствующую вопросу или вводу, и использовать ее для формирования ответа. Он также использует инструмент llm-math для выполнения математических операций – например, преобразования единиц измерения или определения процентного изменения между двумя значениями:

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"] = "YOUR_OPENAI_KEY"process.env["SERPAPI_API_KEY"] = "YOUR_SERPAPI_KEY"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("By searching the Internet, find how many albums has Boldy James dropped since 2010 and how many albums has Nas dropped since 2010? Find who dropped more albums and show the difference in percent.");console.log(result);

После создания переменной model с использованием modelName: "gpt-3.5-turbo" и temperature: 0, мы создаем executor, который объединяет созданную model с указанными инструментами (SerpAPI и Calculator). Во входных данных я попросил LLM искать в Интернете (с использованием SerpAPI) и найти, какой исполнитель выпустил больше альбомов с 2010 года – Nas или Boldy James – и показать процентное отличие (с использованием Calculator).

В этом примере я явно указал LLM “Поиском в Интернете …”, чтобы он получил данные до текущего дня, используя Интернет вместо данных по умолчанию OpenAI, ограниченных 2021 годом.

Вот как выглядит результат:

> node test1.mjsBoldy Джеймс выпустил 4 альбома с 2010 года. Нас выпустил 17 студийных альбомов с 2010 года. Следовательно, Нас выпустил больше альбомов, чем Болди Джеймс. Разница в количестве альбомов составляет 13. Чтобы рассчитать разницу в процентах, мы можем использовать формулу: (Разница / Всего) * 100. В данном случае разница составляет 13, а общее количество 17. Разница в процентах составляет: (13 / 17) * 100 = 76,47%. Таким образом, Нас выпустил на 76,47% больше альбомов, чем Болди Джеймс с 2010 года.

Модели

В LangChain существует три типа моделей: LLM (текстовые модели), модели для чатов и векторные модели текстов. Давайте рассмотрим каждый тип модели на примерах.

Текстовая модель

LangChain предоставляет возможность использовать текстовые модели на JavaScript для получения текстового вывода на основе текстового ввода. Они не такие сложные, как модели для чатов, и лучше всего подходят для простых языковых задач типа «ввод-вывод». Вот пример с использованием OpenAI:

import { OpenAI } from "langchain/llms/openai";const llm = new OpenAI({  openAIApiKey: "ВАШ_КЛЮЧ_OPENAI",  model: "gpt-3.5-turbo",  temperature: 0});const res = await llm.call("Список всех красных ягод");console.log(res);

Как видите, он использует модель gpt-3.5-turbo для составления списка всех красных ягод. В этом примере я установил температуру на 0, чтобы модель была точной. Результат:

1. Клубника2. Клюква3. Малина4. Красная смородина5. Красная крыжовник6. Красные бузины7. Красные черноплодка8. Красные шелковицы

Модель для чатов

Если вам нужны более сложные ответы и разговоры, вам нужно использовать модели для чатов. В чем техническое отличие моделей для чатов от текстовых моделей? Как сказано в документации LangChain:

Модели для чатов – это вариация текстовых моделей. Хотя они используют текстовые модели внутри, интерфейс, который они используют, немного отличается. Вместо использования API «текст в текст», они используют интерфейс, где «сообщения в чате» являются входами и выходами.

Вот простой (немного бесполезный, но забавный) скрипт модели для чатов на JavaScript:

import { ChatOpenAI } from "langchain/chat_models/openai";import { PromptTemplate } from "langchain/prompts";const chat = new ChatOpenAI({  openAIApiKey: "ВАШ_КЛЮЧ_OPENAI",  model: "gpt-3.5-turbo",  temperature: 0});const prompt = PromptTemplate.fromTemplate(`Вы поэтический помощник, всегда отвечающий в рифмах: {question}`);const runnable = prompt.pipe(chat);const response = await runnable.invoke({ question: "Кто лучше, Джокович, Федерер или Надаль?" });console.log(response);

Как видите, код сначала отправляет системное сообщение и сообщает чатботу стать поэтическим помощником, всегда отвечающим в рифмах, а затем отправляет сообщение от пользователя, в котором просит чатбота рассказать, кто лучший теннисист: Джокович, Федерер или Надаль. Если вы запустите эту модель, увидите что-то вроде этого:

AIMessage.content:'В мире тенниса все они сияют ярко,\n' +'Джокович, Федерер и Надаль, великолепный взгляд.\n' +'Каждый со своим уникальным стилем и мастерством,\n' +'Выбрать лучшего - сложное удовольствие.\n' +'\n' +'Джокович, серб, мастер точности,\n' +'С подвижностью и сосредоточенностью играет решительно.\n' +'Его мощные взмахи и неослабевающий задор,\n' +'Делают его силой, от которой трудно уцелеть.\n' +'\n' +'Федерер, швейцарский маэстро, истинный художник,\n' +'Изящный и элегантный, его игра наиболее умна.\n' +'Его плавная техника и волшебное прикосновение,\n' +'Оставляют зрителей в изумлении, от всего сердца.\n' +'\n' +'Надаль, испанский воин на глине,\n' +'Его несгибаемое решимость удерживает оппонентов на расстоянии.\n' +'С его неугомонной силой и неустанной борьбой,\n' +'Он завоевывает корт со всей своей мощью.\n' +'\n' +"Так кто же лучше? Это вопрос вкуса,\n" +"Величие каждого игрока не может быть стерто.\n" +"В конце концов, это любовь к игре, которую мы разделяем,\n" +'Делает их всех чемпионами, безупречными.

Довольно круто!

Эмбеддинги

Модели эмбеддингов предоставляют способ превращения слов и чисел в тексте в векторы, которые могут быть связаны с другими словами или числами. Это может звучать абстрактно, поэтому рассмотрим пример:

import { OpenAIEmbeddings } from "langchain/embeddings/openai";process.env["OPENAI_API_KEY"] = "ВАШ_КЛЮЧ_OPENAI"const embeddings = new OpenAIEmbeddings();const res = await embeddings.embedQuery("Кто создал Всемирную паутину?");console.log(res)

Это вернет длинный список десятичных чисел:

[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 еще элементов]

Вот как выглядит эмбеддинг. Все эти десятичные числа только для шести слов!

Этот эмбеддинг затем может быть использован для связи входного текста с потенциальными ответами, связанными текстами, именами и т. д.

Теперь рассмотрим пример использования моделей эмбеддингов…

Вот скрипт, который примет вопрос “Какое животное самое тяжелое?” и найдет правильный ответ в предоставленном списке возможных ответов, используя эмбеддинги:

import { OpenAIEmbeddings } from "langchain/embeddings/openai";process.env["OPENAI_API_KEY"] = "ВАШ_КЛЮЧ_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("Синий кит - самое тяжелое животное в мире");const res2 = await embeddings.embedQuery("Джордж Оруэлл написал 1984 год");const res3 = await embeddings.embedQuery("Случайные вещи");const text_arr = ["Синий кит - самое тяжелое животное в мире", "Джордж Оруэлл написал 1984 год", "Случайные вещи"]const res_arr = [res1, res2, res3]const question = await embeddings.embedQuery("Какое животное самое тяжелое?");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())])

Этот код использует функцию cosinesim(A, B) для нахождения связи каждого ответа с вопросом. Находя список эмбеддингов, наиболее связанных с вопросом, используя функцию Array.prototype.max и находя максимальное значение в массиве индексов связности, которые были сгенерированы с помощью cosinesim, код может найти правильный ответ, найдя какой текст из text_arr принадлежит наиболее связанному ответу: text_arr[sims.indexOf(sims.max())].

Результат:

Синий кит - самое тяжелое животное в мире

Части

Модели LangChain не могут обрабатывать большие тексты и использовать их для создания ответов. Вот где на помощь приходят части и разбиение текста. Позвольте мне показать вам два простых метода разбиения ваших текстовых данных на части перед их подачей в LangChain.

Разделение частей по символу

Чтобы избежать резких разрывов в частях, вы можете разделить свой текст по абзацам, разделяя их при каждом появлении перевода строки:

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]);

Это один полезный способ разделения текста. Однако, вы можете использовать любой символ в качестве разделителя, а не только \n.

Рекурсивное разделение частей

Если вы хотите строго разделить свой текст на определенную длину символов, вы можете сделать это с помощью RecursiveCharacterTextSplitter:

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

В этом примере текст разделен на части каждые 100 символов с перекрытием в 15 символов.

Размер части и перекрытие

Рассмотрев эти примеры, вы наверняка задались вопросом, что означают параметры размера части и перекрытия, и какие у них последствия для производительности. Позвольте мне просто объяснить это двумя пунктами.

  • Размер части определяет количество символов в каждой части. Чем больше размер части, тем больше данных в части, тем больше времени займет обработка и получение результатов от LangChain; и наоборот.

  • Перекрытие частей – это то, что делает части связанными между собой, чтобы у них был общий контекст. Чем выше перекрытие частей, тем более избыточными будут ваши части, а чем ниже перекрытие, тем меньше контекста будет общего между ними. Как правило, хорошее перекрытие частей составляет от 10% до 20% от размера части, хотя идеальное перекрытие частей может отличаться в зависимости от типа текста и конкретной задачи.

Цепочки

Цепочки – это в основном несколько связанных вместе функциональностей LLM, предназначенных для выполнения более сложных задач, которые не могут быть выполнены простым LLM подходом ввод -> вывод. Давайте посмотрим на интересный пример:

import { ChatPromptTemplate } from "langchain/prompts";import { LLMChain } from "langchain/chains";import { ChatOpenAI } from "langchain/chat_models/openai";process.env["OPENAI_API_KEY"] = "YOUR_OPENAI_KEY"const wiki_text = `Alexander Stanislavovich 'Sasha' Bublik (Александр Станиславович Бублик; born 17 June 1997) is a Kazakhstani professional tennis player. He has been ranked as high as world No. 25 in singles by the Association of Tennis Professionals (ATP), which he achieved in July 2023, and is the current Kazakhstani No. 1 player...Alexander Stanislavovich Bublik was born on 17 June 1997 in Gatchina, Russia and began playing tennis at the age of four. He was coached by his father, Stanislav. On the junior tour, Bublik reached a career-high ranking of No. 19 and won eleven titles (six singles and five doubles) on the International Tennis Federation (ITF) junior circuit.[4][5]...`const chat = new ChatOpenAI({ temperature: 0 });const chatPrompt = ChatPromptTemplate.fromMessages([  [    "system",    "You are a helpful assistant that {action} the provided text",  ],  ["human", "{text}"],]);const chainB = new LLMChain({  prompt: chatPrompt,  llm: chat,});const resB = await chainB.call({  action: "lists all important numbers from",  text: wiki_text,});console.log({ resB });

Этот код использует переменную в своем приглашении и формулирует правильный фактический ответ (temperature: 0). В этом примере я попросил LLM перечислить все важные числа из короткой биографии моего любимого теннисиста.

Вот результат выполнения этого кода:

{  resB: {    text: 'Важные числа из предоставленного текста:\n' +      '\n' +      "- Дата рождения Александра Станиславовича 'Саши' Бублика: 17 июня 1997 года\n" +      "- Наивысший рейтинг одиночного разряда Бублика: мировой № 25\n" +      "- Наивысший рейтинг двойного разряда Бублика: мировой № 47\n" +      "- Количество одиночных титулов Бублика в карьере на ATP Tour: 3\n" +      "- Количество финалов по одиночному разряду Бублика на ATP Tour: 6\n" +      "- Рост Бублика: 1,96 м (6 футов 5 дюймов)\n" +      "- Количество подач асов Бублика в сезоне ATP Tour 2021 года: неизвестно\n" +      "- Рейтинг Бублика на юниорском туре: № 19\n" +      "- Количество титулов Бублика на юниорском туре: 11 (6 в одиночном и 5 в парном разряде)\n" +      "- Прежнее гражданство Бублика: Россия\n" +      "- Текущее гражданство Бублика: Казахстан\n" +      "- Роль Бублика в команде Levitov Chess Wizards: резервный игрок"  }}

Довольно классно, но это не показывает всю мощь связей. Давайте рассмотрим более практический пример:

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("Название альбома"),        artist: z.string().describe("Исполнитель(и), создавший альбом"),        length: z.number().describe("Продолжительность альбома в минутах"),        genre: z.string().optional().describe("Жанр альбома"),      })    )    .describe("Массив упомянутых в тексте музыкальных альбомов"),});const prompt = new ChatPromptTemplate({  promptMessages: [    SystemMessagePromptTemplate.fromTemplate(      "Перечислите все упоминаемые в тексте музыкальные альбомы."    ),    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: "Всегда следует использовать для правильного форматирования вывода",      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: "Мои любимые альбомы: 2001, To Pimp a Butterfly и Led Zeppelin IV",});console.log(JSON.stringify(response, null, 2));

Этот код считывает текстовый ввод, определяет все упомянутые музыкальные альбомы, определяет название каждого альбома, исполнителя, продолжительность и жанр, а затем помещает все данные в формат JSON. Вот результат при вводе «Мои любимые альбомы: 2001, To Pimp a Butterfly и Led Zeppelin IV»:

{  "albums": [    {      "name": "2001",      "artist": "Dr. Dre",      "length": 68,      "genre": "Хип-хоп"    },    {      "name": "To Pimp a Butterfly",      "artist": "Kendrick Lamar",      "length": 79,      "genre": "Хип-хоп"    },    {      "name": "Led Zeppelin IV",      "artist": "Led Zeppelin",      "length": 42,      "genre": "Рок"    }  ]}

Это всего лишь забавный пример, но эту технику можно использовать для структуризации неструктурированных текстовых данных для бесчисленного количества других приложений.

Превосходя OpenAI

Хотя я продолжаю использовать модели OpenAI в качестве примеров различных функциональностей LangChain, он не ограничивается моделями OpenAI. Вы можете использовать LangChain с множеством других LLM и искусственных интеллектуальных сервисов. Полный список LLM, который можно интегрировать с LangChain и JavaScript, можно найти в их документации.

Например, вы можете использовать Cohere с LangChain. После установки Cohere с помощью npm install cohere-ai, вы можете создать простой код вопрос-ответ с LangChain и Cohere, например такой:

import { Cohere } from "langchain/llms/cohere";const model = new Cohere({  maxTokens: 50,  apiKey: "YOUR_COHERE_KEY", // В Node.js по умолчанию process.env.COHERE_API_KEY});const res = await model.call(  "Придумайте название для нового альбома Nas");console.log({ res });

Вывод:

{  res: ' Вот несколько возможных названий для нового альбома Nas:\n' +    '\n' +    "- Королевская земля\n" +    "- Сын Божий: Продолжение\n" +    "- Ученик уличного\n" +    '- Иззи Фри\n' +    '- Nas и иллматический флоу\n' +    '\n' +    'Какие-либо'}

Заключение

В этом руководстве вы увидели разные аспекты и функциональности LangChain в JavaScript. Вы можете использовать LangChain в JavaScript для легкой разработки веб-приложений с искусственным интеллектом и для экспериментов с LLM. Обязательно обратитесь к документации LangChainJS, чтобы узнать больше подробностей о конкретных функциональностях.

Счастливого кодирования и экспериментирования с LangChain в JavaScript! Если вам понравилась эта статья, вам также может быть интересно прочитать о использовании LangChain с Python.

Поделитесь этой статьей


Leave a Reply

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