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

# Vercel AI SDK

> Set up AI agent observability for Next.js and Node.js apps using the Vercel AI SDK integration.

## Set TCC environment variables

<Tip>Our SDKs default to using the `TCC_API_KEY` environment variable.</Tip>

```typescript .env.local theme={null}
TCC_API_KEY="your-api-key"
```

## Instrument AI SDK

If your API calls are directly made from your Next.js API routes, you can use the `registerOTelTCC` function to instrument your calls.

If you're using a Node.js framework (e.g. Express, Fastify, etc.), you can configure the NodeSDK directly with the TCCSpanProcessor.

<Tabs>
  <Tab title="Next.js">
    #### Step 1: Install dependencies

    <CodeGroup>
      ```Title pnpm theme={null}
      pnpm add @contextcompany/otel @vercel/otel @opentelemetry/api
      ```

      ```Title npm theme={null}
      npm i @contextcompany/otel @vercel/otel @opentelemetry/api
      ```

      ```Title bun theme={null}
      bun add @contextcompany/otel @vercel/otel @opentelemetry/api
      ```
    </CodeGroup>

    #### Step 2: Add instrumentation to Next.js

    If you haven't already, add an `instrumentation.[js|ts]` file to your project, under the `app` directory. See the [Next.js Instrumentation guide](https://nextjs.org/docs/app/guides/instrumentation) for more information on instrumenting your Next.js application.

    Call the `registerOTelTCC` function and provide your API key. This allows your app to export any AI SDK related traces to The Context Company.

    ```typescript instrumentation.ts theme={null}
    export async function register() {
      if (process.env.NEXT_RUNTIME === "nodejs") {
        const { registerOTelTCC } = await import("@contextcompany/otel/nextjs");
        registerOTelTCC();
      }
    }
    ```

    Alternatively, you can directly import `TCCSpanProcessor` for use with the `registerOTel` function from `@vercel/otel`. This is essentially the same as using `registerOTelTCC`, but allows you to pass in additional span processors.

    <Expandable title="instrumentation with TCCSpanProcessor">
      ```typescript instrumentation.ts theme={null}
      import { TCCSpanProcessor } from "@contextcompany/otel";
      import { registerOTel } from "@vercel/otel";

      export function register() {
        if (process.env.NEXT_RUNTIME === "nodejs")
          registerOTel({
            spanProcessors: [new TCCSpanProcessor()],
          });
      }
      ```
    </Expandable>

    #### Step 3: Enable telemetry for AI SDK calls

    As of AI SDK v5, telemetry is experimental and requires the `experimental_telemetry` flag to be set to `true`. Ensure you set this flag to `true` for all AI SDK calls.

    <CodeGroup>
      ```typescript generateText theme={null}
      import { generateText } from "ai";

      const result = generateText({
        // ...
        experimental_telemetry: { isEnabled: true }, // required
      });
      ```

      ```typescript streamText theme={null}
      import { streamText } from "ai";

      const result = streamText({
        // ...
        experimental_telemetry: { isEnabled: true }, // required
      });
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Node.js">
    #### Step 1: Install dependencies

    <CodeGroup>
      ```Title pnpm theme={null}
      pnpm add @contextcompany/otel @opentelemetry/sdk-node @opentelemetry/api
      ```

      ```Title npm theme={null}
      npm i @contextcompany/otel @opentelemetry/sdk-node @opentelemetry/api
      ```

      ```Title bun theme={null}
      bun add @contextcompany/otel @opentelemetry/sdk-node @opentelemetry/api
      ```
    </CodeGroup>

    #### Step 2: Add TCCSpanProcessor to NodeSDK

    If you don't already have a NodeSDK instance, we recommend creating one and keeping it in a `tcc.ts` file.

    ```typescript tcc.ts theme={null}
    import { TCCSpanProcessor } from "@contextcompany/otel";
    import { NodeSDK } from "@opentelemetry/sdk-node";

    export const tcc = new NodeSDK({
      spanProcessors: [new TCCSpanProcessor()],
    });
    ```

    #### Step 3: Start the OpenTelemetry NodeSDK

    Add the TCCSpanProcessor to the NodeSDK and start listening for traces with `.start()`.

    <CodeGroup>
      ```typescript Basic example theme={null}
      import { generateText } from "ai";
      import { tcc } from "./tcc";

      // Start listening for traces
      tcc.start();

      async function main() {
        const result = await generateText({
          // ...
          experimental_telemetry: { isEnabled: true }, // required
        });
      }
      ```

      ```typescript Express example theme={null}
      import { streamText } from "ai";
      import express, { Request, Response } from "express";
      import { tcc } from "./tcc";

      const app = express();

      // Start listening for traces
      tcc.start();

      app.post("/", async (req: Request, res: Response) => {
        const result = streamText({
          // ...
          experimental_telemetry: { isEnabled: true }, // required
        });
        result.pipeUIMessageStreamToResponse(res);
      });

      app.listen(8080, () => {
        console.log(`Server listening on port ${8080}`);
      });
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## Adding custom metadata

Custom metadata allows you to add additional properties to your [agent runs](/concepts#agent-runs).

This is particularly useful for tying agent runs to your own specific business logic, letting you filter and analyze agent runs by user, organization, feature, or some other dimension.

Custom metadata must be passed as a key-value pair to the `metadata` object.

```typescript route.ts theme={null}
import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";

const { text } = await generateText({
  // ...
  experimental_telemetry: {
    isEnabled: true,
    metadata: {
      // e.g. tag this agent run with a user id
      userId: "4a6b111c-b53a-4d00-a877-67185022ab9e",
    },
  },
});
```

Agent runs are automatically indexed by your custom metadata fields and can be filtered directly in the dashboard.

<Note>
  The `tcc.*` namespace is reserved. Only the [reserved TCC metadata keys](/concepts#tcc-metadata-keys) (`tcc.runId`, `tcc.sessionId`, `tcc.conversational`, `tcc.agent`, `tcc.userId`, `tcc.userName`, `tcc.orgId`, `tcc.orgName`) are recognized; any other `tcc.*` keys are ignored. None of them appear in your custom metadata.
</Note>

## Adding user feedback

User feedback allows you to collect score (thumbs up & thumbs down) and text feedback (up to 2000 characters) from end users on your [agent runs](/concepts#agent-runs).

This is useful for tracking user satisfaction, identifying problematic responses, and filtering agent runs in the dashboard to focus on positive or negative feedback.

### Step 1: Generate and pass a run ID

```typescript route.ts theme={null}
import { generateText } from "ai";
import { randomUUID } from "crypto";

// Generate a unique run ID (must be a UUID) before your agent execution
const runId = randomUUID();

const { text } = await generateText({
  // ...
  experimental_telemetry: {
    isEnabled: true,
    metadata: {
      "tcc.runId": runId, // Pass the run ID in metadata
    },
  },
});

// Return the runId to your client
return { text, runId };
```

### Step 2: Submit feedback from your client

Store the `runId` on your client, then when the user provides feedback, submit it using the `submitFeedback` function.

Both `score` and `text` are optional individually, but each request must include at least one of them:

`score` is the thumbs rating. Use only `"thumbs_up"` or `"thumbs_down"`.

`text` is written feedback from your user, up to 2000 characters.

```typescript feedback-route.ts theme={null}
import { submitFeedback } from "@contextcompany/otel";

// Submit score and/or text feedback:
await submitFeedback({
  runId: runId, // The run ID from your agent execution
  score: "thumbs_up", // Optional thumbs rating: "thumbs_up" or "thumbs_down"
  text: "This was a helpful response!", // Optional written user feedback, up to 2000 characters
});
```

Agent runs with feedback can be filtered in the dashboard using the feedback filter.

## Tracking agent sessions

[Agent sessions](/concepts#agent-sessions) represent multiple agent runs that are grouped together. The most common use case is tracking entire conversations between a human user and an AI agent in chatbot interfaces.

Agent sessions can be tracked by setting a `tcc.sessionId` key under metadata.

```typescript route.ts theme={null}
import { generateText } from "ai";

const { text } = await generateText({
  // ...
  experimental_telemetry: {
    isEnabled: true,
    metadata: {
      // use tcc.sessionId to track agent sessions
      "tcc.sessionId": "some-session-id",
    },
  },
});
```

The value of `tcc.sessionId` should be a unique identifier for the agent session. This can be any string, but it's generally recommended to use a UUID.

Agent sessions are automatically indexed and can be filtered directly in the dashboard.

## Marking runs as conversational

A [conversational run](/concepts#conversational-runs) is an agent run that was initiated by a user. Marking a run as conversational tells The Context Company that this run involves direct user interaction.

This is important because conversational runs are the only runs monitored for user insights, such as user confusion, frustration, or any other custom insights you want to track. Runs that are not marked as conversational (e.g. background jobs, cron tasks, or internal automations) are excluded from user insight analysis.

Mark a run as conversational by setting `tcc.conversational` to `"true"` in metadata:

```typescript route.ts theme={null}
import { generateText } from "ai";

const { text } = await generateText({
  // ...
  experimental_telemetry: {
    isEnabled: true,
    metadata: {
      "tcc.conversational": "true",
    },
  },
});
```

## Identifying the agent

If your product ships more than one named agent, set the reserved `tcc.agent` metadata key to scope the run to a specific [agent](/concepts#agents). The dashboard's top-level agent selector, per-agent patterns and recaps, and the `agent` filter on the [REST API](/access-data/api) and [MCP tools](/access-data/mcp) all read from this key.

```typescript route.ts theme={null}
import { generateText } from "ai";

const { text } = await generateText({
  // ...
  experimental_telemetry: {
    isEnabled: true,
    metadata: {
      "tcc.agent": "support-agent",
    },
  },
});
```

<Note>
  Agent names that collide with reserved dashboard routes (for example `runs`, `sessions`, `patterns`, `recaps`, `overview`, `search`, `failures`, `feedback`, `tools`, `topics`, `views`, `settings`, `mcp-and-api`) are dropped.
</Note>

## Identifying users and organizations

Attach the end user and their organization to a run as first-class identity using the reserved `tcc.userId`, `tcc.userName`, `tcc.orgId`, and `tcc.orgName` metadata keys. This is **not the same** as adding a `userId` field to custom metadata — these keys promote user and org identity to dedicated dashboard filters and unlock native user/org search, per-user views, and per-org analytics. See [User and organization identity](/concepts#user-and-organization-identity) for the full concept.

Set these whenever you have a stable identifier for the end user or their organization in your product.

```typescript route.ts theme={null}
import { generateText } from "ai";

const { text } = await generateText({
  // ...
  experimental_telemetry: {
    isEnabled: true,
    metadata: {
      "tcc.userId": "user-123",
      "tcc.userName": "Jane Doe",
      "tcc.orgId": "org-456",
      "tcc.orgName": "Acme Inc.",
    },
  },
});
```

<Note>
  `tcc.userName` and `tcc.orgName` require the corresponding ID (`tcc.userId` / `tcc.orgId`) to also be set. Names without IDs are dropped.
</Note>

## Debug mode

You can enable debug mode, which will log any spans that are created and exported.

<CodeGroup>
  ```typescript Next.js theme={null}
  import { registerOTelTCC } from "@contextcompany/otel/nextjs";

  export function register() {
    if (process.env.NEXT_RUNTIME === "nodejs") {
      registerOTelTCC({ debug: true });
    }
  }
  ```

  ```typescript Node.js theme={null}
  import { TCCSpanProcessor } from "@contextcompany/otel";
  import { NodeSDK } from "@opentelemetry/sdk-node";

  export const tcc = new NodeSDK({
    spanProcessors: [new TCCSpanProcessor({ debug: true })],
  });
  ```
</CodeGroup>

## Examples

See our [examples repository](https://github.com/The-Context-Company/examples) for more detailed usage examples.

Examples currently include:

* [Express (ESM)](https://github.com/The-Context-Company/examples/tree/main/express/esm)
* [Express (CJS)](https://github.com/The-Context-Company/examples/tree/main/express/cjs)

## Local mode

Local mode allows you to run The Context Company in a local-first mode. This is [100% open-source](https://github.com/The-Context-Company/observatory) and requires **no account or API key**.

We also offer a [free, cloud development environment](/environments#development-cloud-hosted) for all users.

<img src="https://github.com/The-Context-Company/observatory/raw/main/.github/assets/local.gif" alt="Local mode demonstration" />

### Setup

#### Step 1: Install dependencies

<CodeGroup>
  ```Title pnpm theme={null}
  pnpm add @contextcompany/otel @vercel/otel @opentelemetry/api
  ```

  ```Title npm theme={null}
  npm i @contextcompany/otel @vercel/otel @opentelemetry/api
  ```

  ```Title bun theme={null}
  bun add @contextcompany/otel @vercel/otel @opentelemetry/api
  ```
</CodeGroup>

#### Step 2: Add instrumentation to Next.js

If you haven't already, add an `instrumentation.[js|ts]` file in the **root directory** of your project (or inside the `src` folder if you're using one). Call the `registerOTelTCC` function to instrument your AI SDK calls.

See the [Next.js Instrumentation guide](https://nextjs.org/docs/app/guides/instrumentation) for more information on instrumenting your Next.js application.

```typescript instrumentation.ts theme={null}
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    const { registerOTelTCC } = await import("@contextcompany/otel/nextjs");
    registerOTelTCC({ local: true });
  }
}
```

#### Step 3: Add widget to layout

Add the Local Mode widget to the root layout of your Next.js application.

```tsx app/layout.tsx theme={null}
import Script from "next/script";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        {/* add TCC script: */}
        <Script
          crossOrigin="anonymous"
          src="//unpkg.com/@contextcompany/widget/dist/auto.global.js"
        />
      </head>
      <body>{children}</body>
    </html>
  )
}
```

#### Step 4: Enable telemetry for AI SDK calls

As of AI SDK v5, telemetry is experimental and requires the `experimental_telemetry` flag to be set to `true`. Ensure you set this flag to `true` for all AI SDK calls.

<CodeGroup>
  ```typescript generateText theme={null}
  import { generateText } from "ai";

  const result = generateText({
    // ...
    experimental_telemetry: { isEnabled: true }, // required
  });
  ```

  ```typescript streamText theme={null}
  import { streamText } from "ai";

  const result = streamText({
    // ...
    experimental_telemetry: { isEnabled: true }, // required
  });
  ```
</CodeGroup>

### Anonymous telemetry

By default, The Context Company collects limited anonymous usage data when running local mode. This helps us understand how developers use the tool and guides us in improving it.

**No sensitive or personally identifiable information is ever collected.** You can view exactly which events and values are tracked [here](https://github.com/The-Context-Company/observatory/blob/main/packages/otel/src/nextjs/telemetry/events.ts).

To disable anonymous telemetry, set the `TCC_DISABLE_ANONYMOUS_TELEMETRY` environment variable to `true` in your Next.js project.
