Tracing Vercel AI SDK applications
The open-source Vercel AI SDK is a popular choice for building generative AI applications due to its ease of use and integrations with popular frameworks, such as Next.js. However, builders recognize that to reach production, they also need to incorporate observability into their applications. This cookbook will show you how to use Braintrust's native integration with the Vercel AI SDK for logging and tracing a generative AI application.
Getting started
To get started, make sure you have the following ready to go:
- A Braintrust account and API key
- A project in Braintrust
- An OpenAI API key
npminstalled
In this cookbook, we're going to use a simple chat application that gives you the temperature when you ask about the weather in a given city. The chatbot uses an OpenAI model, which calls one tool that gets the weather from open-meteo and another tool that converts the weather from Celsius to Fahrenheit.
Use npx to download the application locally:
We'll only edit a few files in this example application:
For the application to run successfully, you'll need to rename the .env.local.example file to .env.local in the root of the project and add the following environment variables:
Run the application, and make sure you can access it at http://localhost:3000. Feel free to test the application by asking it about the weather in Philadelphia.
It should look like this:

Tracing the application
Initializing a logger
To send logs to Braintrust, you'll need to initialize a logger by calling the initLogger function. This function takes an apiKey and a projectName as arguments. The apiKey is your Braintrust API key, and the projectName is the name of your project in Braintrust. For lines 1-11 in the app/(preview)/api/chat/route.ts file, uncomment the lines where instructed to load the necessary Braintrust functions and initialize the logger. Lines 1-11 should look like this:
Automatic tracing of models
The Braintrust SDK provides functions to "wrap" models, automatically logging inputs and outputs. When working with the Vercel AI SDK, you can use the wrapAISDKModel function, which provides a common interface for models initiated by the Vercel AI SDK (for example, @ai-sdk/openai).
The wrapAISDKModel function only traces the inputs and outputs of the model. It does not trace intermediary steps such as tool calls that may be invoked during the model's execution. Later in the cookbook, we will explore how to use wrapTraced to trace tool calls and nested functions.
The wrapAISDKModel must be used with a Vercel interface (not a model
interface directly from the OpenAI, Anthropic, or other first-party libraries
from model providers). If you are not using Vercel model interfaces (such as
@ai-sdk/openai) and using a model provider's first-party library, you can
wrap your model
clients
with wrapOpenAI or wrapAnthropic.
To correctly wrap the model client in our weather app example, your model client instantiation code should look like this after uncommenting the proper lines:
When we use the chatbot again, we see three logs appear in Braintrust: one log for the getWeather tool call, one log for the getFahrenheit tool call, and one call to form the final response. However, it'd probably be more useful to have all of these operations in the same log.

Creating spans (and sub-spans)
When tracing events, it's common practice to place child events within a single parent event. As an example, take grouping the three logs that we produced above into the same log record. You can do this using the traced function.
To create a parent span in our weather app, uncomment the traced function (don't forget to uncomment the final line of code that closes the function). You can also uncomment the onFinish argument, which will log the input and output of the streamText function to the parent span. Your POST route should look like this when finished:
After you uncomment those lines of code, you should see the following:

A couple of things happened in this step:
- We created a root span called "POST /api/chat" to group any subsequent logs into.
- We continued to create spans via the
wrapAISDKModelfunction. - We used the
onFinishargument of thestreamTextfunction to gather the input and output of the LLM and return it to the root span.
This looks good so far, but we also want to know about the different tool calls that the LLM is making as it works to form its response.
Tracing tool calls
The last thing that we need to adjust is adding our tool calls and functions to the trace. You can do this by encapsulating existing functions with wrapTraced, which will automatically capture the inputs and outputs of the functions. When using wrapTraced, the hierarchy of nested functions is preserved.
The following code in components/tools.ts has two main components:
- A
getFahrenheittool, which converts a Celsius temperature into Fahrenheit. It also nests thecheckFreezingfunction inside theconvertToFahrenheitfunction. - A
getWeathertool which takes a latitude and longitude as input and returns a Celsius temperature as output.
Uncomment the code where noted so that your tools.ts file looks like this:
After we finish uncommenting the correct lines, we see how the wrapTraced function enriches our trace with tool calls.

Take note of how the type argument in both traced and wrapTraced change the icon within the trace tree. Also, since checkFreezing was called by weatherFunction, the trace preserves the hierarchy.
Next steps
- Customize and extend traces to better optimize for your use case
- Read more about Brainstore, the database that powers the logging backend in Braintrust
