Как создать RAG с Milvus, QwQ-32B и Ollama
Модели ИИ быстро развиваются, и QwQ-32B от Alibaba в последнее время уверенно заявляет о себе. Имея всего 32 миллиарда параметров, эта модель рассуждений среднего размера демонстрирует впечатляющую производительность в математическом рассуждении, творческом письме и генерации кода — соперничая с гораздо более крупными моделями, такими как DeepSeek-R1. Ее эффективность и точность в различных бенчмарках делают ее привлекательным вариантом для широкого спектра приложений ИИ.
11.jpeg
Рисунок 1: Производительность QwQ-32B в сравнении с другими ведущими моделями (Источник)
Помимо своих возможностей, QwQ-32B выделяется доступностью. В отличие от некоторых массивных моделей, которым требуется специализированное оборудование, она эффективно работает на потребительских GPU, таких как RTX 4090, что делает ее отличным выбором для разработчиков и исследователей, которым нужен высококачественный ИИ без ресурсов корпоративного масштаба. Однако, будучи плотной моделью, QwQ-32B иногда может испытывать трудности со сложным рассуждением по длинным текстам и демонстрировать галлюцинации, особенно при обработке расширенных контекстных окон.
Чтобы смягчить эти проблемы и повысить ее надежность, мы можем интегрировать QwQ-32B с Retrieval-Augmented Generation (RAG). В этом руководстве мы пошагово рассмотрим, как построить RAG-систему с использованием QwQ-32B, Milvus (высокопроизводительной векторной базы данных) и Ollama. К концу у вас будет оптимизированный, мощный ИИ-конвейер, сочетающий эффективность, точность и масштабируемость.
Прежде чем перейти к подробностям того, как создать RAG-приложение, давайте быстро рассмотрим все технологии, которые мы будем использовать в этом руководстве.
QwQ-32B против DeepSeek-R1
И QwQ-32B, и DeepSeek-R1 специализируются на рассуждении, но последняя использует архитектуру Mixture-of-Experts (MoE), тогда как QwQ-32B является классической плотной моделью.
- MoE-модели отлично проявляют себя в сценариях, требующих больших знаний (например, Q&A-системы, информационный поиск) и крупномасштабной обработке данных, где разные эксперты обрабатывают отдельные подмножества данных для повышения эффективности. Однако их огромное количество параметров требует облачных или выделенных серверных ресурсов.
- Плотные модели, хотя и вычислительно затратны, лучше подходят для глубоких, связных задач рассуждения (например, сложное логическое рассуждение, углубленное понимание прочитанного) и проектирования алгоритмов, где производительность в реальном времени не является обязательной. Их компактный размер позволяет выполнять локальное развертывание, но иногда они могут генерировать избыточные и ненужные сообщения.
| Плотная модель (QwQ-32B) | MoE-модель (DeepSeek-R1) | |
|---|---|---|
| Преимущества | Более низкая сложность обучения; понятный процесс | Высокая вычислительная эффективность (активирует часть экспертов во время инференса) |
| Преимущества | Связное рассуждение; полное задействование нейронов для контекстного понимания | Масштабируемая емкость модели за счет расширения экспертов |
| Недостатки | Высокие вычислительные затраты на обучение и инференс | Сложное обучение (требует сетей маршрутизации и балансировки нагрузки экспертов) |
| Недостатки | Ограниченная масштабируемость; склонность к переобучению; высокие затраты на хранение/развертывание | Накладные расходы на маршрутизацию (дополнительные вычисления для решений маршрутизации) |
Ни одна архитектура не идеальна. Выбор должен зависеть от требований задачи, характеристик данных, доступных вычислительных ресурсов и бюджетных ограничений.
Я считаю, что в ближайшем будущем мы будем рассматривать гибридные подходы — использовать MoE для первоначального извлечения знаний и грубой обработки, а затем плотные модели для глубокого рассуждения и уточнения, чтобы достигать более высокой производительности.
Почему Milvus?
Milvus — это open-source, высокопроизводительная и хорошо масштабируемая векторная база данных, которая может хранить, индексировать и выполнять поиск по неструктурированным данным миллиардного масштаба с помощью многомерных векторных эмбеддингов. Она идеально подходит для создания современных AI-приложений, таких как retrieval augmented generation (RAG), семантический поиск, мультимодальный поиск и рекомендательные системы.
Чтобы снизить возможные галлюцинации QwQ-32B (по сути, возможные галлюцинации LLM), Milvus хранит внешние или приватные знания и предоставляет контекстную информацию модели QwQ-32B. Это гарантирует, что модель QwQ-32B сможет генерировать более точные результаты.
Почему Ollama?
Ollama — это open-source платформа, которая упрощает локальное развертывание и управление большими языковыми моделями (LLMs). Она обеспечивает удобный пользовательский опыт без облака, позволяя легко загружать модели, устанавливать их и взаимодействовать с ними без необходимости в продвинутых технических навыках. Она позволяет пользователям быстро развертывать модели с помощью простых инструментов командной строки и интеграции с Docker, а также поддерживает управление Modelfile для упрощения контроля версий и повторного использования моделей.
Кроме того, Ollama предлагает богатую библиотеку моделей — от универсальных до предметно-ориентированных. Она обеспечивает кроссплатформенность и совместимость с аппаратным обеспечением — поддерживает macOS, Linux, Windows и развертывания в Docker-контейнерах — с автоматическим обнаружением GPU и приоритизацией ускорения. Она также предоставляет удобные для разработчиков инструменты, такие как REST API и Python SDK, облегчая интеграцию моделей в различные приложения.
И она обеспечивает конфиденциальность данных и гибкость, позволяя пользователям донастраивать, оптимизировать и развертывать решения на базе AI полностью на своих машинах.
Теперь давайте начнем создавать простой RAG-пайплайн с QwQ-32B в качестве языковой модели, Milvus в качестве векторной базы данных и Ollama в качестве фреймворка.
Подготовка
Зависимости и окружение
! pip install pymilvus ollama
Примечание: если вы используете Google, чтобы активировать только что установленные зависимости, вам может потребоваться перезапустить среду выполнения (нажмите на меню "Runtime" в верхней части экрана и выберите "Restart session" в выпадающем меню).
Подготовка данных
Мы используем страницы FAQ из Milvus Documentation 2.4.x в качестве приватных знаний в нашем RAG, что является хорошим источником данных для простого RAG-пайплайна.
Скачайте zip-файл и извлеките документы в папку milvus_docs.
! wget https://github.com/milvus-io/milvus-docs/releases/download/v2.4.6-preview/milvus_docs_2.4.x_en.zip
! unzip -q milvus_docs_2.4.x_en.zip -d milvus_docs
Мы загружаем все markdown-файлы из папки milvus_docs/en/faq. Для каждого документа мы просто используем "# " для разделения содержимого файла, что позволяет приблизительно отделить содержимое каждой основной части markdown-файла.
from glob import glob
text_lines = []
for file_path in glob("milvus_docs/en/faq/*.md", recursive=True):
with open(file_path, "r") as file:
file_text = file.read()
text_lines += file_text.split("# ")
Подготовка LLM и модели эмбеддингов
Ollama поддерживает несколько моделей как для задач на основе LLM, так и для генерации эмбеддингов, что упрощает разработку RAG-приложений. Для этой настройки:
- Мы будем использовать QwQ (32B) в качестве нашей LLM для задач генерации текста.
- Для генерации эмбеддингов мы будем использовать mxbai-embed-large, модель с 334 млн параметров, оптимизированную для семантического сходства.
Перед началом убедитесь, что обе модели загружены локально:
! ollama pull mxbai-embed-large
! ollama pull qwq
Когда эти модели готовы, мы можем перейти к реализации рабочих процессов генерации на основе LLM и поиска на основе эмбеддингов.
import ollama
from ollama import Client
ollama_client = Client(host="http://localhost:11434")
def emb_text(text):
response = ollama_client.embeddings(model="mxbai-embed-large", prompt=text)
return response["embedding"]
Сгенерируйте тестовый эмбеддинг и выведите его размерность и первые несколько элементов.
test_embedding = emb_text("This is a test")
embedding_dim = len(test_embedding)
print(embedding_dim)
print(test_embedding[:10])
1024
[0.23217937350273132, 0.42540550231933594, 0.19742339849472046, 0.4618139863014221, -0.46017369627952576, -0.14087969064712524, -0.18214142322540283, -0.07724273949861526, 0.40015509724617004, 0.8331164121627808]
Загрузка данных в Milvus
Создание коллекции
from pymilvus import MilvusClient
milvus_client = MilvusClient(uri="./milvus_demo.db")
collection_name = "my_rag_collection"
Что касается настройки параметров MilvusClient:
- Установка
uriв виде локального файла, например./milvus.db, является наиболее удобным методом, поскольку она автоматически использует Milvus Lite для хранения всех данных в этом файле. - Если у вас большой объем данных, вы можете развернуть более производительный сервер Milvus в docker или kubernetes. В этой настройке используйте серверный
uri, напримерhttp://localhost:19530, в качестве вашегоuri. - Если вы хотите использовать Zilliz Cloud, полностью управляемый облачный сервис для Milvus, настройте
uriиtoken, которые соответствуют Public Endpoint и Api key в Zilliz Cloud.
Проверьте, существует ли коллекция, и удалите ее, если она существует.
if milvus_client.has_collection(collection_name):
milvus_client.drop_collection(collection_name)
Создайте новую коллекцию с указанными параметрами.
Если мы не укажем информацию о полях, Milvus автоматически создаст поле id по умолчанию для первичного ключа и поле vector для хранения векторных данных. Зарезервированное JSON-поле используется для хранения полей, не определенных схемой, и их значений.
milvus_client.create_collection(
collection_name=collection_name,
dimension=embedding_dim,
metric_type="IP", # Inner product distance
consistency_level="Strong", # Strong consistency level
)
Вставка данных
Пройдитесь по строкам текста, создайте эмбеддинги, а затем вставьте данные в Milvus.
Здесь есть новое поле text, которое является неопределенным полем в схеме коллекции. Оно будет автоматически добавлено в зарезервированное динамическое JSON-поле, которое на высоком уровне можно рассматривать как обычное поле.
from tqdm import tqdm
data = []
for i, line in enumerate(tqdm(text_lines, desc="Creating embeddings")):
data.append({"id": i, "vector": emb_text(line), "text": line})
milvus_client.insert(collection_name=collection_name, data=data)
Creating embeddings: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████| 72/72 [00:06<00:00, 11.86it/s]
{'insert_count': 72, 'ids': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71], 'cost': 0}
Построение RAG-пайплайна
Извлечение данных для запроса
Давайте зададим частый вопрос о Milvus.
question = "How is data stored in milvus?"
Выполните поиск вопроса в коллекции и извлеките топ-3 семантических совпадения.
search_res = milvus_client.search(
collection_name=collection_name,
data=[
emb_text(question)
], # Use the `emb_text` function to convert the question to an embedding vector
limit=3, # Return top 3 results
search_params={"metric_type": "IP", "params": {}}, # Inner product distance
output_fields=["text"], # Return the text field
)
Давайте посмотрим на результаты поиска по запросу.
import json
retrieved_lines_with_distances = [
(res["entity"]["text"], res["distance"]) for res in search_res[0]
]
print(json.dumps(retrieved_lines_with_distances, indent=4))
[
[
" Where does Milvus store data?\n\nMilvus deals with two types of data, inserted data and metadata. \n\nInserted data, including vector data, scalar data, and collection-specific schema, are stored in persistent storage as incremental log. Milvus supports multiple object storage backends, including [MinIO](https://min.io/), [AWS S3](https://aws.amazon.com/s3/?nc1=h_ls), [Google Cloud Storage](https://cloud.google.com/storage?hl=en#object-storage-for-companies-of-all-sizes) (GCS), [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs), [Alibaba Cloud OSS](https://www.alibabacloud.com/product/object-storage-service), and [Tencent Cloud Object Storage](https://www.tencentcloud.com/products/cos) (COS).\n\nMetadata are generated within Milvus. Each Milvus module has its own metadata that are stored in etcd.\n\n###",
231.9922637939453
],
[
"How does Milvus flush data?\n\nMilvus returns success when inserted data are loaded to the message queue. However, the data are not yet flushed to the disk. Then Milvus' data node writes the data in the message queue to persistent storage as incremental logs. If `flush()` is called, the data node is forced to write all data in the message queue to persistent storage immediately.\n\n###",
226.54090881347656
],
[
"What is the maximum dataset size Milvus can handle?\n\n \nTheoretically, the maximum dataset size Milvus can handle is determined by the hardware it is run on, specifically system memory and storage:\n\n- Milvus loads all specified collections and partitions into memory before running queries. Therefore, memory size determines the maximum amount of data Milvus can query.\n- When new entities and and collection-related schema (currently only MinIO is supported for data persistence) are added to Milvus, system storage determines the maximum allowable size of inserted data.\n\n###",
210.63682556152344
]
]
Использование LLM для получения RAG-ответа
Преобразуйте извлеченные документы в строковый формат.
context = "\n".join(
[line_with_distance[0] for line_with_distance in retrieved_lines_with_distances]
)
Определите системный и пользовательский промпты для LLM. Этот промпт собирается с извлеченными документами из Milvus.
SYSTEM_PROMPT = """
Human: You are an AI assistant. You are able to find answers to the questions from the contextual passage snippets provided.
"""
USER_PROMPT = f"""
Use the following pieces of information enclosed in <context> tags to provide an answer to the question enclosed in <question> tags.
<context>
{context}
</context>
<question>
{question}
</question>
"""
Используйте модель QwQ (32B), предоставляемую Ollama, чтобы сгенерировать ответ на основе промптов.
from ollama import chat
from ollama import ChatResponse
response: ChatResponse = ollama_client.chat(
model="qwq",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": USER_PROMPT},
],
)
print(response["message"]["content"])
<think>
Хорошо, мне нужно ответить на вопрос «Как данные хранятся в Milvus?» с использованием предоставленного контекста. Начну с внимательного чтения контекста.
Сначала посмотрим на первый раздел контекста под названием «Где Milvus хранит данные?». В нем упоминается, что Milvus обрабатывает два типа данных: вставленные данные и метаданные.
Вставленные данные включают векторы, скалярные данные и схему коллекции, которые хранятся в виде инкрементальных журналов в постоянном хранилище. Перечислены поддерживаемые бэкенды, такие как MinIO, S3, GCS и т. д. Поэтому я должен отметить, что вставленные данные попадают в эти объектные хранилища через инкрементальные журналы.
Затем метаданные хранятся в etcd для каждого модуля Milvus. Это очевидно — метаданные в etcd.
Во втором разделе о сбросе данных объясняется, что данные сначала попадают в очередь сообщений, а затем в постоянное хранилище при сбросе. Но это скорее о процессе, а не о том, где они хранятся, поэтому, возможно, стоит упомянуть, что вставка сначала происходит в очередь сообщений, прежде чем данные будут записаны.
Третий раздел о максимальном размере набора данных упоминает аппаратные ограничения: память для данных, доступных для запросов, и хранилище для постоянного хранения. Это может относиться к тому, как данные управляются с точки зрения памяти и диска. Пользователь спросил «как хранятся данные», что может включать как место (например, объектные хранилища, etcd), так и процесс (очередь сообщений, затем постоянные журналы).
Собирая все вместе, ответ должен упомянуть:
- Два типа: вставленные данные и метаданные.
Для вставленных данных:
- Хранятся в виде инкрементальных журналов в постоянном хранилище с использованием бэкендов вроде MinIO, S3 и т. д.
- Изначально загружаются в очереди сообщений, затем записываются на диск. flush() при необходимости принудительно выполняет немедленную запись.
Метаданные:
- Хранятся в etcd для каждого модуля.
Также в части про аппаратное обеспечение говорится, что память ограничивает размер данных, доступных для запросов, а хранилище (например, объектные хранилища) ограничивает общий размер набора данных. Поэтому, возможно, стоит упомянуть эти факторы как часть того, как хранятся данные, учитывая ограничения емкости.
Я должен структурировать это ясно, возможно, в виде списков или разделов о вставленных данных и метаданных, затем процессы вроде сброса и, наконец, ограничения, основанные на аппаратном обеспечении.
</think>
Данные в Milvus хранятся двумя основными способами:
1. **Вставленные данные**:
- Включают векторные данные, скалярные данные и схему коллекции.
- Хранятся в виде **инкрементальных журналов** в **постоянном хранилище**, поддерживаемом объектными хранилищами, такими как MinIO, AWS S3, Google Cloud Storage (GCS), Azure Blob Storage, Alibaba Cloud OSS и Tencent Cloud Object Storage (COS).
- При вставке сначала загружаются в **очередь сообщений**. Milvus возвращает успешный результат после вставки в очередь, но не записывает данные на диск немедленно. Затем фоновый процесс записывает эти данные в постоянное хранилище. Метод `flush()` может принудительно выполнить немедленную запись всех данных из очереди в хранилище.
2. **Метаданные**:
- Генерируются внутри модулей Milvus (например, конфигурации коллекций, разделы).
- Хранятся в **etcd**, распределенном хранилище ключ-значение.
**Аппаратные аспекты**:
- **Память**: Объем данных, которые Milvus может запрашивать, ограничен системной памятью, поскольку он загружает указанные коллекции/разделы в память для выполнения запросов.
- **Емкость хранилища**: Максимальный размер набора данных ограничен базовым бэкендом хранилища (например, объектным хранилищем), где все вставленные данные и схема хранятся инкрементально.
Отлично! Мы успешно построили RAG-конвейер с Milvus, QWQ-32B и Ollama.
Заключение
Интегрируя эти технологии, мы можем создать RAG-систему, которая использует Milvus для эффективного хранения и извлечения данных, а также возможности рассуждения QwQ-32B для генерации точных и контекстуально релевантных ответов. Ollama упрощает процесс развертывания, обеспечивая бесшовную и эффективную настройку. Такая комбинация особенно полезна для приложений, требующих извлечения и генерации информации в реальном времени, таких как обучение с помощью ИИ, решение задач на основе логики и многое другое.
Мы надеемся, что, следуя этому руководству, вы сможете создавать RAG-системы, адаптированные к вашим потребностям, и получать реальную пользу от собственных разработок.
Читать далее

Zilliz Cloud Update: Smarter Autoscaling for Cost Savings, Stronger Compliance with Audit Logs, and More
What's new in Zilliz Cloud? Smarter autoscaling with scale-down, audit logs GA, enhanced SSO, and Milvus 2.6 in Private Preview.

Zilliz Cloud Delivers Better Performance and Lower Costs with Arm Neoverse-based AWS Graviton
Zilliz Cloud adopts Arm-based AWS Graviton3 CPUs to cut costs, speed up AI vector search, and power billion-scale RAG and semantic search workloads.

Legal Document Analysis: Harnessing Zilliz Cloud's Semantic Search and RAG for Legal Insights
Enhance legal document analysis with Zilliz Cloud’s Semantic Search and RAG. Improve accuracy, efficiency, and scalability for contracts, case law, and compliance.



