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

# TypeSafe AI

> Trace TypeSafe AI System One calls in Python with OpenInference and send spans to Arize AX for LLM observability.

[TypeSafe AI](https://typesafe.ai) answers typed questions about a piece of state through its [System One](https://docs.typesafe.ai/concepts/system-one) API. A request sends a `state` plus a map of named questions built from three primitives — `Noul` (yes/no), `Choice` (labeled alternatives), and `Score` (an ordered rubric) — and returns one typed answer per question. Arize AX captures every call through the [`openinference-instrumentation-typesafe`](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation/openinference-instrumentation-typesafe) package, which wraps both `TypeSafeClient.system_one` and `AsyncTypeSafeClient.system_one` as OpenInference [decision spans](/docs/ax/concepts/otel-openinference/span-kinds.md#decision).

> [!NOTE]
>
> This is the Python guide. For the TypeScript / JavaScript instrumentor, see [TypeSafe AI (JS)](/docs/ax/integrations/ts-js-agent-frameworks/typesafe/typesafe-tracing-js.md).

A System One call is not a chat exchange, so the state and the answers are recorded as `input.value` and `output.value` rather than as `llm.input_messages` / `llm.output_messages`. Jev is a decision model, not a language model, so the span identifies the model and its token usage under `decision.*` instead of `llm.*`. This keeps Jev usage from being counted as LLM usage.

## Prerequisites

* Python 3.10+
* `typesafe-sdk` 0.6.0 or newer — the range the instrumentor patches
* `openinference-instrumentation-typesafe` 0.1.4 or newer
* An Arize AX account ([sign up](https://arize.com/sign-up/))
* A `TYPESAFE_API_KEY` from [TypeSafe AI](https://docs.typesafe.ai/)

## 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 arize-otel "openinference-instrumentation-typesafe>=0.1.4" typesafe-sdk
```

## 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="typesafe-tracing-example"
export TYPESAFE_API_KEY="<your-typesafe-api-key>"
```

## Setup tracing

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

from arize.otel import register
from openinference.instrumentation.typesafe import TypeSafeAIInstrumentor

tracer_provider = register(
    space_id=os.environ["ARIZE_SPACE_ID"],
    api_key=os.environ["ARIZE_API_KEY"],
    project_name=os.environ["ARIZE_PROJECT_NAME"],
)

TypeSafeAIInstrumentor().instrument(tracer_provider=tracer_provider)
print("Arize AX tracing initialized for TypeSafe AI.")
```

## Run TypeSafe AI

This example asks all three question types about one support ticket in a single call.

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

# Importing instrumentation first ensures the instrumentor is installed
# before the first `system_one` call.
from instrumentation import tracer_provider

from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

# The client reads TYPESAFE_API_KEY from the environment and defaults to
# the jev-latest model.
client = TypeSafeClient()

response = client.system_one(
    state={"document": "I was charged twice. Please fix this ASAP."},
    questions={
        "billing": Noul(instructions="Is this ticket about billing?"),
        "tone": Choice(
            instructions="What is the customer's tone?",
            criteria={"calm": None, "frustrated": None, "angry": None},
        ),
        "urgency": Score(
            instructions="How urgent is this ticket?",
            criteria=["can wait", "this week", "today"],
        ),
    },
)

print(response.nouls["billing"].noul)
print(response.choices["tone"].choice)
print(response.scores["urgency"].score)
```

Questions can be passed as the SDK objects shown above or as raw dictionaries — the instrumentor records either form.

### Expected output

```text wrap theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
Arize AX tracing initialized for TypeSafe AI.
0.98
frustrated
1.99
```

## Verify in Arize AX

1. Open your Arize AX space and select project **`typesafe-tracing-example`**.
2. You should see a new trace within \~30 seconds containing a single `TypeSafeClient` span with `openinference.span.kind` set to `DECISION`, carrying:
   * `input.value` — the request body (`state`, `model`, `questions`) as JSON
   * `output.value` — the response body (`model`, `answers`, `usage`) as JSON
   * `decision.system` and `decision.provider` — both `typesafe`
   * `decision.request.model_name` (`jev-latest` — the instrumentor falls back to the client default when the call omits `model`), `decision.response.model_name` (the resolved version, for example `jev-1.13.0`), and `decision.model_name` (the resolved version when the API reports it, otherwise the requested model)
   * `decision.token_count.input` and `decision.token_count.output`, when the API reports usage. There is no total; add the two if you need one.
   * `llm.invocation_parameters` — the call configuration: the `model` and any `extra_body` fields
3. If no traces appear, see [Troubleshooting](#troubleshooting).

### 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.

> [tab] 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] 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] SDK
>
> Query the project's spans and check that at least one came back.
>
> ```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))
> ```
>
> SDK span references: [Python](/docs/api-clients/python/version-8/client-resources/spans.md) · [TypeScript](/docs/api-clients/typescript/version-1/client-resources/spans.md) · [Go](/docs/api-clients/go/version-2/client-resources/spans.md).

## Trace the async client

`AsyncTypeSafeClient` is instrumented by the same `TypeSafeAIInstrumentor()` call — no extra setup. Its spans are named `AsyncTypeSafeClient` and carry identical attributes.

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

import asyncio

from typesafe_sdk import AsyncTypeSafeClient, Noul


async def main() -> None:
    async with AsyncTypeSafeClient() as client:
        response = await client.system_one(
            state={"document": "I was charged twice."},
            questions={
                "billing": Noul(instructions="Is this about billing?"),
            },
        )
    print(response.nouls["billing"].noul)


asyncio.run(main())
```

## Mask sensitive payloads

The `state` is the document you are asking about, so it often carries customer data. Because the state and the questions are recorded only in `input.value`, `hide_inputs` keeps the whole request off the span, and `hide_outputs` does the same for the answers:

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

from arize.otel import register
from openinference.instrumentation import TraceConfig
from openinference.instrumentation.typesafe import TypeSafeAIInstrumentor

tracer_provider = register(
    space_id=os.environ["ARIZE_SPACE_ID"],
    api_key=os.environ["ARIZE_API_KEY"],
    project_name=os.environ["ARIZE_PROJECT_NAME"],
)

TypeSafeAIInstrumentor().instrument(
    tracer_provider=tracer_provider,
    config=TraceConfig(hide_inputs=True, hide_outputs=True),
)
```

The `decision.*` model names and token counts survive masking. `llm.invocation_parameters` holds no request content — only the model and any `extra_body` fields — so mask it with `hide_llm_invocation_parameters` only if those are sensitive.

You can also suppress tracing for a block with `suppress_tracing()`, and attach session, user, metadata, and tag information with the `using_session`, `using_user`, and `using_attributes` context managers.

## Troubleshooting

* **No traces in Arize AX.** Confirm `ARIZE_SPACE_ID` and `ARIZE_API_KEY` are set in the same shell that runs `example.py`.
* **TypeSafe spans missing but other spans present.** `TypeSafeAIInstrumentor().instrument(...)` must run before the first `system_one` call. Import order is not the constraint here: the instrumentor patches the method on the `TypeSafeClient` class, so importing `typesafe_sdk` — or even constructing a client — before instrumenting is fine. Importing `instrumentation` first is simply the easiest way to guarantee the call order.
* **No span for `client.models.list()`.** Expected — the instrumentor only wraps `system_one`.
* **`TypeSafeAuthenticationError`.** Verify `TYPESAFE_API_KEY` is set and valid; the client reads it from the environment unless you pass `api_key` explicitly.
* **Instrumentor installs but never patches.** It declares `typesafe-sdk >= 0.6.0` as an instrumented dependency, and OpenTelemetry's base instrumentor logs a dependency conflict and skips patching rather than raising when the installed SDK falls outside that range. Check the version with `pip show typesafe-sdk`.
* **Answers present but a question is missing from the response.** The SDK drops answer types it doesn't model yet and logs a warning; the raw payload is still on `response.raw_http_response`.

## Resources

> [card] [TypeSafe AI Documentation](https://docs.typesafe.ai/)

> [card] [OpenInference TypeSafe Instrumentor (Python)](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation/openinference-instrumentation-typesafe)

> [card] [Runnable TypeSafe Tracing Examples](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation/openinference-instrumentation-typesafe/examples)

> [card] [TypeSafe AI SDK on PyPI](https://pypi.org/project/typesafe-sdk/)

> [card] [TypeSafe AI (JS/TS) tracing](/docs/ax/integrations/ts-js-agent-frameworks/typesafe/typesafe-tracing-js.md)
