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" }] }
ベストプラクティス
- 必要なアダプタのみをインポート - ツリーシェイク可能な設計でバンドルサイズを最小化
- Zodスキーマで構造化出力を使用 - 型安全なAI応答
- エージェントループに
maxIterationsを設定 - 暴走実行を防止 - 破壊的なツール呼び出しに
requiresApprovalを使用 - 非同期イテレーション周りのtry/catchでストリーミングエラーを処理
- APIキーセキュリティのためサーバー関数を使用 (クライアント側にキーを公開しない)
- 開発時のオブザーバビリティとデバッギングに
onEventを使用 - A/Bテストやフォールバック戦略のためアダプタをランタイムで切り替え
- ストリーミング中のプログレッシブUI更新に部分JSON解析を使用
- プロバイダ間で切り替える場合、メッセージを正規化
よくあるミス
- クライアント側コードでのAPIキー公開 (サーバー関数を使用)
- ストリーミングエラーの処理なし (非同期イテレーションは例外をスロー可能)
- エージェントループの
maxIterations忘れ (無限実行する可能性) - 必要なものだけでなくすべてのアダプタをインポート (バンドルサイズが大きくなる)
- データ抽出用の構造化出力を使用しない (信頼性の低い文字列パース)
- 毎回レンダリング時に新しいアダプタインスタンスを作成 (メモ化またはモジュールレベルで定義)
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- tanstack-skills
- ライセンス
- MIT
- 最終更新
- 2026/1/31
Source: https://github.com/tanstack-skills/tanstack-skills / ライセンス: MIT