Ollama Web Search API in Go verwenden
KI-Suchagenten mit Go und Ollama erstellen
Mit der Websuche-API von Ollama können Sie lokale LLMs mit Echtzeit-Webinformationen anreichern. Dieser Leitfaden zeigt Ihnen, wie Sie Websuchefunktionen in Go umsetzen, von einfachen API-Aufrufen bis hin zu vollausgestatteten Such-Agenten.

Erste Schritte
Hat Ollama eine offizielle Go-Bibliothek für die Websuche? Ollama stellt eine REST-API für die Websuche bereit, die mit jedem Go-HTTP-Client funktioniert. Obwohl es noch kein offizielles Go-SDK für die Websuche gibt, können Sie die API-Aufrufe leicht mit Standardbibliotheken implementieren.
Erstellen Sie zunächst einen API-Schlüssel aus Ihrem Ollama-Konto. Für einen umfassenden Referenzleitfaden zu Ollama-Befehlen und ihrer Verwendung werfen Sie einen Blick auf das Ollama-Spickzettel.
Setzen Sie Ihren API-Schlüssel als Umgebungsvariable:
export OLLAMA_API_KEY="your_api_key"
Unter Windows PowerShell:
$env:OLLAMA_API_KEY = "your_api_key"
Projektaufbau
Erstellen Sie ein neues Go-Modul:
mkdir ollama-search
cd ollama-search
go mod init ollama-search
Grundlegende Websuche
Wie authentifiziere ich mich mit Ollamas Websuche-API in Go? Setzen Sie den Authorization-Header mit Ihrem API-Schlüssel als Bearer-Token. Erstellen Sie einen API-Schlüssel aus Ihrem Ollama-Konto und übergeben Sie ihn im Anfrage-Header.
Hier ist eine vollständige Implementierung der Websuche-API:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Request/Response types for web_search
type WebSearchRequest struct {
Query string `json:"query"`
MaxResults int `json:"max_results,omitempty"`
}
type WebSearchResult struct {
Title string `json:"title"`
URL string `json:"url"`
Content string `json:"content"`
}
type WebSearchResponse struct {
Results []WebSearchResult `json:"results"`
}
func webSearch(query string, maxResults int) (*WebSearchResponse, error) {
apiKey := os.Getenv("OLLAMA_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("OLLAMA_API_KEY environment variable not set")
}
reqBody := WebSearchRequest{
Query: query,
MaxResults: maxResults,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
var searchResp WebSearchResponse
if err := json.Unmarshal(body, &searchResp); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &searchResp, nil
}
func main() {
results, err := webSearch("What is Ollama?", 5)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println("Search Results:")
fmt.Println("===============")
for i, result := range results.Results {
fmt.Printf("\n%d. %s\n", i+1, result.Title)
fmt.Printf(" URL: %s\n", result.URL)
fmt.Printf(" %s\n", truncate(result.Content, 150))
}
}
func truncate(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen] + "..."
}
Implementierung von Web Fetch
Was ist der Unterschied zwischen den Endpunkten web_search und web_fetch? Der web_search-Endpunkt durchsucht das Internet und gibt mehrere Suchergebnisse mit Titeln, URLs und Textauszügen zurück. Der web_fetch-Endpunkt ruft den vollständigen Inhalt einer bestimmten URL ab und gibt den Seitentitel, den Inhalt und die Links zurück.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Request/Response types for web_fetch
type WebFetchRequest struct {
URL string `json:"url"`
}
type WebFetchResponse struct {
Title string `json:"title"`
Content string `json:"content"`
Links []string `json:"links"`
}
func webFetch(url string) (*WebFetchResponse, error) {
apiKey := os.Getenv("OLLAMA_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("OLLAMA_API_KEY environment variable not set")
}
reqBody := WebFetchRequest{URL: url}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_fetch", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
var fetchResp WebFetchResponse
if err := json.Unmarshal(body, &fetchResp); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &fetchResp, nil
}
func main() {
result, err := webFetch("https://ollama.com")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Title: %s\n\n", result.Title)
fmt.Printf("Content:\n%s\n\n", result.Content)
fmt.Printf("Links found: %d\n", len(result.Links))
for i, link := range result.Links {
if i >= 5 {
fmt.Printf(" ... and %d more\n", len(result.Links)-5)
break
}
fmt.Printf(" - %s\n", link)
}
}
Wiederverwendbares Client-Paket
Erstellen Sie ein wiederverwendbares Ollama-Client-Paket für sauberen Code:
// ollama/client.go
package ollama
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
type Client struct {
apiKey string
httpClient *http.Client
baseURL string
}
func NewClient() (*Client, error) {
apiKey := os.Getenv("OLLAMA_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("OLLAMA_API_KEY environment variable not set")
}
return &Client{
apiKey: apiKey,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
baseURL: "https://ollama.com/api",
}, nil
}
type SearchResult struct {
Title string `json:"title"`
URL string `json:"url"`
Content string `json:"content"`
}
type SearchResponse struct {
Results []SearchResult `json:"results"`
}
type FetchResponse struct {
Title string `json:"title"`
Content string `json:"content"`
Links []string `json:"links"`
}
func (c *Client) WebSearch(query string, maxResults int) (*SearchResponse, error) {
payload := map[string]interface{}{
"query": query,
"max_results": maxResults,
}
return doRequest[SearchResponse](c, "/web_search", payload)
}
func (c *Client) WebFetch(url string) (*FetchResponse, error) {
payload := map[string]string{"url": url}
return doRequest[FetchResponse](c, "/web_fetch", payload)
}
func doRequest[T any](c *Client, endpoint string, payload interface{}) (*T, error) {
jsonData, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", c.baseURL+endpoint, bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+c.apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
var result T
if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}
return &result, nil
}
Verwendung:
package main
import (
"fmt"
"log"
"ollama-search/ollama"
)
func main() {
client, err := ollama.NewClient()
if err != nil {
log.Fatal(err)
}
// Search
results, err := client.WebSearch("Ollama new features", 5)
if err != nil {
log.Fatal(err)
}
for _, r := range results.Results {
fmt.Printf("- %s\n %s\n\n", r.Title, r.URL)
}
// Fetch
page, err := client.WebFetch("https://ollama.com")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Page: %s\n", page.Title)
}
Aufbau eines Such-Agenten
Welche Modelle eignen sich am besten für Go-basierte Ollama-Such-Agenten? Modelle mit starken Tool-Nutzungsfähigkeiten funktionieren am besten, darunter qwen3, gpt-oss und Cloud-Modelle wie qwen3:480b-cloud und deepseek-v3.1-cloud. Wenn Sie mit Qwen3-Modellen arbeiten und Suchergebnisse verarbeiten oder neu sortieren müssen, sehen Sie sich unseren Leitfaden zur Neusortierung von Textdokumenten mit dem Ollama- und Qwen3-Embedding-Modell in Go an.
Hier ist eine vollständige Implementierung eines Such-Agenten:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Chat types
type Message struct {
Role string `json:"role"`
Content string `json:"content,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolName string `json:"tool_name,omitempty"`
}
type ToolCall struct {
Function FunctionCall `json:"function"`
}
type FunctionCall struct {
Name string `json:"name"`
Arguments map[string]interface{} `json:"arguments"`
}
type Tool struct {
Type string `json:"type"`
Function ToolFunction `json:"function"`
}
type ToolFunction struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters map[string]interface{} `json:"parameters"`
}
type ChatRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Tools []Tool `json:"tools,omitempty"`
Stream bool `json:"stream"`
}
type ChatResponse struct {
Message Message `json:"message"`
}
// SearchAgent orchestrates web search with LLM
type SearchAgent struct {
model string
ollamaURL string
apiKey string
maxIter int
httpClient *http.Client
}
func NewSearchAgent(model string) *SearchAgent {
return &SearchAgent{
model: model,
ollamaURL: "http://localhost:11434/api/chat",
apiKey: os.Getenv("OLLAMA_API_KEY"),
maxIter: 10,
httpClient: &http.Client{},
}
}
func (a *SearchAgent) Query(question string) (string, error) {
tools := []Tool{
{
Type: "function",
Function: ToolFunction{
Name: "web_search",
Description: "Search the web for current information",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]string{
"type": "string",
"description": "The search query",
},
},
"required": []string{"query"},
},
},
},
{
Type: "function",
Function: ToolFunction{
Name: "web_fetch",
Description: "Fetch the full content of a web page",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]string{
"type": "string",
"description": "The URL to fetch",
},
},
"required": []string{"url"},
},
},
},
}
messages := []Message{
{Role: "user", Content: question},
}
for i := 0; i < a.maxIter; i++ {
response, err := a.chat(messages, tools)
if err != nil {
return "", fmt.Errorf("chat error: %w", err)
}
messages = append(messages, response.Message)
// No tool calls means we have a final answer
if len(response.Message.ToolCalls) == 0 {
return response.Message.Content, nil
}
// Execute tool calls
for _, toolCall := range response.Message.ToolCalls {
fmt.Printf("🔧 Calling: %s\n", toolCall.Function.Name)
result, err := a.executeTool(toolCall)
if err != nil {
result = fmt.Sprintf("Error: %v", err)
}
// Truncate for context limits
if len(result) > 8000 {
result = result[:8000] + "... [truncated]"
}
messages = append(messages, Message{
Role: "tool",
Content: result,
ToolName: toolCall.Function.Name,
})
}
}
return "", fmt.Errorf("max iterations reached")
}
func (a *SearchAgent) chat(messages []Message, tools []Tool) (*ChatResponse, error) {
reqBody := ChatRequest{
Model: a.model,
Messages: messages,
Tools: tools,
Stream: false,
}
jsonData, _ := json.Marshal(reqBody)
resp, err := a.httpClient.Post(a.ollamaURL, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var chatResp ChatResponse
if err := json.Unmarshal(body, &chatResp); err != nil {
return nil, err
}
return &chatResp, nil
}
func (a *SearchAgent) executeTool(toolCall ToolCall) (string, error) {
switch toolCall.Function.Name {
case "web_search":
query, ok := toolCall.Function.Arguments["query"].(string)
if !ok {
return "", fmt.Errorf("invalid query argument")
}
return a.webSearch(query)
case "web_fetch":
url, ok := toolCall.Function.Arguments["url"].(string)
if !ok {
return "", fmt.Errorf("invalid url argument")
}
return a.webFetch(url)
default:
return "", fmt.Errorf("unknown tool: %s", toolCall.Function.Name)
}
}
func (a *SearchAgent) webSearch(query string) (string, error) {
payload := map[string]interface{}{"query": query, "max_results": 5}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer "+a.apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := a.httpClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
return string(body), nil
}
func (a *SearchAgent) webFetch(url string) (string, error) {
payload := map[string]string{"url": url}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://ollama.com/api/web_fetch", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer "+a.apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := a.httpClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
return string(body), nil
}
func main() {
agent := NewSearchAgent("qwen3:4b")
answer, err := agent.Query("What are the latest features in Ollama?")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println("\n📝 Answer:")
fmt.Println(answer)
}
Wie gehe ich mit großen Websuchantworten in Go um? Kürzen Sie den Antwortinhalt, bevor Sie ihn in den Modellkontext übergeben. Verwenden Sie Stringslicing, um den Inhalt auf ungefähr 8000 Zeichen zu begrenzen, damit er innerhalb der Kontextgrenzen bleibt.
Concurrent Search (Parallele Suche)
Go exceliert bei parallelen Operationen. So führen Sie mehrere Suchen parallel durch. Das Verständnis dafür, wie Ollama parallele Anfragen verarbeitet, kann Ihnen helfen, Ihre parallelen Suchimplementierungen zu optimieren.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"sync"
)
type SearchResult struct {
Query string
Results []struct {
Title string `json:"title"`
URL string `json:"url"`
Content string `json:"content"`
} `json:"results"`
Error error
}
func concurrentSearch(queries []string) []SearchResult {
results := make([]SearchResult, len(queries))
var wg sync.WaitGroup
for i, query := range queries {
wg.Add(1)
go func(idx int, q string) {
defer wg.Done()
result := SearchResult{Query: q}
payload := map[string]string{"query": q}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer "+os.Getenv("OLLAMA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
result.Error = err
results[idx] = result
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &result)
results[idx] = result
}(i, query)
}
wg.Wait()
return results
}
func main() {
queries := []string{
"Ollama latest features",
"local LLM deployment",
"AI search agents Go",
}
results := concurrentSearch(queries)
for _, r := range results {
fmt.Printf("\n🔍 Query: %s\n", r.Query)
if r.Error != nil {
fmt.Printf(" Error: %v\n", r.Error)
continue
}
for _, item := range r.Results[:min(3, len(r.Results))] {
fmt.Printf(" • %s\n", item.Title)
}
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
Kontext und Abbruch
Fügen Sie Kontextunterstützung für Timeouts und Abbrüche hinzu:
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
func webSearchWithContext(ctx context.Context, query string) (string, error) {
payload := map[string]string{"query": query}
jsonData, _ := json.Marshal(payload)
req, err := http.NewRequestWithContext(ctx, "POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("OLLAMA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
func main() {
// Create context with 10 second timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := webSearchWithContext(ctx, "Ollama web search API")
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
fmt.Println("Request timed out")
} else {
fmt.Printf("Error: %v\n", err)
}
return
}
fmt.Println(result)
}
Empfohlene Modelle
Welche Kontextlänge sollte ich für Go-Such-Agenten verwenden? Stellen Sie die Kontextlänge auf etwa 32000 Token ein, um eine angemessene Leistung zu erzielen. Such-Agenten funktionieren am besten mit voller Kontextlänge, da Websuchergebnisse umfangreich sein können. Wenn Sie Ihre Ollama-Modelle verwalten oder an andere Speicherorte verschieben müssen, sehen Sie sich unseren Leitfaden dazu an, wie Sie Ollama-Modelle auf ein anderes Laufwerk oder in einen anderen Ordner verschieben.
| Modell | Parameter | Am besten geeignet für |
|---|---|---|
qwen3:4b |
4B | Schnelle lokale Suchen |
qwen3 |
8B | Allzweck-Agent |
gpt-oss |
Verschiedene | Recherchetasks |
qwen3:480b-cloud |
480B | Komplexes Reasoning (Cloud) |
gpt-oss:120b-cloud |
120B | Langlebige Recherche (Cloud) |
deepseek-v3.1-cloud |
- | Fortgeschrittene Analyse (Cloud) |
Best Practices
- Fehlerbehandlung: Prüfen Sie immer auf Fehler und behandeln Sie API-Fehler elegant.
- Timeouts: Verwenden Sie Kontexte mit Timeouts für Netzwerk Anfragen.
- Ratenbegrenzung: Beachten Sie die API-Ratenbegrenzungen von Ollama. Seien Sie sich möglicher Änderungen an Ollamas API bewusst, wie sie in ersten Anzeichen der Ollama-Enshittification diskutiert werden.
- Ergebniskürzung: Kürzen Sie Ergebnisse auf ca. 8000 Zeichen für Kontextgrenzen.
- Parallele Anfragen: Verwenden Sie Goroutinen für parallele Suchen.
- Verbindungswiederverwendung: Wiederverwenden Sie den HTTP-Client für bessere Leistung.
- Testen: Schreiben Sie umfassende Unit-Tests für Ihre Suchimplementierungen. Befolgen Sie die Go-Unit-Testing-Best-Practices, um sicherzustellen, dass Ihr Code robust und wartbar ist.
Nützliche Links
- Ollama-Spickzettel
- Neusortierung von Textdokumenten mit Ollama und dem Qwen3-Embedding-Modell - in Go
- Ollama-Modelle auf ein anderes Laufwerk oder in einen anderen Ordner verschieben
- Wie Ollama parallele Anfragen verarbeitet
- Erste Anzeichen der Ollama-Enshittification
- Ollama Websuche Blog-Post
- Offizielle Ollama-Dokumentation
- Ollama Go-Beispiele
- Ollama GitHub-Repository