Użycie interfejsu API Ollama Web Search w Go
Twórz agentów wyszukiwania AI za pomocą Go i Ollama
API do wyszukiwania w sieci Ollama pozwala na wzbogacenie lokalnych modeli językowych danymi z sieci w czasie rzeczywistym. Niniejszy przewodnik pokazuje, jak zaimplementować możliwości wyszukiwania w sieci w Go, od prostych wywołań API po pełne agenty wyszukiwania.

Rozpoczęcie pracy
Czy Ollama ma oficjalną bibliotekę Go do wyszukiwania w sieci? Ollama udostępnia API REST do wyszukiwania w sieci, które działa z dowolnym klientem HTTP w Go. Choć nie ma jeszcze oficjalnego SDK Go do wyszukiwania w sieci, możesz łatwo zaimplementować wywołania API korzystając z pakietów standardowych.
Najpierw utwórz klucz API z Twojego konta Ollama. Dla pełnego odniesienia do poleceń i użycia Ollama, sprawdź cheatsheet Ollama.
Ustaw swój klucz API jako zmienną środowiskową:
export OLLAMA_API_KEY="your_api_key"
Na Windows PowerShell:
$env:OLLAMA_API_KEY = "your_api_key"
Konfiguracja projektu
Utwórz nowy moduł Go:
mkdir ollama-search
cd ollama-search
go mod init ollama-search
Podstawowe wyszukiwanie w sieci
Jak uwierzytelnić się w API do wyszukiwania w sieci Ollama w Go? Ustaw nagłówek Authorization z Twoim kluczem API jako token Bearer. Utwórz klucz API z Twojego konta Ollama i przekaż go w nagłówku żądania.
Oto pełna implementacja API do wyszukiwania w sieci:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Typy żądań/odpowiedzi dla 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("zmienna środowiskowa OLLAMA_API_KEY nie jest ustawiona")
}
reqBody := WebSearchRequest{
Query: query,
MaxResults: maxResults,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("nie udało się zserializować żądania: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("nie udało się utworzyć żądania: %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("żądanie nie powiodło się: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("błąd API (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("nie udało się odczytać odpowiedzi: %w", err)
}
var searchResp WebSearchResponse
if err := json.Unmarshal(body, &searchResp); err != nil {
return nil, fmt.Errorf("nie udało się odserializować odpowiedzi: %w", err)
}
return &searchResp, nil
}
func main() {
results, err := webSearch("Co to jest Ollama?", 5)
if err != nil {
fmt.Printf("Błąd: %v\n", err)
return
}
fmt.Println("Wyniki wyszukiwania:")
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] + "..."
}
Implementacja pobierania z sieci
Jaka jest różnica między endpointami web_search a web_fetch? Endpoint web_search wyszukuje internet i zwraca wiele wyników wyszukiwania z tytułami, adresami URL i fragmentami. Endpoint web_fetch pobiera pełną zawartość określonego adresu URL, zwracając tytuł strony, zawartość i linki.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Typy żądań/odpowiedzi dla 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("zmienna środowiskowa OLLAMA_API_KEY nie jest ustawiona")
}
reqBody := WebFetchRequest{URL: url}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("nie udało się zserializować żądania: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_fetch", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("nie udało się utworzyć żądania: %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("żądanie nie powiodło się: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("błąd API (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("nie udało się odczytać odpowiedzi: %w", err)
}
var fetchResp WebFetchResponse
if err := json.Unmarshal(body, &fetchResp); err != nil {
return nil, fmt.Errorf("nie udało się odserializować odpowiedzi: %w", err)
}
return &fetchResp, nil
}
func main() {
result, err := webFetch("https://ollama.com")
if err != nil {
fmt.Printf("Błąd: %v\n", err)
return
}
fmt.Printf("Tytuł: %s\n\n", result.Title)
fmt.Printf("Zawartość:\n%s\n\n", result.Content)
fmt.Printf("Znalezione linki: %d\n", len(result.Links))
for i, link := range result.Links {
if i >= 5 {
fmt.Printf(" ... i %d więcej\n", len(result.Links)-5)
break
}
fmt.Printf(" - %s\n", link)
}
}
Pakiet klienta do ponownego użycia
Utwórz ponownie wykorzystywalny pakiet klienta Ollama dla czystszej kodu:
// 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("zmienna środowiskowa OLLAMA_API_KEY nie jest ustawiona")
}
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("błąd 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
}
Użycie:
package main
import (
"fmt"
"log"
"ollama-search/ollama"
)
func main() {
client, err := ollama.NewClient()
if err != nil {
log.Fatal(err)
}
// Wyszukiwanie
results, err := client.WebSearch("nowe funkcje Ollama", 5)
if err != nil {
log.Fatal(err)
}
for _, r := range results.Results {
fmt.Printf("- %s\n %s\n\n", r.Title, r.URL)
}
// Pobieranie
page, err := client.WebFetch("https://ollama.com")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Strona: %s\n", page.Title)
}
Budowanie agenta wyszukiwania
Które modele najlepiej sprawdzają się w agentach wyszukiwania Ollama w Go? Modele z silnymi możliwościami korzystania z narzędzi działają najlepiej, w tym qwen3, gpt-oss oraz modele chmurowe takie jak qwen3:480b-cloud i deepseek-v3.1-cloud. Jeśli pracujesz z modelem Qwen3 i potrzebujesz przetworzyć lub ponownie ocenić wyniki wyszukiwania, zobacz nasz przewodnik dotyczący ponownego oceniania dokumentów tekstowych z Ollama i modelem Qwen3 Embedding w Go.
Oto pełna implementacja agenta wyszukiwania:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// Typy komunikatów
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 koordynuje wyszukiwanie w sieci z 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: "Wyszukaj w sieci aktualne informacje",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]string{
"type": "string",
"description": "Zapytanie wyszukiwania",
},
},
"required": []string{"query"},
},
},
},
{
Type: "function",
Function: ToolFunction{
Name: "web_fetch",
Description: "Pobierz pełną zawartość strony internetowej",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]string{
"type": "string",
"description": "URL do pobrania",
},
},
"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("błąd w komunikacji: %w", err)
}
messages = append(messages, response.Message)
// Brak wywołań narzędzi oznacza, że mamy gotowy odpowiedź
if len(response.Message.ToolCalls) == 0 {
return response.Message.Content, nil
}
// Wykonaj wywołania narzędzi
for _, toolCall := range response.Message.ToolCalls {
fmt.Printf("🔧 Wywołanie: %s\n", toolCall.Function.Name)
result, err := a.executeTool(toolCall)
if err != nil {
result = fmt.Sprintf("Błąd: %v", err)
}
// Obcięcie dla ograniczeń kontekstu
if len(result) > 8000 {
result = result[:8000] + "... [obcięte]"
}
messages = append(messages, Message{
Role: "tool",
Content: result,
ToolName: toolCall.Function.Name,
})
}
}
return "", fmt.Errorf("osiągnięto maksymalną liczbę iteracji")
}
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("nieprawidłowy argument zapytania")
}
return a.webSearch(query)
case "web_fetch":
url, ok := toolCall.Function.Arguments["url"].(string)
if !ok {
return "", fmt.Errorf("nieprawidłowy argument URL")
}
return a.webFetch(url)
default:
return "", fmt.Errorf("nieznany narzędzie: %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("Jakie są najnowsze funkcje w Ollama?")
if err != nil {
fmt.Printf("Błąd: %v\n", err)
return
}
fmt.Println("\n📝 Odpowiedź:")
fmt.Println(answer)
}
Jak radzić sobie z dużymi odpowiedziami wyszukiwania w sieci w Go? Obcinaj zawartość odpowiedzi przed przekazaniem jej do kontekstu modelu. Użyj wycinkowania ciągu, aby ograniczyć zawartość do około 8000 znaków, aby zmieścić się w ograniczeniach kontekstu.
Wyszukiwanie równoległe
Go wyróżnia się w operacjach równoległych. Oto, jak wykonać wiele wyszukiwań równolegle. Zrozumienie jak Ollama obsługuje żądania równoległe może pomóc Ci zoptymalizować swoje implementacje wyszukiwania równoległego.
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{
"najnowsze funkcje Ollama",
"lokalne wdrożenie modelu językowego",
"agenty wyszukiwania AI w Go",
}
results := concurrentSearch(queries)
for _, r := range results {
fmt.Printf("\n🔍 Zapytanie: %s\n", r.Query)
if r.Error != nil {
fmt.Printf(" Błąd: %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
}
Kontekst i anulowanie
Dodaj obsługę kontekstu dla timeoutów i anulowania:
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() {
// Utwórz kontekst z timeoutem 10 sekund
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := webSearchWithContext(ctx, "API do wyszukiwania w sieci Ollama")
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
fmt.Println("Żądanie przekroczyło czas")
} else {
fmt.Printf("Błąd: %v\n", err)
}
return
}
fmt.Println(result)
}
Zalecane modele
Jaki długość kontekstu należy użyć dla agentów wyszukiwania w Go? Ustaw długość kontekstu na około 32000 tokenów dla rozsądnego wydajności. Agenty wyszukiwania działają najlepiej z pełną długością kontekstu, ponieważ wyniki wyszukiwania w sieci mogą być długie. Jeśli potrzebujesz zarządzać modelami Ollama lub przenieść je na inne lokalizacje, zobacz nasz przewodnik dotyczący jak przenieść modele Ollama na inny dysk lub folder.
| Model | Parametry | Najlepsze do |
|---|---|---|
qwen3:4b |
4B | Szybkie wyszukiwania lokalne |
qwen3 |
8B | Ogólne zastosowanie |
gpt-oss |
Różne | Zadania badawcze |
qwen3:480b-cloud |
480B | Złożone rozumowanie (chmura) |
gpt-oss:120b-cloud |
120B | Długie formy badawcze (chmura) |
deepseek-v3.1-cloud |
- | Zaawansowana analiza (chmura) |
Dla zaawansowanych aplikacji AI, które łączą treść tekstową i wizualną, rozważ eksplorację współmodalnych osadzeń w celu rozszerzenia możliwości wyszukiwania poza zapytania tekstowe.
Najlepsze praktyki
- Obsługa błędów: Zawsze sprawdzaj błędy i łagodnie obsługuj awarie API
- Timeouty: Używaj kontekstu z timeoutami dla żądań sieciowych
- Ograniczanie przepustowości: Szanuj ograniczenia przepustowości API Ollama. Uważaj na potencjalne zmiany w API Ollama, jak omówiono w pierwszych objawach Ollama enshittification
- Obcinanie wyników: Obcinaj wyniki do około 8000 znaków dla ograniczeń kontekstu
- Żądania równoległe: Używaj gorutyn do równoległego wyszukiwania
- Zdalne połączenia: Używaj ponownie HTTP klienta dla lepszej wydajności
- Testowanie: Pisz komprehensywne testy jednostkowe dla swoich implementacji wyszukiwania. Zobacz najlepsze praktyki testowania jednostkowego w Go aby upewnić się, że Twój kod jest odporny i utrzymany
Przydatne linki
- Cheatsheet Ollama
- Ponowne ocenianie dokumentów tekstowych z Ollama i modelem Qwen3 Embedding w Go
- Współmodalne osadzenia: Łączenie modalności AI
- Testowanie jednostkowe w Go: Struktura i najlepsze praktyki
- Jak przenieść modele Ollama na inny dysk lub folder
- Jak Ollama obsługuje żądania równoległe
- Pierwsze objawy Ollama Enshittification
- Blog Ollama Web Search
- Oficjalna dokumentacja Ollama
- Przykłady Go Ollama
- Repositorium GitHub Ollama