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也是刚刚上手,如果有更好的方法,欢迎在评论与我探讨,让我多多学习嘿嘿。

相关推荐
KD10 小时前
设计模式——责任链模式实战,优雅处理Kafka消息
后端·设计模式·kafka
没逻辑16 小时前
gocron - 分布式定时任务管理系统
后端
程序猿DD17 小时前
人工智能如何改变 Anthropic 的工作方式
java·后端
桦说编程17 小时前
Guava Forwarding系列类详解——装饰器模式实战
java·后端·设计模式
VX:Fegn089518 小时前
计算机毕业设计|基于springboot + vue敬老院管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
算法与双吉汉堡18 小时前
【短链接项目笔记】Day2 用户注册
java·redis·笔记·后端·spring
Victor35618 小时前
Netty(18)Netty的内存模型
后端
Victor35618 小时前
Netty(17)Netty如何处理大量的并发连接?
后端
码事漫谈18 小时前
C++共享内存小白入门指南
后端