feat: add web search plugin for enhanced AI provider capabilities

- Introduced a new `webSearchPlugin` to provide unified web search functionality across multiple AI providers.
- Added helper functions for adapting web search parameters for OpenAI, Gemini, and Anthropic providers.
- Updated the built-in plugin index to export the new web search plugin and its configuration type.
- Created a new `helper.ts` file to encapsulate web search adaptation logic and support checks for provider compatibility.
This commit is contained in:
MyPrototypeWhat
2025-07-04 19:35:37 +08:00
parent 13162edcb2
commit 547e5785c0
4 changed files with 196 additions and 1 deletions
@@ -7,3 +7,4 @@ export const BUILT_IN_PLUGIN_PREFIX = 'built-in:'
export { createLoggingPlugin } from './logging'
export type { MCPPromptConfig, ToolUseResult } from './mcpPromptPlugin'
export { createMCPPromptPlugin } from './mcpPromptPlugin'
export { type WebSearchConfig, webSearchPlugin } from './webSearchPlugin'
@@ -0,0 +1,136 @@
/**
* 网络搜索助手函数
* 提取各个 ApiClient 中的网络搜索逻辑,提供统一的适配器
*/
import type { OpenAIProvider } from '@ai-sdk/openai'
// 派生自 OpenAI SDK 的标准工具入参类型
type WebSearchPreviewParams = Parameters<OpenAIProvider['tools']['webSearchPreview']>[0]
// 使用交叉类型合并,并为 extra 添加注释
export type WebSearchConfig = WebSearchPreviewParams & {
/**
* 扩展字段,用于提供给开发者自定义参数的能力
* 这些参数将被合并到对应 provider 的 providerOptions 中
*/
extra?: Record<string, any>
}
/**
* 适配 OpenAI 网络搜索
* 基于 Vercel AI SDK 的 web_search_preview 工具
*/
export function adaptOpenAIWebSearch(params: any, webSearchConfig: WebSearchConfig | boolean): any {
const config = typeof webSearchConfig === 'boolean' ? {} : webSearchConfig
const { extra, ...stdParams } = config
const webSearchTool = {
type: 'web_search_preview',
...stdParams
}
// 假设 params.tools 是一个数组或 undefined
const existingTools = Array.isArray(params.tools) ? params.tools : []
// 将 extra 参数添加到 providerOptions 中
const providerOptions = {
...params.providerOptions,
openai: {
...params.providerOptions?.openai,
...(extra || {})
}
}
return {
...params,
tools: [...existingTools, webSearchTool],
providerOptions
}
}
/**
* 适配 Gemini 网络搜索
* 将 googleSearch 工具放入 providerOptions.google.tools
*/
export function adaptGeminiWebSearch(params: any, webSearchConfig: WebSearchConfig | boolean): any {
const config = typeof webSearchConfig === 'boolean' ? {} : webSearchConfig
const googleSearchTool = { googleSearch: {} }
const existingTools = Array.isArray(params.providerOptions?.google?.tools) ? params.providerOptions.google.tools : []
return {
...params,
providerOptions: {
...params.providerOptions,
google: {
...params.providerOptions?.google,
tools: [...existingTools, googleSearchTool],
...(config.extra || {})
}
}
}
}
/**
* 适配 Anthropic 网络搜索
* 将 web_search_20250305 工具放入 providerOptions.anthropic.tools
*/
export function adaptAnthropicWebSearch(params: any, webSearchConfig: WebSearchConfig | boolean): any {
const config = typeof webSearchConfig === 'boolean' ? {} : webSearchConfig
const webSearchTool = {
type: 'web_search_20250305',
name: 'web_search',
max_uses: 5 // 默认值,可以通过 extra 覆盖
}
const existingTools = Array.isArray(params.providerOptions?.anthropic?.tools)
? params.providerOptions.anthropic.tools
: []
return {
...params,
providerOptions: {
...params.providerOptions,
anthropic: {
...params.providerOptions?.anthropic,
tools: [...existingTools, webSearchTool],
...(config.extra || {})
}
}
}
}
/**
* 通用网络搜索适配器
* 根据 providerId 选择对应的适配函数
*/
export function adaptWebSearchForProvider(
params: any,
providerId: string,
webSearchConfig: WebSearchConfig | boolean
): any {
switch (providerId) {
case 'openai':
return adaptOpenAIWebSearch(params, webSearchConfig)
case 'google':
case 'google-vertex':
return adaptGeminiWebSearch(params, webSearchConfig)
case 'anthropic':
return adaptAnthropicWebSearch(params, webSearchConfig)
default:
// 不支持的 provider,保持原样
return params
}
}
/**
* 检查 provider 是否支持网络搜索
*/
export function isWebSearchSupported(providerId: string): boolean {
const supportedProviders = ['openai', 'google', 'google-vertex', 'anthropic']
return supportedProviders.includes(providerId)
}
@@ -0,0 +1,58 @@
/**
* Web Search Plugin
* 提供统一的网络搜索能力,支持多个 AI Provider
*/
import { definePlugin } from '../../'
import type { AiRequestContext } from '../../types'
import { adaptWebSearchForProvider, isWebSearchSupported, type WebSearchConfig } from './helper'
/**
* 网络搜索插件
*
* 此插件会检查 params.providerOptions.[providerId].webSearch 来激活。
* options.ts 文件负责将高层级的设置(如 assistant.enableWebSearch
* 转换为 providerOptions 中的 webSearch: { enabled: true } 配置。
*/
export const webSearchPlugin = definePlugin({
name: 'webSearch',
transformParams: async (params: any, context: AiRequestContext) => {
const { providerId } = context
// 从 providerOptions 中提取 webSearch 配置
const webSearchConfig = params.providerOptions?.[providerId]?.webSearch
// 检查是否启用了网络搜索 (enabled: false 可用于显式禁用)
if (!webSearchConfig || (typeof webSearchConfig === 'object' && webSearchConfig.enabled === false)) {
return params
}
// 检查当前 provider 是否支持网络搜索
if (!isWebSearchSupported(providerId)) {
// 对于不支持的 provider,只记录警告,不修改参数
console.warn(
`[webSearchPlugin] Provider '${providerId}' does not support web search. Ignoring webSearch parameter.`
)
return params
}
// 使用适配器函数处理网络搜索
const adaptedParams = adaptWebSearchForProvider(params, providerId, webSearchConfig as WebSearchConfig | boolean)
// 清理原始的 webSearch 配置
if (adaptedParams.providerOptions?.[providerId]) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { webSearch, ...rest } = adaptedParams.providerOptions[providerId]
adaptedParams.providerOptions[providerId] = rest
}
return adaptedParams
}
})
// 导出类型定义供开发者使用
export type { WebSearchConfig } from './helper'
// 默认导出
export default webSearchPlugin
+1 -1
View File
@@ -126,6 +126,7 @@ export default class ModernAiProvider {
const plugins: AiPlugin[] = []
// 1. 总是添加通用插件
// plugins.push(textPlugin)
// plugins.push(webSearchPlugin)
// 2. 推理模型时添加推理插件
if (middlewareConfig.enableReasoning) {
@@ -138,7 +139,6 @@ export default class ModernAiProvider {
createMCPPromptPlugin({
enabled: true,
createSystemMessage: (systemPrompt, params, context) => {
console.log('createSystemMessage_context', context.isRecursiveCall)
if (context.modelId.includes('o1-mini') || context.modelId.includes('o1-preview')) {
if (context.isRecursiveCall) {
return null