> ## 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 systemOne calls with the OpenInference instrumentor and send spans to Arize AX for LLM observability.

[TypeSafe AI](https://typesafe.ai) answers typed questions about a piece of state — yes/no, scores, and labeled choices — through its `systemOne` API, returning answers whose TypeScript types are inferred from the questions you ask. Arize AX captures every call through the [`@arizeai/openinference-instrumentation-typesafe`](https://github.com/Arize-ai/openinference/tree/main/js/packages/openinference-instrumentation-typesafe) instrumentor, which emits one OpenInference [decision span](/docs/ax/concepts/otel-openinference/span-kinds.md#decision) per `TypeSafeClient.systemOne` call with the request and answers as JSON, the resolved model, and token usage. Jev is a decision model, not a language model, so the model and its token usage are recorded under `decision.*` instead of `llm.*`. This keeps Jev usage from being counted as LLM usage.

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

> [!NOTE]
>
> `client.models.list()` is not instrumented — only `systemOne` produces spans. SDK retries are folded into one span rather than emitting a span per attempt.

## Prerequisites

* Node.js 20+
* `@typesafe-ai/sdk` 0.6.0 or newer — the range the instrumentor patches
* `@arizeai/openinference-instrumentation-typesafe` 0.4.3 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"}}
npm install @typesafe-ai/sdk tsx typescript \
  "@arizeai/openinference-instrumentation-typesafe@>=0.4.3" \
  @arizeai/openinference-semantic-conventions \
  @opentelemetry/api \
  @opentelemetry/context-async-hooks \
  @opentelemetry/exporter-trace-otlp-proto \
  @opentelemetry/resources \
  @opentelemetry/sdk-trace \
  @opentelemetry/semantic-conventions
```

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

Register a `TracerProvider` that ships spans to Arize AX, then hand it to `TypeSafeInstrumentation` and patch the SDK with `manuallyInstrument`.

```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
// instrumentation.ts
import { context, trace } from "@opentelemetry/api";
import {
  AsyncLocalStorageContextManager,
} from "@opentelemetry/context-async-hooks";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { SimpleSpanProcessor, TracerProvider } from "@opentelemetry/sdk-trace";
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
import * as TypeSafe from "@typesafe-ai/sdk";
import {
  TypeSafeInstrumentation,
} from "@arizeai/openinference-instrumentation-typesafe";
import {
  SEMRESATTRS_PROJECT_NAME,
} from "@arizeai/openinference-semantic-conventions";

const projectName =
  process.env.ARIZE_PROJECT_NAME ?? "typesafe-tracing-example";

export const provider = new TracerProvider({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: projectName,
    [SEMRESATTRS_PROJECT_NAME]: projectName,
  }),
  spanProcessors: [
    new SimpleSpanProcessor({
      exporter: new OTLPTraceExporter({
        url: "https://otlp.arize.com/v1/traces",
        headers: {
          "arize-space-id": process.env.ARIZE_SPACE_ID ?? "",
          "arize-api-key": process.env.ARIZE_API_KEY ?? "",
        },
      }),
    }),
  ],
});

trace.setGlobalTracerProvider(provider);
context.setGlobalContextManager(
  new AsyncLocalStorageContextManager().enable(),
);

const instrumentation = new TypeSafeInstrumentation({
  tracerProvider: provider,
});

instrumentation.manuallyInstrument(TypeSafe);

console.log("Arize AX tracing initialized for TypeSafe AI.");
```

> [!NOTE]
>
> `manuallyInstrument(TypeSafe)` patches `TypeSafeClient.prototype`, so it covers ESM, bundlers, and modules imported before the instrumentor was enabled — import `instrumentation.ts` first and the rest of your code can keep importing `TypeSafeClient` normally. On CommonJS you can instead let Node's `require` hook patch the SDK, by installing `@opentelemetry/instrumentation` and passing the instrumentor to its `registerInstrumentations({ instrumentations: [instrumentation] })` in place of the `manuallyInstrument` call. That path only works when `instrumentation.ts` runs before the SDK is required, which is why the snippet above uses `manuallyInstrument`.

## Run TypeSafe AI

```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
// example.ts

// Importing instrumentation first ensures tracing is set up before the
// TypeSafe client is used.
import { provider } from "./instrumentation";

import { choice, TypeSafeClient } from "@typesafe-ai/sdk";

// The client reads TYPESAFE_API_KEY from the environment and defaults to
// the jev-latest model.
const client = new TypeSafeClient();

const { data } = await client
  .systemOne({
    state: { document: "I was charged twice. Please fix this ASAP." },
    questions: {
      category: choice("What is this ticket about?", {
        billing: null,
        technical: null,
        other: null,
      }),
    },
  })
  .withResponse();

console.log(data.answers.category.choice, data.answers.category.confidence);

// Flush any pending spans before the process exits.
await provider.forceFlush();
```

Run the example with `npx tsx example.ts`. The instrumented `systemOne` still returns an SDK `APIPromise`, so `await`, `withResponse()`, and `map()` behave exactly as they do without tracing.

### Expected output

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

## 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.systemOne` span with `openinference.span.kind` set to `DECISION`. Its input is the JSON request (`state` plus `questions`) and its output is the JSON result (`model`, `answers`, `usage`). It also carries:
   * `decision.system` and `decision.provider` — both `typesafe`
   * `decision.request.model_name` (`jev-latest`, which this instrumentor fills in from 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`. There is no total; add the two if you need one.
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).

## Mask sensitive payloads

The request `state` is the document you are asking about, so it often carries customer data. Pass `traceConfig` to drop inputs, outputs, or both before the span leaves your process:

```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
const instrumentation = new TypeSafeInstrumentation({
  tracerProvider: provider,
  traceConfig: {
    hideInputs: true,
    hideOutputs: true,
  },
});
```

The `decision.*` model names and token counts are still recorded with payloads masked. See the [instrumentor README](https://github.com/Arize-ai/openinference/tree/main/js/packages/openinference-instrumentation-typesafe) for the full list of masking options.

## Troubleshooting

* **No traces in Arize AX.** Confirm `ARIZE_SPACE_ID` and `ARIZE_API_KEY` are set in the same shell that runs `example.ts`. Enable OpenTelemetry debug logs with `export OTEL_LOG_LEVEL=debug` and re-run.
* **Spans missing but `systemOne` returns answers.** `manuallyInstrument(TypeSafe)` must run before the first `systemOne` call, so `instrumentation.ts` has to be imported before any code that calls the SDK.
* **No span for `models.list()`.** Expected — the instrumentor only wraps `systemOne`.
* **`401` from TypeSafe.** Verify `TYPESAFE_API_KEY` is set and valid; the client reads it from the environment unless you pass `apiKey` explicitly.
* **SDK fails to install or import.** The TypeSafe SDK requires Node.js 20+. Check with `node --version`.
* **Process exits before spans flush.** Spans are exported asynchronously; always `await provider.forceFlush()` (or `provider.shutdown()`) before the process exits.

## Resources

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

> [card] [OpenInference TypeSafe Instrumentor (JS/TS)](https://github.com/Arize-ai/openinference/tree/main/js/packages/openinference-instrumentation-typesafe)

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

> [card] [TypeSafe AI SDK on npm](https://www.npmjs.com/package/@typesafe-ai/sdk)

> [card] [TypeSafe AI (Python) tracing](/docs/ax/integrations/python-agent-frameworks/typesafe/typesafe-tracing.md)
