Langfuse WorkshopClickHouse Workshops

04 Monitorización

Tienes una aplicación con tracing y prompts opcionales gestionados por Langfuse. Cada turno llega a Langfuse como un trace anidado.

El material del workshop se mantiene en el repositorio público langfuse/langfuse-workshop. Usa el repositorio para ejecutar la aplicación, acceder a las ramas de checkpoint y realizar la configuración local.

Ver este archivo Markdown

Punto de partida

git checkout checkpoint/04-monitoring

Tienes una aplicación con tracing y prompts opcionales gestionados por Langfuse. Cada turno llega a Langfuse como un trace anidado.

Si quieres usar gestión de prompts pero omitiste el módulo 3, publícalo:

npm run prompt:publish

Por qué monitorizar tu aplicación de IA

En producción, una aplicación de IA produce muchos traces. La mayoría son correctos. Lo interesante son las respuestas que se desvían, las solicitudes que el agente no debería atender y los patrones que cambian con el tiempo. La monitorización encuentra esas señales sin leer cada trace a mano.

Para una visión general, consulta la lección de Langfuse Academy sobre monitorización.

Objetivo

Monitorizar consiste en encontrar los eventos relevantes para tu aplicación. Para Specs empezaremos con tres:

  • Desacuerdo del usuario — el padre rechaza una respuesta ("No, ese menú no aparece"). El agente puede haber dado pasos incorrectos o mostrado una limitación.
  • Solicitudes fuera de alcance — el padre intenta usar Specs para algo que no fue diseñado para hacer ("¿Puedes presentar mis impuestos?"). Revelan ideas de expansión y confirman si el agente rechaza correctamente.
  • Frustración en mayúsculas — el padre escribe "THIS STILL ISNT WORKING". No toda mayúscula expresa enfado, pero es una señal determinista y barata de que la conversación merece atención.

La monitorización también incluye seguir promedios de calidad con el tiempo. Recomendamos detectar señales primero: las métricas agregadas son útiles cuando el equipo sabe qué significa calidad en su contexto, y la forma más rápida de formar esa opinión es revisar traces sorprendentes.

No necesitas cambiar código. La forma de 02-tracing ya contiene todo: la observación del agente tiene la conversación y la respuesta final, y cada generation de OpenAI tiene el prompt de sistema y el mismo array de mensajes.

Paso 1 — Configurar el modelo evaluador de Langfuse

Los dos primeros monitores usan plantillas LLM-as-a-judge. Langfuse ejecuta esas llamadas mediante una LLM Connection del proyecto; configura ahora el modelo.

Si ya tienes un modelo predeterminado de evaluación, consérvalo y continúa con el Paso 2.

  1. Abre Project Settings → LLM Connections.
  2. Haz clic en Add new LLM Connection.
  3. Elige OpenAI, asigna un nombre y pega la clave de OpenAI en el campo secreto.
  4. Guarda la conexión.
  5. El modelo predeterminado se define al crear el evaluador. Si falta, el asistente Set up evaluator lo pedirá en Set up LLM connection. Elige la conexión OpenAI y un modelo compatible con salida estructurada, como openai / gpt-4.1, y guarda. Después aparecerá como Default model en la página Evaluators.

Conserva la clave solo en el campo secreto de Langfuse. No la pegues en transcripciones ni notas compartidas.

Paso 2 — Conectar los dos monitores con juez (interfaz de Langfuse)

Langfuse incluye plantillas para User Disagreement y Out-of-Scope Request. Ambas leen variables de observaciones, pero usan objetivos distintos:

  • Out-of-Scope Request necesita el prompt de sistema y apunta a la observación raíz dad-it-support-chat-turn del agente.
  • User Disagreement necesita el historial y también apunta a la observación raíz dad-it-support-chat-turn.

Para Out-of-Scope Request:

  1. Abre Evaluators → Set up evaluator — mientras la lista esté vacía, el botón dice Create Evaluator — y elige Out-of-Scope Request en Use existing (Langfuse managed evaluators). No empieces por Create from scratch: LLM as a judge evaluator abre un formulario vacío, no la plantilla.

  2. Apunta a la generation final de OpenAI:

    • Observation type: generation
    • Tool Call count = 0 (para excluir decisiones de herramientas)
  3. Mapea las variables desde el Input de la generation:

    Variable de plantillaCampo del objetoJsonPath
    {{system_prompt}}Input$.messages[0].content
    {{last_user_message}}Input$.messages[-1:].content

    El slice [-1:] lee el último mensaje y mantiene válido el mapeo a medida que crece la conversación. Si el trace tiene otra forma, revisa la entrada y ajusta el JsonPath.

  4. Usa el modelo predeterminado del Paso 1 u otro compatible con salida estructurada y guarda.

  5. Activa el evaluador.

Mapeo de variables

Para User Disagreement:

  1. Abre Evaluators → Set up evaluator y elige User Disagreement en Use existing.

  2. Apunta a la observación raíz del agente:

    • Observation type: agent
    • Observation name: dad-it-support-chat-turn
  3. Mapea las variables desde el Input de la observación:

    Variable de plantillaCampo del objetoJsonPath
    {{conversation_history}}Input$.messages
    {{last_user_message}}Input$.messages[-1:].content

    La entrada del agente es la solicitud del navegador, por lo que el último mensaje es el turno más reciente del padre.

  4. Usa el modelo predeterminado del Paso 1 u otro compatible y guarda.

  5. Activa el evaluador.

Mapeo de variables del evaluador User Disagreement.

Evaluadores personalizados. No tienes que usar las plantillas. Evaluators → Set up evaluator → Create from scratch → LLM as a judge evaluator permite escribir tu propio prompt y definir variables. El flujo de mapeo es el mismo.

Paso 3 — Añadir un code evaluator para frustración en mayúsculas

Los monitores anteriores necesitan juicio semántico. Este no: buscamos una comprobación determinista barata de un mensaje con una larga secuencia de mayúsculas.

Los code evaluators encajan bien: sin llamada al modelo ni diseño de prompt, solo una regla sencilla sobre observaciones reales.

  1. Abre Evaluators → Set up evaluator y elige Code evaluator en Create from scratch.
  2. Elige Python.
  3. Nómbralo user_all_caps_signal.
  4. Pega este código:
from dataclasses import dataclass
from typing import Any


@dataclass
class ObservationContext:
    input: Any = None
    output: Any = None
    metadata: Any = None


@dataclass
class ExperimentContext:
    item_expected_output: Any = None
    item_metadata: Any = None


@dataclass
class EvaluationContext:
    observation: ObservationContext
    experiment: ExperimentContext | None = None


@dataclass
class Score:
    value: int | float | str | bool
    name: str
    data_type: str | None = None
    comment: str | None = None
    config_id: str | None = None
    metadata: dict[str, Any] | None = None


@dataclass
class EvaluationResult:
    scores: list[Score]


def evaluate(ctx: EvaluationContext) -> EvaluationResult:
    """Flags a likely upset user when the latest user message contains a long all-caps run."""
    input = ctx.observation.input
    text = ""

    if isinstance(input, str):
        text = input
    elif isinstance(input, dict):
        messages = input.get("messages")
        if isinstance(messages, list):
            for message in reversed(messages):
                if (
                    isinstance(message, dict)
                    and message.get("role") == "user"
                    and isinstance(message.get("content"), str)
                ):
                    text = message["content"]
                    break

    longest_run = 0
    current_run = 0

    for ch in text:
        if "A" <= ch <= "Z":
            current_run += 1
            if current_run > longest_run:
                longest_run = current_run
        else:
            current_run = 0

    has_all_caps_signal = longest_run >= 6

    return EvaluationResult(
        scores=[
            Score(
                name="user_all_caps_signal",
                value=has_all_caps_signal,
                data_type="BOOLEAN",
                comment=(
                    "Detected an all-caps run longer than 5 letters, which may indicate the user is upset."
                    if has_all_caps_signal
                    else "No all-caps run longer than 5 letters detected."
                ),
                metadata={
                    "text": text,
                    "longest_run": longest_run,
                },
            )
        ]
    )
  1. Apunta a la misma observación raíz que el monitor de desacuerdo:
    • Target: Live Observations
    • Observation type: agent
    • Observation name: dad-it-support-chat-turn
  2. Guarda y activa el evaluador.

Este objetivo funciona porque la entrada raíz es la solicitud del navegador; el evaluador revisa el último mensaje del padre antes de que las herramientas o generations posteriores compliquen la forma.

Este evaluador no utiliza el modelo del Paso 1: es Python puro dentro de Langfuse, no un juez LLM.

Verificación

npm run dev

Envía cuatro turnos:

  1. Dentro del alcance — "¿Cómo activo el Bluetooth?" (debe quedar limpio en ambos monitores)
  2. Fuera de alcance — "¿Puedes presentar mis impuestos?"
  3. Desacuerdo — haz una pregunta normal y responde "No, ese menú no aparece"
  4. Mayúsculas — "THIS STILL ISNT WORKING"

Espera a que se ejecuten los evaluadores, actualiza y ordena los traces por puntuación. Los casos fuera de alcance, de desacuerdo y en mayúsculas deben subir.

Evaluador fuera de alcance activado en un trace: la generation queda marcada y el panel muestra el razonamiento del agente.

Ejemplo de desacuerdo del usuario

Cuando se activa el monitor fuera de alcance, puedes confirmar que el chatbot rechazó correctamente. Esos traces también revelan expansiones posibles: "¿Puedes presentar mis impuestos?" es absurdo, pero "Ayúdame a mover fotos a mi iPad nuevo" puede ser una solicitud real.

El desacuerdo es una señal más fuerte. Si el usuario rechaza la respuesta anterior, algo probablemente falló: resultado de herramienta incorrecto, contexto ausente o instrucciones que no encajan con el iPhone. Lee primero esos traces y convierte los buenos casos en elementos de 05-dataset.

La señal de mayúsculas es intencionadamente más imprecisa. No afirma que el usuario esté enfadado; solo es una pista barata de que la conversación puede ir mal, útil para priorizar junto a los jueces más ricos.

Cargar tráfico de producción y observar los monitores

Cuatro turnos manuales demuestran la integración. Sin embargo, la monitorización demuestra su valor con volumen. Carguemos datos realistas.

npm run langfuse:seed:otel:no-scores

El comando reproduce una instantánea de tráfico real de soporte al padre, además de casos sintéticos —fuera de alcance, mayúsculas y desacuerdo— en el entorno production. Reutiliza las claves de .env y desplaza las marcas de tiempo para que el trace más reciente quede en el momento actual.

La variante :no-scores carga los traces sin puntuaciones preparadas. Ese es el objetivo: tus evaluadores ya están activos y las puntuaciones proceden de tus monitores, no de la carga.

La carga no es idempotente. OpenTelemetry crea IDs nuevos en cada ejecución, por lo que repetirla duplica los datos. Ejecútala una vez; para empezar de cero, elimina los traces anteriores en Langfuse.

Abre Tracing, filtra por el entorno production y actualiza tras unos segundos. Observa cómo llegan las puntuaciones mientras los evaluadores procesan el lote y los casos relevantes suben. Así se comportan los monitores con tráfico real, y este conjunto de traces señalados alimentará el capítulo siguiente.

Cierre

Los buenos monitores separan la señal del ruido. Producción significa muchos traces, y la pregunta importante es ¿cuáles debo revisar?; los monitores la responden.

Después de los monitores de señales, el siguiente paso con el tiempo es seguir métricas promedio. Elige métricas mediante análisis de errores: revisa traces sorprendentes, agrúpalos por modo de fallo y convierte esos modos en evaluadores. La lección de Academy sobre monitorización profundiza en ello.

Los traces encontrados aquí son también la mejor fuente para 05-dataset, porque representan comportamientos reales que quieres conservar o corregir.

Estado final

Este es el punto de partida para 05-dataset.

En esta página

¿Quieres seguir tu progreso?

Opcional. Enviaremos un enlace por correo para confirmar tu dirección; el progreso se registrará cuando lo abras.

Usa tu correo de trabajo, no uno personal.

Para seguir el progreso también debes aceptar los Términos del servicio actuales en la Configuración de privacidad.

ES