[Перевод] Создание собственного графического клиента ChatGPT с помощью NextJS и Wing

P. S:

К концу этой статьи вы создадите и развернете клиент ChatGPT с помощью Wing и Next.js.

Это приложение может запускаться локально (в локальном облачном симуляторе) или развертываться у вашего собственного облачного провайдера.

5bcdc2242fb2d274c4452a009b0d3a46.gif

Введение

Создание клиента ChatGPT и его развертывание в вашей собственной облачной инфраструктуре — это хороший способ обеспечить контроль над вашими данными.

Развертывание LLM в собственной облачной инфраструктуре обеспечивает конфиденциальность и безопасность вашего проекта.

Иногда при использовании проприетарных платформ LLM, таких как ChatGPT от OpenAI, у вас могут возникнуть опасения, что ваши данные будут храниться или обрабатываться на удаленных серверах, либо из-за чувствительности данных, поступающих в платформу, либо по другим причинам, связанным с конфиденциальностью.

В этом случае самостоятельное размещение LLM в вашей облачной инфраструктуре или его локальный запуск на вашем компьютере дает вам больше контроля над конфиденциальностью и безопасностью ваших данных.

Wing — это язык программирования, ориентированный на облако, который позволяет создавать и развертывать облачные приложения, не заботясь о базовой инфраструктуре.Он упрощает процесс создания облачных приложений, позволяя определять и управлять облачной инфраструктурой и кодом приложения в рамках одного языка.Wing не зависит от облака — приложения, созданные с его помощью, могут быть скомпилированы и развернуты на различных облачных платформах.

6ae638e12eb877216f68462eb9923393.jpg

Давайте начнем!

Для этого вам необходимо:

  • Иметь некоторое представление о Next.js

  • Установить Wing на свою машину. Не волнуйтесь, если вы не знаете, как это сделать. Мы разберемся с этим вместе в этом проекте.

  • Получить ключ API OpenAI.

Создайте свой проект

Чтобы начать работу, вам нужно установить Wing на свою машину. Выполните следующую команду:

npm install -g winglang

Подтвердите установку, проверив версию:

wing -V

Создайте свои приложения Next.js и Wing.

mkdir assistant
cd assistant
npx create-next-app@latest frontend
mkdir backend && cd backend
wing new empty

Мы успешно создали наши проекты Wing и Next.js в директории Assistant. Имя нашего клиента ChatGPT — Assistant. Звучит круто, правда?

Каталоги frontend и backend содержат наши приложения Next и Wing соответственно. wing new empty создает три файла: package.json, package-lock.json и main.w. Последний является точкой входа приложения.

Запустите свое приложение локально в симуляторе Wing

Симулятор Wing позволяет запускать код, писать модульные тесты и отлаживать его на локальной машине без необходимости развертывания у реального облачного провайдера, что помогает быстрее выполнять итерации.

Чтобы запустить приложение Wing локально, выполните следующую команду:

wing it

Ваше приложение Wing будет работать на localhost:3000.

c8d9b43c3022467a1caf8dcb6bdac61b.jpg

Настройка бэкенда

Давайте установим библиотеки Wing OpenAI и React. Библиотека OpenAI предоставляет стандартный интерфейс для взаимодействия с LLM. Библиотека React позволяет подключить бэкенд Wing к вашему приложению Next.

npm i @winglibs/openai @winglibs/react

Импортируйте эти пакеты в файл main.w. Также импортируем все остальные библиотеки, которые нам понадобятся.

bring openai
bring react
bring cloud
bring ex
bring http

bring — это оператор импорта в Wing. Подумайте об этом так: Wing использует bring для достижения той же функциональности, что и import в JavaScript.

cloud — это библиотека облачных вычислений Wing. Она предоставляет стандартный интерфейс для Cloud API, Bucket, Counter, Domain, Endpoint, Function и многих других облачных ресурсов. ex — стандартная библиотека для взаимодействия с таблицами и облачной базой данных Redis, а http — для вызова различных методов HTTP — отправки и получения информации с удаленных ресурсов.

Получите ключ API OpenAI

Мы будем использовать gpt-4-turbo для нашего приложения, но вы можете использовать любую модель OpenAI.

  • Создайте аккаунт OpenAI, если у вас его еще нет. Чтобы создать новый API-ключ, перейдите на сайт platform.openai.com/api-keys и выберите Create new secret key.

433c3bdabd4255ec134bcc0883a9a0c0.jpg

  • Установите Имя (Name), Проект (Project) и Разрешения (Permissions), затем нажмите Create secret key.

a50980a0874650af8697e54d9f6f9c7c.jpg

Инициализация OpenAI

Создайте класс для инициализации API OpenAI. Мы хотим, чтобы он был многоразовым.

Мы добавим personality в наш класс Assistant, чтобы мы могли диктовать личность нашего ИИ-ассистента при передаче ему подсказки.

let apiKeySecret = new cloud.Secret(name: "OAIAPIKey") as "OpenAI Secret";

class Assistant {
    personality: str;
    openai: openai.OpenAI;

    new(personality: str) {
        this.openai = new openai.OpenAI(apiKeySecret: apiKeySecret);
        this.personality = personality;
    }

    pub inflight ask(question: str): str {
        let prompt = `you are an assistant with the following personality: ${this.personality}. ${question}`;
        let response = this.openai.createCompletion(prompt, model: "gpt-4-turbo");
        return response.trim();
    }
}

Wing объединяет определение инфраструктуры и логику приложения с помощью концепций preflight и inflight соответственно.

Код preflight (обычно это определения инфраструктуры) запускается один раз во время компиляции, а код inflight запускается во время выполнения для реализации поведения вашего приложения.

Облачные хранилища, очереди и конечные точки API — вот некоторые примеры предварительной подготовки. Вам не нужно добавлять ключевое слово preflight при определении preflight, Wing знает это по умолчанию. Но для блока с inflight режимом необходимо добавить слово »inflight».

В приведенном выше коде у нас есть блок inflight. Блоки inflight — это место, где вы пишете асинхронный код времени выполнения, который может напрямую взаимодействовать с ресурсами через их inflight API.

Проверка и хранение облачного ключа.

Давайте рассмотрим, как мы будем защищать наши API-ключи, потому что мы определенно хотим принять во внимание безопасность.

Давайте создадим файл .env в корневом каталоге нашего бэкенда и передадим в него наш API-ключ:

OAIAPIKey = Your_OpenAI_API_key

Мы можем проверить наши API-ключи OpenAI локально, ссылаясь на наш файл .env, а затем, поскольку мы планируем развертывание на AWS, мы пройдем через настройку AWS Secrets Manager.

c48bcd8d27b09db80e0769135bc68de3.jpg

Сначала перейдем на сайт AWS и войдем в консоль. Если у вас нет учетной записи, вы можете создать ее бесплатно.

d5c9f4c617d2c5780bccf3d415fbb310.jpg

Перейдите в Secrets Manager и сохраните значения наших API-ключей.

a15e630c597595cfdfe2c252b04d9f9e.jpg496def10bd634c76058e5ea0a07599d5.jpg

Мы сохранили наш ключ API в облаке под названием OAIAPIKey. Скопируйте ключ и мы перейдем в терминал и подключимся к нашему «секрету», который теперь хранится на платформе AWS.

wing secrets

Теперь вставьте свой API ключ в качестве значения в терминале. Теперь ваши ключи сохранены должным образом, и мы можем начать взаимодействие с нашим приложением.

Хранение ответов ИИ в облаке.

Хранение ответов вашего ИИ в облаке дает вам контроль над данными. Они хранятся в вашей собственной инфраструктуре, в отличие от проприетарных платформ, таких как ChatGPT, где ваши данные хранятся на сторонних серверах, которые вы не можете контролировать. Вы также можете получить эти ответы, когда они вам понадобятся.

Давайте создадим еще один класс, который использует класс Assistant для передачи личности и подсказок нашего ИИ. Мы также будем хранить ответы каждой модели в виде txt-файлов в облачном хранилище.

let counter = new cloud.Counter();

class RespondToQuestions {
    id: cloud.Counter;
    gpt: Assistant;
    store: cloud.Bucket;

    new(store: cloud.Bucket) {
        this.gpt = new Assistant("Respondent");
        this.id = new cloud.Counter() as "NextID";
        this.store = store;
    }

    pub inflight sendPrompt(question: str): str {
        let reply = this.gpt.ask("{question}");
        let n = this.id.inc();
        this.store.put("message-{n}.original.txt", reply);
        return reply;
    }
}

Мы дали нашему помощнику личность «Ответчик». Мы хотим, чтобы он отвечал на вопросы. Вы также можете позволить пользователю на фронтенде диктовать эту личность при отправке своих подсказок.

Каждый раз, когда он генерирует ответ, счетчик увеличивается, а значение счетчика передается в переменную n, используемую для хранения ответов модели в облаке. Однако на самом деле мы хотим создать базу данных, в которой будут храниться как пользовательские подсказки, поступающие с фронтенда, так и ответы нашей модели.

Давайте определим нашу базу данных.

Определение нашей базы данных

В Wing встроена ex.Table — база данных NoSQL для хранения и запроса данных.

let db = new ex.Table({
    name: "assistant",
    primaryKey: "id",
    columns: {
        question: ex.ColumnType.STRING,
        answer: ex.ColumnType.STRING
    }
});

Мы добавили два столбца в определение базы данных: первый — для хранения подсказок пользователя, второй — для хранения ответов модели.

Создание маршрутов и логики API

Мы хотим иметь возможность отправлять и получать данные в нашем бэкенде. Давайте создадим маршруты POST и GET.

let api = new cloud.Api({ cors: true });

api.post("/assistant", inflight((request) => {
    // POST request logic goes here
}));

api.get("/assistant", inflight(() => {
    // GET request logic goes here
}));
let myAssistant = new RespondToQuestions(store) as "Helpful Assistant";

api.post("/assistant", inflight((request) => {
    let prompt = request.body;
    let response = myAssistant.sendPrompt(JSON.stringify(prompt)); 
    let id = counter.inc(); 

    // Insert prompt and response in the database
    db.insert(id, { question: prompt, answer: response });

    return cloud.ApiResponse({
        status: 200
    });
}));

В маршруте POST мы хотим передать в модель запрос пользователя, полученный от фронтенда, и получить ответ. И запрос, и ответ будут храниться в базе данных. cloud.ApiResponse позволяет отправлять ответ на запрос пользователя.

Добавьте логику для получения элементов базы данных, когда фронтенд делает GET-запрос.

api.get("/assistant", inflight(() => {
    let questionsAndAnswers = db.list();

    return cloud.ApiResponse({
        body: JSON.stringify(questionsAndAnswers),
        status: 200
    });
}));

Наш бэкэнд готов. Давайте протестируем его в локальном облачном симуляторе.

Запустите wing it.

Перейдем на localhost:3000 и зададим вопрос нашему помощнику.

98d1da275be2a7f5f74ba33e84104e33.jpg

Наш вопрос и ответ помощника были сохранены в базе данных. Проверим.

a75ddf7a83b2305e12f5b44dab8c1d7e.jpg

Выставление URL вашего API для фронтенда

Нам нужно передать API URL нашего бэкенда фронтенду Next. Здесь нам и пригодится установленная ранее библиотека React.

let website = new react.App({
    projectPath: "../frontend",
    localPort: 4000
});

website.addEnvironment("API_URL", api.url);

Добавьте следующее в layout.js вашего приложения Next.

import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
    title: "Create Next App",
    description: "Generated by create next app",
};

export default function RootLayout({ children }) {
    return (
        
            
            
           
            {children}










        
    );
}

Теперь у нас есть доступ к API_URL в нашем приложении Next.

Реализация логики фронтенда

Давайте реализуем логику фронтенда для вызова нашего бэкенда.

import { useEffect, useState, useCallback } from 'react';
import axios from 'axios';

function App() {

    const [isThinking, setIsThinking] = useState(false);
    const [input, setInput] = useState("");
    const [allInteractions, setAllInteractions] = useState([]);

    const retrieveAllInteractions = useCallback(async (api_url) => {
            await axios ({
              method: "GET",
              url: `${api_url}/assistant`,
            }).then(res => {
              setAllInteractions(res.data)
            })
  }, [])

    const handleSubmit = useCallback(async (e)=> {
        e.preventDefault()

        setIsThinking(!isThinking)


        if(input.trim() === ""){
          alert("Chat cannot be empty")
          setIsThinking(true)

        }

          await axios({
            method: "POST",
            url: `${window.wingEnv.API_URL}/assistant`,
            headers: {
              "Content-Type": "application/json"
            },
            data: input
          })
          setInput("");
          setIsThinking(false);
          await retrieveAllInteractions(window.wingEnv.API_URL);     

  })

    useEffect(() => {
        if (typeof window !== "undefined") {
            retrieveAllInteractions(window.wingEnv.API_URL);
        }
    }, []);

    // Here you would return your component's JSX
    return (
        // JSX content goes here
    );
}

export default App;

Функция retrieveAllInteractions извлекает все вопросы и ответы из базы данных бэкенда. Функция handSubmit отправляет запрос пользователя в бэкенд.

Добавим реализацию JSX.

import { useEffect, useState } from 'react';
import axios from 'axios';
import './App.css';

function App() {
    // ...

    return (
        

My Assistant

Ask anything...

{allInteractions.map((chat) => (

{chat.question}

{chat.answer}

))}

Generating response...

setInput(e.target.value)} />
); } export default App;

Запуск проекта локально

Перейдите в каталог бэкенда и запустите приложение Wing локально с помощью следующей команды

cd ~assistant/backend
wing it

Также запустите фронтенд Next.js:

cd ~assistant/frontend
npm run dev

Давайте посмотрим на наше приложение.

f6133684c614e5683284198d42b2b305.jpg

Давайте зададим нашему ИИ-помощнику пару вопросов от разработчиков из нашего приложения Next App.

17b2dafca4c467ad74b705f05a99e738.jpg

Развертывание приложения на AWS

Мы видели, как наше приложение может работать локально. Wing также позволяет развертывать приложение на любом облачном провайдере, включая AWS. Для развертывания на AWS вам понадобятся Terraform и AWS CLI, сконфигурированные с вашими учетными данными.

  • Скомпилируйте в Terraform/AWS с помощью команды tf-aws. Эта команда указывает компилятору использовать Terraform в качестве механизма инициализации, чтобы привязать все наши ресурсы к набору ресурсов AWS по умолчанию.

cd ~/assistant/backend
wing compile --platform tf-aws main.w
cd ./target/main.tfaws
terraform init
terraform apply

Примечание: terraform apply занимает некоторое время.

Полный код этого урока вы можете найти здесь.

Завершение

Как я уже говорил, мы все должны заботиться о безопасности наших приложений. Создание собственного клиента ChatGPT и развертывание его в облачной инфраструктуре дает вашему приложению очень хорошие гарантии.

В этом руководстве мы показали, как Wing обеспечивает простой подход к созданию масштабируемых облачных приложений, не беспокоясь о базовой инфраструктуре.

Если вы заинтересованы в создании более крутых вещей, Wing имеет активное сообщество разработчиков, которые совместно строят видение облака. Мы будем рады видеть вас там.

Если вас заинтересовала статья, приглашаем в наш телеграм-канал WebWeavers, в котором мы изучаем frontend и дизайн. В нём мы выкладываем интересные и поучительные посты.

Habrahabr.ru прочитано 13952 раза