<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Trazas on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/trazas/</link><description>Recent content in Trazas on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Sat, 12 Sep 2026 07:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/trazas/index.xml" rel="self" type="application/rss+xml"/><item><title>Langfuse v4, día 2 (1 de 8): el modelo de datos cambió debajo, y qué entra de verdad en una traza</title><link>https://blog.lo0.es/posts/langfuse-v4-que-entra-en-una-traza/</link><pubDate>Sat, 12 Sep 2026 07:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/langfuse-v4-que-entra-en-una-traza/</guid><description>&lt;blockquote>
&lt;p>Primer artículo de una serie de ocho sobre operar Langfuse v4 en producción. La arquitectura de la versión 3 y sus diez palancas de rendimiento ya se trataron en &lt;a href="https://blog.lo0.es/posts/langfuse-self-hosting-arquitectura-tuning/">Langfuse por dentro&lt;/a>; la costura con el gateway, en &lt;a href="https://blog.lo0.es/posts/litellm-langfuse-par-operativo/">el par operativo&lt;/a>. Esta serie trata el día 2. Verificado contra Langfuse 4.35.0, commit del 11 de septiembre de 2026.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>&lt;strong>La versión 4 es primero observaciones.&lt;/strong> No hay tabla de trazas. Una traza es un conjunto de observaciones que comparten identificador, y los atributos de traza viven desnormalizados en cada fila: nombre, usuario, sesión, etiquetas, versión, entrega y entorno (&lt;code>clickhouse/migrations/canonical/0039_create_events_full.up.sql&lt;/code>). Toda la intuición de &amp;ldquo;primero creo la traza y luego le cuelgo cosas&amp;rdquo; deja de describir el almacenamiento.&lt;/p>
&lt;p>&lt;strong>Los tipos de observación son diez, no tres.&lt;/strong> A los clásicos &lt;code>SPAN&lt;/code>, &lt;code>EVENT&lt;/code> y &lt;code>GENERATION&lt;/code> se suman &lt;code>AGENT&lt;/code>, &lt;code>TOOL&lt;/code>, &lt;code>CHAIN&lt;/code>, &lt;code>RETRIEVER&lt;/code>, &lt;code>EVALUATOR&lt;/code>, &lt;code>EMBEDDING&lt;/code> y &lt;code>GUARDRAIL&lt;/code> (&lt;code>packages/shared/src/domain/observations.ts:5&lt;/code>). Usar el tipo correcto cambia lo que la interfaz sabe agrupar.&lt;/p>
&lt;p>&lt;strong>Los límites reales están en el código y no coinciden con lo que se supone.&lt;/strong> El cuerpo de una petición a la ingesta clásica está topado en &lt;strong>4,5 MB&lt;/strong> (&lt;code>api/public/ingestion.ts:40&lt;/code>). El cuerpo OTLP, en &lt;strong>512 MiB&lt;/strong>, comprimido y descomprimido, con 413 al superarlo. Un span OTLP por encima de 9,5 MB solo se registra en el log, no se rechaza. Los identificadores admiten 800 caracteres. El nombre de traza, 1.000.&lt;/p>
&lt;p>&lt;strong>El ingestor busca el usuario en cinco sitios y la sesión en seis.&lt;/strong> Con orden de precedencia estricto, empezando por los atributos propios de Langfuse y terminando en los metadatos de otros marcos. Entre los de sesión aparece &lt;code>gen_ai.conversation.id&lt;/code>, que es el estándar de OpenTelemetry, así que una instrumentación conforme funciona sin traducción.&lt;/p>
&lt;p>&lt;strong>Los scores no viajan por OTLP.&lt;/strong> Cero ocurrencias de la palabra en las 3.864 líneas del procesador de ingesta OTLP. Entran por la ruta de ingesta clásica, por el endpoint de scores o por la interfaz. Cualquier diseño que dé por hecho que un evaluador puede escribir su puntuación por el mismo canal que la traza está equivocado.&lt;/p>
&lt;p>&lt;strong>La función de enmascarado clásica no cubre lo que casi todo el mundo cree.&lt;/strong> Solo actúa sobre datos que pasan por las llamadas del SDK. Los atributos que produce una instrumentación automática de terceros salen sin enmascarar. Hay una función nueva que sí actúa sobre los atributos del lote, pero deja fuera los eventos de span, que es donde las convenciones recientes meten los mensajes.&lt;/p>
&lt;p>&lt;strong>Filtrar por etiquetas o por mensaje de estado es un escaneo.&lt;/strong> En la tabla de listados, los campos indexados son el identificador de span, el de traza, el de usuario, el de sesión, las fechas, el modelo y los nombres de metadato. El nombre, las etiquetas, la versión y el mensaje de estado no tienen índice.&lt;/p>
&lt;h2 id="estás-aquí-observe-día-2">Estás aquí: OBSERVE, día 2&lt;/h2>
&lt;p>La serie anterior sobre este par cubrió la elección y el montaje. La &lt;a href="https://blog.lo0.es/posts/langfuse-self-hosting-arquitectura-tuning/">arquitectura de seis servicios&lt;/a> explicaba la versión 3; el &lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">tracing con OpenTelemetry&lt;/a> explicaba el pipeline; el &lt;a href="https://blog.lo0.es/posts/litellm-langfuse-par-operativo/">par con el gateway&lt;/a> explicaba dónde se pierde la traza entre las dos piezas.&lt;/p>
&lt;p>Lo que queda sin cubrir es el trabajo de los tres años siguientes: migrar, dimensionar ClickHouse, retener y borrar, hacer copias, alertar, sacar los datos fuera. Esta serie va de eso, y empieza por la pregunta que condiciona todo lo demás, que es qué se guarda.&lt;/p>
&lt;h2 id="la-analogía-el-archivo-que-cambió-de-sistema">La analogía: el archivo que cambió de sistema&lt;/h2>
&lt;p>Un archivo con carpetas por expediente y documentos dentro de cada carpeta es fácil de entender. Se abre la carpeta, se mira lo que hay. El problema aparece cuando hay que buscar todos los documentos de un tipo en los últimos dos años: hay que abrir todas las carpetas.&lt;/p>
&lt;p>Los archivos grandes resuelven eso al revés. No hay carpeta: cada documento lleva copiada en su cabecera la referencia del expediente, el nombre del cliente y la fecha. Buscar por cliente es inmediato. Reconstruir un expediente es agrupar por referencia. A cambio, cada documento pesa un poco más, y lo que se escribe en esa cabecera importa mucho, porque de ahí sale todo lo que se puede buscar.&lt;/p>
&lt;p>Langfuse v4 hizo exactamente ese cambio. Lo que antes era una carpeta con atributos propios ahora va repetido en la cabecera de cada documento. De ahí salen las dos consecuencias operativas del artículo: qué campos conviene rellenar y qué campos no conviene llenar de contenido.&lt;/p>
&lt;h2 id="el-modelo-de-datos-como-está-en-el-código">El modelo de datos, como está en el código&lt;/h2>
&lt;h3 id="las-tablas">Las tablas&lt;/h3>
&lt;p>Tres piezas nuevas en ClickHouse:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>events_full&lt;/code>&lt;/strong> (migración 0039): la tabla de verdad, con la entrada y la salida completas, comprimidas con ZSTD nivel 3 e indexadas con índices de texto completo sobre las versiones en minúscula. Motor &lt;code>ReplacingMergeTree&lt;/code> ordenado por marca de tiempo del evento y bandera de borrado, particionado por mes de la hora de inicio.&lt;/li>
&lt;li>&lt;strong>&lt;code>events_core&lt;/code>&lt;/strong> (migración 0040): la tabla de listados, más ligera.&lt;/li>
&lt;li>&lt;strong>&lt;code>events_core_mv&lt;/code>&lt;/strong> (migración 0041): la vista materializada que rellena la anterior, y que &lt;strong>trunca a doscientos caracteres&lt;/strong> la entrada, la salida y cada valor de metadato.&lt;/li>
&lt;/ul>
&lt;p>Ese truncado a doscientos caracteres es la primera cosa que hay que saber. Lo que se ve en un listado no es lo que hay guardado; es un recorte. Y al leer una traza completa se aplica además un segundo tope, &lt;code>LANGFUSE_SERVER_SIDE_IO_CHAR_LIMIT&lt;/code>, con valor por defecto de mil (&lt;code>packages/shared/src/env.ts:513&lt;/code>).&lt;/p>
&lt;p>En la tabla &lt;code>events_full&lt;/code> hay una línea de configuración que dice mucho: &lt;code>index_granularity_bytes = '64Mi'&lt;/code>, con el comentario de que evita gránulos muy pequeños debido a filas grandes. Es el propio esquema reconociendo que las filas de este sistema son voluminosas.&lt;/p>
&lt;h3 id="los-diez-tipos-de-observación">Los diez tipos de observación&lt;/h3>
&lt;p>&lt;code>SPAN&lt;/code>, &lt;code>EVENT&lt;/code>, &lt;code>GENERATION&lt;/code>, &lt;code>AGENT&lt;/code>, &lt;code>TOOL&lt;/code>, &lt;code>CHAIN&lt;/code>, &lt;code>RETRIEVER&lt;/code>, &lt;code>EVALUATOR&lt;/code>, &lt;code>EMBEDDING&lt;/code> y &lt;code>GUARDRAIL&lt;/code> (&lt;code>domain/observations.ts:5&lt;/code>), con sus eventos de ingesta correspondientes (&lt;code>server/ingestion/types.ts:279&lt;/code>).&lt;/p>
&lt;p>Para una plataforma con agentes, la diferencia entre marcar una llamada a herramienta como &lt;code>SPAN&lt;/code> genérico o como &lt;code>TOOL&lt;/code> es la diferencia entre poder contar llamadas a herramienta por sesión y tener que reconstruirlo a mano. Vale la pena revisar la instrumentación con esta lista delante.&lt;/p>
&lt;p>El nivel sigue siendo &lt;code>DEBUG&lt;/code>, &lt;code>DEFAULT&lt;/code>, &lt;code>WARNING&lt;/code> y &lt;code>ERROR&lt;/code> (&lt;code>domain/observations.ts:31&lt;/code>), con un mensaje de estado libre.&lt;/p>
&lt;h3 id="los-límites-con-su-origen">Los límites, con su origen&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Campo&lt;/th>
&lt;th>Límite&lt;/th>
&lt;th>Fichero&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Identificadores de traza, span y observación&lt;/td>
&lt;td>800 caracteres, sin retorno de carro&lt;/td>
&lt;td>&lt;code>ingestion/types.ts:10&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Nombre de traza&lt;/td>
&lt;td>1.000 caracteres&lt;/td>
&lt;td>&lt;code>ingestion/types.ts:428&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Entorno&lt;/td>
&lt;td>40 caracteres, patrón de minúsculas, dígitos, guion y guion bajo&lt;/td>
&lt;td>&lt;code>ingestion/types.ts:226&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Score de tipo texto&lt;/td>
&lt;td>500 caracteres&lt;/td>
&lt;td>&lt;code>domain/scores.ts:44&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Nombre de configuración de score&lt;/td>
&lt;td>35 caracteres&lt;/td>
&lt;td>&lt;code>domain/score-configs.ts:11&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cuerpo de la ingesta clásica&lt;/td>
&lt;td>4,5 MB por petición&lt;/td>
&lt;td>&lt;code>api/public/ingestion.ts:40&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cuerpo OTLP&lt;/td>
&lt;td>512 MiB, ajustable&lt;/td>
&lt;td>&lt;code>web/src/env.mjs:583&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Span OTLP considerado grande&lt;/td>
&lt;td>9,5 MB, solo se registra&lt;/td>
&lt;td>&lt;code>packages/shared/src/env.ts:193&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Nombre, mensaje de estado, versión, entrega, etiquetas&lt;/td>
&lt;td>&lt;strong>sin límite declarado&lt;/strong>&lt;/td>
&lt;td>&lt;code>ingestion/types.ts:424&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Metadatos&lt;/td>
&lt;td>&lt;strong>sin límite declarado&lt;/strong>&lt;/td>
&lt;td>&lt;code>ingestion/types.ts:425&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Los dos últimos son los peligrosos, porque no tienen tope de esquema y el techo real acaba siendo el del cuerpo de la petición. Un metadato de tres megas pasa la validación y llega hasta ClickHouse.&lt;/p>
&lt;p>Un apunte sobre el prefijo &lt;code>langfuse&lt;/code> en el campo de entorno: está reservado para las trazas internas del propio producto y se elimina de forma idempotente en las entradas públicas (&lt;code>ingestion/types.ts:228&lt;/code>), incluido el caso de quien intente colarlo duplicado. El valor por defecto es &lt;code>default&lt;/code>.&lt;/p>
&lt;p>Sobre los límites de tasa: en la nube hay cuotas por plan, pero &lt;strong>en self-hosted no se aplican&lt;/strong>. El servicio devuelve directamente que no hay limitación (&lt;code>features/public-api/server/RateLimitService.ts&lt;/code>). Es una buena noticia para exportaciones masivas y una mala para quien confíe en ese mecanismo como protección.&lt;/p>
&lt;h2 id="las-dos-vías-de-instrumentación">Las dos vías de instrumentación&lt;/h2>
&lt;h3 id="lo-que-se-pierde-por-otlp-y-es-una-cosa-concreta">Lo que se pierde por OTLP, y es una cosa concreta&lt;/h3>
&lt;p>La comprobación es sencilla y el resultado es tajante: &lt;strong>no hay ninguna ocurrencia de la palabra &lt;code>score&lt;/code> en el procesador de ingesta OTLP&lt;/strong>, un fichero de 3.864 líneas. Los scores solo entran por tres sitios: el evento &lt;code>score-create&lt;/code> de la ruta de ingesta clásica, el endpoint &lt;code>POST /api/public/scores&lt;/code>, o la interfaz.&lt;/p>
&lt;p>Esto tiene una consecuencia de diseño que conviene tener clara antes de montar evaluadores: la traza puede viajar por el colector de OpenTelemetry con todo lo bueno que eso trae, cola en disco, reintentos y muestreo, pero la puntuación que le ponga un evaluador tiene que ir por un canal distinto y con las claves del proyecto.&lt;/p>
&lt;p>Todo lo demás sí viaja por OTLP. Lo que no está en las listas de mapeo se conserva como metadato convertido a cadena, visible pero fuera de los campos indexados.&lt;/p>
&lt;h3 id="la-cadena-de-precedencia-que-es-el-mapa-que-hay-que-tener">La cadena de precedencia, que es el mapa que hay que tener&lt;/h3>
&lt;p>Verificado en &lt;code>packages/shared/src/server/otel/OtelIngestionProcessor.ts&lt;/code>:&lt;/p>
&lt;p>&lt;strong>Identificador de usuario&lt;/strong> (línea 2545), en este orden: &lt;code>langfuse.user.id&lt;/code>, &lt;code>user.id&lt;/code>, el metadato de observación, el metadato de traza, y por último el metadato del SDK de Vercel.&lt;/p>
&lt;p>&lt;strong>Identificador de sesión&lt;/strong> (línea 2565): &lt;code>langfuse.session.id&lt;/code>, &lt;code>session.id&lt;/code>, &lt;strong>&lt;code>gen_ai.conversation.id&lt;/code>&lt;/strong>, metadato de observación, metadato de traza, metadato de Vercel.&lt;/p>
&lt;p>&lt;strong>Entorno&lt;/strong> (línea 2383): &lt;code>langfuse.environment&lt;/code>, &lt;code>deployment.environment.name&lt;/code>, &lt;code>deployment.environment&lt;/code>; en cada clave se miran primero los atributos del span y después los del recurso.&lt;/p>
&lt;p>&lt;strong>Modelo&lt;/strong> (línea 2753): &lt;code>langfuse.observation.model.name&lt;/code>, &lt;code>gen_ai.response.model&lt;/code>, &lt;code>ai.model.id&lt;/code>, &lt;code>gen_ai.request.model&lt;/code>, &lt;code>llm.response.model&lt;/code>, &lt;code>llm.model_name&lt;/code>, &lt;code>model&lt;/code>.&lt;/p>
&lt;p>&lt;strong>Nivel&lt;/strong> (línea 1250): el atributo propio, con alias aceptados donde &lt;code>SUCCESS&lt;/code> y &lt;code>OK&lt;/code> pasan a &lt;code>DEFAULT&lt;/code>, &lt;code>WARN&lt;/code> a &lt;code>WARNING&lt;/code> y &lt;code>FATAL&lt;/code> o &lt;code>CRITICAL&lt;/code> a &lt;code>ERROR&lt;/code>; si no hay atributo y el estado del span es de error, sale &lt;code>ERROR&lt;/code>.&lt;/p>
&lt;p>&lt;strong>Etiquetas&lt;/strong> (línea 3227): &lt;code>langfuse.trace.tags&lt;/code>, &lt;code>langfuse.tags&lt;/code>, metadatos de observación y de traza, metadato de Vercel, y &lt;code>tag.tags&lt;/code>.&lt;/p>
&lt;p>&lt;strong>Entrada y salida&lt;/strong> (línea 1790): una cadena de unos veinte marcos en orden estricto, empezando por los atributos propios de Langfuse y pasando por Genkit, el SDK de Vercel, el evento estándar de detalles de operación de inferencia de las convenciones a partir de la 1.37, los eventos de mensaje por rol, los atributos de prompt y completion, Logfire, MLflow, TraceLoop, OpenInference y los atributos &lt;code>gen_ai.input.messages&lt;/code> y &lt;code>gen_ai.output.messages&lt;/code>.&lt;/p>
&lt;p>Que esa cadena tenga veinte entradas dice algo sobre el estado real de las convenciones: &lt;a href="https://blog.lo0.es/posts/litellm-gateway-mcp-segunda-puerta/">ya se apuntó&lt;/a> que las convenciones de IA generativa se extrajeron a su propio repositorio sin ninguna versión publicada. Mientras eso siga así, el backend tiene que adivinar, y adivina con una lista de precedencias.&lt;/p>
&lt;p>La parte práctica: &lt;strong>si se emiten los atributos propios de Langfuse, ganan siempre&lt;/strong>. Si se quiere instrumentación neutra respecto al backend, &lt;code>gen_ai.conversation.id&lt;/code> y &lt;code>deployment.environment.name&lt;/code> son los dos atributos estándar que Langfuse sí entiende.&lt;/p>
&lt;h3 id="los-metadatos-y-cómo-acaban-en-la-tabla">Los metadatos, y cómo acaban en la tabla&lt;/h3>
&lt;p>Un metadato se aplana en dos arreglos paralelos, uno de nombres y otro de valores, con rutas separadas por puntos y todo convertido a cadena (&lt;code>otel/utils.ts:239&lt;/code>, aplicado en &lt;code>worker/src/services/IngestionService/index.ts:363&lt;/code>). Un objeto anidado con veinte claves son veinte entradas en cada arreglo.&lt;/p>
&lt;p>Hay un tope de 10.001 posiciones al reconstruir arreglos desde atributos OTel (&lt;code>OtelIngestionProcessor.ts:271&lt;/code>), que es alto pero existe.&lt;/p>
&lt;h2 id="sesiones-usuarios-y-cardinalidad">Sesiones, usuarios y cardinalidad&lt;/h2>
&lt;p>Una sesión es una columna de texto con un índice de filtro Bloom al 1 %. Lo mismo el usuario. La tabla de Postgres que acompaña a las sesiones guarda solo si está marcada, si es pública y su entorno; no hay contador ni tope. &lt;strong>No existe límite de trazas por sesión en el código&lt;/strong>, y la documentación recomienda identificadores de sesión por debajo de 200 caracteres ASCII.&lt;/p>
&lt;p>La diferencia de cardinalidad importa y no es intuitiva: usuario y sesión son texto normal, así que una cardinalidad alta no rompe el esquema, solo infla los filtros Bloom. &lt;strong>El entorno, en cambio, es de cardinalidad baja declarada.&lt;/strong> Generar un valor de entorno dinámico por inquilino degrada ClickHouse de forma medible.&lt;/p>
&lt;p>El coste agregado sale de un mapa de detalles de coste con columnas materializadas de entrada, salida y total. El endpoint de sesiones clásico está obsoleto; el sustituto es el de observaciones de la versión 2 con filtro.&lt;/p>
&lt;h2 id="scores-cinco-tipos-y-tres-fuentes">Scores: cinco tipos y tres fuentes&lt;/h2>
&lt;p>Los tipos son &lt;code>NUMERIC&lt;/code>, &lt;code>CATEGORICAL&lt;/code>, &lt;code>BOOLEAN&lt;/code>, &lt;code>CORRECTION&lt;/code> y &lt;code>TEXT&lt;/code> (&lt;code>domain/scores.ts:45&lt;/code>). El booleano exige valor cero o uno. La corrección no admite configuración asociada. El texto está topado a 500 caracteres.&lt;/p>
&lt;p>Las fuentes son &lt;code>API&lt;/code>, &lt;code>EVAL&lt;/code> y &lt;code>ANNOTATION&lt;/code>. La fuente &lt;code>EVAL&lt;/code> está &lt;strong>reservada para los evaluadores internos&lt;/strong>: la API pública solo acepta las otras dos. Los scores de anotación exigen una configuración asociada salvo el de corrección.&lt;/p>
&lt;p>Sobre lo que cuesta dinero, una precisión que corrige una creencia extendida: en 4.35.0 el permiso de número de evaluadores basados en modelo está en ilimitado &lt;strong>en todos los planes&lt;/strong>, incluida la edición abierta (&lt;code>features/entitlements/constants/entitlements.ts:60&lt;/code>). Lo que sí está limitado por plan es el número de colas de anotación y los días de acceso a datos. Y hay un matiz de versión: los evaluadores a nivel de traza quedan descontinuados en la versión 4.&lt;/p>
&lt;h2 id="enmascarado-y-lo-que-no-cubre">Enmascarado, y lo que no cubre&lt;/h2>
&lt;p>Esta es la parte que hay que leer dos veces si el despliegue tiene que cumplir algo.&lt;/p>
&lt;p>La función clásica del SDK de Python tiene la firma &lt;code>masking_function(*, data, **kwargs)&lt;/code> y cubre &lt;strong>solo&lt;/strong> los datos que pasan por las llamadas del SDK: creación de observación, actualización, y fijación de entrada y salida de la traza. &lt;strong>No cubre los atributos OTel crudos&lt;/strong> que produce una instrumentación automática de terceros.&lt;/p>
&lt;p>El sustituto es una función que actúa sobre los atributos y los atributos de recurso en el lote de exportación. Esa sí alcanza a la instrumentación automática, con una limitación declarada en la documentación: no puede cambiar el nombre del span, los identificadores, la relación con el padre, los atributos de recurso, &lt;strong>los eventos&lt;/strong> ni los enlaces.&lt;/p>
&lt;p>Y ahí está el hueco que importa: las convenciones de IA generativa a partir de la 1.37 meten los mensajes y las respuestas en &lt;strong>eventos de span&lt;/strong>. Es decir, la ruta por la que llegan los prompts en una instrumentación moderna es precisamente la que el enmascarado nuevo no toca.&lt;/p>
&lt;p>Para un despliegue con datos personales, la conclusión es que el enmascarado de cliente no basta y hay que poner un procesador de redacción en el colector. Es el mismo razonamiento que llevó &lt;a href="https://blog.lo0.es/posts/litellm-identidad-trazas-costuras/">en el artículo anterior&lt;/a> a recomendar el colector en medio, y se suma al problema ya documentado de los argumentos de herramienta MCP que se escriben en claro saltándose el interruptor de redacción de mensajes.&lt;/p>
&lt;p>Del lado del servidor no hay red de seguridad: &lt;strong>Langfuse no tiene enmascarado de datos personales en servidor&lt;/strong>. La clave de cifrado que se configura protege secretos, es decir credenciales de proveedores de modelos, cabeceras de integraciones y configuraciones de SSO. &lt;strong>Las entradas y salidas de las trazas no se cifran a nivel de aplicación.&lt;/strong>&lt;/p>
&lt;h2 id="control-de-volumen">Control de volumen&lt;/h2>
&lt;p>Tres palancas, en orden de a qué altura actúan:&lt;/p>
&lt;p>&lt;strong>En el SDK&lt;/strong>: &lt;code>sample_rate&lt;/code>, o la variable &lt;code>LANGFUSE_SAMPLE_RATE&lt;/code>, con muestreo de cabecera a nivel de traza. Si la traza no se muestrea, tampoco viajan sus observaciones ni sus scores. &lt;code>LANGFUSE_TRACING_ENABLED&lt;/code> en falso lo apaga entero.&lt;/p>
&lt;p>&lt;strong>En el colector&lt;/strong>: muestreo probabilístico y muestreo de cola. Es la única capa donde se puede decidir en función del resultado, es decir quedarse con todos los errores y con una fracción del resto.&lt;/p>
&lt;p>&lt;strong>En el servidor&lt;/strong>: un muestreo determinista por proyecto, con hash SHA-256 del identificador de traza (&lt;code>server/ingestion/sampling.ts&lt;/code>). Determinista por traza, así que no parte trazas por la mitad.&lt;/p>
&lt;h2 id="qué-no-meter-en-una-traza">Qué no meter en una traza&lt;/h2>
&lt;p>Con el esquema delante, la lista es concreta.&lt;/p>
&lt;p>&lt;strong>No filtrar por etiquetas ni por mensaje de estado.&lt;/strong> En la tabla de listados los campos con índice son el identificador de span, el de traza, el de usuario, el de sesión, las fechas de creación y actualización, el nombre de modelo, el identificador de experimento y los nombres de metadato. El nombre, las etiquetas, la versión, la entrega y el mensaje de estado no. Filtrar por ellos escanea.&lt;/p>
&lt;p>&lt;strong>No usar los metadatos como almacén.&lt;/strong> Se aplanan a arreglos de cadenas, se truncan a doscientos caracteres en la tabla de listados pero se guardan enteros en la tabla completa, y encima se indexan con un índice de texto completo con segmentación por caracteres no alfabéticos. Un metadato voluminoso multiplica el índice invertido, no solo el dato.&lt;/p>
&lt;p>&lt;strong>No guardar prompts completos sin pensarlo.&lt;/strong> La entrada y la salida llevan compresión ZSTD nivel 3 y dos índices de texto completo sobre sus versiones en minúscula. Guardar el contexto entero de una llamada agéntica de 142.000 tokens en cada turno infla el índice invertido de forma desproporcionada. El truncado del cliente es la palanca barata.&lt;/p>
&lt;p>&lt;strong>No usar el entorno para separar inquilinos.&lt;/strong> Es de cardinalidad baja, tiene 40 caracteres de tope y no aísla nada: mismo proyecto, mismas claves, mismos límites, misma retención. Sirve para segmentar paneles. El aislamiento de verdad son proyectos separados, como ya se planteó &lt;a href="https://blog.lo0.es/posts/litellm-identidad-trazas-costuras/">en el artículo de las costuras&lt;/a>.&lt;/p>
&lt;h2 id="la-versión-3-y-la-fecha-del-calendario">La versión 3 y la fecha del calendario&lt;/h2>
&lt;p>La constante está en el código: &lt;code>V3_SUNSET_DATE = &amp;quot;2026-11-16&amp;quot;&lt;/code> (&lt;code>features/public-api/server/deprecations.ts:11&lt;/code>).&lt;/p>
&lt;p>Conviene precisar qué significa, porque la documentación pública y el código no dicen exactamente lo mismo. El comentario del código es literal: la ruta de ingesta clásica &lt;strong>no se apaga nunca&lt;/strong> y sigue aceptando eventos de score; los eventos de traza y observación fallan solo en el modo de escritura exclusivo de la versión 4, no en los modos mixto ni clásico. Y cuando fallan, fallan &lt;strong>por evento&lt;/strong>, no por lote.&lt;/p>
&lt;p>La cabecera &lt;code>x-langfuse-ingestion-version: 4&lt;/code> activa la escritura directa a la tabla de eventos, que es la ruta en tiempo real. Sin ella se toma el camino diferido. Los SDK recientes la mandan solos. Cualquier valor por encima de cuatro se rechaza.&lt;/p>
&lt;p>En cuanto a nombres, los atributos de entrada y salida a nivel de traza quedan obsoletos en favor de los de observación sobre el span raíz. Los endpoints de lectura clásicos llevan una marca de obsolescencia en el cuerpo de la respuesta, y sus sustitutos son la versión 2 de observaciones, la 3 de scores y la 2 de métricas. Y las ejecuciones de conjunto de datos pasan a llamarse experimentos.&lt;/p>
&lt;p>Hay una pieza curiosa que merece mención porque aparece en los paneles de Redis y despista: una cola llamada de uso de API clásica de la versión 4, que &lt;strong>no ingiere nada&lt;/strong>. Es una tarea periódica cada quince minutos que escanea el registro de consultas de ClickHouse para detectar qué proyectos siguen usando la API antigua, y materializa el resultado en Redis para el aviso de migración de la interfaz.&lt;/p>
&lt;h2 id="cómo-queda-esto-en-código">Cómo queda esto en código&lt;/h2>
&lt;p>Dos piezas, la del cliente y la del colector.&lt;/p>
&lt;p>Del lado de la aplicación, lo que cambia respecto a la instrumentación de siempre es el tipo de observación y el sitio del que sale la identidad:&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="kn">from&lt;/span> &lt;span class="nn">langfuse&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_client&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">propagate_attributes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">lf&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_client&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># la identidad se propaga por contexto, no observación a observación&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">propagate_attributes&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">id_conversacion&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># el mismo en todos los turnos&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">sujeto_del_token&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># del claim, no del cuerpo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;produccion&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">tags&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;soporte&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;n2&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="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># el tipo importa: la interfaz agrupa por él&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">lf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start_as_current_observation&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">as_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;agent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;resolver-incidencia&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">raiz&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">lf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start_as_current_observation&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">as_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;tool&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;buscar_en_inventario&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">herramienta&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">resultado&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mcp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">call&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;buscar&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># truncar aquí, no en el servidor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">herramienta&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">resultado&lt;/span>&lt;span class="p">)[:&lt;/span>&lt;span class="mi">4000&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># el score va por su propio canal, nunca por OTLP&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">score_current_trace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;resuelta&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;BOOLEAN&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El parámetro &lt;code>as_type&lt;/code> admite nueve literales: &lt;code>span&lt;/code>, &lt;code>generation&lt;/code>, &lt;code>embedding&lt;/code>, &lt;code>agent&lt;/code>, &lt;code>tool&lt;/code>, &lt;code>chain&lt;/code>, &lt;code>retriever&lt;/code>, &lt;code>evaluator&lt;/code> y &lt;code>guardrail&lt;/code>. El décimo tipo del modelo, el evento, tiene su propio constructor porque no abre un ámbito.&lt;/p>
&lt;p>Del lado del colector, el procesador de redacción que compensa lo que el enmascarado del SDK no alcanza:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">processors&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="nt">redaction&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="nt">allow_all_keys&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&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="nt">blocked_values&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;\b\d{8}[A-HJ-NP-TV-Z]\b&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># DNI&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;\b[\w.+-]+@[\w-]+\.[\w.]+\b&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># correo&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="nt">summary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">silent&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="nt">tail_sampling&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="nt">policies&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="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">errores&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="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">status_code&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="nt">status_code&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="nt">status_codes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">ERROR]}&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="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">resto&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="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">probabilistic&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="nt">probabilistic&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="nt">sampling_percentage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">exporters&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="nt">otlphttp/langfuse&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="nt">endpoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://langfuse-web.observabilidad.svc:3000/api/public/otel&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="nt">headers&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="nt">Authorization&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Basic ${LANGFUSE_BASIC}&amp;#34;&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="nt">x-langfuse-ingestion-version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;4&amp;#34;&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="nt">sending_queue&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="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&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="nt">storage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">file_storage&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="nt">retry_on_failure&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="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El endpoint lleva la ruta base sin el sufijo de la señal: el exportador añade el suyo. Y la cola en disco es la razón principal de meter el colector en medio, porque el procesador de lotes del SDK descarta cuando se llena.&lt;/p>
&lt;h2 id="checklist-de-instrumentación">Checklist de instrumentación&lt;/h2>
&lt;ol>
&lt;li>Revisar el tipo de cada observación contra la lista de diez. Las llamadas a herramienta como &lt;code>TOOL&lt;/code>, los agentes como &lt;code>AGENT&lt;/code>, los guardrails como &lt;code>GUARDRAIL&lt;/code>.&lt;/li>
&lt;li>Decidir de dónde sale el identificador de usuario y fijarlo en un solo sitio, arriba en la cadena de precedencia.&lt;/li>
&lt;li>Emitir &lt;code>gen_ai.conversation.id&lt;/code> si se quiere instrumentación neutra, o el atributo propio de Langfuse si no importa acoplarse.&lt;/li>
&lt;li>Fijar el entorno con &lt;code>deployment.environment.name&lt;/code>, con pocos valores distintos y nunca uno por inquilino.&lt;/li>
&lt;li>Sacar los scores por su canal propio, con las claves del proyecto, y no esperar que viajen con la traza.&lt;/li>
&lt;li>Truncar entrada y salida en el cliente antes de enviarlas, especialmente con contextos agénticos largos.&lt;/li>
&lt;li>Poner un procesador de redacción en el colector, porque el enmascarado del SDK deja fuera los eventos de span.&lt;/li>
&lt;li>No filtrar por etiquetas en paneles que se consulten a menudo.&lt;/li>
&lt;li>Mandar la cabecera de versión de ingesta, o usar SDK recientes que la manden solos.&lt;/li>
&lt;li>Revisar el tamaño de los metadatos: no hay tope de esquema, el tope es el del cuerpo de la petición.&lt;/li>
&lt;/ol>
&lt;h2 id="trampas-y-cosas-que-no-son-lo-que-parecen">Trampas y cosas que no son lo que parecen&lt;/h2>
&lt;p>&lt;strong>Lo que se ve en un listado está truncado a doscientos caracteres&lt;/strong> por la vista materializada, y la lectura completa tiene otro tope de mil. El dato está entero en la tabla de eventos completa.&lt;/p>
&lt;p>&lt;strong>Los metadatos no tienen límite de tamaño en el esquema.&lt;/strong> Un blob de megas pasa la validación.&lt;/p>
&lt;p>&lt;strong>Un span OTLP de más de 9,5 MB no se rechaza, solo se registra.&lt;/strong> No hay señal hacia el cliente.&lt;/p>
&lt;p>&lt;strong>El entorno tiene cardinalidad baja declarada.&lt;/strong> Un valor por inquilino degrada ClickHouse.&lt;/p>
&lt;p>&lt;strong>Los scores no viajan por OTLP.&lt;/strong> Es por ausencia en el código, no por configuración.&lt;/p>
&lt;p>&lt;strong>El enmascarado clásico no cubre la instrumentación automática&lt;/strong>, y el nuevo no cubre los eventos de span, que es donde las convenciones recientes ponen los mensajes.&lt;/p>
&lt;p>&lt;strong>Langfuse no cifra entradas ni salidas de traza.&lt;/strong> La clave de cifrado protege credenciales de integraciones.&lt;/p>
&lt;p>&lt;strong>En self-hosted no hay límites de tasa en la API.&lt;/strong> No sirve como protección.&lt;/p>
&lt;p>&lt;strong>La ruta de ingesta clásica no se apaga el 16 de noviembre&lt;/strong>, cambia de comportamiento según el modo de escritura, y falla por evento en lugar de por lote.&lt;/p>
&lt;p>&lt;strong>Los evaluadores basados en modelo no están limitados por plan&lt;/strong> en esta versión, contra lo que suele suponerse. Lo que sí lo está son las colas de anotación y los días de acceso a datos.&lt;/p>
&lt;h2 id="la-serie-los-ocho-artículos">La serie: los ocho artículos&lt;/h2>
&lt;p>Este es el primero. El orden está pensado para que cada uno se pueda leer suelto y para que juntos cubran el día 2 completo.&lt;/p>
&lt;ol>
&lt;li>Qué entra en una traza (este artículo): modelo de datos de la versión 4, límites, precedencias, scores, enmascarado e índices.&lt;/li>
&lt;li>Migrar de la versión 3 a la 4 sin ventana: los tres pasos del modo de escritura, las migraciones de fondo reanudables y dónde está el punto de no retorno del retroceso.&lt;/li>
&lt;li>Las colas del worker: el mapa de las treinta y nueve, qué pool dedicar a cada grupo, los interruptores por cola, el particionado y la concurrencia.&lt;/li>
&lt;li>Capacidad y coste real de ClickHouse: cómo medir los bytes por observación con las tablas del sistema, la diferencia entre la tabla completa y la de listados, y el coste de fusión de los índices de texto completo.&lt;/li>
&lt;li>Retención, borrado y protección de datos: por qué un borrado no libera disco, el limpiador de máscaras que viene desactivado, la cola de borrados pendientes y el ciclo de vida de S3 que hay que implementar a mano.&lt;/li>
&lt;li>Copias de seguridad y recuperación cruzada: orden de restauración entre Postgres, ClickHouse y el almacenamiento de objetos, qué rompe cada desajuste, y hasta dónde llega la reproducción de eventos.&lt;/li>
&lt;li>Runbook de saturación: qué alertar de las métricas de cola, las sondas de atasco, el drenado por el endpoint de preparación y la cola de mensajes fallidos.&lt;/li>
&lt;li>Sacar los datos fuera: la integración de almacenamiento de objetos a Parquet, las exportaciones por lotes y la API de métricas, para montar el lago de datos.&lt;/li>
&lt;/ol>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/langfuse-self-hosting-arquitectura-tuning/">Langfuse por dentro&lt;/a>: la arquitectura de seis servicios y las diez palancas de rendimiento de la versión 3.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/litellm-langfuse-par-operativo/">LiteLLM y Langfuse: el par operativo&lt;/a>: las cuatro colas donde se pierde la traza entre las dos piezas.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/litellm-identidad-trazas-costuras/">El gateway no vive solo&lt;/a>: por qué el usuario de la traza no es el de Keycloak, y cuándo meter un colector en medio.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">Tracing LLM con OpenTelemetry GenAI&lt;/a>: el pipeline y las convenciones semánticas.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/prompt-versioning-langfuse-mlflow/">Prompt versioning con Langfuse y MLflow&lt;/a>: la otra capa de la herramienta.&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>: dónde encajan los scores.&lt;/li>
&lt;/ul>
&lt;h2 id="fuentes">Fuentes&lt;/h2>
&lt;ul>
&lt;li>Código de Langfuse 4.35.0, commit &lt;code>39e3cd7d&lt;/code> del 11 de septiembre de 2026.&lt;/li>
&lt;li>&lt;a href="https://langfuse.com/changelog/2026-08-17-langfuse-v4">Anuncio de Langfuse v4&lt;/a>, 17 de agosto de 2026.&lt;/li>
&lt;li>&lt;a href="https://langfuse.com/docs/compatibility">Compatibilidad y migración&lt;/a>, consultado el 12 de septiembre de 2026.&lt;/li>
&lt;li>&lt;a href="https://langfuse.com/docs/observability/features/sessions">Sesiones&lt;/a>, &lt;a href="https://langfuse.com/docs/observability/features/sampling">muestreo&lt;/a> y &lt;a href="https://langfuse.com/docs/observability/features/masking">enmascarado&lt;/a>, consultados el 12 de septiembre de 2026.&lt;/li>
&lt;li>&lt;a href="https://langfuse.com/integrations/native/opentelemetry">Integración nativa con OpenTelemetry&lt;/a>, consultado el 12 de septiembre de 2026.&lt;/li>
&lt;/ul></description></item></channel></rss>