Utilisation de l’API de recherche web Ollama en Go
Créer des agents de recherche IA avec Go et Ollama
L’API Web Search d’Ollama vous permet d’enrichir les LLM locaux avec des informations web en temps réel. Ce guide vous montre comment implémenter les fonctionnalités de recherche web en Go, depuis des appels API simples jusqu’à des agents de recherche complets.

Démarrage
Ollama dispose-t-il d’une bibliothèque officielle Go pour la recherche web ? Ollama fournit une API REST pour la recherche web qui fonctionne avec n’importe quel client HTTP Go. Bien qu’il n’existe pas encore de SDK Go officiel pour la recherche web, vous pouvez facilement implémenter les appels API en utilisant des packages de la bibliothèque 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 le tableau de bord Ollama.
Définissez votre clé API comme 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 s’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 de la sérialisation 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("échec de la requête : %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 de la désérialisation 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 du Web Fetch
Quelle est la différence entre les points de terminaison web_search et web_fetch ? Le point de terminaison web_search interroge Internet et renvoie plusieurs résultats de recherche avec des titres, des URL et des extraits. Le point de terminaison web_fetch récupère le contenu complet d’une URL spécifique, en 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 de la sérialisation 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("échec de la requête : %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 de la désérialisation 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 de plus\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 d’utilisation d’outils solides fonctionnent le mieux, y compris 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 avez besoin de traiter ou de reclasser des résultats de recherche, consultez notre guide sur la reclassification de documents texte avec le modèle Ollama et Qwen3 Embedding en Go.
Voici une implémentation complète d’un agent de recherche :
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Types de discussion
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 un 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 des informations actuelles sur le web",
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 discussion : %w", err)
}
messages = append(messages, response.Message)
// L'absence d'appels d'outils 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("nombre maximal d'itérations atteint")
}
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 grandes réponses de recherche web en Go ? Tronquez le contenu de la réponse avant de le transmettre au contexte du modèle. Utilisez le tranchage de chaînes pour limiter le contenu à environ 8000 caractères pour qu’il s’adapte aux limites de contexte.
Recherche Concurrente
Go excelle dans les opérations concurrentes. 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 concurrente.
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{
"Dernières fonctionnalités d'Ollama",
"Déploiement de LLM locaux",
"Agents de recherche IA 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 d’attente (timeouts) 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 dois-je utiliser pour les agents de recherche Go ? Définissez la longueur du contexte à environ 32 000 jetons pour une performance raisonnable. Les agents de recherche fonctionnent le mieux avec la longueur de contexte complète, car les résultats de la recherche web peuvent être étendus. Si vous avez besoin de gérer vos modèles Ollama ou de les déplacer vers des emplacements différents, consultez notre guide sur comment déplacer les modèles Ollama vers un disque ou un dossier différent.
| Modèle | Paramètres | Idéal pour |
|---|---|---|
qwen3:4b |
4B | Recherches locales rapides |
qwen3 |
8B | Agent à usage général |
gpt-oss |
Divers | Tâches de recherche |
qwen3:480b-cloud |
480B | Raisonnement complexe (cloud) |
gpt-oss:120b-cloud |
120B | Recherche en format long (cloud) |
deepseek-v3.1-cloud |
- | Analyse avancée (cloud) |
Bonnes Pratiques
- Gestion des erreurs : Vérifiez toujours les erreurs et gérez les échecs de l’API avec élégance.
- Délais d’attente : Utilisez le contexte avec des délais pour les requêtes réseau.
- Limitation de débit (Rate Limiting) : Respectez les limites de débit de l’API d’Ollama. Soyez conscient des changements potentiels de l’API d’Ollama, comme discuté dans les premiers signes de l’enshittification d’Ollama.
- Troncation des résultats : Tronquez les résultats à ~8000 caractères pour les limites de contexte.
- Requêtes concurrentes : Utilisez des goroutines pour les recherches parallèles.
- Réutilisation des connexions : Réutilisez le client HTTP pour une meilleure performance.
- Tests : Rédigez des tests unitaires complets pour vos implémentations de recherche. Suivez les bonnes pratiques de test unitaire Go pour vous assurer que votre code est robuste et maintainable.
Liens Utiles
- Aide-mémoire Ollama
- Reclassification de documents texte avec Ollama et le modèle Qwen3 Embedding - en Go
- Comment Déplacer les Modèles Ollama vers un Disque ou un Dossier Différent
- Comment Ollama Gère les Requêtes Parallèles
- Premiers Signes de l’Enshittification d’Ollama
- Article de blog sur la recherche web d’Ollama
- Documentation officielle d’Ollama
- Exemples Go d’Ollama
- Dépôt GitHub d’Ollama