Utiliser l'API de recherche web d'Ollama en Go
Construisez des agents de recherche IA avec Go et Ollama
L’API de recherche web d’Ollama vous permet d’augmenter les LLM locaux avec des informations en temps réel du web. Ce guide vous montre comment implémenter des capacités de recherche web en Go, des appels d’API simples aux agents de recherche complets.

Getting Started
Ollama a-t-il une bibliothèque Go officielle pour la recherche web ? Ollama fournit une API REST pour la recherche web qui fonctionne avec tout client HTTP Go. Bien qu’il n’y ait pas encore de SDK Go officiel pour la recherche web, vous pouvez facilement implémenter les appels d’API à l’aide des packages standard.
Tout d’abord, créez une clé API à partir de votre compte Ollama. Pour une référence complète sur les commandes et l’utilisation d’Ollama, consultez la feuille de rappel Ollama.
Définissez votre clé API en tant que variable d’environnement :
export OLLAMA_API_KEY="your_api_key"
Sur Windows PowerShell :
$env:OLLAMA_API_KEY = "your_api_key"
Configuration du projet
Créez un nouveau module Go :
mkdir ollama-search
cd ollama-search
go mod init ollama-search
Recherche web de base
Comment m’authentifier avec l’API de recherche web d’Ollama en Go ? Définissez l’en-tête Authorization avec votre clé API en tant que jeton Bearer. Créez une clé API à partir de votre compte Ollama et passez-la dans l’en-tête de la requête.
Voici une implémentation complète de l’API de recherche web :
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Types de requête/réponse pour 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("variable d'environnement OLLAMA_API_KEY non définie")
}
reqBody := WebSearchRequest{
Query: query,
MaxResults: maxResults,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("échec du marshalling de la requête : %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("échec de la création de la requête : %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("requête échouée : %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("erreur API (statut %d) : %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("échec de la lecture de la réponse : %w", err)
}
var searchResp WebSearchResponse
if err := json.Unmarshal(body, &searchResp); err != nil {
return nil, fmt.Errorf("échec du démarshalling de la réponse : %w", err)
}
return &searchResp, nil
}
func main() {
results, err := webSearch("Qu'est-ce que Ollama ?", 5)
if err != nil {
fmt.Printf("Erreur : %v\n", err)
return
}
fmt.Println("Résultats de la recherche :")
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] + ". .."
}
Implémentation de la récupération web
Quelle est la différence entre les points de terminaison web_search et web_fetch ? Le point de terminaison web_search interroge l’internet et renvoie plusieurs résultats de recherche avec des titres, des URLs et des extraits. Le point de terminaison web_fetch récupère le contenu complet d’une URL spécifique, renvoyant le titre de la page, le contenu et les liens.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Types de requête/réponse pour 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("variable d'environnement OLLAMA_API_KEY non définie")
}
reqBody := WebFetchRequest{URL: url}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("échec du marshalling de la requête : %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_fetch", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("échec de la création de la requête : %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("requête échouée : %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("erreur API (statut %d) : %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("échec de la lecture de la réponse : %w", err)
}
var fetchResp WebFetchResponse
if err := json.Unmarshal(body, &fetchResp); err != nil {
return nil, fmt.Errorf("échec du démarshalling de la réponse : %w", err)
}
return &fetchResp, nil
}
func main() {
result, err := webFetch("https://ollama.com")
if err != nil {
fmt.Printf("Erreur : %v\n", err)
return
}
fmt.Printf("Titre : %s\n\n", result.Title)
fmt.Printf("Contenu : \n%s\n\n", result.Content)
fmt.Printf("Liens trouvés : %d\n", len(result.Links))
for i, link := range result.Links {
if i >= 5 {
fmt.Printf(" ... et %d autres\n", len(result.Links)-5)
break
}
fmt.Printf(" - %s\n", link)
}
}
Package client réutilisable
Créez un package client Ollama réutilisable pour un code plus propre :
// 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("variable d'environnement OLLAMA_API_KEY non définie")
}
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("erreur API (statut %d) : %s", resp.StatusCode, string(body))
}
var result T
if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}
return &result, nil
}
Utilisation :
package main
import (
"fmt"
"log"
"ollama-search/ollama"
)
func main() {
client, err := ollama.NewClient()
if err != nil {
log.Fatal(err)
}
// Recherche
results, err := client.WebSearch("Nouvelles fonctionnalités d'Ollama", 5)
if err != nil {
log.Fatal(err)
}
for _, r := range results.Results {
fmt.Printf("- %s\n %s\n\n", r.Title, r.URL)
}
// Récupération
page, err := client.WebFetch("https://ollama.com")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Page : %s\n", page.Title)
}
Construction d’un agent de recherche
Quels modèles fonctionnent le mieux pour les agents de recherche Ollama basés sur Go ? Les modèles avec des capacités fortes d’utilisation d’outils fonctionnent le mieux, notamment qwen3, gpt-oss, et les modèles cloud comme qwen3:480b-cloud et deepseek-v3.1-cloud. Si vous travaillez avec des modèles Qwen3 et que vous avez besoin de traiter ou de reclasser les résultats de recherche, consultez notre guide sur le reclassage de documents textuels avec Ollama et le modèle d’embedding Qwen3 en Go.
Voici une implémentation complète d’agent de recherche :
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Types de 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 orchestre la recherche web avec 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: "Rechercher sur le web des informations actuelles",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]string{
"type": "string",
"description": "La requête de recherche",
},
},
"required": []string{"query"},
},
},
},
{
Type: "function",
Function: ToolFunction{
Name: "web_fetch",
Description: "Récupérer le contenu complet d'une page web",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]string{
"type": "string",
"description": "L'URL à récupérer",
},
},
"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("erreur de chat : %w", err)
}
messages = append(messages, response.Message)
// Aucun appel d'outil signifie que nous avons une réponse finale
if len(response.Message.ToolCalls) == 0 {
return response.Message.Content, nil
}
// Exécuter les appels d'outils
for _, toolCall := range response.Message.ToolCalls {
fmt.Printf("🔧 Appel : %s\n", toolCall.Function.Name)
result, err := a.executeTool(toolCall)
if err != nil {
result = fmt.Sprintf("Erreur : %v", err)
}
// Tronquer pour les limites de contexte
if len(result) > 8000 {
result = result[:8000] + "... [tronqué]"
}
messages = append(messages, Message{
Role: "tool",
Content: result,
ToolName: toolCall.Function.Name,
})
}
}
return "", fmt.Errorf("itérations maximales atteintes")
}
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("argument de requête invalide")
}
return a.webSearch(query)
case "web_fetch":
url, ok := toolCall.Function.Arguments["url"].(string)
if !ok {
return "", fmt.Errorf("argument d'URL invalide")
}
return a.webFetch(url)
default:
return "", fmt.Errorf("outil inconnu : %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("Quelles sont les dernières fonctionnalités d'Ollama ?")
if err != nil {
fmt.Printf("Erreur : %v\n", err)
return
}
fmt.Println("\n📝 Réponse :")
fmt.Println(answer)
}
Comment gérer les réponses de recherche web volumineuses en Go ? Tronquer le contenu de la réponse avant de le passer au contexte du modèle. Utilisez la troncature de chaîne pour limiter le contenu à environ 8000 caractères pour s’adapter aux limites de contexte.
Recherche concurrente
Go excelle dans les opérations concourantes. Voici comment effectuer plusieurs recherches en parallèle. Comprendre comment Ollama gère les requêtes parallèles peut vous aider à optimiser vos implémentations de recherche concourante.
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 dernières fonctionnalités",
"déploiement LLM local",
"agents de recherche AI Go",
}
results := concurrentSearch(queries)
for _, r := range results {
fmt.Printf("\n🔍 Requête : %s\n", r.Query)
if r.Error != nil {
fmt.Printf(" Erreur : %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
}
Contexte et annulation
Ajoutez le support du contexte pour les délais et l’annulation :
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() {
// Créer un contexte avec un délai de 10 secondes
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := webSearchWithContext(ctx, "API de recherche web d'Ollama")
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
fmt.Println("La requête a expiré")
} else {
fmt.Printf("Erreur : %v\n", err)
}
return
}
fmt.Println(result)
}
Modèles recommandés
Quelle longueur de contexte devrais-je utiliser pour les agents de recherche Go ? Définissez la longueur de contexte à environ 32000 tokens pour une performance raisonnable. Les agents de recherche fonctionnent le mieux avec une longueur de contexte complète car les résultats de recherche web peuvent être étendus. Si vous avez besoin de gérer vos modèles Ollama ou de les déplacer vers différents emplacements, consultez notre guide sur comment déplacer les modèles Ollama vers un autre disque ou dossier.
| Modèle | Paramètres | Meilleur pour |
|---|---|---|
qwen3:4b |
4B | Recherches locales rapides |
qwen3 |
8B | Agent général |
gpt-oss |
Variés | Tâches de recherche |
qwen3:480b-cloud |
480B | Raisonnement complexe (cloud) |
gpt-oss:120b-cloud |
120B | Recherche longue forme (cloud) |
deepseek-v3.1-cloud |
- | Analyse avancée (cloud) |
Pour des applications avancées d’IA qui combinent du contenu textuel et visuel, envisagez d’explorer les embeddings multimodaux pour étendre vos capacités de recherche au-delà des requêtes textuelles uniquement.
Bonnes pratiques
- Gestion des erreurs : Vérifiez toujours les erreurs et gérez les échecs d’API de manière gracieuse
- Délais : Utilisez le contexte avec des délais pour les requêtes réseau
- Limitation de débit : Respectez les limites de débit de l’API Ollama. Soyez conscient des changements potentiels de l’API Ollama, comme discuté dans les premiers signes de l’enshittification d’Ollama
- Troncature des résultats : Tronquez les résultats à ~8000 caractères pour les limites de contexte
- Requêtes concourantes : Utilisez des goroutines pour des recherches parallèles
- Réutilisation des connexions : Réutilisez le client HTTP pour une meilleure performance
- Tests : Écrivez des tests unitaires complets pour vos implémentations de recherche. Suivez les meilleures pratiques de tests unitaires en Go pour vous assurer que votre code est robuste et maintenable
Liens utiles
- Feuille de rappel Ollama
- Reclassage de documents textuels avec Ollama et le modèle d’embedding Qwen3 - en Go
- Embeddings multimodaux : passer d’une modalité à l’autre
- Tests unitaires en Go : structure et meilleures pratiques
- Comment déplacer les modèles Ollama vers un autre disque ou dossier
- Comment Ollama gère les requêtes parallèles
- Premiers signes de l’enshittification d’Ollama
- Blog Ollama sur la recherche web
- Documentation officielle d’Ollama
- Exemples Go d’Ollama
- Dépôt GitHub d’Ollama