標題:使用 Langchain 為您的文件建立 QA 機器人 😻

描述:使用 Wing Framework、NextJS 和 Langchain 建立的 ChatGPT 用戶端應用程式

canonical_url:https://www.winglang.io/blog/2024/05/29/qa-bot-for-your-docs-with-langchain

發表:真實


長話短說

在本教學中,我們將為您的網站文件建立一個人工智慧驅動的問答機器人。

  • 🌐 建立一個用戶友好的 Next.js 應用程式來接受問題和 URL

  • 🔧 設定一個 Wing 後端來處理所有請求

  • 💡 透過使用 RAG 抓取和分析文件,將 @langchain 納入 AI 驅動的答案

  • 🔄 前端輸入和人工智慧處理的回應之間的完整連接。

問題

什麼是翼?

Wing是一個雲端開源框架。

它允許您將應用程式的基礎架構和程式碼組合為一個單元,並將它們安全地部署到您首選的雲端提供者。

Wing 讓您可以完全控制應用程式基礎架構的配置方式。除了其易於學習的程式語言之外,Wing 還支援 Typescript。

在本教學中,我們將使用 TypeScript。所以,別擔心,您的 JavaScript 和 React 知識足以理解本教學。

翼登陸頁面

{% cta https://wingla.ng/github %} 看 Wing ⭐️ {% endcta %}


使用 Next.js 建立前端

在這裡,您將建立一個簡單的表單,它接受文件 URL 和使用者的問題,然後根據網站的資料回傳回應。

首先,建立一個包含兩個子資料夾的資料夾 - frontendbackendfrontend資料夾包含 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 並將它們暫時記錄到控制台。

QA 機器人表單

完美的! 🎉您已經完成了應用程式的使用者介面。接下來,讓我們設定 Wing 後端。


如何在電腦上設定 Wing

Wing 提供了一個 CLI,使您能夠在專案中執行各種 Wing 操作。

它還提供VSCodeIntelliJ擴展,透過語法突出顯示、編譯器診斷、程式碼完成和片段等功能增強開發人員體驗。

在繼續之前,請停止 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 TS 最小控制台

您已在電腦上成功安裝 Wing。


如何將 Wing 連接到 Next.js 應用程式

在前面的部分中,您已在frontend資料夾中建立了 Next.js 前端應用程式,並在backend資料夾中建立了 Wing 後端。

在本部分中,您將了解如何在 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 並發出這些請求?

更新 Next.js app/layout.tsx檔案中的RootLayout元件,如下所示:

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>
    );
}

透過執行npm run build重新建置 Next.js 專案。

最後,啟動Wing開發伺服器。它會自動啟動 Next.js 伺服器,可以在瀏覽器中透過http://localhost:3001存取該伺服器。

控制台到 URL

您已成功將 Next.js 連接到 Wing。您也可以使用window.wingEnv.<attribute_name>存取環境變數中的資料。

視窗.wingEnv

使用LangChain和Wing處理用戶請求

在本節中,您將學習如何向 Wing 發送請求,使用LangChain 和 OpenA I 處理這些請求,並在 Next.js 前端顯示結果。

首先,我們更新 Next.js app/page.tsx檔案以檢索 API URL 並將使用者資料傳送到 Wing API 端點。

為此,請透過在page.tsx檔案頂部新增以下程式碼片段來擴充 JavaScript window物件。

"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端點。

端點將:

首先,將以下內容匯入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,初始化ChatOpenAIOpenAIEmbeddings ,載入文件頁面,並以文件的形式檢索使用者查詢的答案。

然後,將文件分割成區塊,將區塊保存在MemoryVectorStore中,並使我們能夠使用LangChain 檢索器來取得問題的答案。

從上面的程式碼片段來看,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);
});
  • 從上面的程式碼片段來看,
- The `secret` variable declares a name for the secret (OpenAI API key).
- The [`lift().grant()`](https://www.winglang.io/docs/typescript/inflights#permissions) grants the API endpoint access to the secret value stored in the Wing Secret.
- The [`inflight()`](https://www.winglang.io/docs/typescript/inflights) function accepts the context and request object as parameters, makes a request to LangChain, and returns the result.
- Then, you can access the `apiKey` using the `ctx.secret.value()` function.

最後,透過在終端機中執行此命令將 OpenAI API 金鑰儲存為機密。

翅膀的秘密

恭喜!您已成功完成本教學的專案。

以下是該應用程式的簡短演示:

QA 機器人演示


讓我們更深入地研究 Wing 文件,看看我們的 AI 機器人可以提取哪些資料。

QA 機器人演示


總結一下

到目前為止,我們已經討論了以下內容:

  • 什麼是翼?

  • 如何使用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


共有 0 則留言