Блог / Статьи

Полезная информация для вашего хостинга

Как создать персонализированного ИИ-ассистента с использованием OpenAI

Как создать персонализированного ИИ-ассистента с использованием OpenAI

Представьте, что у вас есть собственный виртуальный помощник, наподобие J.A.R.V.I.S. из фильма «Железный человек», но настроенный под ваши нужды. Этот ИИ-ассистент предназначен для выполнения рутинных задач или любых других, которым вы его обучите.

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

Чтобы создать это, мы будем использовать три основных технологии: OpenAI, LangChain и Next.js.

OpenAI

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

Чтобы получить ваш API-ключ, зарегистрируйтесь на платформе OpenAI. После регистрации вы можете создать ключ в разделе API keys на панели управления.

Как только вы сгенерировали API-ключ, вам нужно добавить его в качестве переменной окружения на вашем компьютере с именем `OPENAI_API_KEY`. Это стандартное имя, которое используют такие библиотеки, как OpenAI и LangChain, поэтому вам не нужно передавать его вручную позже.

Обратите внимание, что Windows, macOS и Linux имеют свои способы установки переменных окружения.

Windows
1. Щелкните правой кнопкой мыши на «Этот компьютер» или «Мой компьютер» и выберите «Свойства».
2. Нажмите «Дополнительные параметры системы» в левом меню.
3. В окне «Свойства системы» нажмите кнопку «Переменные среды».
4. В разделе «Системные переменные» или «Пользовательские переменные» нажмите «Создать» и введите имя `OPENAI_API_KEY` и значение переменной окружения.

macOS и Linux
Чтобы задать постоянную переменную, добавьте следующую строку в ваш конфигурационный файл оболочки, например `~/.bash_profile`, `~/.bashrc` или `~/.zshrc`: 

export OPENAI_API_KEY=value

 LangChain

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

Вы можете спросить: зачем нам это нужно?

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

Этот процесс называется встраиванием (embedding).

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

personal2

Пример: если пользователь задаёт запрос о «дорогих автомобилях», вместо того чтобы искать точные слова, ИИ поймёт, что вы имеете в виду Ferrari, Maserati, Aston Martin, Mercedes Benz и так далее.

Next.js

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

В нашем случае Next.js имеет всё необходимое, чтобы запустить чат-бот для конечных пользователей. Мы создадим интерфейс, используя библиотеку UI React.js — `shadcn/ui`. Она имеет систему маршрутов для создания API-эндпоинта.

personal3

Данные и другие предпосылки

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

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

Я предполагаю, что вы знакомы с JavaScript, React.js и NPM, потому что мы будем использовать их для создания чат-бота.

Также убедитесь, что на вашем компьютере установлен Node.js. Проверьте это командой: 

node -v

 Если Node.js не установлен, следуйте инструкциям на официальном сайте.

Как всё будет работать

Чтобы упростить понимание, вот общий обзор процесса:

1. Пользователь вводит вопрос или запрос в чат-бот.
2. LangChain извлекает связанные документы по запросу пользователя.
3. Запрос, документы и подсказка отправляются в OpenAI API для получения ответа.
4. Ответ отображается пользователю.

Теперь, когда у нас есть общий обзор, давайте начнём!

Установка зависимостей

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

npx create-next-app@latest ai-assistant --typescript --tailwind --eslint

 Эта команда установит Next.js с `shadcn/ui`, TypeScript, Tailwind CSS и ESLint. Она может задать вам несколько вопросов; лучше выбрать стандартные настройки.

После завершения установки перейдите в каталог проекта: 

cd ai-assistant

 Затем установите дополнительные зависимости, такие как `ai`, `openai`, и `langchain`: 

npm i ai openai langchain @langchain/openai remark-gfm

 Создание интерфейса чата

Для создания интерфейса чата мы будем использовать готовые компоненты из `shadcn/ui`, такие как кнопка, аватар и поле ввода. Чтобы добавить эти компоненты, введите: 

npx shadcn-ui@latest add scroll-area button avatar card input

 Создайте новый файл `Chat.tsx` в директории `src/components`. Код для этого файла представлен в статье. 

'use client';

import { Avatar, AvatarFallback, AvatarImage } from '@/ui/avatar';
import { Button } from '@/ui/button';
import {
    Card,
    CardContent,
    CardFooter,
    CardHeader,
    CardTitle,
} from '@/ui/card';
import { Input } from '@/ui/input';
import { ScrollArea } from '@/ui/scroll-area';
import { useChat } from 'ai/react';
import { Send } from 'lucide-react';
import { FunctionComponent, memo } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import ReactMarkdown, { Options } from 'react-markdown';
import remarkGfm from 'remark-gfm';

/**
 * Memoized ReactMarkdown component.
 * The component is memoized to prevent unnecessary re-renders.
 */
const MemoizedReactMarkdown: FunctionComponent<Options> = memo(
    ReactMarkdown,
    (prevProps, nextProps) =>
        prevProps.children === nextProps.children &&
        prevProps.className === nextProps.className
);

/**
 * Represents a chat component that allows users to interact with a chatbot.
 * The component displays a chat interface with messages exchanged between the user and the chatbot.
 * Users can input their questions and receive responses from the chatbot.
 */
export const Chat = () => {
    const { handleInputChange, handleSubmit, input, messages } = useChat({
        api: '/api/chat',
    });

    return (
        <Card className="w-full max-w-3xl min-h-[640px] grid gap-3 grid-rows-[max-content,1fr,max-content]">
            <CardHeader className="row-span-1">
                <CardTitle>AI Assistant</CardTitle>
            </CardHeader>
            <CardContent className="h-full row-span-2">
                <ScrollArea className="h-full w-full">
                    {messages.map((message) => {
                        return (
                            <div
                                className="flex gap-3 text-slate-600 text-sm mb-4"
                                key={message.id}
                            >
                                {message.role === 'user' && (
                                    <Avatar>
                                        <AvatarFallback>U</AvatarFallback>
                                        <AvatarImage src="/user.png" />
                                    </Avatar>
                                )}
                                {message.role === 'assistant' && (
                                    <Avatar>
                                        <AvatarImage src="/kovi.png" />
                                    </Avatar>
                                )}
                                <p className="leading-relaxed">
                                    <span className="block font-bold text-slate-700">
                                        {message.role === 'user' ? 'User' : 'AI'}
                                    </span>
                                    <ErrorBoundary
                                        fallback={
                                            <div className="prose prose-neutral">
                                                {message.content}
                                            </div>
                                        }
                                    >
                                        <MemoizedReactMarkdown
                                            className="prose prose-neutral prose-sm"
                                            remarkPlugins={[remarkGfm]}
                                        >
                                            {message.content}
                                        </MemoizedReactMarkdown>
                                    </ErrorBoundary>
                                </p>
                            </div>
                        );
                    })}
                </ScrollArea>
            </CardContent>
            <CardFooter className="h-max row-span-3">
                <form className="w-full flex gap-2" onSubmit={handleSubmit}>
                    <Input
                        maxLength={1000}
                        onChange={handleInputChange}
                        placeholder="Your question..."
                        value={input}
                    />
                    <Button aria-label="Send" type="submit">
                        <Send size={16} />
                    </Button>
                </form>
            </CardFooter>
        </Card>
    );
};

 Для запуска интерфейса используйте команду: 

npm run dev

По умолчанию среда Next.js localhost работает по адресу localhost:3000. Вот как будет выглядеть интерфейс нашего чат-бота в браузере.:

personal4

Настройка API-эндпоинта

Теперь создайте API-эндпоинт, который будет использовать интерфейс. Для этого создайте файл `route.ts` в директории `src/app/api/chat`. Код для него также представлен в статье. 

import { readData } from '@/lib/data';
import { OpenAIEmbeddings } from '@langchain/openai';
import { OpenAIStream, StreamingTextResponse } from 'ai';
import { Document } from 'langchain/document';
import { MemoryVectorStore } from 'langchain/vectorstores/memory';
import OpenAI from 'openai';

/**
    * Create a vector store from a list of documents using OpenAI embedding.
    */
const createStore = () => {
    const data = readData();

    return MemoryVectorStore.fromDocuments(
        data.map((title) => {
            return new Document({
                pageContent: `Title: ${title}`,
            });
        }),
        new OpenAIEmbeddings()
    );
};
const openai = new OpenAI();

export async function POST(req: Request) {
    const { messages } = (await req.json()) as {
        messages: { content: string; role: 'assistant' | 'user' }[];
    };
    const store = await createStore();
    const results = await store.similaritySearch(messages[0].content, 100);
    const questions = messages
        .filter((m) => m.role === 'user')
        .map((m) => m.content);
    const latestQuestion = questions[questions.length - 1] || '';
    const response = await openai.chat.completions.create({
        messages: [
            {
                content: `You're a helpful assistant. You're here to help me with my questions.`,
                role: 'assistant',
            },
            {
                content: `
                Please answer the following question using the provided context.
                If the context is not provided, please simply say that you're not able to answer
                the question.

            Question:
                ${latestQuestion}

            Context:
                ${results.map((r) => r.pageContent).join('\n')}
                `,
                role: 'user',
            },
        ],
        model: 'gpt-4',
        stream: true,
        temperature: 0,
    });
    const stream = OpenAIStream(response);

    return new StreamingTextResponse(stream);
}

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

Во-первых, следующий код позволяет конечной точке получать запрос POST. Он принимает аргумент messages, который автоматически создается пакетом искусственного интеллекта, запущенным на интерфейсе. 

export async function POST(req: Request) {
    const { messages } = (await req.json()) as {
        messages: { content: string; role: 'assistant' | 'user' }[];
    };
}

  В этом разделе кода мы обрабатываем JSON-файл и сохраняем его в векторном хранилище. 

const createStore = () => {
    const data = readData();

    return MemoryVectorStore.fromDocuments(
        data.map((title) => {
            return new Document({
                pageContent: `Title: ${title}`,
            });
        }),
        new OpenAIEmbeddings()
    );
};

Для простоты в этом руководстве мы сохраняем вектор в памяти. В идеале вам нужно было бы сохранить его в векторной базе данных. Есть несколько вариантов на выбор, таких как:

Apache Cassandra
ElasticSearch
MongoDB Atlas
OpenSearch
Redis
Затем мы извлекаем соответствующий фрагмент из документа на основе запроса пользователя к нему. 

const store = await createStore();
const results = await store.similaritySearch(messages[0].content, 100);

  Наконец, мы отправляем запрос пользователя и связанные с ним документы в OpenAI API, чтобы получить ответ, а затем возвращаем его пользователю. В этом руководстве мы используем модель GPT-4, которая на данный момент является последней и самой мощной моделью в Opensim.

 

const latestQuestion = questions[questions.length - 1] || '';
const response = await openai.chat.completions.create({
    messages: [
        {
            content: `You're a helpful assistant. You're here to help me with my questions.`,
            role: 'assistant',
        },
        {
            content: `
            Please answer the following question using the provided context.
            If the context is not provided, please simply say that you're not able to answer
            the question.

        Question:
            ${latestQuestion}

        Context:
            ${results.map((r) => r.pageContent).join('\n')}
            `,
            role: 'user',
        },
    ],
    model: 'gpt-4',
    stream: true,
    temperature: 0,
});

Мы используем простую инструкцию. Сначала мы просим OpenAI оценить запрос пользователя и ответить на него в соответствии с предоставленным контекстом. Мы также устанавливаем последнюю модель, доступную в OpenAI, gpt-4, и устанавливаем температуру на 0. Наша цель - обеспечить, чтобы ИИ реагировал только в рамках контекста, а не проявлял творческий подход, который часто может привести к галлюцинациям.

И это все. Теперь мы можем попробовать пообщаться с чат-ботом, нашим виртуальным персональным помощником.

 

Заключение

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