作者 | Matt Nikonorov
译者 | 许学文
策划 | 丁晓昀
JS 版的 LangChain,是一个功能丰富的 JavaScript 框架。不管你是开发者还是研究人员都可以利用该框架通过创建语言分析模型和 Agents 来开展各项实验。该框架还提供了十分丰富的功能设置,基于这些功能设置,NLP 爱好者可以通过构建自定义模型来提高文本数据的处理效率。与此同时,作为一个 JS 框架,开发人员可以轻松的将他们的 AI 应用集成到自己的 Web 应用中。
环境准备
安装下面的步骤,我们创建一个新目录并且安装 LangChain 的 npm 包:
1. 执行如下命令,安装 LangChain 的 npm 包
代码语言:javascript复制npm install -S langchain
2. 在目录下面创建一个以.mjs 为后缀的文件(例如:test1.mjs)
Agents(智体)
在 LangChain 中,一个 Agent 代表的是一个具备理解和生成文本能力的实例。通过给这些 Agent 设置特定行为和数据源,就可以训练他们执行各种与语言相关的任务,从而使他们具备为更多的应用提供服务的能力。
创建 LangChain 的 Agent
利用 LangChain 框架创建的 Agent 在数据获取和响应优化上都支持“工具”的配置。请看下面的示例代码。该例中,Agent 体使用 Serp API(一个网络搜索 API)在互联网上搜索与输入内容相关的信息,然后根据搜索得到的内容完成响应数据的生成,与此同时,它还使用 llm-math 工具来执行诸如转换单位、百分比对比等数学运算任务。
代码语言:javascript复制// langchain 智能体引入
import { initializeAgentExecutorWithOptions } from "langchain/agents";
// 引入语言模型:OpenAi
import { ChatOpenAI } from "langchain/chat_models/openai";
// 引入网络搜索工具
import { SerpAPI } from "langchain/tools";
// 引入计算函数 工具
import { Calculator } from "langchain/tools/calculator";
// OpenAI 的 API 访问的密钥
process.env["OPENAI_API_KEY"] = "YOUR_OPENAI_KEY"
// SerpAPI 访问密钥
process.env["SERPAPI_API_KEY"] = "YOUR_SERPAPI_KEY"
// 创建工具链
const tools = [new Calculator(), new SerpAPI()];
// 模型配置,这里用的是 OpenAI gpt-3.5-turbo
const model = new ChatOpenAI({ modelName: "gpt-3.5-turbo", temperature: 0 });
// 智能体初始化
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "openai-functions",
verbose: false,
});
// 执行,这里给出的问题是:"通过搜索互联网,找出自 2010 年以来 Boldy James 发行了多少张专辑,以及 Nas 自 2010 年以来发行了多少张专辑?找出谁发行了更多的专辑,并显示百分比的差异。"
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);
上述代码,在模型创建之后,通过 initializeAgentExecutorWithOptions 函数将模型和工具(SerpAPI 和 Calculator)进行合并,生成了一个 executor(执行者)。在输入端,我们要求 LLM(大语言模型) 通过搜索 Internet(使用 SerpAPI),找出自 2010 年以来,Nas 和 Boldy James 这两位艺术家中谁发行了更多专辑,并技术差值百分比(使用计算器)。
在该例子中,我通过明确地告诉 LLM“通过搜索互联网…”,以使它通过互联网获取最新数据,而不使用 OpenAI 的的默认数据(该数据截止 2021 年),从而得出正确答案。
译者注:OpenAI 于 2023 年 11 月 2 日发布会上,表示其模型数据已经更新到了 2023 年 4 月。
下面是上面代码的输出:
代码语言:javascript复制> node test1.mjs 从 2010 年至今,Boldy James 发了 4 张专辑,Nas 发了 17 张因此,Nas 比 Boldy James 发行的专辑要多,两者发行专辑的差值是 13 我们将使用如下公式:(差值 / 总值)*100,来计算差值百分比在这里,差值是 13,总值是 17 因此差值百分比是:(13/17)*100 = 76.47% 所以,从 2010 年至今,Nas 发布的专辑比 Boldy James 多了 76。47%
模型(Models)
LangChain 中支持三种类型的模型使用方式:
- LLM(大语言模型)
- Chat Model(对话模型)
- Embeddings(Embeddings 技术是一种将高纬数据转为低维数据的技术)
下面通过示例,我们一起来了解这三种模型的使用。
语言模型
LangChain 为 JavaScript 提供了使用语言模型能力,通过该能力 JS 可以根据文本输出生成文本输出。它不像聊天模型那么复杂,最适合处理简单的输入 - 输出的语言任务。下面是一个基于 OpenAI 模型的代码示例:
代码语言:javascript复制import { OpenAI } from "langchain/llms/openai";
const llm = new OpenAI({
openAIApiKey: "你自己的 OpenAI 的密钥",
model: "gpt-3.5-turbo",
temperature: 0
});
const res = await llm.call("List all red berries");
console.log(res);
如你所见,该例是要求 OpenAI 的 gpt-3.5-turbo 模型罗列所有的红色浆果。其中,我将 temperature 设为 0,其目的是为了确保 LLM 输出结果的准确性。下面是输出的结果:
代码语言:javascript复制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";
// 创建对话,配置密钥、模型版本、和 temperature
const chat = new ChatOpenAI({
openAIApiKey: "YOUR_OPENAI_KEY",
model: "gpt-3.5-turbo",
temperature: 0
});
// 通过提示词模版,创建提示词
const prompt = PromptTemplate.fromTemplate(`你现在扮演一个诗人的角色,在回答时请保持语言的韵律: {question}`);
const runnable = prompt.pipe(chat);
// 对话执行
const response = await runnable.invoke({ question: "Djokovic, Federer 和 Nadal,三人中谁是最好的网球运动员?" });
console.log(response);
如你所见,上面的代码首先发送了一条系统消息给对话机器人,告诉它,当前扮演的是一个诗人角色,且在回答的时候要始终使用押韵的方式。然后再向对话机器人发送一条用户消息,让它给出 Djokovic、Federer 和 Nadal 这三人中,谁是最好的网球运动员。如果你运行这个脚本,将会看到如下内容:
代码语言:javascript复制// AI 消息体
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
Embeddings 支持将文本数据转换为向量数据,以便于和其他相关的内容进行关联。这可能听起来有点抽象,让我们直接来看一个例子:
代码语言:javascript复制import { OpenAIEmbeddings } from "langchain/embeddings/openai";
process.env["OPENAI_API_KEY"] = "YOUR_OPENAI_KEY"
const embeddings = new OpenAIEmbeddings();
const res = await embeddings.embedQuery("谁是万维网之父?");
console.log(res)
这里是数据返回,是一大串的浮点数据:
代码语言:javascript复制[
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
]
这就是 Embeddings 的形态。仅仅是为了六个单词,就用了那么多浮点数!利用 Embeddings 技术,可以将输入文本与潜在答案、相关文本、名称等进行关联。
下面让我们来看一个 Embeddings 模型的一个使用案例。
在下面的脚本中,我们向模型提问:“世界上最重的动物是什么?”。然后我们借助 Embeddings 技术让模型能从我们提供的参考答案中找出最佳答案。
代码语言:javascript复制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;
}
// 嵌入 1:蓝鲸是世界上最重的动物
const res1 = await embeddings.embedQuery("The Blue Whale is the heaviest animal in the world");
// 嵌入 2:乔治·奥威尔写了《一九八四》这本书
const res2 = await embeddings.embedQuery("George Orwell wrote 1984");
// 嵌入 3:随机内容
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"]
// 利用 embeddings 转换之后的数据数组
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 ){
// 这里利用 cosinesim 函数,计算问题和每个答案的相识度
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),其次利用 embeddings 技术将每个答案转换为了向量数据,接着使用 cosinesim 函数计算出了每个答案和输入问题的相识度值,最高拿到相识度最高的答案,完成输出。下面是输出的结果:
代码语言:javascript复制The Blue Whale is the heaviest animal in the world
// 蓝鲸是世界上最重的动物
Chunks(数据块)
由于 LangChain 模型在生产响应的时候不支持大文本的输入。所以需要用到诸如文本分割等数据分块的技术将大文本数据分割成多个 Chunk。下面我向你演示 LangChain 中两种简单的文本数据分割方法,以实现大文本输入。
方法一、CharacterTextSplitter
为了避免分割之后,Chunk 中内容中断,可以使用换行符来进行文本拆分,该方法是在每次出现换行符时执行分割,可以通过 CharacterTextSplitter 来实现,示例代码如下:
代码语言:javascript复制import { Document } from "langchain/document";import { CharacterTextSplitter } from "langchain/text_splitter";// 创建一个分割器,使用换行符进行分割,每个区块的大小是 7,区块的重叠度是 3const splitter = new CharacterTextSplitter({ separator: "n", chunkSize: 7, chunkOverlap: 3,});const output = await splitter.createDocuments([your_text]);
这是拆分文本的一种有用的方法,同时,你可以使用任何字符作为 Chunk 的分隔符,而不仅仅是换行符(n)
方法二、RecursiveCharacterTextSplitter
如果要严格按一定长度的字符拆分文本,可以使用 RecursiveCharacterTextSplitter 来实现,示例代码如下:
代码语言:javascript复制import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";const splitter = new RecursiveCharacterTextSplitter({ // chunk 的大小 chunkSize: 100, // chunk 的重叠度 chunkOverlap: 15,});const output = await splitter.createDocuments([your_text]);
在此示例中,会将文本按照每 100 个字符进行一次拆分,每个 Chunk 的重叠度为 15 个字符。
Chunk 的大小和重叠度
通过上面的示例,想必你已经迫不及待的想知道 Chunk 的大小和重叠度这两个参数确切的含义以及它们对性能的影响了吧。下面我简单从两方面解释下:
- chunkSize 决定了每个 Chunk 中的字符数量。chunkSize 的值越大,那么 Chunk 中的字符数就越多,LangChain 处理该 Chunk 和产生对应输出所需的时间就越长,反之亦然。
- chunkOverlap 是用于设置了每个 Chunk 之间共享上下文的大小。chunkOverlap 的值越高,Chunk 的冗余度就越高 ;chunkOverlap 的值越低,Chunk 之间共享的上下文就越少。通常将 chunkOverlap 设置在 Chunk 大小的 10% 到 20% 之间会比较理想,当然,真正理想 chunkOverlap 值还是要根据不同的文本类型和使用场景来确定。
Chains(模型链)
通过单个 LLM 的输入输出是无法完成一些更为复杂的任务,因此需要利用 Chains,通过将多个 LLM 的功能链接一起来完成。下面是一个很有意思的例子:
代码语言:javascript复制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}"],
]);
// 这里将 2 个模型进行了链接
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 的 temperature 设置为 0,以要求 LLM 给出一个基于事实的回答。该例中,我要求 LLM 基于给定的简短知识库,输出我最喜欢网球运动员的关键数据。以下是 LLM 给出的回答:
代码语言:javascript复制{
resB: {
text: 'Important numbers from the provided text:n'
'n'
"- Alexander Stanislavovich 'Sasha' Bublik's date of birth: 17 June 1997n"
"- Bublik's highest singles ranking: world No. 25n"
"- Bublik's highest doubles ranking: world No. 47n"
"- Bublik's career ATP Tour singles titles: 3n"
"- Bublik's career ATP Tour singles runner-up finishes: 6n"
"- Bublik's height: 1.96 m (6 ft 5 in)n"
"- Bublik's number of aces served in the 2021 ATP Tour season: unknownn"
"- Bublik's junior tour ranking: No. 19n"
"- Bublik's junior tour titles: 11 (6 singles and 5 doubles)n"
"- Bublik's previous citizenship: Russian"
"- Bublik's current citizenship: Kazakhstann"
"- Bublik's role in the Levitov Chess Wizards team: reserve member"
}
}
很酷,但这还没有真正展示 Chains 的全部能力。再看一个更实际的例子:
代码语言:javascript复制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”的输出:
代码语言:javascript复制{
"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
尽管在演示 LangChain 不同功能的示例中,我一直都是使用 OpenAI 模型。但其实 LangChain 并不局限于 OpenAI 模型。你可以将 LangChain 与许多其他 LLM 和 AI 服务一起使用。在 LangChain 的官方文档中可以找到 LangChain 的 JS 版本所支持集成的完整 LLM 列表。
例如,你可以将 Cohere 与 LangChain 一起使用。再使用 npm install cohere-ai 安装 Cohere 之后,你就可以像下面示例代码一样,使用 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" // 给 Nas 的新专辑起个名字
);
console.log({ res });
输出的结果如下:
代码语言:javascript复制{
res: ' Here are a few possible names for a new Nas album:n'
'n'
"- King's Landingn"
"- God's Son: The Sequeln"
"- Street's Disciplen"
'- Izzy Freen'
'- Nas and the Illmatic Flown'
'n'
'Do any'
}
总 结
读完本篇文章,相信你已经对 JS 版的 LangChain 各方面能力都有所了解了。现在你可以通过 LangChain 用 JS 开发各种基于 AI 的应用和体验 LLM 了。当然,也请你必参考 LangChainJS 的官方文档,以了解更多有关特定功能的详细信息。
最后,预祝你在 JavaScript 中愉快的使用 LangChain 进行编码和体验!如果你喜欢这篇文章,你可能还想阅读如何在 Python 中使用 LangChain 这篇文章:https://www.sitepoint.com/langchain-python-complete-guide/
原文链接:
https://www.sitepoint.com/langchain-javascript-complete-guide/
相关阅读:
LangChain 的问题所在 (https://www.infoq.cn/article/Rujx6tv3Grxh3HYMZJqK?utm_campaign)
OpenAI 用 45 分钟重塑游戏规则!干掉 MJ、LangChain,创造“不会编程的应用开发者”新职业 (https://www.infoq.cn/article/67vMj2F2HTC24fDdE64a?utm_campaign)
LangChain:2023 年最潮大语言模型 Web 开发框架 (https://www.infoq.cn/article/yl8eJoSfkHbOCyFzCcgw?utm_campaign)
理论 实践详解最热的 LLM 应用框架 LangChain(https://xie.infoq.cn/article/d9e58b3a7e0fe2a369269c923?utm_campaign)
声明:本文由 InfoQ 翻译,未经许可禁止转载。