JavaScript langchain 指南

    在这份综合指南中,我们将深入探讨LangChain的基本组件,并演示如何在JavaScript中利用其强大功能。

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

先决条件

要继续阅读本文,请创建一个新文件夹并安装 LangChain npm 包:

npm install -S langchain

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

代理(Agents)

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

        使用 和 创建变量后,我们创建将创建的变量与指定的工具(SerpAPI 和计算器)相结合。在输入中,我要求 LLM 搜索互联网(使用 SerpAPI)并找到自 2010 年以来哪位艺术家放弃了更多的专辑——Nas 或 Boldy James——并显示百分比差异(使用计算器)。modelmodelName: "gpt-3.5-turbo"temperature: 0executormodel

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

输出如下所示:

> 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.

模型(Models)

LangChain中有三种类型的模型:LLM、聊天模型和文本嵌入模型。让我们通过一些示例来探索每种类型的模型。

语言模型(Language model)

LangChain提供了一种在JavaScript中使用语言模型的方法,以基于文本输入生成文本输出。它不像聊天模型那么复杂,最适合用于简单的输入-输出语言任务。下面是一个使用 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);

       如您所见,它使用模型列出所有红色浆果。在此示例中,我将温度设置为 0,以使 LLM 实际上准确。输出:gpt-3.5-turbo 

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

聊天模型(Chat model)

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

聊天模型是语言模型的变体。虽然聊天模型在后台使用语言模型,但它们使用的界面略有不同。他们没有使用“文本输入,文本输出”API,而是使用“聊天消息”作为输入和输出的界面。

下面是一个简单(非常无用但很有趣)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);

正如你所看到的,代码首先发送一条系统消息,告诉聊天机器人是一个诗意的助手,总是用押韵回答,然后它发送一条人类消息,告诉聊天机器人告诉我谁是更好的网球运动员:德约科维奇、费德勒或纳达尔。如果你运行这个聊天机器人模型,你会看到如下内容:

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.'

很酷!

嵌入(Embeddings)

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

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)

这将返回一长串浮点数

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

这就是嵌入的样子。所有这些浮动都只有六个字!

然后,此嵌入可用于将输入文本与潜在答案、相关文本、名称等相关联。

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

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

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)Array.prototype.maxcosinesimtext_arrtext_arr[sims.indexOf(sims.max())]

输出:

The Blue Whale is the heaviest animal in the world

块(Chunks)

LangChain模型无法处理大型文本并使用它们进行响应。这就是文本拆分的用武之地。让我向你展示两种简单的方法,在将文本数据输入LangChain之前,将文本数据拆分为块。

按字符拆分块(Splitting chunks by character)

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

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

递归拆分块(Recursively splitting chunks)

如果要严格拆分文本以一定长度的字符,可以使用:RecursiveCharacterTextSplitter

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

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

在此示例中,文本每 100 个字符拆分一次,块重叠为 15 个字符。

块大小和重叠(Chunk size and overlap)

通过查看这些示例,您可能已经开始想知道块大小和重叠参数的确切含义,以及它们对性能的影响。好吧,让我简单地用两点来解释它。

  • 块大小决定了每个块中的字符数。块大小越大,块中的数据越多,LangChain处理它并生成输出所需的时间就越长,反之亦然。

  • 块重叠是在块之间共享信息,以便它们共享一些上下文。块重叠度越高,块的冗余性就越大;块重叠越低,块之间共享的上下文就越少。通常,良好的块重叠在块大小的 10% 到 20% 之间,尽管理想的块重叠因不同的文本类型和用例而异。

链(Chains)

基本上是多个 LLM 功能链接在一起,以执行更复杂的任务,否则这些任务无法通过简单的 LLM 方式完成。让我们看一个很酷的例子:input-->output

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

此代码将变量放入其提示符中,并制定一个事实正确的答案(温度:0)。 在这个例子中,我要求LLM列出我最喜欢的网球运动员的简短Wiki简历中的所有重要数字。

下面是此代码的输出:

{
  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"
  }
}

很酷,但这并不能真正显示链条的全部力量。让我们看一个更实际的例子:

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”:

{
  "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"
    }
  ]
}

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

结论

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

祝您在 JavaScript 中使用 LangChain 进行编码和实验愉快!

  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值