mirror of
https://github.com/ollama/ollama.git
synced 2026-03-09 07:16:38 -05:00
ui: use capability-based detection for web search (#14336)
This commit is contained in:
@@ -17,7 +17,10 @@ import {
|
||||
} from "@/hooks/useChats";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { useSelectedModel } from "@/hooks/useSelectedModel";
|
||||
import { useHasVisionCapability } from "@/hooks/useModelCapabilities";
|
||||
import {
|
||||
useHasVisionCapability,
|
||||
useHasToolsCapability,
|
||||
} from "@/hooks/useModelCapabilities";
|
||||
import { useUser } from "@/hooks/useUser";
|
||||
import { DisplayLogin } from "@/components/DisplayLogin";
|
||||
import { ErrorEvent, Message } from "@/gotypes";
|
||||
@@ -149,12 +152,7 @@ function ChatForm({
|
||||
} = useSettings();
|
||||
const { cloudDisabled } = useCloudStatus();
|
||||
|
||||
// current supported models for web search
|
||||
const modelLower = selectedModel?.model.toLowerCase() || "";
|
||||
const supportsWebSearch =
|
||||
modelLower.startsWith("gpt-oss") ||
|
||||
modelLower.startsWith("qwen3") ||
|
||||
modelLower.startsWith("deepseek-v3");
|
||||
const supportsWebSearch = useHasToolsCapability(selectedModel?.model);
|
||||
// Use per-chat thinking level instead of global
|
||||
const thinkLevel: ThinkingLevel =
|
||||
settingsThinkLevel === "none" || !settingsThinkLevel
|
||||
|
||||
@@ -20,3 +20,8 @@ export function useHasVisionCapability(modelName: string | undefined) {
|
||||
const { data: capabilitiesResponse } = useModelCapabilities(modelName);
|
||||
return capabilitiesResponse?.capabilities?.includes("vision") ?? false;
|
||||
}
|
||||
|
||||
export function useHasToolsCapability(modelName: string | undefined) {
|
||||
const { data: capabilitiesResponse } = useModelCapabilities(modelName);
|
||||
return capabilitiesResponse?.capabilities?.includes("tools") ?? false;
|
||||
}
|
||||
|
||||
16
app/ui/ui.go
16
app/ui/ui.go
@@ -829,8 +829,9 @@ func (s *Server) chat(w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
if !hasAttachments {
|
||||
WebSearchEnabled := req.WebSearch != nil && *req.WebSearch
|
||||
hasToolsCapability := slices.Contains(details.Capabilities, model.CapabilityTools)
|
||||
|
||||
if WebSearchEnabled {
|
||||
if WebSearchEnabled && hasToolsCapability {
|
||||
if supportsBrowserTools(req.Model) {
|
||||
browserState, ok := s.browserState(chat)
|
||||
if !ok {
|
||||
@@ -840,7 +841,7 @@ func (s *Server) chat(w http.ResponseWriter, r *http.Request) error {
|
||||
registry.Register(tools.NewBrowserSearch(browser))
|
||||
registry.Register(tools.NewBrowserOpen(browser))
|
||||
registry.Register(tools.NewBrowserFind(browser))
|
||||
} else if supportsWebSearchTools(req.Model) {
|
||||
} else {
|
||||
registry.Register(&tools.WebSearch{})
|
||||
registry.Register(&tools.WebFetch{})
|
||||
}
|
||||
@@ -1648,17 +1649,6 @@ func supportsBrowserTools(model string) bool {
|
||||
return strings.HasPrefix(strings.ToLower(model), "gpt-oss")
|
||||
}
|
||||
|
||||
// Web search tools are simpler, providing only basic web search and fetch capabilities (e.g., "web_search", "web_fetch") without simulating a browser. Currently only qwen3 and deepseek-v3 support web search tools.
|
||||
func supportsWebSearchTools(model string) bool {
|
||||
model = strings.ToLower(model)
|
||||
prefixes := []string{"qwen3", "deepseek-v3"}
|
||||
for _, p := range prefixes {
|
||||
if strings.HasPrefix(model, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// buildChatRequest converts store.Chat to api.ChatRequest
|
||||
func (s *Server) buildChatRequest(chat *store.Chat, model string, think any, availableTools []map[string]any) (*api.ChatRequest, error) {
|
||||
|
||||
@@ -522,3 +522,110 @@ func TestUserAgentTransport(t *testing.T) {
|
||||
|
||||
t.Logf("User-Agent transport successfully set: %s", receivedUA)
|
||||
}
|
||||
|
||||
func TestSupportsBrowserTools(t *testing.T) {
|
||||
tests := []struct {
|
||||
model string
|
||||
want bool
|
||||
}{
|
||||
{"gpt-oss", true},
|
||||
{"gpt-oss-latest", true},
|
||||
{"GPT-OSS", true},
|
||||
{"Gpt-Oss-v2", true},
|
||||
{"qwen3", false},
|
||||
{"deepseek-v3", false},
|
||||
{"llama3.3", false},
|
||||
{"", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.model, func(t *testing.T) {
|
||||
if got := supportsBrowserTools(tt.model); got != tt.want {
|
||||
t.Errorf("supportsBrowserTools(%q) = %v, want %v", tt.model, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebSearchToolRegistration(t *testing.T) {
|
||||
// Validates that the capability-gating logic in chat() correctly
|
||||
// decides which tools to register based on model capabilities and
|
||||
// the web search flag.
|
||||
tests := []struct {
|
||||
name string
|
||||
webSearchEnabled bool
|
||||
hasToolsCap bool
|
||||
model string
|
||||
wantBrowser bool // expects browser tools (gpt-oss)
|
||||
wantWebSearch bool // expects basic web search/fetch tools
|
||||
wantNone bool // expects no tools registered
|
||||
}{
|
||||
{
|
||||
name: "web search enabled with tools capability - browser model",
|
||||
webSearchEnabled: true,
|
||||
hasToolsCap: true,
|
||||
model: "gpt-oss-latest",
|
||||
wantBrowser: true,
|
||||
},
|
||||
{
|
||||
name: "web search enabled with tools capability - non-browser model",
|
||||
webSearchEnabled: true,
|
||||
hasToolsCap: true,
|
||||
model: "qwen3",
|
||||
wantWebSearch: true,
|
||||
},
|
||||
{
|
||||
name: "web search enabled without tools capability",
|
||||
webSearchEnabled: true,
|
||||
hasToolsCap: false,
|
||||
model: "llama3.3",
|
||||
wantNone: true,
|
||||
},
|
||||
{
|
||||
name: "web search disabled with tools capability",
|
||||
webSearchEnabled: false,
|
||||
hasToolsCap: true,
|
||||
model: "qwen3",
|
||||
wantNone: true,
|
||||
},
|
||||
{
|
||||
name: "web search disabled without tools capability",
|
||||
webSearchEnabled: false,
|
||||
hasToolsCap: false,
|
||||
model: "llama3.3",
|
||||
wantNone: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Replicate the decision logic from chat() handler
|
||||
gotBrowser := false
|
||||
gotWebSearch := false
|
||||
|
||||
if tt.webSearchEnabled && tt.hasToolsCap {
|
||||
if supportsBrowserTools(tt.model) {
|
||||
gotBrowser = true
|
||||
} else {
|
||||
gotWebSearch = true
|
||||
}
|
||||
}
|
||||
|
||||
if tt.wantBrowser && !gotBrowser {
|
||||
t.Error("expected browser tools to be registered")
|
||||
}
|
||||
if tt.wantWebSearch && !gotWebSearch {
|
||||
t.Error("expected web search tools to be registered")
|
||||
}
|
||||
if tt.wantNone && (gotBrowser || gotWebSearch) {
|
||||
t.Error("expected no tools to be registered")
|
||||
}
|
||||
if !tt.wantBrowser && gotBrowser {
|
||||
t.Error("unexpected browser tools registered")
|
||||
}
|
||||
if !tt.wantWebSearch && gotWebSearch {
|
||||
t.Error("unexpected web search tools registered")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user