使用 LangChain 为您的文档构建一个 QA 机器人 😻

2025-05-28

使用 LangChain 为您的文档构建一个 QA 机器人 😻

TL;DR

在本教程中,我们将为您的网站文档构建一个由人工智能驱动的问答机器人。

  • 🌐 创建一个用户友好的 Next.js 应用程序来接受问题和 URL

  • 🔧 设置 Wing 后端来处理所有请求

  • 💡 通过使用 RAG 抓取和分析文档,结合 @langchain 获得 AI 驱动的答案

  • 🔄 前端输入和 AI 处理的响应之间的完整连接。

问题

Wing 是什么?

Wing是一个开源云框架。

它允许您将应用程序的基础架构和代码组合为一个单元,并将它们安全地部署到您首选的云提供商。

Wing 让您完全控制应用程序基础架构的配置方式。除了易于学习的编程语言之外,Wing 还支持 Typescript。

在本教程中,我们将使用 TypeScript。所以不用担心,你的 JavaScript 和 React 知识足以理解本教程。

翼登陆页面

查看 Wing⭐️


使用 Next.js 构建前端

在这里,您将创建一个简单的表单,它接受文档 URL 和用户的问题,然后根据网站的数据返回答复。

首先,创建一个包含两个子文件夹的文件夹 -frontendbackendfrontend文件夹包含 Next.js 应用,backend文件夹用于 Wing。

mkdir qa-bot && cd qa-bot
mkdir frontend backend
Enter fullscreen mode Exit fullscreen mode

在该文件夹中frontend,通过运行以下代码片段创建 Next.js 项目:

cd frontend
npx create-next-app ./
Enter fullscreen mode Exit fullscreen mode

下一个应用程序

将下面的代码片段复制到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>
    );
}
Enter fullscreen mode Exit fullscreen mode

上面的代码片段显示了一个表单,该表单接受用户的问题和文档 URL,并将它们记录到控制台。

质量保证机器人表单

完美!🎉您已完成应用程序的用户界面。接下来,让我们设置 Wing 后端。


如何在计算机上设置 Wing

Wing 提供了一个 CLI,使您能够在项目中执行各种 Wing 操作。

它还提供 VSCode 和 IntelliJ 扩展,通过语法高亮、编译器诊断、代码完成和代码片段等功能增强开发人员体验。

在我们继续之前,请停止您的 Next.js 开发服务器并通过在终端中运行下面的代码片段来安装 Wing CLI。

npm install -g winglang@latest
Enter fullscreen mode Exit fullscreen mode

运行以下代码片段以确保 Winglang CLI 已安装并按预期工作:

wing -V
Enter fullscreen mode Exit fullscreen mode

接下来,导航到该backend文件夹​​并创建一个空的 Wing Typescript 项目。确保选择empty模板并选择 Typescript 作为语言。

wing new

Enter fullscreen mode Exit fullscreen mode

永新

将下面的代码片段复制到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";
        })
    );
});
Enter fullscreen mode Exit fullscreen mode

main()函数作为 Wing 的入口点。

它创建一个云函数并在编译时执行。inflight另一方面,该函数在运行时运行并返回一个Hello, world!文本。

通过运行以下代码片段启动 Wing 开发服务器。它会自动在浏览器中打开 Wing 控制台http://localhost:3000

wing it
Enter fullscreen mode Exit fullscreen mode

Wing TS 最小控制台

您已成功在计算机上安装 Wing。


如何将 Wing 连接到 Next.js 应用

从前面的部分中,您已经在frontend文件夹中创建了 Next.js 前端应用程序,并在文件夹中创建了 Wing 后端backend

在本节中,您将学习如何在 Next.js 应用程序和 Wing 后端之间通信和发送数据。

首先,  通过运行以下代码在后端文件夹中安装Wing React库:

npm install @winglibs/react
Enter fullscreen mode Exit fullscreen mode

接下来,更新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);
});
Enter fullscreen mode Exit fullscreen mode

上面的代码片段创建了一个 API 端点 ( /test),用于接受 GET 请求并返回Hello world文本。该main函数还连接到 Next.js 项目,并将 添加api_url为环境变量。

环境变量中包含的 API URL 使我们能够向 Wing API 路由发送请求。如何在 Next.js 应用中检索 API URL 并发出这些请求?

按照以下步骤更新RootLayoutNext.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>
    );
}
Enter fullscreen mode Exit fullscreen mode

通过运行重新构建 Next.js 项目npm run build

最后,启动 Wing 开发服务器。它会自动启动 Next.js 服务器,您可以http://localhost:3001通过浏览器访问。

控制台到 URL

您已成功将 Next.js 连接到 Wing。您还可以使用 访问环境变量中的数据window.wingEnv.<attribute_name>

窗口.wingEnv

使用 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

接下来,更新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);
        }
    };
Enter fullscreen mode Exit fullscreen mode

在创建接受 POST 请求的 Wing 端点之前,请在backend文件夹中安装以下包:

npm install @langchain/community @langchain/openai langchain cheerio
Enter fullscreen mode Exit fullscreen mode

Cheerio使我们能够抓取软件文档网页,而LangChain 包则允许我们访问其各种功能。

LangChain OpenAI 集成包使用 OpenAI 语言模型;因此,您需要一个有效的 API 密钥。您可以从 OpenAI 开发者平台获取。

朗查因

接下来,让我们创建/api处理传入请求的端点。

端点将:

首先,将以下内容导入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";
Enter fullscreen mode Exit fullscreen mode

在函数中添加以下代码片段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;
        })
    );
Enter fullscreen mode Exit fullscreen mode

API 端点接受来自 Next.js 应用程序的用户问题和页面 URL,初始化ChatOpenAIOpenAIEmbeddings,加载文档页面,并以文档的形式检索用户查询的答案。

然后,将文档分成块,将块保存在中,并使我们能够使用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);
});
Enter fullscreen mode Exit fullscreen mode
  • 从上面的代码片段来看,
    • secret变量声明了秘密的名称(OpenAI API 密钥)。
    • 授予lift().grant()API 端点访问 Wing Secret 中存储的秘密值的权限。
    • inflight()函数接受上下文和请求对象作为参数,向 LangChain 发出请求,并返回结果。
    • 然后,您可以apiKey使用该ctx.secret.value()函数进行访问。

最后,通过在终端中运行此命令将 OpenAI API 密钥保存为机密。

翼之秘密

恭喜!您已成功完成本教程的项目。

以下是该应用程序的简要演示:

QA 机器人演示 1


让我们更深入地研究 Wing 文档,看看我们的 AI 机器人可以提取哪些数据。

QA 机器人演示 2


总结

到目前为止,我们已经讨论了以下内容:

  • 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
PREV
使用 NextJS 和 Wing 构建你自己的 ChatGPT 图形客户端 🤯
NEXT
使用这些 React 库和云后端构建全栈应用程序。