1. What is OpenInference? #
A common vocabulary for AI traces.
Definition #
OpenInference is an open source project (initiated by Arize) that defines two things:
- semantic conventions: which attributes to put on a span to describe a model call, a document retrieval, a tool call or an agent step;
- ready-to-use instrumentations that automatically produce these spans for the main AI SDKs and frameworks, in Python, JavaScript and Java.
Everything is built on OpenTelemetry: OpenInference spans are ordinary OpenTelemetry spans, exported over OTLP. Phoenix and Arize AX can display them richly (messages, documents, tokens), and any OTLP backend can store them.
Why conventions? #
Without conventions, each team names its attributes its own way (prompt, llm_input, messages...) and no tool can compute a token cost or display a conversation. With OpenInference, llm.token_count.prompt means the same thing whatever the framework, which enables generic dashboards, evaluations and comparisons.
2. Span kinds #
openinference.span.kind.
The values #
| Kind | Represents | Example |
|---|---|---|
LLM | A call to a language model | chat.completions.create |
EMBEDDING | Computing embeddings | Vectorizing a question |
RETRIEVER | Document retrieval | Query to a vector database |
RERANKER | Document reranking | Cross-encoder on the top 20 results |
CHAIN | A sequence of steps or application logic | Full RAG pipeline |
TOOL | A tool or function call | get_weather(city) |
AGENT | An agent reasoning step | Tool-choice loop then answer |
GUARDRAIL | A safety or compliance check | Personal data detection |
EVALUATOR | An evaluation | Relevance LLM judge |
PROMPT | Rendering a prompt template | Filling in the variables |
Good to know: The list of kinds evolves with the specification (for exampleDECISIONfor routing choices): seespec/semantic_conventions.mdin the OpenInference repository.
3. Essential attributes #
What a span contains.
By family #
| Family | Attributes | Content |
|---|---|---|
| Input and output | input.value, input.mime_type, output.value, output.mime_type | What goes in and out of the step (text or JSON) |
| Model | llm.model_name, llm.provider, llm.invocation_parameters | Model, provider, temperature, max_tokens (JSON) |
| Messages | llm.input_messages.<i>.message.role, .message.content, llm.output_messages.<i>... | The conversation sent and received, message by message |
| Tokens | llm.token_count.prompt, llm.token_count.completion, llm.token_count.total | The basis for computing cost |
| Template | llm.prompt_template.template, .variables, .version | The prompt before filling, to link an answer to a version |
| Documents | retrieval.documents.<i>.document.id, .content, .score, .metadata | Retrieved passages and their score |
| Embeddings | embedding.model_name, embedding.embeddings.<i>.embedding.text, .vector | Vectorized texts |
| Tools | tool.name, tool.description, tool.parameters, tool_call.function.name, tool_call.function.arguments | Tool called and arguments chosen by the model |
| Context | session.id, user.id, metadata, tag.tags | Group by conversation, user, version, feature |
4. Instrumenting #
Automatic first, then manual.
Automatic instrumentation #
pip install arize-phoenix-otel openinference-instrumentation-openai openaifrom phoenix.otel import register
# configure OpenTelemetry et active toutes les instrumentations OpenInference installées
tracer_provider = register(
project_name="support-bot",
endpoint="http://localhost:6006/v1/traces", # Phoenix, en OTLP/HTTP
auto_instrument=True,
)
from openai import OpenAI
client = OpenAI()
client.chat.completions.create(model="gpt-4o-mini",
messages=[{"role": "user", "content": "Bonjour"}])Without Phoenix, the same instrumentation works with the standard OpenTelemetry SDK:
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from openinference.instrumentation.openai import OpenAIInstrumentor
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint="http://collector:4318/v1/traces")))
OpenAIInstrumentor().instrument(tracer_provider=provider)| Ecosystem | Example packages |
|---|---|
| Model providers | openinference-instrumentation-openai, -anthropic, -bedrock, -vertexai, -mistralai, -litellm |
| Frameworks | -langchain, -llama-index, -dspy, -haystack |
| Agents | -openai-agents, -crewai, -smolagents, -autogen |
| JavaScript, Java | @arizeai/openinference-instrumentation-openai, LangChain.js and LangChain4j instrumentations |
Manual instrumentation #
For an in-house step (a search in your own index, a business tool), create the span yourself following the conventions:
from opentelemetry import trace
from openinference.semconv.trace import SpanAttributes, OpenInferenceSpanKindValues
tracer = trace.get_tracer("rag")
def retrieve(question: str):
with tracer.start_as_current_span("retrieve") as span:
span.set_attribute(SpanAttributes.OPENINFERENCE_SPAN_KIND, OpenInferenceSpanKindValues.RETRIEVER.value)
span.set_attribute(SpanAttributes.INPUT_VALUE, question)
docs = index.search(question, k=3)
for i, d in enumerate(docs):
span.set_attribute(f"retrieval.documents.{i}.document.id", d.id)
span.set_attribute(f"retrieval.documents.{i}.document.content", d.text)
span.set_attribute(f"retrieval.documents.{i}.document.score", d.score)
return docsContext: sessions, users, metadata #
from openinference.instrumentation import using_attributes
with using_attributes(session_id=conversation_id, user_id=user.id,
metadata={"app_version": "2.3.0", "feature": "faq"}, tags=["beta"]):
answer = agent.run(question) # tous les spans créés ici portent ces attributsGood practice: Always set session.id for conversations and a version metadata: this is what makes it possible to display a complete conversation and compare two versions of the application.5. Privacy and volume #
Prompts contain sensitive data.
Masking #
Prompts and answers may contain personal data. OpenInference instrumentations accept a masking configuration, in code (TraceConfig) or through environment variables:
| Variable | Effect |
|---|---|
OPENINFERENCE_HIDE_INPUTS | Hides input.value and input messages |
OPENINFERENCE_HIDE_OUTPUTS | Hides outputs |
OPENINFERENCE_HIDE_INPUT_MESSAGES, ..._OUTPUT_MESSAGES | Hides only the messages |
OPENINFERENCE_HIDE_EMBEDDING_VECTORS | Removes vectors, which are often large |
OPENINFERENCE_BASE64_IMAGE_MAX_LENGTH | Limits the size of encoded images |
Common mistake: Hiding all inputs then makes it impossible to evaluate answers. Prefer masking sensitive fields (in code or in an OpenTelemetry Collector) and restricting access to traces.
6. OpenInference and OpenTelemetry's GenAI conventions #
Two close standards.
Comparison #
OpenTelemetry also defines conventions for generative AI (gen_ai.* attributes: gen_ai.request.model, gen_ai.usage.input_tokens...), still being stabilized. OpenInference came earlier, is richer for RAG and agents (documents, reranking, tools, span kinds), and is widely tooled.
| OpenInference | OpenTelemetry GenAI | |
|---|---|---|
| Backed by | OpenInference project (Arize) | OpenTelemetry (CNCF) |
| Prefix | llm.*, retrieval.*, tool.* | gen_ai.* |
| Business span kinds | Yes (openinference.span.kind) | By operation (gen_ai.operation.name) |
| Instrumentations | Many, Python, JS, Java | Growing (contrib instrumentations) |
Good practice: Choose the instrumentation based on your main backend; if you need to feed several tools, an OpenTelemetry Collector can rename attributes (transform processor) to switch from one convention to the other.
