Menggunakan Ollama Web Search API dalam Go
Bangun agen pencarian AI dengan Go dan Ollama
API Pencarian Web Ollama memungkinkan Anda memperluas LLM lokal dengan informasi web secara real-time. Panduan ini menunjukkan cara mengimplementasikan kemampuan pencarian web dalam Go, dari panggilan API sederhana hingga agen pencarian berfitur lengkap.

Memulai
Apakah Ollama memiliki perpustakaan Go resmi untuk pencarian web? Ollama menyediakan API REST untuk pencarian web yang berfungsi dengan klien HTTP Go apa pun. Meskipun belum ada SDK Go resmi untuk pencarian web, Anda dapat dengan mudah mengimplementasikan panggilan API menggunakan paket standar.
Pertama, buat kunci API dari akun Ollama Anda. Untuk referensi menyeluruh mengenai perintah dan penggunaan Ollama, lihat lembar cepat Ollama.
Atur kunci API Anda sebagai variabel lingkungan:
export OLLAMA_API_KEY="your_api_key"
Di Windows PowerShell:
$env:OLLAMA_API_KEY = "your_api_key"
Pengaturan Proyek
Buat modul Go baru:
mkdir ollama-search
cd ollama-search
go mod init ollama-search
Pencarian Web Dasar
Bagaimana cara saya mengotentikasi dengan API pencarian web Ollama dalam Go? Tetapkan header Authorization dengan kunci API Anda sebagai token Bearer. Buat kunci API dari akun Ollama Anda dan masukkan ke dalam header permintaan.
Berikut adalah implementasi lengkap dari API pencarian web:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Tipe permintaan/Respons untuk 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("variabel lingkungan OLLAMA_API_KEY tidak disetel")
}
reqBody := WebSearchRequest{
Query: query,
MaxResults: maxResults,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("gagal mengubah permintaan: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("gagal membuat permintaan: %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("permintaan gagal: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("kesalahan API (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("gagal membaca respons: %w", err)
}
var searchResp WebSearchResponse
if err := json.Unmarshal(body, &searchResp); err != nil {
return nil, fmt.Errorf("gagal mengubah respons: %w", err)
}
return &searchResp, nil
}
func main() {
results, err := webSearch("Apa itu Ollama?", 5)
if err != nil {
fmt.Printf("Kesalahan: %v\n", err)
return
}
fmt.Println("Hasil Pencarian:")
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] + "..."
}
Implementasi Fetch Web
Apa perbedaan antara endpoint web_search dan web_fetch? Endpoint web_search memanggil internet dan mengembalikan beberapa hasil pencarian dengan judul, URL, dan cuplikan. Endpoint web_fetch mengambil konten penuh dari URL tertentu, mengembalikan judul halaman, konten, dan tautan.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Tipe permintaan/Respons untuk 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("variabel lingkungan OLLAMA_API_KEY tidak disetel")
}
reqBody := WebFetchRequest{URL: url}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("gagal mengubah permintaan: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_fetch", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("gagal membuat permintaan: %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("permintaan gagal: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("kesalahan API (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("gagal membaca respons: %w", err)
}
var fetchResp WebFetchResponse
if err := json.Unmarshal(body, &fetchResp); err != nil {
return nil, fmt.Errorf("gagal mengubah respons: %w", err)
}
return &fetchResp, nil
}
func main() {
result, err := webFetch("https://ollama.com")
if err != nil {
fmt.Printf("Kesalahan: %v\n", err)
return
}
fmt.Printf("Judul: %s\n\n", result.Title)
fmt.Printf("Konten:\n%s\n\n", result.Content)
fmt.Printf("Tautan yang ditemukan: %d\n", len(result.Links))
for i, link := range result.Links {
if i >= 5 {
fmt.Printf(" ... dan %d lebih\n", len(result.Links)-5)
break
}
fmt.Printf(" - %s\n", link)
}
}
Paket Klien yang Dapat Digunakan Ulang
Buat paket klien Ollama yang dapat digunakan ulang untuk kode yang lebih bersih:
// 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("variabel lingkungan OLLAMA_API_KEY tidak disetel")
}
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("kesalahan 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
}
Penggunaan:
package main
import (
"fmt"
"log"
"ollama-search/ollama"
)
func main() {
client, err := ollama.NewClient()
if err != nil {
log.Fatal(err)
}
// Pencarian
results, err := client.WebSearch("fitur baru Ollama", 5)
if err != nil {
log.Fatal(err)
}
for _, r := range results.Results {
fmt.Printf("- %s\n %s\n\n", r.Title, r.URL)
}
// Mengambil
page, err := client.WebFetch("https://ollama.com")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Halaman: %s\n", page.Title)
}
Membangun Agen Pencarian
Model mana yang bekerja terbaik untuk agen pencarian Ollama berbasis Go? Model dengan kemampuan penggunaan alat yang kuat bekerja terbaik, termasuk qwen3, gpt-oss, dan model cloud seperti qwen3:480b-cloud dan deepseek-v3.1-cloud. Jika Anda bekerja dengan model Qwen3 dan perlu memproses atau mengevaluasi ulang hasil pencarian, lihat panduan kami mengenai penilaian ulang dokumen teks dengan Ollama dan model embedding Qwen3 dalam Go.
Berikut adalah implementasi agen pencarian lengkap:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Jenis 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 mengatur pencarian web dengan 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: "Cari web untuk informasi terkini",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]string{
"type": "string",
"description": "Pertanyaan pencarian",
},
},
"required": []string{"query"},
},
},
},
{
Type: "function",
Function: ToolFunction{
Name: "web_fetch",
Description: "Mengambil konten penuh dari halaman web",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]string{
"type": "string",
"description": "URL yang akan diambil",
},
},
"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("kesalahan chat: %w", err)
}
messages = append(messages, response.Message)
// Tidak ada panggilan alat berarti kita memiliki jawaban akhir
if len(response.Message.ToolCalls) == 0 {
return response.Message.Content, nil
}
// Eksekusi panggilan alat
for _, toolCall := range response.Message.ToolCalls {
fmt.Printf("š§ Memanggil: %s\n", toolCall.Function.Name)
result, err := a.executeTool(toolCall)
if err != nil {
result = fmt.Sprintf("Kesalahan: %v", err)
}
// Potong untuk batas konteks
if len(result) > 8000 {
result = result[:8000] + "... [dipotong]"
}
messages = append(messages, Message{
Role: "tool",
Content: result,
ToolName: toolCall.Function.Name,
})
}
}
return "", fmt.Errorf("batas iterasi maksimal tercapai")
}
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("argumen query tidak valid")
}
return a.webSearch(query)
case "web_fetch":
url, ok := toolCall.Function.Arguments["url"].(string)
if !ok {
return "", fmt.Errorf("argumen url tidak valid")
}
return a.webFetch(url)
default:
return "", fmt.Errorf("alat tidak diketahui: %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("Apa fitur terbaru dalam Ollama?")
if err != nil {
fmt.Printf("Kesalahan: %v\n", err)
return
}
fmt.Println("\nš Jawaban:")
fmt.Println(answer)
}
Bagaimana cara saya menangani respons pencarian web besar dalam Go? Potong konten respons sebelum mengirimkannya ke konteks model. Gunakan pemotongan string untuk membatasi konten hingga sekitar 8000 karakter untuk memenuhi batas konteks.
Pencarian Bersamaan
Go sangat baik dalam operasi bersamaan. Berikut cara melakukan beberapa pencarian sekaligus. Memahami cara Ollama menangani permintaan bersamaan dapat membantu Anda mengoptimalkan implementasi pencarian bersamaan Anda.
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{
"Fitur terbaru Ollama",
"penempatan LLM lokal",
"agen pencarian AI Go",
}
results := concurrentSearch(queries)
for _, r := range results {
fmt.Printf("\nš Query: %s\n", r.Query)
if r.Error != nil {
fmt.Printf(" Kesalahan: %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
}
Konteks dan Pembatalan
Tambahkan dukungan konteks untuk timeout dan pembatalan:
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() {
// Membuat konteks dengan timeout 10 detik
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := webSearchWithContext(ctx, "API pencarian web Ollama")
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
fmt.Println("Permintaan timeout")
} else {
fmt.Printf("Kesalahan: %v\n", err)
}
return
}
fmt.Println(result)
}
Model yang Direkomendasikan
Panjang konteks apa yang harus saya gunakan untuk agen pencarian Go? Atur panjang konteks sekitar 32000 token untuk kinerja yang wajar. Agen pencarian bekerja terbaik dengan panjang konteks penuh karena hasil pencarian web dapat luas. Jika Anda perlu mengelola model Ollama Anda atau memindahkannya ke lokasi lain, lihat panduan kami mengenai cara memindahkan model Ollama ke drive atau folder berbeda.
| Model | Parameter | Terbaik Untuk |
|---|---|---|
qwen3:4b |
4B | Pencarian lokal cepat |
qwen3 |
8B | Agen tujuan umum |
gpt-oss |
Berbagai | Tugas penelitian |
qwen3:480b-cloud |
480B | Penalaran kompleks (cloud) |
gpt-oss:120b-cloud |
120B | Penelitian panjang (cloud) |
deepseek-v3.1-cloud |
- | Analisis lanjutan (cloud) |
Untuk aplikasi AI lanjutan yang menggabungkan konten teks dan visual, pertimbangkan untuk mengeksplorasi embedding lintas-modal untuk memperluas kemampuan pencarian Anda di luar pertanyaan teks saja.
Praktik Terbaik
- Pengelolaan Kesalahan: Selalu periksa kesalahan dan tangani kegagalan API dengan baik
- Timeout: Gunakan konteks dengan timeout untuk permintaan jaringan
- Pembatasan Tingkat: Hormati batas tingkat API Ollama. Sadari potensi perubahan API Ollama, seperti yang dibahas dalam tanda-tanda awal Ollama enshittification
- Pemotongan Hasil: Potong hasil hingga sekitar 8000 karakter untuk batas konteks
- Permintaan Bersamaan: Gunakan goroutine untuk pencarian bersamaan
- Pemakaian Koneksi: Ulangi klien HTTP untuk kinerja yang lebih baik
- Pengujian: Tulis pengujian unit menyeluruh untuk implementasi pencarian Anda. Ikuti praktik terbaik pengujian unit Go untuk memastikan kode Anda kuat dan dapat dipelihara
Tautan Berguna
- Lembar cepat Ollama
- Mengatur ulang dokumen teks dengan Ollama dan model embedding Qwen3 - dalam Go
- Embedding Lintas-Modal: Menghubungkan Modal AI
- Pengujian Unit Go: Struktur & Praktik Terbaik
- Cara Memindahkan Model Ollama ke Drive atau Folder Berbeda
- Cara Ollama Menangani Permintaan Bersamaan
- Tanda-Tanda Awal Ollama Enshittification
- Blog Ollama Pencarian Web
- Dokumentasi Ollama Resmi
- Contoh Ollama Go
- Repository Ollama GitHub