Cómo construir RAG con Milvus, QwQ-32B y Ollama
Los modelos de IA están evolucionando rápidamente, y QwQ-32B de Alibaba está haciendo una fuerte entrada recientemente. Con solo 32 mil millones de parámetros, este modelo de razonamiento de tamaño medio ofrece un rendimiento impresionante en razonamiento matemático, escritura creativa y generación de código, rivalizando con modelos mucho más grandes como DeepSeek-R1. Su eficiencia y precisión en distintos benchmarks lo convierten en una opción atractiva para una amplia gama de aplicaciones de IA.
11.jpeg
Figura 1: Rendimiento de QwQ-32B en comparación con otros modelos líderes (Fuente)
Más allá de sus capacidades, QwQ-32B destaca por su accesibilidad. A diferencia de algunos modelos masivos que requieren hardware especializado, se ejecuta eficientemente en GPU de consumo como la RTX 4090, lo que lo convierte en una excelente opción para desarrolladores e investigadores que buscan IA de alta calidad sin recursos a escala empresarial. Sin embargo, al ser un modelo denso, QwQ-32B a veces puede tener dificultades con el razonamiento complejo en textos largos y puede presentar alucinaciones, especialmente al manejar ventanas de contexto extendidas.
Para mitigar estos desafíos y mejorar su fiabilidad, podemos integrar QwQ-32B con Retrieval-Augmented Generation (RAG). En este tutorial, veremos cómo construir un sistema RAG usando QwQ-32B, Milvus (una base de datos vectorial de alto rendimiento) y Ollama. Al final, tendrás una canalización de IA optimizada y potente que equilibra eficiencia, precisión y escalabilidad.
Antes de entrar en detalles sobre cómo construir una aplicación RAG, repasemos rápidamente todas las tecnologías que usaremos para este tutorial.
QwQ-32B vs. DeepSeek-R1
Tanto QwQ-32B como DeepSeek-R1 se especializan en razonamiento, pero este último adopta una arquitectura Mixture-of-Experts (MoE), mientras que QwQ-32B es un modelo denso clásico.
- Los modelos MoE destacan en escenarios intensivos en conocimiento (p. ej., sistemas de Q&A, recuperación de información) y procesamiento de datos a gran escala, donde diferentes expertos manejan subconjuntos de datos distintos para mejorar la eficiencia. Sin embargo, su enorme cantidad de parámetros exige recursos en la nube o servidores dedicados.
- Los modelos densos, aunque computacionalmente intensivos, son más adecuados para tareas de razonamiento profundo y coherente (p. ej., razonamiento lógico complejo, comprensión lectora en profundidad) y diseño de algoritmos donde el rendimiento en tiempo real no es imprescindible. Su tamaño compacto permite el despliegue local, pero a veces puede producir mensajes redundantes e innecesarios.
| Modelo denso (QwQ-32B) | Modelo MoE (DeepSeek-R1) | |
|---|---|---|
| Ventajas | Menor complejidad de entrenamiento; proceso sencillo | Alta eficiencia computacional (activa expertos parciales durante la inferencia) |
| Ventajas | Razonamiento coherente; participación completa de las neuronas para la comprensión contextual | Capacidad del modelo escalable mediante la expansión de expertos |
| Desventajas | Altos costos computacionales para entrenamiento e inferencia | Entrenamiento complejo (requiere redes de compuerta y balanceo de carga de expertos) |
| Desventajas | Escalabilidad limitada; propenso al sobreajuste; altos costos de almacenamiento/despliegue | Sobrecarga de enrutamiento (cómputo adicional para decisiones de compuerta) |
Ninguna arquitectura es perfecta. La elección debe depender de los requisitos de la tarea, las características de los datos, los recursos computacionales disponibles y las restricciones presupuestarias.
Creo que veremos enfoques híbridos en un futuro cercano: usar MoE para la recuperación inicial de conocimiento y el procesamiento general, y luego modelos densos para el razonamiento profundo y el refinamiento, con el fin de lograr mejores rendimientos.
¿Por qué Milvus?
Milvus es una base de datos vectorial de código abierto, alto rendimiento y gran escalabilidad que puede almacenar, indexar y buscar datos no estructurados a escala de miles de millones mediante incrustaciones vectoriales de alta dimensión. Es perfecta para crear aplicaciones modernas de IA, como generación aumentada por recuperación (RAG), búsqueda semántica, búsqueda multimodal y sistemas de recomendación.
Para mitigar las posibles alucinaciones de QwQ-32B (de hecho, las posibles de los LLM), Milvus almacena conocimiento externo o privado y proporciona información contextual con el modelo QwQ-32B. Esto garantiza que el modelo QwQ-32B pueda generar resultados más precisos.
¿Por qué Ollama?
Ollama es una plataforma de código abierto que simplifica el despliegue y la gestión locales de modelos de lenguaje grandes (LLMs). Proporciona una experiencia fácil de usar y sin nube, que permite descargas, instalación e interacción con modelos sin esfuerzo y sin requerir habilidades técnicas avanzadas. Permite a los usuarios desplegar modelos rápidamente mediante herramientas sencillas de línea de comandos e integración con Docker, y admite la gestión de Modelfile para agilizar el control de versiones y la reutilización de modelos.
Además, Ollama ofrece una amplia biblioteca de modelos, desde los de propósito general hasta los específicos de dominio. Proporciona compatibilidad multiplataforma y de hardware —con soporte para despliegues en macOS, Linux, Windows y contenedores Docker— con detección automática de GPU y priorización de la aceleración. También proporciona herramientas fáciles de usar para desarrolladores, como REST API y Python SDK, lo que facilita la integración de modelos en diversas aplicaciones.
Y garantiza la privacidad de los datos y la flexibilidad, permitiendo a los usuarios ajustar, optimizar y desplegar soluciones impulsadas por IA completamente en sus máquinas.
Ahora, comencemos a crear una canalización RAG simple con QwQ-32B como modelo de lenguaje, Milvus como base de datos vectorial, y Ollama como framework.
Preparación
Dependencias y entorno
! pip install pymilvus ollama
Nota: Si estás usando Google, para habilitar las dependencias recién instaladas, es posible que necesites reiniciar el entorno de ejecución (haz clic en el menú "Runtime" en la parte superior de la pantalla y selecciona "Restart session" en el menú desplegable).
Preparar los datos
Usamos las páginas de preguntas frecuentes de la documentación de Milvus 2.4.x como conocimiento privado en nuestro RAG, lo cual es una buena fuente de datos para una canalización RAG simple.
Descarga el archivo zip y extrae los documentos en la carpeta 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
Cargamos todos los archivos markdown desde la carpeta milvus_docs/en/faq. Para cada documento, simplemente usamos "# " para separar el contenido del archivo, lo que puede separar aproximadamente el contenido de cada parte principal del archivo 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("# ")
Preparar el LLM y el modelo de embeddings
Ollama admite múltiples modelos tanto para tareas basadas en LLM como para generación de embeddings, lo que facilita desarrollar aplicaciones RAG. Para esta configuración:
- Usaremos QwQ (32B) como nuestro LLM para tareas de generación de texto.
- Para la generación de embeddings, usaremos mxbai-embed-large, un modelo de 334M parámetros optimizado para similitud semántica.
Antes de comenzar, asegúrate de que ambos modelos estén descargados localmente:
! ollama pull mxbai-embed-large
! ollama pull qwq
Con estos modelos listos, podemos proceder a implementar flujos de trabajo de generación impulsada por LLM y recuperación basada en embeddings.
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"]
Genera un embedding de prueba e imprime su dimensión y sus primeros elementos.
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]
Cargar datos en Milvus
Crear la Collection
from pymilvus import MilvusClient
milvus_client = MilvusClient(uri="./milvus_demo.db")
collection_name = "my_rag_collection"
En cuanto a la configuración de los parámetros de MilvusClient:
- Establecer el
uricomo un archivo local, p. ej../milvus.db, es el método más conveniente, ya que utiliza automáticamente Milvus Lite para almacenar todos los datos en este archivo. - Si tienes una gran escala de datos, puedes configurar un servidor Milvus con mayor rendimiento en docker o kubernetes. En esta configuración, usa el
uridel servidor, p. ej.http://localhost:19530, como tuuri. - Si quieres usar Zilliz Cloud, el servicio en la nube totalmente administrado para Milvus, ajusta el
uriy eltoken, que corresponden al Public Endpoint y Api key en Zilliz Cloud.
Comprueba si la collection ya existe y elimínala si existe.
if milvus_client.has_collection(collection_name):
milvus_client.drop_collection(collection_name)
Crea una nueva collection con los parámetros especificados.
Si no especificamos ninguna información de campo, Milvus creará automáticamente un campo id predeterminado para la clave primaria, y un campo vector para almacenar los datos vectoriales. Se usa un campo JSON reservado para almacenar campos no definidos por el esquema y sus valores.
milvus_client.create_collection(
collection_name=collection_name,
dimension=embedding_dim,
metric_type="IP", # Inner product distance
consistency_level="Strong", # Strong consistency level
)
Insertar datos
Itera por las líneas de texto, crea embeddings y luego inserta los datos en Milvus.
Aquí hay un nuevo campo text, que es un campo no definido en el esquema de la collection. Se añadirá automáticamente al campo dinámico JSON reservado, que puede tratarse como un campo normal a alto nivel.
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}
Construir un pipeline RAG
Recuperar datos para una consulta
Especifiquemos una pregunta frecuente sobre Milvus.
question = "How is data stored in milvus?"
Busca la pregunta en la colección y recupera las 3 coincidencias semánticas principales.
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
)
Echemos un vistazo a los resultados de búsqueda de la consulta.
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
]
]
Usar LLM para obtener una respuesta RAG
Convierte los documentos recuperados en un formato de cadena.
context = "\n".join(
[line_with_distance[0] for line_with_distance in retrieved_lines_with_distances]
)
Define prompts de sistema y de usuario para LLM. Este prompt se ensambla con los documentos recuperados de 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>
"""
Usa el modelo QwQ (32B) proporcionado por Ollama para generar una respuesta basada en los prompts.
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>
Bien, necesito responder a la pregunta "¿Cómo se almacenan los datos en Milvus?" usando el contexto proporcionado. Déjame empezar leyendo cuidadosamente el contexto.
Primero, mirando la primera sección del contexto titulada "¿Dónde almacena Milvus los datos?". Menciona que Milvus maneja dos tipos de datos: datos insertados y metadatos.
Los datos insertados incluyen vectores, datos escalares y el esquema de la colección, almacenados como registros incrementales en almacenamiento persistente. Los backends compatibles están listados, como MinIO, S3, GCS, etc. Así que debería señalar que los datos insertados van a estos almacenamientos de objetos mediante registros incrementales.
Luego, los metadatos se almacenan en etcd para cada módulo de Milvus. Eso es sencillo: metadatos en etcd.
El segundo contexto sobre el vaciado de datos explica que los datos primero van a la cola de mensajes y luego al almacenamiento persistente cuando se vacían. Pero esto podría tratar más sobre el proceso que sobre dónde se almacenan, así que quizá mencione que la inserción se realiza primero en la cola de mensajes antes de escribirse definitivamente.
La tercera sección sobre el tamaño máximo del conjunto de datos menciona restricciones de hardware: memoria para datos consultables y almacenamiento para persistencia. Esto podría relacionarse con cómo se gestionan los datos en términos de memoria frente a disco. El usuario preguntó "cómo se almacenan los datos", lo que podría incluir tanto dónde (como almacenamientos de objetos, etcd) como el proceso (cola de mensajes y luego registros persistentes).
Uniéndolo todo, la respuesta debería mencionar:
- Dos tipos: datos insertados y metadatos.
Para los datos insertados:
- Se almacenan como registros incrementales en almacenamiento persistente usando backends como MinIO, S3, etc.
- Inicialmente se cargan en colas de mensajes y luego se escriben en disco. El método flush() fuerza la escritura inmediata si es necesario.
Metadatos:
- Se almacenan en etcd para cada módulo.
Además, la parte de hardware dice que la memoria limita el tamaño de los datos consultables y el almacenamiento (como los almacenamientos de objetos) limita el tamaño total del conjunto de datos. Así que quizá mencione estos factores como parte de cómo se almacenan los datos, considerando las restricciones de capacidad.
Debería estructurar esto claramente, quizá con viñetas o secciones sobre datos insertados frente a metadatos, luego procesos como el vaciado y finalmente las limitaciones basadas en hardware.
</think>
Los datos en Milvus se almacenan de dos formas principales:
1. **Datos insertados**:
- Incluyen datos vectoriales, datos escalares y el esquema de la colección.
- Se almacenan como **registros incrementales** en **almacenamiento persistente**, compatible con backends de almacenamiento de objetos como MinIO, AWS S3, Google Cloud Storage (GCS), Azure Blob Storage, Alibaba Cloud OSS y Tencent Cloud Object Storage (COS).
- Inicialmente se cargan en una **cola de mensajes** cuando se insertan. Milvus devuelve éxito al insertarlos en la cola, pero no los escribe inmediatamente en disco. Un proceso en segundo plano escribe posteriormente estos datos en el almacenamiento persistente. El método `flush()` puede forzar la escritura inmediata de todos los datos en cola al almacenamiento.
2. **Metadatos**:
- Generados internamente por los módulos de Milvus (por ejemplo, configuraciones de colección, particiones).
- Se almacenan en **etcd**, un almacén distribuido de clave-valor.
**Consideraciones de hardware**:
- **Memoria**: La cantidad de datos que Milvus puede consultar está limitada por la memoria del sistema, ya que carga colecciones/particiones especificadas en memoria para las consultas.
- **Capacidad de almacenamiento**: El tamaño máximo del conjunto de datos está limitado por el backend de almacenamiento subyacente (por ejemplo, almacenamiento de objetos), que almacena todos los datos insertados y el esquema de forma incremental.
¡Genial! Hemos construido con éxito una canalización RAG con Milvus, QWQ-32B y Ollama.
Conclusión
Al integrar estas tecnologías, podemos construir un sistema RAG que aprovecha Milvus para el almacenamiento y la recuperación eficientes de datos, y las capacidades de razonamiento de QwQ-32B para generar respuestas precisas y contextualmente relevantes. Ollama agiliza el proceso de despliegue, permitiendo una configuración fluida y eficiente. Esta combinación es particularmente beneficiosa para aplicaciones que requieren recuperación y generación de información en tiempo real, como la tutoría asistida por IA, la resolución de problemas basada en lógica y más.
Esperamos que, al seguir este tutorial, puedas crear sistemas RAG adaptados a tus necesidades y beneficiarte verdaderamente de tus propias creaciones.
Sigue leyendo

Zilliz Skills Breakdown: How AI Agents Master Vector Databases
Zilliz's Milvus Skill (pymilvus, 7 files) and Zilliz Cloud Skill (zilliz-cli, 14 modules) bring vector-DB dev and ops into one Claude Code session.

What Exactly Are AI Agents? Why OpenAI and LangChain Are Fighting Over Their Definition?
AI agents are software programs powered by AI that can perceive their environment, make decisions, and take actions to achieve a goal—often autonomously.

Cosmos World Foundation Model Platform for Physical AI
NVIDIA's Cosmos platform enables safe, digital twin training of GenAI models for physical applications, overcoming data scarcity and safety challenges.



