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

Começando
O Ollama tem uma biblioteca Go oficial para busca na web? O Ollama fornece uma API REST para busca na web que funciona com qualquer cliente HTTP Go. Embora ainda não haja um SDK Go oficial para busca na web, você pode implementar facilmente as chamadas de API usando os 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, confira o resumo de comandos do Ollama.
Defina sua chave de API como uma variável de ambiente:
export OLLAMA_API_KEY="your_api_key"
No Windows PowerShell:
$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 autenticar com a API de busca na web do Ollama no 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 na 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 de 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("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 Busca 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 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 de 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 %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[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 de 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("novos recursos 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)
}
// Busca
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 capacidades robustas 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 o modelo de incorporação Ollama e 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 na 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 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: "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 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)
// Sem chamadas 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("iteração máxima atingida")
}
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 os recursos mais recentes 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 na web no Go? Trunque o conteúdo da resposta antes de passá-lo para o contexto do modelo. Use fatiamento de strings para limitar o conteúdo a aproximadamente 8000 caracteres para caber nos limites de contexto.
Pesquisa Concurrente
Go se destaca em operações concorrentes. Aqui está como realizar múltiplas 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{
"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 de contexto para tempos de espera 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("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 Go? Defina o comprimento do contexto para aproximadamente 32000 tokens para desempenho razoável. Agentes de pesquisa funcionam melhor com comprimento de contexto completo, pois os resultados da pesquisa na web podem ser extensos. Se você precisar gerenciar seus modelos Ollama ou movê-los para locais diferentes, veja nosso guia sobre como mover modelos Ollama para uma unidade ou pasta diferente.
| 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 de longa forma (nuvem) |
deepseek-v3.1-cloud |
- | Análise avançada (nuvem) |
Para aplicações de IA avançadas que combinam conteúdo de texto e visual, considere explorar incorporações multimodais para estender suas capacidades de pesquisa além de consultas apenas de texto.
Melhores Práticas
- Tratamento de Erros: Sempre verifique erros e lida com falhas de API de forma adequada
- Tempos Limite: Use contexto com tempos limite para solicitações de rede
- Limitação de Taxa: Respeite os limites de taxa de API do Ollama. Esteja ciente de possíveis mudanças na API do Ollama, conforme discutido em primeiros sinais da degradação do Ollama
- Truncamento de Resultados: Trunque 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 as melhores práticas de testes unitários em Go para garantir que seu código seja robusto e mantível
Links Úteis
- Resumo de comandos do Ollama
- Reclassificação de documentos de texto com o modelo de incorporação Ollama e Qwen3 - em Go
- Incorporações Multimodais: Conectando Modalidades de IA
- Testes Unitários em Go: Estrutura e Melhores Práticas
- Como Mover Modelos Ollama para uma Unidade ou Pasta Diferente
- Como o Ollama Lida com Solicitações Paralelas
- Primeiros Sinais da Degradação do Ollama
- Postagem de Blog sobre Pesquisa Web do Ollama
- Documentação Oficial do Ollama
- Exemplos do Ollama em Go
- Repositório GitHub do Ollama