paint-brush
Cách xây dựng ứng dụng tóm tắt trang web với Next.js, OpenAI, LangChain và Supabasetừ tác giả@nassermaronie
6,010 lượt đọc
6,010 lượt đọc

Cách xây dựng ứng dụng tóm tắt trang web với Next.js, OpenAI, LangChain và Supabase

từ tác giả Nasser Maronie13m2024/06/27
Read on Terminal Reader

dài quá đọc không nổi

Trong bài viết này, chúng tôi sẽ hướng dẫn bạn cách tạo một ứng dụng web tiện dụng có thể tóm tắt nội dung của bất kỳ trang web nào. Sử dụng [Next.js] để có trải nghiệm web nhanh và mượt mà, [LangChain] để xử lý ngôn ngữ, [OpenAI](https://openai.com/) để tạo bản tóm tắt và [Supabase] để quản lý và lưu trữ dữ liệu vectơ, chúng tôi sẽ cùng nhau xây dựng một công cụ mạnh mẽ.
featured image - Cách xây dựng ứng dụng tóm tắt trang web với Next.js, OpenAI, LangChain và Supabase
Nasser Maronie HackerNoon profile picture

Một ứng dụng có thể hiểu ngữ cảnh của bất kỳ trang web nào.

Trong bài viết này, chúng tôi sẽ hướng dẫn bạn cách tạo một ứng dụng web tiện dụng có thể tóm tắt nội dung của bất kỳ trang web nào. Sử dụng Next.js để có trải nghiệm web mượt mà và nhanh chóng, LangChain để xử lý ngôn ngữ, OpenAI để tạo bản tóm tắt và Supabase để quản lý và lưu trữ dữ liệu vectơ, chúng ta sẽ cùng nhau xây dựng một công cụ mạnh mẽ.



Tại sao chúng tôi xây dựng nó

Tất cả chúng ta đều phải đối mặt với tình trạng quá tải thông tin với quá nhiều nội dung trực tuyến. Bằng cách tạo ra một ứng dụng cung cấp các bản tóm tắt nhanh chóng, chúng tôi giúp mọi người tiết kiệm thời gian và luôn cập nhật thông tin. Cho dù bạn là một công nhân bận rộn, một sinh viên hay chỉ là người muốn cập nhật tin tức và bài viết thì ứng dụng này sẽ là một công cụ hữu ích cho bạn.

Mọi chuyện sẽ diễn ra thế nào

Ứng dụng của chúng tôi sẽ cho phép người dùng nhập bất kỳ URL trang web nào và nhanh chóng nhận được bản tóm tắt ngắn gọn về trang. Điều này có nghĩa là bạn có thể hiểu những điểm chính của các bài viết dài, bài đăng trên blog hoặc tài liệu nghiên cứu mà không cần đọc chúng đầy đủ.

Tiềm năng và tác động

Ứng dụng tóm tắt này có thể hữu ích theo nhiều cách. Nó có thể giúp các nhà nghiên cứu lướt qua các bài báo học thuật, cập nhật cho những người yêu thích tin tức và hơn thế nữa. Ngoài ra, các nhà phát triển có thể xây dựng trên ứng dụng này để tạo ra nhiều tính năng hữu ích hơn nữa.


ngăn xếp công nghệ

Next.js

Next.js là một khung React mạnh mẽ và linh hoạt do Vercel phát triển, cho phép các nhà phát triển xây dựng các ứng dụng web tĩnh và kết xuất phía máy chủ (SSR) một cách dễ dàng. Nó kết hợp các tính năng tốt nhất của React với các khả năng bổ sung để tạo các ứng dụng web được tối ưu hóa và có thể mở rộng.

OpenAI

Mô-đun OpenAI trong Node.js cung cấp cách tương tác với API của OpenAI, cho phép các nhà phát triển tận dụng các mô hình ngôn ngữ mạnh mẽ như GPT-3 và GPT-4. Mô-đun này cho phép bạn tích hợp các chức năng AI nâng cao vào các ứng dụng Node.js của mình.

LangChain.js

LangChain là một framework mạnh mẽ được thiết kế để phát triển các ứng dụng bằng mô hình ngôn ngữ. Ban đầu được phát triển cho Python, sau đó nó đã được điều chỉnh cho các ngôn ngữ khác, bao gồm cả Node.js. Dưới đây là tổng quan về LangChain trong bối cảnh Node.js:

LangChain là gì?

LangChain là một thư viện giúp đơn giản hóa việc tạo ứng dụng bằng mô hình ngôn ngữ lớn (LLM) . Nó cung cấp các công cụ để quản lý và tích hợp LLM vào ứng dụng của bạn, xử lý chuỗi lệnh gọi đến các mô hình này và cho phép thực hiện các quy trình công việc phức tạp một cách dễ dàng.

Mô hình ngôn ngữ lớn (LLM) hoạt động như thế nào?

Các Mô hình ngôn ngữ lớn (LLM) như GPT-3.5 của OpenAI được đào tạo trên lượng lớn dữ liệu văn bản để hiểu và tạo ra văn bản giống con người. Họ có thể tạo phản hồi, dịch ngôn ngữ và thực hiện nhiều tác vụ xử lý ngôn ngữ tự nhiên khác.

Supabase

Supabase là nền tảng backend-as-a-service (BaaS) nguồn mở được thiết kế để giúp các nhà phát triển nhanh chóng xây dựng và triển khai các ứng dụng có thể mở rộng. Nó cung cấp một bộ công cụ và dịch vụ giúp đơn giản hóa việc quản lý cơ sở dữ liệu, xác thực, lưu trữ và khả năng thời gian thực, tất cả đều được xây dựng dựa trên PostgreSQL


Điều kiện tiên quyết

Trước khi chúng tôi bắt đầu, hãy đảm bảo bạn có những điều sau:

  • Đã cài đặt Node.js và npm
  • Tài khoản Supabase
  • Tài khoản OpenAI

Bước 1: Thiết lập Supabase

Đầu tiên, chúng ta cần thiết lập một dự án Supabase và tạo các bảng cần thiết để lưu trữ dữ liệu của mình.

Tạo một dự án Supabase

  1. Vào Supabase và đăng ký tài khoản.


  2. Tạo một dự án mới và ghi lại URL Supabase và khóa API của bạn. Bạn sẽ cần những thứ này sau.

Tập lệnh SQL cho Supabase

Tạo một truy vấn SQL mới trong bảng điều khiển Supabase của bạn và chạy các tập lệnh sau để tạo các bảng và hàm cần thiết:

Đầu tiên, tạo tiện ích mở rộng nếu nó chưa tồn tại cho cửa hàng vector của chúng tôi:

 create extension if not exists vector;


Tiếp theo, tạo một bảng có tên là “tài liệu”. Bảng này sẽ dùng để lưu trữ và nhúng nội dung của trang web dưới dạng vector:

 create table if not exists documents ( id bigint primary key generated always as identity, content text, metadata jsonb, embedding vector(1536) );


Bây giờ, chúng ta cần một hàm để truy vấn dữ liệu được nhúng của mình:

 create or replace function match_documents ( query_embedding vector(1536), match_count int default null, filter jsonb default '{}' ) returns table ( id bigint, content text, metadata jsonb, similarity float ) language plpgsql as $$ begin return query select id, content, metadata, 1 - (documents.embedding <=> query_embedding) as similarity from documents where metadata @> filter order by documents.embedding <=> query_embedding limit match_count; end; $$;


Tiếp theo, chúng ta cần thiết lập bảng để lưu trữ thông tin chi tiết của trang web:

 create table if not exists files ( id bigint primary key generated always as identity, url text not null, created_at timestamp with time zone default timezone('utc'::text, now()) not null );

Bước 2: Thiết lập OpenAI

Tạo dự án OpenAI


  • Điều hướng đến API: Sau khi đăng nhập, hãy điều hướng đến phần API và tạo khóa API mới. Điều này thường có thể truy cập được từ bảng điều khiển.

Bước 3: Thiết lập Next.js

Tạo ứng dụng Next.js

 $ npx create-next-app summarize-page $ cd ./summarize-page


Cài đặt các phụ thuộc cần thiết:

 npm install @langchain/community @langchain/core @langchain/openai @supabase/supabase-js langchain openai axios


Sau đó, chúng tôi sẽ cài đặt Material UI để xây dựng giao diện của mình; vui lòng sử dụng thư viện khác:

 npm install @mui/material @emotion/react @emotion/styled

Bước 4: Khách hàng OpenAI và Supabase

Tiếp theo, chúng ta cần thiết lập ứng dụng khách OpenAI và Supabase. Tạo thư mục libs trong dự án của bạn và thêm các tệp sau.

src/libs/openAI.ts

Tệp này sẽ cấu hình ứng dụng khách OpenAI.

 import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai"; const openAIApiKey = process.env.OPENAI_API_KEY; if (!openAIApiKey) throw new Error('OpenAI API Key not found.') export const llm = new ChatOpenAI({ openAIApiKey, modelName: "gpt-3.5-turbo", temperature: 0.9, }); export const embeddings = new OpenAIEmbeddings( { openAIApiKey, }, { maxRetries: 0 } );
  • llm : Phiên bản mô hình ngôn ngữ sẽ tạo ra các bản tóm tắt của chúng tôi.


  • embeddings : Điều này sẽ tạo các phần nhúng cho tài liệu của chúng tôi, giúp tìm kiếm nội dung tương tự.

src/libs/supabaseClient.ts

Tập tin này sẽ cấu hình máy khách Supabase.

 import { createClient } from "@supabase/supabase-js"; const supabaseUrl = process.env.SUPABASE_URL || ""; const supabaseAnonKey = process.env.SUPABASE_ANON_KEY || ""; if (!supabaseUrl) throw new Error("Supabase URL not found."); if (!supabaseAnonKey) throw new Error("Supabase Anon key not found."); export const supabaseClient = createClient(supabaseUrl, supabaseAnonKey);
  • supabaseClient : Phiên bản máy khách Supabase để tương tác với cơ sở dữ liệu Supabase của chúng tôi.

Bước 5: Tạo dịch vụ cho nội dung và tệp

Tạo một thư mục services và thêm các tệp sau để xử lý việc tìm nạp nội dung và quản lý tệp.

src/services/content.ts

Dịch vụ này sẽ tìm nạp nội dung trang web và làm sạch nó bằng cách xóa các thẻ, tập lệnh và kiểu HTML.

 import axios from "axios"; export async function getContent(url: string): Promise<string> { let htmlContent: string = ""; const response = await axios.get(url as string); htmlContent = response.data; if (!htmlContent) return ""; // Remove unwanted elements and tags return htmlContent .replace(/style="[^"]*"/gi, "") .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "") .replace(/\s*on\w+="[^"]*"/gi, "") .replace( /<script(?![^>]*application\/ld\+json)[^>]*>[\s\S]*?<\/script>/gi, "" ) .replace(/<[^>]*>/g, "") .replace(/\s+/g, " "); }

Hàm này tìm nạp nội dung HTML của một URL nhất định và dọn sạch nó bằng cách xóa kiểu, tập lệnh và thẻ HTML.

src/services/file.ts

Dịch vụ này sẽ lưu nội dung trang web vào Supabase và truy xuất các bản tóm tắt.

 import { embeddings, llm } from "@/libs/openAI"; import { supabaseClient } from "@/libs/supabaseClient"; import { SupabaseVectorStore } from "@langchain/community/vectorstores/supabase"; import { StringOutputParser } from "@langchain/core/output_parsers"; import { ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate, } from "@langchain/core/prompts"; import { RunnablePassthrough, RunnableSequence, } from "@langchain/core/runnables"; import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"; import { formatDocumentsAsString } from "langchain/util/document"; export interface IFile { id?: number | undefined; url: string; created_at?: Date | undefined; } export async function saveFile(url: string, content: string): Promise<IFile> { const doc = await supabaseClient .from("files") .select() .eq("url", url) .single<IFile>(); if (!doc.error && doc.data?.id) return doc.data; const { data, error } = await supabaseClient .from("files") .insert({ url }) .select() .single<IFile>(); if (error) throw error; const splitter = new RecursiveCharacterTextSplitter({ separators: ["\n\n", "\n", " ", ""], }); const output = await splitter.createDocuments([content]); const docs = output.map((d) => ({ ...d, metadata: { ...d.metadata, file_id: data.id }, })); await SupabaseVectorStore.fromDocuments(docs, embeddings, { client: supabaseClient, tableName: "documents", queryName: "match_documents", }); return data; } export async function getSummarization(fileId: number): Promise<string> { const vectorStore = await SupabaseVectorStore.fromExistingIndex(embeddings, { client: supabaseClient, tableName: "documents", queryName: "match_documents", }); const retriever = vectorStore.asRetriever({ filter: (rpc) => rpc.filter("metadata->>file_id", "eq", fileId), k: 2, }); const SYSTEM_TEMPLATE = `Use the following pieces of context, explain what is it about and summarize it. If you can't explain it, just say that you don't know, don't try to make up some explanation. ---------------- {context}`; const messages = [ SystemMessagePromptTemplate.fromTemplate(SYSTEM_TEMPLATE), HumanMessagePromptTemplate.fromTemplate("{format_answer}"), ]; const prompt = ChatPromptTemplate.fromMessages(messages); const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), format_answer: new RunnablePassthrough(), }, prompt, llm, new StringOutputParser(), ]); const format_summarization = ` Give it title, subject, description, and the conclusion of the context in this format, replace the brackets with the actual content: [Write the title here] By: [Name of the author or owner or user or publisher or writer or reporter if possible, otherwise leave it "Not Specified"] [Write the subject, it could be a long text, at least minimum of 300 characters] ---------------- [Write the description in here, it could be a long text, at least minimum of 1000 characters] Conclusion: [Write the conclusion in here, it could be a long text, at least minimum of 500 characters] `; const summarization = await chain.invoke(format_summarization); return summarization; }
  • saveFile : Lưu tệp và nội dung của nó vào Supabase, chia nội dung thành các phần có thể quản lý được và lưu trữ chúng trong kho lưu trữ vectơ.


  • getSummarization : Truy xuất các tài liệu có liên quan từ kho lưu trữ vectơ và tạo bản tóm tắt bằng OpenAI.

Bước 6: Tạo Trình xử lý API

Bây giờ, hãy tạo một trình xử lý API để xử lý nội dung và tạo bản tóm tắt.

pages/api/content.ts

 import { getContent } from "@/services/content"; import { getSummarization, saveFile } from "@/services/file"; import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { if (req.method !== "POST") return res.status(404).json({ message: "Not found" }); const { body } = req; try { const content = await getContent(body.url); const file = await saveFile(body.url, content); const result = await getSummarization(file.id as number); res.status(200).json({ result }); } catch (err) { res.status( 500).json({ error: err }); } }

Trình xử lý API này nhận được một URL, tìm nạp nội dung, lưu nó vào Supabase và tạo một bản tóm tắt. Nó xử lý cả hàm saveFilegetSummarization từ các dịch vụ của chúng tôi.


Bước 7: Xây dựng giao diện người dùng

Cuối cùng, hãy tạo giao diện người dùng trong src/pages/index.tsx để cho phép người dùng nhập URL và hiển thị phần tóm tắt.

src/pages/index.tsx

 import axios from "axios"; import { useState } from "react"; import { Alert, Box, Button, Container, LinearProgress, Stack, TextField, Typography, } from "@mui/material"; export default function Home() { const [loading, setLoading] = useState(false); const [url, setUrl] = useState(""); const [result, setResult] = useState(""); const [error, setError] = useState<any>(null); const onSubmit = async () => { try { setError(null); setLoading(true); const res = await axios.post("/api/content", { url }); setResult(res.data.result); } catch (err) { console.error("Failed to fetch content", err); setError(err as any); } finally { setLoading(false); } }; return ( <Box sx={{ height: "100vh", overflowY: "auto" }}> <Container sx={{ backgroundColor: (theme) => theme.palette.background.default, position: "sticky", top: 0, zIndex: 2, py: 2, }} > <Typography sx={{ mb: 2, fontSize: "24px" }}> Summarize the content of any page </Typography> <TextField fullWidth label="Input page's URL" value={url} onChange={(e) => { if (result) setResult(""); setUrl(e.target.value); }} sx={{ mb: 2 }} /> <Button disabled={loading} variant="contained" onClick={onSubmit} > Summarize </Button> </Container> <Container maxWidth="lg" sx={{ py: 2 }}> {loading ? ( <LinearProgress /> ) : ( <Stack sx={{ gap: 2 }}> {result && ( <Alert> <Typography sx={{ whiteSpace: "pre-line", wordBreak: "break-word", }} > {result} </Typography> </Alert> )} {error && <Alert severity="error">{error.message || error}</Alert>} </Stack> )} </Container> </Box> ); }

Thành phần React này cho phép người dùng nhập URL, gửi nó và hiển thị bản tóm tắt đã tạo. Nó xử lý trạng thái tải và thông báo lỗi để cung cấp trải nghiệm người dùng tốt hơn.


Bước 8: Chạy ứng dụng

Tạo tệp .env trong thư mục gốc của dự án để lưu trữ các biến môi trường của bạn:

 SUPABASE_URL=your-supabase-url SUPABASE_ANON_KEY=your-supabase-anon-key OPENAI_API_KEY=your-openai-api-key


Cuối cùng, khởi động ứng dụng Next.js của bạn:

 npm run dev


Bây giờ, bạn phải có một ứng dụng đang chạy nơi bạn có thể nhập URL của trang web và nhận phản hồi tóm tắt của trang đó.


Phần kết luận

Chúc mừng! Bạn đã xây dựng một ứng dụng tóm tắt trang web đầy đủ chức năng bằng Next.js, OpenAI, LangChain và Supabase. Người dùng có thể nhập URL, tìm nạp nội dung, lưu trữ trong Supabase và tạo bản tóm tắt bằng khả năng của OpenAI. Thiết lập này cung cấp nền tảng vững chắc để cải tiến và tùy chỉnh hơn nữa dựa trên nhu cầu của bạn.


Vui lòng mở rộng dự án này bằng cách thêm nhiều tính năng hơn, cải thiện giao diện người dùng hoặc tích hợp các API bổ sung.

Kiểm tra mã nguồn trong Repo này:

https://github.com/firstpersoncode/summarize-page


Chúc mừng mã hóa!