在本综合指南中,我们将深入探讨LangChain的基本组件,并演示如何在 JavaScript 中利用其强大功能。
LangChainJS是一个多功能的 JavaScript 框架,可帮助开发人员和研究人员创建、试验和分析语言模型和代理。它为自然语言处理 (NLP) 爱好者提供了丰富的功能,从构建自定义模型到高效处理文本数据。作为一个 JavaScript 框架,它还允许开发人员轻松地将其 AI 应用程序集成到 Web 应用程序中。
先决条件 {#prerequisites}
为了遵循本文,请创建一个新文件夹并安装 LangChain npm 包:
npm install -S langchain
.mjs
新建文件夹后,以后缀名(如test1.mjs
)新建一个JS模块文件。
代理 {#agents}
在 LangChain 中,代理是一个能够理解和生成文本的实体。这些代理可以配置特定的行为和数据源,并经过训练以执行各种与语言相关的任务,使其成为适用于各种应用的多功能工具。
创建 LangChain 代理 {#creatingalangchainagent}
可以配置代理使用"工具"来收集所需的数据并制定良好的响应。请看下面的示例。它使用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 和计算器)组合起来的 。在输入中,我要求 LLM 搜索互联网(使用 SerpAPI)并找出自 2010 年以来哪位艺术家发行的专辑更多(Nas 还是 Boldy James),并显示百分比差异(使用计算器)。
在这个例子中,我必须明确地告诉 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、聊天模型和文本嵌入模型。让我们通过一些示例来探索每种类型的模型。
语言模型 {#languagemodel}
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);
如您所见,它使用gpt-3.5-turbo
模型列出所有红色浆果。在此示例中,我将温度设置为 0,以使 LLM 事实准确。输出:
1. Strawberries
2. Cranberries
3. Raspberries
4. Redcurrants
5. Red Gooseberries
6. Red Elderberries
7. Red Huckleberries
8. Red Mulberries
聊天模型 {#chatmodel}
如果您想要更复杂的答案和对话,则需要使用聊天模型。聊天模型在技术上与语言模型有何不同?好吧,用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())])
块 {#chunks}
LangChain 模型无法处理大文本并使用它们做出响应。这就是分块和文本拆分的用武之地。让我向您展示两种简单的方法,在将文本数据输入 LangChain 之前将其拆分成分块。
按字符分割块 {#splittingchunksbycharacter}
为了避免文本块突然中断,您可以按段落拆分文本,方法是在每次出现换行符时将其拆分:
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
。
递归分割块 {#recursivelysplittingchunks}
如果您想要严格按照一定长度的字符分割文本,则可以使用以下方法RecursiveCharacterTextSplitter
:
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 100,
chunkOverlap: 15,
});
const output = await splitter.createDocuments([your_text]);
在这个例子中,文本每 100 个字符被分割一次,其中块重叠 15 个字符。
块大小和重叠 {#chunksizeandoverlap}
通过查看这些示例,您可能开始想知道块大小和重叠参数到底是什么意思,以及它们对性能有何影响。好吧,让我用两点来简单解释一下。
-
块大小决定每个块中的字符数量。块大小越大,块中的数据越多,LangChain 处理并产生输出所需的时间就越长,反之亦然。
-
块重叠是指块之间共享信息,从而共享一些上下文。块重叠越高,块的冗余度就越高;块重叠越低,块之间共享的上下文就越少。一般来说,良好的块重叠介于块大小的 10% 到 20% 之间,尽管理想的块重叠因不同的文本类型和用例而异。
链条 {#chains}
链基本上是多个 LLM 功能链接在一起以执行更复杂的任务,而这些任务无法通过简单的 LLMinput-->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)。在这个例子中,我要求法学硕士从我最喜欢的网球运动员的简短维基传记中列出所有重要的数字。
以下是该代码的输出:
{
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"
}
]
}
这只是一个有趣的例子,但这种技术可以用于为无数其他应用程序构建非结构化文本数据。
超越OpenAI {#goingbeyondopenai}
尽管我一直使用 OpenAI 模型作为 LangChain 不同功能的示例,但它并不局限于 OpenAI 模型。您可以将 LangChain 与众多其他 LLM 和 AI 服务一起使用。您可以在其文档中找到 LangChain 和 JavaScript 可集成 LLM 的完整列表。
例如,你可以将Cohere与 LangChain 一起使用。安装 Cohere 后,使用npm install cohere-ai
,你可以question-->answer
使用 LangChain 和 Cohere 编写一个简单的代码,如下所示:
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 });
输出:
{
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}
在本指南中,您了解了 JavaScript 中 LangChain 的不同方面和功能。您可以使用 JavaScript 中的 LangChain 轻松开发 AI 驱动的 Web 应用并试验 LLM。