Создание end-to-end GenAI-приложения с Ruby и Milvus
Внедрение специализированных фреймворков GenAI, таких как LangChain, позволило нам быстро и легко создавать сложные AI-приложения, используя мощные большие языковые модели (LLMs) такие как ChatGPT и LLaMA. LangChain, например, позволяет нам создать мощное приложение Retrieval Augmented Generation (RAG) всего в несколько строк кода, не требуя глубоких теоретических знаний в области AI.
Эта тенденция означает, что в наши дни специалисты по данным и инженеры машинного обучения больше не являются единственными, кто способен создавать приложения GenAI. Full-stack инженеры или разработчики программного обеспечения теперь могут создавать приложения GenAI с помощью LangChain.
Однако эти фреймворки GenAI обычно написаны на Python, и мы знаем, что некоторые full-stack инженеры и разработчики программного обеспечения редко используют Python в своих проектах. Поэтому существует потребность в расширениях этих фреймворков GenAI на других языках программирования, чтобы эти full-stack инженеры могли использовать мощные LLMs для создания приложений GenAI в своих программных проектах.
В недавнем докладе Андрей Бондарев, Solution Architect в Source Labs LLC, представил Ruby-расширение LangChain под названием LangChain.rb, чтобы full-stack инженерам было проще создавать приложения GenAI в своих программных проектах.
Посмотрите этот доклад на Youtube
Но прежде чем мы обсудим, как создать приложение GenAI с помощью Ruby, давайте кратко рассмотрим внутренние механизмы Retrieval Augmented Generation (RAG), популярного варианта использования GenAI.
Как работает RAG
Ни для кого не секрет, что данные — это золотая жила любого приложения GenAI. Они служат источником информации, используемой GenAI для генерации фактических и точных ответов. Из всех данных, доступных сейчас, 80% можно классифицировать как неструктурированные данные.
Неструктурированные данные — это данные, которые не соответствуют заранее определенному формату данных. Этот тип данных включает изображения, текст, звук и видео. Чтобы машины могли осмысливать эти типы неструктурированных данных, нам необходимо преобразовать их в числовой формат, называемый векторными эмбеддингами.
Основные понятия векторных эмбеддингов
Эмбеддинг состоит из n-мерного вектора, где n обозначает размерность эмбеддинга. Размерность зависит от модели глубокого обучения, которая преобразует данные в эмбеддинг. Эмбеддинг несет семантическое значение данных, которые он представляет.
Мы можем использовать модели глубокого обучения для преобразования различных модальностей данных в эмбеддинги. Например, если у нас есть текстовые данные, мы можем использовать модели OpenAI или Sentence Transformer, чтобы преобразовать эти текстовые данные в эмбеддинг. Если у нас есть данные изображений, мы можем использовать специализированные предварительно обученные модели, способные извлекать признаки изображений, такие как Vision Transformer, в качестве модели эмбеддингов.
Поскольку эмбеддинг несет семантическое значение данных, которые он представляет, мы можем вычислить сходство этого эмбеддинга с другими эмбеддингами в так называемом векторном пространстве. Эмбеддинги со схожими семантическими значениями будут расположены рядом друг с другом в векторном пространстве, как вы можете видеть на визуализации ниже:
Vector embeddings in a vector space.png
Эмбеддинг связанных слов в векторном пространстве
Как показано на изображении выше, эмбеддинги "queen" и "king" расположены близко друг к другу, как и "woman" и "man". Евклидово расстояние между "queen-king" и "woman-man" также будет примерно одинаковым, поскольку они несут схожие значения.
Эта концепция лежит в основе операции векторного поиска, где мы вычисляем сходство между одним эмбеддингом и несколькими эмбеддингами.
Роль векторной базы данных в векторном поиске и приложениях RAG
Реализовать векторный поиск просто, если мы имеем дело только с небольшим количеством эмбеддингов. Однако в реальных случаях мы обычно работаем с тысячами, миллионами или даже миллиардами эмбеддингов. Поэтому нам нужно решение для эффективного хранения эмбеддингов и выполнения быстрого векторного поиска по ним.
Именно здесь вступает в игру векторная база данных, такая как Milvus. Milvus — это векторная база данных с открытым исходным кодом, в которой можно хранить огромные объемы эмбеддингов и выполнять векторный поиск по этим эмбеддингам за доли секунды.
Рабочий процесс преобразования неструктурированных данных в эмбеддинги и их хранения в Milvus
Рабочий процесс преобразования неструктурированных данных в эмбеддинги и их хранения в Milvus
Векторные базы данных также играют ключевую роль в популярных приложениях GenAI, таких как RAG. Как вы, возможно, уже знаете, главная цель RAG — повысить точность ответов, генерируемых LLM, такими как ChatGPT и LLaMA, предоставляя им контекст, который может быть полезен для ответа на запрос пользователя.
В приложении RAG после получения запроса пользователя он преобразуется в эмбеддинг с помощью модели эмбеддингов. Затем выполняется векторный поиск, при котором эмбеддинг запроса сравнивается с контекстными эмбеддингами, хранящимися внутри векторной базы данных, такой как Milvus. Затем извлекаются наиболее похожие контекстные данные и передаются вместе с запросом в LLM. После этого LLM может использовать информацию из контекста для генерации контекстуализированного ответа на запрос пользователя.
RAG
Рабочий процесс RAG
LangChain как популярный фреймворк GenAI
LangChain — это фреймворк, который упрощает создание и разработку приложений GenAI с использованием современных моделей LLM. Он легко интегрируется с популярными поставщиками LLM, такими как OpenAI, Anthropic и Google, а также с поставщиками векторных баз данных, такими как Zilliz.
LangChain также предлагает гибкие абстракции для разработки AI-приложений на базе LLM, что позволяет специалистам по данным и разработчикам программного обеспечения легко создавать сложные системы, такие как RAG, всего несколькими строками кода.
Например, предположим, что мы хотим кратко изложить содержание этой публикации в блоге с помощью GPT-4. Мы можем выполнить эту задачу следующим кодом:
import os
from langchain.chains.summarize import load_summarize_chain
from langchain_community.document_loaders import WebBaseLoader
from langchain_openai import ChatOpenAI
os.environ["LANGCHAIN_TRACING_V2"] = "True"
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
docs = loader.load()
llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo-1106")
chain = load_summarize_chain(llm, chain_type="stuff")
result = chain.invoke(docs)
"""
Вывод: В статье рассматривается концепция автономных агентов на базе LLM с акцентом на компоненты планирования, памяти и использования инструментов. Она включает кейс-стади и примеры proof-of-concept, а также проблемы и ссылки на связанные исследования. Автор подчеркивает потенциал LLM в создании мощных агентов для решения задач, одновременно выделяя ограничения, такие как конечная длина контекста и надежность интерфейсов на естественном языке.
"""
Как видите, используя примерно всего 10 строк кода, мы можем задействовать модель GPT-4 для точного резюмирования длинной публикации в блоге.
С LangChain вы также можете выполнять более сложные задачи. Например, вы можете разбить длинный текст из PDF-документа на фрагменты, преобразовать каждый фрагмент в embedding с помощью выбранной вами embedding-модели, сохранить embedding этих фрагментов внутри векторной базы данных и затем выполнить RAG.
import getpass
import os
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores.milvus import Milvus
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain.embeddings import SentenceTransformerEmbeddings
from langchain_openai import ChatOpenAI
# Set the API key
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")
llm = ChatOpenAI(model="gpt-4")
# Text to be processed
texts = "This is a very long text.....:"
# Split text into chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100) # Example chunk size and overlap
chunk_texts = text_splitter.split_text(texts)
# Instantiate embedding model
embeddings = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
# Store embeddings of chunks inside Milvus DB
vector_db = Milvus.from_texts(texts=chunk_texts, embedding=embeddings, collection_name="rag_milvus")
retriever = vector_db.as_retriever()
# Define function to format documents
def format_docs(docs):
return "\\n\\n".join(doc.page_content for doc in docs)
# Perform RAG (Retrieval Augmented Generation)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| llm
| StrOutputParser()
)
# Example question
question = "What is the main idea of the text?"
# Execute RAG chain and print the result
for chunk in rag_chain.stream(question):
print(chunk, end="", flush=True)
Помимо демонстраций, приведенных выше, LangChain предлагает широкий спектр возможностей. Например, он интегрирует LLM и API из внешних источников, таких как погодные приложения, калькуляторы или Google Search. Такой подход позволяет LLM использовать информацию из этих источников для генерации более точных и контекстных ответов. Мы рассмотрим подробную реализацию этого подхода в следующих разделах.
Вы также можете изучить все возможности LangChain на их странице документации.
Разработка GenAI-приложений с Ruby и Milvus
Python стал де-факто языком программирования для исследований в области ИИ и фреймворков разработки, включая LangChain. Между тем Ruby остается популярным для быстрой разработки программного обеспечения и веб-приложений.
Однако, как вы видели в предыдущем разделе, появление LangChain открывает для разработчиков программного обеспечения возможности интегрировать мощь LLM в свои веб-приложения без знания подробных теорий LLM и ИИ в целом.
Эта возможность создала растущий спрос на расширение таких фреймворков разработки GenAI на другие языки, более знакомые full-stack-разработчикам, такие как Ruby. Чтобы удовлетворить этот спрос, Andrei Bondarev представил LangChain.rb, который является Ruby-расширением оригинального фреймворка LangChain.
LangChain.rb позволяет Ruby full-stack-разработчикам создавать веб-приложения на базе LLM без необходимости использовать несколько языков программирования в своих проектах. С его помощью вы можете легко интегрировать популярные векторные базы данных, LLM и внешние ресурсы в ваши LLM-веб-приложения.
LangChain.rb обладает теми же общими функциональными возможностями, что и оригинальный LangChain, такими как:
Управление промптами: создание, загрузка и сохранение шаблонов промптов для LLM по вашему выбору
Проверка длины контекста: проверка длины контекста входных данных в соответствии с длиной контекста LLM и моделей эмбеддингов по вашему выбору
Разбиение данных на фрагменты: разделение данных на фрагменты по заранее заданным правилам перед их загрузкой в векторные базы данных по вашему выбору
Память разговора: сохранение чата с LLM в память
В следующих разделах мы продемонстрируем разработку простых приложений на базе LLM с помощью LangChain.rb.
Общие RAG-приложения с LangChain.rb
В этом первом примере мы создадим простое и быстрое RAG-приложение с использованием LangChain.rb. Прежде чем вы сможете использовать LangChain.rb в своем Ruby-проекте, обязательно установите gem, выполнив следующую команду:
gem install langchainrb
В этом проекте мы будем использовать Milvus в качестве векторной базы данных и модели от OpenAI как в качестве LLM, так и в качестве моделей эмбеддингов. Чтобы запустить Milvus, нам нужно установить Milvus в Docker и запустить контейнер с помощью следующей команды:
# Download the installation script
curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh
# Start the Docker container
bash standalone_embed.sh start
Теперь, когда мы запустили контейнер Docker, давайте создадим экземпляры Milvus и моделей, которые будем использовать для нашего RAG-приложения.
require 'langchain'
milvus = Langchain::Vectorsearch::Milvus.new(
url: ENV["MILVUS_URL"],
index_name: "Documents",
llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
)
Первое, что нам нужно сделать, — создать схему внутри векторной базы данных Milvus и соответствующий метод индексирования. Затем нам нужно загрузить эту схему, прежде чем мы сможем выполнять векторный поиск с ее использованием.
# Create default schema
milvus.create_default_schema
# Create default index
milvus.create_default_index
# Load default schema
milvus.load_default_schema
Теперь мы можем загрузить некоторые данные в нашу схему. Допустим, у нас есть PDF в локальном каталоге, содержащий информацию о льготах для сотрудников. Если мы хотим сохранить весь текст из этого PDF внутри базы данных Milvus, мы можем сделать это, выполнив следующие команды:
pdf = Langchain.root.join("path/to/my.pdf")
# Add PDF inside of Milvus
milvus.add_data(path: pdf)
После выполнения приведенных выше команд LangChain выполнит всю предварительную обработку под капотом. Он распарсит текст внутри PDF-файла, разделит его на несколько фрагментов, преобразует каждый фрагмент в эмбеддинги, а затем сохранит эмбеддинги внутри векторной базы данных Milvus.
После сохранения наших данных внутри векторной базы данных Milvus мы можем начать задавать вопросы, связанные с нашим PDF-документом. Допустим, мы хотим спросить: «Какова политика компании в отношении отпусков? Сколько дней я могу взять?», тогда мы можем задать вопрос нашим LLM в RAG-системе, просто выполнив эту одну строку кода:
response = milvus.ask(question: "What’s the company’s vacation policy? How much can I take off?")
puts response
"""
Response:
=> The company's vacation policy allows employees to take any reasonable amount of time off with pay,
as long as they consult with their manager in advance and get their work done.
"""
И всё! Помимо создания общего RAG-приложения, мы также можем создать агентное RAG-приложение с помощью LangChain.rb, что обсудим в следующем разделе.
Использование агентов для взаимодействия со сторонними инструментами
Основное ограничение многих LLM — дата отсечения их знаний. У GPT-4, например, дата отсечения — апрель 2023 года. Это означает, что если мы захотим спросить об общих или фактических событиях после апреля 2023 года, мы не получим точный ответ от LLM.
Чтобы решить эту проблему, LangChain.rb позволяет нам создать агентное RAG-приложение. Этот тип RAG-приложения добавляет еще один уровень интеллекта, содержащий "агента", который выступает в роли лица, принимающего решения. Агент анализирует запрос пользователя, а затем выбирает наиболее эффективные сторонние инструменты, которые могут предоставить наиболее подходящий контекст для ответа на запрос.
Допустим, мы хотим спросить нашу LLM о текущей погоде в Нью-Йорке. При использовании обычной RAG-системы LLM не может знать погоду в Нью-Йорке в реальном времени. Скорее всего, она начнет галлюцинировать и выдаст нам какие-то случайные прогнозы погоды.
Рабочий процесс агентного RAG (1).png
Рабочий процесс агентного RAG
Агентный RAG решает эту проблему, позволяя нам использовать в системе инструменты или API, такие как OpenWeather API, чтобы получать погоду в Нью-Йорке в реальном времени. Агент сначала обработает пользовательский запрос, а затем выберет инструменты, которые могут предоставить релевантный контекст для ответа на запрос, прежде чем синтезировать этот контекст в точный ответ.
В следующей демонстрации внутри нашей RAG-системы будут использоваться сторонние инструменты, такие как калькулятор, приложение OpenWeather и Google Search.
weather = Langchain::Tool::Weather.new(api_key: ENV["OPEN_WEATHER_API_KEY"])
google_search = Langchain::Tool::GoogleSearch.new(api_key: ENV["SERPAPI_API_KEY"])
calculator = Langchain::Tool::Calculator.new
Далее нам нужно добавить эти три инструмента в нашу RAG-систему с помощью следующей команды:
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
agent = Langchain::Agent::ReActAgent.new(
llm: openai,
tools: [weather, google_search, calculator]
)
Теперь мы можем начать задавать вопросы нашей LLM. Допустим, мы хотим задать следующий вопрос: “Find current weather in Boston, MA, and Washington, D.C., and take an average.”
response = agent.run(question: "Find current weather in Boston, MA and Washington, D.C. and take an average")
Вывод RAG с интеграцией инструментов
Вывод RAG с интеграцией инструментов
Как видно на скриншоте выше, наша агентная RAG-система смогла точно ответить на запрос. Давайте разберем рабочий процесс этой RAG-системы:
Запрос сначала был отправлен в OpenAI LLM.
Агент распознал необходимость использовать OpenWeather API, чтобы получить текущую погоду в Бостоне и Вашингтоне, округ Колумбия.
После получения данных о погоде агент увидел, что запрос требует вычислить среднее значение погоды в двух городах.
Затем агент вызвал инструмент калькулятора, чтобы вычислить среднее значение погоды.
Наконец, LLM синтезировала результаты в один связный ответ и вернула его пользователю.
Этот пример демонстрирует мощь агентного подхода RAG. Благодаря включению внешних инструментов и API система преодолела ограничения даты отсечения знаний LLM и предоставила точный, актуальный ответ на запрос пользователя.
Использование агентов для взаимодействия с внутренней базой данных
Мы также можем использовать агентный RAG для взаимодействия с нашими внутренними базами данных. Это очень полезно, потому что мы можем запрашивать инсайты о наших данных, используя человекоподобный язык, вместо того чтобы полагаться на традиционные SQL-запросы.
Допустим, у нас есть интернет-магазин, а данные пользователей хранятся в базе данных. Обычно нам пришлось бы писать SQL-запросы, чтобы извлекать инсайты из этих данных. С агентным RAG все, что нам нужно сделать, — это спросить LLM об интересующем нас инсайте, и ответ будет возвращен немедленно.
Например, допустим, мы хотим узнать, сколько записей пользователей хранится в базе данных. Мы можем просто спросить: "Сколько пользователей есть?" и выполнить следующие команды:
require 'langchain'
# Создание экземпляра подключения к базе данных
database = Langchain::Tool::Database.new(connection_string: "postgres://localhost:5432/my_database")
# Создание экземпляра LLM OpenAI
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
# Создание SQLAgent с LLM и подключением к базе данных
agent = Langchain::Agent::SQLAgent.new(
llm: openai,
db: database
)
# Задать вопрос агенту
response = agent.run("Сколько пользователей существует?")
Ниже приведен пример вывода команды:
Вывод RAG с интеграцией SQL
Вывод RAG с интеграцией SQL
Как видите, наша агентная система RAG смогла точно ответить на конкретный вопрос, связанный с данными нашей базы данных. Рабочий процесс агента похож на предыдущий пример:
Запрос был отправлен в LLM OpenAI.
Агент проанализировал запрос и определил, что для подсчета количества пользователей требуется обращение к базе данных.
LLM сгенерировала подходящий SQL-запрос на основе схемы таблицы базы данных.
SQL-запрос был выполнен в базе данных и вернул результат.
Вывод базы данных был отправлен обратно в LLM.
LLM преобразовала результат из базы данных в связный, понятный человеку ответ и предоставила его в качестве окончательного ответа.
Заключение
Появление LangChain делает LLM доступными для специалистов, которые могут не обладать глубокими знаниями теорий ИИ и науки о данных. Мы можем создать мощное приложение RAG с использованием LangChain всего несколькими строками кода.
Именно благодаря этой доступности Андрей Бондарев представил LangChain.rb — расширение LangChain для Ruby. Этот фреймворк позволяет full-stack-разработчикам внедрять мощные возможности LLM в свои веб-приложения без необходимости обладать обширной экспертизой в области ИИ. Кроме того, LangChain.rb избавляет full-stack-разработчиков от необходимости переходить на другой язык программирования, когда они хотят использовать LLM в своих веб-приложениях.
Читать далее

Zilliz Cloud On-Demand Compute: Pay Only for What You Use
The customer case behind Zilliz Cloud On-Demand: how a $10K vector search bill came down to under $500, and the engineering changes that made it possible.

8 Latest RAG Advancements Every Developer Should Know
Explore eight advanced RAG variants that can solve real problems you might be facing: slow retrieval, poor context understanding, multimodal data handling, and resource optimization.

Optimizing Embedding Model Selection with TDA Clustering: A Strategic Guide for Vector Databases
Discover how Topological Data Analysis (TDA) reveals hidden embedding model weaknesses and helps optimize vector database performance.


