Anatomía de un pipeline RAG: indexar, recuperar y generar
Preview gratis Sección 1 · RAG local con Ollama
En este video
- Preguntas a llama3.2:3b sin contexto desde qué monto es gratis el envío y ves cómo inventa 150 cuando la política de PyShop dice 999 pesos
- Recorres el pipeline RAG en dos fases: indexar documentos en chunks y embeddings, y en cada pregunta recuperar el top-k, armar el prompt y generar con cita
- Indexas cuatro fragmentos con ollama.embed y bge-m3 en una matriz de 4 por 1024 que funciona como vector store en memoria
- Recuperas con similitud coseno como producto punto: 0.73 para envíos nacionales contra 0.65 de la trampa de envíos internacionales
- Generas con prompt_rag y ollama.chat: el mismo modelo responde 999 pesos y cita envios-nacionales.md, sin que nada salga de tu máquina
import ollama
MODELO = "llama3.2:3b"
OPC = {"temperature": 0, "seed": 42}
sistema = ("Eres el asistente de soporte "
"de PyShop. Responde en una frase.")
pregunta = "¿Desde qué monto es gratis el envío?"
mensajes = [
{"role": "system", "content": sistema},
{"role": "user", "content": pregunta}]
r = ollama.chat(MODELO, mensajes, options=OPC)
print(r.message.content)
# Paso que el video ejecuta sin mostrarlo en pantalla:
import ollama, re, urllib.request
URL = "https://pypro-assets.sfo3.digitaloceanspaces.com/cursos/rag-con-ollama/datos/pyshop-kb"
def seccion(doc, titulo):
"""Descarga un documento de la base de PyShop y devuelve una de sus secciones ## como fragmento."""
crudo = urllib.request.urlopen(f"{URL}/{doc}").read().decode("utf-8")
m = re.search(rf"^## {re.escape(titulo)}\n(.*?)(?=^## |\Z)", crudo, re.S | re.M)
return {"fuente": f"{doc}#{titulo}", "texto": f"## {titulo}\n" + m.group(1).strip()}
fragmentos = [seccion("envios-nacionales.md", "Costos de envío"),
seccion("envios-internacionales.md", "Costos, impuestos y aduanas"),
seccion("politica-devoluciones.md", "Plazo y requisitos"),
seccion("metodos-de-pago.md", "Tarjeta de crédito o débito")]
def prompt_rag(pregunta, chunks):
"""Prompt de RAG del curso: solo el contexto, cita [doc#sección] y salida explícita si no está."""
contexto = "\n\n".join(f"Fuente: [{c['fuente']}]\n{c['texto']}" for c in chunks)
return ("Eres el asistente interno de PyShop. Responde la pregunta usando solo el contexto de abajo.\n"
"Reglas:\n"
"1. Al final de cada oración pon entre corchetes la fuente que la respalda, copiada tal cual, "
"por ejemplo: El envío es gratis desde 999 pesos [envios-nacionales.md#Costos de envío].\n"
"2. Si el contexto no contiene la respuesta, responde solo: «No está en la documentación.»\n\n"
f"Contexto:\n{contexto}\n\nPregunta: {pregunta}")
import numpy as np
textos = [f["texto"] for f in fragmentos]
r = ollama.embed("bge-m3", textos)
M = np.array(r.embeddings)
print(len(fragmentos), M.shape)
q = ollama.embed("bge-m3", pregunta).embeddings[0]
sims = M @ np.array(q)
print(sims.round(2))
i = int(sims.argmax())
print(fragmentos[i]["fuente"])
prompt = prompt_rag(pregunta, [fragmentos[i]])
mensaje = {"role": "user", "content": prompt}
r = ollama.chat(MODELO, [mensaje], options=OPC)
print(r.message.content)