> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/nemo/relay/llms.txt.
> For full documentation content, see https://docs.nvidia.com/nemo/relay/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo/relay/_mcp/server.

# OpenInference

Use the `openinference` section when you want NeMo Relay lifecycle events
exported as OTLP trace spans with OpenInference-oriented semantics.

OpenInference export maps model-centric payloads directly into trace
attributes. Scope, tool, and LLM start inputs become `input.value`; end outputs
become `output.value`; LLM usage metadata maps to token-count attributes when
the provider response includes usage information.

## `plugins.toml` Example

```toml
version = 1

[[components]]
kind = "observability"
enabled = true

[components.config]
version = 1

[components.config.openinference]
enabled = true
transport = "http_binary"
endpoint = "http://localhost:6006/v1/traces"
service_name = "agent-service"
service_namespace = "nemo"
service_version = "1.0.0"
instrumentation_scope = "nemo-relay-openinference"
timeout_millis = 3000

[components.config.openinference.headers]
authorization = "Bearer <token>"

[components.config.openinference.resource_attributes]
"deployment.environment" = "dev"
```

This configuration registers a plugin-owned OpenInference subscriber and sends
OpenInference-style OTLP spans to Phoenix or another compatible backend.

## Fields

OpenInference uses the same OTLP section shape as
[OpenTelemetry](/observability-plugin/opentelemetry):

| Field                   | Default          | Notes                                                    |
| ----------------------- | ---------------- | -------------------------------------------------------- |
| `enabled`               | `false`          | Must be `true` to construct and register the subscriber. |
| `transport`             | `http_binary`    | `http_binary` or `grpc`.                                 |
| `endpoint`              | Exporter default | OTLP endpoint.                                           |
| `headers`               | `{}`             | String-to-string exporter headers.                       |
| `resource_attributes`   | `{}`             | String-to-string OTLP resource attributes.               |
| `service_name`          | `nemo-relay`     | `service.name` resource attribute.                       |
| `service_namespace`     | Omitted          | Optional `service.namespace`.                            |
| `service_version`       | Omitted          | Optional `service.version`.                              |
| `instrumentation_scope` | Omitted          | Optional instrumentation scope name.                     |
| `timeout_millis`        | `3000`           | Export timeout.                                          |

## Expected Output

The backend should show OpenInference-oriented spans for scopes, tools, and LLM
calls grouped by root scope. LLM usage metadata appears as token counters when
provider responses include usage information.

Each lifecycle span includes `nemo_relay.uuid` and `nemo_relay.parent_uuid`
attributes. These values match ATIF `step.extra.ancestry.function_id` and
`step.extra.ancestry.parent_id` for the same events. For plugin-managed ATIF,
the root agent span's `nemo_relay.uuid` also matches the ATIF `session_id`.
Backend-native `trace_id` and `span_id` values are not written into ATIF.

Redact sensitive event payloads with sanitize guardrails before production
export.

## Plugin Configuration

Use plugin configuration when the application should let NeMo Relay own the
OpenInference subscriber lifecycle.

```python
from nemo_relay import plugin
from nemo_relay.observability import ComponentSpec, ObservabilityConfig, OtlpConfig

config = plugin.PluginConfig(
    components=[
        ComponentSpec(
            ObservabilityConfig(
                openinference=OtlpConfig(
                    enabled=True,
                    transport="http_binary",
                    endpoint="http://localhost:6006/v1/traces",
                    service_name="agent-service",
                    service_namespace="nemo",
                    service_version="1.0.0",
                    instrumentation_scope="nemo-relay-openinference",
                    resource_attributes={"deployment.environment": "dev"},
                    headers={"authorization": "Bearer <token>"},
                )
            )
        )
    ]
)

report = plugin.validate(config)
if any(diagnostic["level"] == "error" for diagnostic in report["diagnostics"]):
    raise RuntimeError(report["diagnostics"])

async with plugin.plugin(config):
    # Run instrumented application work here.
    pass
```

```js
const plugin = require("nemo-relay-node/plugin");
const observability = require("nemo-relay-node/observability");

await plugin.initialize({
  version: 1,
  components: [
    observability.ComponentSpec({
      version: 1,
      openinference: observability.otlpConfig({
        enabled: true,
        transport: "http_binary",
        endpoint: "http://localhost:6006/v1/traces",
        service_name: "agent-service",
        service_namespace: "nemo",
        service_version: "1.0.0",
        instrumentation_scope: "nemo-relay-openinference",
        resource_attributes: {
          "deployment.environment": "dev",
        },
        headers: {
          authorization: "Bearer <token>",
        },
      }),
    }),
  ],
});

try {
  // Run instrumented application work here.
} finally {
  plugin.clear();
}
```

```rust
use nemo_relay::observability::plugin_component::{
    ComponentSpec, ObservabilityConfig, OtlpSectionConfig,
};
use nemo_relay::plugin::{initialize_plugins, validate_plugin_config, PluginConfig};

let component = ComponentSpec::new(ObservabilityConfig {
    openinference: Some(OtlpSectionConfig {
        enabled: true,
        transport: "http_binary".into(),
        endpoint: Some("http://localhost:6006/v1/traces".into()),
        service_name: "agent-service".into(),
        service_namespace: Some("nemo".into()),
        service_version: Some("1.0.0".into()),
        instrumentation_scope: Some("nemo-relay-openinference".into()),
        resource_attributes: [("deployment.environment".into(), "dev".into())].into(),
        headers: [("authorization".into(), "Bearer <token>".into())].into(),
        ..OtlpSectionConfig::default()
    }),
    ..ObservabilityConfig::default()
});

let config = PluginConfig {
    version: 1,
    components: vec![component.into()],
    policy: Default::default(),
};

let report = validate_plugin_config(&config);
assert!(!report.has_errors());

let active = initialize_plugins(config).await?;
```

## Manual API

Use the manual subscriber API when you need an explicit subscriber name or
direct `force_flush` control.

```python
from nemo_relay import OpenInferenceConfig, OpenInferenceSubscriber

config = OpenInferenceConfig()
config.transport = "http_binary"
config.endpoint = "http://localhost:6006/v1/traces"
config.service_name = "agent-service"
config.set_resource_attribute("deployment.environment", "dev")

subscriber = OpenInferenceSubscriber(config)
subscriber.register("openinference-exporter")

# Run instrumented application work here.

subscriber.force_flush()
subscriber.deregister("openinference-exporter")
subscriber.shutdown()
```

```js
const { OpenInferenceSubscriber } = require("nemo-relay-node");

const subscriber = new OpenInferenceSubscriber({
  transport: "http_binary",
  endpoint: "http://localhost:6006/v1/traces",
  serviceName: "agent-service",
  resourceAttributes: {
    "deployment.environment": "dev",
  },
});
subscriber.register("openinference-exporter");

try {
  // Run instrumented application work here.

  subscriber.forceFlush();
} finally {
  subscriber.deregister("openinference-exporter");
  subscriber.shutdown();
}
```

```rust
use nemo_relay::observability::openinference::{
    OpenInferenceConfig, OpenInferenceSubscriber,
};

let config = OpenInferenceConfig::new()
    .with_service_name("agent-service")
    .with_endpoint("http://localhost:6006/v1/traces")
    .with_resource_attribute("deployment.environment", "dev");
let subscriber = OpenInferenceSubscriber::new(config)?;
subscriber.register("openinference-exporter")?;

// Run instrumented application work here.

subscriber.force_flush()?;
let _ = subscriber.deregister("openinference-exporter")?;
subscriber.shutdown()?;
```

## Common Validation Failures

* `transport` is not `http_binary` or `grpc`.
* Headers or resource attributes are not string-to-string maps.
* The OpenInference feature is unavailable in the current build or target.
* Tool and LLM calls do not use managed helpers, so spans contain only scope
  lifecycle data.