其中一种使用LLM构建的常见应用程序是一个系统,可以自动提取PDF文件、网页或公司内部文档中的文本,并在这些文本上方或相关的位置提供回答。这种系统通常可以提供有用的信息和解答,有助于用户更快速、高效地处理相关任务。
基本文档加载器创建向量存储
RetrievalQAChain 是一个结合了一个 Retriever 和一个 QA 链的链。它用于从检索器检索文档,然后使用 QA 链根据检索到的文档回答问题。
// 导入所需的链条和LLM类
import { SimpleSequentialChain, LLMChain } from 'langchain/chains';
import { OpenAI } from 'langchain/llms/openai';
import { RetrievalQAChain } from 'langchain/chains';
import { HNSWLib } from 'langchain/vectorstores/hnswlib';
import { OpenAIEmbeddings } from 'langchain/embeddings/openai';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
// 初始化用于回答问题的语言模型OpenAI GPT-3.5 Turbo
const model = new OpenAI({
modelName: 'gpt-3.5-turbo',
openAIApiKey: process.env.OPENAI_API_KEY,
temperature: 0,
verbose: true,
}, {
baseOptions,
});
// 读取文本文件
const text = fs.readFileSync('state_of_the_union.txt', 'utf8');
// 将文本分割成若干个文档
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
});
const docs = await textSplitter.createDocuments([text]);
// 使用OpenAI的嵌入模型为文档创建向量表示
const vectorStore = await HNSWLib.fromDocuments(docs, new OpenAIEmbeddings({
verbose: true,
openAIApiKey: process.env.OPENAI_API_KEY,
}, {
baseOptions,
}));
// 将向量数据库保存到本地文件
await vectorStore.save(LOCAL_VENCTOR_DATA_PATH);
console.log('向量数据库初始化成功!');
// 将向量数据库包装成检索器
const vectorStoreRetriever = vectorStore.asRetriever();
// 创建序列问答链,使用GPT-3.5和向量数据库
const chain = RetrievalQAChain.fromLLM(model, vectorStoreRetriever);
// 进行问答
const res = await chain.call({
query: '用中文概括这篇文章的主要内容。',
});
console.log(res);
这段代码主要步骤:
- 导入需要的类和模块
- 初始化OpenAI的GPT-3.5语言模型
- 读取文本并分割成文档
- 用OpenAI的embedding为文档创建向量表示
- 将向量数据库保存为本地文件
- 创建序列问答链,使用GPT-3.5和向量数据库
- 进行问答
LLM's on Documents
想要使用语言模型,并且与大量文档结合,存在一个关键问题:但是有一个关键问题。语言模型一次只能检查几千个单词。如果我们有非常大的文档,如何让语言模型回答关于其中所有内容的问题呢?这就是Embedding和向量存储发挥作用的地方。
Embedding
- My dog Rover likes to chase squirrels.
- Fluffy, my cat, refuses to eat from a can.
- The Chevy Bolt accelerates to mph in 6.7 seconds.
如果我们看一下数字空间中的表示,我们可以看到当我们比较与宠物句子相对应的文本片段上的两个向量时,它们非常相似。而如果我们将其与谈论汽车的那个进行比较,它们根本不相似。这将让我们轻松地找出哪些文本片段彼此相似,这在我们考虑要包含哪些文本片段传递给语言模型以回答问题时非常有用。
向量数据库
向量数据库是存储我们在上一步中创建的这些向量表示的一种方式。我们创建这个向量数据库的方式是用来自传入文档的文本块填充它。我们将大文档分成较小块,然后为每个块创建一个Embeddings,最后将它们存储在向量数据库中。这有助于我们为语言模型提供最相关的文本片段,以便只传递最相关的块给模型。
在使用向量数据库查找最相关文本片段时,我们首先为查询创建一个Embeddings,并与向量数据库中的所有向量进行比较,选择最相似的n个。最后,将它们传递到语言模型中,以获得最终答案。
文档(Document)
这些是使用 Document 的核心链。它们对于总结文档、回答有关文档的问题、从文档中提取信息等非常有用。
import { OpenAI } from "langchain/llms/openai";
import {
loadQAStuffChain,
loadQAMapReduceChain,
loadQARefineChain
} from "langchain/chains";
import { Document } from "langchain/document";
// 第一个示例使用 `StuffDocumentsChain`。
const llmA = new OpenAI({});
const chainA = loadQAStuffChain(llmA);
const docs = [
new Document({ pageContent: "Harrison 在哈佛大学读书。" }),
new Document({ pageContent: "Ankush 在普林斯顿大学读书。" }),
];
const resA = await chainA.call({
input_documents: docs,
question: "Harrison 去了哪个大学?",
});
console.log({ resA });
// { resA: { text: ' Harrison 在哈佛大学读书。' } }
// 第二个示例使用 `MapReduceChain`。
// 可选地限制并发请求到语言模型的数量。
const llmB = new OpenAI({ maxConcurrency: 10 });
const chainB = loadQAMapReduceChain(llmB);
const resB = await chainB.call({
input_documents: docs,
question: "Harrison 去了哪个大学?",
});
console.log({ resB });
// { resB: { text: ' Harrison 在哈佛大学读书。' } }
Stuff要素
“要素”文档链是最直接的文档链。它获取一个文档列表,将它们全部插入到一个提示符中,并将该提示符传递给一个 LLM。
Stuff 方法是一种简单易用的工具,可将内容发送到语言模型以获取响应。它便宜且效果不错,但不是总能正常工作。
import { OpenAI } from "langchain/llms/openai";
import { loadQAStuffChain } from "langchain/chains";
import { Document } from "langchain/document";
// 第一个示例使用 `StuffDocumentsChain`。
const llmA = new OpenAI({});
const chainA = loadQAStuffChain(llmA);
const docs = [
new Document({ pageContent: "Harrison 去了哈佛大学。" }),
new Document({ pageContent: "Ankush 去了普林斯顿大学。" }),
];
const resA = await chainA.call({
input_documents: docs,
question: "Harrison 去了哪个大学?",
});
console.log({ resA });
// { resA: { text: ' Harrison 去了哈佛大学。' } }
如果您想在许多不同类型的块上执行相同类型的问答,该怎么办?
Refine提炼
细化文档链通过循环输入文档并迭代地更新其答案来构造响应。对于每个文档,它将所有非文档输入、当前文档和最新的中间答案传递给 LLM 链,以获得新的答案。
由于 Refine 链一次只向 LLM 传递一个文档,因此它非常适合于需要分析比模型上下文更多文档的任务。一个明显的折衷是,这个链会比 Stuff 文档链执行更多的 LLM 调用。还有一些难以迭代完成的任务。例如,当文档频繁地相互交叉引用或者当任务需要许多文档的详细信息时,Refine 链可能性能很差。
import { loadQARefineChain } from "langchain/chains";
import { OpenAI } from "langchain/llms/openai";
import { TextLoader } from "langchain/document_loaders/fs/text";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
// 创建模型和链
const embeddings = new OpenAIEmbeddings();
const model = new OpenAI({ temperature: 0 });
const chain = loadQARefineChain(model);
// 加载文档并创建向量存储
const loader = new TextLoader("./state_of_the_union.txt");
const docs = await loader.loadAndSplit();
const store = await MemoryVectorStore.fromDocuments(docs, embeddings);
// 选择相关文档
const question = "总统关于布雷耶大法官的讲话内容是什么?";
const relevantDocs = await store.similaritySearch(question);
// 调用链
const res = await chain.call({
input_documents: relevantDocs,
question,
});
console.log(res);
/*
{
output_text: '\n' +
'\n' +
"总统表示,斯蒂芬·布雷耶大法官献身于为这个国家服务,并感谢他的贡献。他还提到,凯坦吉·布朗·杰克逊法官将继续布雷耶大法官的卓越传统,并指出了作为罗伊诉韦德案中确认的宪法权利——成为半个世纪的先例——正受到前所未有的攻击。他强调了保护医疗保健的重要性,维护妇女的选择权,以及在美国推进母婴健康护理。他还表达了对LGBTQ+社群的支持,以及保护他们权利的承诺,包括提供全国统一议程以应对阿片类药物流行,增加预防、治疗、危害减少和康复方面的资金,并加强打击家庭暴力法案。"
}
*/
Map reduce
Map reduce 文档链首先对每个文档单独应用一个 LLM 链(Map 步骤) ,将链输出视为一个新文档。然后,它将所有新文档传递到一个单独的组合文档链,以获得单个输出(Reduce 步骤)。它可以选择首先压缩或折叠映射的文档,以确保它们适合组合文档链(这通常会将它们传递给 LLM)。如果需要,则递归执行此压缩步骤。
import { OpenAI } from "langchain/llms/openai";
import { loadQAMapReduceChain } from "langchain/chains";
import { Document } from "langchain/document";
// 可选地限制并发请求到语言模型的数量。
const model = new OpenAI({ temperature: 0, maxConcurrency: 10 });
const chain = loadQAMapReduceChain(model);
const docs = [
new Document({ pageContent: "哈里森去了哈佛大学" }),
new Document({ pageContent: "安库什去了普林斯顿大学" }),
];
const res = await chain.call({
input_documents: docs,
question: "哈里森去了哪个大学",
});
console.log({ res });