Современные большие языковые модели обладают колоссальным объемом знаний, однако их главным ограничением остается фиксированная дата отсечки обучающих данных. Разработчик и создатель курсов Аня Кубо в подробном практическом руководстве на канале freeCodeCamp.org демонстрирует, как обойти эту проблему с помощью технологии RAG (Retrieval-Augmented Generation). В рамках проекта подробно разбирается процесс создания и развертывания умного чат-бота для фанатов «Формулы-1», способного отвечать на сложные вопросы на основе актуальной информации, полученной из интернета в режиме реального времени.
💡 Концепция RAG и решение проблемы устаревания данных LLM 4:00
Retrieval-Augmented Generation (поиск с добавлением генерации), или RAG, представляет собой метод изменения выходных данных большой языковой модели (LLM) путем предоставления ей дополнительного контекста вместе с запросом пользователя. Благодаря этому подходу модель сочетает свою встроенную способность генерировать связный текст с внешними точными фактами.
По мнению Ани Кубо, использование RAG является крайне выгодным решением по трем ключевым причинам:
- Экономическая эффективность: предобученные базовые модели обучаются на гигантских массивах данных с использованием миллиардов параметров. Полноценное переобучение или тонкая настройка (fine-tuning) нейросети под нужды конкретной компании требуют огромных вычислительных мощностей и колоссальных финансовых затрат, тогда как RAG решает эту задачу с минимальными вложениями.
- Преодоление даты отсечки знаний: популярные коммерческие модели имеют строгие временные лимиты на сбор обучающих данных. Например, базовая модель GPT-4 на момент записи материала обладала информацией только до сентября 2021 года. Без RAG на вопрос о действующем чемпионе «Формулы-1» модель назовет Льюиса Хэмилтона, но после подключения веб-скрейпинга свежих статей из Википедии она выдаст правильный актуальный ответ — Макс Ферстаппен.
- Интеграция приватных данных: технология позволяет безопасно дополнять контекст модели закрытой информацией, которой вообще никогда не было в открытом интернете, будь то корпоративная база знаний или страницы личного дневника разработчика.
Дополнительным преимуществом архитектуры RAG Кубо называет существенную экономию API-токенов OpenAI. Вместо отправки длинных неструктурированных документов в контекстное окно ИИ-провайдера система извлекает из векторной базы данных только самые релевантные фрагменты, что минимизирует операционные издержки.
🛠️ Предварительные требования и стек технологий 6:16
Для успешной сборки и запуска демонстрационного чат-бота требуется минимальный набор инструментов, большинство из которых доступны в рамках бесплатных тарифов.
Базовые системные требования включают в себя:
- Свежую стабильную версию среды выполнения Node.js.
- Зарегистрированный аккаунт на платформе OpenAI для доступа к языковым моделям через API.
- Базовое понимание фреймворка Next.js, хотя автор обещает пошаговый разбор каждой строчки кода.
В процессе работы над проектом используется современный стек веб-разработки: JavaScript/TypeScript для написания логики, LangChain.js в качестве оркестратора ИИ-компонентов, serverless-платформа Vercel для хостинга и облачная база данных DataStax Astra DB для хранения векторов.
🧠 Погружение в векторные эмбеддинги 7:07
Понятие векторного эмбеддинга (vector embedding) лежит в основе работы любых современных интеллектуальных систем поиска. Это популярный метод представления сложной информации в числовом формате, который легко обрабатывается алгоритмами глубокого обучения. Эмбеддинги можно генерировать для текстовых файлов, изображений, аудиозаписей и видеоконтента.
Аня Кубо объясняет этот механизм на простом примере:
«Мы можем взять обычное человеческое слово "cat" (кошка) и превратить его в длинный массив чисел, который понятен компьютеру и при этом кодирует семантическое, то есть смысловое значение этого слова».
Компьютеры не способны мыслить абстрактными категориями. Если попытаться найти похожие слова стандартными лексикографическими методами, то для слова "dog" (собака) компьютер подберет слово "dot" (точка), поскольку они отличаются всего на одну букву. Векторные представления решают эту проблему: сравнивая массивы чисел для слов "cat", "path" и "dot", машина математически вычисляет, что векторы "dog" и "cat" находятся ближе всего друг к другу в многомерном пространстве, а значит, они близки по смыслу.
Разработчикам важно учитывать архитектурную особенность: каждая большая языковая модель использует уникальные алгоритмы генерации векторов. По словам Кубо, эмбеддинг слова, созданный моделью от OpenAI, принципиально несовместим с эмбеддингом того же слова от другой LLM, поэтому смешивать их в рамках одной базы данных нельзя.
💾 Настройка векторной базы данных DataStax Astra DB 9:20
Для хранения сгенерированных числовых векторов используется облачная СУБД DataStax Astra DB. Авторизация на платформе осуществляется в один клик через существующий аккаунт Google.
Процесс создания целевой базы данных состоит из следующих шагов:
- Нажатие кнопки «Create a database» на главной панели управления.
- Обязательный выбор типа «Serverless Vector Database» для поддержки работы с векторами.
- Указание имени базы данных — для данного проекта выбрано название
dbf1. - Выбор облачного провайдера (Amazon Web Services, Google Cloud или Microsoft Azure) — автор оставляет вариант AWS.
- Выбор географического региона, наиболее близкого к разработчику (например,
us-east-2).
После нажатия кнопки подтверждения база данных переходит в статус инициализации. Внутри созданной инфраструктуры будут размещаться коллекции (collections), содержащие как исходные текстовые фрагменты, так и соответствующие им математические векторы.
🔑 Подготовка аккаунта и API-ключей OpenAI 11:48
Для взаимодействия с искусственным интеллектом необходимо настроить доступ к API компании OpenAI. Обычный пользовательский интерфейс веб-версии ChatGPT в данном курсе не используется, так как все запросы отправляются напрямую через программный код.
В проекте задействованы две специализированные модели:
gpt-4— мощная языковая модель, отвечающая за финальную генерацию человекоподобных текстовых ответов.text-embedding-3-small— легковесная и экономичная модель для превращения текстовых строк в векторные эмбеддинги.
Для выпуска токена авторизации разработчик должен перейти в раздел «API Keys» личного кабинета OpenAI и сгенерировать новый секретный ключ. Аня Кубо напоминает, что для успешной отправки запросов к API необходимо привязать платежную карту в настройках биллинга и приобрести стартовый пакет кредитов, иначе запросы будут отклоняться системой.
🚀 Инициализация и реструктуризация проекта Next.js 14:16
Создание каркаса веб-приложения выполняется в среде разработки (WebStorm или VS Code) с помощью стандартной утилиты автоматического развертывания Next.js.
В терминале запускается следующая команда:
npx create-next-app nextjs-f1-gpt --typescript
В процессе интерактивной конфигурации автор выбирает строго определенные параметры:
- Использование линтера ESLint — Yes.
- Подключение Tailwind CSS — No (стили будут написаны вручную в файле global.css).
- Использование директории
src/— No. - Использование нового App Router — No (выбирается классическая плоская структура, так как у приложения будет всего одна страница).
- Кастомизация путей импорта (Alias) — No.
После завершения установки зависимостей осуществляется тотальная очистка проекта от дефолтного кода. Автор полностью удаляет папки pages, public и styles.
Вместо них на корневом уровне создаются две пустые директории: app (для кода интерфейса) и script (для служебных сценариев). Внутри папки app разворачивается структура api/chat/route.ts для бэкенд-маршрута, а также папка assets для статичных изображений.
📝 Разработка скрипта сидинга базы данных (loadDb.ts) 20:32
Для первоначального наполнения (сидинга) базы данных в папке script создается TypeScript-файл loadDb.ts. Этот изолированный сценарий отвечает за обход целевых сайтов, извлечение контента и сохранение векторов.
В начале файла прописываются необходимые импорты:
import { DataAPIClient } from "@datastax/astra-db-ts";
import { PuppeteerWebBaseLoader } from "langchain/document_loaders/web/puppeteer";
import { OpenAI } from "openai";
import { config } from "dotenv";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
По словам автора, разделение текста на небольшие фрагменты повышает точность поиска. Когда пользователь задает вопрос, ИИ работает эффективнее всего, если ему скармливают строго выверенный, компактный объем контекста. По этой причине импортируется инструмент RecursiveCharacterTextSplitter.
Зависимости устанавливаются стандартным вызовом:
npm i @datastax/astra-db-ts langchain openai dotenv
Для запуска TypeScript-файлов напрямую из консоли устанавливается пакет ts-node, а в раздел scripts файла package.json добавляется команда "seed": "ts-node script/loadDb.ts". Чтобы запуск проходил без конфликтов со строгой модульной системой, в tsconfig.json встраивается блок конфигурации ts-node со значением модуля "commonjs".
🔐 Настройка переменных окружения и целей скрейпинга 27:42
Безопасное хранение приватных ключей реализуется через файл переменных окружения .env в корне проекта.
В него записываются пять критически важных параметров:
ASTRA_DB_NAMESPACEсо значением"default_keyspace".ASTRA_DB_COLLECTIONсо значением"F1_GPT".ASTRA_DB_API_ENDPOINT(копируется из овервью панели Astra DB).ASTRA_DB_APPLICATION_TOKEN(сгенерированный токен администратора БД).OPENAI_API_KEY(приватный ключ от платформы OpenAI).
В коде скрипта эти переменные извлекаются через объект process.env. Далее инициализируется массив текстовых ссылок на веб-страницы, которые подлежат глубокому анализу.
В список целей Кубо включает базовую страницу Википедии о «Формуле-1», актуальную статью Sky Sports о сенсационном переходе Льюиса Хэмилтона из команды Mercedes в Ferrari, историческую справку о женщинах-пилотах и аналитический материал о самых высокооплачиваемых гонщиках мира за последний сезон.
⚙️ Реализация логики веб-скрейпинга и генерации векторов 36:30
Подключение к базе данных начинается с создания экземпляра DataAPIClient и вызова метода db(), куда передаются конечная точка API и целевое пространство имен.
Для обхода строгих ограничений TypeScript в рамках учебного туториала в tsconfig.json параметр strict временно переводится в положение false. Инструмент разбиения текста splitter настраивается на размер чанка (chunkSize) в 512 символов с перекрытием соседних фрагментов (chunkOverlap) в 100 символов. Перекрытие критически важно для сохранения сквозного контекста, чтобы важная мысль или предложение не оказались грубо разорваны на стыке чанков.
Асинхронная функция createCollection создает таблицу в Astra DB. В конфигурации указывается размерность вектора 1536, жестко соответствующая спецификации модели text-embedding-3-small от OpenAI. В качестве алгоритма поиска близости векторов выбирается метрика dot_product (скалярное произведение).
По утверждению автора, алгоритм dot_product работает примерно на 50% быстрее стандартного косинусного сходства (cosine), хотя и требует обязательной нормализации векторов.
const createCollection = async (similarityMetric: "dot_product" | "cosine" | "euclidean" = "dot_product") => {
const res = await db.createCollection(ASTRA_DB_COLLECTION, {
vector: {
dimension: 1536,
metric: similarityMetric
}
});
console.log(res);
};
Основная функция loadSampleData последовательно перебирает массив URL-адресов. Для каждой ссылки вызывается внутренний загрузчик на базе библиотеки Puppeteer, работающий в фоновом режиме без открытия графического окна (headless: true). Скрипт дожидается полной загрузки структуры документа (wait until DOM content loaded), считывает содержимое тела страницы document.body.innerHTML и с помощью регулярных выражений очищает его от HTML-тегов, оставляя только чистый текст.
Полученный текст дробится на чанки. Каждый отдельный кусочек текста отправляется в API OpenAI через метод openai.embeddings.create. Полученный массив чисел извлекается из ответа ИИ по пути embedding.data[0].embedding и записывается в базу данных с помощью команды collection.insertOne({ $vector: vector, text: chunk }). После запуска команды npm run seed и завершения процесса скрейпинга в базе данных Astra DB формируется структурированный массив, содержащий более 1000 детализированных записей.
💻 Проектирование пользовательского интерфейса (Frontend) 53:43
Разработка визуальной части начинается с файла layout.tsx, где подключаются глобальные стили global.css и задаются метаданные приложения для поисковой оптимизации (SEO) — заголовок F1 GPT и описание.
Основная страница page.tsx помечается директивой "use client", так как она будет активно взаимодействовать с действиями пользователя в браузере. Главным инструментом управления перепиской выступает специализированный хук useChat из библиотеки Vercel AI SDK, которая устанавливается командой npm i ai.
По словам Ани Кубо, этот хук берет на себя всю рутину по управлению состояниями полей ввода, автоматическому обновлению экрана и организации потокового стриминга ответов от нейросети.
Из хука деструктурируются следующие методы и переменные:
const { input, handleInputChange, handleSubmit, append, isLoading, messages } = useChat();
Конструкция страницы содержит логическое ветвление. Если массив messages пуст, на экране отображается приветственный блок текста с описанием бота и панель с четырьмя готовыми подсказками быстрых вопросов. Если сообщения есть, запускается цикл .map(), который рендерит историю диалога в виде чат-пузырей.
В самом низу окна жестко фиксируется форма отправки сообщений, состоящая из текстового поля ввода с обработчиком onChange={handleInputChange} и кнопкой отправки.
В файле global.css прописываются стили для придания приложению аккуратного вида чат-бота: шрифт Verdana, фоновое изображение гоночного трека, центрирование главного контейнера через Flexbox и легкий серо-белый линейный градиент для подложки самого окна чата. Форма ввода прижимается к нижнему краю, разделяясь серой двухпиксельной рамкой, а кнопка отправки окрашивается в фирменный ярко-красный гоночный цвет.
🧩 Разделение интерфейса на функциональные компоненты 11:43
Для поддержания чистоты архитектуры в папке components создаются четыре изолированных компонента: Bubble.tsx, LoadingBubble.tsx, PromptSuggestionRow.tsx и PromptSuggestionButton.tsx.
Компонент LoadingBubble.tsx отвечает за индикацию ожидания ответа от сервера. Вместо использования тяжелых картинок автор создает анимацию трех пульсирующих точек средствами чистого CSS, манипулируя параметром background-size внутри ключевых кадров @keyframes с интервалом в одну секунду. Элемент отображается на экране только в том случае, если переменная isLoading из хука useChat принимает значение true.
Компонент текстового пузыря Bubble.tsx принимает объект сообщения и извлекает из него поля content (текст) и role (роль отправителя). В зависимости от роли стилизация меняется динамически:
- Если
role === "user", пузырю присваивается классuser, выравнивающий текст по правому краю (margin-left: auto), с нежно-голубым фоном и закруглением углов, скошенным к правому нижнему краю. - Если
role === "assistant", элемент выравнивается по левому краю, окрашивается в мягкий сине-серый оттенок со скосом угла влево.
Блок быстрых подсказок PromptSuggestionRow.tsx содержит фиксированный массив из четырех актуальных вопросов о «Формуле-1». Каждая строка пробрасывается в дочернюю кнопку PromptSuggestionButton.tsx.
При клике на подсказку срабатывает функция handlePrompt, которая вручную формирует легитимный объект сообщения: генерирует случайный идентификатор через встроенный браузерный метод crypto.randomUUID(), прописывает текст подсказки в поле content и жестко задает роль "user". Затем это сообщение отправляется в метод append(), мгновенно активируя триггер запроса к бэкенду, как если бы пользователь набрал этот текст руками.
🛡️ Сквозная интеграция API и финальное тестирование 1:32:50
Финальным этапом становится написание бэкенд-обработчика запросов в файле app/api/chat/route.ts. Функция экспортирует асинхронный метод POST.
Логика обработки входящего сообщения выглядит следующим образом:
- Из тела запроса извлекается полный массив истории сообщений
messages. - Программа берет самый последний элемент массива — актуальный вопрос пользователя:
const latestMessage = messages[messages.length - 1].content. - Текст этого вопроса отправляется в OpenAI для создания его векторного эмбеддинга моделью
text-embedding-3-small. - Полученный числовой вектор передается в метод
collection.find()СУБД Astra DB. Инструкцияsortранжирует данные по степени схожести векторов, а операторlimit(10)отсекает ровно 10 самых релевантных текстовых документов из базы. - Текст из найденных документов склеивается в единую строку контекста
docContext.
Затем формируется системный промпт-шаблон для модели gpt-4. В системной инструкции четко прописывается роль: ИИ обязан быть экспертом в «Формуле-1» и использовать предоставленный ниже контекст из веб-сайтов для дополнения своих знаний. Модели строго запрещается упоминать в диалоге слово «контекст» или ссылаться на источники информации, а также выводить картинки.
const template = {
role: "system",
content: `You are an assistant who knows everything about Formula 1.
Use the below context to augment what you know about Formula 1 racing.
The context will provide you with the most recent page data...
START CONTEXT
${docContext}
END CONTEXT
Formatting responses using markdown where applicable.`
};
Этот массив (системный шаблон плюс вся история переписки) передается в финальный метод openai.chat.completions.create с флагом stream: true. Результат заворачивается в утилиты OpenAIStream и StreamingTextResponse для обеспечения эффекта мгновенной потоковой печати букв на фронтенде.
В рамках финального тестирования Аня Кубо наглядно демонстрирует силу RAG-архитектуры. При выборе подсказки «Кто станет новым пилотом Ferrari?» чат-бот мгновенно выдает точный ответ: Льюис Хэмилтон, который подписал многолетний контракт, рассчитанный минимум до конца 2026 года.
Для сравнения автор временно отключает передачу контекста из базы данных и задает модели тот же вопрос напрямую. Лишенная актуального контекста базовая модель GPT-4 начинает путаться в фактах, ссылаться на пилотский состав сезона 2022 года и утверждать, что последним крупным трансфером Ferrari было подписание Карлоса Сайнса в 2021 году. Тест доказывает успешную интеграцию внешних знаний в ИИ-приложение.