Blog | Desarrollador | | 14 min de lectura

Cómo configurar LangChain con VectorAI DB para RAG en las propias instalaciones

LangChain con la base de datos de VectorAI

Resumen

  • El tutorial muestra cómo crear un proceso RAG local utilizando LangChain con VectorAI DB como almacén de vectores.
  • La abstracción VectorStore de LangChain permite a los equipos cambiar de backend vectorial sin tener que reescribir la cadena de recuperación.
  • Los desarrolladores pueden utilizar las representaciones de OpenAI o trabajar de forma totalmente local con las representaciones de HuggingFace y Ollama.
  • El flujo de trabajo abarca la configuración de Docker, la segmentación de documentos, el almacenamiento de vectores, la búsqueda de similitudes y una cadena RAG completa.
  • Este modelo se adapta a casos de uso en instalaciones propias, en entornos aislados físicamente, en el perímetro y relacionados con la residencia de datos, en los que los almacenes vectoriales en la nube no son la opción ideal.

LangChain’s VectorStore La abstracción separa la cadena de recuperación de la base de datos que almacena tus representaciones vectoriales. Esto significa que puedes sustituir un almacén de vectores alojado por VectorAI DB sin modificar apenas la estructura de la cadena formada por el recuperador, el prompt y el LLM.

Este tutorial te guía en la creación de ese flujo de trabajo desde cero. Ejecutarás VectorAI DB de forma local con Docker, cargarás y dividirás documentos en fragmentos, generarás representaciones, las almacenarás en una base de datos vectorial local y conectarás el almacén a una cadena RAG de LangChain. El tutorial abarca tanto las representaciones de OpenAI como las locales de HuggingFace, y muestra cómo sustituir el modelo de lenguaje grande (LLM) de OpenAI por Ollama para obtener un flujo de trabajo totalmente local.

Al finalizar, dispondrás de una aplicación de «Retrieval-Augmented Generation» (RAG) operativa que funciona sin necesidad de una base de datos vectorial en la nube. Si estás migrando desde Pinecone, una implementación alojada de Qdrant u otro almacén vectorial gestionado, el cambio es menor de lo que podría parecer. Para conocer los motivos por los que los desarrolladores están pasando de los almacenes vectoriales alojados a alternativas locales, consulta el artículo comparativo sobre bases de datos vectoriales integradas.

Arquitectura de bases de datos de Langchain y VectorAI

La arquitectura del flujo de trabajo de LangChain + VectorAI DB. Ruta de ingesta (arriba): los documentos pasan por el cargador, el divisor de texto y el modelo de incrustación, y llegan a VectorAI DB. Ruta de consulta (abajo): la pregunta del usuario se incrusta con el mismo modelo; VectorAI DB recupera los fragmentos más cercanos y los resultados pasan por la plantilla de prompt y el LLM para generar una respuesta con referencias.

Requisitos previos. 

Comprueba lo siguiente antes de empezar.

  • Docker está instalado y en funcionamiento.
  • Python 3.10 o superior.
  • VectorAI DB Community Edition ejecutándose localmente. Si aún no lo has configurado, sigue la guía de instalación de VectorAI DB antes de continuar.
  • Ollama instalado con llama3.2 tirado. Correr ollama pull llama3.2 en un ordenador con conexión a Internet.

Cómo funciona la interfaz VectorStore de LangChain

LangChain’s VectorStore La clase base define una interfaz estándar que implementa todo backend compatible. La interfaz abarca cuatro operaciones fundamentales: from_documents() para crear un almacén e incorporar documentos en una sola llamada, add_texts() para añadir contenido a una tienda ya existente, similarity_search() para recuperar los documentos más relevantes para una consulta, y as_retriever() para convertir el almacén en un recuperador que se utilice en una cadena de LangChain Expression Language (LCEL).

Cualquier backend compatible puede integrarse en la ruta de recuperación común de LangChain, aunque las características específicas de cada backend —como la sintaxis de filtrado, las métricas de distancia y la búsqueda híbrida— siguen variando. Lo que no cambia es la propia cadena de recuperación. El recuperador, la plantilla de prompt, el modelo de lenguaje grande (LLM) y el analizador de salida siguen siendo los mismos, independientemente del backend que almacene los vectores.

El siguiente diagrama lo ilustra con claridad. El panel de la izquierda muestra una instancia de un almacén vectorial en la nube. El panel de la derecha muestra el equivalente en VectorAI DB. Cambian la instrucción de importación de la línea 1, así como el nombre de la clase y los parámetros de conexión de la línea 7. Las líneas 8 a 17 (los documentos, la incrustación, la llamada al recuperador y la cadena LCEL) son idénticas en ambos casos.

cambiar el backend de almacenamiento de vectores

Comparación entre la instanciación de Cloud Vector Store y la de VectorAI DB. La importación y el cambio de nombre de la clase. La lógica de la cadena de las líneas 15 a 17 es idéntica.

Paso 1: Iniciar VectorAI DB

Ejecuta VectorAI DB como un contenedor de Docker. Descarga la imagen e iníciala con un volumen persistente:

docker pull actian/vectorai:latest
docker run -d --name vectorai \
  -v ./local_data:/var/lib/actian-vectorai \
  -p 6573-6575:6573-6575 \
  -e ACTIAN_VECTORAI_ACCEPT_EULA=YES \
  actian/vectorai:latest

El contenedor expone REST en el puerto 6573, gRPC en el puerto 6574 y una interfaz de usuario web local en el puerto 6575. La integración de LangChain se conecta a través de gRPC en localhost:6574. La edición Community es suficiente para este tutorial y admite hasta 5.000 vectores almacenados.

Comprueba que el contenedor se haya iniciado correctamente:

docker logs vectorai

Deberías ver Ready to accept connections... cerca del final de la salida antes de continuar.

estado de la conexión de salida del terminal

Salida del terminal correspondiente a la comprobación del estado de la conexión, en la que se confirman la versión y el estado del servidor de la base de datos de VectorAI.

Paso 2: Instalar las dependencias de Python

Instala todos los paquetes necesarios con un solo comando:

pip install langchain langchain-core langchain-text-splitters \
  langchain-actian-vectorai langchain-openai \
  langchain-huggingface langchain-ollama \
  actian-vectorai-client sentence-transformers

LangChain ha ido dividiendo sus integraciones en paquetes independientes. langchain-huggingface sustituye a las clases de HuggingFace que antes se encontraban en langchain-community, y langchain-ollama sustituye a las clases de Ollama. En este tutorial se utilizan en todo momento los paquetes independientes actuales. El uso de la versión obsoleta langchain-community Estas rutas generarán advertencias de obsolescencia en las versiones más recientes de LangChain.

Si tienes pensado utilizar la ruta de incrustación de OpenAI en el paso 5, configura tu clave de interfaz de programación de aplicaciones (API) antes de ejecutar el código de incrustación:

export OPENAI_API_KEY="your-api-key-here"

Paso 3: Conectarse a la base de datos de VectorAI desde Python

Comprueba la conexión antes de cargar cualquier documento. Crea una colección de prueba, comprueba que aparece en la lista de colecciones y, a continuación, elimínala. Este paso permite verificar que el servidor acepta tanto operaciones de lectura como de escritura antes de continuar.

from actian_vectorai import VectorAIClient, VectorParams, Distance
# Connect to VectorAI DB over gRPC.
client = VectorAIClient("localhost:6574")
client.connect()
# Create a test collection to confirm the server accepts writes.
client.collections.create(
    "connection_test",
    vectors_config=VectorParams(size=128, distance=Distance.Cosine),
)
# Confirm the collection was created.
collections = client.collections.list()
print(f"Collections: {collections}")
# Expected: ['connection_test']

# Remove the test collection before proceeding.
client.collections.delete("connection_test")
print("Connection verified. Ready to proceed.")
client.close()

Si client.connect() plantea una ConnectionError, comprueba que el contenedor de Docker se esté ejecutando con docker ps y que el puerto 6574 no esté bloqueado por otro proceso.

Paso 4: Cargar y dividir los documentos en fragmentos

Utiliza un pequeño conjunto de documentos en línea, de modo que no tengas dependencias externas para este paso. El contenido abarca conceptos relacionados con las bases de datos vectoriales y el RAG, que permiten obtener resultados de búsqueda significativos en los pasos 6 y 7.

from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter

# Inline documents keep this tutorial self-contained.
# In a production pipeline, replace this with a document loader
# such as PyPDFLoader, DirectoryLoader, or a custom ingestion process.
raw_documents = [
    Document(page_content="""A vector database stores high-dimensional numerical
representations of data called embeddings. Each embedding captures the semantic
meaning of the original content, allowing the database to find similar items by
comparing their positions in vector space rather than matching exact keywords.""",
    metadata={"source": "intro", "topic": "vector-databases"}),

    Document(page_content="""Retrieval-Augmented Generation combines a retrieval
system with a language model. The retrieval component finds relevant documents from
a vector store based on the user question, and the language model generates an answer
grounded in those retrieved documents rather than relying on its training data alone.""",
    metadata={"source": "intro", "topic": "rag"}),

    Document(page_content="""Embedding models convert text into fixed-length numerical
vectors. The choice of embedding model determines the vector dimension and the quality
of semantic similarity. OpenAI text-embedding-ada-002 produces 1536-dimensional vectors.
Sentence transformers such as all-MiniLM-L6-v2 produce 384-dimensional vectors and
run locally without an external API key.""",
    metadata={"source": "intro", "topic": "embeddings"}),

    Document(page_content="""Metadata filtering narrows the candidate set before
running similarity search. Attaching fields such as document type, date, or source
to each stored vector enables queries scoped to a specific subset of your collection
without changing the embedding or search logic.""",
    metadata={"source": "intro", "topic": "filtering"}),
]

# RecursiveCharacterTextSplitter preserves sentence boundaries before splitting.
splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=50)
docs = splitter.split_documents(raw_documents)

print(f"Produced {len(docs)} chunks from {len(raw_documents)} documents.")

El metadata campo en cada uno Document se almacena como carga útil en la base de datos de VectorAI y está disponible para su filtrado en el momento de la búsqueda.

Número de fragmentos de salida del terminal

Salida de la terminal que muestra el recuento de fragmentos generados a partir de los cuatro documentos en línea.

Paso 5: Insertar y guardar documentos

Esta es la parte fundamental del tutorial. Hay dos opciones disponibles, dependiendo de si dispones de una clave API de OpenAI o si necesitas trabajar totalmente sin conexión. Elige una de ellas y utilízala de forma coherente a lo largo de los pasos 6 y 7.

Hay una restricción que se aplica a ambas vías. Una colección creada con un modelo de incrustación no puede aceptar vectores de otro modelo, ya que las dimensiones son diferentes. Las incrustaciones de OpenAI tienen 1536 dimensiones. El modelo de HuggingFace que se utiliza a continuación genera vectores de 384 dimensiones. Si cambias de modelo de incrustación entre ejecuciones, pasa force_recreate=True para eliminar y volver a crear la colección automáticamente.

Vía 1: Embeddings de OpenAI

from langchain_actian_vectorai import ActianVectorAIVectorStore
from langchain_openai import OpenAIEmbeddings
from actian_vectorai import VectorAIClient

store = ActianVectorAIVectorStore.from_documents(
    documents=docs,
    embedding=OpenAIEmbeddings(),    # produces 1536-dim vectors
    collection_name="rag_documents",
    url="localhost:6574",
    force_recreate=True,
)

print("Done.")

# Confirm vectors are stored and queryable.
client = VectorAIClient("localhost:6574")
client.connect()
print(f"Active collections: {client.collections.list()}")
test = store.similarity_search("vector database", k=1)
print(f"Test search returned {len(test)} result. Vectors are queryable.")
client.close()

from_documents() gestiona la conexión a la base de datos de VectorAI, crea la colección e inserta los vectores en una sola llamada. Esta es la única parte del proceso que cambia cuando se cambia el backend.

Ruta 2: Incrustaciones locales de HuggingFace. Utiliza esta ruta si el proceso debe ejecutarse sin llamadas a API externas.

from langchain_actian_vectorai import ActianVectorAIVectorStore
from langchain_huggingface import HuggingFaceEmbeddings
from actian_vectorai import VectorAIClient
# all-MiniLM-L6-v2 produces 384-dim vectors and runs on CPU.
# Downloads ~90 MB on first use. Subsequent runs load from cache.
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")

store = ActianVectorAIVectorStore.from_documents(
    documents=docs,
    embedding=embeddings,            # produces 384-dim vectors
    collection_name="rag_documents",
    url="localhost:6574",
    force_recreate=True,
)

print("Done.")

# Confirm vectors are stored and queryable.
client = VectorAIClient("localhost:6574")
client.connect()
print(f"Active collections: {client.collections.list()}")
test = store.similarity_search("vector database", k=1)
print(f"Test search returned {len(test)} result. Vectors are queryable.")
client.close()

Para descargar el modelo inicial es necesario disponer de conexión a Internet. Una vez almacenado en la caché, este proceso se ejecuta totalmente sin conexión.

ruta de salida del terminal 2

Salida de la terminal de la ruta 2 (embedding locales de HuggingFace) que confirma que se ha creado la colección y que los vectores están disponibles para consultas. 

Cuando se necesita una configuración explícita de la recopilación. El from_documents() El constructor deduce la dimensión del vector a partir del modelo de incrustación y crea la colección automáticamente. Si necesitas controlar directamente parámetros como la métrica de distancia, utiliza VectorAIClient directamente y pasar el cliente al almacén vectorial:

from actian_vectorai import VectorAIClient, VectorParams, Distance
from langchain_actian_vectorai import ActianVectorAIVectorStore
from langchain_openai import OpenAIEmbeddings

client = VectorAIClient("localhost:6574")
client.connect()
client.collections.create(
    "rag_documents",
    vectors_config=VectorParams(size=1536, distance=Distance.Cosine),
)

store = ActianVectorAIVectorStore(
    client=client,
    collection_name="rag_documents",
    embedding=OpenAIEmbeddings(),
)

Paso 6: Consultar el almacén de vectores

Realiza una búsqueda por similitud en los documentos almacenados. El código que aparece a continuación vuelve a conectarse a la colección existente para que se ejecute correctamente desde una sesión nueva de Python.

/,code>from langchain_actian_vectorai import ActianVectorAIVectorStore
from langchain_huggingface import HuggingFaceEmbeddings
from actian_vectorai import VectorAIClient

# Utiliza el mismo modelo de incrustación que se utilizó durante la ingesta.
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
client = VectorAIClient("localhost:6574")
client.connect()

store = ActianVectorAIVectorStore(
    client=client,
    collection_name="rag_documents",
    embedding=embeddings,
)

# Búsqueda básica por similitud: devuelve los k documentos más similares.
results = store.similarity_search("¿Cómo funciona RAG?", k=3)
for doc in results:
    print(doc.page_content[:120])
    print()

Para recuperar documentos con sus puntuaciones brutas, utiliza similarity_search_with_score():

scored = store.similarity_search_with_score("How does RAG work?", k=3)
for doc, score in scored:
    print(f"score={score:.4f}  {doc.page_content[:100]}")

Cuando la integración devuelve la distancia coseno, los valores más bajos indican coincidencias más cercanas. Utiliza la distribución de puntuaciones de tus propios datos para elegir un umbral, en lugar de dar por sentado un valor de corte universal:

# Example threshold. Tune this against your own evaluation data.

THRESHOLD = 0.3

confident_results = [

    (doc, score) for doc, score in scored if score < THRESHOLD

]

Para normalizar las puntuaciones en un intervalo de 0 a 1, en el que un valor más alto indica mayor relevancia, utiliza similarity_search_with_relevance_scores():

relevance = store.similarity_search_with_relevance_scores("How does RAG work?", k=3)
for doc, score in relevance:
    print(f"relevance={score:.3f}  {doc.page_content[:100]}")

puntuaciones brutas del coseno

Salida de la terminal que muestra los resultados de la búsqueda por similitud con puntuaciones coseno sin procesar y puntuaciones de relevancia normalizadas para los tres métodos de búsqueda.

Paso 7: Crear la cadena RAG

Conecta el «retriever» a un LLM y crea una cadena completa de preguntas y respuestas utilizando LCEL. Ambos bloques de código se vuelven a conectar a la base de datos de VectorAI al inicio, de modo que se ejecutan correctamente desde una sesión nueva.

Con OpenAI

from langchain_actian_vectorai import ActianVectorAIVectorStore
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from actian_vectorai import VectorAIClient

embeddings = OpenAIEmbeddings()
client = VectorAIClient("localhost:6574")
client.connect()

store = ActianVectorAIVectorStore(
    client=client,
    collection_name="rag_documents",
    embedding=embeddings,
)

retriever = store.as_retriever(search_type="similarity", search_kwargs={"k": 3})

# For diverse results that cover different aspects of the query,
# use Max Marginal Relevance search instead:
# retriever = store.as_retriever(
#     search_type="mmr",
#     search_kwargs={"k": 4, "fetch_k": 20, "lambda_mult": 0.5},
# )

prompt = ChatPromptTemplate.from_template("""Answer the question using only the
context below. If the context does not contain enough information to answer,
say so.

Context:
{context}

Question: {question}

Answer:""")

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

query = "What is Retrieval-Augmented Generation and how does it work?"
print(f"Query: {query}")
print()
answer = chain.invoke(query)
print(f"Answer: {answer}")

Sustituye el LLM por Ollama para obtener un proceso totalmente local:

from langchain_actian_vectorai import ActianVectorAIVectorStore
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_ollama import OllamaLLM
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from actian_vectorai import VectorAIClient

embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
client = VectorAIClient("localhost:6574")
client.connect()

store = ActianVectorAIVectorStore(
    client=client,
    collection_name="rag_documents",
    embedding=embeddings,
)

retriever = store.as_retriever(search_type="similarity", search_kwargs={"k": 3})

prompt = ChatPromptTemplate.from_template("""Answer the question using only the
context below. If the context does not contain enough information to answer,
say so.

Context:
{context}

Question: {question}

Answer:""")

# Run `ollama pull llama3.2` before using this path.
# llama3.2 requires approximately 2 GB of available RAM.
# If you see an out-of-memory error, use llama3.2:1b (~700 MB) instead.
llm = OllamaLLM(model="llama3.2")

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

query = "What is Retrieval-Augmented Generation and how does it work?"
print(f"Query: {query}")
print()
answer = chain.invoke(query)
print(f"Answer: {answer}")

La respuesta procede directamente de los documentos almacenados, no de los datos de entrenamiento del modelo. El sistema de recuperación extrajo el fragmento relevante, la indicación lo transmitió como contexto y el modelo de lenguaje grande (LLM) generó una respuesta basada en ese material. El proceso de OpenAI produce la misma estructura con la misma consulta.

La estructura de la cadena LCEL es la misma en ambas rutas. Solo cambia la instanciación del LLM. Se trata del mismo principio que el intercambio del almacén de vectores del paso 5. La abstracción mantiene la lógica de la cadena separada del componente que realiza el trabajo.

ruta de Ollama

Salida de la terminal de la ruta de Ollama, en la que se muestran la consulta y la respuesta fundamentada extraída de los documentos almacenados. La ruta de OpenAI genera una salida equivalente utilizando ChatOpenAI en lugar de OllamaLLM.

Cuándo utilizar este patrón y cuándo buscar otras alternativas

Este modelo es adecuado para servidores locales e implementaciones en la nube privada, redes aisladas en las que los documentos no pueden salir de la red, entornos de cumplimiento normativo con requisitos de residencia de datos, dispositivos periféricos en los que se prefiere un almacén vectorial local ligero a depender de la nube, e implementaciones en las que el coste es un factor determinante y las tarifas de salida de las bases de datos vectoriales en la nube suponen un problema.

Plantéate un enfoque diferente en estas situaciones. Para cargas de trabajo vectoriales inferiores a 1 millón en un equipo que ya utiliza Postgres, utilizar pgvector en la base de datos existente resulta más sencillo que emplear un contenedor adicional. En el caso de las funciones sin servidor con requisitos estrictos de arranque en frío, el tiempo de inicio del contenedor añade una latencia que puede resultar inaceptable. Para los equipos que no tienen control sobre su propia infraestructura, un almacén vectorial gestionado elimina la carga operativa que conlleva este modelo.

Conclusión

Has creado un flujo de trabajo RAG basado en un almacén vectorial local. El modelo de lenguaje grande (LLM), la plantilla de prompt, el retriever y el analizador de salida son los mismos que utilizaría un backend alojado. Ese es el valor práctico de LangChain: VectorStore Resumen: el cambio de backends afecta a la llamada de instanciación, no a la cadena.

La regla a seguir a partir de ahora es muy sencilla. Si tu volumen de trabajo no supera el límite de 5.000 vectores de la Community Edition, la solución local que aquí se describe es suficiente. Si supera ese límite o requiere funciones como la búsqueda híbrida o la multitenencia, tanto el plan de pago de VectorAI DB como el ecosistema de integración más amplio de LangChain se adaptan de forma independiente entre sí.

Para obtener información sobre un backend de memoria persistente para agentes, consulta «Uso de VectorAI DB como backend de memoria persistente para agentes de CrewAI». Para obtener información sobre un flujo de trabajo RAG que se ejecuta en hardware con recursos limitados, consulta «Ejecución de Gemma 4 en hardware periférico con VectorAI DB».