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:
| Workflow | Recommended approach |
|---|---|
| Browse and search data | Filter or search observations directly. For the overall request and response, use the root observation. |
| Run evaluations | Create an observation-level evaluator that targets the observation containing the required input, output, and context. |
| Record a summary | Store 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 field | OpenTelemetry attributes |
|---|---|
input | langfuse.observation.input, gen_ai.prompt, input.value, mlflow.spanInputs |
output | langfuse.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?
- Enable debug logging in your OpenTelemetry exporter to inspect the raw span attributes.
- Open the observation in Langfuse and check its metadata.
- Review the provider's semantic conventions.
Common conventions include:
- OpenLLMetry:
gen_ai.promptandgen_ai.completion - OpenInference:
input.valueandoutput.value - MLflow:
mlflow.spanInputsandmlflow.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?
- Update to the latest Langfuse SDK version.
- Open the trace and verify that the expected observation was exported.
- Check that the observation itself has input and output.
- 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.
Last edited