0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vercel AI SDK でローカル LLM チャットを作る

0
Posted at

Vercel AI SDK は、LLM とのストリーミング対話を TypeScript/JavaScript で扱うためのライブラリ群です。React / Vue / Svelte 向けの useChat、サーバー側の streamText、多数のプロバイダ抽象化が揃っており、チャット UI と薄い API サーバーを短いコードで繋げるのに向いています。

本記事では、SDK の機能と基本的な使い方を整理し、その上で SVG を生成・プレビューできるチャットアプリを作例として紹介します。SVG 部分は SDK の機能ではなくフロント側の拡張ですが、「テキスト応答の中に埋め込んだ構造化データを、チャット内でリッチに描画する」パターンの具体例として読んでもらえると思います。ちなみに Open WebUI の Artifacts を使えば簡単に同じことができますので、開発に興味のない方はそちらをお勧めします。

Vercel AI SDK とは

SDK は大きくサーバー側(ai)とクライアント側(@ai-sdk/vue 等)に分かれます。

パッケージ 役割
ai streamTexttoDataStreamResponse、プロバイダ抽象化
@ai-sdk/vue Vue 3 向け useChat
ollama-ai-provider ローカル Ollama 接続
@ai-sdk/openai OpenAI API 接続

ポイントは、フロントが期待する Data Stream Protocol に合わせてバックエンドが応答を返すことです。自前で SSE をパースするコードを書く代わりに、useChat がメッセージ一覧・入力・ストリーミング受信・ローディング状態を面倒見てくれます。

サーバー側で使う機能

機能 用途
streamText LLM からトークンを逐次受け取る
toDataStreamResponse useChat 互換の HTTP レスポンスに変換
system システムプロンプト注入
Provider 切替 同じ API 形状で Ollama / OpenAI を替える

最小構成のバックエンドは次のイメージです。

import { streamText } from 'ai'
import { createOllama } from 'ollama-ai-provider'

const ollama = createOllama({ baseURL: 'http://localhost:11434/api' })

const result = streamText({
  model: ollama('qwen3.6:27b'),
  system: 'あなたは親切なアシスタントです。',
  messages: validation.messages,
})

return result.toDataStreamResponse()

streamText の戻り値をそのまま toDataStreamResponse() で返すのが、useChat との接続における定石です。

クライアント側で使う機能

Vue 3 では @ai-sdk/vueuseChat を使います。

import { useChat } from '@ai-sdk/vue'

const { messages, input, handleSubmit, isLoading, append, error } = useChat({
  api: '/api/chat',
  body: computed(() => ({
    modelId: selectedModelId.value,
  })),
  onFinish: (message, { usage }) => {
    // 応答完了時の処理(トークン数記録など)
  },
})
API 用途
messages 会話履歴(ストリーミング中も更新される)
handleSubmit フォーム送信
append 任意のユーザーメッセージを手動追加
body モデル ID など追加パラメータ
isLoading / error UI 状態

追加パラメータは bodycomputed で渡すのが重要です。modelId を plain なオブジェクトで渡すと、モデル切替がリクエストに反映されません。

本記事の作例で使っていない機能

SDK には Tool Calling、Structured Output、Embeddings、RAG などもありますが、SVG Chat では使っていません。リッチ UI は応答テキストをクライアントでパースする方式で実現しています(後述)。

開発時の構成

作例プロジェクト svgchat1 では yarn workspaces でフロント(Vite + Vue)とバックエンド(H3)を分け、Vite が /apilocalhost:3001 へプロキシします。ブラウザから Ollama へ直接接続せず、API キーもバックエンドの環境変数だけに置く構成です。

yarn install
cp .env.example .env
ollama serve   # 別ターミナル
yarn dev

環境変数の例です。

変数 用途
OLLAMA_BASE_URL Ollama 接続先
OLLAMA_MODEL デフォルトモデル
OPENAI_API_KEY 設定時のみ OpenAI モデルを一覧に追加

作例: SVG Chat

上記の SDK 構成をそのまま使い、ローカル Ollama(および任意で OpenAI)と対話して SVG を扱う Web チャットを作りました。

svgchat1.png

何ができるか

  • チャットで SVG の生成・編集指示を出す
  • 応答中の SVG をプレビューし、.svg ファイルとしてダウンロード
  • ベクトル描画キャンバスでラフを描き、SVG として LLM に送信
  • 既存 .svg をキャンバスに読み込み、編集を依頼
  • ヘッダーで Ollama / OpenAI モデルを切り替え
  • アシスタント応答の Markdown 表示、トークン使用量などのメタ情報表示

チャットの芯は SDK 標準の useChat + streamText です。SVG 固有の処理はフロント側の拡張に閉じています。

エンドポイント

メソッド パス 役割
POST /api/chat Data Stream でチャット応答
GET /api/models 利用可能モデル一覧

モデル ID は {provider}:{modelName} 形式です(例: ollama:qwen3.6:27bopenai:gpt-4o)。

POST /api/chat リクエスト

{
  "messages": [
    { "role": "user", "content": "赤い円の SVG を作って" }
  ],
  "modelId": "ollama:qwen3.6:27b"
}

バックエンドは modelId を解析し、Ollama または OpenAI の LanguageModel を streamText に渡します。応答終了時には StreamData.appendMessageAnnotation でモデル名やトークン数を付与し、フロントの onFinishmessage.annotations の両方からメタ情報を表示できます。

データの流れ

  1. ユーザーがメッセージ送信(必要なら SVG 付き)
  2. useChatPOST /api/chat を呼ぶ
  3. バックエンドが streamText で LLM へ中継
  4. Data Stream を useChat が受信し messages を更新
  5. MessageItem がテキストを Markdown と SVG に分割して描画

SDK が担うのは 2〜4 です。5 以降はアプリ固有の UI レイヤです。

操作の要点

操作 方法
モデル選択 ヘッダーのプルダウン
メッセージ送信 Ctrl+Enter
キャンバス付き送信 Shift+Ctrl+Enter
SVG アップロード 描画キャンバス → ツールバー

SVG レンダラー(カスタムコンテンツレンダラー)

SVG Chat の見どころの一つが、LLM 応答の ```svg ブロックをチャット内で画像としてプレビューする仕組みです。SDK の Tool Calling ではなく、テキスト応答 + クライアント側パース + 専用 Vue コンポーネントという構成です。

なぜこの方式か

方式 特徴
Tool Calling サーバーが関数を実行。型付きだが API 実装が増える
Structured Output JSON スキーマで返却。パースは楽だが UI 柔軟性に限界
テキストフェンス + クライアントパース プロンプトで形式を約束。useChat のままリッチ UI を載せられる

SVG のように「説明文 + 長いソースコード」を返す用途では、Markdown のコードフェンスが LLM にとっても自然です。ストリーミング完了後にクライアントが抽出すれば、Tool Calling を導入せずにプレビュー・ダウンロード UI を載せられます。

AI への指示

backend/src/prompts/system.ts で出力形式を固定します。

- SVG を出力する場合は必ず ```svg コードブロックで返してください
- viewBox、width、height を明示してください
- SVG 以外の説明はコードブロックの外に書いてください
- 有効な XML として well-formed な SVG を生成してください

ユーザー側も同じ形式に揃えます。キャンバスやアップロードから LLM へ渡す SVG は wrapSvgInFence()```svg フェンスに包み、append または handleSubmit で送信します。

フロントエンド実装

抽出 — frontend/src/lib/svg.ts

export function extractSvgs(text: string): string[] {
  // ```svg ... ``` を優先
  // なければ <svg>...</svg> を拾う
}

export function extractTextWithoutSvgs(text: string): string {
  // 説明文だけ Markdown 用に残す
}

描画 — MessageItem.vue が会話の各メッセージで分割表示します。

  1. MarkdownContent — アシスタントの説明文
  2. SvgContentRenderer — 抽出した SVG のプレビューとダウンロード
  3. フォールバック — パースできない場合は生テキスト

レンダラー本体 — SvgContentRenderer.vue

  • props で SVG 文字列を受け取る
  • v-html で SVG を描画
  • SvgDownloadButton.svg 保存
<SvgContentRenderer
  v-for="(svg, index) in svgs"
  :key="index"
  :svg="svg"
  :index="index"
/>

新しいリッチ表示(表、グラフなど)を足す場合も、同じパターンでフェンスタグ → 抽出関数 → レンダラーコンポーネント → MessageItem と拡張できます。

注意点

  • ストリーミング途中は SVG が不完全で、一時的にプレビュー失敗することがある(完了後に再描画)
  • v-html 利用のため、信頼できない LLM 出力を公開する用途ではサニタイズが必要
  • SVG 品質はモデル依存。プロンプトで形式を補正するしかない

成果物

まとめ

Vercel AI SDK は、streamText + toDataStreamResponse(サーバー)と useChat(クライアント)を繋ぐだけで、ストリーミングチャットの骨格が完成します。プロバイダを差し替えるだけで Ollama と OpenAI を同じ UI から使えるのも便利です。

SVG Chat は、その SDK 構成の上に ```svg フェンスとカスタムコンテンツレンダラーを載せた作例です。Tool Calling を使わなくても、プロンプトで出力形式を約束し、クライアントでパースすれば、チャット内にドメイン固有のリッチ UI を載せられます。

「まずローカル LLM でチャットを動かしたい」「その上で LLM の出力をテキスト以外でも見せたい」という段階なら、本記事の構成はそのまま転用しやすいと思います。

おまけ

LLMにどの程度の絵心があるか、比較してみました。「家の屋根に座ってこっちを見ている猫の絵を描いてください。」というプロンプトで描いてもらった例です。

llama3.2:3b(ローカルLLM/ファイルサイズ2㎇)

llama3.2:3bは、何を描いているのか分かりません。

cat-llama3.2-3b.png

qwen3.6:27b(ローカルLLM/ファイルサイズ17GB)

qwen3.6:27bは、屋根瓦の線や猫の耳がずれています。gpt-5.5と同じ元ネタがありそうです。

cat-qwen3.6-27b.png

gpt-5.5(クラウドLLM/ファイルサイズ不明)

gpt-5.5は、指示通りに描けています。

cat-gpt-5.5.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?