使用 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 并发出这些请求?
按照以下步骤更新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>
    );
}
通过运行重新构建 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 后端开发教程 - Java、Spring Boot 实战 - msg200.com
            后端开发教程 - Java、Spring Boot 实战 - msg200.com
          










