<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sintesis on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/sintesis/</link><description>Recent content in Sintesis on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Fri, 22 May 2026 16:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/sintesis/index.xml" rel="self" type="application/rss+xml"/><item><title>Anatomía de una petición LLM en producción, mayo 2026: tour por las seis etapas siguiendo una sola request</title><link>https://blog.lo0.es/posts/anatomia-request-llm-mayo-2026/</link><pubDate>Fri, 22 May 2026 16:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/anatomia-request-llm-mayo-2026/</guid><description>&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>El blog ha desplegado a lo largo de varias series las piezas que sostienen un sistema LLM en producción: la &lt;strong>etapa Data&lt;/strong> (&lt;a href="https://blog.lo0.es/posts/data-versioning-dvc-lakefs/">versionado de datasets&lt;/a>, &lt;a href="https://blog.lo0.es/posts/postgresql-qdrant-ingestion-microservicios/">ingestión y vector stores&lt;/a>, &lt;a href="https://blog.lo0.es/posts/rag-kafka-datalake-arquitectura/">RAG sobre Kafka&lt;/a>), la &lt;strong>etapa Tune&lt;/strong> (&lt;a href="https://blog.lo0.es/posts/fine-tuning-continuo-produccion/">fine-tuning continuo&lt;/a>), la &lt;strong>etapa Eval&lt;/strong> (&lt;a href="https://blog.lo0.es/posts/evals-llm-la-capa-despues-de-tracing/">evals como capa después del tracing&lt;/a>, &lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">guardrails y safety&lt;/a>), la &lt;strong>etapa Deploy&lt;/strong> (&lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache&lt;/a>, &lt;a href="https://blog.lo0.es/posts/pagedattention-deep-dive/">PagedAttention&lt;/a>, &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">disaggregated serving&lt;/a>, &lt;a href="https://blog.lo0.es/posts/cluster-h100-plataforma-multi-tenant/">cluster GPU multi-tenant&lt;/a>, &lt;a href="https://blog.lo0.es/posts/vllm-kubernetes/">vLLM en Kubernetes&lt;/a>, &lt;a href="https://blog.lo0.es/posts/operators-llm-kubernetes/">operators de LLM en K8s&lt;/a>), la &lt;strong>etapa Observe&lt;/strong> (&lt;a href="https://blog.lo0.es/posts/agentsight-tracing-llm/">tracing con AgentSight&lt;/a>, &lt;a href="https://blog.lo0.es/posts/mcp-observability-otel/">MCP observability&lt;/a>, &lt;a href="https://blog.lo0.es/posts/ebpf-on-device-inference-drift/">eBPF + drift&lt;/a>), la &lt;strong>etapa Retrain&lt;/strong> (&lt;a href="https://blog.lo0.es/posts/retrain-cerrar-el-bucle-feedback-dataset-adapter/">cerrar el bucle feedback → dataset → adapter&lt;/a>), y los componentes transversales (&lt;a href="https://blog.lo0.es/posts/prompt-versioning-langfuse-mlflow/">prompt versioning&lt;/a> y &lt;a href="https://blog.lo0.es/posts/data-versioning-dvc-lakefs/">data versioning&lt;/a>). Lo que falta es &lt;strong>unirlo&lt;/strong>: ver una única petición atravesando todas las piezas en orden, en una historia coherente. Eso hace este post. Cogemos una request específica de un chatbot de soporte multi-tenant, la rebobinamos hacia atrás hasta los datos que entrenaron el adapter que la sirve hoy, la seguimos hacia adelante por el serving, la vemos llegar al store de feedback cuando el usuario marca thumbs-down, y la dejamos como semilla del próximo ciclo trimestral de retrain. El recorrido sirve como mapa mental y como guía del integrador: el sistema no se sostiene si una sola de las siete piezas (seis etapas + dos transversales) está rota o ausente. La lección práctica del tour no es ninguna nueva — es que &lt;strong>todo está conectado&lt;/strong>, que las medidas locales mienten cuando se aíslan, y que el coste real de no operar bien una etapa lo paga otra etapa más adelante.&lt;/p>
&lt;h2 id="estás-aquí-todas-las-etapas-a-la-vez">Estás aquí: todas las etapas a la vez&lt;/h2>
&lt;p>A diferencia de los posts anteriores, donde el mini-mapa marcaba una sola caja activa, este recorre &lt;strong>todo&lt;/strong> el pipeline. Es el único post del blog que activa las seis etapas y los dos componentes transversales simultáneamente, porque seguimos una request real que las cruza todas.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 180" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="tour por todas las etapas y transversales del pipeline LLMOps">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#ff8a4c;stroke-width:2.4}.cross{fill:#ffe9d6;stroke-width:1.6;stroke:#c66;rx:6}.lbl{font:600 12px sans-serif;fill:#222}.sm{font:11px sans-serif;fill:#444}.tiny{font:600 10px sans-serif;fill:#333}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#trm)}.cyc{stroke:#c66;stroke-width:2;fill:none;stroke-dasharray:4 2;marker-end:url(#trm)}.trace{stroke:#1a73e8;stroke-width:2.4;fill:none}&lt;/style>
&lt;defs>&lt;marker id="trm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" class="lbl">Tour completo: una request atraviesa las 6 etapas y los 2 componentes transversales&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box active"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box active"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box active"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box active"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box active"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box active"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="cyc" d="M710,72 L710,82 L85,82 L85,72"/>
&lt;rect x="30" y="98" width="357" height="25" class="cross"/>
&lt;text x="208" y="115" text-anchor="middle" class="sm">Prompt versioning (Langfuse / MLflow Prompts)&lt;/text>
&lt;rect x="405" y="98" width="360" height="25" class="cross"/>
&lt;text x="585" y="115" text-anchor="middle" class="sm">Data versioning (DVC / lakeFS) · Schema Registry&lt;/text>
&lt;text x="50" y="148" class="tiny">trace_id · prompt_id · prompt_version · dataset_id · dataset_version · model_id · model_version · deployment_id&lt;/text>
&lt;path class="trace" d="M30,165 Q200,140 400,165 T760,160"/>
&lt;text x="755" y="172" text-anchor="end" class="tiny" fill="#1a73e8">trace que recorre todo el sistema&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h2 id="la-analogía-análisis-forense-de-una-request">La analogía: análisis forense de una request&lt;/h2>
&lt;p>Cuando ocurre un accidente aéreo, el análisis forense no se limita a mirar los últimos segundos del vuelo. El equipo de investigación rebobina hasta el mantenimiento de los seis meses previos, los protocolos del fabricante, el currículo del piloto, el briefing meteorológico, las decisiones del controlador, la historia de incidentes en el mismo modelo. La conclusión rara vez es &lt;em>&amp;ldquo;el ala se rompió&amp;rdquo;&lt;/em>; es &lt;em>&amp;ldquo;el ala se rompió porque un protocolo de inspección redactado de tal forma no detectaba microfisuras que el modelo de cálculo del 2014 no consideraba críticas y que sí lo eran a partir de cierto ciclo de fatiga&amp;rdquo;&lt;/em>.&lt;/p>
&lt;p>Cuando una petición LLM en producción &lt;strong>falla&lt;/strong> o &lt;strong>acierta&lt;/strong>, también hay una cadena causal larga detrás. La respuesta que el usuario ve es el último frame; lo que la determinó empieza meses antes y se ramifica por seis etapas operativas. Si sólo miras el último frame, atribuyes el resultado al modelo. Si miras la cadena entera, ves que el modelo es uno de doce factores y rara vez el más importante.&lt;/p>
&lt;p>Este post hace ese análisis forense, pero al revés: en lugar de partir de un fallo y rebobinar, partimos de una &lt;strong>request específica que funciona&lt;/strong> y desglosamos qué tuvo que pasar para que llegara a funcionar, y qué pasará después con ella. Es un tour guiado, no un diagnóstico de incidente. Pero la disciplina mental es la misma: ninguna etapa es autónoma, y entender el sistema significa entender los puentes entre etapas, no solo las cajas.&lt;/p>
&lt;h2 id="el-escenario-chatbot-de-soporte-multi-tenant-para-clientes-regulados">El escenario: chatbot de soporte multi-tenant para clientes regulados&lt;/h2>
&lt;p>Para el tour usamos un escenario concreto realista, lo bastante representativo como para que las observaciones se transporten a la mayoría de despliegues serios en mayo 2026. Es un &lt;strong>producto SaaS de soporte al cliente&lt;/strong> con LLM, vendido a varios clientes corporativos (multi-tenant) en sectores regulados (banca, seguros, salud). El producto:&lt;/p>
&lt;ul>
&lt;li>Acepta preguntas en lenguaje natural por chat embebido en la web del cliente.&lt;/li>
&lt;li>Recupera fragmentos relevantes del knowledge base interno del cliente (documentos de producto, condiciones contractuales, FAQs).&lt;/li>
&lt;li>Responde citando el fragmento utilizado, en el idioma del usuario, con tono adaptado a la marca.&lt;/li>
&lt;li>Si la pregunta requiere una acción (cancelar, modificar plan, abrir caso), invoca una &lt;strong>tool&lt;/strong> del catálogo expuesto vía MCP, con confirmación del usuario.&lt;/li>
&lt;li>Está obligado por contrato a no inventar datos del cliente, a respetar la guía de estilo del tenant, y a cumplir compliance ENS / NIS2 sobre datos en la UE.&lt;/li>
&lt;/ul>
&lt;p>La infraestructura es &lt;strong>on-premise&lt;/strong> sobre Kubernetes (RKE2 con CNI Cilium, en el patrón cubierto en &lt;a href="https://blog.lo0.es/posts/rke2-cilium-bgp/">Cilium BGP&lt;/a> y CNI networking). El GPU plane es un &lt;strong>cluster genérico 4×H100 SXM&lt;/strong> (80 GB cada una, NVLink) para serving e entrenamiento productivo, más una &lt;strong>RTX 4090&lt;/strong> (24 GB) para desarrollo, evals offline y reembeddings ad-hoc.&lt;/p>
&lt;p>La request que vamos a seguir:&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Usuario&lt;/strong> (cliente final, español, plan premium de una aseguradora):
&lt;em>&amp;quot;¿Cómo cancelo mi suscripción premium? Llevo tres meses pagando y no he usado la mitad de las coberturas.&amp;quot;&lt;/em>&lt;/p>
&lt;/blockquote>
&lt;p>A primera vista, una pregunta simple. Vamos a ver qué hay detrás.&lt;/p>
&lt;h2 id="paso-0--el-frame-la-respuesta-que-el-usuario-ve">Paso 0 — El frame: la respuesta que el usuario ve&lt;/h2>
&lt;p>A las 11:47:32 de un martes, el usuario envía el mensaje. A los 4.2 segundos, ve aparecer en la UI:&lt;/p>
&lt;blockquote>
&lt;p>Para cancelar tu suscripción premium, sigue estos pasos: (1) Accede al portal de cliente con tu DNI y contraseña. (2) Ve a &lt;em>Mis pólizas&lt;/em> → &lt;em>Premium Vida 2024&lt;/em>. (3) Pulsa &lt;em>Solicitar cancelación&lt;/em>. (4) Selecciona el motivo (en tu caso, &amp;ldquo;no uso del producto&amp;rdquo;) y confirma. Te llegará un correo de confirmación en 24-48 h.&lt;/p>
&lt;p>Antes de cancelar, te recuerdo que tu plan incluye &lt;strong>revisión médica anual gratuita&lt;/strong> y &lt;strong>asistencia 24/7 en viaje&lt;/strong>, coberturas que se pierden con la cancelación. Si quieres, puedo ayudarte a revisar tu uso de coberturas en los últimos tres meses para ver si te conviene cambiar a un plan más ajustado en vez de cancelar.&lt;/p>
&lt;p>&lt;em>Fuente: [Política de cancelación, sección 4.2 de Condiciones Generales 2024]&lt;/em>&lt;/p>
&lt;/blockquote>
&lt;p>El usuario marca después de leerla un &lt;strong>thumbs-down&lt;/strong> y deja en el formulario: &lt;em>&amp;ldquo;la respuesta es correcta pero el tono es demasiado vendedor; sólo quería saber cómo cancelar&amp;rdquo;&lt;/em>.&lt;/p>
&lt;p>Esa secuencia —pregunta, respuesta, thumbs-down con feedback estructurado— es el último frame visible. Vamos hacia atrás para entender qué tuvo que ocurrir para que la respuesta saliera así.&lt;/p>
&lt;h2 id="rebobinando-hacia-atrás-lo-que-ya-estaba-en-su-sitio-antes-de-la-request">Rebobinando hacia atrás: lo que ya estaba en su sitio antes de la request&lt;/h2>
&lt;p>Antes de que el usuario escribiera, &lt;strong>el sistema ya tenía&lt;/strong> un modelo cargado en serving, un prompt activo etiquetado como &lt;code>production&lt;/code>, un índice vectorial actualizado, un dataset versionado del último fine-tuning, y un golden eval set que validó la promotion. Cada uno de esos artefactos llegó allí por un proceso. Recorremos cuatro saltos hacia atrás.&lt;/p>
&lt;h3 id="t--90-días--etapa-retrain-anterior-cierra-el-ciclo-previo">t = −90 días — Etapa Retrain anterior cierra el ciclo previo&lt;/h3>
&lt;p>Hace tres meses, durante un ciclo de Retrain trimestral, ocurrieron dos cosas. La primera: el equipo de soporte revisó el feedback acumulado de los seis meses previos y vio un patrón —el modelo respondía con tono excesivamente formal a usuarios premium, que reportaban &amp;ldquo;se siente robótico&amp;rdquo;—. La segunda: un incidente puntual (un cliente cancela por una respuesta percibida como brusca) disparó un mini-ciclo incident-driven.&lt;/p>
&lt;p>El proceso, en detalle cubierto en el &lt;a href="https://blog.lo0.es/posts/retrain-cerrar-el-bucle-feedback-dataset-adapter/">post de Retrain&lt;/a>, siguió cinco sub-procesos:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Captura de feedback&lt;/strong> — thumbs-down explícitos + feedback implícito (abandonments, retries) acumulados en una tabla &lt;code>feedback_signals&lt;/code> de Postgres, todos con &lt;code>trace_id&lt;/code> que permite rebobinar hasta el contexto exacto.&lt;/li>
&lt;li>&lt;strong>Triage por causa raíz&lt;/strong> — el cluster de incidentes &amp;ldquo;tono brusco&amp;rdquo; se categorizó como &lt;code>prompt issue&lt;/code> (no era el modelo respondiendo mal, era el system prompt que pedía un registro demasiado formal). Un sub-cluster era &lt;code>model issue&lt;/code> (en algunos casos el modelo se cerraba en banda incluso con un prompt más cálido).&lt;/li>
&lt;li>&lt;strong>Enriquecimiento del dataset&lt;/strong> — el equipo anotó manualmente 280 casos donde el modelo fue demasiado brusco, etiquetados con la respuesta de referencia (&amp;ldquo;cómo debería haber respondido&amp;rdquo;). Doble anotación en el 20% críticos; los casos con quality score &amp;lt; 4 quedaron fuera.&lt;/li>
&lt;li>&lt;strong>Decisión de cadencia&lt;/strong> — el incidente se trató como incident-driven; el resto del Retrain trimestral siguió calendario.&lt;/li>
&lt;li>&lt;strong>Promotion&lt;/strong> — el nuevo adapter &lt;code>customer_support_v7&lt;/code> pasó por eval gates contra &lt;code>customer_support_v6&lt;/code>, canary 5% durante una semana, y se promovió cuando las métricas del golden set mostraron mejora estable en el segmento &amp;ldquo;tono / claridad&amp;rdquo; sin regresiones en el resto.&lt;/li>
&lt;/ol>
&lt;p>Resultado: el adapter activo en producción cuando el usuario envió la request del Paso 0 es &lt;code>customer_support_v7&lt;/code>, entrenado sobre el dataset enriquecido &lt;code>enriched_retrain_2026_q1&lt;/code> versión 3, con doble lineage hasta el incidente original.&lt;/p>
&lt;h3 id="t--60-días--etapa-data-el-dataset-enriquecido-se-versiona-y-entra-a-circulación">t = −60 días — Etapa Data: el dataset enriquecido se versiona y entra a circulación&lt;/h3>
&lt;p>Inmediatamente después de Retrain, la etapa Data del &lt;a href="https://blog.lo0.es/posts/pipeline-llmops-seis-etapas/">pipeline LLMOps&lt;/a> hace su trabajo. Tres operaciones críticas, cubiertas en detalle en el &lt;a href="https://blog.lo0.es/posts/data-versioning-dvc-lakefs/">post de data versioning&lt;/a>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Versionado inmutable del dataset enriquecido&lt;/strong> con DVC, hash sha256 propagado al registry. El identificador &lt;code>(enriched_retrain_2026_q1, v3, sha256:9af...)&lt;/code> se convierte en el ticket de equipaje que recorrerá las próximas etapas.&lt;/li>
&lt;li>&lt;strong>Schema contract&lt;/strong> validado por CI: cada fila cumple el JSON Schema del entry esperado por el trainer (&lt;code>example_id&lt;/code>, &lt;code>input.user_query&lt;/code>, &lt;code>input.retrieved_context&lt;/code>, &lt;code>expected_output&lt;/code>, &lt;code>rubric&lt;/code>, &lt;code>segment&lt;/code>, &lt;code>difficulty&lt;/code>). Una validación falla en CI si alguna fila rompe el contract.&lt;/li>
&lt;li>&lt;strong>Holdout segregation check&lt;/strong>: hash sha256 normalizado de cada &lt;code>input&lt;/code> se compara contra todos los hashes del golden eval set activo (&lt;code>customer_support_golden_v12&lt;/code>). Cero solapamientos = el dataset no contamina la eval. Si hubiera habido uno solo, el CI habría bloqueado el merge.&lt;/li>
&lt;/ul>
&lt;p>En paralelo, el &lt;strong>corpus RAG&lt;/strong> (manuales de producto, FAQs, condiciones generales del tenant aseguradora) se mantiene vivo. El &lt;a href="https://blog.lo0.es/posts/postgresql-qdrant-ingestion-microservicios/">pipeline de ingestión&lt;/a> sigue capturando cambios desde el CMS del cliente: una nueva sección de la política de cancelación se modificó en febrero y se reindexó en Qdrant. Como cuenta el &lt;a href="https://blog.lo0.es/posts/rag-kafka-datalake-arquitectura/">post sobre RAG sobre Kafka&lt;/a>, el corpus no se reentrena con cada cambio: se reembedea solo el delta, y &lt;code>lakeFS&lt;/code> mantiene un branch del bucket de embeddings con la versión nueva. El branch se mergea a &lt;code>main&lt;/code> cuando el &lt;code>recall@10&lt;/code> sobre un set de queries representativas se mantiene por encima del threshold (0.78 en este sistema).&lt;/p>
&lt;h3 id="t--45-días--etapa-tune-el-adapter-customer_support_v7-se-entrena">t = −45 días — Etapa Tune: el adapter customer_support_v7 se entrena&lt;/h3>
&lt;p>Tres semanas tras cerrar el dataset, el entrenamiento del nuevo adapter LoRA arranca. Como detalla el &lt;a href="https://blog.lo0.es/posts/fine-tuning-continuo-produccion/">post de fine-tuning continuo&lt;/a>, el patrón productivo en 2026 evita reentrenar el modelo base — costoso, lento, irreversible — y favorece &lt;strong>adapter LoRA sobre un modelo base estable&lt;/strong> (en este sistema, Llama 3 70B-instruct cuantizado a INT8 para serving). El entrenamiento:&lt;/p>
&lt;ul>
&lt;li>Corre sobre 4 de las H100 (NVLink, tensor parallel) durante ~6 horas.&lt;/li>
&lt;li>Usa &lt;code>transformers + PEFT + bitsandbytes&lt;/code>, con monitoring por MLflow.&lt;/li>
&lt;li>Cada step registra el &lt;code>dataset_id&lt;/code>, &lt;code>dataset_version&lt;/code>, &lt;code>dataset_hash&lt;/code> como input artifact en MLflow.&lt;/li>
&lt;li>El output —un fichero &lt;code>customer_support_v7.safetensors&lt;/code> de ~280 MB con los pesos LoRA— se sube a MinIO con su propio hash, y MLflow registra &lt;code>model_id, model_version, parent_dataset&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>A este punto, la cadena de lineage está cerrada en este tramo:&lt;/p>
&lt;pre tabindex="0">&lt;code>enriched_retrain_2026_q1, v3, sha256:9af...
│
▼
mlflow run train, run_id: 0xa721...
│
▼
customer_support_v7, sha256:5c1...
&lt;/code>&lt;/pre>&lt;h3 id="t--38-días--etapa-eval-el-adapter-v7-pasa-por-eval-gates">t = −38 días — Etapa Eval: el adapter v7 pasa por eval gates&lt;/h3>
&lt;p>El adapter recién entrenado no se promociona. Pasa por una &lt;strong>suite de evals&lt;/strong> cubierta en detalle en el &lt;a href="https://blog.lo0.es/posts/evals-llm-la-capa-despues-de-tracing/">post sobre evals&lt;/a>. El golden eval set —&lt;code>customer_support_golden_v12&lt;/code>, 850 ejemplos curados por humanos, con kappa inter-anotador 0.81— se ejecuta contra dos modelos: el adapter v7 candidato y el v6 actualmente en producción. Las métricas:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Métrica&lt;/th>
&lt;th>v6 (prod)&lt;/th>
&lt;th>v7 (cand.)&lt;/th>
&lt;th>Threshold&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Faithfulness al fragmento RAG&lt;/td>
&lt;td>0.87&lt;/td>
&lt;td>0.89&lt;/td>
&lt;td>≥ 0.82&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Toxicidad (low is good)&lt;/td>
&lt;td>0.012&lt;/td>
&lt;td>0.011&lt;/td>
&lt;td>≤ 0.02&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Tono &amp;ldquo;cálido pero profesional&amp;rdquo; (judge LLM)&lt;/td>
&lt;td>0.71&lt;/td>
&lt;td>0.84&lt;/td>
&lt;td>≥ 0.78&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Format compliance (markdown estructurado)&lt;/td>
&lt;td>0.94&lt;/td>
&lt;td>0.93&lt;/td>
&lt;td>≥ 0.90&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Helpful-but-not-pushy (judge LLM)&lt;/td>
&lt;td>0.66&lt;/td>
&lt;td>0.79&lt;/td>
&lt;td>≥ 0.75&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Latency p95 (ms)&lt;/td>
&lt;td>2840&lt;/td>
&lt;td>2910&lt;/td>
&lt;td>≤ 3500&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>A esto se añade la &lt;strong>suite de guardrails y safety&lt;/strong> cubierta en el &lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">post de guardrails&lt;/a>: jailbreak resistance, PII leakage detection, prompt injection sobre tools MCP. El v7 mejora en safety en dos métricas y empata en el resto.&lt;/p>
&lt;p>El v7 entra al canary 5% del tráfico durante 7 días, manteniendo monitoreo cercano. Al final del canary, las &lt;strong>métricas online&lt;/strong> confirman lo que el offline anticipaba: mejora en tono y helpfulness, latencia equivalente, sin nuevos modos de fallo. Promotion aprobada. El v7 pasa al label &lt;code>production&lt;/code>.&lt;/p>
&lt;h3 id="t--31-días--etapa-deploy-el-adapter-v7-entra-a-serving">t = −31 días — Etapa Deploy: el adapter v7 entra a serving&lt;/h3>
&lt;p>El adapter &lt;code>customer_support_v7&lt;/code> se promueve al cluster de serving. Tres piezas cubiertas en posts independientes entran en juego.&lt;/p>
&lt;p>&lt;strong>vLLM como motor de inferencia.&lt;/strong> El motor vive sobre Kubernetes, deployado vía un Operator dedicado, como cuenta el &lt;a href="https://blog.lo0.es/posts/operators-llm-kubernetes/">post sobre operators de LLM&lt;/a> y el &lt;a href="https://blog.lo0.es/posts/vllm-kubernetes/">post sobre vLLM en K8s&lt;/a>. El operator es responsable de detectar el nuevo adapter en el registry, hot-loadearlo sin reiniciar el motor (capacidad nativa de vLLM con &lt;code>--enable-lora&lt;/code>), y dirigir tráfico a partir del label.&lt;/p>
&lt;p>&lt;strong>Disaggregated serving.&lt;/strong> Como detalla el &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">post sobre disaggregated serving&lt;/a>, el sistema separa &lt;strong>prefill&lt;/strong> (intensivo en compute, throughput-bound) y &lt;strong>decode&lt;/strong> (intensivo en memoria, latencia-bound) en pools de GPUs diferentes. La request del usuario, cuando llegue, prefila en un pod especializado y decodea en otro, comunicándose por NVLink + un fabric KV cache compartido.&lt;/p>
&lt;p>&lt;strong>Cluster GPU multi-tenant.&lt;/strong> El cluster H100 sirve a varios tenants, no solo a la aseguradora del Paso 0. Como cuenta el &lt;a href="https://blog.lo0.es/posts/cluster-h100-plataforma-multi-tenant/">post sobre cluster multi-tenant&lt;/a>, el aislamiento se materializa en cuatro planos: namespace de Kubernetes, ACLs sobre adapters (sólo el namespace del tenant carga sus LoRAs), partitioning del &lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache&lt;/a> por tenant (un tenant no puede leer prefijos cacheados de otro), y quota de tokens/minuto enforzada en el gateway.&lt;/p>
&lt;p>&lt;strong>Prompt registry sincronizado.&lt;/strong> El &lt;code>system_prompt&lt;/code> del producto vive en Langfuse con label &lt;code>production&lt;/code>. La versión activa es &lt;code>customer_support_system_prompt&lt;/code>, versión 12. El gateway lee el prompt de Langfuse en el path de la request (con cache de pocos segundos para no martillear el registry). Detallado en el &lt;a href="https://blog.lo0.es/posts/prompt-versioning-langfuse-mlflow/">post de prompt versioning&lt;/a>.&lt;/p>
&lt;p>Resultado en t = −31 días: la combinación &lt;code>(adapter v7, prompt v12, golden v12)&lt;/code> está activa y servida. El sistema está listo para la request que llegará 31 días más tarde.&lt;/p>
&lt;h2 id="avanzando-la-request-del-usuario-atraviesa-el-sistema">Avanzando: la request del usuario atraviesa el sistema&lt;/h2>
&lt;p>Volvemos al Paso 0: 11:47:32 de un martes. El usuario pulsa Enter. Vamos en tiempo real, en milisegundos.&lt;/p>
&lt;h3 id="t--0-ms--ingreso-por-el-gateway">t = 0 ms — Ingreso por el gateway&lt;/h3>
&lt;p>El navegador del usuario hace POST a &lt;code>chat.aseguradora-ejemplo.com/api/chat&lt;/code>. El tráfico atraviesa el edge load balancer y entra al API gateway del producto SaaS. El gateway:&lt;/p>
&lt;ul>
&lt;li>Autentica el JWT del usuario (cliente final del tenant aseguradora).&lt;/li>
&lt;li>Extrae el &lt;code>tenant_id&lt;/code>, valida que su quota de tokens/minuto no esté agotada.&lt;/li>
&lt;li>Resuelve qué &lt;code>model_id&lt;/code>, &lt;code>adapter_id&lt;/code>, &lt;code>prompt_id&lt;/code> corresponden a este tenant y producto. En este caso: &lt;code>llama-3-70b-int8&lt;/code> + &lt;code>customer_support_v7&lt;/code> + prompt label &lt;code>production&lt;/code>.&lt;/li>
&lt;li>Construye un &lt;code>trace_id&lt;/code> único (W3C TraceContext, propagable a OTel) y arranca un span raíz.&lt;/li>
&lt;/ul>
&lt;p>A los 8 ms, el gateway pasa la request al pool de prefill.&lt;/p>
&lt;h3 id="t--8-ms--pull-del-prompt-versionado">t = 8 ms — Pull del prompt versionado&lt;/h3>
&lt;p>Antes de servir, el cliente OpenAI-compatible que el motor usa internamente hace pull del system prompt activo. Como detalla el &lt;a href="https://blog.lo0.es/posts/prompt-versioning-langfuse-mlflow/">post sobre prompt versioning&lt;/a>, el patrón es:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prompt_registry&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pull&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;customer_support_system_prompt&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">label&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;production&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># apuntando ahora a v12&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Cache local de 30 s reduce el round-trip al 0.1 % de las requests&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El span OTel del prompt pull lleva los atributos &lt;code>gen_ai.prompt.id = customer_support_system_prompt&lt;/code>, &lt;code>gen_ai.prompt.version = 12&lt;/code>, &lt;code>gen_ai.prompt.label = production&lt;/code>. Quedan propagados a todos los hijos.&lt;/p>
&lt;h3 id="t--12-ms--retrieval-rag">t = 12 ms — Retrieval RAG&lt;/h3>
&lt;p>El sistema necesita contexto de la base de conocimiento del tenant. Ejecuta:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">query_embedding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">encoder&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_query&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chunks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">qdrant&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">collection&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;tenant_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">tenant_id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">_kb_v3&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">vector&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">query_embedding&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">limit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">score_threshold&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.72&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">reranked&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">reranker&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rerank&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_query&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chunks&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">top_k&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A los 38 ms, el reranker devuelve dos fragmentos: uno de la &lt;em>Política de cancelación, sección 4.2&lt;/em> y otro de &lt;em>Beneficios del plan premium, sección 2.1&lt;/em>. Como detalla el &lt;a href="https://blog.lo0.es/posts/postgresql-qdrant-ingestion-microservicios/">post sobre PostgreSQL + Qdrant&lt;/a>, el corpus del tenant se mantiene aislado por colección y ACL: ningún tenant puede leer chunks de otro.&lt;/p>
&lt;h3 id="t--40-ms--construcción-del-payload-final">t = 40 ms — Construcción del payload final&lt;/h3>
&lt;p>El motor compone:&lt;/p>
&lt;pre tabindex="0">&lt;code>[system_prompt v12]
+ [contexto recuperado: 2 chunks]
+ [historial breve de la sesión: 1 turno previo]
+ [user query]
&lt;/code>&lt;/pre>&lt;p>Total: ~1850 tokens de contexto. El span OTel registra &lt;code>gen_ai.request.input_tokens = 1850&lt;/code>, &lt;code>gen_ai.request.model = llama-3-70b-int8&lt;/code>, &lt;code>gen_ai.request.adapter = customer_support_v7&lt;/code>.&lt;/p>
&lt;h3 id="t--45-ms--prefill">t = 45 ms — Prefill&lt;/h3>
&lt;p>El payload entra al pool de prefill. La GPU procesa los 1850 tokens en una sola pasada paralela, computando para cada token sus vectores K y V (clave y valor de atención). Esos vectores se materializan como &lt;strong>KV cache&lt;/strong>, cubierto en detalle en el &lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">post de fundamentos del KV cache&lt;/a>. El cache resultante ocupa ~120 MB de VRAM en INT8.&lt;/p>
&lt;p>Aquí aparece una optimización clave: el system prompt v12 está cacheado en el pool de prefill (prefix caching, cubierto en el &lt;a href="https://blog.lo0.es/posts/pagedattention-deep-dive/">post sobre PagedAttention&lt;/a>). Como el system prompt es el mismo para esta tenant, los primeros ~500 tokens del contexto no se recomputan: se leen del cache de prefijo. Eso reduce el prefill efectivo de 1850 tokens a ~1350 tokens, ahorrando ~270 ms de compute.&lt;/p>
&lt;p>A los 580 ms (prefill efectivo), el TTFT (time to first token) está listo. El primer token sale hacia el pool de decode.&lt;/p>
&lt;h3 id="t--580-ms--decode-streaming">t = 580 ms — Decode (streaming)&lt;/h3>
&lt;p>El pool de decode recibe el KV cache prefilled y empieza la generación token a token. Como detalla el &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">post sobre disaggregated serving&lt;/a>, la separación prefill/decode es lo que permite que un sistema multi-tenant mantenga TPS estable: el pool de decode está dimensionado para sostener miles de sesiones decodeando en paralelo a bajo coste por token, mientras el de prefill se dimensiona para bursts de TTFT cortos.&lt;/p>
&lt;p>Generación a ~80 tokens/segundo. La respuesta tendrá ~290 tokens. Tiempo total de decode: ~3.6 s. Streaming: el usuario empieza a ver palabras desde t = 580 ms.&lt;/p>
&lt;p>Mientras el decode avanza, el motor emite spans hijo en cada iteración con &lt;code>gen_ai.response.tokens_generated&lt;/code>, &lt;code>gen_ai.response.cache_hit_ratio&lt;/code>, &lt;code>gen_ai.response.cumulative_latency&lt;/code>. El &lt;a href="https://blog.lo0.es/posts/agentsight-tracing-llm/">post sobre AgentSight&lt;/a> y el &lt;a href="https://blog.lo0.es/posts/mcp-observability-otel/">post sobre MCP observability con OTel&lt;/a> cubren la instrumentación detallada de esta capa.&lt;/p>
&lt;h3 id="t--4-200-ms--respuesta-completa-span-raíz-cerrado">t = 4 200 ms — Respuesta completa, span raíz cerrado&lt;/h3>
&lt;p>La generación termina. El motor cierra el span raíz con &lt;code>gen_ai.response.completion_tokens = 290&lt;/code>, &lt;code>gen_ai.response.finish_reason = stop&lt;/code>, &lt;code>gen_ai.response.total_latency_ms = 4200&lt;/code>. El usuario ve la respuesta final. La sesión queda lista para un siguiente turno o para que el usuario haga clic en thumbs-up/thumbs-down.&lt;/p>
&lt;p>A esta altura, todas las etapas activas han participado:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Data&lt;/strong> (pre-existente): el corpus RAG indexado, el dataset que entrenó el adapter, el golden set que lo validó.&lt;/li>
&lt;li>&lt;strong>Tune&lt;/strong> (pre-existente): el adapter v7 entrenado hace 45 días.&lt;/li>
&lt;li>&lt;strong>Eval&lt;/strong> (pre-existente): los gates que aprobaron la promotion.&lt;/li>
&lt;li>&lt;strong>Deploy&lt;/strong> (en este preciso instante): vLLM + disaggregated + KV cache + multi-tenant.&lt;/li>
&lt;li>&lt;strong>Observe&lt;/strong> (en este preciso instante): los spans OTel emitidos a Langfuse + Tempo, las métricas a Prometheus.&lt;/li>
&lt;li>&lt;strong>Retrain&lt;/strong> (a punto de activarse): el feedback que el usuario marcará en 15 segundos.&lt;/li>
&lt;/ul>
&lt;h2 id="en-paralelo-observe-está-mirando">En paralelo: Observe está mirando&lt;/h2>
&lt;p>Mientras la request sucede, varias piezas de Observe corren en paralelo y dejan huella estructurada.&lt;/p>
&lt;p>&lt;strong>Tracing OTel.&lt;/strong> Cada span (gateway, prompt pull, retrieval, prefill, decode) viaja a Langfuse y a un colector OTel que los reenvía a un backend (Tempo / Jaeger). El &lt;code>trace_id&lt;/code> único enlaza todos los spans. Como detalla el &lt;a href="https://blog.lo0.es/posts/agentsight-tracing-llm/">post sobre tracing con AgentSight&lt;/a>, la propagación end-to-end es el principal habilitador del debug post-incidente: sin ella, no se puede reconstruir qué pasó tres semanas más tarde.&lt;/p>
&lt;p>&lt;strong>Métricas de runtime.&lt;/strong> El motor emite métricas Prometheus por intervalo: &lt;code>gpu_utilization&lt;/code>, &lt;code>kv_cache_usage&lt;/code>, &lt;code>tokens_per_second&lt;/code>, &lt;code>queue_depth&lt;/code>, &lt;code>prefill_latency_p95&lt;/code>, &lt;code>decode_latency_p95&lt;/code>. Las métricas no se asocian a un trace; son agregadas por tenant y servicio.&lt;/p>
&lt;p>&lt;strong>LLM-as-judge online.&lt;/strong> Un porcentaje configurable de respuestas (en este sistema, 2%) se ejecuta también por un judge LLM en background, que puntúa la respuesta contra una rúbrica simple (correcta / parcial / incorrecta + score de tono). El judge no bloquea la respuesta al usuario; alimenta el dashboard.&lt;/p>
&lt;p>&lt;strong>Drift estadístico.&lt;/strong> En paralelo, una pipeline más lenta computa drift sobre la distribución de inputs y outputs. Como cuenta el &lt;a href="https://blog.lo0.es/posts/ebpf-on-device-inference-drift/">post sobre eBPF + drift&lt;/a>, el monitoreo de bajo nivel (latencia, error rate por endpoint) se complementa con drift detection estadístico (KS test, embedding distance) que detecta cuando &amp;ldquo;algo va mal&amp;rdquo; antes de que un thumbs-down lo confirme.&lt;/p>
&lt;p>&lt;strong>Safety y guardrails monitor.&lt;/strong> El &lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">post sobre guardrails&lt;/a> describe la capa que vigila intentos de jailbreak, PII leakage, prompt injection vía tools MCP. En este caso, ninguno se dispara.&lt;/p>
&lt;p>Todas estas piezas operan &lt;strong>continuamente&lt;/strong>, no por request. Pero esta request en particular dejó su huella en cada una de ellas.&lt;/p>
&lt;h2 id="el-feedback-el-bucle-se-cierra">El feedback: el bucle se cierra&lt;/h2>
&lt;p>A los 15 segundos de leer la respuesta, el usuario marca thumbs-down y deja en el formulario: &lt;em>&amp;ldquo;la respuesta es correcta pero el tono es demasiado vendedor; sólo quería saber cómo cancelar&amp;rdquo;&lt;/em>. Ese gesto, aparentemente trivial, dispara una secuencia importante.&lt;/p>
&lt;h3 id="inserción-en-feedback_signals">Inserción en feedback_signals&lt;/h3>
&lt;p>Como detalla el &lt;a href="https://blog.lo0.es/posts/retrain-cerrar-el-bucle-feedback-dataset-adapter/">post sobre Retrain&lt;/a>, el thumbs-down se persiste como una fila estructurada en una tabla Postgres:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">feedback_signals&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">signal_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">trace_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">request_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">signal_type&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">signal_value&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">prompt_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">prompt_version&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">user_segment&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">occurred_at&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">gen_random_uuid&lt;/span>&lt;span class="p">(),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;4f5...&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">-- el trace_id del Paso 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;r-22a...&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">-- request_id
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;thumbs&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;{&amp;#34;vote&amp;#34;:&amp;#34;down&amp;#34;,&amp;#34;reason&amp;#34;:&amp;#34;too pushy&amp;#34;,&amp;#34;text&amp;#34;:&amp;#34;sólo quería saber cómo cancelar&amp;#34;}&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;customer_support_system_prompt&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;llama-3-70b-int8+customer_support_v7&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;premium-es&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;2026-05-19T11:47:51+02:00&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Con esto, la fila queda enlazada por &lt;code>trace_id&lt;/code> a todo lo que ocurrió: prompt v12, contexto recuperado, output completo, métricas de latencia, score del judge (en este caso 0.82, considerado bueno por el judge pero el humano discrepa).&lt;/p>
&lt;h3 id="triage-por-causa-raíz">Triage por causa raíz&lt;/h3>
&lt;p>El equipo MLE pasa por triage la próxima mañana. Combinando reglas heurísticas, LLM-as-classifier y revisión humana:&lt;/p>
&lt;ul>
&lt;li>La señal no es &lt;code>model issue&lt;/code>: el modelo respondió correctamente al prompt que recibió.&lt;/li>
&lt;li>No es &lt;code>retrieval issue&lt;/code>: los chunks recuperados eran los correctos.&lt;/li>
&lt;li>No es &lt;code>infra issue&lt;/code>: la latencia fue normal.&lt;/li>
&lt;li>Es &lt;strong>&lt;code>prompt issue&lt;/code>&lt;/strong>: el system prompt v12 instruye al modelo a &amp;ldquo;ofrecer alternativas antes de procesar acciones destructivas&amp;rdquo;. Esa instrucción genera el &amp;ldquo;tono vendedor&amp;rdquo; en algunos contextos.&lt;/li>
&lt;/ul>
&lt;p>El incidente se acumula con otros del mes en el cluster &amp;ldquo;tono vendedor&amp;rdquo;. Cuando el cluster supere un threshold (típicamente 30-50 incidentes del mismo tipo o un porcentaje del total), entrará a un mini-ciclo incident-driven o esperará al Retrain trimestral, dependiendo del tamaño.&lt;/p>
&lt;h3 id="el-siguiente-ciclo-lo-recoge">El siguiente ciclo lo recoge&lt;/h3>
&lt;p>Tres meses más tarde, en el siguiente Retrain trimestral, este feedback es uno de muchos que motivarán dos cambios:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Nueva versión de prompt v13&lt;/strong> con instrucción ajustada: &amp;ldquo;ofrecer alternativas sólo si el usuario no expresa intención clara de cancelar&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>Posible refuerzo del adapter&lt;/strong> con casos de tono más directo para premium-es. Si el cluster lo justifica.&lt;/li>
&lt;/ul>
&lt;p>El v13 entrará en su propia eval gate. El golden set crecerá con casos donde el tono correcto sea &amp;ldquo;directo, no vendedor&amp;rdquo;. El v8 del adapter (si llega) reentrenará sobre el dataset enriquecido &lt;code>enriched_retrain_2026_q2&lt;/code> que ya contiene este caso anotado.&lt;/p>
&lt;p>El ciclo se cierra. La request del Paso 0 ha contribuido a la versión del sistema que servirá a otro usuario tres meses después.&lt;/p>
&lt;h2 id="lo-que-va-en-cada-trace-identidad-y-trazabilidad">Lo que va en cada trace: identidad y trazabilidad&lt;/h2>
&lt;p>Si el lector mira los siete identificadores omnipresentes en este recorrido, ve la red de identidades que permite todo lo anterior. Es la &lt;strong>infraestructura de identidad&lt;/strong> del sistema LLM en producción:&lt;/p>
&lt;pre tabindex="0">&lt;code>trace_id 4f5... (unique per request)
request_id r-22a... (idem)
prompt_id customer_support_system_prompt
prompt_version 12
prompt_label production
dataset_id enriched_retrain_2026_q1
dataset_version v3 (sha256:9af...)
model_id llama-3-70b-int8
adapter_id customer_support_v7 (sha256:5c1...)
deployment_id d-prod-7b
schema_version 3.2
tenant_id aseguradora-ejemplo
user_segment premium-es
golden_set_id customer_support_golden_v12
&lt;/code>&lt;/pre>&lt;p>Si una sola pieza de ese conjunto falta o no propaga, &lt;strong>la cadena se rompe&lt;/strong>. El siguiente incidente investigado caerá en &amp;ldquo;no podemos rebobinar hasta el origen porque el sistema no lo registró&amp;rdquo;. Por eso los componentes transversales —prompt versioning y data versioning— no son lujos: son la conexión sin la cual las otras seis etapas operan a ciegas.&lt;/p>
&lt;h2 id="diagrama-síntesis-cómo-encajan-las-piezas">Diagrama síntesis: cómo encajan las piezas&lt;/h2>
&lt;pre tabindex="0">&lt;code> ┌─────────────────────────────────────────┐
│ Usuario (cliente final, B2C) │
└─────────────────┬───────────────────────┘
│ chat msg + JWT
▼
┌─────────────────────────────────────────┐
│ Edge LB + WAF + Cilium CNI │
└─────────────────┬───────────────────────┘
│ HTTPS, mTLS interno
▼
┌─────────────────────────────────────────────────┐
│ API Gateway (auth, quota, model routing) │
│ - Resuelve tenant → model + adapter + prompt │
│ - Inicia trace_id (W3C) │
└──────┬─────────────────────┬────────────────────┘
│ │
(pull prompt) │ │ (pull config)
▼ ▼
┌────────────────────┐ ┌──────────────────────┐
│ Langfuse Prompt │ │ Model registry │
│ Registry (v12) │ │ (adapter v7) │
└─────────┬──────────┘ └──────────┬───────────┘
│ │
└──────────┬───────────────┘
│ payload listo
▼
┌──────────────────────────────────────────┐
│ vLLM motor (K8s Operator) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Pool prefill │ → │ Pool decode │ │
│ │ (H100×N) │ │ (H100×M) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ KV cache fabric │ │
│ └──────────────────┘ │
│ - prefix caching del system prompt │
│ - PagedAttention │
└──────┬───────────────────────────────────┘
│ tokens stream
▼
┌─────────────────────────────────────────┐
│ Usuario ve respuesta + UI thumbs/UX │
└─────────────────┬───────────────────────┘
│ feedback (15 s después)
▼
┌─────────────────────────────────────────┐
│ feedback_signals (Postgres) │
│ + Langfuse scores │
└─────────────────┬───────────────────────┘
│
┌────────────────────────┼────────────────────────┐
│ │ │
▼ ▼ ▼
triage ciclo Retrain trimestral dataset_id
causa raíz o incident-driven enriquecido (DVC)
│
▼
Tune del v8
(próximo ciclo)
En paralelo durante toda la request, instrumentación OTel:
spans → Tempo / Jaeger ; eventos → Langfuse ; métricas → Prometheus
&lt;/code>&lt;/pre>&lt;h2 id="el-stack-on-premise-aplicado">El stack on-premise aplicado&lt;/h2>
&lt;p>Llevar lo anterior a una infra on-premise genérica de perfil consultor (RTX 4090 + cluster 4×H100 SXM):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Capa&lt;/th>
&lt;th>Recursos típicos&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Plano de red&lt;/td>
&lt;td>Edge LB (HAProxy / nginx ingress) + CNI Cilium con BGP, cubierto en &lt;a href="https://blog.lo0.es/posts/rke2-cilium-bgp/">Cilium BGP&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plano de cómputo K8s&lt;/td>
&lt;td>RKE2 con dos nodes managers + node pool de GPU&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plano GPU productivo&lt;/td>
&lt;td>4× H100 SXM (NVLink, 80 GB cada una), particionadas vía MIG en pools prefill/decode&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plano GPU desarrollo&lt;/td>
&lt;td>1× RTX 4090 (24 GB) para evals offline, drift-check embeddings, smoke tests&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plano storage&lt;/td>
&lt;td>MinIO o Ceph object store; DVC remote + lakeFS backend&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plano datos OLTP&lt;/td>
&lt;td>Postgres 18 con replicación; pgvector 0.8 para casos pequeños&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plano vector&lt;/td>
&lt;td>Qdrant o Milvus para corpus RAG grandes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plano stream&lt;/td>
&lt;td>Kafka (Redpanda / Apache puro) + Schema Registry; CDC con Debezium o Flink CDC&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plano observabilidad&lt;/td>
&lt;td>OTel Collector + Tempo (traces) + Prometheus (metrics) + Loki (logs); Langfuse para LLM-específico&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plano runtime security&lt;/td>
&lt;td>Tetragon, cubierto en &lt;a href="https://blog.lo0.es/posts/tetragon-runtime-security/">post sobre runtime security&lt;/a>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>La densidad real no es la suma de las cajas: es la &lt;strong>operativa&lt;/strong> que ata las cajas. Un cluster con todas las piezas pero sin disciplina de versionado, sin propagación de &lt;code>trace_id&lt;/code> extremo a extremo, sin schema contracts y sin retraining cadenciado, es un cluster que sirve LLM &lt;strong>una vez&lt;/strong> y que envejece. La diferencia entre un proyecto y una plataforma es exactamente eso.&lt;/p>
&lt;h2 id="diez-puentes-entre-etapas-donde-se-rompe-el-sistema">Diez puentes entre etapas donde se rompe el sistema&lt;/h2>
&lt;p>El recorrido revela algo importante: los fallos rara vez están &lt;strong>dentro&lt;/strong> de una etapa; están en los &lt;strong>puentes&lt;/strong> entre etapas. Diez puentes habituales:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Data → Tune&lt;/strong>: el dataset no propaga su &lt;code>(dataset_id, dataset_version)&lt;/code> al trainer. Mismo dataset entrenado dos veces produce dos &lt;code>model_id&lt;/code> que no se pueden distinguir.&lt;/li>
&lt;li>&lt;strong>Tune → Eval&lt;/strong>: el modelo entrenado no propaga su lineage al run de eval. El eval pasa, pero no queda registrado contra qué dataset se entrenó. Tres meses después, irreproducible.&lt;/li>
&lt;li>&lt;strong>Eval → Deploy&lt;/strong>: la promotion ocurre sin que el sistema de serving registre qué versión del adapter está sirviendo en cada instante. El día que el modelo da una respuesta peligrosa, no se sabe qué adapter respondió.&lt;/li>
&lt;li>&lt;strong>Deploy → Observe&lt;/strong>: el motor no emite &lt;code>gen_ai.request.adapter&lt;/code>, &lt;code>gen_ai.prompt.version&lt;/code>, &lt;code>gen_ai.dataset.version&lt;/code> como atributos del span. Los traces existen pero no se pueden cruzar con el lineage.&lt;/li>
&lt;li>&lt;strong>Observe → Retrain&lt;/strong>: el feedback se captura en una herramienta (Langfuse, Phoenix) pero nadie lo lee. La etapa Retrain &amp;ldquo;está&amp;rdquo;, pero el feedback se acumula sin triagear.&lt;/li>
&lt;li>&lt;strong>Retrain → Data&lt;/strong>: el dataset enriquecido se mete en el siguiente Tune sin pasar por la disciplina de versionado, schema contract y holdout check. Contaminación silenciosa del golden set.&lt;/li>
&lt;li>&lt;strong>Prompt versioning ↔ todo&lt;/strong>: el &lt;code>prompt_id, prompt_version&lt;/code> no se propaga a los spans. El día que el equipo descubre que un cambio de prompt regresionó el sistema, no puede aislar cuál ni cuándo.&lt;/li>
&lt;li>&lt;strong>Data versioning ↔ todo&lt;/strong>: el &lt;code>dataset_id, dataset_version&lt;/code> no aparece en el experiment tracking. Se &amp;ldquo;vuelve a entrenar v8&amp;rdquo; pero nadie puede demostrar que sea sobre el dataset enriquecido y no sobre el viejo.&lt;/li>
&lt;li>&lt;strong>MCP ↔ tools&lt;/strong>: el sistema invoca tools (cancelación, modificación de pólizas) pero no registra &lt;code>gen_ai.tool.invocation_id&lt;/code> enlazado al trace. Las acciones quedan disociadas de la respuesta que las generó.&lt;/li>
&lt;li>&lt;strong>Schema Registry ↔ datos&lt;/strong>: los datasets versionan contenido pero no schema. Un breaking change en el &lt;code>expected_output&lt;/code> rompe el eval silenciosamente; nadie nota nada hasta que un humano revisa los resultados.&lt;/li>
&lt;/ol>
&lt;p>Los puentes están cubiertos a lo largo del blog. La operativa los enforza. La cultura del equipo los mantiene.&lt;/p>
&lt;h2 id="cómo-recorrer-el-blog">Cómo recorrer el blog&lt;/h2>
&lt;p>Si llegas a este post desde fuera y quieres una ruta de lectura:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>El mapa&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/pipeline-llmops-seis-etapas/">Pipeline LLMOps de seis etapas&lt;/a> — el mapa maestro de todo lo demás.&lt;/li>
&lt;li>&lt;strong>El contexto&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/mlops-llms-panorama-2026/">MLOps específico para LLMs en 2026&lt;/a> — el panorama y por qué LLMOps no es MLOps clásico.&lt;/li>
&lt;li>&lt;strong>Inferencia desde dentro hacia afuera&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache&lt;/a> → &lt;a href="https://blog.lo0.es/posts/pagedattention-deep-dive/">PagedAttention deep dive&lt;/a> → &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">Disaggregated serving&lt;/a> → &lt;a href="https://blog.lo0.es/posts/cluster-h100-plataforma-multi-tenant/">Cluster GPU multi-tenant&lt;/a> → &lt;a href="https://blog.lo0.es/posts/vllm-kubernetes/">vLLM en K8s&lt;/a> → &lt;a href="https://blog.lo0.es/posts/operators-llm-kubernetes/">Operators LLM K8s&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Datos&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/data-versioning-dvc-lakefs/">Data versioning con DVC y lakeFS&lt;/a> → &lt;a href="https://blog.lo0.es/posts/postgresql-qdrant-ingestion-microservicios/">PostgreSQL + Qdrant ingestión&lt;/a> → &lt;a href="https://blog.lo0.es/posts/rag-kafka-datalake-arquitectura/">RAG sobre Kafka&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Tune&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/fine-tuning-continuo-produccion/">Fine-tuning continuo en producción&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Eval&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/evals-llm-la-capa-despues-de-tracing/">Evals: la capa después del tracing&lt;/a> → &lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">Guardrails y safety&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Observe&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/agentsight-tracing-llm/">AgentSight tracing LLM&lt;/a> → &lt;a href="https://blog.lo0.es/posts/mcp-observability-otel/">MCP observability con OTel&lt;/a> → &lt;a href="https://blog.lo0.es/posts/ebpf-on-device-inference-drift/">eBPF on-device + drift&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Retrain&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/retrain-cerrar-el-bucle-feedback-dataset-adapter/">Cerrar el bucle feedback → dataset → adapter&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Transversales&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/prompt-versioning-langfuse-mlflow/">Prompt versioning con Langfuse y MLflow&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Infra de soporte&lt;/strong> (la base sobre la que se monta todo): &lt;a href="https://blog.lo0.es/posts/rke2-cilium-bgp/">RKE2 con Cilium BGP&lt;/a>, &lt;a href="https://blog.lo0.es/posts/hubble-observabilidad-ebpf/">Hubble + observabilidad eBPF&lt;/a>, &lt;a href="https://blog.lo0.es/posts/tetragon-runtime-security/">Tetragon runtime security&lt;/a>.&lt;/li>
&lt;/ol>
&lt;h2 id="lo-que-no-hemos-cubierto-todavía">Lo que no hemos cubierto (todavía)&lt;/h2>
&lt;p>A primer nivel está lo principal. Los siguientes posts del blog —cuando los temas lo justifiquen— podrían profundizar en:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Schema Registry para LLM data y prompts&lt;/strong>: la otra mitad del data contract.&lt;/li>
&lt;li>&lt;strong>AI Gateway dedicado&lt;/strong>: LiteLLM, Portkey, Kong AI Gateway como plano de control.&lt;/li>
&lt;li>&lt;strong>OTel gen_ai semantic conventions&lt;/strong>: el estándar emergente que ata los siete identificadores del bloque &amp;ldquo;identidad&amp;rdquo; en spans bien formados.&lt;/li>
&lt;li>&lt;strong>Federated learning sobre datos de clientes regulados&lt;/strong>: cómo entrenar sin centralizar el corpus.&lt;/li>
&lt;li>&lt;strong>Capacity planning&lt;/strong> para clusters multi-tenant compartidos.&lt;/li>
&lt;li>&lt;strong>Disaster recovery&lt;/strong> de un servicio LLM: cómo reproducir el estado del sistema 30 días atrás.&lt;/li>
&lt;li>&lt;strong>Cost accounting por tenant&lt;/strong>: tokens × pesos × adapter × infraestructura → factura.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/pipeline-llmops-seis-etapas/">El pipeline LLMOps de seis etapas&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/mlops-llms-panorama-2026/">MLOps específico para LLMs en 2026&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/data-versioning-dvc-lakefs/">Data versioning para LLMOps&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/postgresql-qdrant-ingestion-microservicios/">PostgreSQL + Qdrant para ingestión&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/rag-kafka-datalake-arquitectura/">RAG sobre Kafka: arquitectura técnica&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/fine-tuning-continuo-produccion/">Fine-tuning continuo en producción&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/evals-llm-la-capa-despues-de-tracing/">Evals: la capa después del tracing&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">Guardrails y safety en LLMs&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache: la memoria de trabajo de la inferencia LLM&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/pagedattention-deep-dive/">PagedAttention por dentro&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">Disaggregated serving: prefill y decode&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/cluster-h100-plataforma-multi-tenant/">El cluster GPU como plataforma multi-tenant&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/vllm-kubernetes/">vLLM en Kubernetes&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/operators-llm-kubernetes/">Operators LLM en Kubernetes&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/agentsight-tracing-llm/">AgentSight: tracing LLM end-to-end&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/mcp-observability-otel/">MCP por dentro y observabilidad con OTel&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/ebpf-on-device-inference-drift/">eBPF en inferencia local y drift detection&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/retrain-cerrar-el-bucle-feedback-dataset-adapter/">Retrain: cerrar el bucle feedback → dataset → adapter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/prompt-versioning-langfuse-mlflow/">Prompt versioning con Langfuse y MLflow&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/rke2-cilium-bgp/">RKE2 con Cilium BGP&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/hubble-observabilidad-ebpf/">Hubble + observabilidad eBPF&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/tetragon-runtime-security/">Tetragon runtime security&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.w3.org/TR/trace-context/">W3C Trace Context&lt;/a> — propagación de &lt;code>traceparent&lt;/code> y &lt;code>tracestate&lt;/code> end-to-end.&lt;/li>
&lt;li>&lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">OpenTelemetry GenAI Semantic Conventions&lt;/a> — atributos &lt;code>gen_ai.*&lt;/code> para spans LLM.&lt;/li>
&lt;li>&lt;a href="https://langfuse.com/docs">Langfuse documentation&lt;/a> — observability y prompt registry.&lt;/li>
&lt;li>&lt;a href="https://docs.vllm.ai/">vLLM documentation&lt;/a> — motor de inferencia productivo con PagedAttention y LoRA hot-swap.&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/">Kubernetes Operators&lt;/a> — patrón de gestión declarativa.&lt;/li>
&lt;li>&lt;a href="https://mlflow.org/docs/latest/">MLflow Tracking and Model Registry&lt;/a> — lineage de runs e input artifacts.&lt;/li>
&lt;li>&lt;a href="https://dvc.org/">DVC&lt;/a> y &lt;a href="https://lakefs.io/">lakeFS&lt;/a> — versionado de datasets, unificadas en Nov 2025.&lt;/li>
&lt;li>&lt;a href="https://openlineage.io/">OpenLineage&lt;/a> — estándar abierto de eventos de lineage entre sistemas.&lt;/li>
&lt;li>ENS / NIS2: marcos de compliance que aplican a operadores en la UE; lectura recomendada para el contexto en que opera el escenario.&lt;/li>
&lt;/ul></description></item></channel></rss>