استخدام واجهة برمجة التطبيقات الخاصة ببحث Ollama على الويب في Go
أنشئ وكلاء بحث ذكاء اصطناعي باستخدام Go وOllama
واجهة بحث الويب في Ollama تتيح لك تحسين نماذج LLM المحلية بمعلومات الويب في الوقت الفعلي. توضح هذه المقالة لك كيفية تنفيذ قدرات البحث عبر الويب في Go، من مكالمات API بسيطة إلى وكلاء البحث المتكاملين.

البدء
هل لدى Ollama مكتبة Go رسمية لبحث الويب؟ يوفر Ollama واجهة برمجة تطبيقات REST لبحث الويب تعمل مع أي عميل HTTP في Go. على الرغم من عدم وجود مكتبة Go رسمية لبحث الويب بعد، يمكنك تنفيذ مكالمات API بسهولة باستخدام حزم المكتبة القياسية.
أولاً، أنشئ مفتاح API من حسابك في Ollama. للمراجعة الشاملة حول أوامر Ollama واستخدامها، تحقق من قائمة مصطلحات Ollama.
قم بتعيين مفتاح API كمتغير بيئة:
export OLLAMA_API_KEY="your_api_key"
على PowerShell في Windows:
$env:OLLAMA_API_KEY = "your_api_key"
إعداد المشروع
أنشئ وحدة Go جديدة:
mkdir ollama-search
cd ollama-search
go mod init ollama-search
البحث عبر الويب الأساسي
كيف أقوم بالتوقيع مع واجهة بحث الويب في Ollama في Go؟ ضع رأس “Authorization” مع مفتاح API الخاص بك كرمز Bearer. أنشئ مفتاح API من حسابك في Ollama وارسله في رأس الطلب.
إليك تنفيذ كامل لواجهة بحث الويب:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// أنواع الطلب/الاستجابة لبحث الويب
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("OLLAMA_API_KEY variable بيئة not set")
}
reqBody := WebSearchRequest{
Query: query,
MaxResults: maxResults,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("فشل marshaling الطلب: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_search", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("فشل إنشاء الطلب: %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("فشل الطلب: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("خطأ في API (الحالة %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("فشل قراءة الاستجابة: %w", err)
}
var searchResp WebSearchResponse
if err := json.Unmarshal(body, &searchResp); err != nil {
return nil, fmt.Errorf("فشل unmarshal الاستجابة: %w", err)
}
return &searchResp, nil
}
func main() {
results, err := webSearch("ما هو Ollama؟", 5)
if err != nil {
fmt.Printf("خطأ: %v\n", err)
return
}
fmt.Println("نتائج البحث:")
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] + "..."
}
تنفيذ البحث عبر الويب
ما الفرق بين طرفي web_search و web_fetch؟ يسأل طرف web_search الإنترنت ويُرجع نتائج بحث متعددة مع العناوين، الروابط، والملخصات. يحصل طرف web_fetch على المحتوى الكامل لعنوان URL معين، ويُرجع عنوان الصفحة والمحتوى والروابط.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// أنواع الطلب/الاستجابة لـ 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("OLLAMA_API_KEY variable بيئة not set")
}
reqBody := WebFetchRequest{URL: url}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("فشل marshaling الطلب: %w", err)
}
req, err := http.NewRequest("POST", "https://ollama.com/api/web_fetch", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("فشل إنشاء الطلب: %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("فشل الطلب: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("خطأ في API (الحالة %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("فشل قراءة الاستجابة: %w", err)
}
var fetchResp WebFetchResponse
if err := json.Unmarshal(body, &fetchResp); err != nil {
return nil, fmt.Errorf("فشل unmarshal الاستجابة: %w", err)
}
return &fetchResp, nil
}
func main() {
result, err := webFetch("https://ollama.com")
if err != nil {
fmt.Printf("خطأ: %v\n", err)
return
}
fmt.Printf("العنوان: %s\n\n", result.Title)
fmt.Printf("المحتوى:\n%s\n\n", result.Content)
fmt.Printf("الروابط المستعارة: %d\n", len(result.Links))
for i, link := range result.Links {
if i >= 5 {
fmt.Printf(" ... و %d أكثر\n", len(result.Links)-5)
break
}
fmt.Printf(" - %s\n", link)
}
}
حزمة العميل القابلة لإعادة الاستخدام
أنشئ حزمة عميل Ollama قابلة لإعادة الاستخدام لجعل الكود نظيفًا:
// 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("OLLAMA_API_KEY variable بيئة not set")
}
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("خطأ في API (الحالة %d): %s", resp.StatusCode, string(body))
}
var result T
if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}
return &result, nil
}
الاستخدام:
package main
import (
"fmt"
"log"
"ollama-search/ollama"
)
func main() {
client, err := ollama.NewClient()
if err != nil {
log.Fatal(err)
}
// البحث
results, err := client.WebSearch("مزايا جديدة في Ollama", 5)
if err != nil {
log.Fatal(err)
}
for _, r := range results.Results {
fmt.Printf("- %s\n %s\n\n", r.Title, r.URL)
}
// الاسترجاع
page, err := client.WebFetch("https://ollama.com")
if err != nil {
log.Fatal(err)
}
fmt.Printf("الصفحة: %s\n", page.Title)
}
بناء وكيل البحث
ما النماذج التي تعمل بشكل أفضل لوكيل البحث القائم على Go في Ollama؟ تعمل النماذج ذات قدرات استخدام الأدوات القوية بشكل أفضل، بما في ذلك qwen3، gpt-oss، والنماذج السحابية مثل qwen3:480b-cloud و deepseek-v3.1-cloud. إذا كنت تعمل مع نماذج Qwen3 وتحتاج إلى معالجة أو إعادة ترتيب نتائج البحث، راجع دليلنا حول إعادة ترتيب وثائق النص مع Ollama ونموذج Qwen3 Embedding في Go.
إليك تنفيذ وكيل البحث الكامل:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
// أنواع المحادثة
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 ينظم بحث الويب مع 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: "ابحث عبر الإنترنت عن معلومات حديثة",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]string{
"type": "string",
"description": "الاستعلام البحثي",
},
},
"required": []string{"query"},
},
},
},
{
Type: "function",
Function: ToolFunction{
Name: "web_fetch",
Description: "احصل على المحتوى الكامل لصفحة ويب",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]string{
"type": "string",
"description": "الرابط للاسترجاع",
},
},
"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("خطأ في المحادثة: %w", err)
}
messages = append(messages, response.Message)
// لا وجود لمكالمات الأدوات يعني أن لدينا إجابة نهائية
if len(response.Message.ToolCalls) == 0 {
return response.Message.Content, nil
}
// تنفيذ مكالمات الأدوات
for _, toolCall := range response.Message.ToolCalls {
fmt.Printf("🔧 Calling: %s\n", toolCall.Function.Name)
result, err := a.executeTool(toolCall)
if err != nil {
result = fmt.Sprintf("خطأ: %v", err)
}
// قطع النص لحدود السياق
if len(result) > 8000 {
result = result[:8000] + "... [تم قطعه]"
}
messages = append(messages, Message{
Role: "tool",
Content: result,
ToolName: toolCall.Function.Name,
})
}
}
return "", fmt.Errorf("تم الوصول إلى عدد الأشواط الأقصى")
}
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("حجة الاستعلام غير صحيحة")
}
return a.webSearch(query)
case "web_fetch":
url, ok := toolCall.Function.Arguments["url"].(string)
if !ok {
return "", fmt.Errorf("حجة الرابط غير صحيحة")
}
return a.webFetch(url)
default:
return "", fmt.Errorf("أداة غير معروفة: %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("ما هي الميزات الأحدث في Ollama؟")
if err != nil {
fmt.Printf("خطأ: %v\n", err)
return
}
fmt.Println("\n📝 الإجابة:")
fmt.Println(answer)
}
كيف أتعامل مع استجابات بحث الويب الكبيرة في Go؟ قطع المحتوى المستلم قبل إرساله إلى سياق النموذج. استخدم قطع النص لتحديد المحتوى إلى حوالي 8000 حرف لملاءمة حدود السياق.
البحث المتزامن
Go يتفوق في العمليات المتزامنة. إليك كيفية إجراء عدة بحثات في وقت واحد. فهم كيف يتعامل Ollama مع الطلبات المتزامنة يمكن أن يساعدك في تحسين تنفيذاتك في البحث المتزامن.
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 الأحدث",
"نشر نماذج LLM المحلية",
"وكلاء البحث في AI مع Go",
}
results := concurrentSearch(queries)
for _, r := range results {
fmt.Printf("\n🔍 الاستعلام: %s\n", r.Query)
if r.Error != nil {
fmt.Printf(" خطأ: %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
}
السياق والยก
أضف دعم السياق للوقت المحدد والإلغاء:
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() {
// إنشاء سياق مع وقف بعد 10 ثوانٍ
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := webSearchWithContext(ctx, "واجهة بحث الويب في Ollama")
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
fmt.Println("انتهت مهلة الطلب")
} else {
fmt.Printf("خطأ: %v\n", err)
}
return
}
fmt.Println(result)
}
النماذج الموصى بها
ما طول السياق الذي يجب أن أستخدمه لوكيل البحث في Go؟ ضع طول السياق إلى حوالي 32000 رمز لضمان الأداء المعتدل. تعمل وكلاء البحث بشكل أفضل مع طول السياق الكامل، نظرًا لأن نتائج بحث الويب يمكن أن تكون واسعة. إذا كنت بحاجة إلى إدارة نماذج Ollama أو نقلها إلى مواقع مختلفة، راجع دليلنا حول كيفية نقل نماذج Ollama إلى محرك أو مجلد مختلف.
| النموذج | عدد المعلمات | الأفضل لـ |
|---|---|---|
qwen3:4b |
4B | البحث المحلي السريع |
qwen3 |
8B | الوكلاء العامة |
gpt-oss |
مختلف | المهام البحثية |
qwen3:480b-cloud |
480B | التفكير المعقد (السحابة) |
gpt-oss:120b-cloud |
120B | الأبحاث الطويلة (السحابة) |
deepseek-v3.1-cloud |
- | التحليل المتقدم (السحابة) |
لتطبيقات AI المتقدمة التي تدمج المحتوى النصي والمرئي، يُرجى النظر في استكشاف التجريدات عبر الأوضاع لتوسيع قدرات البحث خارج الاستعلامات النصية فقط.
الممارسات الموصى بها
- إدارة الأخطاء: تحقق دائمًا من الأخطاء وتعامل مع فشل واجهة برمجة التطبيقات بسلاسة
- الوقت المحدد: استخدم السياق مع وقتي للطلبات الشبكية
- الحد من السرعة: احترم حدود واجهة برمجة التطبيقات لـ Ollama. تأكد من التغييرات المحتملة في واجهة برمجة التطبيقات لـ Ollama، كما تم مناقشته في العلامات الأولى لـ Ollama Enshittification
- الحد من النتائج: قطع النتائج إلى ~8000 حرف لحدود السياق
- الطلبات المتزامنة: استخدم go routines للبحث المتزامن
- إعادة استخدام الاتصالات: استخدم عميل HTTP لإعادة استخدامه لتحسين الأداء
- الاختبار: اكتب اختبارات وحدة شاملة لتنفيذات بحثك. اتبع أفضل الممارسات لاختبار الوحدات في Go
الروابط المفيدة
- قائمة مصطلحات Ollama
- إعادة ترتيب وثائق النص مع Ollama ونموذج Qwen3 Embedding - في Go
- التجريدات عبر الأوضاع: جسر بين وسائل AI
- اختبار الوحدات في Go: الهيكل وال أفضل الممارسات
- كيفية نقل نماذج Ollama إلى محرك أو مجلد مختلف
- كيف يتعامل Ollama مع الطلبات المتزامنة
- العلامات الأولى لـ Ollama Enshittification
- مدونة واجهة بحث الويب في Ollama
- التوثيق الرسمي لـ Ollama
- أمثلة Go لـ Ollama
- مستودع GitHub لـ Ollama