Svelte Quickstart

The AI SDK is a powerful Typescript library designed to help developers build AI-powered applications.

In this quickstart tutorial, you'll build a simple AI-chatbot with a streaming user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the SDK in your own projects.

If you are unfamiliar with the concepts of Prompt Engineering and HTTP Streaming, you can optionally read these documents first.

Prerequisites

To follow this quickstart, you'll need:

  • Node.js 18+ and pnpm installed on your local development machine.
  • An OpenAI API key.

If you haven't obtained your OpenAI API key, you can do so by signing up on the OpenAI website.

Set Up Your Application

This guide applies to SvelteKit versions 4 and below.

Start by creating a new SvelteKit application. This command will create a new directory named my-ai-app and set up a basic SvelteKit application inside it.

npx sv create my-ai-app

Navigate to the newly created directory:

cd my-ai-app

Install Dependencies

Install ai and @ai-sdk/openai, the AI SDK's OpenAI provider.

The AI SDK is designed to be a unified interface to interact with any large language model. This means that you can change model and providers with just one line of code! Learn more about available providers and building custom providers in the providers section.

pnpm
npm
yarn
pnpm add -D ai @ai-sdk/openai @ai-sdk/svelte zod

Make sure you are using ai version 3.1 or higher.

Configure OpenAI API Key

Create a .env.local file in your project root and add your OpenAI API Key. This key is used to authenticate your application with the OpenAI service.

touch .env.local

Edit the .env.local file:

.env.local
OPENAI_API_KEY=xxxxxxxxx

Replace xxxxxxxxx with your actual OpenAI API key.

Vite does not automatically load environment variables onto process.env, so you'll need to import OPENAI_API_KEY from $env/static/private in your code (see below).

Create an API route

Create a SvelteKit Endpoint, src/routes/api/chat/+server.ts and add the following code:

src/routes/api/chat/+server.ts
import { createOpenAI } from '@ai-sdk/openai';
import { streamText } from 'ai';
import { OPENAI_API_KEY } from '$env/static/private';
const openai = createOpenAI({
apiKey: OPENAI_API_KEY,
});
export async function POST({ request }) {
const { messages } = await request.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
});
return result.toDataStreamResponse();
}

If you see type errors with OPENAI_API_KEY or your POST function, run the dev server.

Let's take a look at what is happening in this code:

  1. Create an OpenAI provider instance with the createOpenAI function from the @ai-sdk/openai package.
  2. Define a POST request handler and extract messages from the body of the request. The messages variable contains a history of the conversation with you and the chatbot and will provide the chatbot with the necessary context to make the next generation.
  3. Call streamText, which is imported from the ai package. This function accepts a configuration object that contains a model provider (defined in step 1) and messages (defined in step 2). You can pass additional settings to further customise the model's behaviour.
  4. The streamText function returns a StreamTextResult. This result object contains the toDataStreamResponse function which converts the result to a streamed response object.
  5. Return the result to the client to stream the response.

Wire up the UI

Now that you have an API route that can query an LLM, it's time to set up your frontend. The AI SDK's UI package abstracts the complexity of a chat interface into one class, Chat. Its properties and API are largely the same as React's useChat.

Update your root page (src/routes/+page.svelte) with the following code to show a list of chat messages and provide a user message input:

src/routes/+page.svelte
<script>
import { Chat } from '@ai-sdk/svelte';
const chat = new Chat();
</script>
<main>
<ul>
{#each chat.messages as message}
<li>{message.role}: {message.content}</li>
{/each}
</ul>
<form onsubmit={chat.handleSubmit}>
<input bind:value={chat.input} />
<button type="submit">Send</button>
</form>
</main>

This page utilizes the Chat class, which will, by default, use the POST route handler you created earlier. The hook provides functions and state for handling user input and form submission. The Chat class provides multiple utility functions and state variables:

  • messages - the current chat messages (an array of objects with id, role, and content properties).
  • input - the current value of the user's input field.
  • handleSubmit - function to handle form submission.

Running Your Application

With that, you have built everything you need for your chatbot! To start your application, use the command:

pnpm run dev

Head to your browser and open http://localhost:5173. You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Svelte.

Enhance Your Chatbot with Tools

While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where tools come in.

Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response.

For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information.

Let's enhance your chatbot by adding a simple weather tool.

Update Your API Route

Modify your src/routes/api/chat/+server.ts file to include the new weather tool:

src/routes/api/chat/+server.ts
import { createOpenAI } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';
import { OPENAI_API_KEY } from '$env/static/private';
const openai = createOpenAI({
apiKey: OPENAI_API_KEY,
});
export async function POST({ request }) {
const { messages } = await request.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();
}

In this updated code:

  1. You import the tool function from the ai package and z from zod for schema validation.

  2. You define a tools object with a weather tool. This tool:

    • Has a description that helps the model understand when to use it.
    • Defines parameters using a Zod schema, specifying that it requires a location string to execute this tool. The model will attempt to extract this parameter from the context of the conversation. If it can't, it will ask the user for the missing information.
    • Defines an execute function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API.

Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary parameters. The execute function will then be automatically run, and you can access the results via toolInvocations that is available on the message object.

Try asking something like "What's the weather in New York?" and see how the model uses the new tool.

Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result in the toolInvocations key of the message object.

Update the UI

To display the tool invocations in your UI, update your src/routes/+page.svelte file:

src/routes/+page.svelte
<script>
import { Chat } from '@ai-sdk/svelte';
const chat = new Chat();
</script>
<main>
<ul>
{#each chat.messages as message}
<li>
{message.role}:
{#if message.toolInvocations}
<pre>{JSON.stringify(message.toolInvocations, null, 2)}</pre>
{:else}
{message.content}
{/if}
</li>
{/each}
</ul>
<form onsubmit={chat.handleSubmit}>
<input bind:value={chat.input} />
<button type="submit">Send</button>
</form>
</main>

With this change, you check each message for any tool calls (toolInvocations). These tool calls will be displayed as stringified JSON. Otherwise, you show the message content as before.

Now, when you ask about the weather, you'll see the tool invocation and its result displayed in your chat interface.

Enabling Multi-Step Tool Calls

You may have noticed that while the tool results are visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation.

To solve this, you can enable multi-step tool calls using the maxSteps option in your Chat class. This feature will automatically send tool results back to the model to trigger an additional generation. In this case, you want the model to answer your question using the results from the weather tool.

Update Your UI

Modify your src/routes/+page.svelte file to include the maxSteps option:

src/routes/+page.svelte
<script>
import { Chat } from '@ai-sdk/svelte';
const chat = new Chat({ maxSteps: 5 });
</script>
<!-- ... rest of your component code -->

Head back to the browser and ask about the weather in a location. You should now see the model using the weather tool results to answer your question.

By setting maxSteps to 5, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius.

Update Your API Route

Update your src/routes/api/chat/+server.ts file to add a new tool to convert the temperature from Fahrenheit to Celsius:

src/routes/api/chat/+server.ts
import { createOpenAI } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';
import { OPENAI_API_KEY } from '$env/static/private';
const openai = createOpenAI({
apiKey: OPENAI_API_KEY,
});
export async function POST({ request }) {
const { messages } = await request.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,
};
},
}),
convertFahrenheitToCelsius: tool({
description: 'Convert a temperature in fahrenheit to celsius',
parameters: z.object({
temperature: z
.number()
.describe('The temperature in fahrenheit to convert'),
}),
execute: async ({ temperature }) => {
const celsius = Math.round((temperature - 32) * (5 / 9));
return {
celsius,
};
},
}),
},
});
return result.toDataStreamResponse();
}

Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction:

  1. The model will call the weather tool for New York.
  2. You'll see the tool result displayed.
  3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius.
  4. The model will then use that information to provide a natural language response about the weather in New York.

This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful.

This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information.

How does @ai-sdk/svelte differ from @ai-sdk/react?

The surface-level difference is that Svelte uses classes to manage state, whereas React uses hooks, so useChat in React is Chat in Svelte. Other than that, there are a few things to keep in mind:

1. Arguments to classes aren't reactive by default

Unlike in React, where hooks are rerun any time their containing component is invalidated, code in the script block of a Svelte component is only run once when the component is created. This means that, if you want arguments to your class to be reactive, you need to make sure you pass a reference into the class, rather than a value:

<script>
import { Chat } from '@ai-sdk/svelte';
let { id } = $props();
// won't work; the class instance will be created once, `id` will be copied by value, and won't update when $props.id changes
let chat = new Chat({ id });
// will work; passes `id` by reference, so `Chat` always has the latest value
let chat = new Chat({
get id() {
return id;
},
});
</script>

Keep in mind that this normally doesn't matter; most parameters you'll pass into the Chat class are static (for example, you typically wouldn't expect your onError handler to change).

2. You can't destructure class properties

In vanilla JavaScript, destructuring class properties copies them by value and "disconnects" them from their class instance:

const classInstance = new Whatever();
classInstance.foo = 'bar';
const { foo } = classInstance;
classInstance.foo = 'baz';
console.log(foo); // 'bar'

The same is true of classes in Svelte:

<script>
import { Chat } from '@ai-sdk/svelte';
const chat = new Chat();
let { messages } = chat;
chat.append({ content: 'Hello, world!', role: 'user' }).then(() => {
console.log(messages); // []
console.log(chat.messages); // [{ content: 'Hello, world!', role: 'user' }] (plus some other stuff)
});
</script>

3. Instance synchronization requires context

In React, hook instances with the same id are synchronized -- so two instances of useChat will have the same messages, status, etc. if they have the same id. For most use cases, you probably don't need this behavior -- but if you do, you can create a context in your root layout file using createAIContext:

<script>
import { createAIContext } from '@ai-sdk/svelte';
let { children } = $props();
createAIContext();
// all hooks created after this or in components that are children of this component
// will have synchronized state
</script>
{@render children()}

Where to Next?

You've built an AI chatbot using the AI SDK! From here, you have several paths to explore: