immersive-oa-sdk
OpenTelemetry instrumentation for AI applications, from Immersive AI Security.
immersive-oa-sdk gives you visibility into what your AI application is
actually doing. One call sets up the instrumentation an AI app needs — LLM,
agent and tool calls, plus the HTTP and messaging layers around them — and sends
a copy of that trace data to Observe & Assess, where it is assessed for AI
security risk.
If your application doesn't use OpenTelemetry yet, immersive-oa-sdk sets it up
for you. If it already does, immersive-oa-sdk adds to that setup and never
replaces it. Your own observability vendor keeps receiving everything it
receives today.
Requirements
Python 3.11 or newer.
OpenTelemetry 1.45 or newer (contrib 0.66b0 or newer). immersive-oa-sdk
relies on internals of opentelemetry.sdk, opentelemetry.attributes and
several opentelemetry.instrumentation.* packages, so it is tested against
every OpenTelemetry release in that range.
Install
Install immersive-oa-sdk from PyPI, with the openllmetry or
openinference extra for LLM, agent and MCP spans (see
LLM, agent and MCP calls). The package imports as
immersive_oa_sdk.
pip install "immersive-oa-sdk[openllmetry]"
# or
uv add "immersive-oa-sdk[openllmetry]"
Quickstart
This sends a small FastAPI app's spans to Observe & Assess.
-
In Observe & Assess, add a project on the Projects page. Copy the ingest token it shows: it's shown only once.
-
Install
immersive-oa-sdk,fastapianduvicorn, and inmain.pycallimmersive_oa_sdk.configure()before you create the FastAPI app:import fastapi import immersive_oa_sdk immersive_oa_sdk.configure() app = fastapi.FastAPI() @app.get("/hello") def hello() -> dict[str, str]: return {"hello": "world"}
-
Run it with your token, your Observe & Assess address, and a name and environment for the service:
export IMMERSIVE_API_KEY=<token> export IMMERSIVE_ENDPOINT=https://<your Observe & Assess address> export OTEL_SERVICE_NAME=hello export OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=dev uvicorn main:app --port 8000
-
Call your app with
curl http://localhost:8000/hello. Within a few seconds the project in Observe & Assess shows thedevenvironment.
Configuration
configure() reads these, and its keyword arguments of the same name win over
them:
| Variable | Argument | Default | What |
|---|---|---|---|
IMMERSIVE_API_KEY |
api_key |
none, required | The project's ingest token. |
IMMERSIVE_ENDPOINT |
endpoint |
none, required | Base URL of your Observe & Assess instance; /v1/traces is appended. |
IMMERSIVE_CA_CERT |
ca_cert |
the system trust store | Path to a CA bundle for that endpoint. |
IMMERSIVE_CAPTURE_CONTENT |
capture_content |
true |
false keeps prompt and completion text off GenAI spans. |
IMMERSIVE_ENABLED |
true |
false turns the SDK off without removing the call. |
|
service_name |
OTEL_SERVICE_NAME |
The service's name, unless the app registers its own tracer provider. |
configure() never raises and never stops your app. A missing endpoint or
token, or a malformed token, logs one warning and leaves export off.
It never reads OTEL_EXPORTER_*, so your own exporters keep their endpoint,
headers and certificates and ours never picks them up. It does follow the
standard variables that describe your app rather than an exporter:
OTEL_SERVICE_NAMEnames the service, andOTEL_RESOURCE_ATTRIBUTESsets its environment, asdeployment.environment.name=<environment>. Spans with no environment are filed underdefault. Both apply to your own OpenTelemetry pipeline too.OTEL_TRACES_SAMPLERpicks the sampler whenimmersive-oa-sdkregisters the tracer provider. Assessment requests are always sampled whatever it says.OTEL_PROPAGATORSkeeps your propagators; ours is added alongside. Keepbaggagein it, or assessment requests are correlated only in the first service they reach.OTEL_PYTHON_DISABLED_INSTRUMENTATIONSturns off any of the bundled instrumentations, by the namesopentelemetry-instrumentuses.OTEL_SEMCONV_STABILITY_OPT_IN, if you set it, picks the HTTP attributes. Otherwise HTTP spans carry the stable ones.
What's captured
configure() turns on the instrumentation for each of these that is
installed, unless you instrument it yourself:
- Web frameworks: FastAPI, Starlette, Flask, Django and aiohttp.
- HTTP clients: requests, httpx, urllib3 and aiohttp.
- gRPC, client and server.
- Messaging: Celery, Kafka (kafka-python and confluent-kafka), RabbitMQ (pika and aio-pika), and SQS through boto3 clients.
- GenAI, with an extra: OpenAI, Anthropic, LangChain and LangGraph, and MCP, prompts and completions included unless you turn content capture off.
Every span the app creates, through these or its own tracing, is exported to Observe & Assess, and to your own exporters as well if you have them.
Call configure() early
Call configure() once, at start-up, before you create your web framework's
app object, and create it through the framework's module, as below, rather
than with a class you imported earlier, such as from fastapi import FastAPI.
configure() turns on the HTTP instrumentation, which replaces the app class
on the framework's module, so an app created from the original class isn't
traced:
import fastapi
import immersive_oa_sdk
immersive_oa_sdk.configure()
app = fastapi.FastAPI()
If you instrument a web framework, HTTP client or messaging library yourself,
for example with FlaskInstrumentor().instrument(), do that before
configure() too, which leaves it as you set it up. Called afterwards,
OpenTelemetry ignores it, along with its options.
Call configure() at start-up in the workers that consume your queues too,
before they start consuming. A message published while handling an
assessment request carries its request_id and sampling decision, so the
worker's spans join the request's trace.
Apps that already use OpenTelemetry
If your application registers its own tracer provider, call
immersive_oa_sdk.configure() after it does: immersive-oa-sdk then adds its
exporter to your provider, which keeps your resource and sampler. OpenTelemetry
ignores a tracer provider registered after the first, so calling configure()
first would leave your own provider unused.
Wrap your sampler in immersive_oa_sdk.sampler() too, so that requests sent by
an Observe & Assess assessment are always sampled. Everything else is still
sampled as your own sampler decides:
import immersive_oa_sdk
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.sampling import ParentBased, TraceIdRatioBased
provider = TracerProvider(
resource=Resource.create(),
sampler=immersive_oa_sdk.sampler(ParentBased(TraceIdRatioBased(0.1))),
)
trace.set_tracer_provider(provider)
immersive_oa_sdk.configure()
configure() logs a warning if your provider's sampler isn't wrapped. If
immersive-oa-sdk registers the provider itself, its sampler is already wrapped
and follows OTEL_TRACES_SAMPLER for everything else.
If the service you assess calls other services of yours that also use
immersive-oa-sdk, keep their samplers ParentBased, as in the example above.
Only the service that verifies an assessment request, normally the one you
assess, always samples it; the services it calls keep their spans only if their
sampler follows its decision. ParentBased is the OpenTelemetry default. If you
set OTEL_TRACES_SAMPLER, use one of its parentbased_* values.
LLM, agent and MCP calls
LLM, agent and MCP spans come from an extra, so you keep the GenAI
instrumentation you may already run. immersive-oa-sdk[openllmetry] adds
OpenLLMetry's instrumentation for OpenAI, Anthropic, LangChain and LangGraph,
and MCP, and immersive-oa-sdk[openinference] adds OpenInference's for the same
libraries. configure() turns each on when its library is installed. A
library your app already instruments, with OpenLLMetry or OpenInference, is
left as it is.
Install one extra. With both, configure() warns and turns on only one
flavour, so no call is traced twice: the one your app already instruments a
library with, or else OpenLLMetry.
Those spans record prompt and completion text, which Observe & Assess checks
for AI security risk. To keep it out, pass capture_content=False to
configure() or set IMMERSIVE_CAPTURE_CONTENT=false. That turns off
OpenLLMetry's own content capture for the whole process, including any
OpenLLMetry instrumentation of your own. OpenInference reads its switches
when it's turned on, so OpenInference instrumentation of your own is covered
only if you turn it on after configure().
Agents built by hand
An agent written straight against an LLM SDK, rather than a framework such as
LangGraph, gets spans for its LLM calls but none for the agent or its tools, so
it doesn't appear in Observe & Assess's agent inventory. Trace it with
immersive_oa_sdk.agent() and its tools with immersive_oa_sdk.tool(), as
decorators on sync or async functions, or as context managers:
import immersive_oa_sdk
@immersive_oa_sdk.tool("search")
async def search(query: str) -> str: ...
@immersive_oa_sdk.agent("planner")
async def plan(goal: str) -> str:
return await search(goal)
with immersive_oa_sdk.agent("planner"):
...
The agent's name is how it appears in Observe & Assess, so keep it stable.
Both are harmless without configure().
Limitations
- Samplers downstream must be parent-based. A service the assessed one
calls, with a sampler that isn't
ParentBased, may drop an assessment request's spans. - Every hop must be instrumented. A service, or a library in one, that
doesn't pass the trace context and baggage on breaks the chain: everything
past it is traced without the request's
request_id, so it isn't correlated with the assessment. - Forwarding through your own collector gives observability, not
assessment correlation. Spans your collector forwards to Observe & Assess
from apps without
immersive-oa-sdkare stored and scanned, but onlyimmersive-oa-sdkin the app verifies an assessment request's marker and tags its spans with therequest_id. - With OpenInference, agents are LangChain runs. OpenInference has no
agent span, so the first span of each LangChain run, a LangGraph graph's,
is reported as an agent named after it. A sub-agent inside another isn't
told apart from a plain graph node. An OpenAI or Anthropic call LangChain
makes is traced twice, once by each library's instrumentation; the second
span sits under your request rather than the graph, so it names no agent.
OpenInference instruments only
openai2.8,anthropic1.8 andmcp1.24 or newer. - MCP 2 isn't instrumented with OpenLLMetry yet, and SQS through raw botocore clients, rather than boto3, is traced without carrying context.
Metadata
Release files for immersive-oa-sdk 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| immersive_oa_sdk-0.1.0.tar.gz | 39.1 kB | With provenance | Details for immersive_oa_sdk-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| immersive_oa_sdk-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for immersive_oa_sdk-0.1.0-py3-none-any.whl |
Total release size: 85.0 kB
Release files / immersive_oa_sdk-0.1.0.tar.gz
| Download URL | immersive_oa_sdk-0.1.0.tar.gz |
|---|---|
| Size | 39.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5958bbf032b8350b65aded84a45b3cdd5c434618f9e29cf919f856635a4eaed4
|
|
BLAKE2b-256 checksum How to use checksums |
f4acd55495d3bbc7b180c07d048f80beb9c35294b89c5c5e4785d6c9552770ba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logRelease files / immersive_oa_sdk-0.1.0-py3-none-any.whl
| Download URL | immersive_oa_sdk-0.1.0-py3-none-any.whl |
|---|---|
| Size | 45.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fb97a83a77e6d37f877a16a428671fbe475c92bc1008c0283e9c802e40497078
|
|
BLAKE2b-256 checksum How to use checksums |
65d52b2d828be0de5fd9d027dcfd13354a52841d23647d0d19c4e55a06c110c6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logSecurity for immersive-oa-sdk 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for immersive_oa_sdk-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.