Fermer

octobre 31, 2023

Un guide complet de LangChain en JavaScript —

Un guide complet de LangChain en JavaScript —


Dans ce guide complet, nous approfondirons les composants essentiels de LangChaîne et démontrer comment exploiter sa puissance en JavaScript.

LangChainJS est un framework JavaScript polyvalent qui permet aux développeurs et aux chercheurs de créer, d’expérimenter et d’analyser des modèles et des agents de langage. Il offre un riche ensemble de fonctionnalités pour les passionnés de traitement du langage naturel (NLP), de la création de modèles personnalisés à la manipulation efficace de données textuelles. En tant que framework JavaScript, il permet également aux développeurs d’intégrer facilement leurs applications d’IA dans des applications Web.

Table des matières

Conditions préalables

Pour suivre cet article, créez un nouveau dossier et installez le package LangChain npm :

npm install -S langchain

Après avoir créé un nouveau dossier, créez un nouveau fichier de module JS en utilisant le .mjs suffixe (tel que test1.mjs).

Agents

Dans LangChain, un agent est une entité capable de comprendre et de générer du texte. Ces agents peuvent être configurés avec des comportements et des sources de données spécifiques et formés pour effectuer diverses tâches liées au langage, ce qui en fait des outils polyvalents pour un large éventail d’applications.

Créer un agent LangChain

Les agents peuvent être configurés pour utiliser des « outils » pour collecter les données dont ils ont besoin et formuler une bonne réponse. Jetez un œil à l’exemple ci-dessous. Il utilise API Serp (une API de recherche sur Internet) pour rechercher sur Internet des informations pertinentes pour la question ou la saisie, et les utiliser pour répondre. Il utilise également le llm-math outil pour effectuer des opérations mathématiques — par exemple, pour convertir des unités ou trouver une variation en pourcentage entre deux valeurs :

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

Après avoir créé le model variable utilisant modelName: "gpt-3.5-turbo" et temperature: 0nous créons le executor qui combine la création model avec les outils spécifiés (SerpAPI et Calculatrice). Dans la contribution, j’ai demandé au LLM de rechercher sur Internet (à l’aide de SerpAPI) et de trouver quel artiste a sorti le plus d’albums depuis 2010 – Nas ou Boldy James – et d’afficher la différence en pourcentage (à l’aide de la calculatrice).

Dans cet exemple, j’ai dû dire explicitement au LLM « En recherchant sur Internet… » pour qu’il obtienne des données jusqu’à nos jours en utilisant Internet au lieu d’utiliser les données par défaut d’OpenAI limitées à 2021.

Voici à quoi ressemble le résultat :

> node test1.mjs
Boldy James has released 4 albums since 2010. Nas has released 17 studio albums since 2010. 

Therefore, Nas has released more albums than Boldy James. The difference in the number of albums is 13.

To calculate the difference in percent, we can use the formula: (Difference / Total) * 100.

In this case, the difference is 13 and the total is 17.

The difference in percent is: (13 / 17) * 100 = 76.47%.

So, Nas has released 76.47% more albums than Boldy James since 2010.

Des modèles

Il existe trois types de des modèles dans LangChain : LLM, modèles de discussion et modèles d’intégration de texte. Explorons chaque type de modèle avec quelques exemples.

Modèle de langage

LangChain fournit un moyen d’utiliser des modèles de langage en JavaScript pour produire une sortie texte basée sur une entrée de texte. Ce n’est pas aussi complexe qu’un modèle de chat, et il est mieux utilisé avec des tâches simples de langage d’entrée-sortie. Voici un exemple utilisant OpenAI :

import { OpenAI } from "langchain/llms/openai";

const llm = new OpenAI({
  openAIApiKey: "YOUR_OPENAI_KEY",
  model: "gpt-3.5-turbo",
  temperature: 0
});

const res = await llm.call("List all red berries");

console.log(res);

Comme vous pouvez le constater, il utilise le gpt-3.5-turbo modèle pour lister tous les fruits rouges. Dans cet exemple, j’ai réglé la température sur 0 pour rendre le LLM factuellement précis. Sortir:

1. Strawberries
2. Cranberries
3. Raspberries
4. Redcurrants
5. Red Gooseberries
6. Red Elderberries
7. Red Huckleberries
8. Red Mulberries

Modèle de discussion

Si vous souhaitez des réponses et des conversations plus sophistiquées, vous devez utiliser des modèles de chat. En quoi les modèles de chat sont-ils techniquement différents des modèles de langage ? Eh bien, selon les mots du Documentation LangChain:

Les modèles de chat sont une variante des modèles de langage. Bien que les modèles de chat utilisent des modèles de langage sous le capot, l’interface qu’ils utilisent est un peu différente. Plutôt que d’utiliser une API « texte entrant, texte sortant », ils utilisent une interface où les « messages de discussion » sont les entrées et les sorties.

Voici un script de modèle de chat JavaScript simple (assez inutile mais amusant) :

import { ChatOpenAI } from "langchain/chat_models/openai";
import { PromptTemplate } from "langchain/prompts";

const chat = new ChatOpenAI({
  openAIApiKey: "YOUR_OPENAI_KEY",
  model: "gpt-3.5-turbo",
  temperature: 0
});
const prompt = PromptTemplate.fromTemplate(`You are a poetic assistant that always answers in rhymes: {question}`);
const runnable = prompt.pipe(chat);
const response = await runnable.invoke({ question: "Who is better, Djokovic, Federer or Nadal?" });
console.log(response);

Comme vous pouvez le voir, le code envoie d’abord un message système et dit au chatbot d’être un assistant poétique qui répond toujours par des rimes, puis il envoie un message humain demandant au chatbot de me dire qui est le meilleur joueur de tennis : Djokovic, Federer ou Nadal. Si vous exécutez ce modèle de chatbot, vous verrez quelque chose comme ceci :

AIMessage.content:
'In the realm of tennis, they all shine bright,\n' +
'Djokovic, Federer, and Nadal, a glorious sight.\n' +
'Each with their unique style and skill,\n' +
'Choosing the best is a difficult thrill.\n' +
'\n' +
'Djokovic, the Serb, a master of precision,\n' +
'With agility and focus, he plays with decision.\n' +
'His powerful strokes and relentless drive,\n' +
"Make him a force that's hard to survive.\n" +
'\n' +
'Federer, the Swiss maestro, a true artist,\n' +
'Graceful and elegant, his game is the smartest.\n' +
'His smooth technique and magical touch,\n' +
'Leave spectators in awe, oh so much.\n' +
'\n' +
'Nadal, the Spaniard, a warrior on clay,\n' +
'His fierce determination keeps opponents at bay.\n' +
'With his relentless power and never-ending fight,\n' +
'He conquers the court, with all his might.\n' +
'\n' +
"So, who is better? It's a question of taste,\n" +
"Each player's greatness cannot be erased.\n" +
"In the end, it's the love for the game we share,\n" +
'That makes them all champions, beyond compare.'

Plutôt cool!

Intégrations

Les modèles d’intégration fournissent un moyen de transformer les mots et les nombres d’un texte en vecteurs, qui peuvent ensuite être associés à d’autres mots ou nombres. Cela peut paraître abstrait, alors regardons un exemple :

import { OpenAIEmbeddings } from "langchain/embeddings/openai";

process.env["OPENAI_API_KEY"] = "YOUR_OPENAI_KEY"

const embeddings = new OpenAIEmbeddings();
const res = await embeddings.embedQuery("Who created the world wide web?");
console.log(res)

Cela renverra une longue liste de flottants :

[
  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 more items
]

Voici à quoi ressemble une intégration. Tous ces flotteurs pour seulement six mots !

Cette intégration peut ensuite être utilisée pour associer le texte saisi à des réponses potentielles, des textes associés, des noms et bien plus encore.

Examinons maintenant un cas d’utilisation d’intégration de modèles…

Voici maintenant un script qui répondra à la question « Quel est l’animal le plus lourd ? » et trouvez la bonne réponse dans la liste fournie des réponses possibles en utilisant les intégrations :

import { OpenAIEmbeddings } from "langchain/embeddings/openai";

process.env["OPENAI_API_KEY"] = "YOUR_OPENAI_KEY"

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("The Blue Whale is the heaviest animal in the world");
const res2 = await embeddings.embedQuery("George Orwell wrote 1984");
const res3 = await embeddings.embedQuery("Random stuff");

const text_arr = ["The Blue Whale is the heaviest animal in the world", "George Orwell wrote 1984", "Random stuff"]
const res_arr = [res1, res2, res3]

const question = await embeddings.embedQuery("What is the heaviest animal?");

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

Ce code utilise le cosinesim(A, B) fonction pour trouver la relation de chaque réponse à la question. En trouvant la liste des plongements les plus liés à la question à l’aide du Array.prototype.max fonction en recherchant la valeur maximale dans le tableau d’index de relation générés à l’aide de cosinesimle code est alors capable de trouver la bonne réponse en trouvant de quel texte provient text_arr appartient à la réponse la plus connexe : text_arr[sims.indexOf(sims.max())].

Sortir:

The Blue Whale is the heaviest animal in the world

Morceaux

Les modèles LangChain ne peuvent pas gérer des textes volumineux et les utiliser pour formuler des réponses. C’est ici que morceaux et fractionnement du texte entrez. Laissez-moi vous montrer deux méthodes simples pour diviser vos données texte en morceaux avant de les introduire dans LangChain.

Diviser les morceaux par caractère

Pour éviter les ruptures brusques de morceaux, vous pouvez diviser vos textes par paragraphe en les divisant à chaque occurrence d’une nouvelle ligne :

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

C’est un moyen utile de diviser un texte. Cependant, vous pouvez utiliser n’importe quel caractère comme séparateur de morceaux, pas seulement \n.

Diviser des morceaux de manière récursive

Si vous souhaitez diviser strictement votre texte selon une certaine longueur de caractères, vous pouvez le faire en utilisant RecursiveCharacterTextSplitter:

import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 100,
  chunkOverlap: 15,
});

const output = await splitter.createDocuments([your_text]);

Dans cet exemple, le texte est divisé tous les 100 caractères, avec un chevauchement de 15 caractères.

Taille des morceaux et chevauchement

En examinant ces exemples, vous avez probablement commencé à vous demander exactement ce que signifient les paramètres de taille de bloc et de chevauchement, et quelles implications ils ont sur les performances. Eh bien, laissez-moi l’expliquer simplement en deux points.

  • La taille du morceau détermine le nombre de caractères qui seront dans chaque morceau. Plus la taille du bloc est grande, plus le bloc contient de données, plus il faudra de temps à LangChain pour les traiter et produire une sortie, et vice versa.

  • Le chevauchement des morceaux est ce qui partage les informations entre les morceaux afin qu’ils partagent un certain contexte. Plus le chevauchement des morceaux est élevé, plus vos morceaux seront redondants ; plus le chevauchement des morceaux est faible, moins le contexte sera partagé entre les morceaux. Généralement, un bon chevauchement de fragments se situe entre 10 % et 20 % de la taille du fragment, bien que le chevauchement idéal varie selon les types de texte et les cas d’utilisation.

Chaînes

Chaînes sont essentiellement plusieurs fonctionnalités LLM liées entre elles pour effectuer des tâches plus complexes qui ne pourraient pas être effectuées autrement avec un simple LLM input-->output mode. Regardons un exemple sympa :

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

Ce code prend une variable dans son invite et formule une réponse factuellement correcte (température : 0). Dans cet exemple, j’ai demandé au LLM de répertorier tous les chiffres importants d’une courte biographie Wiki de mon joueur de tennis préféré.

Voici le résultat de ce code :

{
  resB: {
    text: 'Important numbers from the provided text:\n' +
      '\n' +
      "- Alexander Stanislavovich 'Sasha' Bublik's date of birth: 17 June 1997\n" +
      "- Bublik's highest singles ranking: world No. 25\n" +
      "- Bublik's highest doubles ranking: world No. 47\n" +
      "- Bublik's career ATP Tour singles titles: 3\n" +
      "- Bublik's career ATP Tour singles runner-up finishes: 6\n" +
      "- Bublik's height: 1.96 m (6 ft 5 in)\n" +
      "- Bublik's number of aces served in the 2021 ATP Tour season: unknown\n" +
      "- Bublik's junior tour ranking: No. 19\n" +
      "- Bublik's junior tour titles: 11 (6 singles and 5 doubles)\n" +
      "- Bublik's previous citizenship: Russia\n" +
      "- Bublik's current citizenship: Kazakhstan\n" +
      "- Bublik's role in the Levitov Chess Wizards team: reserve member"
  }
}

Plutôt cool, mais cela ne montre pas vraiment toute la puissance des chaînes. Jetons un coup d’œil à un exemple plus pratique :

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("The name of the album"),
        artist: z.string().describe("The artist(s) that made the album"),
        length: z.number().describe("The length of the album in minutes"),
        genre: z.string().optional().describe("The genre of the album"),
      })
    )
    .describe("An array of music albums mentioned in the text"),
});
const prompt = new ChatPromptTemplate({
  promptMessages: [
    SystemMessagePromptTemplate.fromTemplate(
      "List all music albums mentioned in the following text."
    ),
    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: "Should always be used to properly format output",
      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: "My favorite albums are: 2001, To Pimp a Butterfly and Led Zeppelin IV",
});

console.log(JSON.stringify(response, null, 2));

Ce code lit un texte d’entrée, identifie tous les albums de musique mentionnés, identifie le nom, l’artiste, la durée et le genre de chaque album, et enfin met toutes les données au format JSON. Voici le résultat compte tenu de l’entrée « Mes albums préférés sont : 2001, To Pimp a Butterfly et 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"
    }
  ]
}

Ceci n’est qu’un exemple amusant, mais cette technique peut être utilisée pour structurer des données textuelles non structurées pour d’innombrables autres applications.

Aller au-delà d’OpenAI

Même si je continue d’utiliser les modèles OpenAI comme exemples des différentes fonctionnalités de LangChain, cela ne se limite pas aux modèles OpenAI. Vous pouvez utiliser LangChain avec une multitude d’autres LLM et services d’IA. Vous pouvez trouver la liste complète des LLM intégrables LangChain et JavaScript dans leur documentation.

Par exemple, vous pouvez utiliser Adhérer avec LangChain. Après avoir installé Cohere, en utilisant npm install cohere-aivous pouvez faire un simple question-->answer codez en utilisant LangChain et Cohere comme ceci :

import { Cohere } from "langchain/llms/cohere";

const model = new Cohere({
  maxTokens: 50,
  apiKey: "YOUR_COHERE_KEY", 
});
const res = await model.call(
  "Come up with a name for a new Nas album"
);
console.log({ res });

Sortir:

{
  res: ' Here are a few possible names for a new Nas album:\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' +
    'Do any'
}

Conclusion

Dans ce guide, vous avez vu les différents aspects et fonctionnalités de LangChain en JavaScript. Vous pouvez utiliser LangChain en JavaScript pour développer facilement des applications Web basées sur l’IA et expérimenter les LLM. Assurez-vous de vous référer au Documentation LangChainJS pour plus de détails sur des fonctionnalités spécifiques.

Bon codage et expérimentation avec LangChain en JavaScript ! Si vous avez apprécié cet article, vous aimeriez peut-être aussi en savoir plus sur utiliser LangChain avec Python.






Source link