Notas prácticas: Crear una tubería RAG local de nivel profesional — 100% gratuita, sin
Guía paso a paso para utilizar las notas prácticas: Crear una tubería RAG local de nivel profesional — 100% gratuita, sin contratos, verificaciones ni espacios para código listos para uso por los equipos que implementan este patrón.
Esta guía reconstruye el proceso desde las materias primas hasta un sistema funcional para: crear una tubería RAG local de nivel industrial, 100% gratuita y sin necesidad de nube. Se centra en pasos operativos claros, verificaciones explícitas y código que se puede incorporar directamente a un repositorio sin tener que adivinar su propósito. En la etapa de visión general, se deben definir las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Documente tanto el camino óptimo como el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.
Por qué es importante
Al trabajar en la etapa de “¿Por qué es importante?”, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de un fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y verificables a scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
La tecnología completa
Al trabajar en la etapa The Complete Tech Stack, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
Requisitos previos
Al trabajar en la etapa de Requisitos previos, anote primero el contrato: los datos de entrada necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando se pasa de entornos de demostración a entornos compartidos. Mida el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente. Al trabajar en la etapa de Requisitos previos, anote primero el contrato: los datos de entrada necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente tanto el camino óptimo como el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.
Parte 1 — Configuración del entorno
La etapa de Configuración del entorno de la Parte 1 funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.
Paso 1: Crear el proyecto
La Etapa 1, Crear el escenario, funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Separe la política de fragmentación de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.
mkdir local-rag
cd local-rag
uv init
Etapa 2: Crear y activar el entorno virtual
El paso 2, “Crear y preparar”, funciona mejor cuando se trata como un área medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la demostración a entornos compartidos. Separe la política de fragmentación de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.
uv venv
.venv\Scripts\activate
El paso 2, “Crear y preparar”, funciona mejor cuando se trata como un área medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación juntos. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son ajustes realizados posteriormente.
Paso 3: Instalar todas las dependencias
En el Paso 3, Instalar toda la etapa, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Cite los pasajes que realmente sustentan la respuesta. Sin citaciones, los operadores no pueden distinguir entre una alucinación y una laguna en el indexado.
uv add google-genai pypdf chromadb rich python-dotenv huggingface_hub fpdf2
Paso 4: Crear la estructura del proyecto
En el Paso 4, “Crear la etapa”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Considere esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Cite los pasajes que realmente sustentan la respuesta; sin citas, los operadores no podrán distinguir entre alucinaciones y brechas en el indexado.
mkdir pdfs
mkdir pdfs\versions
type nul > local_rag.ipynb
type nul > .env
type nul > .gitignore
local-rag/
├── .venv/ ← virtual environment (never commit)
├── pdfs/ ← drop your PDFs here
│ └── versions/ ← test PDFs for CDC testing
├── chroma_db/ ← auto-created on first ingest
├── memory_checkpoints/ ← auto-created on first memory session
├── staleness_registry.json ← auto-created
├── chunk_registry.json ← auto-created
├── local_rag.ipynb ← your notebook
├── .env ← API keys (never commit)
├── .gitignore
└── pyproject.toml
Paso 5: Configurar claves API
En la etapa 5 “Configurar API”, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad temprana del costo evita facturas inesperadas cuando la tarea pasa de un entorno de demostración a uno compartido. Cite los pasajes que realmente sustentan la respuesta; sin citas, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado. En la etapa 5 “Configurar API”, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las intentonas repetidas, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras adicionales realizadas posteriormente.
GEMINI_API_KEY=your_gemini_key_here
HF_API_KEY=your_huggingface_token_here
.env
chroma_db/
memory_checkpoints/
staleness_registry.json
chunk_registry.json
__pycache__/
.venv/
*.pyc
Paso 6: Configurar VS Code
Al trabajar en la fase de Paso 6: Configurar, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación ayuda a mantener honestas las futuras modificaciones del código. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Mida el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
Ctrl+Shift+P → Python: Select Interpreter → .venv\Scripts\python.exe
Parte 2: Desglose del pipeline principal
Al trabajar en la etapa del Pipeline Básico, Parte 2, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
Célula 1 — Dependencias (dentro del cuaderno de notas)
Al trabajar en las Dependencias de Célula 1 dentro de la etapa, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Mida el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
# Run once inside the notebook if uv add was not used externally
# %pip install google-genai pypdf chromadb rich python-dotenv huggingface_hub fpdf2
Al trabajar en las Dependencias de Célula 1 dentro de la etapa, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente tanto el camino óptimo como el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras adicionales realizadas posteriormente.
Célula 2 — Configuración
La etapa de configuración de la Célula 2 funciona mejor cuando se trata como una superficie medible. Capture una transcripción de éxito, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado. Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.
import os
from dotenv import load_dotenv
load_dotenv()
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
EMBED_DIM = 768
HF_API_KEY = os.environ.get("HF_API_KEY", "")
GEMINI_EMBED_MODEL = "gemini-embedding-001"
HF_LLM_MODEL = "openai/gpt-oss-20b:groq"
CHROMA_DB_PATH = "./chroma_db"
COLLECTION_NAME = "local_rag"
CHUNK_SIZE = 800
CHUNK_OVERLAP = 120
TOP_K = 5
EMBED_BATCH_SIZE = 50
BATCH_SLEEP_SEC = 0.3
LLM_MAX_NEW_TOKENS = 1024
LLM_TEMPERATURE = 0.1
assert GEMINI_API_KEY, "❌ GEMINI_API_KEY not set"
assert HF_API_KEY, "❌ HF_API_KEY not set"
Célula 3 — Extracción de texto PDF
La etapa de texto PDF de Cell 3 funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Separe la política de fragmentación de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.
from pypdf import PdfReader
def extract_text_from_pdf(pdf_path: str) -> tuple[str, int]:
reader = PdfReader(pdf_path)
pages = []
for i, page in enumerate(reader.pages):
text = page.extract_text()
if text and text.strip():
pages.append(f"[Page {i + 1}]\n{text.strip()}")
return "\n\n".join(pages), len(reader.pages)
Cell 4 — Fragmentación con ventana deslizante
La etapa de Ventana Deslizante de Cell 4 funciona mejor cuando se trata como una superficie medible. Capture un transcripto ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la demostración a entornos compartidos. Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.
def chunk_text(text: str) -> list[str]:
chunks, start = [], 0
while start < len(text):
end = start + CHUNK_SIZE
chunk = text[start:end].strip()
if len(chunk) >= 80:
chunks.append(chunk)
start += CHUNK_SIZE - CHUNK_OVERLAP
return chunks
La etapa de Ventana Deslizante de Cell 4 funciona mejor cuando se trata como una superficie medible. Capture un transcripto ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.
Cell 5 — Gemini Embeddings
En la etapa de Embeddings Gemini de Célula 5, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Separe la construcción del cliente del bucle de mensajes para que sea posible cambiar los proveedores sin tener que reescribir la máquina de estados de la conversación.
from google import genai
from google.genai import types
genai_client = genai.Client(api_key=GEMINI_API_KEY)
def embed_documents_batch(chunks: list[str]) -> list[list[float]]:
all_embeddings = []
for i, chunk in enumerate(chunks, 1):
result = genai_client.models.embed_content(
model=GEMINI_EMBED_MODEL,
contents=chunk,
config=types.EmbedContentConfig(
task_type="RETRIEVAL_DOCUMENT",
output_dimensionality=EMBED_DIM,
),
)
all_embeddings.append(result.embeddings[0].values)
return all_embeddings
def embed_query(text: str) -> list[float]:
result = genai_client.models.embed_content(
model=GEMINI_EMBED_MODEL,
contents=text,
config=types.EmbedContentConfig(
task_type="RETRIEVAL_QUERY",
output_dimensionality=EMBED_DIM,
),
)
return result.embeddings[0].values
Célula 6 — Almacenamiento y recuperación en ChromaDB
Para la etapa de almacenamiento en ChromaDB de Célula 6, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Trate esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Cite los pasajes que realmente sustentan la respuesta. Sin citaciones, los operadores no pueden distinguir entre alucinaciones y brechas en el indexado.
import uuid, chromadb
def get_collection():
client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
return client.get_or_create_collection(
name=COLLECTION_NAME,
metadata={"hnsw:space": "cosine"},
)
def store_in_chroma(chunks, embeddings, doc_name):
collection = get_collection()
ids = [str(uuid.uuid4()) for _ in chunks]
metadatas = [{"source": doc_name, "chunk_index": i}
for i in range(len(chunks))]
collection.add(ids=ids, embeddings=embeddings,
documents=chunks, metadatas=metadatas)
return len(chunks)
def retrieve_context(query: str) -> list[dict]:
collection = get_collection()
query_embedding = embed_query(query)
results = collection.query(
query_embeddings=[query_embedding],
n_results=TOP_K,
include=["documents", "metadatas", "distances"],
)
chunks = []
for doc, meta, dist in zip(results["documents"][0],
results["metadatas"][0],
results["distances"][0]):
chunks.append({
"text": doc,
"source": meta.get("source", "unknown"),
"chunk_index": meta.get("chunk_index", -1),
"score": round(1 - dist, 4),
})
return sorted(chunks, key=lambda x: x["score"], reverse=True)
Célula 7 — Ejecución de la ingesta
Para la fase de ingestión en ejecución de Célula 7, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo de tokens o consultas junto con los resultados funcionales. La visibilidad temprana del costo evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Cite los pasajes que realmente sirvieron de base para la respuesta; sin citas, los operadores no pueden distinguir entre alucinaciones y brechas en el indexado.
PDF_PATH = "./pdfs/attention.pdf"
raw_text, page_count = extract_text_from_pdf(PDF_PATH)
chunks = chunk_text(raw_text)
embeddings = embed_documents_batch(chunks)
stored = store_in_chroma(chunks, embeddings,
os.path.basename(PDF_PATH))
Para la fase de ingestión en ejecución de Cell 7, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.
Cell 8 — LLM: gpt-oss-20b a través de HuggingFace
Al trabajar en la etapa Cell 8 LLM gpt-oss-20b, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad en lugar de a un proceso complicado. Almacene en caché las instrucciones del sistema estables y los esquemas de las herramientas. Reenviar un preámbulo idéntico es una causa común de desperdicio de recursos.
from huggingface_hub import InferenceClient
hf_client = InferenceClient(api_key=HF_API_KEY)
def build_messages(query: str, context_chunks: list[dict]) -> list[dict]:
context_str = "\n\n---\n\n".join([
f"[Source: {c['source']} | Chunk #{c['chunk_index']} | "
f"Relevance: {c['score']}]\n{c['text']}"
for c in context_chunks
])
return [
{"role": "system", "content": SYSTEM_MSG},
{"role": "user", "content":
f"CONTEXT:\n{context_str}\n\nQUESTION:\n{query}"},
]
def generate_answer(messages: list[dict]) -> str:
completion = hf_client.chat.completions.create(
model=HF_LLM_MODEL,
messages=messages,
max_tokens=LLM_MAX_NEW_TOKENS,
temperature=LLM_TEMPERATURE,
)
return completion.choices[0].message.content.strip()
Cell 9–11 — El pipeline ask() y REPL
Al trabajar en la etapa de Celda 9 11, anote primero el contrato: los datos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta etapa como un contrato entre los datos de entrada y las salidas validadas. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
query → embed_query() → ChromaDB cosine search → top-5 chunks
→ build_messages() → generate_answer() → printed answer
1. attention.pdf chunk #34 [██████████████████████░░░░░░░░] 0.7335
Parte 3 — Memoria de conversación
Al trabajar en la etapa de Memoria de Conversación de la Parte 3, anote primero el contrato: los datos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación ayuda a mantener honestos los cambios posteriores en el código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. Cambiar constantemente los prompts rara vez soluciona un sistema de recuperación deficiente.
El problema
Al trabajar en la fase de definición del problema, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación ayuda a mantener honestas las futuras modificaciones del código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben encontrarse en un lugar donde los operadores puedan realizar auditorías sin tener que leer todo el sistema. Mida la tasa de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
La solución: ConversationMemory
Al trabajar en la etapa Solution ConversationMemory, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no de mejoras posteriores. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
class ConversationMemory:
def __init__(self, session_id: str = None):
self.session_id = session_id or datetime.now().strftime("%Y%m%d_%H%M%S")
self.filepath = os.path.join(MEMORY_DIR, f"{self.session_id}.json")
self.history = []
# Auto-loads if resuming an existing session
if os.path.exists(self.filepath):
self._load()
def add_turn(self, question: str, answer: str, chunks: list[dict]):
# Append user + assistant turns, checkpoint immediately
...
self._save()
def get_messages_with_history(self, query, context_chunks, system_msg):
# Injects last 6 Q&A pairs into the message list before the current turn
...
# New session
memory = ConversationMemory()
# Resume yesterday's session
memory = ConversationMemory("20260413_104959")
Turn 4 question: "How does that compare to what you said about the BLEU score?"
Turn 4 answer: "The context also reports a BLEU score of 28.4 for the
Transformer (big) on WMT 2014 English-to-German. This matches
exactly what I previously stated."
Parte 4 — Seguimiento de la antigüedad, CDC y ponderación por recencia
Al trabajar en la etapa 4 de Seguimiento de Obsolescencia, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y probables a scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Mida el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
El problema en el mundo real
Al trabajar en la etapa del problema del mundo real, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
Rastreo de obsolescencia (Celda 14A)
Al trabajar en la etapa de Célula de Seguimiento de Obsolescencia 14A, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Mida el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente. Al trabajar en la etapa de Célula de Seguimiento de Obsolescencia 14A, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente tanto el camino óptimo como el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras adicionales realizadas posteriormente.
def compute_file_hash(pdf_path: str) -> str:
sha = hashlib.sha256()
with open(pdf_path, "rb") as f:
for block in iter(lambda: f.read(65536), b""):
sha.update(block)
return sha.hexdigest()
Motor CDC (Célula 14B)
La etapa Engine Cell 14B del CDC funciona mejor cuando se trata como una superficie medible. Capture una transcripción exitosa, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado. Separe la política de fragmentación de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.
def compute_chunk_hash(text: str) -> str:
return hashlib.md5(text.encode("utf-8")).hexdigest()
def diff_chunks(old_registry: dict, new_chunks: list[str]) -> dict:
new_hash_map = {compute_chunk_hash(c): c for c in new_chunks}
old_hashes = set(old_registry.keys())
new_hashes = set(new_hash_map.keys())
return {
"added": {h: new_hash_map[h] for h in (new_hashes - old_hashes)},
"removed": {h: old_registry[h] for h in (old_hashes - new_hashes)},
"unchanged": {h: old_registry[h] for h in (old_hashes & new_hashes)},
}
v1 → v2 CDC result:
✅ Unchanged : 8 (kept — zero re-embedding cost)
➕ Added : 6 (embedded + inserted)
➖ Removed : 4 (deleted from ChromaDB)
💰 API calls saved: 8/14 (57% reuse)
v2 → v3 CDC result:
✅ Unchanged : 10 (kept - zero re-embedding cost)
➕ Added : 4 (embedded + inserted)
➖ Removed : 2 (deleted from ChromaDB)
💰 API calls saved: 10/14 (71% reuse)
Recuperación ponderada por recencia (Cell 14C)
La etapa Recency-Weighted Retrieval Cell 14C funciona mejor cuando se trata como una superficie medible. Capture una transcripción exitosa, un caso de fallo y la nota de reversión antes de ampliar el alcance. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.
def recency_decay(ingested_at_str: str, half_life_days: float = 30) -> float:
ingested = datetime.fromisoformat(ingested_at_str)
days_gone = (datetime.now() - ingested).total_seconds() / 86400
λ = math.log(2) / half_life_days
return round(math.exp(-λ * days_gone), 4)
blended = alpha * cosine_score + (1 - alpha) * recency_score
# Default: 0.85 * cosine + 0.15 * recency
Parte 5 — Pruebas con PDFs de múltiples versiones
La prueba de la Parte 5 con entornos de pruebas funciona mejor cuando se trata como una superficie medible. Capture un transcripte ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos. Separe la política de fragmentación de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad. La prueba de la Parte 5 con entornos de pruebas funciona mejor cuando se trata como una superficie medible. Capture un transcripte ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente junto con los resultados positivos también los caminos de recuperación. Las reintentos, las verificaciones humanas y el manejo de mensajes no entregados forman parte del producto, no son ajustes realizados posteriormente.
TEST 1: Full ingest of v1
TEST 2: Staleness check — same file, correctly skipped
TEST 3: Baseline queries against v1
TEST 4: Copy v2 over active file → CDC kicks in
TEST 5: Same queries now return v2 content, newer chunks visible in recency scores
TEST 6: Copy v3 over active file → second CDC cycle
TEST 7: Recency verification — v3 chunks score highest across the board
TEST 8: Full stack test — weighted retrieval + conversation memory combined
Resultados y respuestas verificadas
En la fase de Resultados y Respuestas Verificadas, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Cite los pasajes que realmente sustentan la respuesta. Sin citaciones, los operadores no pueden distinguir entre una alucinación y una laguna en el indexado.
Resumen de Eficiencia
En la fase de Resumen de Eficiencia, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Considere esta fase como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Cite los pasajes que realmente sustentan la respuesta. Sin citaciones, los operadores no pueden distinguir entre alucinaciones y brechas en el indexado.
Limitaciones Conocidas
En la fase de Limitaciones Conocidas, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Cite los pasajes que realmente sirvieron de base para la respuesta. Sin citaciones, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado. En la fase de Limitaciones Conocidas, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras realizadas posteriormente.
Lo que has creado
Al trabajar en la etapa de “Lo que has creado”, escribe primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiere unidades pequeñas y verificables a scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Mide el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
PDF on disk
└► SHA256 hash check (staleness)
├► Unchanged → skip
└► Changed → CDC diff
├► Unchanged chunks → kept in ChromaDB (zero API cost)
├► Removed chunks → deleted from ChromaDB
└► Added chunks → embed (Gemini) → store (ChromaDB)
↓
User question
└► embed_query() [RETRIEVAL_QUERY task type]
└► ChromaDB cosine search (TOP_K × 3 candidates)
└► recency_decay() per chunk
└► blended score re-ranking
└► top-5 chunks as context
└► ConversationMemory.get_messages_with_history()
└► gpt-oss-20b via Groq/HuggingFace
└► grounded answer + checkpoint to disk
Lista de verificación operativa
La etapa de la lista de verificación operativa funciona mejor cuando se trata como un indicador medible. Registra una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance.
Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un único lugar que los operadores puedan auditar sin tener que leer todo el sistema.
Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambien las métricas de calidad.
Agregue una prueba de funcionamiento básica que ejecute la ruta crítica en el proceso CI utilizando configuraciones fijas, y no APIs pagadas en tiempo real, siempre que lo permitan los presupuestos.
Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son algo que se añada posteriormente.
Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambien las métricas de calidad.
Antes de promocionar el conjunto de herramientas, congele las versiones, capture una transcripción de referencia para la ruta crítica y confirme los pasos de reversión. Los entornos compartidos requieren límites de velocidad, verificaciones de tenencia y un responsable claro para la rotación de credenciales secretas. Prefiera una fiabilidad sólida a demostraciones ingeniosas pero puntuales.
Nota para el lote 8d172e929623: mantenga las claves del proveedor fuera del repositorio, establezca un límite para los tokens por sesión y almacene las transcripciones junto a los archivos de prueba para que los cambios en los modelos posteriores sigan siendo comparables.
Lecturas relacionadas
- Notas prácticas: Construyendo un pipeline ETL de nivel producción para RAG: Un enfoque básico — Guía paso a paso de las Notas prácticas: Construyendo un pipeline ETL de nivel producción para RAG: Un enfoque básico: contratos, verificaciones y espacios de código listos para usar para los equipos que implementan este patrón.