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

Почему 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 без рассуждений (практические рекомендации)
-
Два рычага управления:
- Булев флаг
thinking, поддерживаемый функцией рассуждений Ollama; и - Команды мягкого переключения 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: Сильная производительность, но требует осторожной обработки вывода рассуждений
- Другие модели: Тщательно тестируйте; поведение рассуждений варьируется в зависимости от семейства моделей
Ссылки и дальнейшее чтение
Официальная документация
- Справочное руководство API Ollama — Полная документация REST API
- Официальный Go-пакет Ollama — Документация Go SDK
- Репозиторий Ollama на GitHub — Исходный код и проблемы
Альтернативы Go SDK
- Интеграция Ollama в LangChainGo — Для цепочечных приложений
- swdunlop/ollama-client — Community-клиент с вызовом инструментов
- xyproto/ollamaclient — Еще один community-вариант
Ресурсы по конкретным моделям
- Документация Qwen — Официальная информация о моделях Qwen
- Информация о GPT-OSS — Детали о модели GPT-OSS
Смежные темы
- Построение RAG-приложений на Go — Примеры LangChainGo
- Пакет context в Go — Ключевой для таймаутов и отмены
- Лучшие практики HTTP-клиента Go — Документация стандартной библиотеки
Другие полезные ссылки
Для более широкого сравнения Ollama с другой локальной и облачной LLM-инфраструктурой, посмотрите наше Хостинг LLM: Локальные, Self-Hosted и облачная инфраструктура в сравнении.
- Установка и настройка Ollama
- Шпаргалка по Ollama
- Шпаргалка по Go
- Архитектура и реализация RAG-систем
- Ранжирование текстовых документов с Ollama и моделью Qwen3 Embedding - на Go
- Ограничение LLM структурированным выводом: Ollama, Qwen3 и Python или Go
- Сравнение LLM: Qwen3:30b vs GPT-OSS:20b
- Проблемы со структурированным выводом в Ollama GPT-OSS