【译】JavaScript中LangChain的完整指南

JavaScript中LangChain的完整指南

原文地址:www.sitepoint.com/langchain-j...

在这个全面的指南中,我们将深入研究的基本组成部分LangChain并演示如何在JavaScript中利用它的功能。

LangChainJS是一个通用的JavaScript框架,它使开发人员和研究人员能够创建、实验和分析语言模型和代理。它为自然语言处理(NLP)爱好者提供了丰富的功能集,从构建自定义模型到有效地操作文本数据。作为一个JavaScript框架,它还允许开发人员轻松地将他们的AI应用程序集成到web应用程序中。

先决条件

为了遵循本文,创建一个新文件夹并安装LangChain npm包:

npm install -S langchain

创建新文件夹后,使用.mjs后缀(如test1.mjs)创建一个新的JS模块文件。

代理

在LangChain中,agent是一个能够理解和生成文本的实体。可以用特定的行为和数据源配置这些代理,并训练它们执行各种与语言相关的任务,使它们成为广泛应用程序的通用工具。

创建LangChain代理

可以将代理配置为使用"工具"来收集所需的数据并制定良好的响应。看看下面的例子。它使用Serp API(一种internet搜索API)在internet上搜索与问题或输入相关的信息,并使用这些信息做出响应。它还使用llm-math工具来执行数学运算-例如,转换单位或查找两个值之间的百分比变化:

javascript 复制代码
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);

在使用modelName: "gpt-3.5-turbo"和temperature: 0创建模型变量之后,我们创建将创建的模型与指定的工具(SerpAPI和Calculator)结合起来的执行器。在输入中,我要求LLM在互联网上搜索(使用SerpAPI),找出自2010年以来哪个艺术家发行了更多的专辑------Nas还是Boldy James------并显示百分比差异(使用Calculator)。

在这个例子中,我必须明确地告诉LLM"通过搜索互联网......",让它使用互联网获取数据,直到今天,而不是使用OpenAI的默认数据限制到2021年。

下面是输出结果:

erlang 复制代码
> 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.

模型

在LangChain中有三种类型的模型:llm、聊天模型和文本嵌入模型。让我们用一些例子来探索每种类型的模型。

语言模型

LangChain提供了一种在JavaScript中使用语言模型来基于文本输入生成文本输出的方法。它不像聊天模型那么复杂,它最适合用于简单的输入输出语言任务。下面是一个使用OpenAI的例子:

javascript 复制代码
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);

正如你所看到的,它使用gpt-3.5涡轮模型列出所有的红色浆果。在本例中,我将temperature设置为0,以使LLM实际准确。输出:

markdown 复制代码
1. Strawberries
2. Cranberries
3. Raspberries
4. Redcurrants
5. Red Gooseberries
6. Red Elderberries
7. Red Huckleberries
8. Red Mulberries

聊天模式

如果需要更复杂的答案和对话,则需要使用聊天模型。聊天模型在技术上与语言模型有什么不同?用LangChain文档的话说:

聊天模型是语言模型的一种变体。虽然聊天模型在底层使用语言模型,但它们使用的界面略有不同。他们没有使用"文本输入,文本输出"的API,而是使用"聊天消息"作为输入和输出的接口。

下面是一个简单的(无用但有趣的)JavaScript聊天模型脚本:

javascript 复制代码
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);

正如你所看到的,代码首先发送了一个系统信息,并告诉聊天机器人成为一个诗意的助手,总是以押韵的方式回答,然后它发送了一个人类信息,告诉聊天机器人告诉我谁是更好的网球运动员:德约科维奇、费德勒还是纳达尔。如果你运行这个聊天机器人模型,你会看到这样的东西:

vbnet 复制代码
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.'

很酷!

嵌入的

嵌入模型提供了一种将文本中的单词和数字转换为向量的方法,然后可以将其与其他单词或数字相关联。这可能听起来很抽象,所以让我们来看一个例子:

javascript 复制代码
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)

将返回一个float类型的长列表:

css 复制代码
[  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]

这就是嵌入的样子。这么多float就为了六个字!

然后,这种嵌入可以用来将输入文本与潜在的答案、相关文本、姓名等联系起来。

现在让我们看一个嵌入模型的用例......

现在有一个脚本将回答这个问题"最重的动物是什么?",并通过嵌入在提供的可能答案列表中找到正确答案:

ini 复制代码
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())])

这段代码使用cosinesim(A, B)函数来查找问题的每个答案的相关性。通过查找使用cosinesim生成的相关性索引数组中的最大值,通过使用array .prototype.max函数查找与问题最相关的嵌入列表,然后代码能够通过查找text_arr中的哪个文本属于最相关的答案来找到正确的答案:text_arr[sims.indexOf(sims.max())]。

输出:

csharp 复制代码
The Blue Whale is the heaviest animal in the world

LangChain模型不能处理大文本并使用它们做出响应。这就是块和文本分割的用武之地。让我向您展示两个简单的方法,在将文本数据输入到LangChain之前将其分割成块。

按字符分割数据块

为了避免在块中突然中断,您可以通过在每次出现换行符时拆分段落来拆分文本:

javascript 复制代码
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:

ini 复制代码
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输入->输出方式完成。让我们来看一个很酷的例子:

vbnet 复制代码
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 });

在这个例子中,我要求LLM从我最喜欢的网球运动员的Wiki简介中列出所有重要的数字。

下面是这段代码的输出:

swift 复制代码
{
  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"
  }
}

很酷,但这并没有真正展示链的全部力量。让我们来看一个更实际的例子:

php 复制代码
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));

这段代码读取输入文本,识别所有提到的音乐专辑,识别每个专辑的名称、艺术家、长度和类型,最后将所有数据转换为JSON格式。下面是输入"我最喜欢的专辑是:2001,To Pimp a Butterfly和Led Zeppelin IV"的输出:

json 复制代码
{
  "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"
    }
  ]
}

这只是一个有趣的示例,但该技术可用于为无数其他应用程序构建非结构化文本数据。

超越OpenAI

尽管我一直使用OpenAI模型作为LangChain不同功能的示例,但它并不局限于OpenAI模型。您可以将LangChain与许多其他llm和AI服务一起使用。你可以在他们的文档中找到LangChain和JavaScript集成llm的完整列表。

例如,您可以使用Cohere和LangChain。安装完Cohere后,使用npm install coherence -ai,你可以使用LangChain和Cohere编写一个简单的问题->答案代码,如下所示:

javascript 复制代码
import { Cohere } from "langchain/llms/cohere";

const model = new Cohere({
  maxTokens: 50,
  apiKey: "YOUR_COHERE_KEY", // In Node.js defaults to process.env.COHERE_API_KEY
});
const res = await model.call(
  "Come up with a name for a new Nas album"
);
console.log({ res });

输出:

vbnet 复制代码
{
  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'
}

结论

在本指南中,您已经看到了JavaScript中LangChain的不同方面和功能。您可以在JavaScript中使用LangChain轻松开发ai驱动的web应用程序并尝试llm。请务必参考LangChainJS文档,了解有关特定功能的更多细节。

愉快地在JavaScript中编写和试验LangChain !如果您喜欢这篇文章,那么您可能还想阅读有关在Python中使用LangChain的内容。

相关推荐
joan_8516 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特39 分钟前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
一个处女座的程序猿O(∩_∩)O3 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
燃先生._.9 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖10 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
black^sugar11 小时前
纯前端实现更新检测
开发语言·前端·javascript
2401_8576009512 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_8576009512 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js