SincroDev Logo SincroDev

Cómo crear un asistente interno con RAG sobre documentos de empresa


Uno de los errores más comunes al intentar usar IA dentro de una empresa es creer que un chatbot general ya “sabe” todo lo que el equipo necesita.

No sabe:

  • tus políticas internas
  • tus contratos
  • tus manuales
  • tus procedimientos reales
  • tus documentos privados

Ahí entra RAG.

En lugar de esperar que el modelo recuerde algo que nunca vio o que ya está desactualizado, le das contexto recuperado desde tu propia base documental justo antes de responder.

En este tutorial vas a construir la base de un asistente interno con RAG para consultar documentos de empresa.

Qué vas a construir

El flujo será este:

Documentos internos

Extracción de texto

Chunking

Embeddings

Vector store

Pregunta del usuario

Retrieval de chunks relevantes

Prompt con contexto

Respuesta del LLM

No vamos a fingir que esto ya es “copilot corporativo full production”.

Vamos a construir una base razonable y útil sobre la que después sí puedes crecer.

Cuándo conviene esta solución

Tiene sentido cuando:

  • el conocimiento importante vive en documentos internos
  • necesitas respuestas trazables
  • quieres reducir alucinaciones
  • necesitas algo mejor que “buscar a mano en PDFs”

No conviene cuando:

  • el problema es completamente determinista
  • tus datos son mínimos y estables
  • una base FAQ simple resolvería lo mismo

RAG no reemplaza el criterio. Lo amplifica cuando el contexto documental sí importa.

Caso de uso realista

Imagina que quieres un asistente interno que responda preguntas como:

  • “¿Cuál es la política de reembolsos?”
  • “¿Cómo se aprueba una compra?”
  • “¿Qué dice el manual sobre accesos?”
  • “¿Cuál es el flujo para onboarding de un cliente?”

Ese tipo de preguntas no requiere una IA “creativa”. Requiere una IA bien anclada en documentación real.

Requisitos previos

Para seguir este tutorial necesitas:

  • Python 3.10+
  • una API key del proveedor de embeddings/modelo
  • documentos de texto o markdown para la primera prueba

También puedes reutilizar este recurso base del proyecto:

Y si quieres la parte conceptual antes de implementar:

Paso 1: Preparar un conjunto mínimo de documentos

Para la primera versión, no intentes meter toda la empresa.

Empieza con un subconjunto útil y controlado, por ejemplo:

  • políticas internas
  • manuales operativos
  • onboarding
  • procedimientos frecuentes

Regla práctica:

mejor pocos documentos buenos que una montaña de archivos inconsistentes.

Puedes usar una carpeta así:

docs/
  ├── politica-reembolsos.txt
  ├── onboarding-clientes.txt
  ├── accesos-internos.txt
  └── compras-y-aprobaciones.txt

Paso 2: Cargar los documentos

El recurso mínimo del repo usa TextLoader, que es suficiente para empezar.

Ejemplo conceptual:

from langchain_community.document_loaders import TextLoader

loader = TextLoader("docs/politica-reembolsos.txt")
documents = loader.load()

Si más adelante trabajas con PDFs, HTML o Notion exportado, cambiarás esta capa, pero el flujo general sigue siendo el mismo.

Paso 3: Dividir en chunks

Los LLM no deberían recibir documentos enormes enteros si solo necesitan una parte.

Por eso el texto se divide en fragmentos.

Ejemplo:

from langchain_text_splitters import CharacterTextSplitter

text_splitter = CharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=100,
)

texts = text_splitter.split_documents(documents)

Puntos clave:

  • chunk muy chico: pierde contexto
  • chunk muy grande: baja precisión

Como punto de partida práctico:

  • 400-800 tokens o equivalente aproximado
  • algo de overlap cuando el contenido lo requiera

Paso 4: Generar embeddings

Cada chunk se convierte en un vector numérico.

Ese vector no “responde”. Sirve para encontrar qué fragmentos son semánticamente cercanos a una pregunta.

Ejemplo:

from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings()

La calidad del retrieval depende mucho de esta etapa.

Si eliges mal el modelo de embeddings o mezclas varios sin control, los resultados se degradan.

Paso 5: Guardar en una base vectorial

Para una primera versión local y simple, el recurso del repo usa FAISS.

Ejemplo:

from langchain_community.vectorstores import FAISS

vectorstore = FAISS.from_documents(texts, embeddings)

Esto es muy útil para:

  • pruebas rápidas
  • demos técnicas
  • validación inicial del caso

Cuando el sistema crezca, probablemente querrás:

  • persistencia más robusta
  • versionado de índice
  • metadatos más ricos
  • filtros por permisos

Paso 6: Configurar el modelo generativo

Una vez resuelto el retrieval, toca la parte generativa.

Ejemplo:

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    temperature=0,
    model_name="gpt-3.5-turbo",
)

Para asistentes internos, normalmente conviene baja temperatura.

La prioridad no es creatividad. Es estabilidad y consistencia.

Paso 7: Unir retrieval + generación

En el recurso actual, eso se hace con RetrievalQA.

Ejemplo:

from langchain.chains import RetrievalQA

qa = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=vectorstore.as_retriever(),
)

Eso conecta:

  • pregunta del usuario
  • recuperación de chunks
  • prompt final
  • respuesta del modelo

Y te da una primera versión operativa muy rápido.

Paso 8: Hacer una primera pregunta

Ejemplo:

query = "¿Cuál es nuestra política de reembolsos para planes anuales?"
response = qa.invoke(query)
print(response["result"])

Si los documentos y chunks están bien, la respuesta ya debería apoyarse en contenido real en vez de memoria genérica del modelo.

Ejemplo mínimo completo

La idea base del recurso descargable es esta:

import os
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain.chains import RetrievalQA

loader = TextLoader("datos.txt")
documents = loader.load()

text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
texts = text_splitter.split_documents(documents)

embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(texts, embeddings)

llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo")

qa = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=vectorstore.as_retriever(),
)

query = "¿Qué dice el documento?"
response = qa.invoke(query)
print(response["result"])

Puedes descargar una base ya preparada aquí:

Paso 9: Mejorar el prompt del sistema

Un error común es dejar que el modelo responda “como quiera”.

Mejor define reglas explícitas:

Responde solo usando el contexto proporcionado.
Si no encuentras la respuesta en los documentos, dilo claramente.
No inventes políticas ni procedimientos.
Incluye referencias al final si corresponde.

Esto no elimina errores por completo, pero reduce bastante las respuestas inventadas.

Paso 10: Añadir metadatos desde temprano

Aunque el ejemplo mínimo funcione sin ellos, en un sistema interno real conviene guardar metadatos por chunk:

  • documento
  • sección
  • fecha
  • tipo de contenido
  • nivel de acceso

Eso después te permite:

  • citar fuentes
  • filtrar por área
  • versionar conocimiento
  • controlar permisos

Sin metadatos, el sistema responde. Pero responde a ciegas.

Errores comunes

1) Meter documentos sucios

Si los textos tienen ruido, duplicados o estructura caótica, el retrieval empeora y la respuesta también.

2) Usar chunks enormes

Tener más contexto en un chunk no siempre ayuda. Muchas veces empeora precisión.

3) No permitir abstención

Si el sistema no puede decir “no lo encontré”, inventará más de la cuenta.

4) No evaluar con preguntas reales

Un demo bonito no demuestra utilidad. Lo que importa es cómo responde a preguntas reales del equipo.

5) Ignorar permisos

Este punto es crítico.

Si vas a usar documentación interna, no puedes asumir que todos deberían recuperar los mismos chunks.

Qué medir para saber si sirve

No evalúes solo si “suena bien”.

Mide al menos:

  • si trae los chunks correctos
  • si la respuesta realmente está soportada por el contexto
  • cuántas veces debería abstenerse y no lo hace
  • latencia total

La calidad de un sistema RAG no se mide por simpatía. Se mide por grounding.

Cómo llevar esto hacia producción

Una evolución razonable sería:

  1. ingesta reproducible de documentos
  2. chunking consistente
  3. índice versionado
  4. metadatos
  5. retrieval con filtros
  6. logs de consulta y respuesta
  7. UI o API interna

En muchos equipos, el error es saltar del experimento local directamente a “asistente corporativo”.

Mejor escalar por capas.

Cuándo esta solución ya aporta valor real

Este tipo de asistente se vuelve útil bastante antes de ser perfecto.

Por ejemplo, cuando ya puede:

  • orientar a soporte interno
  • resumir políticas
  • responder preguntas repetitivas
  • ayudar a onboarding
  • reducir búsquedas manuales en documentación

Ese es el objetivo correcto al principio: utilidad concreta, no espectáculo.

Resumen

Para construir un asistente interno con RAG necesitas:

  1. documentos relevantes
  2. chunking razonable
  3. embeddings consistentes
  4. una base vectorial
  5. retrieval bien planteado
  6. prompt con reglas de abstención
  7. evaluación con preguntas reales

RAG no hace que el modelo “sepa más”. Hace algo mejor: le da acceso controlado al contexto que realmente importa para tu empresa.

Y ahí está la diferencia entre un chatbot simpático y una herramienta interna que de verdad ayuda.

Si quieres convertir documentación dispersa en un asistente interno útil, integrarlo a tus procesos o diseñar una versión más seria para producción, puedes escribirme desde Sobre Mí.