Skip to main content
openinference-instrumentation-annotation adds annotation-driven tracing to any Java application. Annotate your methods with @Chain, @LLM, @Tool, @Agent, or @Span, and a ByteBuddy agent intercepts each call at class load to produce OpenInference spans backed by OpenTelemetry. Use it when you’ve built an agent or pipeline by hand and want OpenInference semantics without a framework-specific instrumentor.

Prerequisites

Launch Phoenix

No uv? pip install arize-phoenix && phoenix serve does the same thing. See Terminal setup for customization.
Phoenix serves its UI and OTLP HTTP on port 6006, and OTLP gRPC on port 4317. For a local instance that’s http://localhost:6006. Leave it running while you work.

Install

Add the dependencies to build.gradle:
The -parameters compiler flag is required. It keeps parameter names in the class file so the annotation agent can record them as span attributes. Without it, parameters appear as arg0, arg1, and so on.

Setup

Set the Phoenix endpoint, the project name, and your OpenAI key:
The annotation instrumentor needs three things at startup: the ByteBuddy agent installed before any annotated class is loaded, an OITracer wrapping an OpenTelemetry tracer, and that OITracer registered with the OpenInferenceAgent so the intercepted methods know where to emit spans. The exporter sends spans over OTLP HTTP to <PHOENIX_COLLECTOR_ENDPOINT>/v1/traces.

Available annotations

Parameters are captured as input.value and the return value as output.value. Use @ExcludeFromSpan on a parameter to drop it from the input attribute, and @SpanMapping to map a parameter or field to a specific OpenInference semantic convention attribute.

Run annotated code

Annotate your service methods with the span kind that fits each step. The agent intercepts the methods at class load, so there’s nothing else to wire up.
Build and run:
You should see output like:

Observe

Open Phoenix and select the annotation-tracing-example project. Each call to service.answer(...) produces one trace:
Select the root qa-agent span to inspect the captured inputs and outputs of each step.

Troubleshooting

  • No spans, but OpenInferenceAgentInstaller.install() ran. The ByteBuddy agent only rewrites classes loaded after it installs. Make install() the first statement in main, and don’t reference any annotated class before that line.
  • Parameters appear as arg0, arg1, and so on. The compiler stripped parameter names. Add compileJava { options.compilerArgs += '-parameters' } to build.gradle, as in the Install step.
  • Spans dropped at JVM exit. BatchSpanProcessor exports asynchronously. Call tracerProvider.forceFlush().join(...) and tracerProvider.shutdown().join(...) before main returns.
  • Lost spans across thread boundaries. OpenTelemetry context doesn’t follow work onto a CompletableFuture, ExecutorService, or reactive pipeline automatically. Wrap the work with io.opentelemetry.context.Context.current().wrap(...).
  • Hiding sensitive fields. Pass a TraceConfig when you construct the OITracer: new OITracer(provider.get("..."), TraceConfig.builder().hideInputs(true).hideOutputs(true).build()).

Resources

Annotation instrumentor README

OpenInference package