Оглавление
- Настройка
— Инициализация проекта Ruby on Rails с PostgreSQL
— Настройка PGVector
— Настройка OpenAI
— Создание простого чата с помощью Hotwired
- Прототип
— Chat API
— Разрушитель
- Внедрение
— Фрагменты данных
— Вектор
— Как найти наиболее релевантные фрагменты
- "Краткое содержание"
Введение
В современном быстро меняющемся мире перед предприятиями стоит сложная задача предоставления точных и своевременных ответов на запросы пользователей. Будь то помощь клиентам, обмен технической документацией или просто обмен знаниями, потребность в надежной и эффективной системе для решения вопросов пользователей стала абсолютно необходимой. И вот здесь в игру вступает невероятная мощь чат-бота с искусственным интеллектом, подпитываемая специализированной базой знаний.
Имея опыт более 300 проектов по разработке программного обеспечения, в том числе несколько с интеграцией OpenAI, Rubyroid Labs пользуется доверием с 2013 года. Если вы ищете надежного партнера для беспрепятственной интеграции ChatGPT в ваше приложение Ruby on Rails, свяжитесь с нами сегодня.
Чат-бот с искусственным интеллектом, который может отвечать на вопросы на основе определенной базы знаний, является ценным активом для организаций, стремящихся автоматизировать взаимодействие с клиентами и улучшить общее взаимодействие с пользователем. В отличие от чат-ботов более общего назначения, эти чат-боты, основанные на знаниях, предназначены для предоставления точных и контекстуально релевантных ответов, используя тщательно подобранную базу данных.
Прелесть этого подхода заключается в возможности адаптировать ответы чат-бота к определенному домену или теме. Создавая базу знаний, включающую соответствующую информацию о продуктах, услугах, политиках или любой другой теме, чат-бот становится бесценным ресурсом для пользователей, ищущих конкретную информацию.
Вариантов использования таких чат-ботов, основанных на знаниях, множество. Например, компания электронной коммерции может создать чат-бота, который помогает клиентам с запросами о продуктах, доступности и информации о доставке. Точно так же образовательные учреждения могут использовать чат-ботов, чтобы отвечать на часто задаваемые вопросы о курсах, поступлении и объектах кампуса. Кроме того, есть много других случаев, некоторые из которых перечислены в другой нашей записи в блоге.
В нашем случае нас попросили разработать чат-бота для юридического консультирования. Поэтому он должен основывать свои ответы только на предоставленной базе знаний, и ответы должны быть очень конкретными. База знаний состоит из 1 миллиарда слов. Мы столкнулись со многими проблемами, и в этой статье мы покажем вам, как мы их решили.
В этой статье мы проведем вас через процесс настройки проекта Ruby on Rails, интеграции ChatGPT и создания функций для получения и использования базы знаний для ответов на вопросы пользователей. К концу у вас будут необходимые навыки для разработки собственного чат-бота, основанного на знаниях, адаптированного к конкретной области или теме вашей организации, который позволит пользователям получать точные и актуальные ответы на основе конкретной базы знаний, которую вы предоставляете.
Ради этого примера мы собираемся встроить информацию с сайта RubyroidLabs, тем самым чат-бот с искусственным интеллектом сможет отвечать на вопросы о компании.
Вот что мы собираемся построить:
Давайте перейдем к этому решению шаг за шагом.
Настраивать
Инициализировать проект Ruby on Rails с помощью PostgreSQL
Проверить среду
ruby --version # ruby 3.2.2 rails --version # Rails 7.0.5
Инициализировать проект Rails (документы)
rails new my_gpt --database=postgresql --css=tailwind cd my_gpt
Настройка базы данных
Лучший способ установить PostgreSQL на MacOS — вообще не устанавливать его. Вместо этого просто запустите док-контейнер с требуемой версией PostgreSQL. Мы будем использовать образ ankane/pgvector, поэтому у нас будет предустановлено расширение pgvector.
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres --name my_gpt_postgres ankane/pgvector
Добавьте это к config/database.yml
в раздел default
или development
:
default: &default host: localhost username: postgres password: postgres
Затем инициализируйте структуру базы данных:
rake db:create rake db:migrate
Запустите приложение
./bin/dev
Настройка PGVector
Для работы с PGVector мы будем использовать гем neighbor. Если вы запускаете PostgreSQL с Docker, как описано выше, нет необходимости устанавливать и собирать расширение PGVector. Итак, вы можете перейти к этому:
bundle add neighbor rails generate neighbor:vector rake db:migrate
Настройка OpenAI
Для вызовов API OpenAI мы будем использовать гем ruby-openai.
bundle add ruby-openai
Создайте файл config/initializers/openai.rb
со следующим содержимым:
OpenAI.configure do |config| config.access_token = Rails.application.credentials.openai.access_token config.organization_id = Rails.application.credentials.openai.organization_id end
Добавьте свой ключ API OpenAI к учетным данным. Вы можете найти их в своем аккаунте OpenAI.
rails credentials:edit openai: access_token: xxxxx organization_id: org-xxxxx
Создайте простой чат с Hotwired
Создайте контроллер вопросов app/controllers/questions_controller.rb
:
class QuestionsController < ApplicationController def index end def create @answer = "I don't know." end private def question params[:question][:question] end end
Добавьте маршруты к config/routes.rb
:
resources :questions, only: [:index, :create]
Создайте макет чата в app/views/questions/index.html.erb
:
<div class="w-full"> <div class="h-48 w-full rounded mb-5 p-3 bg-gray-100"> <%= turbo_frame_tag "answer" %> </div> <%= turbo_frame_tag "new_question", target: "_top" do %> <%= form_tag questions_path, class: 'w-full' do |f| %> <input type="text" class="w-full rounded" name="question[question]" placeholder="Type your question"> <% end %> <% end %> </div>
Отображение ответа с помощью турбопотока. Создайте файл app/views/questions/create.turbo_stream.erb
и заполните его:
<%= turbo_stream.update('answer', @answer) %>
Готово 🎉 Откройте http://localhost:3000/questions и проверьте.
Опытный образец
Чат API
Начнем с самой простой и очевидной реализации — предоставим все наши данные ChatGPT и попросим его основывать свой ответ только на предоставленных данных. Хитрость здесь заключается в том, чтобы «сказать «я не знаю», если на вопрос нельзя ответить, исходя из контекста».
Итак, скопируем все данные со страницы услуги и прикрепим их как контекст.
context = <<~LONGTEXT RubyroidLabs custom software development services. We can build a website, web application, or mobile app for you using Ruby on Rails. We can also check your application for bugs, errors and inefficiencies as part of our custom software development services. Services: * Ruby on Rails development. Use our Ruby on Rails developers in your project or hire us to review and refactor your code. * CRM development. We have developed over 20 CRMs for real estate, automotive, energy and travel companies. * Mobile development. We can build a mobile app for you that works fast, looks great, complies with regulations and drives your business. * Dedicated developers. Rubyroid Labs can boost your team with dedicated developers mature in Ruby on Rails and React Native, UX/UI designers, and QA engineers. * UX/UI design. Rubyroid Labs can create an interface that will engage your users and help them get the most out of your application. * Real estate development. Rubyroid Labs delivers complex real estate software development services. Our team can create a website, web application and mobile app for you. * Technology consulting. Slash your tech-related expenses by 20% with our help. We will review your digital infrastructure and audit your code, showing you how to optimize it. LONGTEXT
Сообщение для ChatGPT составлено так:
message_content = <<~CONTENT Answer the question based on the context below, and if the question can't be answered based on the context, say \"I don't know\". Context: #{context} --- Question: #{question} CONTENT
Затем сделайте API-запрос к ChatGPT:
openai_client = OpenAI::Client.new response = openai_client.chat(parameters: { model: "gpt-3.5-turbo", messages: [{ role: "user", content: message_content }], temperature: 0.5, }) @answer = response.dig("choices", 0, "message", "content")
Нарушитель сделки
Дело в том, что у каждого Chat API или Completion API есть лимиты.
Для gpt-3.5-turbo
по умолчанию это 4096 токенов. Давайте измерим, из скольких токенов состоят наши данные с помощью OpenAI Tokenizer:
Всего 276 токенов, не так уж и много. Однако это только с одной страницы. Всего у нас есть 300 тысяч токенов данных.
Что, если мы переключимся на gpt-4-32k
? Он может обрабатывать до 32 768 токенов! Предположим, что для наших целей этого достаточно. Какова будет цена за один запрос? GPT-4 с контекстом 32K стоит $0,06/1K токенов. Таким образом, это $ 2+ за запрос.
Здесь в игру вступает встраивание.
Вложения
Блоки данных
Чтобы уложиться в лимиты или не тратить весь бюджет на 32 тыс. запросов, давайте предоставим ChatGPT самые актуальные данные. Для этого давайте разделим все данные на небольшие куски и сохраним их в базе данных PostgreSQL:
Теперь, основываясь на вопросе пользователя, нам нужно найти наиболее релевантный фрагмент в нашей базе данных. Здесь нам может помочь Embeddings API. Он получает текст и возвращает вектор (массив из 1536 чисел).
Таким образом, мы генерируем вектор для каждого чанка через Embeddings API и сохраняем его в БД.
response = openai_client.embeddings( parameters: { model: 'text-embedding-ada-002', input: 'Rubyroid Labs has been on the web and mobile...' } ) response.dig('data', 0, 'embedding') # [0.0039921924, -0.01736092, -0.015491072, ...]
Вот так сейчас выглядит наша база данных:
Код:
rails g model Item page_name:string text:text embedding:vector{1536} rake db:migrate
Миграция:
class CreateItems < ActiveRecord::Migration[7.0] def change create_table :items do |t| t.string :page_name t.text :text t.vector :embedding, limit: 1536 t.timestamps end end end
Модель:
class Item < ApplicationRecord has_neighbors :embedding end
Задача Rake (lib/tasks/index_data.rake):
DATA = [ ['React Native Development', 'Rubyroid Labs has been on the web and mobile...'], ['Dedicated developers', 'Rubyroid Labs can give you a team of dedicated d...'], ['Ruby on Rails development', 'Rubyroid Labs is a full-cycle Ruby on Rails...'], # ... ] desc 'Fills database with data and calculate embeddings for each item.' task index_data: :environment do openai_client = OpenAI::Client.new DATA.each do |item| page_name, text = item response = openai_client.embeddings( parameters: { model: 'text-embedding-ada-002', input: text } ) embedding = response.dig('data', 0, 'embedding') Item.create!(page_name:, text:, embedding:) puts "Data for #{page_name} created!" end end
Запускаем рейк-задачу:
rake index_data
Вектор
Что такое вектор? Проще говоря, вектор — это кортеж, или, другими словами, массив чисел. Например, [2, 3]
. В двумерном пространстве это может относиться к точке на скалярной плоскости:
То же самое относится к трехмерным и более пространствам:
Если бы у нас были векторы 2d, а не векторы 1536d, мы могли бы отобразить их на скалярной плоскости следующим образом:
Как найти наиболее релевантные чанки
Итак, приложение получает следующий вопрос: «Как долго RubyroidLabs присутствует на рынке мобильного программного обеспечения?». Рассчитаем и его вектор.
response = openai_client.embeddings( parameters: { model: 'text-embedding-ada-002', input: 'How long has RubyroidLabs been on the mobile software market?' } ) response.dig('data', 0, 'embedding') # [0.009017303, -0.016135506, 0.0013286859, ...]
И отобразить его на скалярной плоскости:
Теперь мы можем математически найти ближайшие векторы. Для этой задачи не требуется ИИ. Это то, для чего мы ранее настроили PGVector.
nearest_items = Item.nearest_neighbors( :embedding, question_embedding, distance: "euclidean" ) context = nearest_items.first.text
А теперь просто поместите этот контекст в Chat API, как мы уже делали ранее.
message_content = <<~CONTENT Answer the question based on the context below, and if the question can't be answered based on the context, say \"I don't know\". Context: #{context} --- Question: #{question} CONTENT # a call to Chat API
Вот это 🎉
Наши ответы в чате основаны на всей предоставленной нами информации. Более того, он почти не тратит дополнительные деньги на вопрос, но дает лучший ответ. Однако вам придется заплатить один раз за расчет вложений при инициализации базы данных. За 300 тысяч токенов с Ada v2 это стоит всего 0,03 доллара.
Rubyroid Labs сотрудничает с компаниями по всему миру, чтобы интегрировать OpenAI в свою деятельность. Если вы хотите изменить свой чат-бот или другой диалоговый интерфейс, пожалуйста, свяжитесь с нами.
Краткое содержание
Подведем итоги:
- Разделите имеющиеся у вас данные на небольшие фрагменты. Вычислите вложение для каждого фрагмента.
- Сохраняйте фрагменты с соответствующими вложениями в векторную БД, например, PostgreSQL плюс PGVector.
- Инициализация приложения завершена. Теперь вы можете получить вопрос от пользователя. Рассчитайте вложение для этого вопроса.
- Получить кусок из БД с ближайшим вектором к вектору вопросов.
- Отправьте вопрос в Chat API, предоставив фрагмент из предыдущего шага.
- Получите ответ от Chat API и отобразите его пользователю 🎉
Полная логика чата вынесена в отдельный класс:
# frozen_string_literal: true class AnswerQuestion attr_reader :question def initialize(question) @question = question end def call message_to_chat_api(<<~CONTENT) Answer the question based on the context below, and if the question can't be answered based on the context, say \"I don't know\". Context: #{context} --- Question: #{question} CONTENT end private def message_to_chat_api(message_content) response = openai_client.chat(parameters: { model: 'gpt-3.5-turbo', messages: [{ role: 'user', content: message_content }], temperature: 0.5 }) response.dig('choices', 0, 'message', 'content') end def context question_embedding = embedding_for(question) nearest_items = Item.nearest_neighbors( :embedding, question_embedding, distance: "euclidean" ) context = nearest_items.first.text end def embedding_for(text) response = openai_client.embeddings( parameters: { model: 'text-embedding-ada-002', input: text } ) response.dig('data', 0, 'embedding') end def openai_client @openai_client ||= OpenAI::Client.new end end # AnswerQuestion.new("Yours question..").call
Что еще можно сделать для улучшения качества ответов:
- Размер фрагмента. Найдите оптимальный размер фрагмента данных. Можно попробовать разбить их на мелкие, получить ближайшие N из базы и связать их в один контекст. И наоборот, вы можете попытаться создать большие фрагменты и получить только один — ближайший.
- Длина контекста. С помощью
gpt-3.5-turbo
вы можете отправить 4096 токенов. Сgpt-3.5-turbo-16k
— 16 384 токена. Сgpt-4-32k
до 32 768 токенов. Найдите все, что соответствует вашим потребностям. - Модели. Существует множество моделей ИИ, которые можно использовать для встраивания или чата. В этом примере мы использовали
gpt-3.5-turbo
для чата иtext-embedding-ada-002
для вложений. Вы можете попробовать разные. - Встраивания. OpenAI Embeddings API — не единственный способ расчета вложений. Существует множество других открытых и проприетарных моделей, которые могут вычислять вложения.