ExAgent.Observability.OpenTelemetry (ExAgent v2.0.0)

View Source

Optional native OpenTelemetry instrumentation, using the application's tracer.

tracing = ExAgent.Observability.OpenTelemetry.new()
agent = ExAgent.new(model: model, observability: tracing)

Add :opentelemetry_api, an SDK and an exporter in the host application and configure ExAgent.Observability.BoundedProcessor for finite admission and observable drops/export failures (see docs/guides/observability.md). The native SDK's batch processor has a soft queue threshold rather than that hard bound. This module never starts or replaces a tracer provider, configures an exporter, or performs network IO. :tracer may be an application-supplied native tracer; otherwise the instrumentation scope is :exagent, version "1".

The small exagent.gen_ai.v1 profile uses gen_ai.usage.* on model requests only. Run totals use exagent.usage.* and are inclusive of descendants; do not sum them with generation usage. Costs are estimates in cents from the existing execution ledger, not a second pricing callback or an authoritative bill. The profile is pinned to GenAI commit b5d8440f6f126738fd50f927752cd669772c517b; no schema URL is invented. Native Anthropic input is cache-exclusive and is normalized only for generation gen_ai.usage.input_tokens; OpenAI-compatible built-ins already report an inclusive total. Custom model semantics are unknown: their input remains in exagent.usage.reported_input_tokens instead of claiming a GenAI total. Run aggregates retain the existing ledger's provider-native token semantics. A durable pause ends its run span with exagent.status=paused, without an OTel error. Resume creates a new attempt/span under the same run identity. Run usage and host request/tool counts are cumulative lifetime snapshots, not attempt deltas or successful-effect counts; do not sum them across resumed attempts. exagent.continuation.* contains only the validated public reference identity and numeric version/revision, never approval actors, tokens or stored records. A Server terminal synthesized after abort/worker loss has only its last progress snapshot: exagent.usage.source=last_progress, partial usage and an unknown total cost. Observed token/count subtotals are retained, and any known monetary subtotal uses exagent.cost.known_subtotal_cents instead of claiming a complete exagent.cost.cents. Normal core results keep their reported status.

Content is off by default. Opting in requires both content: true and redact: fn field, value -> {:ok, safe_binary} | :drop end. The callback sees only explicitly selected prompt/output/messages/tool arguments/results, never model structs, dependencies, configuration, exceptions or hidden reasoning. Content is admitted only after successful redaction, valid UTF-8 and byte-limit checks. Invalid/raising callbacks and oversized values drop the entire field. Opaque values and structs are dropped; redaction input is bounded plain data. No prefixes of unredacted content are exported. Redactors are trusted local callbacks and should be fast; they are not sandboxed.

max_content_bytes defaults to 4096 per field (at most two fields per span). max_content_input_bytes defaults to 65536; oversized inputs are not sent to the redactor. Labels/IDs are bounded to 256 bytes. Use non-secret identifiers for agent, tool, model and correlation names. Arbitrary metadata, baggage, provider bodies, exception messages and stacktraces are never attributes.

capture_context/0 and with_context/2 propagate only span context and this configuration across application-owned tasks. These handles are ephemeral, not snapshot/JSON data. Server queues capture context at admission; a public lazy stream captures it at enumeration, unless trace_context: is supplied. Every attachment restores both prior OTel context and Logger metadata.

Native SDK span storage provides idempotent completion. A monitor per recording operation also closes its span when the executing process dies (including :kill), without another tracing registry or export queue. Hard termination cannot recover unreported usage and is marked partial. SDK shutdown or VM death can lose spans. Arbitrary synchronous processors/samplers installed by the host can block instrumentation; use the bounded batch setup in the example.

Instrumentation failures emit [:exagent, :observability, :failure] with a numeric count and a fixed stage. Redaction drops emit [:exagent, :observability, :content_dropped], without content or raw errors.

Summary

Functions

Capture a runtime-only span context; baggage and arbitrary context values are excluded.

Build an opt-in instrumentation configuration without changing the SDK.

Run a function under a captured context, restoring the caller even on exceptions.

Types

t()

@type t() :: %ExAgent.Observability.OpenTelemetry{
  content: term(),
  max_content_bytes: term(),
  max_content_input_bytes: term(),
  redact: term(),
  tracer: term()
}

Functions

capture_context()

Capture a runtime-only span context; baggage and arbitrary context values are excluded.

new(opts \\ [])

Build an opt-in instrumentation configuration without changing the SDK.

with_context(arg1, fun)

Run a function under a captured context, restoring the caller even on exceptions.