Het gebruik van de Ollama Web Search API in Go
Maak AI zoekagents met Go en Ollama
Ollama’s Web Search API laat je lokale LLMs verrijken met real-time webinformatie. Deze gids laat je zien hoe je web zoekfunctionaliteiten in Go kunt implementeren, van eenvoudige API-aanroepen tot volledig uitgeruste zoekagenten.

Aan de slag
Heeft Ollama een officiële Go-bibliotheek voor webzoekfuncties? Ollama biedt een REST API voor webzoekfuncties die werkt met elke Go HTTP-client. Hoewel er nog geen officiële Go-SDK voor webzoekfuncties beschikbaar is, kun je de API-aanroepen eenvoudig implementeren met behulp van standaardbibliotheekpakketten.
Eerst, maak een API-sleutel aan vanuit je Ollama account. Voor een uitgebreid overzicht van Ollama-commands en gebruik, zie de Ollama cheatsheet.
Stel je API-sleutel in als een omgevingsvariabele:
export OLLAMA_API_KEY="your_api_key"
Op Windows PowerShell:
$env:OLLAMA_API_KEY = "your_api_key"
Projectopzet
Maak een nieuw Go-module aan:
mkdir ollama-search
cd ollama-search
go mod init ollama-search
Basiswebzoekfunctie
Hoe authenticatieer ik met de webzoek-API van Ollama in Go? Stel de Authorization-header in met je API-sleutel als een Bearer-token. Maak een API-sleutel aan vanuit je Ollama-account en geef deze door in de aanvraagheader.
Hieronder staat een volledige implementatie van de webzoek-API:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Request/Response types voor 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 omgevingsvariabele niet ingesteld")
}
reqBody := WebSearchRequest{
Query: query,
MaxResults: maxResults,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("mogelijkheid om aanvraag te maken is mislukt: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("mogelijkheid om aanvraag te maken is mislukt: %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("aanvraag is mislukt: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API-fout (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("mogelijkheid om antwoord te lezen is mislukt: %w", err)
}
var searchResp WebSearchResponse
if err := json.Unmarshal(body, &searchResp); err != nil {
return nil, fmt.Errorf("mogelijkheid om antwoord te ontsleutelen is mislukt: %w", err)
}
return &searchResp, nil
}
func main() {
results, err := webSearch("Wat is Ollama?", 5)
if err != nil {
fmt.Printf("Fout: %v\n", err)
return
}
fmt.Println("Zoekresultaten:")
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 Implementatie
Wat is het verschil tussen de web_search en web_fetch endpoints? Het web_search-eindpunt voert een internetzoekopdracht uit en retourneert meerdere zoekresultaten met titels, URLs en samenvattingen. Het web_fetch-eindpunt haalt de volledige inhoud van een specifieke URL op, en retourneert de paginatitel, inhoud en links.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Request/Response types voor 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 omgevingsvariabele niet ingesteld")
}
reqBody := WebFetchRequest{URL: url}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("mogelijkheid om aanvraag te maken is mislukt: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_fetch", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("mogelijkheid om aanvraag te maken is mislukt: %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("aanvraag is mislukt: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API-fout (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("mogelijkheid om antwoord te lezen is mislukt: %w", err)
}
var fetchResp WebFetchResponse
if err := json.Unmarshal(body, &fetchResp); err != nil {
return nil, fmt.Errorf("mogelijkheid om antwoord te ontsleutelen is mislukt: %w", err)
}
return &fetchResp, nil
}
func main() {
result, err := webFetch("https://ollama.com")
if err != nil {
fmt.Printf("Fout: %v\n", err)
return
}
fmt.Printf("Titel: %s\n\n", result.Title)
fmt.Printf("Inhoud:\n%s\n\n", result.Content)
fmt.Printf("Gevonden links: %d\n", len(result.Links))
for i, link := range result.Links {
if i >= 5 {
fmt.Printf(" ... en %d meer\n", len(result.Links)-5)
break
}
fmt.Printf(" - %s\n", link)
}
}
Herbruikbare Clientbibliotheek
Maak een herbruikbare Ollama-clientbibliotheek aan voor schoner 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 omgevingsvariabele niet ingesteld")
}
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-fout (status %d): %s", resp.StatusCode, string(body))
}
var result T
if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}
return &result, nil
}
Gebruik:
package main
import (
"fmt"
"log"
"ollama-search/ollama"
)
func main() {
client, err := ollama.NewClient()
if err != nil {
log.Fatal(err)
}
// Zoek
results, err := client.WebSearch("Nieuwe functies in Ollama", 5)
if err != nil {
log.Fatal(err)
}
for _, r := range results.Results {
fmt.Printf("- %s\n %s\n\n", r.Title, r.URL)
}
// Haal op
page, err := client.WebFetch("https://ollama.com")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Pagina: %s\n", page.Title)
}
Zoekagent Bouwen
Welke modellen werken het beste voor Go-gebaseerde Ollama-zoekagenten? Modellen met sterke tool-gebruiksfaciliteiten werken het beste, waaronder qwen3, gpt-oss, en cloudmodellen zoals qwen3:480b-cloud en deepseek-v3.1-cloud. Als je werkt met Qwen3-modellen en moet je zoekresultaten verwerken of herschikken, zie dan onze gids over herordenen van tekstdocumenten met Ollama en Qwen3 Embedding model in Go.
Hieronder staat een volledige implementatie van een zoekagent:
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 coördineert webzoekfuncties met 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: "Zoek op het internet naar actuele informatie",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]string{
"type": "string",
"description": "De zoekopdracht",
},
},
"required": []string{"query"},
},
},
},
{
Type: "function",
Function: ToolFunction{
Name: "web_fetch",
Description: "Haal de volledige inhoud van een webpagina op",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]string{
"type": "string",
"description": "De URL om op te halen",
},
},
"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("chatfout: %w", err)
}
messages = append(messages, response.Message)
// Geen toolaansroepen betekent dat we een eindantwoord hebben
if len(response.Message.ToolCalls) == 0 {
return response.Message.Content, nil
}
// Voer toolaansroepen uit
for _, toolCall := range response.Message.ToolCalls {
fmt.Printf("🔧 Aanroepen: %s\n", toolCall.Function.Name)
result, err := a.executeTool(toolCall)
if err != nil {
result = fmt.Sprintf("Fout: %v", err)
}
// Truncate voor contextlimieten
if len(result) > 8000 {
result = result[:8000] + "... [verkort]"
}
messages = append(messages, Message{
Role: "tool",
Content: result,
ToolName: toolCall.Function.Name,
})
}
}
return "", fmt.Errorf("max aantal iteraties bereikt")
}
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("ongeldige queryargument")
}
return a.webSearch(query)
case "web_fetch":
url, ok := toolCall.Function.Arguments["url"].(string)
if !ok {
return "", fmt.Errorf("ongeldige urlargument")
}
return a.webFetch(url)
default:
return "", fmt.Errorf("onbekende 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("Wat zijn de nieuwste functies in Ollama?")
if err != nil {
fmt.Printf("Fout: %v\n", err)
return
}
fmt.Println("\n📝 Antwoord:")
fmt.Println(answer)
}
Hoe verwerk ik grote webzoekantwoorden in Go? Truncate de antwoordinhoud voordat je deze doorgeeft aan de modelcontext. Gebruik string slicing om de inhoud te beperken tot ongeveer 8000 tekens om binnen de contextlimieten te blijven.
Concurrente zoekfunctie
Go is goed in concurrente bewerkingen. Hieronder staat hoe je meerdere zoekacties parallel kunt uitvoeren. Het begrijpen van hoe Ollama parallelle aanvragen verwerkt kan je helpen bij het optimaliseren van je concurrente zoekimplementaties.
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 nieuwste functies",
"lokale LLM implementatie",
"AI zoekagenten Go",
}
results := concurrentSearch(queries)
for _, r := range results {
fmt.Printf("\n🔍 Query: %s\n", r.Query)
if r.Error != nil {
fmt.Printf(" Fout: %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
}
Context en Annulering
Voeg contextondersteuning toe voor time-outs en annulering:
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() {
// Maak context met 10 seconden time-out
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("Aanvraag is verlopen")
} else {
fmt.Printf("Fout: %v\n", err)
}
return
}
fmt.Println(result)
}
Aanbevolen modellen
Welke contextlengte moet ik gebruiken voor Go-zoekagenten? Stel de contextlengte in op ongeveer 32000 tokens voor redelijke prestaties. Zoekagenten werken het beste met de volledige contextlengte, omdat webzoekresultaten uitgebreid kunnen zijn. Als je je Ollama-modellen moet beheren of naar andere locaties moet verplaatsen, zie dan onze gids over hoe je Ollama-modellen verplaatst naar een andere schijf of map.
| Model | Parameters | Bestemd voor |
|---|---|---|
qwen3:4b |
4B | Snel lokale zoekacties |
qwen3 |
8B | Algemene doeleinden |
gpt-oss |
Verschillend | Onderzoekstaken |
qwen3:480b-cloud |
480B | Complexere redenering (cloud) |
gpt-oss:120b-cloud |
120B | Langere onderzoeksrapporten (cloud) |
deepseek-v3.1-cloud |
- | Geavanceerde analyse (cloud) |
Voor geavanceerde AI-toepassingen die tekst en visuele inhoud combineren, overweeg dan cross-modal embeddings om je zoekfunctionaliteiten uit te breiden tot meer dan alleen tekstvragen.
Beste praktijken
- Foutafhandeling: Controleer altijd op fouten en behandel API-fouten op een beleefde manier
- Time-outs: Gebruik context met time-outs voor netwerkverzoeken
- Beperkingen: Respecteer Ollama’s API-beperkingen. Wees bewust van mogelijke veranderingen in de Ollama API, zoals besproken in de eerste tekenen van Ollama enshittification
- Resultaatverkorting: Verkort resultaten tot ongeveer 8000 tekens voor contextlimieten
- Concurrente aanvragen: Gebruik goroutines voor parallelle zoekacties
- Verbindingsherbruik: Herbruik HTTP-client voor betere prestaties
- Testen: Schrijf uitgebreide eenheidstests voor je zoekimplementaties. Volg de beste praktijken voor Go-eenheidstesten om ervoor te zorgen dat je code robuust en onderhoudbaar is
Nuttige links
- Ollama cheatsheet
- Herordenen van tekstdocumenten met Ollama en Qwen3 Embedding model - in Go
- Cross-Modal Embeddings: Bridging AI Modalities
- Go Unit Testing: Structure & Best Practices
- Hoe je Ollama-modellen verplaatst naar een andere schijf of map
- Hoe Ollama parallelle aanvragen verwerkt
- De eerste tekenen van Ollama enshittification
- Ollama Web Search Blog Post
- Ollama Officiële Documentatie
- Ollama Go Voorbeelden
- Ollama GitHub Repository