Inicio / Artículos / RAG exclusivo para VPC en un EC2: Ollama, Qdrant y LangChain

RAG exclusivo para VPC en un EC2: Ollama, Qdrant y LangChain

Sustituya el conocimiento tribal por un asistente basado en un manual citado que impide que los documentos salgan de su cuenta de AWS: tres contenedores en un EC2 con GPU.

1723 palabras

El problema del oráculo de conocimiento no oficial

Toda organización de ingeniería cuenta con personas que poseen las respuestas: cómo funcionan los despliegues, qué establece la política de licencias, por qué Kafka superó a SQS. Eso funciona hasta que esas personas están de licencia, cambian de equipo o un nuevo empleado necesita la respuesta a las 11 PM durante un incidente.

Una forma mejor: cualquiera escribe “¿Puedo hacer un despliegue el viernes?” y recibe una respuesta documentada en segundos, basada en manuales reales e acompañada de una cita. No es una suposición. Ni una alucinación convincente.

Una restricción guió el diseño: ningún documento interno sale de la cuenta de AWS. Sin claves de OpenAI. Sin base de datos vectorial alojada en la nube. Todo tenía que ejecutarse dentro de la VPC en una sola máquina EC2.

Lo que obtienes

Un chat sencillo de Streamlit. Haz una pregunta; expande las fuentes para ver qué archivo y sección sirvieron de base en la respuesta. Ejemplo: las dependencias del servicio de pago se obtienen de deployment-runbook.md, sin inventar nombres de servidor.

Arquitectura: tres contenedores

Un comando docker compose up activa:

version: "3.9"

services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    restart: unless-stopped

  qdrant:
    image: qdrant/qdrant:v1.18.2
    container_name: qdrant
    ports:
      - "6333:6333"
      - "6334:6334"
    volumes:
      - qdrant_data:/qdrant/storage
    environment:
      - QDRANT__SERVICE__ENABLE_CORS=true
    restart: unless-stopped

  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: rag-app
    ports:
      - "8501:8501"
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
      - QDRANT_URL=http://qdrant:6333
      - COLLECTION=company_docs
      - EMBED_MODEL=nomic-embed-text
      - LLM_MODEL=llama3.1:8b
      - S3_CORPUS_BUCKET=${S3_CORPUS_BUCKET}
      - S3_CORPUS_PREFIX=${S3_CORPUS_PREFIX:-corpus/}
      - AWS_REGION=${AWS_REGION:-us-east-1}
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
    volumes:
      - corpus_data:/app/corpus
      - ./scripts:/app/scripts
    depends_on:
      - ollama
      - qdrant
    restart: unless-stopped

volumes:
  ollama_data:
  qdrant_data:
  corpus_data:
  • Ollama — modelo de incrustación local además de un modelo de chat (por ejemplo, llama3.1). Sin claves API, sin facturación por token.
  • Qdrant — almacena vectores por bloques y recupera rápidamente los vecinos más cercanos.
  • LangChain — recuperación → organización del contexto → generación, con citas.

Todo esto se ejecuta en una instancia GPU EC2 dentro de la VPC, de modo que los corpus nunca salen de dicha zona.

Conexión del flujo de preguntas

La cadena al estilo LCEL recupera, formatea e invita al modelo de chat local a responder:

from operator import itemgetter
from langchain_core.runnables import RunnableLambda, RunnableParallel

chain = RunnableParallel(
    question=itemgetter("question"),           # pass question through
    docs=itemgetter("question") | retriever,   # embed + search Qdrant
).assign(
    answer=(
        {"context": itemgetter("docs") | RunnableLambda(format_docs),
         "question": itemgetter("question")}
        | prompt_template    # inject into system/human message
        | llm                # send to Ollama
        | str_parser         # extract text from response
    )
)

La recuperación mantiene su carácter léxico y denso según la configuración establecida; la generación se realiza mediante Ollama. Las citaciones provienen de los metadatos de los fragmentos que devuelve Qdrant.

Notas sobre la ingesta y las operaciones

Inserte los documentos en formato markdown o runbooks en la ruta de ingesta, córtelos en fragmentos, incorpórelos con el modelo de incrustación local y actualícelos en Qdrant. Ejecute nuevamente el proceso cuando cambien los documentos. Monitoree el espacio en disco ocupado por los modelos, la memoria de la GPU para las solicitudes concurrentes y los grupos de seguridad para asegurarse de que solo la VPN o el bastión tengan acceso a Streamlit y Qdrant.

Por qué este patrón es óptimo para el conocimiento interno

Usted sacrifica la calidad máxima del modelo a cambio de la gravedad de los datos y el control de costos. Para preguntas como “¿qué dice nuestro runbook?”, ese sacrificio es adecuado. Incluso cuando Aaron no está disponible, el runbook sigue respondiendo, y la citación indica dónde verificar la información.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de salud y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Mantenga la implementación sencilla: utilice Compose, pruebas de estado y un único lugar para rotar modelos. La sencillez es la forma en que las herramientas internas sobreviven al contacto con los horarios reales de atención al cliente.

Restricción: los datos nunca salen de la VPC

La regla ineludible determinó cada elección de proveedor. Las API gestionadas compatibles con OpenAI quedaron descartadas, al igual que las bases de datos vectoriales alojadas en la nube. Incluso las subidas “temporales” a entornos de pruebas fueron rechazadas. Esto dejó solo una instancia EC2 con GPU, un archivo compose y las normas de IAM/grupos de seguridad.

Si su equipo legal no puede delimitar claramente el ENI de EC2, esta arquitectura no es adecuada para usted. Si sí pueden hacerlo, obtendrá las referencias necesarias sin tener que enviar PDFs a terceros.

Detalles del pipeline de ingesta

Los documentos de procedimientos y las soluciones de problemas se almacenan en formato markdown dentro de un directorio bajo vigilancia. Una tarea los divide en fragmentos superpuestos, los procesa con el modelo de incrustación local mediante Ollama y actualiza los datos en Qdrant junto con la metadata del camino y la sección correspondiente. En caso de fallo, se debe interrumpir la actualización de ese archivo en lugar de dejar sin procesar parcialmente un documento de política.

Vuelve a incrustar el contenido cuando cambie el hash de un archivo. Evita volver a incrustar todo el corpus con cada pequeña edición si puedes hacer diferencias por ruta.

Comportamiento en tiempo de consulta

La aplicación Streamlit envía la pregunta del usuario a un recuperador configurado con un valor pequeño de k. Los textos recuperados se formatean en una instrucción que indica al modelo de chat que responda únicamente a partir del contexto y que se niegue cuando este sea escaso. Las citaciones se generan a partir de los metadatos para que una persona pueda abrir la sección exacta del manual.

Ese comportamiento de negación ante contextos escasos es más importante que los ajustes de temperatura. Un texto sin sentido pero con una citación falsa es peor que “No veo eso en los manuales”.

Modos de fallo a considerar

Agotamiento de memoria de la GPU cuando alguien carga un modelo de chat más grande sin aumentar el tamaño de la instancia. Crecimiento excesivo del disco en Qdrant si se ingieren datos binarios innecesarios. Respuestas obsoletas tras un cambio en el proceso que nadie volvió a procesar. Pilas de Compose que vinculan puertos a 0.0.0.0 en una subred pública: hay que mantenerlas privadas.

La persona de guardia debe saber cómo reiniciar Ollama, reconstruir el volumen de Qdrant a partir del markdown original y verificar que una pregunta clave siga haciendo referencia al archivo correcto.

Qué significa “listo”

Un nuevo empleado ya en el segundo día puede hacer preguntas sobre despliegues sin tener que contactar a Aaron. Las respuestas incluyen rutas específicas. Los límites de la VPC están bajo revisión constante. El costo es un EC2 más almacenamiento: algo predecible. Eso ya constituye éxito suficiente para un asistente de conocimiento interno; solo se debe buscar mejorar la calidad de los modelos avanzados una vez que las referencias sean fiables.

Lecturas relacionadas