intermediate45 minLección 2 de 5

Trazado de Llamadas LLM y Pasos de Agentes

Aprende a crear traces y spans detallados para llamadas LLM, pasos de agentes e integraciones con frameworks usando LangFuse.

Trazado de Llamadas LLM y Pasos de Agentes

El trazado es el núcleo de la observabilidad de LangFuse. Cada llamada LLM, paso de recuperación, invocación de herramienta o decisión de agente puede capturarse como un span estructurado dentro de un trace. Esta lección muestra cómo construir árboles de trace ricos y anidados y cómo instrumentar pipelines de LangChain y LlamaIndex.


Creando Spans y Traces

Cada trace comienza con langfuse.trace(). Dentro de él, creas spans para cada paso lógico:

python
from langfuse import Langfuse langfuse = Langfuse() trace = langfuse.trace( name="qa-agent", input={"question": "¿Cuál es la capital de Francia?"}, user_id="user_42", session_id="sess_001" )

Los spans pueden anidarse arbitrariamente:

python
# Span raíz (ej.: "recuperar documentos") recuperacion = trace.span(name="recuperar") # Span hijo (ej.: "incrustar consulta") incrustacion = recuperacion.span(name="incrustar-consulta") incrustacion.end( input={"query": "capital de Francia"}, output={"embedding_dim": 1536} ) # Otro span hijo (ej.: "búsqueda vectorial") busqueda = recuperacion.span(name="busqueda-vectorial") busqueda.end( input={"top_k": 5}, output={"results": ["doc1", "doc3", "doc7"]} ) recuperacion.end()
ℹ️Note

Un trace representa una solicitud completa de extremo a extremo (una consulta de usuario). Un span representa una sola operación dentro de esa solicitud. Múltiples spans forman una jerarquía de árbol. El span raíz de un trace es su primer span; todos los demás son hijos de algún span padre.

⚠️Warning

Siempre llama a .end() en un span cuando la operación termine. Los spans huérfanos (sin .end()) permanecerán "abiertos" en el panel de LangFuse y sesgarán las métricas de latencia. Usa el patrón context manager (with trace.span() as s:) para garantizar el cierre.


Jerarquía de Trace (Diagrama ASCII)

100%

Cada span puede contener sus propios input, output, metadata, usage y level (DEBUG, WARNING, ERROR).

Jerarquía de Span Anidado (Secuencia)

100%

Ciclo de Vida del Trace

100%

Agregando Metadatos y Puntuaciones

python
# Agregando metadatos a un span span = trace.span( name="llm-call", metadata={ "model": "gpt-4", "temperature": 0.7, "max_tokens": 500 } ) # Agregando una puntuación después de que el span termine trace.score( name="utilidad", value=0.85, comment="Buena respuesta, pero podría ser más corta" ) # Tipos de puntuación: NUMERIC, BOOLEAN, CATEGORICAL trace.score(name="toxicidad", value=False, data_type="BOOLEAN") trace.score(name="dificultad", value="medio", data_type="CATEGORICAL")
⚠️Warning

Las puntuaciones se adjuntan a un trace o span después del hecho. No bloquean la ejecución. Asegúrate de tener una referencia al objeto trace/span (o su ID) para puntuarlo posteriormente.

💡Tip

Usa metadatos para etiquetar spans con contexto de negocio: environment, region, model_version, prompt_template_name, user_tier. Estos campos se convierten en dimensiones filtrables en los paneles. El etiquetado consistente en todos los spans permite potentes filtros cruzados.

Tipos de Datos de Puntuación

Tipo de DatoEjemplo PythonVisualización en PanelCaso de Uso
NUMERICvalue=0.85Histograma, avg/min/maxCorrección, utilidad, relevancia
BOOLEANvalue=TrueTasa de aprobación/rechazo, gráfico circularToxicidad, verificaciones de seguridad, guardrails
CATEGORICALvalue="medio"Gráfico de barras, distribuciónDificultad, prioridad, clase de intención

Trazando Ejecuciones de LangChain

LangFuse proporciona un callback handler para LangChain que auto-instrumenta chains:

python
from langfuse.callback import CallbackHandler from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate # Crear el handler (uno por proyecto) langfuse_handler = CallbackHandler() prompt = ChatPromptTemplate.from_template("Cuenta un chiste corto sobre {tema}") model = ChatOpenAI(model="gpt-4") chain = prompt | model # El handler se conecta automáticamente a cada paso resultado = chain.invoke({"tema": "programación"}, config={"callbacks": [langfuse_handler]})

Cada paso de LangChain (template de prompt, llamada LLM, parser, recuperador) se convierte en un span separado dentro de un solo trace.

Avanzado: Agente LangChain con Herramientas

python
from langfuse.callback import CallbackHandler from langchain.agents import create_openai_functions_agent, AgentExecutor from langchain.tools import tool from langchain_openai import ChatOpenAI langfuse_handler = CallbackHandler() @tool def get_weather(city: str) -> str: """Get the current weather for a city.""" return f"Sunny, 22°C in {city}" @tool def calculate(expression: str) -> str: """Evaluate a mathematical expression.""" return str(eval(expression)) llm = ChatOpenAI(model="gpt-4") agent = create_openai_functions_agent(llm, [get_weather, calculate]) executor = AgentExecutor(agent=agent, tools=[get_weather, calculate]) result = executor.invoke( {"input": "What is the weather in Paris plus 5?"}, config={"callbacks": [langfuse_handler]} )

Cada invocación de herramienta aparece como un span hijo separado, y el bucle de razonamiento del agente crea un árbol de trace que muestra la ruta completa de decisión.


Trazando Pipelines de LlamaIndex

python
from langfuse.llama_index import LlamaIndexCallbackHandler from llama_index.core import VectorStoreIndex, SimpleDirectoryReader # Inicializar handler handler = LlamaIndexCallbackHandler() documentos = SimpleDirectoryReader("./data").load_data() index = VectorStoreIndex.from_documents(documentos) query_engine = index.as_query_engine() respuesta = query_engine.query("¿Qué es LangFuse?") # Descargar traces handler.flush()

Instrumentación Personalizada con Decoradores

Para máximo control, usa el decorador @observe():

python
from langfuse.decorators import observe @observe() def obtener_clima(ciudad: str) -> str: """Esta función se traza automáticamente.""" respuesta = call_weather_api(ciudad) return respuesta @observe(as_type="generation") def llamar_llm(prompt: str, model: str = "gpt-4") -> str: """Marca este span como una 'generation' (llamada LLM).""" ...
⚠️Warning

El decorador @observe funciona con cualquier función de Python, no solo llamadas LLM. Usa el parámetro as_type para distinguir generaciones (llamadas LLM) de spans regulares.

Avanzado: Instrumentación Personalizada con Agrupación de Traces

Agrupa traces relacionados bajo una sola sesión para conversaciones completas de múltiples turnos:

python
# trace_grouping.py from langfuse import Langfuse from langfuse.decorators import observe langfuse = Langfuse() @observe() def process_message(session_id: str, message: str, turn_number: int) -> str: """Process a single message in a multi-turn conversation.""" context = retrieve_context(message) response = generate_response(message, context) trace = langfuse.current_trace() if trace: trace.score(name="coherence", value=0.9, data_type="NUMERIC") trace.update(session_id=session_id) return response @observe(as_type="generation") def generate_response(message: str, context: str) -> str: """Call the LLM with context.""" return "París es la capital de Francia." session_1 = "sess_conversation_001" for i, msg in enumerate(["¡Hola!", "¿Cuál es la capital de Francia?"]): process_message(session_1, msg, i + 1) langfuse.flush()
💡Tip

Al trazar bucles de agentes, establece session_id en cada trace para que el panel de LangFuse agrupe todos los turnos de una conversación. Luego puedes filtrar por sesión para reproducir toda la trayectoria del agente.


Comparación: Enfoques de Instrumentación

EnfoqueEsfuerzoGranularidadAlcance automáticoMejor para
Spans manualesAltoControl totalManualPipelines personalizados, investigación
CallbackHandler LangChainBajoPor paso de chainAutomáticoAplicaciones LangChain
CallbackHandler LlamaIndexBajoPor paso de índiceAutomáticoAplicaciones LlamaIndex
Decorador @observeMedioPor funciónEnvuelve la funciónCualquier código Python

Resumen de Tipos de Span

Tipo de SpanConvención de nameMetadatos RecomendadosSeguimiento de Uso
Generación LLMllm-call, openai-completion, anthropic-generatemodel, temperature, max_tokens, providerprompt_tokens, completion_tokens, total
Recuperaciónvector-search, embed-query, bm25-searchtop_k, index_name, embedding_modelGeneralmente ninguno
Ejecución de Herramientaget_weather, calculate, search_webtool_name, tool_inputGeneralmente ninguno
Lógica / Enrutamientoclassify-intent, guardrail-check, format-responsedecision, confidenceGeneralmente ninguno
Manejador de Errorerror-handling, fallbackerror_type, retry_countGeneralmente ninguno

Interactive Questions

Practice Question

Un agente hace 3 llamadas de herramienta en secuencia. Cada llamada de herramienta debe aparecer como un span separado compartiendo el mismo trace padre. ¿Cómo estructuras esto?

Practice Question

¿Qué clase de LangFuse instrumenta automáticamente las chains de LangChain sin creación manual de spans?

Practice Question

¿Qué hace el decorador @observe() cuando se aplica a una función Python?

Practice Question

Después de crear un trace, ¿cómo se le adjunta una puntuación?

Practice Question

Notas que un span permanece 'abierto' en el panel de LangFuse durante horas. ¿Cuál es la causa más probable?


Success

Conclusiones Clave

  • Un trace envuelve una solicitud completa; los spans capturan operaciones individuales en una estructura de árbol.
  • Siempre llama a .end() en los spans, o usa context managers para cierre automático.
  • Los metadatos y etiquetas hacen que los spans sean filtrables en el panel — sé consistente con los nombres de las claves.
  • Los callbacks de LangChain y LlamaIndex proporcionan instrumentación con esfuerzo cero.
  • El decorador @observe() brinda control detallado sobre código Python personalizado.
  • Usa session_id para agrupar conversaciones de múltiples turnos y trayectorias de agentes.
Progreso40%