如何在 Vercel AI SDK 中接入 transformers.js 作为模型 provider 并实现工具调用【免费下载链接】transformers.jsState-of-the-art Machine Learning for the web. Run Transformers directly in your browser, with no need for a server!项目地址: https://gitcode.com/GitHub_Trending/tr/transformers.js如果你的目标是让 Vercel AI SDK 直接以 Transformers.jshuggingface/transformers作为模型 provider在浏览器里跑推理并支持工具调用tool calling那么整条路径是通过browser-ai/transformers-js这个 provider 包完成的它把 Web Worker 搭建、消息传递、进度跟踪、流式输出、中断处理都封装好了让你继续使用 AI SDK 标准的streamText、generateText、useChat接口不需要自己搭模型服务。本文的操作依据是仓库中的 Vercel AI SDK 集成文档 与 Next.js 聊天应用教程。前提条件与版本对应文档给出的环境要求Node.js 18npm 9支持 WebGPU 的浏览器Chrome 113、Edge 113或开启对应 flags 的 Firefox/Safariprovider 包与 AI SDK 的版本对应关系browser-ai/transformers-jsAI SDK说明v2.0.0v6.xCurrent stablev1.0.0v5.xLegacy最短路径streamText tools 直接实现工具调用在 Next.js 页面之外任意 Node 或浏览器环境接入 provider 并实现工具调用的最小代码如下来自集成文档的 Tool calling 一节npm install browser-ai/transformers-js huggingface/transformers ai ai-sdk/reactimport { 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: Whats 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), });几个要点模型名是一个 ONNX 格式的 Hugging Face 模型 ID例如onnx-community/Qwen3-0.6B-ONNX。文档建议在工具调用场景使用 Qwen3 这类擅长多步推理的模型。tools中每个工具用tool()定义description给模型看inputSchema用 Zod 描述入参execute是真正的执行函数返回对象会作为工具结果回传给模型。stopWhen: stepCountIs(5)用于限制推理步数防止工具调用进入无限循环。模型在首次使用时才会被下载工具调用还支持needsApproval选项用于人工确认human-in-the-loop流程完整用法见下文教程中的getLocation工具。完整路径Next.js 聊天应用中的 provider 接入与工具调用下面按教程顺序走一遍可运行的完整工程。创建项目并安装依赖npx create-next-applatest next-ai-chatbot cd next-ai-chatbotnpm install ai ai-sdk/react browser-ai/transformers-js huggingface/transformers zodzod是工具入参 schema 所必需的安装命令里不能省。配置 Next.js 排除 Node.js 端依赖Transformers.js 底层使用 ONNX Runtime同时覆盖浏览器和 Node.js 两种运行环境。既然推理只跑在浏览器里就需要告诉 Next.js 在打包时排除 Node.js 专属包。更新next.config.tsimport type { NextConfig } from next; const nextConfig: NextConfig { output: export, // optional: export as a static site turbopack: {}, webpack: (config) { config.resolve.alias { ...config.resolve.alias, sharp$: false, onnxruntime-node$: false, }; return config; }, }; export default nextConfig;其中output: export是可选的导出为静态站点而sharp$、onnxruntime-node$两个 alias 是浏览器推理必需的排除项。创建 Web Worker 承接推理在主线程跑模型推理会阻塞 UI。browser-ai/transformers-js提供了现成的 worker handler创建src/app/worker.tsimport { TransformersJSWorkerHandler } from browser-ai/transformers-js; const handler new TransformersJSWorkerHandler(); self.onmessage (msg: MessageEvent) { handler.onmessage(msg); };handler 会接管模型加载、推理、流式输出以及与主线程的通信。定义模型配置创建src/app/models.ts声明可用的 ONNX 模型。文档示例中给出两个配置import { WorkerLoadOptions } from browser-ai/transformers-js; export interface ModelConfig extends OmitWorkerLoadOptions, modelId { id: string; name: string; supportsWorker?: boolean; } export const MODELS: ModelConfig[] [ { id: onnx-community/Qwen3-0.6B-ONNX, name: Qwen3 0.6B, device: webgpu, dtype: q4f16, supportsWorker: true, }, { id: onnx-community/granite-4.0-350m-ONNX-web, name: Granite 4.0 350M, device: webgpu, dtype: fp16, supportsWorker: true, }, ];device: webgpu表示用 WebGPU 加速。dtype是数据类型/精度设置两个示例分别使用q4f16与fp16按模型而定不要互换。supportsWorker控制模型是否在 Web Worker 中加载以获得更好性能。定义工具创建src/app/tools.ts每个工具用 Zod 做入参校验import { tool } from ai; import z from zod; export const createTools () ({ getCurrentTime: tool({ description: Get the current date and time., inputSchema: z.object({}), execute: async () { const now new Date(); return { timestamp: now.toISOString(), date: now.toLocaleDateString(en-US, { weekday: long, year: numeric, month: long, day: numeric, }), time: now.toLocaleTimeString(en-US, { hour: 2-digit, minute: 2-digit, second: 2-digit, hour12: true, }), timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, }; }, }), randomNumber: tool({ description: Generate a random integer between min and max (inclusive)., inputSchema: z.object({ min: z.number().describe(The minimum value (inclusive)), max: z.number().describe(The maximum value (inclusive)), }), execute: async ({ min, max }) { return Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min) 1)) Math.ceil(min); }, }), getLocation: tool({ description: Get the users current geographic location., inputSchema: z.object({}), needsApproval: true, // requires user confirmation before executing execute: async () { return new Promise((resolve, reject) { navigator.geolocation.getCurrentPosition( (pos) resolve({ latitude: pos.coords.latitude, longitude: pos.coords.longitude, }), (err) reject(err.message), ); }); }, }), });getLocation设置了needsApproval: trueAI SDK 会在真正执行该工具前暂停等待用户批准或拒绝。实现自定义 ChatTransportuseChat需要一个 transport 来处理与模型的通信浏览器端推理要自己实现ChatTransport。创建src/app/chat-transport.tsimport { ChatTransport, UIMessageChunk, streamText, convertToModelMessages, ChatRequestOptions, createUIMessageStream, stepCountIs, } from ai; import { TransformersJSLanguageModel, TransformersUIMessage, transformersJS, } from browser-ai/transformers-js; import { MODELS } from ./models; import { createTools } from ./tools; export class TransformersChatTransport implements ChatTransportTransformersUIMessage { private model: TransformersJSLanguageModel; private tools: ReturnTypetypeof createTools; constructor() { const config MODELS[0]; this.model transformersJS(config.id, { device: config.device, dtype: config.dtype, ...(config.supportsWorker ? { worker: new Worker(new URL(./worker.ts, import.meta.url), { type: module, }), } : {}), }); this.tools createTools(); } async sendMessages( options: { chatId: string; messages: TransformersUIMessage[]; abortSignal: AbortSignal | undefined; } { trigger: submit-message | submit-tool-result | regenerate-message; messageId: string | undefined; } ChatRequestOptions, ): PromiseReadableStreamUIMessageChunk { const { messages, abortSignal } options; const prompt await convertToModelMessages(messages); return createUIMessageStreamTransformersUIMessage({ execute: async ({ writer }) { // Track download progress if the model hasnt been downloaded yet let downloadProgressId: string | undefined; const availability await this.model.availability(); if (availability ! available) { await this.model.createSessionWithProgress( (progress: number) { const percent Math.round(progress * 100); if (progress 1) { if (downloadProgressId) { writer.write({ type: data-modelDownloadProgress, id: downloadProgressId, data: { status: complete, progress: 100, message: Model ready!, }, }); } return; } if (!downloadProgressId) { downloadProgressId download-${Date.now()}; } writer.write({ type: data-modelDownloadProgress, id: downloadProgressId, data: { status: downloading, progress: percent, message: Downloading model... ${percent}%, }, }); }, ); } const result streamText({ model: this.model, tools: this.tools, stopWhen: stepCountIs(5), messages: prompt, abortSignal, }); writer.merge(result.toUIMessageStream({ sendStart: false })); }, }); } async reconnectToStream(): PromiseReadableStreamUIMessageChunk | null { return null; } }transport 的四个关键职责文档原样归纳Availability check推理前用model.availability()判断模型是否已下载。Progress streaming把下载进度作为自定义 data partdata-modelDownloadProgress写给 UI 渲染进度条。Tool support把tools传给streamText()模型才能调用工具。Step limitingstopWhen: stepCountIs(5)防止工具调用无限循环。reconnectToStream()返回null是有意为之——文档说明客户端 AI 不支持流重连client-side AI doesnt support stream reconnection。组装聊天页面创建src/app/page.tsx用useChat接上 transport并处理工具审批交互use client; import { useState } from react; import { useChat } from ai-sdk/react; import { TransformersUIMessage } from browser-ai/transformers-js; import { lastAssistantMessageIsCompleteWithApprovalResponses } from ai; import { TransformersChatTransport } from ./chat-transport; export default function ChatPage() { const [input, setInput] useState(); const { messages, sendMessage, status, stop, addToolApprovalResponse, } useChatTransformersUIMessage({ transport: new TransformersChatTransport(), experimental_throttle: 75, // Automatically resumes after tool approval responses are submitted sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses, }); const handleSubmit (e: React.FormEvent) { e.preventDefault(); if (input.trim() status ready) { sendMessage({ text: input }); setInput(); } }; return ( div style{{ maxWidth: 600, margin: 0 auto, padding: 24 }} h1AI Chatbot/h1 div {messages.map((message) ( div key{message.id} style{{ marginBottom: 16 }} strong{message.role user ? You : Assistant}:/strong {message.parts.map((part, i) { switch (part.type) { case text: return p key{i}{part.text}/p; case data-modelDownloadProgress: if (!part.data.message) return null; return ( div key{i} p{part.data.message}/p {part.data.status downloading ( progress value{part.data.progress} max{100} / )} /div ); default: // Handle tool parts if (part.type.startsWith(tool-) state in part) { if ( part.state approval-requested approval in part ) { return ( div key{i} style{{ border: 1px solid #ccc, padding: 8 }} pTool strong{part.type.replace(tool-, )}/strong wants to run./p button onClick{() addToolApprovalResponse({ id: part.approval!.id, approved: true }) } Approve /button button onClick{() addToolApprovalResponse({ id: part.approval!.id, approved: false, reason: User denied, }) } Deny /button /div ); } if (output in part part.output) { return ( pre key{i} style{{ background: #f5f5f5, padding: 8 }} {JSON.stringify(part.output, null, 2)} /pre ); } } return null; } })} /div ))} /div {status submitted pemThinking.../em/p} form onSubmit{handleSubmit} input value{input} onChange{(e) setInput(e.target.value)} placeholderAsk something... style{{ width: 100%, padding: 8 }} / div style{{ marginTop: 8 }} {status streaming ? ( button typebutton onClick{stop}Stop/button ) : ( button typesubmit disabled{!input.trim()}Send/button )} /div /form /div ); }页面按part.type区分消息内容text是模型文本输出data-modelDownloadProgress是 transport 写下的下载进度tool-*是工具调用 part状态包括approval-requested、output-available等。sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses让useChat在用户批准/拒绝工具后自动恢复生成。运行与验证启动开发服务器npm run dev在浏览器打开终端显示的 URL。文档描述的验证方式第一次发送消息时模型会在浏览器中下载并缓存之后的访问直接加载缓存模型。依次尝试以下提示验证工具调用What time is it?—— 应触发getCurrentTime工具Generate a random number between 1 and 100—— 应触发randomNumber工具Where am I located?—— 会触发工具审批approval弹窗点击 Approve/Deny 后流程继续。如果浏览器不支持 WebGPU 而无法进行浏览器内推理可以用doesBrowserSupportTransformersJS()做兼容检查回退到服务端模型import { transformersJS, TransformersUIMessage, doesBrowserSupportTransformersJS, } from browser-ai/transformers-js; const { sendMessage, messages, stop } useChatTransformersUIMessage({ transport: doesBrowserSupportTransformersJS() ? new TransformersChatTransport(model) : new DefaultChatTransport({ api: /api/chat }), });限制与边界WebGPU 是硬前提Chrome 113、Edge 113或开启 flags 的 Firefox/Safari不支持时按上面回退到服务端路线provider 本身不能凭空工作。模型只在首次使用时下载UI 上是否展示进度取决于你实现createSessionWithProgress不实现则首次响应会包含一次不可见的下载等待。stopWhen: stepCountIs(5)在教程里是固定值用于防工具调用死循环如果你的工具链需要更多轮次需要自行调整该上限。客户端 transport 的reconnectToStream()固定返回null即断开后无法重连续流。工具调用效果与模型相关文档建议选 Qwen3 这类推理模型或为工具调用微调过的模型。下一步教程给出的后续方向在models.ts中增加更多模型并加一个模型选择器多模型实现的完整参考代码见仓库文档指向的 next-vercel-ai-sdk-v6-tool-calling 示例工程以及为不支持 WebGPU 的环境加上面doesBrowserSupportTransformersJS()的服务端回退。更多 provider 能力embeddings、vision、transcription可查阅 Vercel AI SDK 集成文档 中的对应章节。【免费下载链接】transformers.jsState-of-the-art Machine Learning for the web. Run Transformers directly in your browser, with no need for a server!项目地址: https://gitcode.com/GitHub_Trending/tr/transformers.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考