文档
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
类引用
函数引用
接口引用
类型别名引用
变量引用
指南

运行时适配器切换

运行时适配器切换与类型安全

了解如何在运行时让用户在 LLM 提供商之间切换,同时保持完整的 TypeScript 类型安全。

简单的方法

使用 TanStack AI,模型直接传递给适配器工厂函数。这为您提供了定义点上的完整类型安全性和自动补全

typescript
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { anthropicText } from '@tanstack/ai-anthropic'
import { openaiText } from '@tanstack/ai-openai'

type Provider = 'openai' | 'anthropic'

// Define adapters with their models - autocomplete works here!
const adapters = {
  anthropic: () => anthropicText('claude-sonnet-4-5'),  // ✅ Autocomplete!
  openai: () => openaiText('gpt-5.2'),  // ✅ Autocomplete!
}

// In your request handler:
const provider: Provider = request.body.provider || 'openai'

const stream = chat({
  adapter: adapters[provider](),
  messages,
})

为什么这样有效

每个适配器工厂函数都将其第一个参数作为模型名称,并返回一个完全类型的适配器

typescript
// These are equivalent:
const adapter1 = openaiText('gpt-5.2')
const adapter2 = new OpenAITextAdapter({ apiKey: process.env.OPENAI_API_KEY }, 'gpt-5.2')

// The model is stored on the adapter
console.log(adapter1.selectedModel) // 'gpt-5.2'

当您将适配器传递给 chat() 时,它会使用来自 adapter.selectedModel 的模型。这意味着

  • 完整的自动补全 - 在键入模型名称时,TypeScript 知道有效的选项
  • 类型验证 - 无效的模型名称会导致编译错误
  • 简洁的代码 - 不需要单独的 model 参数

完整示例

这是一个完整的示例,展示了一个多提供商的聊天 API

typescript
import { createFileRoute } from '@tanstack/react-router'
import { chat, maxIterations, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { anthropicText } from '@tanstack/ai-anthropic'
import { geminiText } from '@tanstack/ai-gemini'
import { ollamaText } from '@tanstack/ai-ollama'

type Provider = 'openai' | 'anthropic' | 'gemini' | 'ollama'

// Define adapters with their models
const adapters = {
  anthropic: () => anthropicText('claude-sonnet-4-5'),
  gemini: () => geminiText('gemini-2.0-flash-exp'),
  ollama: () => ollamaText('mistral:7b'),
  openai: () => openaiText('gpt-5.2'),
}

export const Route = createFileRoute('/api/chat')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const abortController = new AbortController()
        const body = await request.json()
        const { messages, data } = body

        const provider: Provider = data?.provider || 'openai'

        const stream = chat({
          adapter: adapters[provider](),
          tools: [...],
          systemPrompts: [...],
          messages,
          abortController,
        })

        return toServerSentEventsResponse(stream, { abortController })
      },
    },
  },
})

与图像适配器一起使用

相同的模式适用于图像生成

typescript
import { generateImage } from '@tanstack/ai'
import { openaiImage } from '@tanstack/ai-openai'
import { geminiImage } from '@tanstack/ai-gemini'

const imageAdapters = {
  openai: () => openaiImage('gpt-image-1'),
  gemini: () => geminiImage('gemini-2.0-flash-preview-image-generation'),
}

// Usage
const result = await generateImage({
  adapter: imageAdapters[provider](),
  prompt: 'A beautiful sunset over mountains',
  size: '1024x1024',
})

与总结适配器一起使用

以及用于总结

typescript
import { summarize } from '@tanstack/ai'
import { openaiSummarize } from '@tanstack/ai-openai'
import { anthropicSummarize } from '@tanstack/ai-anthropic'

const summarizeAdapters = {
  openai: () => openaiSummarize('gpt-5-mini'),
  anthropic: () => anthropicSummarize('claude-sonnet-4-5'),
}

// Usage
const result = await summarize({
  adapter: summarizeAdapters[provider](),
  text: longDocument,
  maxLength: 100,
  style: 'concise',
})

从 Switch 语句迁移

如果您有使用 switch 语句的现有代码,以下是如何迁移

之前

typescript
let adapter
let model

switch (provider) {
  case 'anthropic':
    adapter = anthropicText()
    model = 'claude-sonnet-4-5'
    break
  case 'openai':
  default:
    adapter = openaiText()
    model = 'gpt-5.2'
    break
}

const stream = chat({
  adapter: adapter as any,
  model: model as any,
  messages,
})

之后

typescript
const adapters = {
  anthropic: () => anthropicText('claude-sonnet-4-5'),
  openai: () => openaiText('gpt-5.2'),
}

const stream = chat({
  adapter: adapters[provider](),
  messages,
})

关键的更改

  1. 用工厂函数对象替换 switch 语句
  2. 每个工厂函数创建一个包含模型的适配器
  3. 不再需要 as any 转换 - 完整的类型安全!