Verwendung der Ollama Web Search API in Go
AI-Suchagenten mit Go und Ollama erstellen
Ollamas Web-Search-API ermöglicht es Ihnen, lokale LLMs mit Echtzeit-Webinformationen zu erweitern. Diese Anleitung zeigt Ihnen, wie Sie Web-Suchfunktionen in Go implementieren, von einfachen API-Aufrufen bis hin zu vollwertigen Suchagenten.

Einstieg
Hat Ollama eine offizielle Go-Bibliothek für die Websuche? Ollama bietet eine REST-API für die Websuche, die mit jedem Go-HTTP-Client funktioniert. Obwohl es noch keine offizielle Go-SDK für die Websuche gibt, können Sie die API-Aufrufe problemlos mit Standardbibliotheks-Paketen implementieren.
Erstellen Sie zunächst einen API-Schlüssel von Ihrem Ollama-Konto. Für einen umfassenden Überblick über Ollama-Befehle und deren Verwendung, besuchen Sie den Ollama-Cheat-Sheet.
Legen Sie Ihren API-Schlüssel als Umgebungsvariable fest:
export OLLAMA_API_KEY="your_api_key"
Auf Windows PowerShell:
$env:OLLAMA_API_KEY = "your_api_key"
Projekt-Einrichtung
Erstellen Sie ein neues Go-Modul:
mkdir ollama-search
cd ollama-search
go mod init ollama-search
Grundlegende Websuche
Wie authentifiziere ich mich bei Ollamas Web-Search-API in Go? Setzen Sie den Authorization-Header mit Ihrem API-Schlüssel als Bearer-Token. Erstellen Sie einen API-Schlüssel von Ihrem Ollama-Konto und geben Sie ihn im Anfrage-Header an.
Hier ist eine vollständige Implementierung der Web-Search-API:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Anfrage/Antwort-Typen für 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-Umgebungsvariable nicht gesetzt")
}
reqBody := WebSearchRequest{
Query: query,
MaxResults: maxResults,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("Fehler beim Erstellen der Anfrage: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("Fehler beim Erstellen der Anfrage: %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("Anfrage fehlgeschlagen: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API-Fehler (Status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("Fehler beim Lesen der Antwort: %w", err)
}
var searchResp WebSearchResponse
if err := json.Unmarshal(body, &searchResp); err != nil {
return nil, fmt.Errorf("Fehler beim Entpacken der Antwort: %w", err)
}
return &searchResp, nil
}
func main() {
results, err := webSearch("Was ist Ollama?", 5)
if err != nil {
fmt.Printf("Fehler: %v\n", err)
return
}
fmt.Println("Suchergebnisse:")
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] + "..."
}
Web-Fetch-Implementierung
Was ist der Unterschied zwischen den Endpunkten web_search und web_fetch? Der Endpunkt web_search durchsucht das Internet und gibt mehrere Suchergebnisse mit Titeln, URLs und Auszügen zurück. Der Endpunkt web_fetch 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"
)
// Anfrage/Antwort-Typen für 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-Umgebungsvariable nicht gesetzt")
}
reqBody := WebFetchRequest{URL: url}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("Fehler beim Erstellen der Anfrage: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_fetch", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("Fehler beim Erstellen der Anfrage: %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("Anfrage fehlgeschlagen: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API-Fehler (Status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("Fehler beim Lesen der Antwort: %w", err)
}
var fetchResp WebFetchResponse
if err := json.Unmarshal(body, &fetchResp); err != nil {
return nil, fmt.Errorf("Fehler beim Entpacken der Antwort: %w", err)
}
return &fetchResp, nil
}
func main() {
result, err := webFetch("https://ollama.com")
if err != nil {
fmt.Printf("Fehler: %v\n", err)
return
}
fmt.Printf("Titel: %s\n\n", result.Title)
fmt.Printf("Inhalt:\n%s\n\n", result.Content)
fmt.Printf("Gefundene Links: %d\n", len(result.Links))
for i, link := range result.Links {
if i >= 5 {
fmt.Printf(" ... und %d weitere\n", len(result.Links)-5)
break
}
fmt.Printf(" - %s\n", link)
}
}
Wiederverwendbares Client-Paket
Erstellen Sie ein wiederverwendbares Ollama-Client-Paket für saubereren 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-Umgebungsvariable nicht gesetzt")
}
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-Fehler (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)
}
// Suche
results, err := client.WebSearch("Ollama neue Funktionen", 5)
if err != nil {
log.Fatal(err)
}
for _, r := range results.Results {
fmt.Printf("- %s\n %s\n\n", r.Title, r.URL)
}
// Abrufen
page, err := client.WebFetch("https://ollama.com")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Seite: %s\n", page.Title)
}
Aufbau eines Suchagenten
Welche Modelle eignen sich am besten für Go-basierte Ollama-Suchagenten? Modelle mit starken Tool-Use-Fä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 rangieren müssen, sehen Sie sich unsere Anleitung zum Neu-Rangieren von Textdokumenten mit Ollama und dem Qwen3-Embedding-Modell in Go.
Hier ist eine vollständige Implementierung eines Suchagenten:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Chat-Typen
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 orchestriert Websuche mit 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: "Suche im Web nach aktuellen Informationen",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]string{
"type": "string",
"description": "Die Suchanfrage",
},
},
"required": []string{"query"},
},
},
},
{
Type: "function",
Function: ToolFunction{
Name: "web_fetch",
Description: "Holt den vollständigen Inhalt einer Webseite",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]string{
"type": "string",
"description": "Die URL, die abgerufen werden soll",
},
},
"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-Fehler: %w", err)
}
messages = append(messages, response.Message)
// Keine Tool-Aufrufe bedeuten, dass wir eine endgültige Antwort haben
if len(response.Message.ToolCalls) == 0 {
return response.Message.Content, nil
}
// Führe Tool-Aufrufe aus
for _, toolCall := range response.Message.ToolCalls {
fmt.Printf("🔧 Aufruf: %s\n", toolCall.Function.Name)
result, err := a.executeTool(toolCall)
if err != nil {
result = fmt.Sprintf("Fehler: %v", err)
}
// Kürze für Kontextgrenzen
if len(result) > 8000 {
result = result[:8000] + "... [gekürzt]"
}
messages = append(messages, Message{
Role: "tool",
Content: result,
ToolName: toolCall.Function.Name,
})
}
}
return "", fmt.Errorf("Maximale Iterationen erreicht")
}
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("ungültiges Abfrageargument")
}
return a.webSearch(query)
case "web_fetch":
url, ok := toolCall.Function.Arguments["url"].(string)
if !ok {
return "", fmt.Errorf("ungültiges URL-Argument")
}
return a.webFetch(url)
default:
return "", fmt.Errorf("unbekanntes 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("Was sind die neuesten Funktionen in Ollama?")
if err != nil {
fmt.Printf("Fehler: %v\n", err)
return
}
fmt.Println("\n📝 Antwort:")
fmt.Println(answer)
}
Wie kann ich große Websuchergebnisse in Go verarbeiten? Kürzen Sie den Antwortinhalt, bevor Sie ihn an den Modellkontext übergeben. Verwenden Sie String-Slicing, um den Inhalt auf etwa 8000 Zeichen zu begrenzen, um innerhalb der Kontextgrenzen zu bleiben.
Parallelsuche
Go eignet sich hervorragend für parallele Operationen. Hier ist, wie Sie mehrere Suchen parallel durchführen können. Das Verständnis 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 neueste Funktionen",
"Lokale LLM-Implementierung",
"AI-Suchagenten Go",
}
results := concurrentSearch(queries)
for _, r := range results {
fmt.Printf("\n🔍 Abfrage: %s\n", r.Query)
if r.Error != nil {
fmt.Printf(" Fehler: %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 Abbruch 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() {
// Erstellen Sie einen Kontext mit 10-Sekunden-Timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := webSearchWithContext(ctx, "Ollama Web-Such-API")
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
fmt.Println("Anfrage ist abgelaufen")
} else {
fmt.Printf("Fehler: %v\n", err)
}
return
}
fmt.Println(result)
}
Empfohlene Modelle
Welche Kontextlänge sollte ich für Go-Suchagenten verwenden? Setzen Sie die Kontextlänge auf etwa 32000 Tokens für eine vernünftige Leistung. Suchagenten funktionieren am besten mit voller Kontextlänge, da Websuchergebnisse umfangreich sein können. Wenn Sie Ihre Ollama-Modelle verwalten oder an verschiedene Orte verschieben müssen, sehen Sie sich unsere Anleitung zum Ollama-Modelle auf eine andere Festplatte oder einen anderen Ordner verschieben.
| Modell | Parameter | Beste Verwendung |
|---|---|---|
qwen3:4b |
4B | Schnelle lokale Suchen |
qwen3 |
8B | Allgemeiner Agent |
gpt-oss |
Verschieden | Forschungsaufgaben |
qwen3:480b-cloud |
480B | Komplexe Schlussfolgerungen (Cloud) |
gpt-oss:120b-cloud |
120B | Langform-Forschung (Cloud) |
deepseek-v3.1-cloud |
- | Fortgeschrittene Analyse (Cloud) |
Für fortgeschrittene KI-Anwendungen, die Text- und Bildinhalte kombinieren, sollten Sie die cross-modale Einbettungen in Betracht ziehen, um Ihre Suchfähigkeiten über textbasierte Abfragen hinaus zu erweitern.
Beste Praktiken
- Fehlerbehandlung: Überprüfen Sie immer auf Fehler und behandeln Sie API-Fehlschläge elegant
- Timeouts: Verwenden Sie Kontext mit Timeouts für Netzwerkanfragen
- Rate Limiting: Respektieren Sie die API-Rate-Limits von Ollama. Seien Sie sich möglicher Änderungen der Ollama-API bewusst, wie in erste Anzeichen der Ollama-Verschlechterung diskutiert
- Ergebnis-Trunkierung: Trunkieren Sie Ergebnisse auf ~8000 Zeichen für Kontextlimits
- Parallelanfragen: Verwenden Sie Goroutines für parallele Suchen
- Verbindungswiederverwendung: Wiederverwenden Sie HTTP-Clients für bessere Leistung
- Testen: Schreiben Sie umfassende Unit-Tests für Ihre Suchimplementierungen. Folgen Sie Go Unit-Testing Best Practices, um sicherzustellen, dass Ihr Code robust und wartbar ist
Nützliche Links
- Ollama Cheatsheet
- Neuordnung von Textdokumenten mit Ollama und Qwen3 Embedding-Modell - in Go
- Cross-Modal Embeddings: Brücken zwischen KI-Modalitäten
- Go Unit Testing: Struktur & Best Practices
- Wie man Ollama-Modelle auf eine andere Festplatte oder einen anderen Ordner verschiebt
- Wie Ollama Parallelanfragen behandelt
- Erste Anzeichen der Ollama-Verschlechterung
- Ollama Web-Such-Blog-Post
- Ollama offizielle Dokumentation
- Ollama Go Beispiele
- Ollama GitHub Repository