Next.js + AI-SDK 实战:模型注册表从类型设计到工具调用全解析

前言

在 AI 应用开发中,当你需要同时集成 OpenAI、Google、DeepSeek、Ollama 等多个厂商的模型时,是否常遇到这些问题:

  • 不同提供商的接口格式、调用方式千差万别,代码里充斥着大量条件判断;
  • 模型的工具调用能力、推理特性各不相同,适配逻辑散落在业务代码中;
  • 配置文件改了就得重新部署,动态启用 / 禁用模型全靠硬编码;
  • 新接入一个模型时,要从头写一套适配逻辑,重复劳动且容易出错。

这些问题的核心,在于缺乏一套统一的模型管理机制 ------ 而这正是本文要分享的「模型注册表系统」的设计初衷。

小nuo基于 Next.js 和 AI-SDK,从零构建了一套可复用的模型管理方案:通过 TypeScript 类型约束确保配置安全,用动态加载 + 降级策略实现配置灵活更新,以注册表模式屏蔽多厂商接口差异,再针对工具调用、推理模型等场景设计专用逻辑。

无论你是正在开发多模型 AI 应用的开发者,还是想了解如何在 Next.js 中规范化管理 AI 资源,小nuo带你从实际问题出发,拆解从类型定义到工具调用的全链路实现,最终获得一套可直接复用的模型管理方案。

第一部分:模型类型定义和配置格式

1.1 Model 接口设计原理 /lib/types/models.ts

这个接口设计的每个字段都有特定用途:

TypeScript 复制代码
export interface Model {
    id:             string
    name:           string
    provider:       string
    providerId:     string
    enabled:        boolean
    toolCallType:  'native' | 'manual'
    toolCallModel?: string
}
  • id : 模型的唯一标识符,用于在代码中引用特定模型
  • name : 用户界面显示的友好名称
  • provider : 提供商的完整名称(如 "Google Generative AI")
  • providerId : 提供商的简短标识符(如 "google"),用于代码逻辑判断
  • enabled : 布尔值,控制模型是否在 UI 中可选
  • toolCallType : 关键字段,区分模型的工具调用能力
  • toolCallModel: 可选字段,为不支持原生工具调用的模型指定替代模型

1.2 models.json 格式设计原理 /lib/config/models.json

JSON格式选择原因:

  1. 可读性: JSON 格式便于人工编辑和维护
  2. 动态加载: 可以在运行时动态获取,无需重新编译
  3. 版本控制友好: 文本格式便于 Git 跟踪变更
  4. 类型安全: 通过 TypeScript 接口确保数据结构正确
JSON 复制代码
{
  "models": [
    {
      "id": "deepseek-reasoner",
      "name": "DeepSeek R1",
      "provider": "DeepSeek",
      "providerId": "deepseek",
      "enabled": true,
      "toolCallType": "manual",
      "toolCallModel": "deepseek-chat"
    },
    {
      "id": "deepseek-chat",
      "name": "DeepSeek V3",
      "provider": "DeepSeek",
      "providerId": "deepseek",
      "enabled": true,
      "toolCallType": "manual"
    },
    {
      "id": "gpt-4.1",
      "name": "GPT-4.1",
      "provider": "OpenAI",
      "providerId": "openai",
      "enabled": true,
      "toolCallType": "native"
    },
    {
      "id": "gpt-4.1-mini",
      "name": "GPT-4.1 mini",
      "provider": "OpenAI",
      "providerId": "openai",
      "enabled": true,
      "toolCallType": "native"
    },
    {
      "id": "gpt-4.1-nano",
      "name": "GPT-4.1 nano",
      "provider": "OpenAI",
      "providerId": "openai",
      "enabled": true,
      "toolCallType": "native"
    },
    {
      "id": "o3-mini",
      "name": "o3 mini",
      "provider": "OpenAI",
      "providerId": "openai",
      "enabled": true,
      "toolCallType": "native"
    },
    {
      "id": "gpt-4o",
      "name": "GPT-4o",
      "provider": "OpenAI",
      "providerId": "openai",
      "enabled": true,
      "toolCallType": "native"
    },
    {
      "id": "gpt-4o-mini",
      "name": "GPT-4o mini",
      "provider": "OpenAI",
      "providerId": "openai",
      "enabled": true,
      "toolCallType": "native"
    }
  ]
}

每个模型对象的字段含义:

  • toolCallType 为 "native" 表示模型原生支持工具调用
  • toolCallType 为 "manual" 表示需要通过其他模型处理工具调用
  • toolCallModel 字段只在 "manual" 类型时需要,指定用于工具调用的模型

第二部分:模型配置加载系统

2.1 validateModel 函数 /lib/config/models.ts

用于验证模型对象是否接口要求

TypeScript 复制代码
// 验证模型
export function validateModel(model: Model) {
  // 验证流程
  return (
    typeof model.id === "string" &&
    typeof model.name === "string" &&
    typeof model.provider === "string" &&
    typeof model.providerId === "string" &&
    typeof model.enabled === "boolean" &&
    (model.toolCallType === "native" || model.toolCallType === "manual") &&
    model.toolCallModel == "string"
  );
}

2.2 getModels 函数详细实现 /lib/config/models.ts

TypeScript 复制代码
import { Model } from '@/lib/types/models'
import { getBaseUrl } from '@/lib/utils/url'
import defaultModels from './default-models.json'

export function validateModel(model: any): model is Model {
  return (
    typeof model.id === 'string' &&
    typeof model.name === 'string' &&
    typeof model.provider === 'string' &&
    typeof model.providerId === 'string' &&
    typeof model.enabled === 'boolean' &&
    (model.toolCallType === 'native' || model.toolCallType === 'manual') &&
    (model.toolCallModel === undefined ||
      typeof model.toolCallModel === 'string')
  )
}

export async function getModels(): Promise<Model[]> {
  try {
    // Get the base URL using the centralized utility function
    const baseUrlObj = await getBaseUrl()

    /**
     *Construct the models.json URL
     *Beacuse Next.js rules 
     *The path /config/models.json = public/config/models.json
     */
    const modelUrl = new URL('/config/models.json', baseUrlObj)
    console.log('Attempting to fetch models from:', modelUrl.toString())

    try {
      const response = await fetch(modelUrl, {
        cache: 'no-store',
        headers: {
          Accept: 'application/json'
        }
      })

      if (!response.ok) {
        console.warn(
          `HTTP error when fetching models: ${response.status} ${response.statusText}`
        )
        throw new Error(`HTTP error! status: ${response.status}`)
      }

      const text = await response.text()

      // Check if the response starts with HTML doctype
      if (text.trim().toLowerCase().startsWith('<!doctype')) {
        console.warn('Received HTML instead of JSON when fetching models')
        throw new Error('Received HTML instead of JSON')
      }

      const config = JSON.parse(text)
      if (Array.isArray(config.models) && config.models.every(validateModel)) {
        console.log('Successfully loaded models from URL')
        return config.models
      }
    } catch (error: any) {
      // Fallback to default models if fetch fails
      console.warn(
        'Fetch failed, falling back to default models:',
        error.message || 'Unknown error'
      )

      if (
        Array.isArray(defaultModels.models) &&
        defaultModels.models.every(validateModel)
      ) {
        console.log('Successfully loaded default models')
        return defaultModels.models
      }
    }
  } catch (error) {
    console.warn('Failed to load models:', error)
  }

  // Last resort: return empty array
  console.warn('All attempts to load models failed, returning empty array')
  return []
}

函数执行流程详解:

第20-21行: 获取基础URL,用于构建模型配置文件的完整路径

第24行 : 构建 /config/models.json 的完整URL

第25行: 记录尝试获取模型的URL,便于调试

第28-33行: 发起HTTP请求获取模型配置

  • cache: 'no-store' : 禁用缓存,确保获取最新配置
  • Accept: 'application/json' : 明确请求JSON格式

第35-40行: 检查HTTP响应状态

  • 如果响应不成功,记录警告并抛出错误

第42行: 获取响应文本内容

第45-48行: 检查响应是否为HTML而非JSON

  • 这是为了处理某些部署环境可能返回404页面的情况

第50-54行: 解析JSON并验证模型数组

  • 使用 validateModel 确保每个模型对象都符合接口要求

第55-68行: 错误处理和回退逻辑

  • 如果网络请求失败,回退到默认模型配置
  • 同样使用 validateModel 验证默认配置

第74-76行: 最后的安全网

  • 如果所有尝试都失败,返回空数组而不是崩溃

其中用到的工具函数 /lib/utils/url.ts

TypeScript 复制代码
import { headers } from 'next/headers'

/**
 * Helper function to get base URL from headers
 * Extracts URL information from Next.js request headers
 */
export async function getBaseUrlFromHeaders(): Promise<URL> {
  const headersList = await headers()
  const baseUrl = headersList.get('x-base-url')
  const url = headersList.get('x-url')
  const host = headersList.get('x-host')
  const protocol = headersList.get('x-protocol') || 'http:'

  try {
    // Try to use the pre-constructed base URL if available
    if (baseUrl) {
      return new URL(baseUrl)
    } else if (url) {
      return new URL(url)
    } else if (host) {
      const constructedUrl = `${protocol}${
        protocol.endsWith(':') ? '//' : '://'
      }${host}`
      return new URL(constructedUrl)
    } else {
      return new URL('<http://localhost:3000>')
    }
  } catch (urlError) {
    // Fallback to default URL if any error occurs during URL construction
    return new URL('<http://localhost:3000>')
  }
}

/**
 * Resolves the base URL using environment variables or headers
 * Centralizes the base URL resolution logic used across the application
 * @returns A URL object representing the base URL
 */
export async function getBaseUrl(): Promise<URL> {
  // Check for environment variables first
  const baseUrlEnv = process.env.NEXT_PUBLIC_BASE_URL || process.env.BASE_URL
  
  if (baseUrlEnv) {
    try {
      const baseUrlObj = new URL(baseUrlEnv)
      console.log('Using BASE_URL environment variable:', baseUrlEnv)
      return baseUrlObj
    } catch (error) {
      console.warn(
        'Invalid BASE_URL environment variable, falling back to headers'
      )
      // Fall back to headers if the environment variable is invalid
    }
  }
  
  // If no valid environment variable is available, use headers
  return await getBaseUrlFromHeaders()
}

/**
 * Gets the base URL as a string
 * Convenience wrapper around getBaseUrl that returns a string
 * @returns A string representation of the base URL
 */
export async function getBaseUrlString(): Promise<string> {
  const baseUrlObj = await getBaseUrl()
  return baseUrlObj.toString()
}

第三部分:提供商注册表系统

3.1 registry 创建详解 /lib/utils/registry.ts

TypeScript 复制代码
import { anthropic } from '@ai-sdk/anthropic'
import { createAzure } from '@ai-sdk/azure'
import { deepseek } from '@ai-sdk/deepseek'
import { createFireworks, fireworks } from '@ai-sdk/fireworks'
import { google } from '@ai-sdk/google'
import { groq } from '@ai-sdk/groq'
import { createOpenAI, openai } from '@ai-sdk/openai'
import { xai } from '@ai-sdk/xai'
import {
  createProviderRegistry,
  extractReasoningMiddleware,
  wrapLanguageModel
} from 'ai'
import { createOllama } from 'ollama-ai-provider'

export const registry = createProviderRegistry({
  openai,
  anthropic,
  google,
  groq,
  ollama: createOllama({
    baseURL: `${process.env.OLLAMA_BASE_URL}/api`
  }),
  azure: createAzure({
    apiKey: process.env.AZURE_API_KEY,
    resourceName: process.env.AZURE_RESOURCE_NAME,
    apiVersion: '2025-03-01-preview'
  }),
  deepseek,
  fireworks: {
    ...createFireworks({
      apiKey: process.env.FIREWORKS_API_KEY
    }),
    languageModel: fireworks
  },
  'openai-compatible': createOpenAI({
    apiKey: process.env.OPENAI_COMPATIBLE_API_KEY,
    baseURL: process.env.OPENAI_COMPATIBLE_API_BASE_URL
  }),
  xai
})

export function getModel(model: string) {
  const [provider, ...modelNameParts] = model.split(':') ?? []
  const modelName = modelNameParts.join(':')
  if (model.includes('ollama')) {
    const ollama = createOllama({
      baseURL: `${process.env.OLLAMA_BASE_URL}/api`
    })

    // if model is deepseek-r1, add reasoning middleware
    if (model.includes('deepseek-r1')) {
      return wrapLanguageModel({
        model: ollama(modelName),
        middleware: extractReasoningMiddleware({
          tagName: 'think'
        })
      })
    }

    // if ollama provider, set simulateStreaming to true
    return ollama(modelName, {
      simulateStreaming: true
    })
  }

  // if model is groq and includes deepseek-r1, add reasoning middleware
  if (model.includes('groq') && model.includes('deepseek-r1')) {
    return wrapLanguageModel({
      model: groq(modelName),
      middleware: extractReasoningMiddleware({
        tagName: 'think'
      })
    })
  }

  // if model is fireworks and includes deepseek-r1, add reasoning middleware
  if (model.includes('fireworks') && model.includes('deepseek-r1')) {
    return wrapLanguageModel({
      model: fireworks(modelName),
      middleware: extractReasoningMiddleware({
        tagName: 'think'
      })
    })
  }

  return registry.languageModel(
    model as Parameters<typeof registry.languageModel>[0]
  )
}

export function isProviderEnabled(providerId: string): boolean {
  switch (providerId) {
    case 'openai':
      return !!process.env.OPENAI_API_KEY
    case 'anthropic':
      return !!process.env.ANTHROPIC_API_KEY
    case 'google':
      return !!process.env.GOOGLE_GENERATIVE_AI_API_KEY
    case 'groq':
      return !!process.env.GROQ_API_KEY
    case 'ollama':
      return !!process.env.OLLAMA_BASE_URL
    case 'azure':
      return !!process.env.AZURE_API_KEY && !!process.env.AZURE_RESOURCE_NAME
    case 'deepseek':
      return !!process.env.DEEPSEEK_API_KEY
    case 'fireworks':
      return !!process.env.FIREWORKS_API_KEY
    case 'xai':
      return !!process.env.XAI_API_KEY
    case 'openai-compatible':
      return (
        !!process.env.OPENAI_COMPATIBLE_API_KEY &&
        !!process.env.OPENAI_COMPATIBLE_API_BASE_URL
      )
    default:
      return false
  }
}

export function getToolCallModel(model?: string) {
  const [provider, ...modelNameParts] = model?.split(':') ?? []
  const modelName = modelNameParts.join(':')
  switch (provider) {
    case 'deepseek':
      return getModel('deepseek:deepseek-chat')
    case 'fireworks':
      return getModel(
        'fireworks:accounts/fireworks/models/llama-v3p1-8b-instruct'
      )
    case 'groq':
      return getModel('groq:llama-3.1-8b-instant')
    case 'ollama':
      const ollamaModel =
        process.env.NEXT_PUBLIC_OLLAMA_TOOL_CALL_MODEL || modelName
      return getModel(`ollama:${ollamaModel}`)
    case 'google':
      return getModel('google:gemini-2.0-flash')
    default:
      return getModel('openai:gpt-4o-mini')
  }
}

export function isToolCallSupported(model?: string) {
  const [provider, ...modelNameParts] = model?.split(':') ?? []
  const modelName = modelNameParts.join(':')

  if (provider === 'ollama') {
    return false
  }

  if (provider === 'google') {
    return false
  }

  // Deepseek R1 is not supported
  // Deepseek v3's tool call is unstable, so we include it in the list
  return !modelName?.includes('deepseek')
}

export function isReasoningModel(model: string): boolean {
  if (typeof model !== 'string') {
    return false
  }
  return (
    model.includes('deepseek-r1') ||
    model.includes('deepseek-reasoner') ||
    model.includes('o3-mini')
  )
}

每个提供商的配置解析:

第17行 : openai - 直接使用 AI SDK 的 OpenAI 提供商

第18行 : anthropic - 直接使用 AI SDK 的 Anthropic 提供商

第19行 : google - 直接使用 AI SDK 的 Google 提供商

第20行 : groq - 直接使用 AI SDK 的 Groq 提供商

第21-23行 : ollama 配置

  • 使用 createOllama 创建自定义配置
  • baseURL 从环境变量 OLLAMA_BASE_URL 获取,添加 /api 后缀

第24-28行 : azure 配置

  • 需要 API 密钥和资源名称
  • 指定 API 版本为 '2025-03-01-preview'

第29行 : deepseek - 直接使用 AI SDK 的 DeepSeek 提供商

第30-35行 : fireworks 配置

  • 使用展开运算符合并 createFireworks 的结果
  • 额外添加 languageModel: fireworks 属性

第36-39行 : openai-compatible 配置

  • 支持 OpenAI 兼容的 API
  • 需要自定义 API 密钥和基础URL

第40行 : xai - 直接使用 AI SDK 的 xAI 提供商

3.2 getModel 函数详细解析 registry.ts:43-90

函数逐行解析:

TypeScript 复制代码
// ...
// /lib/utils/registry.ts
export function getModel(model: string) {
  const [provider, ...modelNameParts] = model.split(':') ?? []
  const modelName = modelNameParts.join(':')
  if (model.includes('ollama')) {
    const ollama = createOllama({
      baseURL: `${process.env.OLLAMA_BASE_URL}/api`
    })

    // if model is deepseek-r1, add reasoning middleware
    if (model.includes('deepseek-r1')) {
      return wrapLanguageModel({
        model: ollama(modelName),
        middleware: extractReasoningMiddleware({
          tagName: 'think'
        })
      })
    }

    // if ollama provider, set simulateStreaming to true
    return ollama(modelName, {
      simulateStreaming: true
    })
  }

  // if model is groq and includes deepseek-r1, add reasoning middleware
  if (model.includes('groq') && model.includes('deepseek-r1')) {
    return wrapLanguageModel({
      model: groq(modelName),
      middleware: extractReasoningMiddleware({
        tagName: 'think'
      })
    })
  }

  // if model is fireworks and includes deepseek-r1, add reasoning middleware
  if (model.includes('fireworks') && model.includes('deepseek-r1')) {
    return wrapLanguageModel({
      model: fireworks(modelName),
      middleware: extractReasoningMiddleware({
        tagName: 'think'
      })
    })
  }

  return registry.languageModel(
    model as Parameters<typeof registry.languageModel>[0]
  )
}
// ...

第44行: 使用解构赋值分割模型字符串

  • model.split(':') 将 "provider:model-name" 分割
  • [provider, ...modelNameParts] 获取提供商和模型名部分

第45行: 重新组合模型名称

  • 处理模型名称中可能包含冒号的情况

第46-65行: Ollama 特殊处理

  • 第47-49行:创建 Ollama 实例,配置基础URL

  • 第52-59行:DeepSeek R1 推理模型处理

    • 使用 wrapLanguageModel 包装模型
    • 添加 extractReasoningMiddleware 中间件
    • tagName: 'think' 指定推理标签
  • 第62-64行:普通 Ollama 模型处理

    • 设置 simulateStreaming: true 模拟流式输出

第67-75行: Groq + DeepSeek R1 处理

  • 检查是否为 Groq 提供商的 DeepSeek R1 模型
  • 同样应用推理中间件

第77-85行: Fireworks + DeepSeek R1 处理

  • 检查是否为 Fireworks 提供商的 DeepSeek R1 模型
  • 同样应用推理中间件

第87-89行: 默认处理

  • 使用注册表的 languageModel 方法获取模型实例
  • 类型断言确保参数类型正确

为什么 DeepSeek 需要其他提供商模型应用推理中间件?

从模型注册表的实现来看,DeepSeek 提供商是直接注册的,没有特殊包装逻辑。

但是在 model.json 配置中,我们可以看到 DeepSeek 的模型配置:toolCallType: "manual"

说明 DeepSeek 原生提供商本身没有推理中间件的包装逻辑。

这个返回语句的具体作用 registry.ts:87-89

这行代码的意图是:

  1. 调用注册表的标准方法 :使用 registry.languageModel() 方法获取模型实例
  2. 类型安全转换model as Parameters<typeof registry.languageModel>[0] 确保传入的模型字符串符合注册表方法期望的类型
  3. 处理标准提供商:对于 OpenAI、Anthropic、Google、DeepSeek 等不需要特殊处理的提供商,直接通过注册表获取模型

第四部分:提供商启用检查系统

4.1 isProviderEnabled 函数详解 registry.ts:92-120

每个 case 的环境变量检查:

TypeScript 复制代码
// /lib/utils/registry.ts
// ...
export function isProviderEnabled(providerId: string): boolean {
  switch (providerId) {
    case 'openai':
      return !!process.env.OPENAI_API_KEY
    case 'anthropic':
      return !!process.env.ANTHROPIC_API_KEY
    case 'google':
      return !!process.env.GOOGLE_GENERATIVE_AI_API_KEY
    case 'groq':
      return !!process.env.GROQ_API_KEY
    case 'ollama':
      return !!process.env.OLLAMA_BASE_URL
    case 'azure':
      return !!process.env.AZURE_API_KEY && !!process.env.AZURE_RESOURCE_NAME
    case 'deepseek':
      return !!process.env.DEEPSEEK_API_KEY
    case 'fireworks':
      return !!process.env.FIREWORKS_API_KEY
    case 'xai':
      return !!process.env.XAI_API_KEY
    case 'openai-compatible':
      return (
        !!process.env.OPENAI_COMPATIBLE_API_KEY &&
        !!process.env.OPENAI_COMPATIBLE_API_BASE_URL
      )
    default:
      return false
  }
}
// ...

第94-95行: OpenAI 检查

  • !!process.env.OPENAI_API_KEY 双重否定转换为布尔值
  • 只需要 API 密钥

第96-97行: Anthropic 检查

  • 只需要 ANTHROPIC_API_KEY

第98-99行: Google 检查

  • 需要 GOOGLE_GENERATIVE_AI_API_KEY

第100-101行: Groq 检查

  • 需要 GROQ_API_KEY

第102-103行: Ollama 检查

  • 需要 OLLAMA_BASE_URL,不是 API 密钥而是服务地址

第104-105行: Azure 检查

  • 需要两个环境变量:AZURE_API_KEYAZURE_RESOURCE_NAME
  • 使用 && 确保两个都存在

第106-107行: DeepSeek 检查

  • 需要 DEEPSEEK_API_KEY

第108-109行: Fireworks 检查

  • 需要 FIREWORKS_API_KEY

第110-111行: xAI 检查

  • 需要 XAI_API_KEY

第112-116行: OpenAI 兼容检查

  • 需要两个环境变量:API 密钥和基础URL
  • 使用括号和 && 确保两个都存在

第117-118行: 默认情况

  • 返回 false,未知提供商默认不启用

第五部分:工具调用支持系统

5.1 getToolCallModel 函数详解 registry.ts:122-143

函数逐行解析:

TypeScript 复制代码
export function getToolCallModel(model?: string) {
  const [provider, ...modelNameParts] = model?.split(':') ?? []
  const modelName = modelNameParts.join(':')
  switch (provider) {
    case 'deepseek':
      return getModel('deepseek:deepseek-chat')
    case 'fireworks':
      return getModel(
        'fireworks:accounts/fireworks/models/llama-v3p1-8b-instruct'
      )
    case 'groq':
      return getModel('groq:llama-3.1-8b-instant')
    case 'ollama':
      const ollamaModel =
        process.env.NEXT_PUBLIC_OLLAMA_TOOL_CALL_MODEL || modelName
      return getModel(`ollama:${ollamaModel}`)
    case 'google':
      return getModel('google:gemini-2.0-flash')
    default:
      return getModel('openai:gpt-4o-mini')
  }
}

第123-124行 : 解析模型字符串,与 getModel 函数相同的逻辑

第125-142行: 根据提供商选择工具调用模型

  • 第126-127行 : DeepSeek 使用 deepseek-chat 作为工具调用模型

  • 第128-131行 : Fireworks 使用 llama-v3p1-8b-instruct 模型

  • 第132-133行 : Groq 使用 llama-3.1-8b-instant 模型

  • 第134-137行: Ollama 特殊处理

    • 优先使用环境变量 NEXT_PUBLIC_OLLAMA_TOOL_CALL_MODEL
    • 如果未设置,使用原模型名称
  • 第138-139行 : Google 使用 gemini-2.0-flash 作为工具调用模型

  • 第140-141行 : 默认情况使用 gpt-4o-mini

5.2 isToolCallSupported 函数详解 registry.ts:145-160

函数逐行解析:

TypeScript 复制代码
export function isToolCallSupported(model?: string) {
  const [provider, ...modelNameParts] = model?.split(':') ?? []
  const modelName = modelNameParts.join(':')

  if (provider === 'ollama') {
    return false
  }

  if (provider === 'google') {
    return false
  }

  // Deepseek R1 is not supported
  // Deepseek v3's tool call is unstable, so we include it in the list
  return !modelName?.includes('deepseek')
}

第146-147行: 解析模型字符串

第149-151行: Ollama 提供商检查

  • Ollama 模型不支持原生工具调用,返回 false

第153-155行: Google 提供商检查

  • Google 模型不支持原生工具调用,返回 false

第157-159行: DeepSeek 模型检查

  • DeepSeek R1 不支持工具调用
  • DeepSeek V3 的工具调用不稳定,也标记为不支持
  • 使用 !modelName?.includes('deepseek') 检查

第六部分:推理模型支持系统

6.1 isReasoningModel 函数详解 registry.ts:162-171

TypeScript 复制代码
// registry.ts
// ...
export function isReasoningModel(model: string): boolean {
  if (typeof model !== 'string') {
    return false
  }
  return (
    model.includes('deepseek-r1') ||
    model.includes('deepseek-reasoner') ||
    model.includes('o3-mini')
  )
}
// ...

函数逐行解析:

第163-165行: 类型检查

  • 确保输入是字符串类型
  • 如果不是字符串,返回 false

第166-170行: 推理模型模式匹配

  • 检查模型名称是否包含 deepseek-r1
  • 检查模型名称是否包含 deepseek-reasoner
  • 检查模型名称是否包含 o3-mini
  • 使用 || 操作符,任一条件满足即返回 true

总结

这个模型注册表系统的设计体现了以下关键原则:

  1. 类型安全: 通过 TypeScript 接口和验证函数确保数据完整性
  2. 配置灵活性: 支持动态配置加载和回退机制
  3. 提供商抽象: 统一接口处理不同 AI 提供商的差异
  4. 特殊处理: 为推理模型和工具调用提供专门的逻辑
  5. 环境驱动: 基于环境变量的运行时配置

每个函数都有明确的职责,通过组合这些函数,系统能够灵活地管理多个 AI 提供商和模型,同时保持代码的可维护性和扩展性。

小nuo,目前大二还在努力学,对于AI-SDK也是刚刚上手,如果有更好的方法,欢迎在评论与我探讨,让我多多学习嘿嘿。

相关推荐
CaffeinePro26 分钟前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax1 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH1 小时前
Koa和Express的区别
后端
MariaH1 小时前
Koa框架的使用
后端
blanks20202 小时前
生成 公钥私钥 笔记
node.js
luckdewei2 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某3 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy4 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom4 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github
用户1474853079748 小时前
CodeX使用Skill生成游戏美术和音乐资源,一分钟入门
后端