Клиенты Go для Ollama: сравнение SDK и примеры Qwen3/GPT-OSS

Интеграция Ollama с Go: руководство по SDK, примеры и лучшие практики для продакшена.

Содержимое страницы

Это руководство предоставляет всесторонний обзор доступных Go SDK для Ollama и сравнивает их функциональные возможности.

Мы исследуем практические примеры на Go для вызова моделей Qwen3 и GPT-OSS, размещенных на Ollama — как через сырые REST-запросы, так и с использованием официального клиента Go, включая детальную обработку режимов мышления и не-мышления в Qwen3.

go и ollama

Почему Ollama + Go?

Ollama предоставляет небольшой, практичный HTTP API (обычно работающий на http://localhost:11434), предназначенный для нагрузок generate и chat, с встроенной поддержкой потоковой передачи и возможностями управления моделями. Официальная документация подробно описывает структуры запросов/ответов и семантику потоковой передачи для /api/generate и /api/chat.

Go — отличный выбор для создания клиентов Ollama благодаря мощной поддержке стандартной библиотеки для HTTP, отличной обработке JSON, нативным примитивам параллелизма и статически типизированным интерфейсам, которые обнаруживают ошибки на этапе компиляции.

По состоянию на октябрь 2025 года, вот варианты Go SDK, которые вы, скорее всего, будете рассматривать.


Go SDK для Ollama — что доступно?

SDK / Пакет Статус и “владелец” Область применения (Генерация/Чат/Потоковая передача) Управление моделями (pull/list и т.д.) Дополнительно / Примечания
github.com/ollama/ollama/api Официальный пакет внутри репозитория Ollama; используется самим CLI ollama Полное покрытие, соответствующее REST; поддержка потоковой передачи Да Считается каноническим клиентом Go; API тесно соответствует документации.
LangChainGo (github.com/tmc/langchaingo/llms/ollama) Сообщество (LangChainGo) с модулем LLM Ollama Чат/Завершение + потоковая передача через абстракции фреймворка Ограниченно (управление моделями не является основной целью) Отлично, если вы хотите цепочки, инструменты, векторные хранилища на Go; менее полноценный SDK.
github.com/swdunlop/ollama-client Клиент сообщества Фокус на чате; хорошие эксперименты с вызовом инструментов Частично Создан для экспериментов с вызовом инструментов; не полное покрытие.
Другие SDK сообщества (например, ollamaclient, сторонние “go-ollama-sdk”) Сообщество Разное Разное Качество и покрытие различаются; оценивайте по репозиторию.

Рекомендация: Для продакшена предпочтите github.com/ollama/ollama/api — он поддерживается вместе с основным проектом и отражает REST API.


Qwen3 & GPT-OSS на Ollama: мышление vs не-мышление (что нужно знать)

  • Режим мышления в Ollama разделяет “рассуждение” модели от окончательного вывода при включении. Ollama документирует поведение включения/отключения мышления для поддерживаемых моделей.
  • (https://www.glukhov.org/ru/post/2025/10/qwen3-30b-vs-gpt-oss-20b/ “Qwen3:30b vs GPT-OSS:20b: Технические детали, производительность и сравнение скорости”) поддерживает динамическое переключение: добавьте /think или /no_think в системные/пользовательские сообщения для переключения режимов по очереди; последняя инструкция побеждает.
  • GPT-OSS: пользователи сообщают, что отключение мышления (например, /set nothink или --think=false) может быть ненадежным для gpt-oss:20b; планируйте фильтрацию/скрытие любого рассуждения, которое ваш интерфейс не должен отображать.

Часть 1 — Вызов Ollama через сырой REST (Go, net/http)

Общие типы

Сначала давайте определим общие типы и вспомогательные функции, которые мы будем использовать в наших примерах:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

// ---- Типы API Chat ----

type ChatMessage struct {
	Role    string `json:"role"`
	Content string `json:"content"`
}

type ChatRequest struct {
	Model    string        `json:"model"`
	Messages []ChatMessage `json:"messages"`
	// Некоторые серверы предоставляют управление мышлением как булевый флаг.
	// Даже если он опущен, вы все равно можете управлять Qwen3 через теги /think или /no_think.
	Think   *bool          `json:"think,omitempty"`
	Stream  *bool          `json:"stream,omitempty"`
	Options map[string]any `json:"options,omitempty"`
}

type ChatResponse struct {
	Model     string `json:"model"`
	CreatedAt string `json:"created_at"`
	Message   struct {
		Role     string `json:"role"`
		Content  string `json:"content"`
		Thinking string `json:"thinking,omitempty"` // присутствует, когда мышление включено
	} `json:"message"`
	Done bool `json:"done"`
}

// ---- Типы API Generate ----

type GenerateRequest struct {
	Model   string         `json:"model"`
	Prompt  string         `json:"prompt"`
	Think   *bool          `json:"think,omitempty"`
	Stream  *bool          `json:"stream,omitempty"`
	Options map[string]any `json:"options,omitempty"`
}

type GenerateResponse struct {
	Model     string `json:"model"`
	CreatedAt string `json:"created_at"`
	Response  string `json:"response"`           // окончательный текст для не-потокового
	Thinking  string `json:"thinking,omitempty"` // присутствует, когда мышление включено
	Done      bool   `json:"done"`
}

// ---- Вспомогательные функции ----

func httpPostJSON(url string, payload any) ([]byte, error) {
	body, err := json.Marshal(payload)
	if err != nil {
		return nil, err
	}
	c := &http.Client{Timeout: 60 * time.Second}
	resp, err := c.Post(url, "application/json", bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

// bptr возвращает указатель на значение булева типа
func bptr(b bool) *bool { return &b }

Чат — Qwen3 с включенным мышлением (и как его отключить)

func chatQwen3Thinking() error {
	endpoint := "http://localhost:11434/api/chat"

	req := ChatRequest{
		Model:   "qwen3:8b-thinking", // любой тег :*-thinking, который вы загрузили
		Think:   bptr(true),
		Stream:  bptr(false),
		Messages: []ChatMessage{
			{Role: "system", Content: "Вы точный помощник."},
			{Role: "user",   Content: "Объясните рекурсию с коротким примером на Go."},
		},
	}

	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out ChatResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	fmt.Println("🧠 мышление:\n", out.Message.Thinking)
	fmt.Println("\n💬 ответ:\n", out.Message.Content)
	return nil
}

// Отключите мышление для следующего хода, установив:
// (a) Think=false, и/или
// (b) добавьте "/no_think" в последнее системное/пользовательское сообщение (мягкий переключатель Qwen3).
// Qwen3 учитывает последнюю инструкцию /think или /no_think в многоходовых чатах.
func chatQwen3NoThinking() error {
	endpoint := "http://localhost:11434/api/chat"

	req := ChatRequest{
		Model:  "qwen3:8b-thinking",
		Think:  bptr(false),
		Stream: bptr(false),
		Messages: []ChatMessage{
			{Role: "system", Content: "Вы кратки. /no_think"},
			{Role: "user",   Content: "Объясните рекурсию в одном предложении."},
		},
	}

	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out ChatResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	// Ожидается, что мышление будет пустым; все равно обрабатывайте защищенно.
	if out.Message.Thinking != "" {
		fmt.Println("🧠 мышление (неожиданное):\n", out.Message.Thinking)
	}
	fmt.Println("\n💬 ответ:\n", out.Message.Content)
	return nil
}

(Мягкий переключатель /think и /no_think Qwen3 документирован командой Qwen; последняя инструкция побеждает в многоходовых чатах.)

Чат — GPT-OSS с мышлением (и предупреждение)

func chatGptOss() error {
	endpoint := "http://localhost:11434/api/chat"

	req := ChatRequest{
		Model:  "gpt-oss:20b",
		Think:  bptr(true),   // запрос разделенного рассуждения, если поддерживается
		Stream: bptr(false),
		Messages: []ChatMessage{
			{Role: "user", Content: "Что такое динамическое программирование? Объясните основную идею."},
		},
	}

	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out ChatResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	// Известная особенность: отключение мышления может не полностью подавлять рассуждение на gpt-oss:20b.
	// Всегда фильтруйте/скрывайте мышление в интерфейсе, если вы не хотите его отображать.
	fmt.Println("🧠 мышление:\n", out.Message.Thinking)
	fmt.Println("\n💬 ответ:\n", out.Message.Content)
	return nil
}

Пользователи сообщают, что отключение мышления для gpt-oss:20b (например, /set nothink или --think=false) может игнорироваться — планируйте фильтрацию на стороне клиента при необходимости.

Генерация — Qwen3 и GPT-OSS

func generateQwen3() error {
	endpoint := "http://localhost:11434/api/generate"
	req := GenerateRequest{
		Model:  "qwen3:4b-thinking",
		Prompt: "В 2–3 предложениях, для чего используются B-деревья в базах данных?",
		Think:  bptr(true),
	}
	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out GenerateResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	if out.Thinking != "" {
		fmt.Println("🧠 мышление:\n", out.Thinking)
	}
	fmt.Println("\n💬 ответ:\n", out.Response)
	return nil
}

func generateGptOss() error {
	endpoint := "http://localhost:11434/api/generate"
	req := GenerateRequest{
		Model:  "gpt-oss:20b",
		Prompt: "Кратко объясните обратное распространение в нейронных сетях.",
		Think:  bptr(true),
	}
	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out GenerateResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	if out.Thinking != "" {
		fmt.Println("🧠 мышление:\n", out.Thinking)
	}
	fmt.Println("\n💬 ответ:\n", out.Response)
	return nil
}

Формы REST и поведение потоковой передачи взяты напрямую из справочника API Ollama.

Часть 2 — Вызов Ollama через официальный Go SDK (github.com/ollama/ollama/api)

Официальный пакет предоставляет клиент с методами, соответствующими REST API. Сам CLI Ollama использует этот пакет для взаимодействия с сервисом, что делает его наиболее надежным выбором для совместимости.

Установка

go get github.com/ollama/ollama/api

Чат — Qwen3 (режим мышления ВКЛ/ВЫКЛ)

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/ollama/ollama/api"
)

func chatWithQwen3Thinking(ctx context.Context, thinking bool) error {
	client, err := api.ClientFromEnvironment() // учитывает OLLAMA_HOST, если установлен
	if err != nil {
		return err
	}

	req := &api.ChatRequest{
		Model: "qwen3:8b-thinking",
		// Многие сборки сервера предоставляют мышление как флаг верхнего уровня;
		// также можно управлять Qwen3 через /think или /no_think в сообщениях.
		Think: api.Ptr(thinking),
		Messages: []api.Message{
			{Role: "system", Content: "Вы — точный помощник."},
			{Role: "user",   Content: "Объясните сортировку слиянием с коротким примером на Go."},
		},
	}

	var resp api.ChatResponse
	if err := client.Chat(ctx, req, &resp); err != nil {
		return err
	}

	if resp.Message.Thinking != "" {
		fmt.Println("🧠 мышление:\n", resp.Message.Thinking)
	}
	fmt.Println("\n💬 ответ:\n", resp.Message.Content)
	return nil
}

func main() {
	ctx := context.Background()
	if err := chatWithQwen3Thinking(ctx, true); err != nil {
		log.Fatal(err)
	}
	// Пример: без мышления
	if err := chatWithQwen3Thinking(ctx, false); err != nil {
		log.Fatal(err)
	}
}

Чат — GPT-OSS (обработка рассуждений защищенным способом)

func chatWithGptOss(ctx context.Context) error {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}
	req := &api.ChatRequest{
		Model: "gpt-oss:20b",
		Think: api.Ptr(true),
		Messages: []api.Message{
			{Role: "user", Content: "Что такое мемоизация и когда она полезна?"},
		},
	}
	var resp api.ChatResponse
	if err := client.Chat(ctx, req, &resp); err != nil {
		return err
	}
	// Если вы хотите скрыть рассуждения, сделайте это здесь, независимо от флагов.
	if resp.Message.Thinking != "" {
		fmt.Println("🧠 мышление:\n", resp.Message.Thinking)
	}
	fmt.Println("\n💬 ответ:\n", resp.Message.Content)
	return nil
}

Генерация — Qwen3 & GPT-OSS

func generateWithQwen3(ctx context.Context) error {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}
	req := &api.GenerateRequest{
		Model:  "qwen3:4b-thinking",
		Prompt: "Суммируйте роль B-дерева в индексировании.",
		Think:  api.Ptr(true),
	}
	var resp api.GenerateResponse
	if err := client.Generate(ctx, req, &resp); err != nil {
		return err
	}
	if resp.Thinking != "" {
		fmt.Println("🧠 мышление:\n", resp.Thinking)
	}
	fmt.Println("\n💬 ответ:\n", resp.Response)
	return nil
}

func generateWithGptOss(ctx context.Context) error {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}
	req := &api.GenerateRequest{
		Model:  "gpt-oss:20b",
		Prompt: "Объясните метод градиентного спуска простыми словами.",
		Think:  api.Ptr(true),
	}
	var resp api.GenerateResponse
	if err := client.Generate(ctx, req, &resp); err != nil {
		return err
	}
	if resp.Thinking != "" {
		fmt.Println("🧠 мышление:\n", resp.Thinking)
	}
	fmt.Println("\n💬 ответ:\n", resp.Response)
	return nil
}

Поверхность официального пакета отражает документацию REST и обновляется вместе с основным проектом.


Потоковые ответы

Для потоковой передачи в реальном времени установите Stream: bptr(true) в вашем запросе. Ответ будет доставлен в виде JSON-кусков, разделенных новой строкой:

func streamChatExample() error {
	endpoint := "http://localhost:11434/api/chat"
	req := ChatRequest{
		Model:  "qwen3:8b-thinking",
		Think:  bptr(true),
		Stream: bptr(true), // Включить потоковую передачу
		Messages: []ChatMessage{
			{Role: "user", Content: "Объясните алгоритм быстрой сортировки шаг за шагом."},
		},
	}

	body, _ := json.Marshal(req)
	resp, err := http.Post(endpoint, "application/json", bytes.NewReader(body))
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	decoder := json.NewDecoder(resp.Body)
	for {
		var chunk ChatResponse
		if err := decoder.Decode(&chunk); err == io.EOF {
			break
		} else if err != nil {
			return err
		}

		// Обработка мышления и контента по мере их поступления
		if chunk.Message.Thinking != "" {
			fmt.Print(chunk.Message.Thinking)
		}
		fmt.Print(chunk.Message.Content)

		if chunk.Done {
			break
		}
	}
	return nil
}

С официальным SDK используйте функцию обратного вызова для обработки потоковых кусков:

func streamWithOfficialSDK(ctx context.Context) error {
	client, _ := api.ClientFromEnvironment()

	req := &api.ChatRequest{
		Model: "qwen3:8b-thinking",
		Think: api.Ptr(true),
		Messages: []api.Message{
			{Role: "user", Content: "Объясните бинарные деревья поиска."},
		},
	}

	err := client.Chat(ctx, req, func(resp api.ChatResponse) error {
		if resp.Message.Thinking != "" {
			fmt.Print(resp.Message.Thinking)
		}
		fmt.Print(resp.Message.Content)
		return nil
	})

	return err
}

Работа с Qwen3 мышление vs не-мышление (практические рекомендации)

  • Два рычага:

    1. Булевый флаг thinking, поддерживаемый функцией мышления Ollama; и
    2. Мягкие команды Qwen3 /think и /no_think в последнем системном/пользовательском сообщении. Самая последняя инструкция управляет следующими ходами.
  • Режим по умолчанию: не-мышление для быстрых ответов; переключайтесь на мышление для задач, требующих пошагового рассуждения (математика, планирование, отладка, сложный анализ кода).

  • Потоковые интерфейсы: когда включено мышление, вы можете видеть перемешанные рассуждения/контент в потоковых кадрах — буферизуйте или отображайте их отдельно и предоставьте пользователям переключатель “показать рассуждения”. (См. документацию API по формату потоковой передачи.)

  • Многоходовые диалоги: Qwen3 запоминает режим мышления из предыдущих ходов. Если вы хотите переключить его в середине диалога, используйте оба флага И мягкую команду переключения для надежности.

Примечания для GPT-OSS

  • Рассматривайте рассуждения как присутствующие даже если вы пытались их отключить; фильтруйте на стороне клиента, если ваш интерфейс не должен их отображать.
  • Для производственных приложений, использующих GPT-OSS, реализуйте логику фильтрации на стороне клиента, которая может обнаруживать и удалять шаблоны рассуждений при необходимости.
  • Тщательно тестируйте свою конкретную версию модели GPT-OSS, так как поведение может различаться между разными квантованиями и версиями.

Лучшие практики и советы для производства

Обработка ошибок и таймауты

Всегда реализуйте правильную обработку таймаутов и восстановление после ошибок:

func robustChatRequest(ctx context.Context, model string, messages []api.Message) (*api.ChatResponse, error) {
	// Установите разумный таймаут
	ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
	defer cancel()

	client, err := api.ClientFromEnvironment()
	if err != nil {
		return nil, fmt.Errorf("создание клиента: %w", err)
	}

	req := &api.ChatRequest{
		Model:    model,
		Messages: messages,
		Options: map[string]interface{}{
			"temperature": 0.7,
			"num_ctx":     4096, // размер окна контекста
		},
	}

	var resp api.ChatResponse
	if err := client.Chat(ctx, req, &resp); err != nil {
		return nil, fmt.Errorf("запрос чата не удался: %w", err)
	}

	return &resp, nil
}

Пулинг соединений и повторное использование

Повторно используйте клиент Ollama для запросов вместо создания нового каждый раз:

type OllamaService struct {
	client *api.Client
}

func NewOllamaService() (*OllamaService, error) {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return nil, err
	}
	return &OllamaService{client: client}, nil
}

func (s *OllamaService) Chat(ctx context.Context, req *api.ChatRequest) (*api.ChatResponse, error) {
	var resp api.ChatResponse
	if err := s.client.Chat(ctx, req, &resp); err != nil {
		return nil, err
	}
	return &resp, nil
}

Настройка окружения

Используйте переменные окружения для гибкого развертывания:

export OLLAMA_HOST=http://localhost:11434
export OLLAMA_NUM_PARALLEL=2
export OLLAMA_MAX_LOADED_MODELS=2

Официальный SDK автоматически учитывает OLLAMA_HOST через api.ClientFromEnvironment().

Мониторинг и логирование

Реализуйте структурированное логирование для производственных систем:

func loggedChat(ctx context.Context, logger *log.Logger, req *api.ChatRequest) error {
	start := time.Now()
	client, _ := api.ClientFromEnvironment()

	var resp api.ChatResponse
	err := client.Chat(ctx, req, &resp)

	duration := time.Since(start)
	logger.Printf("model=%s duration=%v error=%v tokens=%d",
		req.Model, duration, err, len(resp.Message.Content))

	return err
}

Заключение

  • Для проектов на Go github.com/ollama/ollama/api — это наиболее полный, готовый к производству выбор. Он поддерживается вместе с основным проектом Ollama, используется официальным CLI и предоставляет полное покрытие API с гарантированной совместимостью.

  • Для более высокоуровневых абстракций рассмотрите LangChainGo, когда вам нужны цепочки, инструменты, векторные хранилища и конвейеры RAG — хотя вы пожертвуете частью низкоуровневого контроля ради удобства.

  • Qwen3 предоставляет чистый, надежный контроль над режимом мышления с помощью как флагов, так и переключателей на уровне сообщений (/think, /no_think), что делает его идеальным для приложений, которым нужны как быстрые ответы, так и глубокое рассуждение.

  • Для GPT-OSS всегда планируйте очистку вывода рассуждений на стороне клиента при необходимости, так как флаг отключения мышления может не соблюдаться последовательно.

  • В производстве реализуйте правильную обработку ошибок, пулинг соединений, таймауты и мониторинг для создания надежных приложений на основе Ollama.

Комбинация сильной типизации Go, отличной поддержки многопоточности и простого API Ollama делает эту стэков идеальной для создания приложений с искусственным интеллектом — от простых чат-ботов до сложных систем RAG.

Ключевые выводы

Вот краткое руководство для выбора подхода:

Случай использования Рекомендуемый подход Почему
Производственное приложение github.com/ollama/ollama/api Официальная поддержка, полное покрытие API, обновляется вместе с основным проектом
Конвейер RAG/цепочки/инструменты LangChainGo Абстракции высокого уровня, интеграции с векторными хранилищами
Обучение/эксперименты Сырой REST с net/http Полная прозрачность, отсутствие зависимостей, образовательный аспект
Быстрое прототипирование Официальный SDK Баланс простоты и мощности
Потоковой чат-интерфейс Официальный SDK с обратными вызовами Встроенная поддержка потоковой передачи, чистый API

Рекомендации по выбору модели:

  • Qwen3: Лучший выбор для приложений, требующих управляемого режима мышления, надежных многоходовых диалогов
  • GPT-OSS: Сильная производительность, но требует защитной обработки вывода рассуждений/мышления
  • Другие модели: Тщательно тестируйте; поведение мышления варьируется в зависимости от семейства моделей

Ссылки и дополнительные материалы

Официальная документация

Альтернативы SDK для Go

Ресурсы, специфичные для моделей

Связанные темы

Другие полезные ссылки