Представьте, что у вас есть собственный виртуальный помощник, наподобие 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).
Он упрощает компьютерам анализ и поиск закономерностей в языковых данных, а также помогает понимать семантику информации, которую они получают из человеческого языка.
Пример: если пользователь задаёт запрос о «дорогих автомобилях», вместо того чтобы искать точные слова, ИИ поймёт, что вы имеете в виду Ferrari, Maserati, Aston Martin, Mercedes Benz и так далее.
Next.js
Нам нужен фреймворк для создания пользовательского интерфейса, чтобы пользователи могли взаимодействовать с нашим чат-ботом.
В нашем случае Next.js имеет всё необходимое, чтобы запустить чат-бот для конечных пользователей. Мы создадим интерфейс, используя библиотеку UI React.js — `shadcn/ui`. Она имеет систему маршрутов для создания API-эндпоинта.
Данные и другие предпосылки
Идеально, если у нас уже есть подготовленные данные. Они будут обработаны, сохранены в векторное хранилище и отправлены в 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. Вот как будет выглядеть интерфейс нашего чат-бота в браузере.:
Настройка 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. Наша цель - обеспечить, чтобы ИИ реагировал только в рамках контекста, а не проявлял творческий подход, который часто может привести к галлюцинациям.
И это все. Теперь мы можем попробовать пообщаться с чат-ботом, нашим виртуальным персональным помощником.
Заключение
Мы только что создали простой чат-бот! Конечно, есть возможности для его улучшения. Если вы планируете использовать его в продакшене, храните векторные данные в подходящей базе данных вместо памяти. Также можно добавить больше данных и настроить подсказки для улучшения ответов ИИ.