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

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

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

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

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

go and ollama

Почему Ollama + Go?

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

Go — отличный выбор для создания клиентов Ollama, благодаря его сильной стандартной библиотеке для HTTP, отличной обработке JSON, нативным примитивам конкурентности и статически типизированным интерфейсам, которые выявляют ошибки на этапе компиляции. Чтобы увидеть, как Ollama сравнивается с vLLM, Docker Model Runner, LocalAI и облачными провайдерами — включая случаи, когда следует выбирать каждый из них, — см. Хостинг LLM: Локальные, Self-Hosted и облачная инфраструктура в сравнении.

На октябрь 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) Community-фреймворк (LangChainGo) с модулем LLM для Ollama Чат/Завершение + потоковая передача через абстракции фреймворка Ограниченно (управление моделями не является основной целью) Отлично подходит, если вам нужны цепочки (chains), инструменты, векторные хранилища на Go; это скорее не сырой SDK.
github.com/swdunlop/ollama-client Community-клиент Фокус на чате; хорошие эксперименты с вызовом инструментов Частично Создан для экспериментов с вызовом инструментов; не является полным 1:1 покрытием.
Другие community SDK (напр., ollamaclient, сторонние “go-ollama-sdk”) Community Варьируется Варьируется Качество и покрытие варьируются; оценивайте каждый репозиторий отдельно.

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


Qwen3 и GPT-OSS на Ollama: режим рассуждения (thinking) vs без рассуждений (что нужно знать)

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

Часть 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"`           // итоговый текст для non-stream
	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: "You are a precise assistant."},
			{Role: "user",   Content: "Explain recursion with a short Go example."},
		},
	}

	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: "You are brief. /no_think"},
			{Role: "user",   Content: "Explain recursion in one sentence."},
		},
	}

	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: "What is dynamic programming? Explain the core idea."},
		},
	}

	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.
// Всегда фильтруйте/скрывайте рассуждения в UI, если вы не хотите их показывать.
	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: "In 2–3 sentences, what are B-Trees used for in databases?",
		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: "Briefly explain backpropagation in neural networks.",
		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)

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

Установка

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: "You are a precise assistant."},
			{Role: "user",   Content: "Explain merge sort with a short Go snippet."},
		},
	}

	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: "What is memoization and when is it useful?"},
		},
	}
	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: "Summarize the role of a B-Tree in indexing.",
		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: "Explain gradient descent in simple terms.",
		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 API и обновляется вместе с основным проектом.


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

Для потоковой передачи в реальном времени установите 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: "Explain quicksort algorithm step by step."},
		},
	}

	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 используйте функцию обратного вызова (callback) для обработки блоков потоковой передачи:

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: "Explain binary search trees."},
		},
	}
	
	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 в последнем системном/пользовательском сообщении. Самая свежая инструкция управляет следующими ходами.
  • Позиция по умолчанию: без рассуждений для быстрых ответов; переходите на режим рассуждений для задач, требующих пошагового рассуждения (математика, планирование, отладка, сложный анализ кода).

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

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

Примечания по GPT-OSS

  • Считайте рассуждения присутствующими, даже если вы пытались их отключить; фильтруйте на клиенте, если ваш UX не должен их показывать.
  • Для производственных приложений, использующих 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("creating client: %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("chat request failed: %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 Баланс простоты и мощности
Чат-UI с потоковой передачей Официальный SDK с колбэками Встроенная поддержка потоковой передачи, чистый API

Руководство по выбору моделей:

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

Ссылки и дальнейшее чтение

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

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

Ресурсы по конкретным моделям

Смежные темы

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

Для более широкого сравнения Ollama с другой локальной и облачной LLM-инфраструктурой, посмотрите наше Хостинг LLM: Локальные, Self-Hosted и облачная инфраструктура в сравнении.

Подписаться

Получайте новые материалы про системы, инфраструктуру и AI engineering.