mirror of
https://github.com/ollama/ollama.git
synced 2026-07-15 20:54:54 -05:00
agent/tools: surface actionable web auth error (#17169)
This commit is contained in:
@@ -13,17 +13,14 @@ import (
|
||||
internalcloud "github.com/ollama/ollama/internal/cloud"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWebSearchAuthRequired = errors.New("web search requires authentication")
|
||||
ErrWebFetchAuthRequired = errors.New("web fetch requires authentication")
|
||||
)
|
||||
|
||||
const (
|
||||
maxWebFetchContentRunes = 60_000
|
||||
webSearchTimeout = 15 * time.Second
|
||||
webFetchTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
var ErrWebAuthRequired = errors.New("Not authenticated. Run `ollama signin` and try again.")
|
||||
|
||||
type WebSearch struct{}
|
||||
|
||||
func (w *WebSearch) Name() string {
|
||||
@@ -76,7 +73,7 @@ func (w *WebSearch) Execute(ctx context.Context, _ agent.ToolContext, args map[s
|
||||
if err != nil {
|
||||
var authErr api.AuthorizationError
|
||||
if errors.As(err, &authErr) {
|
||||
return agent.ToolResult{}, fmt.Errorf("%w: %s", ErrWebSearchAuthRequired, authErr)
|
||||
return agent.ToolResult{}, ErrWebAuthRequired
|
||||
}
|
||||
return agent.ToolResult{}, err
|
||||
}
|
||||
@@ -160,7 +157,7 @@ func (w *WebFetch) Execute(ctx context.Context, _ agent.ToolContext, args map[st
|
||||
if err != nil {
|
||||
var authErr api.AuthorizationError
|
||||
if errors.As(err, &authErr) {
|
||||
return agent.ToolResult{}, fmt.Errorf("%w: %s", ErrWebFetchAuthRequired, authErr)
|
||||
return agent.ToolResult{}, ErrWebAuthRequired
|
||||
}
|
||||
return agent.ToolResult{}, err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package tools
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -20,6 +21,61 @@ func TestWebToolsRequireApproval(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var webToolCases = []struct {
|
||||
name string
|
||||
tool coreagent.Tool
|
||||
args map[string]any
|
||||
path string
|
||||
}{
|
||||
{"search", &WebSearch{}, map[string]any{"query": "ollama"}, "/api/experimental/web_search"},
|
||||
{"fetch", &WebFetch{}, map[string]any{"url": "https://ollama.com"}, "/api/experimental/web_fetch"},
|
||||
}
|
||||
|
||||
// runWebTool executes tool against a stub server that responds to every
|
||||
// request with status and body, returning the resulting error.
|
||||
func runWebTool(t *testing.T, tool coreagent.Tool, args map[string]any, path string, status int, body string) error {
|
||||
t.Helper()
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != path {
|
||||
t.Fatalf("path = %q, want %q", r.URL.Path, path)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write([]byte(body))
|
||||
}))
|
||||
t.Cleanup(ts.Close)
|
||||
t.Setenv("OLLAMA_HOST", ts.URL)
|
||||
_, err := tool.Execute(t.Context(), coreagent.ToolContext{}, args)
|
||||
return err
|
||||
}
|
||||
|
||||
func TestWebToolsReportAuthenticationError(t *testing.T) {
|
||||
for _, tt := range webToolCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := runWebTool(t, tt.tool, tt.args, tt.path, http.StatusUnauthorized,
|
||||
`{"error":"unauthorized","signin_url":"https://ollama.com/signin"}`)
|
||||
if !errors.Is(err, ErrWebAuthRequired) {
|
||||
t.Fatalf("error = %v, want %v", err, ErrWebAuthRequired)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebToolsPreserveNonAuthenticationErrors(t *testing.T) {
|
||||
for _, tt := range webToolCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := runWebTool(t, tt.tool, tt.args, tt.path, http.StatusTooManyRequests,
|
||||
`{"error":"web search quota exceeded"}`)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "web search quota exceeded") {
|
||||
t.Fatalf("error = %q, want original error message", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebFetchRejectsUnsupportedScheme(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user