使用 LangChain 为您的文档构建一个 QA 机器人 😻
TL;DR
在本教程中,我们将为您的网站文档构建一个由人工智能驱动的问答机器人。
-
🌐 创建一个用户友好的 Next.js 应用程序来接受问题和 URL
-
🔧 设置 Wing 后端来处理所有请求
-
💡 通过使用 RAG 抓取和分析文档,结合 @langchain 获得 AI 驱动的答案
-
🔄 前端输入和 AI 处理的响应之间的完整连接。
Wing 是什么?
Wing是一个开源云框架。
它允许您将应用程序的基础架构和代码组合为一个单元,并将它们安全地部署到您首选的云提供商。
Wing 让您完全控制应用程序基础架构的配置方式。除了易于学习的编程语言之外,Wing 还支持 Typescript。
在本教程中,我们将使用 TypeScript。所以不用担心,你的 JavaScript 和 React 知识足以理解本教程。
使用 Next.js 构建前端
在这里,您将创建一个简单的表单,它接受文档 URL 和用户的问题,然后根据网站的数据返回答复。
首先,创建一个包含两个子文件夹的文件夹 -frontend
和backend
。frontend
文件夹包含 Next.js 应用,backend
文件夹用于 Wing。
mkdir qa-bot && cd qa-bot
mkdir frontend backend
在该文件夹中frontend
,通过运行以下代码片段创建 Next.js 项目:
cd frontend
npx create-next-app ./
将下面的代码片段复制到app/page.tsx
文件中,以创建接受用户问题和文档 URL 的表单:
"use client";
import { useState } from "react";
export default function Home() {
const [documentationURL, setDocumentationURL] = useState<string>("");
const [question, setQuestion] = useState<string>("");
const [disable, setDisable] = useState<boolean>(false);
const [response, setResponse] = useState<string | null>(null);
const handleUserQuery = async (e: React.FormEvent) => {
e.preventDefault();
setDisable(true);
console.log({ question, documentationURL });
};
return (
<main className='w-full md:px-8 px-3 py-8'>
<h2 className='font-bold text-2xl mb-8 text-center text-blue-600'>
Documentation Bot with Wing & LangChain
</h2>
<form onSubmit={handleUserQuery} className='mb-8'>
<label className='block mb-2 text-sm text-gray-500'>Webpage URL</label>
<input
type='url'
className='w-full mb-4 p-4 rounded-md border text-sm border-gray-300'
placeholder='https://www.winglang.io/docs/concepts/why-wing'
required
value={documentationURL}
onChange={(e) => setDocumentationURL(e.target.value)}
/>
<label className='block mb-2 text-sm text-gray-500'>
Ask any questions related to the page URL above
</label>
<textarea
rows={5}
className='w-full mb-4 p-4 text-sm rounded-md border border-gray-300'
placeholder='What is Winglang? OR Why should I use Winglang? OR How does Winglang work?'
required
value={question}
onChange={(e) => setQuestion(e.target.value)}
/>
<button
type='submit'
disabled={disable}
className='bg-blue-500 text-white px-8 py-3 rounded'
>
{disable ? "Loading..." : "Ask Question"}
</button>
</form>
{response && (
<div className='bg-gray-100 w-full p-8 rounded-sm shadow-md'>
<p className='text-gray-600'>{response}</p>
</div>
)}
</main>
);
}
上面的代码片段显示了一个表单,该表单接受用户的问题和文档 URL,并将它们记录到控制台。
完美!🎉您已完成应用程序的用户界面。接下来,让我们设置 Wing 后端。
如何在计算机上设置 Wing
Wing 提供了一个 CLI,使您能够在项目中执行各种 Wing 操作。
它还提供 VSCode 和 IntelliJ 扩展,通过语法高亮、编译器诊断、代码完成和代码片段等功能增强开发人员体验。
在我们继续之前,请停止您的 Next.js 开发服务器并通过在终端中运行下面的代码片段来安装 Wing CLI。
npm install -g winglang@latest
运行以下代码片段以确保 Winglang CLI 已安装并按预期工作:
wing -V
接下来,导航到该backend
文件夹并创建一个空的 Wing Typescript 项目。确保选择empty
模板并选择 Typescript 作为语言。
wing new
将下面的代码片段复制到backend/main.ts
文件中。
import { cloud, inflight, lift, main } from "@wingcloud/framework";
main((root, test) => {
const fn = new cloud.Function(
root,
"Function",
inflight(async () => {
return "hello, world";
})
);
});
该main()
函数作为 Wing 的入口点。
它创建一个云函数并在编译时执行。inflight
另一方面,该函数在运行时运行并返回一个Hello, world!
文本。
通过运行以下代码片段启动 Wing 开发服务器。它会自动在浏览器中打开 Wing 控制台http://localhost:3000
。
wing it
您已成功在计算机上安装 Wing。
如何将 Wing 连接到 Next.js 应用
从前面的部分中,您已经在frontend
文件夹中创建了 Next.js 前端应用程序,并在文件夹中创建了 Wing 后端backend
。
在本节中,您将学习如何在 Next.js 应用程序和 Wing 后端之间通信和发送数据。
首先, 通过运行以下代码在后端文件夹中安装Wing React库:
npm install @winglibs/react
接下来,更新main.ts
文件,如下所示:
import { main, cloud, inflight, lift } from "@wingcloud/framework";
import React from "@winglibs/react";
main((root, test) => {
const api = new cloud.Api(root, "api", { cors: true })
;
//👇🏻 create an API route
api.get(
"/test",
inflight(async () => {
return {
status: 200,
body: "Hello world",
};
})
);
//👉🏻 placeholder for the POST request endpoint
//👇🏻 connects to the Next.js project
const react = new React.App(root, "react", { projectPath: "../frontend" });
//👇🏻 an environment variable
react.addEnvironment("api_url", api.url);
});
上面的代码片段创建了一个 API 端点 ( /test
),用于接受 GET 请求并返回Hello world
文本。该main
函数还连接到 Next.js 项目,并将 添加api_url
为环境变量。
环境变量中包含的 API URL 使我们能够向 Wing API 路由发送请求。如何在 Next.js 应用中检索 API URL 并发出这些请求?
按照以下步骤更新RootLayout
Next.js 文件中的组件:app/layout.tsx
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang='en'>
<head>
{/** ---👇🏻 Adds this script tag 👇🏻 ---*/}
<script src='./wing.js' defer />
</head>
<body className={inter.className}>{children}</body>
</html>
);
}
通过运行重新构建 Next.js 项目npm run build
。
最后,启动 Wing 开发服务器。它会自动启动 Next.js 服务器,您可以http://localhost:3001
通过浏览器访问。
您已成功将 Next.js 连接到 Wing。您还可以使用 访问环境变量中的数据window.wingEnv.<attribute_name>
。
使用 LangChain 和 Wing 处理用户请求
在本节中,您将学习如何向 Wing 发送请求,使用LangChain 和 OpenA I 处理这些请求,并在 Next.js 前端显示结果。
首先,让我们更新 Next.jsapp/page.tsx
文件以检索 API URL 并将用户的数据发送到 Wing API 端点。
为此,window
通过在文件顶部添加以下代码片段来扩展 JavaScript 对象page.tsx
。
"use client";
import { useState } from "react";
interface WingEnv {
api_url: string;
}
declare global {
interface Window {
wingEnv: WingEnv;
}
}
接下来,更新handleUserQuery
函数以将包含用户问题和网站 URL 的 POST 请求发送到 Wing API 端点。
//👇🏻 sends data to the api url
const [response, setResponse] = useState<string | null>(null);
const handleUserQuery = async (e: React.FormEvent) => {
e.preventDefault();
setDisable(true);
try {
const request = await fetch(`${window.wingEnv.api_url}/api`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ question, pageURL: documentationURL }),
});
const response = await request.text();
setResponse(response);
setDisable(false);
} catch (err) {
console.error(err);
setDisable(false);
}
};
在创建接受 POST 请求的 Wing 端点之前,请在backend
文件夹中安装以下包:
npm install @langchain/community @langchain/openai langchain cheerio
Cheerio使我们能够抓取软件文档网页,而LangChain 包则允许我们访问其各种功能。
LangChain OpenAI 集成包使用 OpenAI 语言模型;因此,您需要一个有效的 API 密钥。您可以从 OpenAI 开发者平台获取。
接下来,让我们创建/api
处理传入请求的端点。
端点将:
- 接受来自 Next.js 应用程序的问题和文档 URL,
- 使用LangChain 文档加载器加载文档页面,
- 将检索到的文档拆分成块,
- 转换分块文档并将其保存在LangChain 矢量存储中,
- 并创建一个检索函数,从向量存储中检索文档。
首先,将以下内容导入main.ts
文件:
import { main, cloud, inflight, lift } from "@wingcloud/framework";
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";
import { CheerioWebBaseLoader } from "@langchain/community/document_loaders/web/cheerio";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import { createRetrievalChain } from "langchain/chains/retrieval";
import React from "@winglibs/react";
在函数中添加以下代码片段main()
以创建/api
端点:
api.post(
"/api",
inflight(async (ctx, request) => {
//👇🏻 accept user inputs from Next.js
const { question, pageURL } = JSON.parse(request.body!);
//👇🏻 initialize OpenAI Chat for LLM interactions
const chatModel = new ChatOpenAI({
apiKey: "<YOUR_OPENAI_API_KEY>",
model: "gpt-3.5-turbo-1106",
});
//👇🏻 initialize OpenAI Embeddings for Vector Store data transformation
const embeddings = new OpenAIEmbeddings({
apiKey: "<YOUR_OPENAI_API_KEY>",
});
//👇🏻 creates a text splitter function that splits the OpenAI result chunk size
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 200, //👉🏻 characters per chunk
chunkOverlap: 20,
});
//👇🏻 creates a document loader, loads, and scraps the page
const loader = new CheerioWebBaseLoader(pageURL);
const docs = await loader.load();
//👇🏻 splits the document into chunks
const splitDocs = await splitter.splitDocuments(docs);
//👇🏻 creates a Vector store containing the split documents
const vectorStore = await MemoryVectorStore.fromDocuments(
splitDocs,
embeddings //👉🏻 transforms the data to the Vector Store format
);
//👇🏻 creates a document retriever that retrieves results that answers the user's questions
const retriever = vectorStore.asRetriever({
k: 1, //👉🏻 number of documents to retrieve (default is 2)
});
//👇🏻 creates a prompt template for the request
const prompt = ChatPromptTemplate.fromTemplate(`
Answer this question.
Context: {context}
Question: {input}
`);
//👇🏻 creates a chain containing the OpenAI chatModel and prompt
const chain = await createStuffDocumentsChain({
llm: chatModel,
prompt: prompt,
});
//👇🏻 creates a retrieval chain that combines the documents and the retriever function
const retrievalChain = await createRetrievalChain({
combineDocsChain: chain,
retriever,
});
//👇🏻 invokes the retrieval Chain and returns the user's answer
const response = await retrievalChain.invoke({
input: `${question}`,
});
if (response) {
return {
status: 200,
body: response.answer,
};
}
return undefined;
})
);
API 端点接受来自 Next.js 应用程序的用户问题和页面 URL,初始化ChatOpenAI
和OpenAIEmbeddings
,加载文档页面,并以文档的形式检索用户查询的答案。
然后,将文档分成块,将块保存在中,并使我们能够使用LangChain 检索器MemoryVectorStore
获取问题的答案。
从上面的代码片段可以看出,OpenAI API 密钥直接输入到代码中;这可能会导致安全漏洞,使攻击者可以访问 API 密钥。为了防止这种数据泄露,Wing 允许您将私钥和凭据保存在名为 的变量中secrets
。
当您创建秘密时,Wing 会将这些数据保存在.env
文件中,以确保其安全且可访问。
更新main()
函数以从 Wing Secret 获取 OpenAI API 密钥。
main((root, test) => {
const api = new cloud.Api(root, "api", { cors: true });
//👇🏻 creates the secret variable
const secret = new cloud.Secret(root, "OpenAPISecret", {
name: "open-ai-key",
});
api.post(
"/api",
lift({ secret })
.grant({ secret: ["value"] })
.inflight(async (ctx, request) => {
const apiKey = await ctx.secret.value();
const chatModel = new ChatOpenAI({
apiKey,
model: "gpt-3.5-turbo-1106",
});
const embeddings = new OpenAIEmbeddings({
apiKey,
});
//👉🏻 other code snippets & configurations
);
const react = new React.App(root, "react", { projectPath: "../frontend" });
react.addEnvironment("api_url", api.url);
});
- 从上面的代码片段来看,
- 该
secret
变量声明了秘密的名称(OpenAI API 密钥)。 - 授予
lift().grant()
API 端点访问 Wing Secret 中存储的秘密值的权限。 - 该
inflight()
函数接受上下文和请求对象作为参数,向 LangChain 发出请求,并返回结果。 - 然后,您可以
apiKey
使用该ctx.secret.value()
函数进行访问。
- 该
最后,通过在终端中运行此命令将 OpenAI API 密钥保存为机密。
恭喜!您已成功完成本教程的项目。
以下是该应用程序的简要演示:
让我们更深入地研究 Wing 文档,看看我们的 AI 机器人可以提取哪些数据。
总结
到目前为止,我们已经讨论了以下内容:
- Wing 是什么?
- 如何使用 Wing 并使用 Langchain 查询数据,
- 如何将 Wing 连接到 Next.js 应用程序
- 如何在 Next.js 前端和 Wing 后端之间发送数据。
Wing旨在重拾您的创意灵感,缩小想象与创造之间的差距。Wing 的另一大优势在于它是开源的。因此,如果您期待构建利用云服务的分布式系统,或为云开发的未来做出贡献,Wing是您的最佳选择。
欢迎随意为 GitHub 存储库做出贡献, 并 与团队和庞大的开发者社区分享您的想法。
本教程的源代码可在此处获得。
感谢您的阅读!🎉
文章来源:https://dev.to/winglang/build-a-qa-bot-for-your-documentation-with-langchain-27i4