В этой статье мы покажем вам, как создать удобное веб-приложение, которое может суммировать содержимое любой веб-страницы. Используя Next.js для бесперебойной и быстрой работы в Интернете, LangChain для обработки языка, OpenAI для создания сводок и Supabase для управления и хранения векторных данных, мы вместе создадим мощный инструмент.
Мы все сталкиваемся с информационной перегрузкой из-за большого количества контента в Интернете. Создавая приложение, предоставляющее быстрые сводки, мы помогаем людям экономить время и оставаться в курсе событий. Независимо от того, являетесь ли вы занятым работником, студентом или просто человеком, который хочет быть в курсе новостей и статей, это приложение станет для вас полезным инструментом.
Наше приложение позволит пользователям вводить любой URL-адрес веб-сайта и быстро получать краткую информацию о странице. Это означает, что вы можете понять основные моменты длинных статей, сообщений в блогах или исследовательских работ, не читая их полностью.
Это приложение для обобщения может быть полезным во многих отношениях. Это может помочь исследователям просматривать научные статьи, держать любителей новостей в курсе и многое другое. Кроме того, разработчики могут использовать это приложение для создания еще более полезных функций.
Next.js — это мощная и гибкая среда React, разработанная Vercel, которая позволяет разработчикам с легкостью создавать серверный рендеринг (SSR) и статические веб-приложения. Он сочетает в себе лучшие функции React с дополнительными возможностями для создания оптимизированных и масштабируемых веб-приложений.
Модуль OpenAI в Node.js предоставляет способ взаимодействия с API OpenAI, позволяя разработчикам использовать мощные языковые модели, такие как GPT-3 и GPT-4. Этот модуль позволяет вам интегрировать расширенные функции искусственного интеллекта в ваши приложения Node.js.
LangChain — мощная платформа, предназначенная для разработки приложений с языковыми моделями. Первоначально разработанный для Python, с тех пор он был адаптирован для других языков, включая Node.js. Вот обзор LangChain в контексте Node.js:
LangChain — библиотека, упрощающая создание приложений с использованием больших языковых моделей (LLM) . Он предоставляет инструменты для управления LLM и их интеграции в ваши приложения, обработки цепочки вызовов этих моделей и с легкостью обеспечивает сложные рабочие процессы.
Модели больших языков (LLM), такие как GPT-3.5 от OpenAI, обучаются на огромных объемах текстовых данных, чтобы понимать и генерировать текст, похожий на человеческий. Они могут генерировать ответы, переводить языки и выполнять множество других задач по обработке естественного языка.
Supabase — это платформа BaaS с открытым исходным кодом, предназначенная для того, чтобы помочь разработчикам быстро создавать и развертывать масштабируемые приложения. Он предлагает набор инструментов и сервисов, которые упрощают управление базами данных, аутентификацию, хранение и возможности работы в реальном времени, и все это построено на базе PostgreSQL.
Прежде чем мы начнем, убедитесь, что у вас есть следующее:
Сначала нам нужно настроить проект Supabase и создать необходимые таблицы для хранения наших данных.
Перейдите на Supabase и зарегистрируйте учетную запись.
Создайте новый проект и запишите URL-адрес Supabase и ключ API. Они понадобятся вам позже.
Создайте новый SQL-запрос на панели управления Supabase и запустите следующие сценарии для создания необходимых таблиц и функций:
Сначала создайте расширение для нашего векторного хранилища, если оно еще не существует:
create extension if not exists vector;
Затем создайте таблицу с именем «документы». Эта таблица будет использоваться для хранения и встраивания содержимого веб-страницы в векторном формате:
create table if not exists documents ( id bigint primary key generated always as identity, content text, metadata jsonb, embedding vector(1536) );
Теперь нам нужна функция для запроса наших встроенных данных:
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; $$;
Далее нам нужно настроить таблицу для хранения сведений о веб-странице:
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 );
$ npx create-next-app summarize-page $ cd ./summarize-page
Установите необходимые зависимости:
npm install @langchain/community @langchain/core @langchain/openai @supabase/supabase-js langchain openai axios
Затем мы установим Material UI для создания нашего интерфейса; не стесняйтесь использовать другую библиотеку:
npm install @mui/material @emotion/react @emotion/styled
Далее нам нужно настроить клиенты OpenAI и Supabase. Создайте каталог libs
в своем проекте и добавьте следующие файлы.
src/libs/openAI.ts
Этот файл будет настраивать клиент 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
: экземпляр языковой модели, который будет генерировать наши сводки.
embeddings
: для наших документов будут созданы вложения, которые помогут находить похожий контент.src/libs/supabaseClient.ts
Этот файл будет настраивать клиент 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
: экземпляр клиента Supabase для взаимодействия с нашей базой данных Supabase. Создайте каталог services
и добавьте следующие файлы для обработки получения содержимого и управления файлами.
src/services/content.ts
Эта служба получит содержимое веб-страницы и очистит его, удалив 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, " "); }
Эта функция извлекает HTML-содержимое заданного URL-адреса и очищает его, удаляя стили, сценарии и HTML-теги.
src/services/file.ts
Этот сервис сохранит содержимое веб-страницы в Supabase и получит сводки.
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
: сохраняет файл и его содержимое в Supabase, разбивает содержимое на управляемые фрагменты и сохраняет их в векторном хранилище.
getSummarization
: извлекает соответствующие документы из векторного хранилища и генерирует сводку с помощью OpenAI.Теперь давайте создадим обработчик API для обработки контента и создания сводки.
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 }); } }
Этот обработчик API получает URL-адрес, извлекает контент, сохраняет его в Supabase и генерирует сводку. Он обрабатывает функции saveFile
и getSummarization
из наших сервисов.
Наконец, давайте создадим интерфейс в src/pages/index.tsx
чтобы пользователи могли вводить URL-адреса и отображать сводные данные.
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> ); }
Этот компонент React позволяет пользователям вводить URL-адрес, отправлять его и отображать сгенерированную сводку. Он обрабатывает состояния загрузки и сообщения об ошибках, чтобы обеспечить лучшее взаимодействие с пользователем.
Создайте файл .env в корне вашего проекта для хранения переменных среды:
SUPABASE_URL=your-supabase-url SUPABASE_ANON_KEY=your-supabase-anon-key OPENAI_API_KEY=your-openai-api-key
Наконец, запустите приложение Next.js:
npm run dev
Теперь у вас должно быть работающее приложение, в котором вы можете ввести URL-адрес веб-страницы и получить сводные ответы страницы.
Поздравляем! Вы создали полнофункциональное приложение для обобщения веб-страниц, используя Next.js, OpenAI, LangChain и Supabase. Пользователи могут ввести URL-адрес, получить контент, сохранить его в Supabase и создать сводку, используя возможности OpenAI. Эта настройка обеспечивает надежную основу для дальнейших улучшений и настройки в соответствии с вашими потребностями.
Не стесняйтесь расширять этот проект, добавляя дополнительные функции, улучшая пользовательский интерфейс или интегрируя дополнительные API.
https://github.com/firstpersoncode/summarize-page
Приятного кодирования!