標題:使用 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 %}
在這裡,您將建立一個簡單的表單,它接受文件 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 提供了一個 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。
在前面的部分中,您已在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
存取該伺服器。
您已成功將 Next.js 連接到 Wing。您也可以使用window.wingEnv.<attribute_name>
存取環境變數中的資料。
在本節中,您將學習如何向 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
端點。
端點將:
接受來自 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
,載入文件頁面,並以文件的形式檢索使用者查詢的答案。
然後,將文件分割成區塊,將區塊保存在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 金鑰儲存為機密。
恭喜!您已成功完成本教學的專案。
以下是該應用程式的簡短演示:
讓我們更深入地研究 Wing 文件,看看我們的 AI 機器人可以提取哪些資料。
到目前為止,我們已經討論了以下內容:
什麼是翼?
如何使用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