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

Começando
O Ollama tem uma biblioteca oficial para Go para pesquisa web? O Ollama fornece uma API REST para pesquisa web que funciona com qualquer cliente HTTP Go. Embora ainda não haja uma SDK oficial para Go para pesquisa web, você pode facilmente implementar as chamadas da 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 do Ollama, consulte a folha de dicas do 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 Básica
Como autenticar com a API de pesquisa web do 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 a 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 a 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 da API (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("falha ao ler a resposta: %w", err)
}
var searchResp WebSearchResponse
if err := json.Unmarshal(body, &searchResp); err != nil {
return nil, fmt.Errorf("falha ao desserializar a resposta: %w", err)
}
return &searchResp, nil
}
func main() {
results, err := webSearch("O que é 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 Fetch Web
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 a 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 a 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 da API (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("falha ao ler a resposta: %w", err)
}
var fetchResp WebFetchResponse
if err := json.Unmarshal(body, &fetchResp); err != nil {
return nil, fmt.Errorf("falha ao desserializar a 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 %d mais\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[Fetch链](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 da 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)
}
// Pesquisar
results, err := client.WebSearch("novas funcionalidades do Ollama", 5)
if err != nil {
log.Fatal(err)
}
for _, r := range results.Results {
fmt.Printf("- %s\n %s\n\n", r.Title, r.URL)
}
// Buscar
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 do Ollama baseados em Go? Modelos com capacidades fortes 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ê estiver trabalhando com modelos Qwen3 e precisar processar ou reclassificar resultados de pesquisa, veja nosso guia sobre reclassificação de documentos de texto com Ollama e modelo Qwen3 Embedding 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: "Pesquisar a 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: "Recuperar o conteúdo completo de uma página da web",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]string{
"type": "string",
"description": "A URL a ser recuperada",
},
},
"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 ferramentas
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("atingido o número máximo de iterações")
}
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("Quais são as últimas funcionalidades do Ollama?")
if err != nil {
fmt.Printf("Erro: %v\n", err)
return
}
fmt.Println("\n📝 Resposta:")
fmt.Println(answer)
}
Como lidar com grandes respostas de pesquisa web em Go? Truncar o conteúdo da resposta antes de passá-lo para o contexto do modelo. Use fatiamento de string para limitar o conteúdo a aproximadamente 8000 caracteres para caber nos limites de contexto.
Pesquisa Concorrente
O Go é excelente para operações concorrentes. Aqui está como realizar várias pesquisas em paralelo. Entender como o Ollama lida com solicitações paralelas pode ajudá-lo 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{
"Funcionalidades mais recentes do Ollama",
"implantação local de LLM",
"agentes de pesquisa de AI em Go",
}
results := concurrentSearch(queries)
for _, r := range results {
fmt.Printf("\n🔍 Query: %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 ao contexto para temporizadores 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() {
// Crie contexto com timeout de 10 segundos
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := webSearchWithContext(ctx, "API de pesquisa web do Ollama")
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
fmt.Println("Solicitação expirou")
} 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 total do contexto, pois os resultados da pesquisa da web podem ser extensos. Se você precisar gerenciar seus modelos do Ollama ou movê-los para diferentes locais, veja nosso guia sobre como mover modelos do Ollama para diferentes unidades ou pastas.
| Modelo | Parâmetros | Melhor Para |
|---|---|---|
qwen3:4b |
4B | Pesquisas locais rápidas |
qwen3 |
8B | Agente de propósito geral |
gpt-oss |
Vários | Tarefas de pesquisa |
qwen3:480b-cloud |
480B | Raciocínio complexo (nuvem) |
gpt-oss:120b-cloud |
120B | Pesquisa longa-formato (nuvem) |
deepseek-v3.1-cloud |
- | Análise avançada (nuvem) |
Para aplicações avançadas de IA que combinam conteúdo de texto e visual, considere explorar embeddings multimodais para estender suas capacidades de pesquisa além de consultas de texto apenas.
Boas Práticas
- Tratamento de Erros: Sempre verifique por erros e trate falhas da API de forma gentil
- Temporizadores: Use contexto com temporizadores para solicitações de rede
- Limites de Taxa: Respeite os limites de taxa da API do Ollama. Esteja ciente das possíveis mudanças na API do Ollama, conforme discutido em primeiros sinais de enshittification do Ollama
- Truncamento de Resultados: Truncar resultados para ~8000 caracteres para limites de contexto
- Solicitações Concorrentes: Use goroutines para pesquisas paralelas
- Reutilização de Conexão: Reutilize o cliente HTTP para melhor desempenho
- Testes: Escreva testes unitários abrangentes para suas implementações de pesquisa. Siga melhores práticas de testes unitários em Go para garantir que seu código seja robusto e mantível
Links Úteis
- Folha de dicas do Ollama
- Reclassificação de documentos de texto com Ollama e modelo Qwen3 Embedding - em Go
- Embeddings Multimodais: Conectando Modalidades de IA
- Testes Unitários em Go: Estrutura e Melhores Práticas
- Como mover modelos do Ollama para diferentes unidades ou pastas
- Como o Ollama lida com solicitações paralelas
- Primeiros sinais de enshittification do Ollama
- Post do Blog da API de Pesquisa Web do Ollama
- Documentação Oficial do Ollama
- Exemplos do Ollama em Go
- Repositório do GitHub do Ollama