Usando a API de Busca Web do Ollama em Go
Construa agentes de busca com IA usando Go e Ollama
A API de Pesquisa Web da Ollama permite aprimorar LLMs locais com informações da web em tempo real. Este guia mostra como implementar capacidades de pesquisa web em Go, desde chamadas simples de API até agentes de pesquisa completos.

Introdução
A Ollama possui uma biblioteca oficial em Go para pesquisa web? A Ollama oferece uma API REST para pesquisa web que funciona com qualquer cliente HTTP em Go. Embora não haja um SDK oficial em Go para pesquisa web ainda, você pode implementar facilmente as chamadas de API usando pacotes da biblioteca padrão.
Primeiro, crie uma chave de API a partir da sua conta Ollama. Para uma referência abrangente sobre comandos e uso da Ollama, consulte a folha de dicas da Ollama.
Defina sua chave de API como uma variável de ambiente:
export OLLAMA_API_KEY="your_api_key"
No PowerShell do Windows:
$env:OLLAMA_API_KEY = "your_api_key"
Configuração do Projeto
Crie um novo módulo Go:
mkdir ollama-search
cd ollama-search
go mod init ollama-search
Pesquisa Web Básica
Como faço para autenticar na API de pesquisa web da Ollama em Go? Defina o cabeçalho Authorization com sua chave de API como um token Bearer. Crie uma chave de API a partir da sua conta Ollama e passe-a no cabeçalho da solicitação.
Aqui está uma implementação completa da API de pesquisa web:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Tipos de Solicitação/Resposta para 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("variável de ambiente OLLAMA_API_KEY não definida")
}
reqBody := WebSearchRequest{
Query: query,
MaxResults: maxResults,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("falha ao serializar solicitação: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("falha ao criar solicitação: %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("solicitação falhou: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("erro na API (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("falha ao ler resposta: %w", err)
}
var searchResp WebSearchResponse
if err := json.Unmarshal(body, &searchResp); err != nil {
return nil, fmt.Errorf("falha ao desserializar resposta: %w", err)
}
return &searchResp, nil
}
func main() {
results, err := webSearch("What is Ollama?", 5)
if err != nil {
fmt.Printf("Erro: %v\n", err)
return
}
fmt.Println("Resultados da Pesquisa:")
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] + "..."
}
Implementação de Web Fetch
Qual é a diferença entre os endpoints web_search e web_fetch? O endpoint web_search consulta a internet e retorna vários resultados de pesquisa com títulos, URLs e trechos. O endpoint web_fetch recupera o conteúdo completo de uma URL específica, retornando o título da página, o conteúdo e os links.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Tipos de Solicitação/Resposta para 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("variável de ambiente OLLAMA_API_KEY não definida")
}
reqBody := WebFetchRequest{URL: url}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("falha ao serializar solicitação: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_fetch", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("falha ao criar solicitação: %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("solicitação falhou: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("erro na API (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("falha ao ler resposta: %w", err)
}
var fetchResp WebFetchResponse
if err := json.Unmarshal(body, &fetchResp); err != nil {
return nil, fmt.Errorf("falha ao desserializar resposta: %w", err)
}
return &fetchResp, nil
}
func main() {
result, err := webFetch("https://ollama.com")
if err != nil {
fmt.Printf("Erro: %v\n", err)
return
}
fmt.Printf("Título: %s\n\n", result.Title)
fmt.Printf("Conteúdo:\n%s\n\n", result.Content)
fmt.Printf("Links encontrados: %d\n", len(result.Links))
for i, link := range result.Links {
if i >= 5 {
fmt.Printf(" ... e mais %d\n", len(result.Links)-5)
break
}
fmt.Printf(" - %s\n", link)
}
}
Pacote de Cliente Reutilizável
Crie um pacote de cliente Ollama reutilizável para um código mais limpo:
// 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("variável de ambiente OLLAMA_API_KEY não definida")
}
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("erro na 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
}
Uso:
package main
import (
"fmt"
"log"
"ollama-search/ollama"
)
func main() {
client, err := ollama.NewClient()
if err != nil {
log.Fatal(err)
}
// Pesquisa
results, err := client.WebSearch("Ollama new features", 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("Página: %s\n", page.Title)
}
Construindo um Agente de Pesquisa
Quais modelos funcionam melhor para agentes de pesquisa Ollama baseados em Go? Modelos com fortes capacidades de uso de ferramentas funcionam melhor, incluindo qwen3, gpt-oss e modelos em nuvem como qwen3:480b-cloud e deepseek-v3.1-cloud. Se você está trabalhando com modelos Qwen3 e precisa processar ou reordenar resultados de pesquisa, veja nosso guia sobre reordenação de documentos de texto com Ollama e o modelo de incorporação Qwen3 em Go.
Aqui está uma implementação completa de um agente de pesquisa:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Tipos 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 orquestra a pesquisa web com 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: "Pesquise na web por informações atuais",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]string{
"type": "string",
"description": "A consulta de pesquisa",
},
},
"required": []string{"query"},
},
},
},
{
Type: "function",
Function: ToolFunction{
Name: "web_fetch",
Description: "Recupere o conteúdo completo de uma página web",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]string{
"type": "string",
"description": "A URL para recuperar",
},
},
"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("erro no chat: %w", err)
}
messages = append(messages, response.Message)
// Nenhuma chamada de ferramenta significa que temos uma resposta final
if len(response.Message.ToolCalls) == 0 {
return response.Message.Content, nil
}
// Executar chamadas de ferramenta
for _, toolCall := range response.Message.ToolCalls {
fmt.Printf("🔧 Chamando: %s\n", toolCall.Function.Name)
result, err := a.executeTool(toolCall)
if err != nil {
result = fmt.Sprintf("Erro: %v", err)
}
// Truncar para limites de contexto
if len(result) > 8000 {
result = result[:8000] + "... [truncado]"
}
messages = append(messages, Message{
Role: "tool",
Content: result,
ToolName: toolCall.Function.Name,
})
}
}
return "", fmt.Errorf("máximo de iterações atingido")
}
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("argumento de consulta inválido")
}
return a.webSearch(query)
case "web_fetch":
url, ok := toolCall.Function.Arguments["url"].(string)
if !ok {
return "", fmt.Errorf("argumento de URL inválido")
}
return a.webFetch(url)
default:
return "", fmt.Errorf("ferramenta desconhecida: %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("What are the latest features in Ollama?")
if err != nil {
fmt.Printf("Erro: %v\n", err)
return
}
fmt.Println("\n📝 Resposta:")
fmt.Println(answer)
}
Como faço para lidar com grandes respostas de pesquisa web em Go? Trunque o conteúdo da resposta antes de passá-lo ao contexto do modelo. Use fatiamento de string para limitar o conteúdo a aproximadamente 8000 caracteres para caber dentro dos limites de contexto.
Pesquisa Concorrente
O Go é excelente em operações concorrentes. Veja como executar várias pesquisas em paralelo. Compreender como a Ollama lida com solicitações paralelas pode ajudar a otimizar suas implementações de pesquisa 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 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 latest features",
"local LLM deployment",
"AI search agents Go",
}
results := concurrentSearch(queries)
for _, r := range results {
fmt.Printf("\n🔍 Consulta: %s\n", r.Query)
if r.Error != nil {
fmt.Printf(" Erro: %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
}
Contexto e Cancelamento
Adicione suporte a contexto para tempos limite e cancelamento:
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() {
// Criar contexto com tempo limite de 10 segundos
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("Tempo limite da solicitação excedido")
} else {
fmt.Printf("Erro: %v\n", err)
}
return
}
fmt.Println(result)
}
Modelos Recomendados
Qual comprimento de contexto devo usar para agentes de pesquisa em Go? Defina o comprimento do contexto para aproximadamente 32000 tokens para um desempenho razoável. Agentes de pesquisa funcionam melhor com o comprimento de contexto total, pois os resultados de pesquisa web podem ser extensos. Se você precisa gerenciar seus modelos Ollama ou movê-los para diferentes localizações, veja nosso guia sobre como mover modelos Ollama para outro drive ou pasta.
| Modelo | Parâmetros | Melhor Para |
|---|---|---|
qwen3:4b |
4B | Pesquisas locais rápidas |
qwen3 |
8B | Agente de uso geral |
gpt-oss |
Vários | Tarefas de pesquisa |
qwen3:480b-cloud |
480B | Raciocínio complexo (nuvem) |
gpt-oss:120b-cloud |
120B | Pesquisa extensa (nuvem) |
deepseek-v3.1-cloud |
- | Análise avançada (nuvem) |
Boas Práticas
- Tratamento de Erros: Sempre verifique por erros e lidere com falhas da API graciosamente
- Tempos Limite: Use contexto com tempos limite para solicitações de rede
- Limitação de Taxa: Respeite os limites de taxa da API da Ollama. Esteja ciente de possíveis mudanças na API da Ollama, como discutido em primeiros sinais de enshittification da Ollama
- Truncamento de Resultados: Trunque os resultados para ~8000 caracteres devido aos limites de contexto
- Solicitações Concorrentes: Use goroutines para pesquisas paralelas
- Reuso de Conexão: Reutilize o cliente HTTP para melhor desempenho
- Testes: Escreva testes unitários abrangentes para suas implementações de pesquisa. Siga as boas práticas de teste unitário em Go para garantir que seu código seja robusto e manutenível
Links Úteis
- Folha de dicas da Ollama
- Reordenação de documentos de texto com Ollama e o modelo de incorporação Qwen3 - em Go
- Como Mover Modelos Ollama para Outro Drive ou Pasta
- Como a Ollama Lida com Solicitações Paralelas
- Primeiros Sinais de Enshittification da Ollama
- Post de Blog sobre Pesquisa Web da Ollama
- Documentação Oficial da Ollama
- Exemplos da Ollama em Go
- Repositório GitHub da Ollama