vue-llm: AI SDK для Vue и Nuxt — стриминговый чат, Generative UI и модели прямо в браузере
- пятница, 30 октября 2026 г. в 00:00:10
Если вы делали AI-фичу на Vue, то, скорее всего, сталкивались с одним и тем же: большинство готовых инструментов для LLM-интерфейсов написаны с прицелом на React, а во Vue их приходится адаптировать или писать стриминг, tool calling и состояние чата вручную. Я сделал vue-llm — open-source библиотеку, которая закрывает это для Vue 3, Nuxt и даже Vue 2.7.
Сразу ссылки, чтобы можно было пощупать:
Песочница без API-ключа (модель скачивается и работает прямо в браузере на WebGPU): https://vue-llm.js.org/ru/playground
Документация на русском: https://vue-llm.js.org/ru/
npm: npm i vue-llm
Ниже — что умеет библиотека и как это выглядит в коде.
Основа — композабл useChat. Он стримит ответ модели токен за токеном в реактивное состояние: сообщения, статус, ошибка, отмена, перегенерация.
<script setup lang="ts"> import { useChat } from 'vue-llm' import { createProxyProvider } from 'vue-llm/server' const { messages, input, handleSubmit, status, stop } = useChat({ provider: createProxyProvider('/api/chat'), model: 'gpt-4o', }) </script> <template> <div v-for="m in messages" :key="m.id"> <b>{{ m.role }}:</b> <template v-for="(part, i) in m.parts" :key="i"> <span v-if="part.type === 'text'">{{ part.text }}</span> </template> </div> <form @submit.prevent="handleSubmit"> <input v-model="input"> <button v-if="status === 'streaming'" type="button" @click="stop">Стоп</button> </form> </template>
Сообщения состоят из «частей» (parts): текст, рассуждения модели, вызовы инструментов, файлы, источники. Поэтому один и тот же шаблон показывает и обычный ответ, и reasoning у Claude или Qwen3, и вызов tool.
API-ключ при этом остаётся на сервере. Серверная часть — обычный Web-стандартный обработчик Request → Response, который работает в Nuxt/Nitro, Node, Bun, Deno и edge-рантаймах:
// server/api/chat.post.ts (Nuxt) import { createLLMHandler } from 'vue-llm/server' import { createAnthropic } from 'vue-llm/providers' const handler = createLLMHandler({ provider: createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! }), model: 'claude-sonnet-4-5', rateLimit: { maxRequests: 30, interval: 60_000 }, }) export default defineEventHandler(event => handler(toWebRequest(event)))
Провайдеры из коробки: OpenAI (и любой OpenAI-совместимый API), Anthropic, Google Gemini, Ollama. Если у вас уже есть свой бэкенд со стримингом, createSSEProvider подключает useChat к нему: вы описываете, как ваши SSE-события превращаются в куски ответа, и всё остальное работает как есть.
Самая интересная для меня часть. Когда модель вызывает инструмент, вместо текста можно показать ваш Vue-компонент. Аргументы стримятся в компонент по мере генерации, то есть карточка начинает заполняться ещё до того, как модель дописала вызов.
import { defineTool } from 'vue-llm' export const getWeather = defineTool<{ city: string }, Weather>({ name: 'get_weather', description: 'Погода в городе', parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] }, execute: ({ city }) => fetch(`/api/weather?city=${city}`).then(r => r.json()), })
<MessageParts v-for="m in messages" :key="m.id" :message="m" :components="{ get_weather: WeatherCard }" @tool-result="addToolResult" />
Инструмент с execute выполняется сам. Инструмент без execute ставит чат на паузу и ждёт ответа из интерфейса — так делается human-in-the-loop: модель предлагает забронировать столик, пользователь жмёт «Подтвердить» в карточке, addToolResult передаёт ответ, и разговор продолжается. Живое демо (работает без сети): https://vue-llm.js.org/ru/guide/generative-ui
Под капотом для этого пришлось написать парсер «недописанного» JSON: {"city":"Мос превращается в { city: 'Мос' }, а незаконченные ключи отбрасываются до тех пор, пока не допишутся.
createWebLLM запускает открытые модели (Llama 3.2, Qwen, SmolLM, Phi) на WebGPU через WebLLM. Никакого сервера и ключа, данные не покидают устройство.
import { CreateMLCEngine } from '@mlc-ai/web-llm' import { createWebLLM } from 'vue-llm/providers' const ai = createWebLLM({ createEngine: CreateMLCEngine, onProgress: ({ progress }) => console.log(`${Math.round(progress * 100)}%`), }) useChat({ provider: ai, model: 'Llama-3.2-1B-Instruct-q4f16_1-MLC' })
Это тот же useChat, что и с OpenAI, — меняется только провайдер. Можно, например, давать пользователям без WebGPU серверную модель, а остальным — локальную. Первая загрузка модели занимает от 400 МБ, дальше она берётся из кеша браузера за секунду-две.
Писать окно чата с нуля утомительно: автоскролл, который не дёргает пользователя, листающего вверх; Enter для отправки, который не срабатывает при наборе на китайском или японском; вставка картинок из буфера; drag-and-drop файлов. Поэтому библиотека устроена гибридно:
в npm лежит headless-логика: usePromptInput и useAutoScroll;
готовые компоненты копируются в ваш проект как в shadcn — одной командой, после чего код ваш:
npx shadcn-vue@latest add https://vue-llm.js.org/r/chat.json
Компоненты используют токены темы shadcn, поэтому сразу выглядят как остальной ваш интерфейс, включая тёмную тему.
vue-llm/devtools добавляет в Vue DevTools вкладку LLM: список всех useChat, токены, время до первого токена, скорость генерации, вызовы инструментов и таймлайн каждого запроса. Без плагина библиотека ничего не трассирует и не тратит на это ресурсы.
Модуль подключается одной командой:
npx nuxi module add vue-llm
Он делает auto-import композаблов и провайдеров, регистрирует компоненты и в dev-режиме сам подключает вкладку в DevTools. Проверено на Nuxt 3 и 4.
Хороший вопрос, и я сам с него начинал. vue-llm сделан Vue-first: Nuxt-модуль, Vue-компоненты для Generative UI, интеграция с Vue DevTools, провайдер для моделей в браузере, адаптер под существующий SSE-бэкенд и поддержка Vue 2.7 для композаблов (у многих легаси-админок до сих пор Vue 2). Зависимостей в рантайме нет, кроме самого Vue.
Библиотека под MIT, версия 1.3, покрыта тестами на Vue 3 и Vue 2.7. В планах — MCP-клиент (подключать MCP-серверы как инструменты), ответы с цитатами для RAG и голосовой ввод.
Буду рад обратной связи: что неудобно, чего не хватает, где документация непонятна. Issues и PR приветствуются, а если проект пригодится — поставьте звёздочку на GitHub, это правда помогает его продвигать.
Песочница: https://vue-llm.js.org/ru/playground
Документация: https://vue-llm.js.org/ru/