> ## Documentation Index
> Fetch the complete documentation index at: https://arize-ax.mintlify.site/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Temporal

> Trace Temporal workflows, activities, and LLM calls in Arize AX with Temporal OpenTelemetry and OpenInference.

[Temporal](https://temporal.io/) is a durable execution platform for long-running workflows. Temporal's Python SDK can emit OpenTelemetry spans for workflow and activity execution. Send those spans to Arize AX, then use OpenInference instrumentors inside activities to capture LLM calls in the same trace.

<Note>
  This page uses Temporal's OpenTelemetry support plus OpenInference LLM instrumentation. It is not a Temporal-specific OpenInference instrumentor.
</Note>

## Prerequisites

* Python 3.9+
* A Temporal Python application
* An Arize AX account ([sign up](https://arize.com/sign-up/))
* Your Arize AX **Space ID** and **API Key**

## Launch Arize AX

1. Sign in to your [Arize AX account](https://app.arize.com/).
2. From **Space Settings**, copy your **Space ID** and **API Key**. You will set them as `ARIZE_SPACE_ID` and `ARIZE_API_KEY` below.

## Install

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
pip install "temporalio[opentelemetry]" opentelemetry-sdk opentelemetry-exporter-otlp-proto-http openinference-instrumentation-openai openai
```

## Configure credentials

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
export ARIZE_SPACE_ID="<your-space-id>"
export ARIZE_API_KEY="<your-api-key>"
export ARIZE_PROJECT_NAME="temporal-tracing-example"
export OPENAI_API_KEY="<your-openai-api-key>"
```

## Setup tracing

Configure Arize AX as the OpenTelemetry exporter and enable the OpenInference OpenAI instrumentor before Temporal client and worker code runs. Use Temporal's replay-safe tracer provider so workflow spans use deterministic IDs and are not exported again when Temporal replays workflow code:

```python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
# instrumentation.py
import os

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from openinference.instrumentation.openai import OpenAIInstrumentor
from temporalio.contrib.opentelemetry import create_tracer_provider

tracer_provider = create_tracer_provider(
    resource=Resource.create(
        {
            "service.name": "temporal-worker",
            "openinference.project.name": os.environ["ARIZE_PROJECT_NAME"],
        }
    )
)

tracer_provider.add_span_processor(
    BatchSpanProcessor(
        OTLPSpanExporter(
            endpoint="https://otlp.arize.com/v1/traces",
            headers={
                "space_id": os.environ["ARIZE_SPACE_ID"],
                "api_key": os.environ["ARIZE_API_KEY"],
            },
        )
    )
)

trace.set_tracer_provider(tracer_provider)
OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)
print("Arize AX tracing initialized for Temporal.")
```

For EU spaces, change the exporter endpoint to:

```text theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
https://otlp.eu-west-1a.arize.com/v1/traces
```

## Add Temporal OpenTelemetry tracing

Register Temporal's OpenTelemetry plugin when creating the Temporal client:

```python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
# worker.py
from instrumentation import tracer_provider  # noqa: F401

from temporalio.client import Client
from temporalio.contrib.opentelemetry import OpenTelemetryPlugin


async def connect_client() -> Client:
    return await Client.connect(
        "localhost:7233",
        plugins=[OpenTelemetryPlugin(add_temporal_spans=True)],
    )
```

With the plugin installed, Temporal creates spans for client calls, workflows, and activities, and propagates trace context across client, workflow, and activity boundaries. OpenAI calls made inside activities are captured by OpenInference and nest under the active Temporal span when they share the same OpenTelemetry context.

## Put LLM calls in activities

Temporal workflow code must stay deterministic. Put network I/O, including LLM calls, in activities:

```python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
from openai import AsyncOpenAI
from temporalio import activity


@activity.defn
async def draft_reply(ticket: str) -> str:
    response = await AsyncOpenAI(max_retries=0).chat.completions.create(
        model="gpt-5.4-mini",
        messages=[
            {"role": "system", "content": "Draft a concise support reply."},
            {"role": "user", "content": ticket},
        ],
    )
    return response.choices[0].message.content or ""
```

## Verify in Arize

1. Run a workflow that executes an activity with an LLM call.
2. Open your Arize AX space and select project **`temporal-tracing-example`**.
3. You should see Temporal workflow/activity spans and OpenInference LLM spans in the trace.

### Check from the skill, CLI, or SDK

Confirm spans are actually reaching your Arize AX project. Use whichever fits your workflow — the skill and CLI work for any framework; the SDK check is shown for each language.

<Tabs>
  <Tab title="Arize skill (agent)">
    Install the [Arize Skills](https://github.com/Arize-ai/arize-skills) plugin and let your coding agent check for you:

    ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
    npx skills add Arize-ai/arize-skills
    ```

    Then prompt your agent:

    > Use the `arize-trace` skill to export and analyze recent traces from my project. Confirm spans are arriving, and summarize any errors or latency issues.
  </Tab>

  <Tab title="AX CLI">
    Export recent spans for your project — any rows mean traces are landing:

    ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
    ax spans export "$ARIZE_PROJECT_NAME" --space "$ARIZE_SPACE_ID" \
      --limit 5 --stdout | jq 'length'
    ```

    A non-zero count confirms spans reached Arize AX. Run `ax auth login` first if you have not authenticated. See the [`ax spans` reference](/docs/api-clients/cli/spans).
  </Tab>

  <Tab title="SDK">
    Query the project's spans and check that at least one came back.

    <CodeGroup>
      ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
      import os
      from arize import ArizeClient

      client = ArizeClient(api_key=os.environ["ARIZE_API_KEY"])
      resp = client.spans.list(
          project=os.environ["ARIZE_PROJECT_NAME"],
          space=os.environ["ARIZE_SPACE_ID"],
          limit=5,
      )
      count = len(resp.spans)
      print(
          f"{count} span(s) found" if count else "No spans yet — recheck setup"
      )
      ```

      ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
      // Reads ARIZE_API_KEY from the environment.
      import { listSpans } from "@arizeai/ax-client";

      const { data: spans } = await listSpans({
        project: process.env.ARIZE_PROJECT_NAME!,
        space: process.env.ARIZE_SPACE_ID!,
        limit: 5,
      });
      const count = spans.length;
      console.log(
        count ? `${count} span(s) found` : "No spans yet — recheck setup",
      );
      ```

      ```go Go theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
      client, err := arize.NewClient(
          arize.Config{APIKey: os.Getenv("ARIZE_API_KEY")},
      )
      if err != nil {
          log.Fatal(err)
      }
      resp, err := client.Spans.List(ctx, spans.ListRequest{
          Project: os.Getenv("ARIZE_PROJECT_NAME"),
          Space:   os.Getenv("ARIZE_SPACE_ID"),
          Limit:   5,
      })
      if err != nil {
          log.Fatal(err)
      }
      fmt.Printf("%d span(s) found\n", len(resp.Spans))
      ```
    </CodeGroup>

    SDK span references: [Python](/docs/api-clients/python/version-8/client-resources/spans) · [TypeScript](/docs/api-clients/typescript/version-1/client-resources/spans) · [Go](/docs/api-clients/go/version-2/client-resources/spans).
  </Tab>
</Tabs>

## What Arize captures

* Temporal workflow, activity, and client-call spans from Temporal's OpenTelemetry plugin
* OpenInference LLM spans for SDK calls made inside activities
* Errors, retries, latency, and parent-child relationships when context is propagated correctly

## Troubleshooting

* **LLM spans are not nested under activities.** Ensure the LLM call runs inside the activity execution and that both Temporal and OpenInference use the same tracer provider.
* **Duplicate or surprising workflow spans.** Temporal may replay workflow code. Keep I/O and LLM calls in activities, not workflow methods.
* **No traces in Arize.** Confirm the tracing setup runs before the Temporal client and worker are created, and that the exporter endpoint and Arize headers are set.

## Resources

<CardGroup>
  <Card icon="book-open" href="https://docs.temporal.io/develop/python/platform/observability" title="Temporal Python observability" horizontal />

  <Card icon="terminal" href="https://github.com/Arize-ai/openinference/tree/main/python/instrumentation/openinference-instrumentation-openai" title="OpenInference OpenAI Instrumentor" horizontal />

  <Card icon="book-open" href="/docs/ax/concepts/otel-openinference/context-propagation" title="OpenTelemetry context propagation" horizontal />
</CardGroup>
