Agent Skills by ALSEL
汎用LLM・AI開発⭐ リポ 16品質スコア 67/100

tanstack-ai

プロバイダーに依存しない、タイプセーフなAI SDKです。ストリーミング、ツール呼び出し、構造化出力、マルチモーダルコンテンツに対応しています。複数のAIプロバイダーを統一されたインターフェースで利用でき、型安全性により開発時のエラーを削減できます。

description の原文を見る

Provider-agnostic, type-safe AI SDK for streaming, tool calling, structured output, and multimodal content.

SKILL.md 本文

概要

TanStack AI は、OpenAI、Anthropic、Gemini、Ollama など向けのツリーシェイク可能なアダプタを備えたモジュール式でプロバイダ非依存のAI SDKです。ストリーミングファースト テキスト生成、承認ワークフロー付きのツール呼び出し、Zodスキーマによる構造化出力、マルチモーダルコンテンツサポート、チャット/完了UIのためのReactフック提供します。

コア: @tanstack/ai バニラクライアント: @tanstack/ai-client (フレームワーク非依存) React: @tanstack/ai-react Solid: @tanstack/ai-solid アダプタ: @tanstack/ai-openai@tanstack/ai-anthropic@tanstack/ai-gemini@tanstack/ai-ollama 言語: TypeScript/JavaScript、PHP、Python ステータス: Alpha

インストール

npm install @tanstack/ai @tanstack/ai-react
# または、フレームワーク非依存なバニラクライアント:
npm install @tanstack/ai @tanstack/ai-client
# プロバイダアダプタ (必要なものだけインストール):
npm install @tanstack/ai-openai
npm install @tanstack/ai-anthropic
npm install @tanstack/ai-gemini
npm install @tanstack/ai-ollama

PHP インストール

composer require tanstack/ai tanstack/ai-openai

Python インストール

pip install tanstack-ai tanstack-ai-openai

コア: generate()

import { generate } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai/adapters'

const result = await generate({
  adapter: openaiText({ model: 'gpt-4o' }),
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'Explain React hooks in 3 sentences.' },
  ],
})

// 非同期イテレーションでストリーミング
for await (const chunk of result) {
  process.stdout.write(chunk.text)
}

プロバイダアダプタ

import { openaiText } from '@tanstack/ai-openai/adapters'
import { anthropicText } from '@tanstack/ai-anthropic/adapters'
import { geminiText } from '@tanstack/ai-gemini/adapters'
import { ollamaText } from '@tanstack/ai-ollama/adapters'

// OpenAI
const openai = openaiText({ model: 'gpt-4o' })

// Anthropic
const anthropic = anthropicText({ model: 'claude-sonnet-4-20250514' })

// Google Gemini
const gemini = geminiText({ model: 'gemini-pro' })

// Ollama (ローカル)
const ollama = ollamaText({ model: 'llama3' })

// ランタイムアダプタ切り替え
const adapter = process.env.AI_PROVIDER === 'anthropic' ? anthropic : openai

React フック

useChat

import { useChat } from '@tanstack/ai-react'

function ChatUI() {
  const { messages, input, setInput, handleSubmit, isLoading } = useChat({
    adapter: openaiText({ model: 'gpt-4o' }),
  })

  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.id}>
          <strong>{msg.role}:</strong> {msg.content}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Type a message..."
        />
        <button type="submit" disabled={isLoading}>
          Send
        </button>
      </form>
    </div>
  )
}

useCompletion

import { useCompletion } from '@tanstack/ai-react'

function CompletionUI() {
  const { completion, input, setInput, handleSubmit, isLoading } = useCompletion({
    adapter: openaiText({ model: 'gpt-4o' }),
  })

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <textarea
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Enter prompt..."
        />
        <button type="submit" disabled={isLoading}>Generate</button>
      </form>
      {completion && <div>{completion}</div>}
    </div>
  )
}

Solid.js フック

import { createChat } from '@tanstack/ai-solid'

function ChatUI() {
  const chat = createChat({
    adapter: openaiText({ model: 'gpt-4o' }),
  })

  return (
    <div>
      <For each={chat.messages()}>
        {(msg) => (
          <div>
            <strong>{msg.role}:</strong> {msg.content}
          </div>
        )}
      </For>
      <form onSubmit={chat.handleSubmit}>
        <input
          value={chat.input()}
          onInput={(e) => chat.setInput(e.target.value)}
          placeholder="Type a message..."
        />
        <button type="submit" disabled={chat.isLoading()}>
          Send
        </button>
      </form>
    </div>
  )
}

バニラクライアント

ReactやSolidなしでフレームワーク非依存に使用する場合:

import { createAIClient } from '@tanstack/ai-client'
import { openaiText } from '@tanstack/ai-openai/adapters'

const client = createAIClient({
  adapter: openaiText({ model: 'gpt-4o' }),
})

// 状態の変更を購読
client.subscribe((state) => {
  console.log('Messages:', state.messages)
  console.log('Loading:', state.isLoading)
})

// メッセージを送信
await client.send('Hello, world!')

// 会話をクリア
client.clear()

ストリーミング

ストリーミング戦略

import { generate } from '@tanstack/ai'

// デフォルト: チャンクが到着するたびにストリーミング
const result = await generate({
  adapter: openaiText({ model: 'gpt-4o' }),
  messages: [...],
  stream: true,
})

for await (const chunk of result) {
  // 各チャンクを処理
  console.log(chunk.text)
}

利用可能なストリーミング戦略:

  • Batch - すべてのチャンクを配信前に収集
  • Punctuation - 文の境界でストリーミング
  • WordBoundary - 単語の境界でストリーミング
  • Composite - 複数の戦略を組み合わせ

Server-Sent Events (SSE)

// サーバー側SSEエンドポイント
import { createReplayStream } from '@tanstack/ai'

export async function handler(req: Request) {
  const stream = createReplayStream({
    adapter: openaiText({ model: 'gpt-4o' }),
    messages: await req.json(),
  })

  return new Response(stream, {
    headers: { 'Content-Type': 'text/event-stream' },
  })
}

構造化出力

import { generate } from '@tanstack/ai'
import { convertZodToJsonSchema } from '@tanstack/ai'
import { z } from 'zod'

const RecipeSchema = z.object({
  name: z.string(),
  ingredients: z.array(z.object({
    item: z.string(),
    amount: z.string(),
  })),
  steps: z.array(z.string()),
  cookTime: z.number(),
})

const result = await generate({
  adapter: openaiText({ model: 'gpt-4o' }),
  messages: [{ role: 'user', content: 'Give me a pasta recipe' }],
  schema: convertZodToJsonSchema(RecipeSchema),
})

// result は z.infer<typeof RecipeSchema> として型付けされます
console.log(result.name, result.ingredients)

ツール呼び出し

基本的なツール

import { generate } from '@tanstack/ai'

const result = await generate({
  adapter: openaiText({ model: 'gpt-4o' }),
  messages: [{ role: 'user', content: 'What is the weather in NYC?' }],
  tools: {
    getWeather: {
      description: 'Get weather for a location',
      parameters: z.object({
        location: z.string(),
        unit: z.enum(['celsius', 'fahrenheit']).optional(),
      }),
      execute: async ({ location, unit }) => {
        const data = await fetchWeather(location, unit)
        return data
      },
    },
  },
})

承認ワークフロー付きツール呼び出し

import { ToolCallManager } from '@tanstack/ai'

const manager = new ToolCallManager({
  tools: {
    deleteUser: {
      description: 'Delete a user account',
      parameters: z.object({ userId: z.string() }),
      requiresApproval: true, // ヒューマン承認が必要
      execute: async ({ userId }) => {
        await deleteUser(userId)
        return { success: true }
      },
    },
  },
  onApprovalRequired: async (toolCall) => {
    // ユーザーに承認のために提示
    return await showApprovalDialog(toolCall)
  },
})

エージェントループ

const result = await generate({
  adapter: openaiText({ model: 'gpt-4o' }),
  messages: [{ role: 'user', content: 'Research and summarize the topic' }],
  tools: { search, summarize, writeReport },
  maxIterations: 10, // エージェントループの反復回数を制限
})

マルチモーダルコンテンツ

// 画像
const result = await generate({
  adapter: openaiText({ model: 'gpt-4o' }),
  messages: [{
    role: 'user',
    content: [
      { type: 'text', text: 'What is in this image?' },
      { type: 'image_url', image_url: { url: 'https://example.com/photo.jpg' } },
    ],
  }],
})

// DALL-Eで画像生成
import { openaiImage } from '@tanstack/ai-openai/adapters'

const image = await generate({
  adapter: openaiImage({ model: 'dall-e-3' }),
  messages: [{ role: 'user', content: 'A sunset over mountains' }],
})

// Gemini Imagenで画像生成
import { geminiImage } from '@tanstack/ai-gemini/adapters'

const image = await generate({
  adapter: geminiImage({ model: 'imagen-3' }),
  messages: [{ role: 'user', content: 'A futuristic cityscape at night' }],
})

シンキングモデル (リーズニングトークン)

拡張された推論/思考能力を持つモデルのサポート:

import { generate } from '@tanstack/ai'
import { anthropicText } from '@tanstack/ai-anthropic/adapters'

const result = await generate({
  adapter: anthropicText({ model: 'claude-sonnet-4-20250514' }),
  messages: [{ role: 'user', content: 'Solve this complex math problem step by step...' }],
  thinking: {
    enabled: true,
    budget: 10000, // 最大シンキングトークン
  },
})

// シンキング/リーズニング出力にアクセス
console.log('Thinking:', result.thinking)
console.log('Response:', result.text)

// シンキングトークンでストリーミング
for await (const chunk of result) {
  if (chunk.type === 'thinking') {
    console.log('[Thinking]', chunk.text)
  } else {
    process.stdout.write(chunk.text)
  }
}

メッセージユーティリティ

import { generateMessageId, normalizeToUIMessage } from '@tanstack/ai'

// 一意のメッセージIDを生成
const id = generateMessageId()

// プロバイダ固有のメッセージをUI形式に正規化
const uiMessage = normalizeToUIMessage(providerMessage)

オブザーバビリティ

const result = await generate({
  adapter: openaiText({ model: 'gpt-4o' }),
  messages: [...],
  onEvent: (event) => {
    // 構造化された型付きイベント
    switch (event.type) {
      case 'text':
        console.log('Text chunk:', event.data)
        break
      case 'tool_call':
        console.log('Tool called:', event.name)
        break
      case 'error':
        console.error('Error:', event.error)
        break
    }
  },
})

AI デブツール

TanStack AIにはAIワークフローをデバッグするための専用デブツールパネルが含まれています:

import { TanStackDevtools } from '@tanstack/react-devtools'
import { AIDevtoolsPanel } from '@tanstack/ai-react/devtools'

function App() {
  return (
    <TanStackDevtools
      plugins={[
        {
          id: 'ai',
          name: 'AI',
          render: () => <AIDevtoolsPanel />,
        },
      ]}
    />
  )
}

AI デブツール機能:

  • メッセージインスペクタ - メタデータ付きの完全な会話履歴を表示
  • トークン使用量 - リクエストごとの入力/出力トークンとコストを追跡
  • ストリーミング可視化 - ストリーミングチャンクのリアルタイムビュー
  • ツール呼び出しデバッギング - ツール呼び出し、パラメータ、結果を検査
  • シンキング/リーズニングビューア - シンキングモデルからのリーズニングトークンをデバッグ
  • アダプタ切り替え - 開発中に異なるプロバイダをテスト
  • リクエスト/レスポンスログ - 完全なHTTPリクエスト/レスポンス検査

TanStack Start 統合

// AIツールとサーバー関数間の共有実装
import { createServerFn } from '@tanstack/react-start'
import { generate } from '@tanstack/ai'

const aiChat = createServerFn({ method: 'POST' })
  .validator(z.object({ messages: z.array(messageSchema) }))
  .handler(async ({ data }) => {
    const result = await generate({
      adapter: openaiText({ model: 'gpt-4o' }),
      messages: data.messages,
    })
    return result
  })

部分JSON パーサー

段階的に到着するストリーミング構造化出力用:

import { parsePartialJson } from '@tanstack/ai'

// ストリーミング中の不完全なJSONを解析
const partial = parsePartialJson('{"name": "Pasta", "ingredients": [{"item": "flour"')
// 戻り値: { name: "Pasta", ingredients: [{ item: "flour" }] }

ベストプラクティス

  1. 必要なアダプタのみをインポート - ツリーシェイク可能な設計でバンドルサイズを最小化
  2. Zodスキーマで構造化出力を使用 - 型安全なAI応答
  3. エージェントループにmaxIterationsを設定 - 暴走実行を防止
  4. 破壊的なツール呼び出しにrequiresApprovalを使用
  5. 非同期イテレーション周りのtry/catchでストリーミングエラーを処理
  6. APIキーセキュリティのためサーバー関数を使用 (クライアント側にキーを公開しない)
  7. 開発時のオブザーバビリティとデバッギングにonEventを使用
  8. A/Bテストやフォールバック戦略のためアダプタをランタイムで切り替え
  9. ストリーミング中のプログレッシブUI更新に部分JSON解析を使用
  10. プロバイダ間で切り替える場合、メッセージを正規化

よくあるミス

  • クライアント側コードでのAPIキー公開 (サーバー関数を使用)
  • ストリーミングエラーの処理なし (非同期イテレーションは例外をスロー可能)
  • エージェントループのmaxIterations忘れ (無限実行する可能性)
  • 必要なものだけでなくすべてのアダプタをインポート (バンドルサイズが大きくなる)
  • データ抽出用の構造化出力を使用しない (信頼性の低い文字列パース)
  • 毎回レンダリング時に新しいアダプタインスタンスを作成 (メモ化またはモジュールレベルで定義)

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
tanstack-skills
リポジトリ
tanstack-skills/tanstack-skills
ライセンス
MIT
最終更新
2026/1/31

Source: https://github.com/tanstack-skills/tanstack-skills / ライセンス: MIT

本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: tanstack-skills · tanstack-skills/tanstack-skills · ライセンス: MIT