# Using Transformers.js with the Vercel AI SDK
[Vercel AI SDK](https://ai-sdk.dev/) is a popular toolkit for building AI-powered applications. With [`@browser-ai/transformers-js`](https://www.browser-ai.dev/docs/ai-sdk-v6/transformers-js), you can use Transformers.js as a model provider for the AI SDK, enabling in-browser (and server-side) inference with a clean, declarative API.
This guide covers the core concepts and API patterns. For a full step-by-step project walkthrough, see the [Building a Next.js AI Chatbot](../tutorials/next-ai-sdk) tutorial.
## Why use the Vercel AI SDK with Transformers.js?
The `@browser-ai/transformers-js` provider builds on top of `@huggingface/transformers` to give you a standard AI SDK interface — handling Web Worker setup, message passing, progress tracking, streaming, interrupt handling, and state management, so you can use the same `streamText`, `generateText`, and `useChat` APIs you'd use with any other AI SDK provider.
Read more about this [here](https://www.browser-ai.dev/docs/ai-sdk-v6/transformers-js/why).
## Installation
```bash
npm install @browser-ai/transformers-js @huggingface/transformers ai @ai-sdk/react
```
| @browser-ai/transformers-js | AI SDK | Notes |
|---|---|---|
| v2.0.0+ | v6.x | Current stable |
| v1.0.0 | v5.x | Legacy |
## Text generation
### Streaming text
```js
import { streamText } from "ai";
import { transformersJS } from "@browser-ai/transformers-js";
const result = streamText({
model: transformersJS("HuggingFaceTB/SmolLM2-360M-Instruct"),
prompt: "Invent a new holiday and describe its traditions.",
});
for await (const textPart of result.textStream) {
console.log(textPart);
}
```
### Non-streaming text
```js
import { generateText } from "ai";
import { transformersJS } from "@browser-ai/transformers-js";
const result = await generateText({
model: transformersJS("HuggingFaceTB/SmolLM2-360M-Instruct"),
prompt: "Invent a new holiday and describe its traditions.",
});
console.log(result.text);
```
## Text embeddings
```js
import { embed, embedMany } from "ai";
import { transformersJS } from "@browser-ai/transformers-js";
// Single embedding
const { embedding } = await embed({
model: transformersJS.embedding("Supabase/gte-small"),
value: "Hello, world!",
});
// Multiple embeddings
const { embeddings } = await embedMany({
model: transformersJS.embedding("Supabase/gte-small"),
values: ["Hello", "World", "AI"],
});
```
## Audio transcription
```js
import { experimental_transcribe as transcribe } from "ai";
import { transformersJS } from "@browser-ai/transformers-js";
const transcript = await transcribe({
model: transformersJS.transcription("Xenova/whisper-base"),
audio: audioFile,
});
console.log(transcript.text);
console.log(transcript.segments); // segments with timestamps
```
## Vision models
```js
import { streamText } from "ai";
import { transformersJS } from "@browser-ai/transformers-js";
const result = streamText({
model: transformersJS("HuggingFaceTB/SmolVLM-256M-Instruct", {
isVisionModel: true,
device: "webgpu",
}),
messages: [
{
role: "user",
content: [
{ type: "text", text: "Describe this image" },
{ type: "image", image: someImageBlobOrUrl },
],
},
],
});
for await (const chunk of result.textStream) {
console.log(chunk);
}
```
## Web Worker offloading
For better performance, run model inference off the main thread with a Web Worker.
**1. Create `worker.ts`:**
```typescript
import { TransformersJSWorkerHandler } from "@browser-ai/transformers-js";
const handler = new TransformersJSWorkerHandler();
self.onmessage = (msg: MessageEvent) => {
handler.onmessage(msg);
};
```
**2. Pass the worker when creating the model:**
```js
import { streamText } from "ai";
import { transformersJS } from "@browser-ai/transformers-js";
const model = transformersJS("HuggingFaceTB/SmolLM2-360M-Instruct", {
device: "webgpu",
worker: new Worker(new URL("./worker.ts", import.meta.url), {
type: "module",
}),
});
const result = streamText({
model,
messages: [{ role: "user", content: "Hello!" }],
});
```
## Download progress tracking
Models are downloaded on first use. Track progress to provide a better UX:
```js
import { streamText } from "ai";
import { transformersJS } from "@browser-ai/transformers-js";
const model = transformersJS("HuggingFaceTB/SmolLM2-360M-Instruct");
const availability = await model.availability();
if (availability === "unavailable") {
console.log("Browser doesn't support Transformers.js");
} else if (availability === "downloadable") {
await model.createSessionWithProgress(({ progress }) => {
console.log(`Download progress: ${Math.round(progress * 100)}%`);
});
}
// Model is ready
const result = streamText({ model, prompt: "Hello!" });
```
## Tool calling
For best tool calling results, use reasoning models like Qwen3 which handle multi-step reasoning well.
```js
import { streamText, tool, stepCountIs } from "ai";
import { transformersJS } from "@browser-ai/transformers-js";
import { z } from "zod";
const result = await streamText({
model: transformersJS("onnx-community/Qwen3-0.6B-ONNX"),
messages: [{ role: "user", content: "What's the weather in San Francisco?" }],
tools: {
weather: tool({
description: "Get the weather in a location",
inputSchema: z.object({
location: z.string().describe("The location to get the weather for"),
}),
execute: async ({ location }) => ({
location,
temperature: 72 + Math.floor(Math.random() * 21) - 10,
}),
}),
},
stopWhen: stepCountIs(5),
});
```
Tool calling also supports [tool execution approval (`needsApproval`)](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling#tool-execution-approval) for human-in-the-loop workflows.
## `useChat` with custom transport
When using the `useChat` hook, you create a [custom transport](https://ai-sdk.dev/docs/ai-sdk-ui/transport) to handle client-side inference. Here's a minimal example:
```typescript
import {
ChatTransport, UIMessageChunk, streamText,
convertToModelMessages, ChatRequestOptions,
} from "ai";
import {
TransformersJSLanguageModel,
TransformersUIMessage,
} from "@browser-ai/transformers-js";
export class TransformersChatTransport
implements ChatTransport
{
constructor(private readonly model: TransformersJSLanguageModel) {}
async sendMessages(
options: {
chatId: string;
messages: TransformersUIMessage[];
abortSignal: AbortSignal | undefined;
} & {
trigger: "submit-message" | "submit-tool-result" | "regenerate-message";
messageId: string | undefined;
} & ChatRequestOptions,
): Promise> {
const prompt = await convertToModelMessages(options.messages);
const result = streamText({
model: this.model,
messages: prompt,
abortSignal: options.abortSignal,
});
return result.toUIMessageStream();
}
async reconnectToStream(): Promise | null> {
return null; // client-side AI doesn't support stream reconnection
}
}
```
Then use it in your component:
```typescript
import { useChat } from "@ai-sdk/react";
import { transformersJS, TransformersUIMessage } from "@browser-ai/transformers-js";
const model = transformersJS("HuggingFaceTB/SmolLM2-360M-Instruct", {
device: "webgpu",
worker: new Worker(new URL("./worker.ts", import.meta.url), { type: "module" }),
});
const { sendMessage, messages, stop } = useChat({
transport: new TransformersChatTransport(model),
});
```
## Browser compatibility fallback
If the device doesn't support in-browser inference, you can fall back to a server-side model:
```typescript
import {
transformersJS, TransformersUIMessage,
doesBrowserSupportTransformersJS,
} from "@browser-ai/transformers-js";
const { sendMessage, messages, stop } = useChat({
transport: doesBrowserSupportTransformersJS()
? new TransformersChatTransport(model)
: new DefaultChatTransport({ api: "/api/chat" }),
});
```
## Further reading
- [Building a Next.js AI Chatbot](../tutorials/next-ai-sdk) — a step-by-step tutorial building a full chatbot with tool calling
- [`@browser-ai/transformers-js` documentation](https://www.browser-ai.dev/docs/ai-sdk-v6/transformers-js)
- [Vercel AI SDK documentation](https://ai-sdk.dev/)