FaqWhy are trace input and output empty?

Why are trace input and output empty?

Trace-level input and output are deprecated as part of Langfuse v4. In the observation-centric data model, a trace ID groups related observations; the trace is no longer a separate record with its own input and output.

For new instrumentation, write:

  • The overall request and response to the root or workflow observation.
  • Step-specific input and output to the observation representing that operation.

In most applications, the root observation already carries the same input and output that was previously written to the trace.

An empty trace input or output does not necessarily indicate missing data. Check the observations in the trace. If the relevant observation contains the expected input and output, the data was ingested correctly.

Use observation input and output

Observation input and output support the same core workflows without relying on the deprecated trace fields:

WorkflowRecommended approach
Browse and search dataFilter or search observations directly. For the overall request and response, use the root observation.
Run evaluationsCreate an observation-level evaluator that targets the observation containing the required input, output, and context.
Record a summaryStore the summary on a root or dedicated workflow observation instead of writing separate input and output values to the trace level.

If you still use trace-level LLM-as-a-Judge evaluators, follow the evaluator upgrade guide.

Set input and output on the root observation

Use a root observation to represent the overall application or agent invocation:

from langfuse import get_client

langfuse = get_client()

with langfuse.start_as_current_observation(
    as_type="span",
    name="my-pipeline",
) as root_span:
    user_input = "What's the weather like?"
    result = process_request(user_input)

    root_span.update(
        input={"query": user_input},
        output={"response": result},
    )
import { startActiveObservation } from "@langfuse/tracing";

await startActiveObservation("my-pipeline", async (rootSpan) => {
  const userInput = "What's the weather like?";
  const result = await processRequest(userInput);

  rootSpan.update({
    input: { query: userInput },
    output: { response: result },
  });
});

If observation input or output is missing

Flush short-lived applications

Langfuse sends data in the background. Scripts, serverless functions, and notebooks can exit before all observations are exported.

Call flush() or forceFlush() before the application exits:

from langfuse import get_client

langfuse = get_client()

# Your code here...

langfuse.flush()
import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";

const langfuseSpanProcessor = new LangfuseSpanProcessor();
const sdk = new NodeSDK({
  spanProcessors: [langfuseSpanProcessor],
});

sdk.start();

async function main() {
  // Your code here...
}

main().finally(() => langfuseSpanProcessor.forceFlush());

Check decorator input/output capture

The Python @observe() decorator captures function arguments and return values by default. If LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED=false, enable capture globally or for the relevant function:

from langfuse import observe

@observe(capture_input=True, capture_output=True)
def my_function(data):
    return process(data)

Ensure the relevant observation is exported

If shouldExportSpan filters out your root observation, its input and output are not sent to Langfuse. Keep the root observation, or store the required values on another exported observation and target that observation in downstream workflows.

See the span filtering documentation for details.

Map OpenTelemetry input and output

OpenTelemetry integrations use different attribute names. Langfuse maps these span attributes to observation input and output, in priority order:

Observation fieldOpenTelemetry attributes
inputlangfuse.observation.input, gen_ai.prompt, input.value, mlflow.spanInputs
outputlangfuse.observation.output, gen_ai.completion, output.value, mlflow.spanOutputs

See the OpenTelemetry property mapping for the complete reference.

from opentelemetry import trace
import json

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("my-operation") as span:
    span.set_attribute(
        "langfuse.observation.input",
        json.dumps(input_data),
    )
    span.set_attribute(
        "langfuse.observation.output",
        json.dumps(output_data),
    )
import { trace } from "@opentelemetry/api";

const tracer = trace.getTracer("my-service");

tracer.startActiveSpan("my-operation", (span) => {
  span.setAttribute(
    "langfuse.observation.input",
    JSON.stringify(inputData),
  );
  span.setAttribute(
    "langfuse.observation.output",
    JSON.stringify(outputData),
  );

  span.end();
});
Which attributes does my OpenTelemetry provider use?
  1. Enable debug logging in your OpenTelemetry exporter to inspect the raw span attributes.
  2. Open the observation in Langfuse and check its metadata.
  3. Review the provider's semantic conventions.

Common conventions include:

  • OpenLLMetry: gen_ai.prompt and gen_ai.completion
  • OpenInference: input.value and output.value
  • MLflow: mlflow.spanInputs and mlflow.spanOutputs

If your provider uses different names, map them to the Langfuse observation attributes shown above.

Temporary compatibility for legacy evaluators

The trace I/O methods are deprecated. Use them only while an existing trace-level evaluator still reads trace input or output. Do not add them to new instrumentation.

After upgrading your SDK or ingestion path, trace input and output are no longer written automatically. Until you have upgraded the evaluator, you can continue supplying the legacy fields explicitly:

from langfuse import get_client

langfuse = get_client()
user_input = "What's the weather like?"

with langfuse.start_as_current_observation(
    as_type="span",
    name="my-pipeline",
) as root_span:
    result = process_request(user_input)

    root_span.set_trace_io(
        input={"query": user_input},
        output={"response": result},
    )
import {
  setActiveTraceIO,
  startActiveObservation,
} from "@langfuse/tracing";

await startActiveObservation("my-pipeline", async () => {
  const userInput = "What's the weather like?";
  const result = await processRequest(userInput);

  setActiveTraceIO({
    input: { query: userInput },
    output: { response: result },
  });
});

Once the observation-level evaluator is validated, remove these calls.

Still having issues?

  1. Update to the latest Langfuse SDK version.
  2. Open the trace and verify that the expected observation was exported.
  3. Check that the observation itself has input and output.
  4. For short-lived applications, confirm that the client flushes before exit.

If the observation data is still missing, open a GitHub discussion with your SDK version, instrumentation snippet, and a screenshot of the observation tree.


Was this page helpful?

Last edited