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-800tokens 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:
- ingesta reproducible de documentos
- chunking consistente
- índice versionado
- metadatos
- retrieval con filtros
- logs de consulta y respuesta
- 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:
- documentos relevantes
- chunking razonable
- embeddings consistentes
- una base vectorial
- retrieval bien planteado
- prompt con reglas de abstención
- 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í.