From 4ea8a065038e1cc7f1acddab2f67ebe58ccb9ced Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Wed, 4 Feb 2026 20:14:51 -0800 Subject: [PATCH] LLMs: auto-inject web search --- .../llms/server/anthropic/anthropic.models.ts | 15 +------- .../llms/server/listModels.dispatch.ts | 9 ++--- src/modules/llms/server/models.mappings.ts | 34 +++++++++++++++++++ .../server/openai/models/openai.models.ts | 25 +++++++------- .../server/openai/models/perplexity.models.ts | 10 +++--- .../llms/server/openai/models/xai.models.ts | 4 +-- 6 files changed, 60 insertions(+), 37 deletions(-) diff --git a/src/modules/llms/server/anthropic/anthropic.models.ts b/src/modules/llms/server/anthropic/anthropic.models.ts index dc7952432..ac2a5bd6c 100644 --- a/src/modules/llms/server/anthropic/anthropic.models.ts +++ b/src/modules/llms/server/anthropic/anthropic.models.ts @@ -1,6 +1,6 @@ import * as z from 'zod/v4'; -import { LLM_IF_ANT_PromptCaching, LLM_IF_ANT_ToolsSearch, LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Reasoning, LLM_IF_OAI_Vision, LLM_IF_Tools_WebSearch } from '~/common/stores/llms/llms.types'; +import { LLM_IF_ANT_PromptCaching, LLM_IF_ANT_ToolsSearch, LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Reasoning, LLM_IF_OAI_Vision } from '~/common/stores/llms/llms.types'; import { Release } from '~/common/app.release'; import type { ModelDescriptionSchema } from '../llm.server.types'; @@ -314,16 +314,3 @@ export function llmsAntCreatePlaceholderModel(model: AnthropicWire_API_Models_Li }; } -/** - * Injects the LLM_IF_Tools_WebSearch interface for models that have web search/fetch parameters. - * This allows the UI to show the web search indicator automatically based on model capabilities. - */ -export function llmsAntInjectWebSearchInterface(model: ModelDescriptionSchema): ModelDescriptionSchema { - const hasWebParams = model.parameterSpecs?.some(spec => - spec.paramId === 'llmVndAntWebSearch' || spec.paramId === 'llmVndAntWebFetch', - ); - return (hasWebParams && !model.interfaces?.includes(LLM_IF_Tools_WebSearch)) ? { - ...model, - interfaces: [...model.interfaces, LLM_IF_Tools_WebSearch], - } : model; -} diff --git a/src/modules/llms/server/listModels.dispatch.ts b/src/modules/llms/server/listModels.dispatch.ts index 91bccde1c..cad183334 100644 --- a/src/modules/llms/server/listModels.dispatch.ts +++ b/src/modules/llms/server/listModels.dispatch.ts @@ -8,10 +8,11 @@ import { createDebugWireLogger } from '~/server/wire'; import { fetchJsonOrTRPCThrow } from '~/server/trpc/trpc.router.fetchers'; import type { ModelDescriptionSchema } from './llm.server.types'; +import { llmsAutoInjectWebSearchInterface } from './models.mappings'; // protocol: Anthropic -import { anthropicInjectVariants, anthropicValidateModelDefs_DEV, AnthropicWire_API_Models_List, hardcodedAnthropicModels, llmsAntCreatePlaceholderModel, llmsAntInjectWebSearchInterface } from './anthropic/anthropic.models'; +import { anthropicInjectVariants, anthropicValidateModelDefs_DEV, AnthropicWire_API_Models_List, hardcodedAnthropicModels, llmsAntCreatePlaceholderModel } from './anthropic/anthropic.models'; import { ANTHROPIC_API_PATHS, anthropicAccess } from './anthropic/anthropic.access'; // protocol: Gemini @@ -69,7 +70,8 @@ function createDispatch(dispatch: ListModelsDispatch): ListModelsDispatch< export async function listModelsRunDispatch(access: AixAPI_Access, signal?: AbortSignal): Promise { const dispatch = _listModelsCreateDispatch(access, signal); const wireModels = await dispatch.fetchModels(); - return dispatch.convertToDescriptions(wireModels); + return dispatch.convertToDescriptions(wireModels) + .map(llmsAutoInjectWebSearchInterface); // unified way of auto-injecting cosmetic/derived IFs } @@ -146,8 +148,7 @@ function _listModelsCreateDispatch(access: AixAPI_Access, signal?: AbortSignal): return llmsAntCreatePlaceholderModel(model); }) // inject thinking variants using the centralized variant system - .reduce(anthropicInjectVariants, []) - .map(llmsAntInjectWebSearchInterface); + .reduce(anthropicInjectVariants, []); }, }); } diff --git a/src/modules/llms/server/models.mappings.ts b/src/modules/llms/server/models.mappings.ts index 05abcdfb6..c9fd5d1f4 100644 --- a/src/modules/llms/server/models.mappings.ts +++ b/src/modules/llms/server/models.mappings.ts @@ -1,6 +1,40 @@ +import type { DModelParameterId } from '~/common/stores/llms/llms.parameters'; +import { LLM_IF_Tools_WebSearch } from '~/common/stores/llms/llms.types'; + import type { ModelDescriptionSchema } from './llm.server.types'; +// -- Auto-inject web search interface -- + +/** + * Parameter IDs that imply the model supports web search capabilities. + * When any of these are present in parameterSpecs, LLM_IF_Tools_WebSearch is auto-added to interfaces. + */ +const _webSearchParamIds: string[] = [ + 'llmVndAntWebFetch', + 'llmVndAntWebSearch', + 'llmVndGeminiGoogleSearch', + 'llmVndMoonshotWebSearch', + 'llmVndOaiWebSearchContext', + 'llmVndOrtWebSearch', + 'llmVndPerplexitySearchMode', + 'llmVndXaiWebSearch', + 'llmVndXaiXSearch', +] as const satisfies DModelParameterId[]; + +/** + * Auto-injects LLM_IF_Tools_WebSearch for models that have web search/fetch parameters. + * Applied centrally in listModelsRunDispatch so individual vendors don't need to manage this. + */ +export function llmsAutoInjectWebSearchInterface(model: ModelDescriptionSchema): ModelDescriptionSchema { + const hasWebParams = model.parameterSpecs?.some(spec => _webSearchParamIds.includes(spec.paramId)); + return (hasWebParams && !model.interfaces?.includes(LLM_IF_Tools_WebSearch)) ? { + ...model, + interfaces: [...model.interfaces, LLM_IF_Tools_WebSearch], + } : model; +} + + // -- Dev model definitions check -- /** diff --git a/src/modules/llms/server/openai/models/openai.models.ts b/src/modules/llms/server/openai/models/openai.models.ts index 288f6211c..574b0eb68 100644 --- a/src/modules/llms/server/openai/models/openai.models.ts +++ b/src/modules/llms/server/openai/models/openai.models.ts @@ -1,6 +1,6 @@ import type { OpenAIWire_API_Models_List } from '~/modules/aix/server/dispatch/wiretypes/openai.wiretypes'; -import { DModelInterfaceV1, LLM_IF_HOTFIX_NoTemperature, LLM_IF_HOTFIX_StripImages, LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Json, LLM_IF_OAI_PromptCaching, LLM_IF_OAI_Reasoning, LLM_IF_OAI_Responses, LLM_IF_OAI_Vision, LLM_IF_Outputs_Audio, LLM_IF_Tools_WebSearch } from '~/common/stores/llms/llms.types'; +import { DModelInterfaceV1, LLM_IF_HOTFIX_NoTemperature, LLM_IF_HOTFIX_StripImages, LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Json, LLM_IF_OAI_PromptCaching, LLM_IF_OAI_Reasoning, LLM_IF_OAI_Responses, LLM_IF_OAI_Vision, LLM_IF_Outputs_Audio } from '~/common/stores/llms/llms.types'; import { Release } from '~/common/app.release'; import type { ModelDescriptionSchema } from '../../llm.server.types'; @@ -19,7 +19,7 @@ export const hardcodedOpenAIVariants: ModelVariantMap = { label: 'GPT-5.2 (No-thinking)', hidden: true, // hidden by default as redundant, user can unhide in settings description: 'Supports temperature control for creative applications. GPT-5.2 with reasoning disabled (reasoning_effort=none).', - interfaces: [LLM_IF_OAI_Responses, LLM_IF_OAI_Chat, LLM_IF_OAI_Vision, LLM_IF_OAI_Fn, LLM_IF_OAI_Json, LLM_IF_OAI_PromptCaching, LLM_IF_Tools_WebSearch], // NO LLM_IF_OAI_Reasoning, NO LLM_IF_HOTFIX_NoTemperature + interfaces: [LLM_IF_OAI_Responses, LLM_IF_OAI_Chat, LLM_IF_OAI_Vision, LLM_IF_OAI_Fn, LLM_IF_OAI_Json, LLM_IF_OAI_PromptCaching], // NO LLM_IF_OAI_Reasoning, NO LLM_IF_HOTFIX_NoTemperature parameterSpecs: [ { paramId: 'llmVndOaiReasoningEffort52', initialValue: 'none', hidden: true }, // factory 'none', not changeable { paramId: 'llmVndOaiWebSearchContext' }, @@ -59,6 +59,7 @@ const IFS_GPT_AUDIO: DModelInterfaceV1[] = [LLM_IF_OAI_Chat, LLM_IF_Outputs_Audi const IFS_CHAT_MIN: DModelInterfaceV1[] = [LLM_IF_OAI_Chat, LLM_IF_OAI_Vision, LLM_IF_OAI_Fn, LLM_IF_OAI_Json] as const; const IFS_CHAT_CACHE: DModelInterfaceV1[] = [LLM_IF_OAI_Chat, LLM_IF_OAI_Vision, LLM_IF_OAI_Fn, LLM_IF_OAI_Json, LLM_IF_OAI_PromptCaching] as const; const IFS_CHAT_CACHE_REASON: DModelInterfaceV1[] = [LLM_IF_OAI_Chat, LLM_IF_OAI_Vision, LLM_IF_OAI_Fn, LLM_IF_OAI_Json, LLM_IF_OAI_PromptCaching, LLM_IF_OAI_Reasoning] as const; +// NOTE: 'LLM_IF_Tools_WebSearch' is auto-injected by llmsAutoInjectWebSearchInterface() for models with web search parameterSpecs - no need to add it manually // per-type parameter specs const PS_DEEP_RESEARCH = [{ paramId: 'llmVndOaiWebSearchContext' as const, initialValue: 'medium', hidden: true } as const]; @@ -80,7 +81,7 @@ export const _knownOpenAIChatModels: ManualMappings = [ description: 'Most capable model for professional work and long-running agents. Improvements in general intelligence, long-context, agentic tool-calling, and vision.', contextWindow: 400000, maxCompletionTokens: 128000, - interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_Tools_WebSearch, LLM_IF_HOTFIX_NoTemperature], + interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_HOTFIX_NoTemperature], parameterSpecs: [ { paramId: 'llmVndOaiReasoningEffort52', initialValue: 'medium' /* our decision: set to medium to have thinking - clones can set to 'none' to have temperature */ }, { paramId: 'llmVndOaiWebSearchContext' }, @@ -122,7 +123,7 @@ export const _knownOpenAIChatModels: ManualMappings = [ description: 'GPT-5.2 model powering ChatGPT. Fast, capable for everyday work with clear improvements in info-seeking, how-tos, technical writing.', contextWindow: 128000, maxCompletionTokens: 16384, - interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE, LLM_IF_Tools_WebSearch, LLM_IF_HOTFIX_NoTemperature], + interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE, LLM_IF_HOTFIX_NoTemperature], parameterSpecs: [ { paramId: 'llmVndOaiWebSearchContext' }, // { paramId: 'llmVndOaiVerbosity' }, // 2026-01-20: still unsupported @@ -166,7 +167,7 @@ export const _knownOpenAIChatModels: ManualMappings = [ description: 'The best model for coding and agentic tasks with configurable reasoning effort.', contextWindow: 400000, maxCompletionTokens: 128000, - interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_Tools_WebSearch, LLM_IF_HOTFIX_NoTemperature], + interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_HOTFIX_NoTemperature], parameterSpecs: [ { paramId: 'llmVndOaiReasoningEffort4' }, { paramId: 'llmVndOaiWebSearchContext' }, { paramId: 'llmVndOaiVerbosity' }, @@ -191,7 +192,7 @@ export const _knownOpenAIChatModels: ManualMappings = [ contextWindow: 128000, maxCompletionTokens: 16384, // interfaces: [LLM_IF_OAI_Responses, LLM_IF_OAI_Chat, LLM_IF_OAI_Vision, LLM_IF_OAI_PromptCaching], // no function calling or reasoning - interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_Tools_WebSearch, LLM_IF_HOTFIX_NoTemperature], + interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_HOTFIX_NoTemperature], parameterSpecs: [ { paramId: 'llmVndOaiWebSearchContext' }, // { paramId: 'llmVndOaiVerbosity' }, // 2026-01-20: still unsupported @@ -258,7 +259,7 @@ export const _knownOpenAIChatModels: ManualMappings = [ description: 'The best model for coding and agentic tasks across domains.', contextWindow: 400000, maxCompletionTokens: 128000, - interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_Tools_WebSearch, LLM_IF_HOTFIX_NoTemperature], + interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_HOTFIX_NoTemperature], parameterSpecs: [ { paramId: 'llmVndOaiReasoningEffort4' }, { paramId: 'llmVndOaiWebSearchContext' }, { paramId: 'llmVndOaiVerbosity' }, // gpt-5-class nets have verbosity control @@ -312,7 +313,7 @@ export const _knownOpenAIChatModels: ManualMappings = [ description: 'A version of GPT-5 optimized for agentic coding in Codex.', contextWindow: 400000, maxCompletionTokens: 128000, - interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_Tools_WebSearch, LLM_IF_HOTFIX_NoTemperature], + interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_HOTFIX_NoTemperature], parameterSpecs: [ { paramId: 'llmVndOaiReasoningEffort' }, // works { paramId: 'llmVndOaiWebSearchContext' }, // works, although is not triggered often @@ -330,7 +331,7 @@ export const _knownOpenAIChatModels: ManualMappings = [ description: 'Updated web search model in Chat Completions API. 60% cheaper with domain filtering support.', contextWindow: 400000, maxCompletionTokens: 100000, - interfaces: [...IFS_CHAT_MIN, LLM_IF_Tools_WebSearch], + interfaces: IFS_CHAT_MIN, parameterSpecs: [{ paramId: 'llmVndOaiWebSearchContext', initialValue: 'medium' }], // Search enabled by default chatPrice: { input: 1.25, cache: { cType: 'oai-ac', read: 0.125 }, output: 10 }, // benchmark: TBD @@ -348,7 +349,7 @@ export const _knownOpenAIChatModels: ManualMappings = [ description: 'A faster, more cost-efficient version of GPT-5 for well-defined tasks.', contextWindow: 400000, maxCompletionTokens: 128000, - interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_Tools_WebSearch, LLM_IF_HOTFIX_NoTemperature], + interfaces: [LLM_IF_OAI_Responses, ...IFS_CHAT_CACHE_REASON, LLM_IF_HOTFIX_NoTemperature], parameterSpecs: [{ paramId: 'llmVndOaiReasoningEffort4' }, { paramId: 'llmVndOaiWebSearchContext' }, { paramId: 'llmVndOaiVerbosity' }, { paramId: 'llmVndOaiImageGeneration' }, { paramId: 'llmForceNoStream' }], chatPrice: { input: 0.25, cache: { cType: 'oai-ac', read: 0.025 }, output: 2 }, benchmark: { cbaElo: 1390 }, // gpt-5-mini-high @@ -733,7 +734,7 @@ export const _knownOpenAIChatModels: ManualMappings = [ description: 'Latest snapshot of the GPT-4o model optimized for web search capabilities.', contextWindow: 128000, maxCompletionTokens: 16384, - interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Json, LLM_IF_Tools_WebSearch, LLM_IF_HOTFIX_NoTemperature], // NOTE: 2025-03-15: confirmed on 'playground' that this model does not support images + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Json, LLM_IF_HOTFIX_NoTemperature], // NOTE: 2025-03-15: confirmed on 'playground' that this model does not support images parameterSpecs: [{ paramId: 'llmVndOaiWebSearchContext' }, { paramId: 'llmVndOaiWebSearchGeolocation' }], chatPrice: { input: 2.5, output: 10 }, // benchmarks don't apply to search models @@ -817,7 +818,7 @@ export const _knownOpenAIChatModels: ManualMappings = [ description: 'Latest snapshot of the GPT-4o Mini model optimized for web search capabilities.', contextWindow: 128000, maxCompletionTokens: 16384, - interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Json, LLM_IF_Tools_WebSearch, LLM_IF_HOTFIX_NoTemperature], // NOTE: this support function calling, but only its own, not a Custom Function + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Json, LLM_IF_HOTFIX_NoTemperature], // NOTE: this support function calling, but only its own, not a Custom Function parameterSpecs: [{ paramId: 'llmVndOaiWebSearchContext' }, { paramId: 'llmVndOaiWebSearchGeolocation' }], chatPrice: { input: 0.15, output: 0.6 }, // benchmarks don't apply to search models diff --git a/src/modules/llms/server/openai/models/perplexity.models.ts b/src/modules/llms/server/openai/models/perplexity.models.ts index 3f49879ba..8b6586e08 100644 --- a/src/modules/llms/server/openai/models/perplexity.models.ts +++ b/src/modules/llms/server/openai/models/perplexity.models.ts @@ -1,7 +1,7 @@ import type { ModelDescriptionSchema } from '../../llm.server.types'; import { createVariantInjector, ModelVariantMap } from '../../llm.server.variants'; -import { LLM_IF_OAI_Chat, LLM_IF_OAI_Reasoning, LLM_IF_Tools_WebSearch } from '~/common/stores/llms/llms.types'; +import { LLM_IF_OAI_Chat, LLM_IF_OAI_Reasoning } from '~/common/stores/llms/llms.types'; // configuration @@ -38,7 +38,7 @@ const _knownPerplexityChatModels: ModelDescriptionSchema[] = [ label: 'Sonar Deep Research', description: 'Expert-level research model for exhaustive searches and comprehensive reports. 128k context.', contextWindow: 128000, - interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Reasoning, LLM_IF_Tools_WebSearch], + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Reasoning], parameterSpecs: [ { paramId: 'llmVndOaiReasoningEffort' }, // REUSE! { paramId: 'llmVndOaiWebSearchContext', initialValue: 'low' }, // REUSE! @@ -58,7 +58,7 @@ const _knownPerplexityChatModels: ModelDescriptionSchema[] = [ label: 'Sonar Reasoning Pro', description: 'Premier reasoning model (DeepSeek R1) with Chain of Thought. 128k context.', contextWindow: 128000, - interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Reasoning, LLM_IF_Tools_WebSearch], + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Reasoning], parameterSpecs: [ { paramId: 'llmVndOaiWebSearchContext', initialValue: 'low' }, // REUSE! { paramId: 'llmVndPerplexitySearchMode' }, @@ -78,7 +78,7 @@ const _knownPerplexityChatModels: ModelDescriptionSchema[] = [ description: 'Advanced search model for complex queries and deep content understanding. 200k context.', contextWindow: 200000, maxCompletionTokens: 8000, - interfaces: [LLM_IF_OAI_Chat, LLM_IF_Tools_WebSearch], + interfaces: [LLM_IF_OAI_Chat], parameterSpecs: [ { paramId: 'llmVndOaiWebSearchContext', initialValue: 'low' }, // REUSE! { paramId: 'llmVndPerplexitySearchMode' }, @@ -95,7 +95,7 @@ const _knownPerplexityChatModels: ModelDescriptionSchema[] = [ label: 'Sonar', description: 'Lightweight, cost-effective search model for quick, grounded answers. 128k context.', contextWindow: 128000, - interfaces: [LLM_IF_OAI_Chat, LLM_IF_Tools_WebSearch], + interfaces: [LLM_IF_OAI_Chat], parameterSpecs: [ { paramId: 'llmVndOaiWebSearchContext', initialValue: 'low' }, // REUSE! { paramId: 'llmVndPerplexitySearchMode' }, diff --git a/src/modules/llms/server/openai/models/xai.models.ts b/src/modules/llms/server/openai/models/xai.models.ts index 988390bb6..3a47c8846 100644 --- a/src/modules/llms/server/openai/models/xai.models.ts +++ b/src/modules/llms/server/openai/models/xai.models.ts @@ -2,7 +2,7 @@ import * as z from 'zod/v4'; import { fetchJsonOrTRPCThrow } from '~/server/trpc/trpc.router.fetchers'; -import { LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Json, LLM_IF_OAI_Reasoning, LLM_IF_OAI_Vision, LLM_IF_Tools_WebSearch } from '~/common/stores/llms/llms.types'; +import { LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Json, LLM_IF_OAI_Reasoning, LLM_IF_OAI_Vision } from '~/common/stores/llms/llms.types'; import { Release } from '~/common/app.release'; import type { ModelDescriptionSchema } from '../../llm.server.types'; @@ -36,7 +36,7 @@ const PRICE_40 = { // we don't add LLM_IF_OAI_Responses explicitly here, as the code fully treats XAI/XAI Models with responses const XAI_IF: ModelDescriptionSchema['interfaces'] = [ - LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Json, LLM_IF_Tools_WebSearch, + LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Json, ] as const; const XAI_IF_Vision: ModelDescriptionSchema['interfaces'] = [