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

# LLM auto-capture

> Wrap OpenAI or Anthropic clients to emit token metadata through nozle.track().

The wrappers patch the supplied client instance's create method, preserve its return value, and emit one `nozle.track()` event when provider usage metadata is available.

They capture model, input tokens, output tokens, latency, and an optional feature tag. Prompt and completion content are not inspected or sent.

## OpenAI

```bash theme={null}
npm install openai
```

```ts theme={null}
import OpenAI from 'openai'
import { Nozle, wrapOpenAI } from '@nozle-js/node'

const nozle = new Nozle({
  apiKey: process.env.NOZLE_SECRET_KEY!,
  baseUrl: 'https://api.nozle.app',
  eventsUrl: 'https://core.nozle.app',
})

const openai = wrapOpenAI(new OpenAI(), nozle, {
  customerId: 'workspace_123',
  metricCode: 'llm_tokens',
  feature: 'assistant_reply',
})

const response = await openai.chat.completions.create({
  model: 'example-chat-model',
  messages: [{ role: 'user', content: 'Hello' }],
})
```

### OpenAI streaming

The wrapper reads usage from stream chunks. Request usage in the stream so a final usage-bearing chunk is available:

```ts theme={null}
const stream = await openai.chat.completions.create({
  model: 'example-chat-model',
  messages: [{ role: 'user', content: 'Explain vector search' }],
  stream: true,
  stream_options: { include_usage: true },
})

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? '')
}
```

Tracking is attempted after the stream finishes.

## Anthropic

```bash theme={null}
npm install @anthropic-ai/sdk
```

```ts theme={null}
import Anthropic from '@anthropic-ai/sdk'
import { Nozle, wrapAnthropic } from '@nozle-js/node'

const anthropic = wrapAnthropic(new Anthropic(), nozle, {
  customerId: 'workspace_123',
  metricCode: 'llm_tokens',
  feature: 'assistant_reply',
})

const message = await anthropic.messages.create({
  model: 'example-anthropic-model',
  max_tokens: 1_024,
  messages: [{ role: 'user', content: 'Hello' }],
})
```

For Anthropic streams, the wrapper reads input usage from `message_start` and output usage from `message_delta` events.

## `WrapOptions`

| Field        | Type     | Required | Description                             |
| ------------ | -------- | -------- | --------------------------------------- |
| `customerId` | `string` | yes      | Customer passed to `nozle.track()`.     |
| `metricCode` | `string` | no       | Event code; defaults to `llm_tokens`.   |
| `feature`    | `string` | no       | Included as a `feature` event property. |

## Event properties

```json theme={null}
{
  "model": "example-chat-model",
  "input_tokens": 320,
  "output_tokens": 84,
  "latency_ms": 742,
  "feature": "assistant_reply"
}
```

Cost calculation remains server-side. Configure the Feature and model cost rules to interpret these raw values.

## Delivery behavior

The wrapper starts tracking without awaiting it so billing telemetry does not delay the provider response. It also relies on `nozle.track()` subscription resolution when no explicit subscription can be supplied through `WrapOptions`.

For workflows requiring durable delivery, explicit transaction IDs, or strict error handling, track manually through your queue instead of relying on the wrapper.

```ts theme={null}
const response = await providerCall()

await nozle.track(
  'workspace_123',
  'llm_tokens',
  {
    model: response.model,
    input_tokens: response.inputTokens,
    output_tokens: response.outputTokens,
  },
  {
    subscriptionId: 'workspace_123_subscription',
    transactionId: `llm:${response.id}:usage`,
  },
)
```
