Notas prácticas: Reclasificación en RAG: codificadores cruzados, reclasificadores de LLM y latencia
Guía paso a paso para utilizar las notas prácticas: Reclasificación en RAG: codificadores cruzados, herramientas de reclasificación de LLM y latencia; contratos, verificaciones y espacios para código listo para usar destinados a los equipos que implementan este patrón.
Las notas siguientes reconstruyen un camino práctico sobre “Reranking para RAG: Cross-Encoders, LLM Rerankers y compromisos de latencia”. Se da énfasis en los contratos, las verificaciones y los marcadores de posición para código listo para usar, en lugar de en un enfoque motivacional. Al trabajar en la sección de Resumen, 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 de código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben encontrarse en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.
El puente desde la recuperación hasta el ranking
El proceso que va desde la recuperación hasta el ranking funciona mejor cuando se trata como una superficie medible. Capture un caso exitoso, 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 mejoras realizadas posteriormente. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agenciales amplían el contexto de manera excesiva; los límites estrictos evitan que las demostraciones se conviertan en facturas inesperadas.
Qué hace realmente el reranking
“What Reranking Actually Does” funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, 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 error debe apuntar a una sola responsabilidad y no a un proceso complicado. Asigne un límite de tokens por turno y por sesión; las herramientas agentes amplían el contexto de forma excesiva, por lo que los límites máximos evitan que las demostraciones se conviertan en facturas inesperadas.
Por qué la recuperación de información en primera pasada es ruidosa por diseño
Why First-Pass Retrieval is Noisy by Design funciona mejor cuando se trata como una superficie medible. Capture una transcripción ideal, 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 las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agentes amplían el contexto de forma excesiva; los límites máximos evitan que las demostraciones se conviertan en facturas inesperadas. Why First-Pass Retrieval is Noisy by Design funciona mejor cuando se trata como una superficie medible. Capture 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 secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.
Las dos principales familias de reclasificación
Para las dos familias principales de reclasificación, defina los datos de entrada, el responsable de la etapa y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la etapa 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, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Prefiera salidas estructuradas con validación de esquema sobre texto libre cuando el siguiente paso sea escribir código o realizar una llamada a una herramienta.
Los cross-encoders son la opción práctica por defecto
Para los codificadores cruzados, que son la opción práctica por defecto, se deben definir las entradas, el responsable de cada 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. Es preferible utilizar 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. Cuando el paso siguiente sea código o una llamada a una herramienta, es mejor contar con salidas estructuradas con validación de esquema en lugar de texto sin formato.
import cohere
import time
def rerank_cross_encoder(
query: str,
candidates: list[dict],
top_n: int = 5,
model: str = "rerank-v4.0-pro",
) -> list[dict]:
"""
The practical default for second-stage ranking.
Passes the query and candidate texts to a dedicated cross-encoder model.
"""
co = cohere.ClientV2()
# Extract just the text content for the API call
documents = [c["content"] for c in candidates]
resp = co.rerank(
model=model,
query=query,
documents=documents,
top_n=top_n,
)
# Reattach the original metadata and the new score
reranked = []
for r in resp.results:
original_chunk = candidates[r.index]
reranked.append({
**original_chunk,
"rerank_score": r.relevance_score
})
return reranked
Los reordenadores de LLM son flexibles pero costosos
En el caso de los reordenadores de LLM, que son flexibles pero costosos, se deben definir 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. 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. Prefiera resultados estructurados con validación de esquema sobre textos en formato libre cuando el siguiente paso sea código o una llamada a una herramienta. En el caso de los reordenadores de LLM, que son flexibles pero costosos, se deben definir 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. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el grafo.
>import anthropic
JUDGE_PROMPT = """\
You are a strict relevance judge. Given a user query and a candidate document chunk,
rate how well the chunk answers the query on a scale of 0 to 10.
Respond with ONLY a JSON object in this exact format:
{"score": <int>, "reason": "<one short sentence>"}
Query: {query}
Chunk: {chunk}"""
async def rerank_llm(
query: str,
candidates: list[dict],
top_n: int = 5,
) -> list[dict]:
"""
Expensive special forces. Uses an LLM to reason about nuance and completeness.
"""
client = anthropic.AsyncAnthropic()
scored = []
for c in candidates:
resp = await client.messages.create(
model="claude-opus-4-6",
max_tokens=128,
messages=[{
"role": "user",
"content": JUDGE_PROMPT.format(query=query, chunk=c["content"]),
}],
)
import json
try:
result = json.loads(resp.content[0].text)
scored.append({
**c,
"rerank_score": result["score"],
"reason": result.get("reason", "")
})
except (json.JSONDecodeError, KeyError):
# Fallback if the model fails to follow JSON instructions
scored.append({**c, "rerank_score": 0, "reason": "parse_error"})
# Sort by the LLM-assigned score descending
scored.sort(key=lambda x: x["rerank_score"], reverse=True)
return scored[:top_n]
El compromiso de latencia
Al trabajar en el tema del compromiso de latencia, 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 honestos los cambios posteriores en el código. 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. 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 consumo excesivo.
def rerank_with_timing(
rerank_fn: callable,
query: str,
candidates: list[dict],
top_n: int = 5,
) -> tuple[list[dict], float]:
"""
Measure the exact cost of the reranking stage.
"""
t0 = time.perf_counter()
results = rerank_fn(query, candidates, top_n)
latency_ms = (time.perf_counter() - t0) * 1000
return results, latency_ms
Cuándo vale la pena realizar un reclasificación
Al trabajar en “Cuándo vale la pena volver a clasificar”, 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 sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no 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 agotamiento.
Cuándo volver a clasificar es un exceso
Al trabajar en “When Reranking is Overkill”, 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 las comprobaciones de éxito y rechace las completaciones parciales silenciosas. 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 agotamiento. Al trabajar en “When Reranking is Overkill”, 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. 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 lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.
Modos de fallo en el reclasificado
Los modos de fallo del reclasificado funcionan mejor cuando se tratan como una superficie medible. Capture una transcripción clave, 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 correos no entregados forman parte del producto, no son mejoras posteriores. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agenciales amplían el contexto de manera excesiva; los límites estrictos evitan que las demostraciones se conviertan en facturas inesperadas.
def dedupe_candidates(
candidates: list[dict],
similarity_threshold: float = 0.85,
) -> list[dict]:
seen_tokens: list[set[str]] = []
deduped = []
for c in candidates:
tokens = set(c["content"].lower().split())
is_dup = False
for s in seen_tokens:
# Calculate simple Jaccard similarity
overlap = len(tokens & s) / max(len(tokens | s), 1)
if overlap >= similarity_threshold:
is_dup = True
break
if not is_dup:
deduped.append(c)
seen_tokens.append(tokens)
return deduped
from datetime import datetime, timezone
def apply_metadata_boost(
candidates: list[dict],
freshness_halflife_days: int = 90,
) -> list[dict]:
now = datetime.now(timezone.utc)
boosted = []
for c in candidates:
score = c.get("rerank_score", 0.0)
# Hard penalty for superseded documentation
if c.get("status") == "superseded":
score *= 0.4
# Gradual decay for older documents
updated = c.get("updated_at")
if updated:
age_days = (now - updated).days
decay_factor = max(0.5, 1 - age_days / (freshness_halflife_days * 2))
score *= decay_factor
boosted.append({**c, "rerank_score": score})
# Sort again based on the adjusted scores
boosted.sort(key=lambda x: x["rerank_score"], reverse=True)
return boosted
Cómo evaluar correctamente el reclasificado
“Cómo evaluar adecuadamente el reordenamiento” funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, 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. Asigne un presupuesto de tokens por turno y por sesión; las herramientas agenciales amplían el contexto de forma excesiva; los límites estrictos evitan que las demostraciones se conviertan en facturas inesperadas.
from dataclasses import dataclass
@dataclass
class RerankEvalCase:
query: str
expected_substring: str
category: str # e.g., "identifier", "procedure", "troubleshooting"
def eval_reranking(
cases: list[RerankEvalCase],
retrieve_fn: callable,
rerank_fns: dict[str, callable | None],
top_n: int = 3,
) -> dict:
"""
Compare multiple reranking strategies against a baseline.
Measures hit rate at top-N and tracks latency overhead.
"""
results = {}
for name, rerank_fn in rerank_fns.items():
hits = 0
total_latency = 0.0
by_type: dict[str, dict] = {}
for case in cases:
# Get the exact same starting candidates for every strategy
candidates = retrieve_fn(case.query)
if rerank_fn is not None:
reranked, lat = rerank_with_timing(
rerank_fn, case.query, candidates, top_n
)
total_latency += lat
else:
# Baseline: just take the top-N from first-pass retrieval
reranked = candidates[:top_n]
# Check if the expected evidence made it into the final prompt window
top_contents = [r["content"] for r in reranked]
found = any(case.expected_substring in c for c in top_contents)
hits += int(found)
# Track metrics by query category
by_type.setdefault(case.category, {"hit": 0, "total": 0})
by_type[case.category]["total"] += 1
by_type[case.category]["hit"] += int(found)
total = len(cases)
results[name] = {
"hit_rate": hits / total if total else 0,
"avg_latency_ms": total_latency / total if total else 0,
"by_type": {
t: {**v, "rate": v["hit"] / v["total"]}
for t, v in by_type.items()
},
}
return results
Una recomendación práctica por defecto
Una Recomendación Por Defecto Práctica 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. 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. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agentes amplían el contexto de forma excesiva; los límites estrictos evitan que las demostraciones se conviertan en facturas inesperadas. Una Recomendación Por Defecto Práctica 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. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.
def two_stage_retrieve(
query: str,
retrieve_fn: callable,
top_k: int = 20,
top_n: int = 5,
) -> tuple[list[dict], dict]:
"""
The complete production pipeline for second-stage ranking.
"""
t0 = time.perf_counter()
# Stage 1: Fast hybrid retrieval
candidates = retrieve_fn(query)[:top_k]
# Clean up the candidate pool
candidates = dedupe_candidates(candidates)
# Stage 2: Cross-encoder rerank
# We score slightly more than top_n to allow metadata boosts to reorder the edges
score_limit = min(top_n * 2, len(candidates))
reranked = rerank_cross_encoder(query, candidates, top_n=score_limit)
# Apply business logic for freshness and status
final = apply_metadata_boost(reranked)[:top_n]
latency = (time.perf_counter() - t0) * 1000
trace = {
"query": query,
"first_pass_count": len(candidates),
"post_rerank_count": len(reranked),
"final_count": len(final),
"latency_ms": latency,
}
return final, trace
¿Qué sigue?
Para lo que viene a continuación, defina las entradas, el responsable de la etapa y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la etapa desde 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. Prefiera salidas estructuradas con validación de esquema sobre texto en formato libre cuando el siguiente paso sea código o una llamada a una herramienta.
Continuar leyendo
Para “Continuar leyendo”, 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. Prefiera unidades pequeñas y verificables sobre scripts extensos. Cuando una tarea falla, el error debe indicar una única responsabilidad y no un proceso complicado. Prefiera salidas estructuradas con validación de esquema sobre texto en formato libre cuando el siguiente paso sea código o una llamada a una herramienta.
Lista de verificación operativa
Para la “Lista de verificación operativa”, 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 respuesta y el costo de tokens o consultas junto con los resultados funcionales. Ver la información sobre costos desde el principio evita facturas inesperadas cuando se pasa de entornos de demostración a entornos compartidos.
Prefiera salidas estructuradas con validación de esquema en lugar de texto libre cuando el siguiente paso sea escribir código o hacer una llamada a una herramienta.
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.
Fije las versiones de dependencias y registre el resumen de la imagen que se utilizó en la demostración. La reproducibilidad es mejor que el conocimiento basado en prácticas internas.
Prefiera unidades pequeñas y probables a scripts extensos. Cuando falla un paso, el error debe indicar una única responsabilidad y no un proceso complicado.
Antes de promocionar la solución, congele las versiones, capture una transcripción de referencia para el camino crítico 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 sencilla a demostraciones ingeniosas pero puntuales.
Nota por lotes para cdeb69942ea2: 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 posteriores en el modelo sigan siendo comparables.
Al trabajar en la nota de fortalecimiento número 0, 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. Prefiera unidades pequeñas y probables a scripts extensos. Cuando falla un paso, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado.
Detalle de reforzamiento 0/916: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.
La nota de reforzamiento 1 funciona mejor cuando se trata como una superficie 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 en tokens o consultas junto a los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el proceso pasa de la demostración a entornos compartidos.
Detalle de reforzamiento 1/916: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.
Para la nota de fortalecimiento 2, 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.
Detalle de fortalecimiento 2/916: mida el tiempo de ejecución, la clase del error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.
Al trabajar en la nota de fortalecimiento 3, 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 las verificaciones de éxito y rechace las completaciones parciales silenciosas.
Detalle de refuerzo 3/916: mida el tiempo de ejecución, la clase del error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.
La nota de refuerzo 4 funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, 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 secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo.
Detalle de refuerzo 4/916: mida el tiempo de ejecución, la clase del error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.
Lecturas relacionadas
- Notas prácticas: Ejecutar un LLM local útil en 30 minutos (Programación, RAG, Voz) — Guía paso a paso de las Notas prácticas: Ejecutar un LLM local útil en 30 minutos (Programación, RAG, Voz): contratos, verificaciones y espacios para código listo para usar para los equipos que implementan este patrón.
- Notas prácticas: Construir RAG a partir de principios básicos con solo Python. — Guía paso a paso de las Notas prácticas: Construir RAG a partir de principios básicos con solo Python.: contratos, verificaciones y espacios para código listo para usar para los equipos que implementan este patrón.