Guía práctica para crear una base de conocimiento local con LangChain

En sectores como la banca, la salud o el derecho, cada día circulan miles de documentos: normativas internas, plantillas de contratos, guías clínicas, manuales de cumplimiento... Cuando un empleado necesita localizar rápidamente el "proceso de aprobación de vacaciones" o los "cuidados postoperatorios de una cirugía específica", a menudo debe rebuscar en múltiples carpetas compartidas o incluso preguntar repetidamente a compañeros veteranos. Esta forma ineficiente de acceder al conocimiento no solo ralentiza la toma de decisiones, sino que se convierte en el mayor obstáculo durante la formación de nuevos ingresos.

El problema se agrava cuando se intenta subir información sensible a herramientas de IA en la nube para responder preguntas: es prácticamente imposible. Las barreras de privacidad de datos y cumplimiento normativo hacen que las empresas desistan.

¿Existe una solución que ofrezca una experiencia inteligente de "pregunta y respuesta" garantizando que todos los datos permanezcan siempre en la red interna? La respuesta es sí: crear un sistema de base de conocimiento local basado en LangChain. No se trata de una simple mejora de un buscador, sino de una arquitectura cerrada que integra un modelo de lenguaje grande (LLM), recuperación vectorial y gestión privada de documentos, logrando que "el conocimiento esté disponible pero no salga del dominio".

La lógica central de este sistema no es compelja: primero, fragmentas tus documentos PDF, Word, etc., y los conviertes en "vectores semánticos" matemáticos que se almacenan en una base de datos vectorial local. Cuando un usuario formula una pregunta, el sistema busca en la base los fragmentos más relevantes y luego se los entrega a un modelo de lenguaje grande ejecutado localmente para generar una respuesta en lenguaje natural. Todo el proceso funciona sin conexión a Internet y sin depender de ninguna API externa.

  1. Carga y partición de documentos

El primer paso es cargar los documentos y dividirlos adecuadamente. Por ejemplo, puedes usar PyPDFLoader para cargar contenido PDF y luego RecursiveCharacterTextSplitter para dividir el texto largo en fragmentos del tamaño adecuado. El equilibrio es clave: un fragmento demasiado grande puede exceder el contexto del modelo; demasiado pequeño puede romper la semántica. En la práctica, valores como chunk_size=400 y chunk_overlap=80 funcionan bien para documentos técnicos y normativas.

from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# Cargar PDF
loader = PyPDFLoader("manual.pdf")
docs_raw = loader.load()

# Partición con solapamiento
divisor = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=80)
fragmentos = divisor.split_documents(docs_raw)

  1. Vectorización y almacenamiento

La vectorización determina la capacidad del sistema para "comprender" el texto. Para escenarios en chino, los modelos diseñados específicamente para este idioma ofrecen mejores resultados. Recomendamos usar text2vec-base-chinese o BAAI/bge-small-zh-v1.5. A continuación se muestra cómo construir un índice FAISS persistente.

from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS

# Modelo de embeddings
embeddings = HuggingFaceEmbeddings(model_name="shibing624/text2vec-base-chinese")

# Crear base vectorial
indice = FAISS.from_documents(fragmentos, embeddings)

# Guardar en disco
indice.save_local("almacen/faiss")

Con apenas una decena de líneas hemos creado un índice de conocimiento persistente. FAISS construye internamente una estructura ANN (vecinos más cercanos aproximados) que permite recuperar coincidencias en milisegundos, incluso con decenas de miles de fragmentos.

  1. Integración del modelo de lengauje local

En la arquitectura RAG, el LLM actúa como un "generador condicionado" que sintetiza respuestas a partir de los fragmentos relevantes recuperados. Esto evita el costoso ajuste fino y supera el problema del "conocimiento congelado" de los modelos preentrenados puros. Modelos como Qwen2-7B, ChatGLM3-6B o Llama3-8B son adecuados para ejecución local. Si dispones de GPU, puedes cargar el modelo completo desde HuggingFace; si la memoria es limitada, puedes usar cuantización GGUF con llama.cpp para inferencia en CPU.

from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
from langchain.llms import HuggingFacePipeline

modelo_id = "Qwen/Qwen2-7B-Instruct"
tokenizador = AutoTokenizer.from_pretrained(modelo_id, trust_remote_code=True)
modelo = AutoModelForCausalLM.from_pretrained(modelo_id, trust_remote_code=True, device_map="auto")

cadena = pipeline(
    "text-generation",
    model=modelo,
    tokenizer=tokenizador,
    max_new_tokens=512,
    temperature=0.7,
    top_p=0.9,
    repetition_penalty=1.15
)

llm_local = HuggingFacePipeline(pipeline=cadena)

  1. Cadena de pregunta-respuesta

Una vez que el LLM está listo, lo combinamos con el índice vectorial para formar una cadena completa de recuperación y generación.

from langchain.chains import RetrievalQA

recuperador = indice.as_retriever(search_kwargs={"k": 3})
qa = RetrievalQA.from_chain_type(
    llm=llm_local,
    chain_type="stuff",
    retriever=recuperador,
    return_source_documents=True
)

consulta = "¿Cuál es el procedimiento para solicitar vacaciones?"
respuesta = qa(consulta)
print("Respuesta:", respuesta["result"])
print("Fuentes:", [doc.metadata for doc in respuesta["source_documents"]])

El flujo RetrievalQA codifica automáticamente la pregunta, realiza la búsqueda de similitud, concatena el contexto y genera la respuesta, además de rastrear el origen de cada respuesta, lo que mejora la confianza del sistema.

  1. Consideraciones clave para la implementación

  • Estrategia de partición de texto: Para documentos con estructura jerárquica (por ejemplo, archivos Markdown con títulos), usa MarkdownHeaderTextSplitter para dividir por secciones. Para textos legales donde el contexto es crucial, incrementa chunk_overlap a 100 o más.
  • Elección del modelo de embeddings: Aunque bge-small es ligero, puede ser insuficiente en dominios con terminología especializada (medicina, patentes). Considera utilizar bge-base o bge-large; aunque la latencia de inferencia aumenta, la precisión de recuperación suele mejorar más del 15%. También puedes hacer un ajuste fino con pocos ejemplos en tu corpus.
  • Selección de la base de datos vectorial:
    • FAISS: Ideal para despliegues mononodo, arranque rápido, perfecto para prototipos y sistemas pequeños/medios.
    • Chroma: API simple, integración rápida, adecuado para iteraciones ágiles.
    • Milvus: Soporta clúster distribuido y escalado dinámico, más adecuado para entornos de producción con alta concurrencia, aunque aumenta la complejidad de operación.
  • Ajustes según hardware:
    • VRAM ≥12 GB: Ejecuta modelos de 6B–7B en precisión FP16.
    • VRAM 8–10 GB: Utiliza versiones cuantizadas INT4 (formato GGML) mediante llama.cpp u Ollama.
    • Sin GPU: Emplea modelos muy pequeños como Phi-2 o TinyLlama en CPU; la calidad de salida es limitada pero suficiente para tareas simples.
  • Mejoras de ingeniería: Implementa caché para preguntas frecuentes (p. ej., "¿cómo solicitar un viaje de negocios?"). Usa colas asíncronas para procesar nuevos documentos sin bloquear el servicio principal. Muestra en la interfaz los enlaces a los documentos fuente para que el usuario pueda ver el párrafo original, aumentando la transparencia.

Arquitectura general del sistema

La arquitectura consta de tres capas: la interfaz de usuario (web UI o línea de comandos), la capa de aplicación LangChain que orquesta todo el flujo RAG, y la capa inferior compuesta por la base de datos vectorial y el LLM local, todo ejecutado en servidores propios de la empresa.

+------------------+       +---------------------+
|  Pregunta usuario |<----->|   Capa LangChain    |
+------------------+       +----------+----------+
                                       |
                  +-------------------v-------------------+
                  |   Flujo de Recuperación y Generación   |
                  |                                         |
                  | 1. Cargador → Divisor de texto         |
                  | 2. Embeddings → Almacén vectorial      |
                  | 3. Recuperador → Generador LLM         |
                  +-------------------+-------------------+
                                      |
                    +-----------------v------------------+
                    | DB vectorial (FAISS / Chroma)      |
                    +--------------------------------------+

                    +--------------------------------------+
                    |  LLM local (Qwen2, ChatGLM3, etc.)   |
                    +--------------------------------------+

Este diseño cerrado elimina por completo el riesgo de fuga de datos y otorga a la empresa el control total sobre el sistema de IA. Además, activa esos activos documentales que antes estaban "dormidos": los PDF que solo se consultaban pasivamente ahora se convierten en cuerpos de conocimiento con los que se puede dialogar.

Con el avance continuo de los LLM más pequeños y los modelos de embeddings eficientes, este tipo de sistemas seguirá reduciendo la barrera de despliegue. Quizás en un futuro cercano cada equipo pueda tener su propio "asistente de conocimiento privado", y la base de todo ello es la pila tecnológica LangChain + LLM local que hemos explorado hoy.

Etiquetas: LangChain RAG embeddings faiss base de conocimiento local

Publicado el 7-23 11:46