L'uso dell'Ollama Web Search API in Go
Costruisci agenti di ricerca AI con Go e Ollama
L’API di ricerca web di Ollama ti permette di integrare LLM locali con informazioni in tempo reale dal web. Questa guida ti mostra come implementare le capacità di ricerca web in Go, dal semplice utilizzo dell’API alle funzionalità complete degli agenti di ricerca.

Getting Started
Ollama ha una libreria ufficiale per la ricerca web in Go? Ollama fornisce un’API REST per la ricerca web che funziona con qualsiasi client HTTP in Go. Sebbene non esista ancora una libreria ufficiale Go per la ricerca web, puoi facilmente implementare le chiamate API utilizzando i pacchetti della libreria standard.
Per prima cosa, crea una chiave API dal tuo account Ollama. Per un riferimento completo sui comandi e sull’utilizzo di Ollama, consulta la guida rapida di Ollama.
Imposta la tua chiave API come variabile di ambiente:
export OLLAMA_API_KEY="your_api_key"
Su Windows PowerShell:
$env:OLLAMA_API_KEY = "your_api_key"
Setup del Progetto
Crea un nuovo modulo Go:
mkdir ollama-search
cd ollama-search
go mod init ollama-search
Ricerca Web Base
Come autenticarsi con l’API di ricerca web di Ollama in Go? Imposta l’intestazione Authorization con la tua chiave API come token Bearer. Crea una chiave API dal tuo account Ollama e passala nell’intestazione della richiesta.
Ecco un’implementazione completa dell’API di ricerca web:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Tipi di richiesta/risposta per 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("variabile di ambiente OLLAMA_API_KEY non impostata")
}
reqBody := WebSearchRequest{
Query: query,
MaxResults: maxResults,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("fallito nel marshalling della richiesta: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("fallito nella creazione della richiesta: %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("richiesta fallita: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("errore API (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("fallito nel leggere la risposta: %w", err)
}
var searchResp WebSearchResponse
if err := json.Unmarshal(body, &searchResp); err != nil {
return nil, fmt.Errorf("fallito nel unmarshalling della risposta: %w", err)
}
return &searchResp, nil
}
func main() {
results, err := webSearch("Cosa è Ollama?", 5)
if err != nil {
fmt.Printf("Errore: %v\n", err)
return
}
fmt.Println("Risultati della ricerca:")
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] + "..."
}
Implementazione di Fetch Web
Qual è la differenza tra gli endpoint web_search e web_fetch? L’endpoint web_search effettua una query sul web e restituisce diversi risultati di ricerca con titoli, URL e snippet. L’endpoint web_fetch recupera il contenuto completo di un URL specifico, restituendo il titolo della pagina, il contenuto e i collegamenti.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Tipi di richiesta/risposta per 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("variabile di ambiente OLLAMA_API_KEY non impostata")
}
reqBody := WebFetchRequest{URL: url}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("fallito nel marshalling della richiesta: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_fetch", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("fallito nella creazione della richiesta: %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("richiesta fallita: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("errore API (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("fallito nel leggere la risposta: %w", err)
}
var fetchResp WebFetchResponse
if err := json.Unmarshal(body, &fetchResp); err != nil {
return nil, fmt.Errorf("fallito nel unmarshalling della risposta: %w", err)
}
return &fetchResp, nil
}
func main() {
result, err := webFetch("https://ollama.com")
if err != nil {
fmt.Printf("Errore: %v\n", err)
return
}
fmt.Printf("Titolo: %s\n\n", result.Title)
fmt.Printf("Contenuto:\n%s\n\n", result.Content)
fmt.Printf("Collegamenti trovati: %d\n", len(result.Links))
for i, link := range result.Links {
if i >= 5 {
fmt.Printf(" ... e %d altri\n", len(result.Links)-5)
break
}
fmt.Printf(" - %s\n", link)
}
}
Pacchetto Client Riutilizzabile
Crea un pacchetto client riutilizzabile per Ollama per un codice più pulito:
// 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("variabile di ambiente OLLAMA_API_KEY non impostata")
}
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("errore API (status %d): %s", resp.StatusCode, string(body))
}
var result T
if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}
return &result, nil
}
Utilizzo:
package main
import (
"fmt"
"log"
"ollama-search/ollama"
)
func main() {
client, err := ollama.NewClient()
if err != nil {
log.Fatal(err)
}
// Ricerca
results, err := client.WebSearch("Nuove funzionalità di Ollama", 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("Pagina: %s\n", page.Title)
}
Costruzione di un Agente di Ricerca
Quali modelli funzionano meglio per agenti di ricerca basati su Go per Ollama? I modelli con forti capacità di utilizzo degli strumenti funzionano meglio, tra cui qwen3, gpt-oss e modelli cloud come qwen3:480b-cloud e deepseek-v3.1-cloud. Se stai lavorando con modelli Qwen3 e devi elaborare o riorientare i risultati della ricerca, consulta la nostra guida su riordinare documenti di testo con Ollama e modello Qwen3 Embedding in Go.
Ecco un’implementazione completa di un agente di ricerca:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Tipi di chat
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 coordina la ricerca web con 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: "Cerca sul web informazioni attuali",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]string{
"type": "string",
"description": "La query di ricerca",
},
},
"required": []string{"query"},
},
},
},
{
Type: "function",
Function: ToolFunction{
Name: "web_fetch",
Description: "Recupera il contenuto completo di una pagina web",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]string{
"type": "string",
"description": "L'URL da recuperare",
},
},
"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("errore nella chat: %w", err)
}
messages = append(messages, response.Message)
// Nessun richiamo degli strumenti significa che abbiamo una risposta finale
if len(response.Message.ToolCalls) == 0 {
return response.Message.Content, nil
}
// Esegui i richiami degli strumenti
for _, toolCall := range response.Message.ToolCalls {
fmt.Printf("🔧 Richiamo: %s\n", toolCall.Function.Name)
result, err := a.executeTool(toolCall)
if err != nil {
result = fmt.Sprintf("Errore: %v", err)
}
// Tronca per i limiti del contesto
if len(result) > 8000 {
result = result[:8000] + "... [troncato]"
}
messages = append(messages, Message{
Role: "tool",
Content: result,
ToolName: toolCall.Function.Name,
})
}
}
return "", fmt.Errorf("raggiunto il numero massimo di iterazioni")
}
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("argomento query non valido")
}
return a.webSearch(query)
case "web_fetch":
url, ok := toolCall.Function.Arguments["url"].(string)
if !ok {
return "", fmt.Errorf("argomento url non valido")
}
return a.webFetch(url)
default:
return "", fmt.Errorf("strumento sconosciuto: %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("Quali sono le ultime funzionalità in Ollama?")
if err != nil {
fmt.Printf("Errore: %v\n", err)
return
}
fmt.Println("\n📝 Risposta:")
fmt.Println(answer)
}
Come gestire risposte di ricerca web molto grandi in Go? Tronca il contenuto della risposta prima di passarlo al contesto del modello. Utilizza la slicing delle stringhe per limitare il contenuto a circa 8000 caratteri per adattarlo ai limiti del contesto.
Ricerca Concurrente
Go eccelle nelle operazioni concorrenti. Ecco come eseguire diverse ricerche in parallelo. Comprendere come Ollama gestisce le richieste parallele può aiutarti a ottimizzare le tue implementazioni di ricerca concorrente.
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 ricercaConcorrente(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{
"Le ultime funzionalità di Ollama",
"deployamento LLM locale",
"agenti di ricerca AI in Go",
}
results := ricercaConcorrente(queries)
for _, r := range results {
fmt.Printf("\n🔍 Query: %s\n", r.Query)
if r.Error != nil {
fmt.Printf(" Errore: %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
}
Contesto e Annullamento
Aggiungi il supporto al contesto per i timeout e l’annullamento:
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() {
// Crea contesto con timeout di 10 secondi
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := webSearchWithContext(ctx, "API di ricerca web di Ollama")
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
fmt.Println("Richiesta scaduta")
} else {
fmt.Printf("Errore: %v\n", err)
}
return
}
fmt.Println(result)
}
Modelli Consigliati
Qual è la lunghezza del contesto che dovrei utilizzare per gli agenti di ricerca in Go? Imposta la lunghezza del contesto a circa 32000 token per un’adeguata prestazione. Gli agenti di ricerca funzionano meglio con la lunghezza completa del contesto poiché i risultati della ricerca web possono essere estesi. Se devi gestire i tuoi modelli Ollama o spostarli in posizioni diverse, consulta la nostra guida su come spostare i modelli Ollama in un diverso disco o cartella.
| Modello | Parametri | Migliore Per |
|---|---|---|
qwen3:4b |
4B | Ricerche locali rapide |
qwen3 |
8B | Agente generico |
gpt-oss |
Vari | Compiti di ricerca |
qwen3:480b-cloud |
480B | Ragionamento complesso (cloud) |
gpt-oss:120b-cloud |
120B | Ricerca lunga (cloud) |
deepseek-v3.1-cloud |
- | Analisi avanzata (cloud) |
Per applicazioni AI avanzate che combinano contenuti testuali e visivi, considera l’explorazione di embedding cross-modal per estendere le tue capacità di ricerca oltre le query testuali.
Linee Guida per la Migliore Pratica
- Gestione degli errori: Controlla sempre per gli errori e gestisci le fallite delle API in modo gentile
- Timeout: Utilizza il contesto con i timeout per le richieste di rete
- Limiti di velocità: Rispetta i limiti di velocità dell’API di Ollama. Sii consapevole di potenziali modifiche all’API di Ollama, come discusso in primi segni di Ollama enshittification
- Troncamento dei risultati: Tronca i risultati a circa 8000 caratteri per i limiti del contesto
- Richieste concorrenti: Utilizza le goroutine per le ricerche parallele
- Riuso delle connessioni: Riusa il client HTTP per una migliore prestazione
- Test: Scrivi test unitari completi per le tue implementazioni di ricerca. Segui le migliori pratiche per i test unitari in Go per assicurarti che il tuo codice sia robusto e mantenibile
Link Utili
- Guida rapida di Ollama
- Riordinare documenti di testo con Ollama e modello Qwen3 Embedding - in Go
- Embedding Cross-Modal: Collegare le Modalità AI
- Test Unitari in Go: Struttura e Best Practice
- Come spostare i modelli Ollama in un diverso disco o cartella
- Come Ollama Gestisce le Richieste Parallele
- Primi segni di Ollama Enshittification
- Blog Post Ollama Web Search
- Documentazione Ufficiale Ollama
- Esempi Ollama Go
- Repository GitHub Ollama