---
title: "Add Attributes, Metadata, Users"
---

### Using context to customize spans

In order to customize spans that are created via auto-instrumentation, The Otel Context can be used to set span attributes created during a block of code (think child spans or spans under that block of code). Our [`openinference`](https://github.com/Arize-ai/openinference/tree/main) packages offer convenient tools to write and read from the OTel Context. Phoenix re-exports those helpers directly from `arize-phoenix-otel` (Python, >=0.16.0) and `@arizeai/phoenix-otel` (TypeScript), so you can register tracing and manage context attributes from the same package. The benefit of this approach is that OpenInference auto [instrumentors](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation) will pass (e.g. inherit) these attributes to all spans underneath a parent trace.

Supported Context Attributes include:

* **Session ID\*** Unique identifier for a session

* **User ID\*** Unique identifier for a user.

* **Metadata** Metadata associated with a span.

* **Tags\*** List of tags to give the span a category.

* **Prompt Template**

  * **Template** Used to generate prompts as Python f-strings.

  * **Version** The version of the prompt template.

  * **Variables** key-value pairs applied to the prompt template.

### Install Phoenix OTEL

<Tabs>

  <Tab title="Python" icon="python">

    ```bash
    pip install "arize-phoenix-otel>=0.16.0"
    ```

    <Note>
    Requires `arize-phoenix-otel` **0.16.0 or later** to import the context managers directly from `phoenix.otel`. On older versions, import them from `openinference.instrumentation` instead.
    </Note>

  </Tab>

  <Tab title="TypeScript" icon="js">

    ```bash
    npm install @arizeai/phoenix-otel
    ```

  </Tab>

  <Tab title="Go" icon="golang">

    ```bash
    go get github.com/Arize-ai/openinference/go/openinference-instrumentation
    ```

    <Note>
    Phoenix does not ship a `phoenix-otel-go` wrapper. Use the OpenInference Go `instrumentation` package directly to store context attributes on `context.Context`. See [Go integrations](/docs/phoenix/integrations) for tracer-provider setup.
    </Note>

  </Tab>

</Tabs>

### Specifying a session

<Tabs>

  <Tab title="Python" icon="python">

    We provide a `using_session` context manager to add session a ID to the current OpenTelemetry Context. OpenInference auto [instrumentators](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation) will read this Context and pass the session ID as a span attribute, following the OpenInference [semantic conventions](https://github.com/Arize-ai/openinference/tree/main/python/openinference-semantic-conventions). Its input, the session ID, must be a non-empty string.

    ```python
    from phoenix.otel import using_session

    with using_session(session_id="my-session-id"):
        # Calls within this block will generate spans with the attributes:
        # "session.id" = "my-session-id"
        ...
    ```

    It can also be used as a decorator:

    ```python
    @using_session(session_id="my-session-id")
    def call_fn(*args, **kwargs):
        # Calls within this function will generate spans with the attributes:
        # "session.id" = "my-session-id"
        ...
    ```

  </Tab>

  <Tab title="TypeScript" icon="js">

    We provide a `setSession` function which allows you to set a sessionId on context. You can use this utility in conjunction with [`context.with`](https://opentelemetry.io/docs/languages/js/context/#set-active-context) to set the active context. OpenInference auto instrumentations will then pick up these attributes and add them to any spans created within the `context.with` callback.

    ```javascript
    import { context, setSession } from "@arizeai/phoenix-otel"

    context.with(
      setSession(context.active(), { sessionId: "session-id" }),
      () => {
        // Calls within this block will generate spans with the attributes:
        // "session.id" = "session-id"
      }
    )
    ```

  </Tab>

  <Tab title="Go" icon="golang">

    Use `instrumentation.WithSession` to attach a session ID to the context. OpenInference Go auto-instrumentors (OpenAI Go, Anthropic Go) automatically read this from `context.Context` and add `session.id` to every LLM span descended from it.

    ```go
    import "github.com/Arize-ai/openinference/go/openinference-instrumentation"

    ctx = instrumentation.WithSession(ctx, "my-session-id")
    // Calls within this context will generate spans with the attributes:
    // "session.id" = "my-session-id"
    ```

  </Tab>

</Tabs>

### Specifying users

<Tabs>

  <Tab title="Python" icon="python">

    We provide a `using_user` context manager to add user ID to the current OpenTelemetry Context. OpenInference auto [instrumentators](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation) will read this Context and pass the user ID as a span attribute, following the OpenInference [semantic conventions](https://github.com/Arize-ai/openinference/tree/main/python/openinference-semantic-conventions). Its input, the user ID, must be a non-empty string.

    ```python
    from phoenix.otel import using_user
    with using_user("my-user-id"):
        # Calls within this block will generate spans with the attributes:
        # "user.id" = "my-user-id"
        ...
    ```

    It can also be used as a decorator:

    ```python
    @using_user("my-user-id")
    def call_fn(*args, **kwargs):
        # Calls within this function will generate spans with the attributes:
        # "user.id" = "my-user-id"
        ...
    ```

  </Tab>

  <Tab title="TypeScript" icon="js">

    We provide a `setUser` function which allows you to set a userId on context. You can use this utility in conjunction with [`context.with`](https://opentelemetry.io/docs/languages/js/context/#set-active-context) to set the active context. OpenInference auto instrumentations will then pick up these attributes and add them to any spans created within the `context.with` callback.

    ```javascript
    import { context, setUser } from "@arizeai/phoenix-otel"

    context.with(
      setUser(context.active(), { userId: "user-id" }),
      () => {
        // Calls within this block will generate spans with the attributes:
        // "user.id" = "user-id"
      }
    )
    ```

  </Tab>

  <Tab title="Go" icon="golang">

    Use `instrumentation.WithUser` to attach a user ID to the context. OpenInference Go auto-instrumentors automatically read this from `context.Context` and add `user.id` to every LLM span descended from it.

    ```go
    import "github.com/Arize-ai/openinference/go/openinference-instrumentation"

    ctx = instrumentation.WithUser(ctx, "my-user-id")
    // Calls within this context will generate spans with the attributes:
    // "user.id" = "my-user-id"
    ```

  </Tab>

</Tabs>

### Specifying Metadata

<Tabs>

  <Tab title="Python" icon="python">

    We provide a `using_metadata` context manager to add metadata to the current OpenTelemetry Context. OpenInference auto [instrumentators](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation) will read this Context and pass the metadata as a span attribute, following the OpenInference [semantic conventions](https://github.com/Arize-ai/openinference/tree/main/python/openinference-semantic-conventions). Its input, the metadata, must be a dictionary with string keys. This dictionary will be serialized to JSON when saved to the OTEL Context and remain a JSON string when sent as a span attribute.

    ```python
    from phoenix.otel import using_metadata
    metadata = {
        "key-1": value_1,
        "key-2": value_2,
        ...
    }
    with using_metadata(metadata):
        # Calls within this block will generate spans with the attributes:
        # "metadata" = "{\"key-1\": value_1, \"key-2\": value_2, ... }" # JSON serialized
        ...
    ```

    It can also be used as a decorator:

    ```python
    @using_metadata(metadata)
    def call_fn(*args, **kwargs):
        # Calls within this function will generate spans with the attributes:
        # "metadata" = "{\"key-1\": value_1, \"key-2\": value_2, ... }" # JSON serialized
        ...
    ```

  </Tab>

  <Tab title="TypeScript" icon="js">

    We provide a `setMetadata` function which allows you to set a metadata attributes on context. You can use this utility in conjunction with [`context.with`](https://opentelemetry.io/docs/languages/js/context/#set-active-context) to set the active context. OpenInference auto instrumentations will then pick up these attributes and add them to any spans created within the `context.with` callback. Metadata attributes will be serialized to a JSON string when stored on context and will be propagated to spans in the same way.

    ```javascript
    import { context, setMetadata } from "@arizeai/phoenix-otel"

    context.with(
      setMetadata(context.active(), { key1: "value1", key2: "value2" }),
      () => {
        // Calls within this block will generate spans with the attributes:
        // "metadata" = '{"key1": "value1", "key2": "value2"}'
      }
    )
    ```

  </Tab>

  <Tab title="Go" icon="golang">

    Use `instrumentation.WithMetadata` to attach a JSON-encoded metadata string to the context. OpenInference Go auto-instrumentors automatically read this from `context.Context` and add `metadata` to every LLM span descended from it.

    ```go
    import (
        "encoding/json"

        "github.com/Arize-ai/openinference/go/openinference-instrumentation"
    )

    metadata := map[string]any{"key1": "value1", "key2": "value2"}
    metadataJSON, _ := json.Marshal(metadata)

    ctx = instrumentation.WithMetadata(ctx, string(metadataJSON))
    // Calls within this context will generate spans with the attributes:
    // "metadata" = '{"key1": "value1", "key2": "value2"}'
    ```

  </Tab>

</Tabs>

### Specifying Tags

<Tabs>

  <Tab title="Python" icon="python">

    We provide a `using_tags` context manager to add tags to the current OpenTelemetry Context. OpenInference auto [instrumentators](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation) will read this Context and pass the tags as a span attribute, following the OpenInference [semantic conventions](https://github.com/Arize-ai/openinference/tree/main/python/openinference-semantic-conventions). The input, the tag list, must be a list of strings.

    ```python
    from phoenix.otel import using_tags
    tags = ["tag_1", "tag_2", ...]
    with using_tags(tags):
        # Calls within this block will generate spans with the attributes:
        # "tag.tags" = "["tag_1","tag_2",...]"
        ...
    ```

    It can also be used as a decorator:

    ```python
    @using_tags(tags)
    def call_fn(*args, **kwargs):
        # Calls within this function will generate spans with the attributes:
        # "tag.tags" = "["tag_1","tag_2",...]"
        ...
    ```

  </Tab>

  <Tab title="TypeScript" icon="js">

    We provide a `setTags` function which allows you to set a list of string tags on context. You can use this utility in conjunction with [`context.with`](https://opentelemetry.io/docs/languages/js/context/#set-active-context) to set the active context. OpenInference auto instrumentations will then pick up these attributes and add them to any spans created within the `context.with` callback. Tags, like metadata, will be serialized to a JSON string when stored on context and will be propagated to spans in the same way.

    ```javascript
    import { context, setTags } from "@arizeai/phoenix-otel"

    context.with(
      setTags(context.active(), ["value1", "value2"]),
      () => {
        // Calls within this block will generate spans with the attributes:
        // "tag.tags" = '["value1", "value2"]'
      }
    )
    ```

  </Tab>

  <Tab title="Go" icon="golang">

    Use `instrumentation.WithTags` (variadic strings) to attach a list of tags to the context. OpenInference Go auto-instrumentors automatically read this from `context.Context` and add `tag.tags` to every LLM span descended from it.

    ```go
    import "github.com/Arize-ai/openinference/go/openinference-instrumentation"

    ctx = instrumentation.WithTags(ctx, "tag_1", "tag_2")
    // Calls within this context will generate spans with the attributes:
    // "tag.tags" = '["tag_1","tag_2"]'
    ```

  </Tab>

</Tabs>

### Customizing Attributes

<Tabs>

  <Tab title="Python" icon="python">

    We provide a `using_attributes` context manager to add attributes to the current OpenTelemetry Context. OpenInference auto [instrumentators](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation) will read this Context and pass the attributes fields as span attributes, following the OpenInference [semantic conventions](https://github.com/Arize-ai/openinference/tree/main/python/openinference-semantic-conventions). This is a convenient context manager to use if you find yourself using many of the previous ones in conjunction.

    ```python expandable
    from phoenix.otel import using_attributes
    tags = ["tag_1", "tag_2", ...]
    metadata = {
        "key-1": value_1,
        "key-2": value_2,
        ...
    }
    prompt_template = "Please describe the weather forecast for {city} on {date}"
    prompt_template_variables = {"city": "Johannesburg", "date":"July 11"}
    prompt_template_version = "v1.0"
    with using_attributes(
        session_id="my-session-id",
        user_id="my-user-id",
        metadata=metadata,
        tags=tags,
        prompt_template=prompt_template,
        prompt_template_version=prompt_template_version,
        prompt_template_variables=prompt_template_variables,
    ):
        # Calls within this block will generate spans with the attributes:
        # "session.id" = "my-session-id"
        # "user.id" = "my-user-id"
        # "metadata" = "{\"key-1\": value_1, \"key-2\": value_2, ... }" # JSON serialized
        # "tag.tags" = "["tag_1","tag_2",...]"
        # "llm.prompt_template.template" = "Please describe the weather forecast for {city} on {date}"
        # "llm.prompt_template.variables" = "{\"city\": \"Johannesburg\", \"date\": \"July 11\"}" # JSON serialized
        # "llm.prompt_template.version " = "v1.0"
        ...
    ```

    The previous example is equivalent to doing the following, making `using_attributes` a very convenient tool for the more complex settings.

    ```python expandable
    with (
        using_session("my-session-id"),
        using_user("my-user-id"),
        using_metadata(metadata),
        using_tags(tags),
        using_prompt_template(
            template=prompt_template,
            version=prompt_template_version,
            variables=prompt_template_variables,
        ),
    ):
        # Calls within this block will generate spans with the attributes:
        # "session.id" = "my-session-id"
        # "user.id" = "my-user-id"
        # "metadata" = "{\"key-1\": value_1, \"key-2\": value_2, ... }" # JSON serialized
        # "tag.tags" = "["tag_1","tag_2",...]"
        # "llm.prompt_template.template" = "Please describe the weather forecast for {city} on {date}"
        # "llm.prompt_template.variables" = "{\"city\": \"Johannesburg\", \"date\": \"July 11\"}" # JSON serialized
        # "llm.prompt_template.version " = "v1.0"
        ...
    ```

    It can also be used as a decorator:

    ```python expandable
    @using_attributes(
        session_id="my-session-id",
        user_id="my-user-id",
        metadata=metadata,
        tags=tags,
        prompt_template=prompt_template,
        prompt_template_version=prompt_template_version,
        prompt_template_variables=prompt_template_variables,
    )
    def call_fn(*args, **kwargs):
        # Calls within this function will generate spans with the attributes:
        # "session.id" = "my-session-id"
        # "user.id" = "my-user-id"
        # "metadata" = "{\"key-1\": value_1, \"key-2\": value_2, ... }" # JSON serialized
        # "tag.tags" = "["tag_1","tag_2",...]"
        # "llm.prompt_template.template" = "Please describe the weather forecast for {city} on {date}"
        # "llm.prompt_template.variables" = "{\"city\": \"Johannesburg\", \"date\": \"July 11\"}" # JSON serialized
        # "llm.prompt_template.version " = "v1.0"
        ...
    ```

  </Tab>

  <Tab title="TypeScript" icon="js">

    We provide a `setAttributes` function which allows you to add a set of attributes to context. You can use this utility in conjunction with [`context.with`](https://opentelemetry.io/docs/languages/js/context/#set-active-context) to set the active context. OpenInference auto instrumentations will then pick up these attributes and add them to any spans created within the `context.with` callback. Attributes set on context using `setAttributes` must be valid span [attribute values](https://opentelemetry.io/docs/specs/otel/common/#attribute).

    ```javascript
    import { context, setAttributes } from "@arizeai/phoenix-otel"

    context.with(
      setAttributes(context.active(), { myAttribute: "test" }),
      () => {
        // Calls within this block will generate spans with the attributes:
        // "myAttribute" = "test"
      }
    )
    ```

    You can also use multiple setters at the same time to propagate multiple attributes to the span below. Since each setter function returns a new context, they can be used together as follows.

    ```javascript
    import { context, setAttributes, setSession } from "@arizeai/phoenix-otel"

    context.with(
      setAttributes(
        setSession(context.active(), { sessionId: "session-id" }),
        { myAttribute: "test" }
      ),
      () => {
        // Calls within this block will generate spans with the attributes:
        // "myAttribute" = "test"
        // "session.id" = "session-id"
      }
    )
    ```

    You can also use `setAttributes` in conjunction with the [OpenInference Semantic Conventions](https://github.com/Arize-ai/openinference/blob/main/spec/semantic_conventions.md) to set OpenInference attributes manually.

    ```javascript
    import { context, setAttributes } from "@arizeai/phoenix-otel"

    context.with(
      setAttributes(context.active(), {
        "session.id": "session-id"
      }),
      () => {
        // Calls within this block will generate spans with the attributes:
        // "session.id" = "session-id"
      }
    )
    ```

  </Tab>

  <Tab title="Go" icon="golang">

    Go has no single-shot equivalent of `using_attributes` — compose the `With*` helpers instead. Each returns a new context; chain them to attach session, user, metadata, and tags together. Auto-instrumentors pick up all four from `context.Context`.

    ```go
    import (
        "encoding/json"

        "github.com/Arize-ai/openinference/go/openinference-instrumentation"
    )

    metadata := map[string]any{"key-1": "value_1", "key-2": "value_2"}
    metadataJSON, _ := json.Marshal(metadata)

    ctx = instrumentation.WithSession(ctx, "my-session-id")
    ctx = instrumentation.WithUser(ctx, "my-user-id")
    ctx = instrumentation.WithMetadata(ctx, string(metadataJSON))
    ctx = instrumentation.WithTags(ctx, "tag_1", "tag_2")
    // Calls within this context will generate spans with the attributes:
    // "session.id"  = "my-session-id"
    // "user.id"     = "my-user-id"
    // "metadata"    = '{"key-1":"value_1","key-2":"value_2"}'
    // "tag.tags"    = '["tag_1","tag_2"]'
    ```

    For prompt-template attributes (`llm.prompt_template.*`), set them directly on the LLM span using `semconv.LLMPromptTemplate` / `LLMPromptTemplateVersion` / `LLMPromptTemplateVariables` from `github.com/Arize-ai/openinference/go/openinference-semantic-conventions` — see [Prompt Templates](/docs/phoenix/tracing/how-to-tracing/add-metadata/instrumenting-prompt-templates-and-prompt-variables).

  </Tab>

</Tabs>

### Span Processing

The tutorials and code snippets in these docs default to the `SimpleSpanProcessor.` A `SimpleSpanProcessor` processes and exports spans as they are created. This means that if you create 5 spans, each will be processed and exported before the next span is created in code. This can be helpful in scenarios where you do not want to risk losing a batch, or if you’re experimenting with OpenTelemetry in development. However, it also comes with potentially significant overhead, especially if spans are being exported over a network - each time a call to create a span is made, it would be processed and sent over a network before your app’s execution could continue.

The `BatchSpanProcessor` processes spans in batches before they are exported. This is usually the right processor to use for an application in production but it does mean spans may take some time to show up in Phoenix.

In production we recommend the `BatchSpanProcessor` over `SimpleSpanProcessor` when deployed and the `SimpleSpanProcessor` when developing.

#### BatchSpanProcessor Example - Using arize-phoenix-otel library

```python
from phoenix.otel import register

# configure the Phoenix tracer for batch processing
tracer_provider = register(
  project_name="my-llm-app", # Default is 'default'
  batch=True, # Default is 'False'
)
```

#### BatchSpanProcessor Example - Using OTel library

```python
from opentelemetry.sdk.trace.export import SimpleSpanProcessor, BatchSpanProcessor

tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint)))
```
