Because
Node.jsSDK

LLM spans

Record LLM calls automatically or manually.

Anthropic - wrapAnthropic

Wrap your Anthropic client once and every messages.create() call is traced automatically. Import from @refactorlabs/because/anthropic:

import Anthropic from '@anthropic-ai/sdk';
import { wrapAnthropic } from '@refactorlabs/because/anthropic';

const anthropic = wrapAnthropic(new Anthropic());

Then use it exactly as before:

await because.startTrace('Assess claim', async () => {
  // llm span recorded automatically - messages, model, response, tokens
  const message = await anthropic.messages.create({
    model: 'claude-sonnet-4-6',
    max_tokens: 1024,
    messages: [{ role: 'user', content: prompt }]
  });

  const result = parseResponse(message.content[0].text);
});

Each span captures the provider, model, messages (and system prompt if set), response text, token counts, and stop reason.

Streaming

Streaming works transparently. Pass stream: true as normal - the span is recorded when the stream completes:

const stream = await anthropic.messages.create({
  model: 'claude-sonnet-4-6',
  max_tokens: 1024,
  messages,
  stream: true
});

for await (const event of stream) {
  if (
    event.type === 'content_block_delta' &&
    event.delta.type === 'text_delta'
  ) {
    process.stdout.write(event.delta.text);
  }
}
// span recorded here, after the stream is consumed

Install

@anthropic-ai/sdk is a peer dependency - use the version already in your project:

npm install @refactorlabs/because
# @anthropic-ai/sdk already in your project

OpenAI - wrapOpenAI

Same pattern for OpenAI. Import from @refactorlabs/because/openai:

import OpenAI from 'openai';
import { wrapOpenAI } from '@refactorlabs/because/openai';

const openai = wrapOpenAI(new OpenAI());

Every chat.completions.create() call is traced automatically:

await because.startTrace('Generate summary', async () => {
  // llm span recorded automatically
  const completion = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [{ role: 'user', content: prompt }]
  });
});

Streaming

Pass stream_options: { include_usage: true } to capture token counts in the stream:

const stream = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages,
  stream: true,
  stream_options: { include_usage: true }
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
}
// span recorded here

Install

openai is a peer dependency - use the version already in your project:

npm install @refactorlabs/because
# openai already in your project

Manual spans

For other providers or when you need full control, use addSpan with type: 'llm' directly.

because.addSpan('llm', 'Classify intent', {
  input: {
    messages: [
      { role: 'system', content: 'You are a claims assessor.' },
      { role: 'user', content: 'Is this claim fraudulent? ...' }
    ],
    provider: 'anthropic',
    model: 'claude-sonnet-4-6'
  },
  output: { response: 'No indicators of fraud detected.' },
  durationMs: 340
});

Plain prompt

If you're not using a chat-style API, pass prompt instead of messages:

because.addSpan('llm', 'Summarise claim', {
  input: {
    prompt: 'Summarise the following claim in one sentence: ...',
    provider: 'openai',
    model: 'gpt-4o'
  },
  output: { response: 'Rear-end collision claim for $7,590 damage.' },
  durationMs: 820
});

Callback form

Use the callback form to auto-set output and timing:

const result = await because.addSpan(
  'llm',
  'Classify intent',
  { input: { messages, provider: 'anthropic', model: 'claude-sonnet-4-6' } },
  async () => {
    const res = await myLlmClient.call(messages);
    return { response: res.text };
  }
);

Input fields

FieldTypeDescription
messagesLlmMessage[]Chat messages (role + content pairs)
promptstringPlain string prompt (alternative to messages)
providerstringProvider name, e.g. 'anthropic'
modelstringModel identifier, e.g. 'gpt-4o'

Output fields

FieldTypeDescription
responsestring | JsonObjectThe model's response

Types

import type {
  LlmSpanInput,
  LlmSpanOutput,
  LlmMessage
} from '@refactorlabs/because';
type LlmMessage = {
  role: 'system' | 'user' | 'assistant' | 'tool';
  content: string;
};

type LlmSpanInput = {
  prompt?: string;
  messages?: LlmMessage[];
  provider?: string;
  model?: string;
};

type LlmSpanOutput = {
  response?: string | JsonObject;
};

On this page