> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://humanloop.com/docs/v5/integrations/vercel-ai-sdk/agent-nextjs-observability/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://humanloop.com/_mcp/server. > Add logging to your Vercel AI SDK application with Humanloop. Add Humanloop observability to a chat agent by calling the tool built with Vercel AI SDK. It builds on the AI SDK's [Next.js example](https://sdk.vercel.ai/docs/getting-started/nextjs-app-router). > **Tip** > > Looking for Node.js? See the [guide here](/docs/v5/integrations/vercel-ai-sdk/agent-nodejs-observability). ## Prerequisites #### Account setup Create a Humanloop Account 1. [Create an account](https://app.humanloop.com/signup) or [log in](https://app.humanloop.com/login) to Humanloop 2. Get a Humanloop API key from [Organization Settings](https://app.humanloop.com/account/api-keys). Add an OpenAI API Key If you're the first person in your organization, you'll need to add an API key to a model provider. 1. Go to OpenAI and [grab an API key](https://platform.openai.com/api-keys). 2. In Humanloop [Organization Settings](https://app.humanloop.com/account/api-keys) set up OpenAI as a model provider. > **Info** > > Using the Prompt Editor will use your OpenAI credits in the same way that the > OpenAI playground does. Keep your API keys for Humanloop and the model > providers private. #### Create project Create a new Next.js project. This command will create a new directory named `my-ai-app` and set up a basic Next.js application inside of it. **`npm`** ```bash title="npm" npx create-next-app@latest my-ai-app ``` **`pnpm`** ```bash title="pnpm" pnpm create next-app@latest my-ai-app ``` **`yarn`** ```bash title="yarn" yarn create next-app@latest my-ai-app ``` Navigate to the newly created directory: ```bash cd my-ai-app ``` #### Install dependencies To follow this guide, you'll also need Node.js 18+ installed on your machine. Install `ai`, '@ai-sdk/react', and `@ai-sdk/openai`, along with other necessary dependencies. **`npm`** ```bash title="npm" npm install ai @ai-sdk/react @ai-sdk/openai zod npm install -D @types/node tsx typescript ``` **`pnpm`** ```bash title="pnpm" pnpm add ai @ai-sdk/react @ai-sdk/openai zod pnpm add -D @types/node tsx typescript ``` **`yarn`** ```bash title="yarn" yarn add ai @ai-sdk/react @ai-sdk/openai zod yarn add -D @types/node tsx typescript ``` #### Configure API keys Add a `.env.local` file to your project with your Humanloop and OpenAI API keys. ```bash touch .env.local ``` #### .env.local ``` HUMANLOOP_API_KEY= OPENAI_API_KEY= ``` ## Full code If you'd like to immediately try out the full example, you can copy and paste the code below and run the app. #### Setup **`npm`** ```bash title="npm" wordWrap npm install ai @ai-sdk/react @ai-sdk/openai zod npm install -D @types/node tsx typescript npm install @vercel/otel @opentelemetry/api @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation ``` **`pnpm`** ```bash title="pnpm" wordWrap pnpm add ai @ai-sdk/react @ai-sdk/openai zod pnpm add -D @types/node tsx typescript pnpm add @vercel/otel @opentelemetry/api @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation ``` **`yarn`** ```bash title="yarn" wordWrap yarn add ai @ai-sdk/react @ai-sdk/openai zod yarn add -D @types/node tsx typescript yarn add @vercel/otel @opentelemetry/api @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation ``` **`.env.local`** ```plaintext title=".env.local" wordWrap highlight={4-6} HUMANLOOP_API_KEY= OPENAI_API_KEY= OTEL_EXPORTER_OTLP_ENDPOINT=https://api.humanloop.com/v5/import/otel OTEL_EXPORTER_OTLP_PROTOCOL=http/json OTEL_EXPORTER_OTLP_HEADERS="X-API-KEY=" # Humanloop API key ``` **`instrumentation.ts`** ```typescript title="instrumentation.ts" import { registerOTel } from "@vercel/otel"; export function register() { registerOTel({ serviceName: "humanloop-ai-sdk-agent", }); } ``` #### Backend Route Handler **`app/api/chat/route.ts`** ```typescript title="app/api/chat/route.ts" maxLines=20 import { openai } from "@ai-sdk/openai"; import { streamText, tool } from "ai"; import { z } from "zod"; export const maxDuration = 30; export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: openai("gpt-4o"), messages, experimental_telemetry: { isEnabled: true, metadata: { "humanloop.directoryPath": "path/to/directory", }, }, tools: { weather: tool({ description: "Get the weather in a location (fahrenheit)", parameters: z.object({ location: z.string().describe("The location to get the weather for"), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return result.toDataStreamResponse(); } ``` #### UI **`app/page.tsx`** ```typescript title="app/page.tsx" maxLines=50 'use client'; import { useChat } from '@ai-sdk/react'; export default function Chat() { const { messages, input, handleInputChange, handleSubmit } = useChat({ maxSteps: 5, }); return (
{messages.map(m => (
{m.role === 'user' ? 'User: ' : 'AI: '} {m.parts.map((p, idx) => p.type === "tool-invocation" || p.type === "source" ? (
{JSON.stringify(p, null, 2)}
) : p.type === "text" ? (

{p.text}

) : (

{p.reasoning}

) )}
))}
); } ``` **`npm`** ```bash title="npm" npm run dev ``` **`pnpm`** ```bash title="pnpm" pnpm run dev ``` **`yarn`** ```bash title="yarn" yarn dev ``` ## Create the agent ### Create a Route Handler We start with a backend route that handles a chat request and streams back a response from a model. This model can call a function to get the weather in a given location. **`app/api/chat/route.ts`** ```typescript title="app/api/chat/route.ts" maxLines=50 import { openai } from "@ai-sdk/openai"; import { streamText, tool } from "ai"; import { z } from "zod"; export const maxDuration = 30; export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: openai("gpt-4o"), messages, tools: { weather: tool({ description: "Get the weather in a location (fahrenheit)", parameters: z.object({ location: z.string().describe("The location to get the weather for"), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return result.toDataStreamResponse(); } ``` ### Wire up the UI Now that you have a Route Handler that can query an LLM, it's time to setup your frontend. The AI SDK's [UI](https://sdk.vercel.ai/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one hook, `useChat`. Update your root page to show a chat interface and provide a user message input. > **Note** > > The `maxSteps` prop allows the model to take multiple "steps" for a given > generation, using tool calls to refine its response. **`app/page.tsx`** ```typescript title="app/page.tsx" maxLines=50 'use client'; import { useChat } from '@ai-sdk/react'; export default function Chat() { const { messages, input, handleInputChange, handleSubmit } = useChat({ maxSteps: 5, }); return (
{messages.map(m => (
{m.role === 'user' ? 'User: ' : 'AI: '} {m.parts.map((p, idx) => p.type === "tool-invocation" || p.type === "source" ? (
{JSON.stringify(p, null, 2)}
) : p.type === "text" ? (

{p.text}

) : (

{p.reasoning}

) )}
))}
); } ``` ### Run the agent Start your app and give the agent a try. **`npm`** ```bash title="npm" npm run dev ``` **`pnpm`** ```bash title="pnpm" pnpm run dev ``` **`yarn`** ```bash title="yarn" yarn dev ``` ![](/docs/_fern-img/8bad44bcfad07c4da07d99c3022ed88b4171655d5438dacc79ea9d6890b6cad3.webp) ## Log to Humanloop The agent works and is capable of function calling. However, we rely on inputs and outputs to reason about the behavior. Humanloop logging allows you to observe the steps taken by the agent, which we will demonstrate below. We'll use Vercel AI SDK's built-in OpenTelemetry tracing to log to Humanloop. ### Set up OpenTelemetry Install dependencies. **`npm`** ```bash wordWrap title="npm" npm install @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation ``` **`pnpm`** ```bash wordWrap title="pnpm" pnpm add @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation ``` **`yarn`** ```bash wordWrap title="yarn" yarn add @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation ``` Create a file called `instrumentation.ts` in your root or /src directory and add the following code: **`instrumentation.ts`** ```typescript title="instrumentation.ts" import { registerOTel } from "@vercel/otel"; export function register() { registerOTel({ serviceName: "humanloop-ai-sdk-agent", }); } ``` Configure the [OpenTelemetry exporter](https://opentelemetry.io/docs/specs/otel/protocol/exporter/) to forward logs to Humanloop. **`.env.local`** ```plaintext highlight={4-6} title=".env.local" wordWrap HUMANLOOP_API_KEY= OPENAI_API_KEY= OTEL_EXPORTER_OTLP_ENDPOINT=https://api.humanloop.com/v5/import/otel OTEL_EXPORTER_OTLP_PROTOCOL=http/json OTEL_EXPORTER_OTLP_HEADERS="X-API-KEY=" # Humanloop API key ``` ### Trace AI SDK calls The telemetry metadata associates Logs with your Files on Humanloop. We will use a Humanloop [Prompt](/docs/explanation/prompts) to log LLM calls, and a Humanloop [Flow](/docs/explanation/flows) to group related generation calls into a trace. The `humanloop.directoryPath` specifies the path to a Directory where your Files and Logs will be located. **`app/api/chat/route.ts`** ```typescript title="app/api/chat/route.ts" highlight={13-19} maxLines=50 import { openai } from "@ai-sdk/openai"; import { streamText, tool } from "ai"; import { z } from "zod"; export const maxDuration = 30; export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: openai("gpt-4o"), messages, experimental_telemetry: { isEnabled: true, metadata: { "humanloop.directoryPath": "path/to/directory", }, }, tools: { weather: tool({ description: "Get the weather in a location (fahrenheit)", parameters: z.object({ location: z.string().describe("The location to get the weather for"), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return result.toDataStreamResponse(); } ``` Restart your app, and have a conversation with the agent. ### Explore logs on Humanloop Now you can explore your logs on the [Humanloop platform](https://app.humanloop.com), and see the steps taken by the agent during your conversation. ![](/docs/_fern-img/d273503c2ee913947a83dbf84bb661674b4b8445b750e8af0777b31037638859.webp) ## Debugging If you run into any issues, add OpenTelemetry debug logging to ensure the Exporter is working correctly. **`npm`** ```bash title="npm" npm install @opentelemetry/api ``` **`pnpm`** ```bash title="pnpm" pnpm add @opentelemetry/api ``` **`yarn`** ```bash title="yarn" yarn add @opentelemetry/api ``` **`instrumentation.ts`** ```typescript title="instrumentation.ts" import { registerOTel } from "@vercel/otel"; import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api"; diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); export function register() { registerOTel({ serviceName: "humanloop-ai-sdk-agent", }); } ``` ## Next steps Logging is the first step to observing your AI product. Read these guides to learn more about evals on Humanloop: * Add [monitoring Evaluators](/docs/v5/guides/observability/monitoring) to evaluate Logs as they're made against a File. * See evals in action in our [tutorial on evaluating an agent](/docs/v5/tutorials/agent-evaluation). > Add logging to your Vercel AI SDK application with Humanloop.