ããã«ã¡ã¯ã é»é ç·ç ã®éèITæ¬éšïŒå
ŒXIæ¬éšïŒã®äžéã§ãã æ¬èšäºã¯ é»éç·ç Advent Calendar 2025 11æ¥ç®ã®èšäºã§ãã æè¿äžçªå¬ããã£ãããšãšèšãã°ã AWS ã®ã³ã³ãœãŒã«æ€çŽ¢ã§ãECRããšå
¥åãããšäžçºãšå
¥åãããšã³ã³ãã ã¬ãžã¹ã㪠ãžé£ã¹ãããã«ãªã£ãããšã§ãã ãECSããã®é·ç§»ãããcontainer registryããšæ€çŽ¢ããŠããã®ã¯ç§ã ãã§ã¯ãªãã¯ãã§ãã ããŠãæ¬æ¥ã¯æè¿ç»å ŽããTanStackãšã³ã·ã¹ãã ã®äžã€ã TanStack AI ãå©çšããŠãã£ããããããäœã£ãŠã¿ãŸãããæ§ã
ãªãšã³ã·ã¹ãã ã§ç¥ãããTanStackã§ãããAIã¢ããªã±ãŒã·ã§ã³éçºã®åéã«åå
¥ããŠããŸããã æ¬èšäºã§ã¯ãTanStack AI ã®ç¹åŸŽã Vercel AI SDK ãšã®éãã解説ãã Amazon Bedrock ãšçµã¿åãããŠãã¡ã€ã«æ·»ä»å¯Ÿå¿ã®ãã£ããããããæ§ç¯ããæ¹æ³ã玹ä»ããŸãã TanStack AIãšã¯ äž»ãªç¹åŸŽ TanStack AI ãçãŸããèæ¯ TanStack AI vs Vercel AI SDK TanStack AI ãéžã¶ã¹ãã±ãŒã¹ Vercel AI SDK ãéžã¶ã¹ãã±ãŒã¹ TanStack AI ã®å®è£
äŸ ã¯ã©ã€ã¢ã³ãå®è£
äŸ ãµãŒããŒå®è£
äŸ Vercel AI SDKã®å©çšäŸïŒæ¯èŒçšïŒ ã¯ã©ã€ã¢ã³ãåŽã®å®è£
äŸ ãµãŒããŒåŽã®å®è£
äŸ Amazon Bedrock ãšã¯ Amazon Bedrock ã®ç¹åŸŽ Amazon Bedrock ã䜿ãã¡ãªãã ä»ã®ã¯ã©ãŠã AI ãµãŒãã¹ãšã®æ¯èŒ å©çšå¯èœãªã¢ãã« äž»ãªæ©èœæ¯èŒ éžå®ã®æé TanStack AI à Amazon Bedrockãšã®æ¥ç¶ ãµãŒããŒåŽã®å®è£
äŸ Amazon Bedrock ã¢ããã¿ãŒã¯ã©ã¹ Express ãµãŒã㌠ã¯ã©ã€ã¢ã³ãåŽã®å®è£
äŸ ã«ã¹ã¿ã Connection Adapter useChat ããã¯ã®äœ¿çš å®è£
ã«ããã£ãŠã®ãã€ã³ãïŒ1ïŒStreamChunk ã®åœ¢åŒ å®è£
ã«ããã£ãŠã®ãã€ã³ãïŒ2ïŒExtended ThinkingïŒæèããã»ã¹ïŒã®ãµããŒã ãã¡ã€ã«æ·»ä»ã®ãµããŒã ã㢠ãŸãšã TanStack AIãšã¯ TanStack AI ã¯ãè€æ°ã® AI ãããã€ããŒã«å¯Ÿå¿ããçµ±äžã€ã³ã¿ãŒãã§ãŒã¹ãæäŸãã ãªãŒãã³ãœãŒã¹ SDK ã§ãã TanStack AI Alpha: Your AI, Your Way ã«ãããŠãAI ããŒã«ã®ã¹ã€ã¹ãç®æãããšè¿°ã¹ãŠããããç¹å®ã®ãã³ããŒããã©ãããã©ãŒã ã«äŸåããªãäžç«çãªèšèšãæèããŠäœãããããšããæå³ã ãšçè§£ããŠããŸãã TanStack AI takes a different approach. We're building the Switzerland of AI tooling. An honest, open source set of libraries (across multiple languages) that works with your existing stack instead of replacing it. äž»ãªç¹åŸŽ ãã³ããŒããã¯ã€ã³ãªãïŒ OpenAIãAnthropicã Google GeminiãOllama ãªã©è€æ°ã®ãããã€ããŒã«å¯Ÿå¿ å®å
šãªåå®å
šæ§ïŒ TypeScript ã§ãšã³ãããŒãšã³ãã®åå®å
šãå®çŸ ãã¬ãŒã ã¯ãŒã¯ éäŸåïŒReactãSolidãVanilla JS ãªã©ä»»æã®ç°å¢ã§å©çšå¯èœ ãã«ãã¢ãŒãã«å¯Ÿå¿ïŒããã¹ããç»åãé³å£°ãåç»ãããã¥ã¡ã³ãããµããŒã MIT ã©ã€ã»ã³ã¹ïŒ å®å
šãª ãªãŒãã³ãœãŒã¹ TanStack AI ãçãŸããèæ¯ TanStack ã®åµèšè
Tanner Linsley æ°ã¯ãæ¢åã® AI SDK ïŒVercel AI SDK ïŒ ã«å¯Ÿããæžå¿µãã TanStack AI ãéçºããŸããã TanStack AI ã¯ããããã®èª²é¡ã解決ãããäžç«çãªã SDK ãšããŠèšèšãããŠããŸããCloudflare ã Netlify ãã¹ãã³ãµãŒãšããŠåå ããŠããã®ãããªãŒãã³ã§ç§»æ€å¯èœãªããŒã«ã圌ãã®ãã©ãããã©ãŒã ã«ãæ©æµãããããããã ãšèããŠããŸãã TanStack AI vs Vercel AI SDK é
ç® TanStack AI Vercel AI SDK å²åŠ äžç«ã» ãªãŒãã³ãœãŒã¹ Next.js/Vercel ãšã³ã·ã¹ãã çµ±å ãããã€ããŒæ° 4ïŒOpenAI, Anthropic, Gemini, OllamaïŒ å€æ° ã¹ããªãŒãã³ã°å¶åŸ¡ ã¹ããŒã管çããŠæç€ºçã«ç®¡ç HTTP ã¹ããªãŒã çæãŸã§ã匷ããµããŒã æç床 ã¢ã«ãã¡çïŒ2025幎åŸåãïŒ å®å®ç ãããã€å
ã©ãã§ãïŒ AWS , Cloudflare, Netlify çïŒ ã©ãã§ãããã ãVercel æé©å çŸæç¹ã§ã¯å§åçã«Vercel AI SDK ã髿©èœã§ãããVercelæäŸã® SDK ã¯ã©ãããŠããã©ãããã©ãŒã äŸåéšåãåºãŠããŸãã æšæº OSS ãšããŠå©çšå¯èœã§ã¯ãããŸãããNext.jsãšå¯æ¥ã«çµ±åãããŠãããVercelãããã€æã«æé©åãããŠããŸããäŸãã°Vercel Functionsã® ã¿ã€ã ã¢ãŠã æå®ãªã©ãã»ããã§å©çšå¯èœã«ãªã£ãŠããçãVercelãããã€ããªãå Žåã¯äžèŠãªæ©èœãããã€ããããŸãã TanStack AI ãéžã¶ã¹ãã±ãŒã¹ Vercel 以å€ã®ç°å¢ïŒ AWS ãCloudflare çïŒã«ãããã€ãã å°æ¥çã«ãããã€ããŒãåãæ¿ããå¯èœæ§ããã TanStack ãšã³ã·ã¹ãã ã䜿çšããŠãã Vercel AI SDK ãéžã¶ã¹ãã±ãŒã¹ Next.js + Vercel ã§éçºããŠãã ä»ããæ¬çªç°å¢ã§äœ¿ãããïŒTanStack AI ã¯ãŸã ã¢ã«ãã¡çïŒ å€æ°ã®ãããã€ããŒã«å¯Ÿå¿ããã è±å¯ãªããã¥ã¡ã³ããšäºäŸãå¿
èŠ TanStack AI ã®å®è£
äŸ ã¯ã©ã€ã¢ã³ãå®è£
äŸ import { useChat } from '@tanstack/ai-react' import { fetchServerSentEvents } from '@tanstack/ai-react/connection' function Chat () { const { messages , sendMessage , isLoading } = useChat( { connection : fetchServerSentEvents( '/api/chat' ), } ) const handleSubmit = ( e : React.FormEvent ) => { e. preventDefault () const input = e. currentTarget . querySelector ( 'input' ) if (input?.value) { sendMessage(input.value) input.value = '' } } return ( < div > < div className = "messages" > { messages. map (( msg ) => ( < div key = { msg. id } className = { `message ${ msg.role } ` } > { msg.content } </ div > )) } </ div > < form onSubmit = { handleSubmit } > < input type = "text" placeholder = "ã¡ãã»ãŒãžãå
¥å..." /> < button type = "submit" disabled = { isLoading } > éä¿¡ </ button > </ form > </ div > ) } ãµãŒããŒå®è£
äŸ import { chat, toStreamResponse } from '@tanstack/ai' import { openai } from '@tanstack/ai-openai' export async function POST ( request : Request ) { const { messages } = await request.json() const response = await chat( { provider : openai( { apiKey : process .env.OPENAI_API_KEY } ), model : 'gpt-4o' , messages , } ) return toStreamResponse(response) } Vercel AI SDK ã®å©çšäŸïŒæ¯èŒçšïŒ TanStack AIãšã®å·®å submitæã® sendMessage(input.value) çžåœã®åŠçã¯ã useChat ããã¯ããããªã«ãã£ãŠãããã ã¯ã©ã€ã¢ã³ãåŽã®å®è£
äŸ import { useChat } from 'ai/react' export function Chat () { const { messages , input , handleInputChange , handleSubmit , isLoading , } = useChat( { api : '/api/chat' , } ) return ( < div > < div className = "messages" > { messages. map (( msg ) => ( < div key = { msg. id } className = { `message ${ msg.role } ` } > { msg.content } </ div > )) } </ div > < form onSubmit = { handleSubmit } > < input type = "text" placeholder = "ã¡ãã»ãŒãžãå
¥å..." value = { input } onChange = { handleInputChange } /> < button type = "submit" disabled = { isLoading } > éä¿¡ </ button > </ form > </ div > ) } ãµãŒããŒåŽã®å®è£
äŸ import { streamText } from 'ai' import { openai } from '@ai-sdk/openai' export async function POST(req: Request) { const { messages } = await req.json() const result = streamText({ model: openai('gpt-4o'), messages, }) // ã¹ããªãŒãã³ã°ã¬ã¹ãã³ã¹ããã®ãŸãŸè¿ã return result.toDataStreamResponse() Amazon Bedrock ãšã¯ Amazon Bedrockã¯ã AWS ãæäŸãããã«ãããŒãžã AI ãµãŒãã¹ã§ããAnthropic ClaudeãMeta Llamaã Amazon Titanãªã©ãè€æ°ã®åºç€ã¢ãã«ãçµ±äžããã API ã§å©çšã§ããŸãã Amazon Bedrock ã®ç¹åŸŽ ãã«ãã¢ãã« ïŒ è€æ°ãã³ããŒã®ã¢ãã«ãåäž API ã§å©çš IAM çµ±å ïŒ AWS ã®èªèšŒã»èªå¯åºç€ãæŽ»çš VPC å¯Ÿå¿ ïŒ PrivateLink ã§ã»ãã¥ã¢ãªæ¥ç¶ ãªãŒãžã§ã³ ïŒ æ±äº¬ïŒap-northeast-1ïŒã§å©çšå¯èœ åŸé課é ïŒ AWS è«æ±ã«çµ±åãäºæž¬å¯èœãªã³ã¹ã管ç Amazon Bedrock ã䜿ãã¡ãªãã AWS ã® IAM ã«ããèªèšŒã»èªå¯ãå©çšããã VPC PrivateLink ã§ã»ãã¥ã¢ãªæ¥ç¶ãããã AWS ã®è«æ±ã«çµ±åããŠã³ã¹ã管çãããã CloudTrail ã§ ã¢ã¯ã»ã¹ãã° ãååŸããã ä»ã® ã¯ã©ãŠã AI ãµãŒãã¹ãšã®æ¯èŒ ãã«ãã¢ãã« AI ãµãŒãã¹ã¯å ã¯ã©ãŠã ãã³ããŒãæäŸããŠããŸãã AWS ã® Bedrockã Google Cloud ã® Vertex AIã Microsoft ã® Azure AI FoundryïŒæ§ Azure AI StudioïŒãæ¯èŒããŠã¿ãŸãã â»åç§°ãæäŸã¢ãã«ã»æ©èœã¯é次å€åãããããå
¬åŒã®æ
å ±ãåç
§ããŠãã ããã å©çšå¯èœãªã¢ãã« ãããã€ã㌠Amazon Bedrock Vertex AI Azure AI Foundry Anthropic Claude â
Claude 4, 3.5, 3 ç³» â
Claude 3.5, 3 ç³» â OpenAI GPT â â â
GPT-4o, GPT-4, GPT-3.5 Google Gemini â â
Gemini 2.0, 1.5 â Meta Llama â
Llama 3ç³» â
Llama 3ç³» â
Llama 3ç³» Mistral â
Mistral Large, Small â
Mistral â
Mistral Large èªç€Ÿã¢ãã« Amazon Nova, Triton Gemini Phi-4, Phi-3 äž»ãªæ©èœæ¯èŒ æ©èœ Amazon Bedrock Vertex AI Azure AI Foundry çµ±äž API Converse API Gemini API / Model Garden Azure OpenAI + Azure AI Inference ãã¡ã€ã³ãã¥ãŒãã³ã° å¯ å¯ å¯ RAG æ©èœ Knowledge Bases Vertex AI Agent BuilderïŒçµ±åäžïŒVertex AI SearchïŒ Azure AI Search 飿º ãšãŒãžã§ã³ã Bedrock Agents Vertex AI Agent Builder Azure AI Foundry Agent Service ã¬ãŒãã¬ãŒã« Guardrails for Bedrock Responsible AIããŒã«çŸ€ Content Safety â»ãã¡ã€ã³ãã¥ãŒãã³ã°ã¯ã¢ãã«ã«ãã£ãŠå¯äžå¯ããããŸãã éžå®ã®æé çŸæç¹ã§ã¯ã¢ãã«ããŒã¹ã§å©çšåºç€ãéžã¶ããšã«ãªãããšæããŸãã å®éã¯åãµãŒãã¹ã®ã¢ãã«ã«ã¿ãã°ãèŠãªãããèŠä»¶ã«ãã£ãã¢ãã«ã䜿ãããã©ããã察象ã®ãªãŒãžã§ã³ã§æäŸãããŠãããã©ãããå å³ããŠæ¯èŒããå¿
èŠããããŸãã Claude ã䜿ããã ïŒ Amazon Bedrock ãŸã㯠Vertex AI OpenAI GPT ã䜿ããã ïŒAzure AI Foundry Gemini ã䜿ããã ïŒVertex AI ä»åã¯äœ¿ãæ
£ãã AWS ç°å¢ã§ Claude ãå©çšãããã£ãããã Amazon Bedrock ãéžæããŸããã TanStack AI à Amazon Bedrockãšã®æ¥ç¶ TanStack AI ãš Amazon Bedrock ãæ¥ç¶ããæ¹æ³ã説æããåã«ãTanStack AI ã® 2 å±€æ§é ïŒåŒã³åºãAIãããã€ããŒã§ãåèš3ã€ã®åœ¹å²ã«åãããŠããããšãçè§£ããå¿
èŠããããŸãã ã¯ã©ã€ã¢ã³ãå±€ useChat + Connection Adapter (SSE/HTTP/WebSocket) SSE ã¹ããªãŒã ã®ãªã¯ãšã¹ããšã¬ã¹ãã³ã¹ãã³ããªã³ã° fetchServerSentEvents ã fetchHttpStream ãªã© ãµãŒããŒå±€ chat() + Provider Adapter (OpenAI/Anthropic/etc) @tanstack/ai-openai ã @tanstack/ai-anthropic ãªã© AI ãããã€ã㌠OpenAI / Anthropic / Google Gemini / Ollama ãµããŒããããŠããªãå Žåã¯èªåã§å®è£
ãå¿
èŠ TanStack AI ã¯çŸåšã¢ã«ãã¡çã§ãããå
¬åŒã«ãµããŒããããŠããAIãããã€ããŒã¯ä»¥äžã® 4 ã€ã§ãïŒ OpenAI ( @tanstack/ai-openai ) Anthropic ( @tanstack/ai-anthropic ) Google Gemini ( @tanstack/ai-gemini ) Ollama ( @tanstack/ai-ollama ) Amazon Bedrock çšã®ã¢ããã¿ãŒã¯å
¬åŒã«ã¯æäŸãããŠããŸãã ãAnthropic ã® Claude ã䜿ãããå Žåã @tanstack/ai-anthropic ã§çŽæ¥ Anthropic API ã«æ¥ç¶ããããšã¯å¯èœã§ããã Amazon Bedrock çµç±ã§å©çšãããå Žåã¯ã«ã¹ã¿ã ã¢ããã¿ãŒãå®è£
ããå¿
èŠããããŸãã TanStack AIã¯å
¬åŒã«ã«ã¹ã¿ã å¯èœãšè¬³ã£ãŠãããããæ¬æ¥ã¯ã©ã€ã¢ã³ãå±€ïŒãµãŒããŒå±€ãšãã« Bedrockã®ã¢ããã¿ãŒãäœæããŠæ¥ç¶ãããã£ãã®ã§ãããTanStack AIãæªã αçã§ãããå
¬éæ
å ±ãå°ãªãããã«ãµãŒããµã€ãã¯èªåã§æ¥ç¶åŠçãèšããŸãã ä»åã®å®è£
æ¹éã¯ä»¥äžã®ãšããã§ãã ãµãŒããŒåŽ ïŒ Amazon Bedrock SDK ãçŽæ¥äœ¿çšããTanStack AI ã® StreamChunk 圢åŒã§éä¿¡ãã ã¯ã©ã€ã¢ã³ãåŽ ïŒTanStack AI ã® useChat ãã㯠+ ã«ã¹ã¿ã Connection Adapter ã§ã¬ã¹ãã³ã¹ãåãåã ãã®æ¹æ³ã«ãããTanStack AI ã® useChat ããã¯ã®æ©æµïŒã¡ãã»ãŒãžç®¡çãããŒãã£ã³ã°ç¶æ
ãåå®å
šæ§ïŒãåããªããã Amazon Bedrock ãšæ¥ç¶ã§ããŸãã ãµãŒããŒåŽã®å®è£
äŸ Amazon Bedrock ã¢ããã¿ãŒã¯ã©ã¹ AWS SDK ã䜿çšã㊠Amazon Bedrock ã® Converse API ãåŒã³åºããTanStack AI ã® StreamChunk 圢åŒã§ã¹ããªãŒã ãè¿ããŸãã import { BedrockRuntimeClient, ConverseStreamCommand, } from '@aws-sdk/client-bedrock-runtime' export interface StreamChunk { type : 'content' | 'thinking' | 'done' | 'error' id ?: string model ?: string timestamp ?: number delta ?: string // å¢åããŒã¯ã³ content ?: string // 环ç©ã³ã³ãã³ã role ?: string // 'assistant' finishReason ?: string error ?: string } // Adapterãšèšã£ãŠããããTanStack AIãšã®ã¢ããã¿ã§ã¯ãªãåã«awsã®sdkããæ¥ç¶ããBedrockãžã®Adapter export class BedrockAdapter { private client : BedrockRuntimeClient private modelId : string async * chatStream ( messages , options ): AsyncGenerator < StreamChunk > { const command = new ConverseStreamCommand( { modelId : this .modelId, messages : this .formatMessages(messages), additionalModelRequestFields : options.enableThinking ? { thinking : { type : 'enabled' , budget_tokens : 5000 } } : undefined , } ) const response = await this .client. send (command) for await ( const event of response.stream!) { if (event.contentBlockDelta?.delta) { const delta = event.contentBlockDelta.delta if ( 'thinking' in delta) { yield { type : 'thinking' , delta : delta.thinking, ... } } else if ( 'text' in delta) { yield { type : 'content' , delta : delta.text, role : 'assistant' , ... } } } if (event.messageStop) { yield { type : 'done' , finishReason : 'end_turn' } } } } } Express ãµãŒã㌠Bedrock ã¢ããã¿ãŒã䜿çšããSSE 圢åŒã§ã¯ã©ã€ã¢ã³ãã«éä¿¡ããŸãã import express from 'express' import { bedrock } from './adapters/bedrock-adapter.js' const app = express() const adapter = bedrock( { region : process .env.AWS_REGION || 'us-east-1' , modelId : process .env.BEDROCK_MODEL_ID || 'claude-sonnet-4' , } ) app.post( '/api/chat' , async ( req , res ) => { const { messages } = req. body // SSE ããããŒèšå® res.setHeader( 'Content-Type' , 'text/event-stream' ) res.setHeader( 'Cache-Control' , 'no-cache' ) res.setHeader( 'Connection' , 'keep-alive' ) const stream = adapter.chatStream(messages, { systemPrompt : 'Hello World' , enableThinking : true , } ) for await ( const chunk of stream) { res. write ( `data: ${ JSON . stringify (chunk) } \n\n ` ) if (chunk. type === 'done' || chunk. type === 'error' ) break } res.end() } ) ã¯ã©ã€ã¢ã³ãåŽã®å®è£
äŸ ã«ã¹ã¿ã Connection Adapter TanStack AI ã® ConnectionAdapter ã€ã³ã¿ãŒãã§ãŒã¹ãå®è£
ãããµãŒããŒããã®ã¬ã¹ãã³ã¹ãåŠçããŸãã import { useChat, type ConnectionAdapter } from '@tanstack/ai-react' import type { UIMessage, ModelMessage, StreamChunk } from '@tanstack/ai' // ã«ã¹ã¿ã Connection Adapter function createBedrockConnection(): ConnectionAdapter { return { async *connect( messages: UIMessage[] | ModelMessage[], _data?: Record < string , unknown > , abortSignal?: AbortSignal ): AsyncIterable < StreamChunk > { // ã¡ãã»ãŒãžã Bedrock 圢åŒã«å€æ const chatMessages = messages.map (( msg ) => ( { role : msg.role, content : extractTextContent(msg), } )) const response = await fetch ( '/api/chat' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } , body : JSON . stringify ( { messages : chatMessages } ), signal : abortSignal, } ) const reader = response. body ?.getReader() const decoder = new TextDecoder () let buffer = '' while ( true ) { const { done , value } = await reader!.read() if (done) break buffer += decoder. decode (value, { stream : true } ) const lines = buffer. split ( ' \n ' ) buffer = lines. pop () || '' for ( const line of lines) { if (line. startsWith ( 'data: ' )) { const data = JSON . parse (line. slice ( 6 )) as StreamChunk yield data } } } } , } } useChat ããã¯ã®äœ¿çš ã«ã¹ã¿ã Connection Adapter ã useChat ã«æž¡ããŠäœ¿çšããŸãã function App () { const connection = useMemo(() => createBedrockConnection(), [] ) // TanStack AI ã® useChat ãã㯠const { messages , sendMessage , isLoading } = useChat( { connection , } ) return ( < div > { messages. map (( message ) => ( < div key = { message. id } className = { message.role } > { message.parts. map (( part , idx ) => { if (part. type === 'thinking' ) { return < div key = { idx } className = "thinking" > { part.content } </ div > } if (part. type === 'text' ) { return < div key = { idx } > { part.content } </ div > } return null } ) } </ div > )) } < form onSubmit = { ( e ) => { e. preventDefault () sendMessage(input) } } > < input type = "text" /> < button disabled = { isLoading } > éä¿¡ </ button > </ form > </ div > ) } å®è£
ã«ããã£ãŠã®ãã€ã³ãïŒ1ïŒStreamChunk ã®åœ¢åŒ TanStack AI ã® useChat ãæ£ããåäœããããã«ã¯ããµãŒããŒããè¿ã SSE ã以äžã® StreamChunk 圢åŒã«æºæ ããŠããå¿
èŠããããŸãïŒ // ContentStreamChunk { type : 'content' , delta: 'æ°ãã' , content: 'æ°ãã' , role: 'assistant' , id, model, timestamp } // ThinkingStreamChunk { type : 'thinking' , delta: 'èãäž...' , content: 'èãäž...' , id, model, timestamp } // DoneStreamChunk { type : 'done' , finishReason: 'end_turn' , id, model, timestamp } // ErrorStreamChunk { type : 'error' , error: 'ãšã©ãŒã¡ãã»ãŒãž' } ãã®åœ¢åŒã«æºæ ããŠããã°ã useChat ã¯èªåçã«ã¡ãã»ãŒãžã UIMessage ã«å€æãã thinking 㯠ThinkingPart ãšããŠã content 㯠TextPart ãšããŠç®¡çãããŸãã å®è£
ã«ããã£ãŠã®ãã€ã³ãïŒ2ïŒExtended ThinkingïŒæèããã»ã¹ïŒã®ãµããŒã Claude Sonnet 4 / Opus 4 ã§ã¯ Extended Thinking æ©èœãå©çšå¯èœã§ãããã®æ©èœãæå¹ã«ãããšãClaude ãåçãçæããåã®ãæèéçšããã¹ããªãŒãã³ã°ã§ååŸã§ããŸãã // Thinking æå¹æã®ã¬ã¹ãã³ã¹äŸ { type : 'thinking' , delta: 'ãŠãŒã¶ãŒã¯...' , content: 'ãŠãŒã¶ãŒã¯...' } { type : 'thinking' , delta: 'ã«ã€ããŠè³ªåããŠ' , content: 'ãŠãŒã¶ãŒã¯...ã«ã€ããŠè³ªåããŠ' } { type : 'content' , delta: 'ããã«ã¡ã¯ïŒ' , content: 'ããã«ã¡ã¯ïŒ' } { type : 'content' , delta: 'ã質åã«' , content: 'ããã«ã¡ã¯ïŒã質åã«' } { type : 'done' , finishReason: 'end_turn' } UI åŽã§ thinking ã¿ã€ãã®ãã£ã³ã¯ãå¥é衚瀺ããããšã§ãAI ã®æèéçšãå¯èŠåã§ããŸãã ãã¡ã€ã«æ·»ä»ã®ãµããŒã ããã ãã ãšé¢çœããªãã®ã§ããã¡ã€ã«æ·»ä»ããµããŒãããŠã¿ãŸãã ãã¡ãããµãŒããŒãµã€ãã TanStack AI ã®å Žåã¯ã«ã¹ã¿ã ãé£ãããã§ãããä»åã¯ãµãŒãã«ã€ããŠã¯Bedrockã® SDK ãå©çšããŠããã ããªã®ã§ãã¯ã©ã€ã¢ã³ãåŽã§ãã¡ã€ã«æ
å ±ã管çãã倿Žãã»ãšãã©ã§ãã ãªããBedrock ã§ã¯ Claude ç³»ã®ã¢ãã«ã®å Žå以äžã®åœ¢åŒããµããŒãããŠããŸãã ã«ããŽãª åœ¢åŒ ç»å JPEG , PNG , GIF, WebP ããã¥ã¡ã³ã PDF, CSV , DOC, DOCX, XLS, XLSX, HTML, TXT, MD 1. æ·»ä»ãã¡ã€ã«ã®ç®¡ç TanStack AI ã® sendMessage ã¯ããã¹ãã®ã¿ãåãåããããæ·»ä»ãã¡ã€ã«ã¯å¥é管çããŸãã // æ·»ä»ãã¡ã€ã«ã®åå®çŸ© interface Attachment { type : 'image' | 'document' mediaType : string // 'image/png', 'application/pdf' ãªã© data : string // Base64 ãšã³ã³ãŒãæžã¿ããŒã¿ name : string } function App () { const [ pendingAttachments , setPendingAttachments ] = useState< Attachment []>( [] ) const attachmentsRef = useRef< Attachment []>( [] ) // ... } 2. ref ã䜿ã£ãæ·»ä»ãã¡ã€ã«ã®åãæž¡ã // ã«ã¹ã¿ã Connection AdapterïŒæ·»ä»ãã¡ã€ã«å¯Ÿå¿ïŒ function createBedrockConnection ( getAttachments : () => Attachment []): ConnectionAdapter { return { async * connect ( messages , _data , abortSignal ) { const chatMessages = messages. map (( msg , index ) => ( { role : msg.role, content : extractTextContent(msg), // æåŸã®ãŠãŒã¶ãŒã¡ãã»ãŒãžã«ã®ã¿æ·»ä»ãã¡ã€ã«ãä»äž ...(msg.role === 'user' && index === messages. length - 1 ? { attachments : getAttachments() } : {} ), } )) // fetch ã§ãµãŒããŒã«éä¿¡... } , } } // 䜿çšåŽ const connection = useMemo( () => createBedrockConnection(() => attachmentsRef. current ), [] ) 3. éä¿¡æã®ã¿ã€ãã³ã°å¶åŸ¡ sendMessage ãåŒã¶ åã« attachmentsRef ã§ç®¡çãããã¡ã€ã«ãéä¿¡ããŸãã const handleSendMessage = async () => { // éä¿¡åã« attachmentsRef ãèšå®ïŒconnect 颿°ããåç
§ãããïŒ attachmentsRef. current = [ ...pendingAttachments ] // UI 衚瀺çšã«ä¿å setAttachments(( prev ) => [ ...prev, ...pendingAttachments ] ) // å
¥åãã¯ãªã¢ setInput( '' ) setPendingAttachments( [] ) // ã¡ãã»ãŒãžéä¿¡ïŒãã®æç¹ã§ connect ãåŒã°ããattachmentsRef ãåç
§ïŒ await sendMessage(messageText) // éä¿¡å®äºåŸã«ã¯ãªã¢ attachmentsRef. current = [] } 4. ãµãŒãåŽã§ã®æ·»ä» ãµãŒããŒåŽã§ã¯ãæ·»ä»ãã¡ã€ã«ã远å ãã formatMessages颿°ãèšãã Converse API ã«æž¡ãåã«ã¡ãã»ãŒãžãæŽåœ¢ããŸãã private formatMessages(messages: ChatMessage[]): BedrockMessage[] { return messages. map (( msg ) => { const content: ContentBlock [] = [] // ããã¹ãã³ã³ãã³ã if (msg.content) { content. push ( { text : msg.content } ) } // æ·»ä»ãã¡ã€ã« if (msg.attachments) { for ( const attachment of msg.attachments) { if (attachment. type === 'image' ) { content. push ( { image : { format : this .getImageFormat(attachment.mediaType), source : { bytes : Buffer .from(attachment.data, 'base64' ) } , } , } ) } else if (attachment. type === 'document' ) { content. push ( { document : { format : this .getDocumentFormat(attachment.mediaType), name : sanitizeDocumentName(attachment. name ), source : { bytes : Buffer .from(attachment.data, 'base64' ) } , } , } ) } } } return { role : msg.role, content } } ) } ã㢠ãŸãšã TanStack AI ãš Amazon Bedrock ãçµã¿åãããããšã§ã以äžã®ã¡ãªãããåŸãããŸã TanStack AI ã«ããåå®å
šæ§ãReact ããã¯ãã¹ããªãŒãã³ã°å¶åŸ¡ Bedrock ã® ãšã³ã¿ãŒãã©ã€ãº æ©èœïŒIAM èªèšŒã VPC çµ±åãã¬ããã³ã¹ïŒ ã«ã¹ã¿ã ã¢ããã¿ãŒã®å®è£
ã¯æéãããããŸãããTanStack AI ã®èšèšææ³ïŒAsyncGenerator ãã¿ãŒã³ãStreamChunk åïŒã«åŸãã°æ¯èŒçã·ã³ãã«ã«å®è£
ã§ããŸãã TanStack AI ã¯ãŸã ã¢ã«ãã¡çã§ãããå°æ¥çã«ã¯å
¬åŒã§ Bedrock ã¢ããã¿ãŒããµããŒããããå¯èœæ§ããããŸãããããŸã§ã¯ãä»å玹ä»ãããããªã«ã¹ã¿ã ã¢ããã¿ãŒã§å¯Ÿå¿ã§ããŸãã ãã ãçµè«ãšããŠãçŸæç¹ã§ Bedrockçå
¬åŒã«æ¥ç¶ãæäŸãããŠããªãAIåºç€ãå©çšããå Žåã¯Vercel AI SDK ãå©çšããã»ããç°¡æœãªæ§æã«ã§ããŸãã TanStack AIã®äžçªã®èª²é¡ãšããŠãæ¥ç¶å
ã®ã¢ããã¿ãèªåã§æ§ããå¿
èŠãããç¹ãçŸæç¹ã§ããªãã® ããã«ãã㯠ãšãªããŸãã ããããVercel ãšã³ã·ã¹ãã ã«ããã¯ã€ã³ãããŠããŸããªãããã«ããTanStack AI ã®ä»åŸã®é²æ©ã«ã¯æåŸ
ããŠããŸãã 以äžãæèªããããšãããããŸããã ç§ãã¡ã¯äžç·ã«åããŠããã仲éãåéããŠããŸãïŒ é»éç·ç ãã£ãªã¢æ¡çšãµã€ã å·çïŒ @kamino.shunichiro ã¬ãã¥ãŒïŒ Ishizawa Kento (@kent) ïŒ Shodo ã§å·çãããŸãã ïŒ