Clientes de Go para Ollama: comparación de SDK y ejemplos de Qwen3/GPT-OSS

Integre Ollama con Go: guía del SDK, ejemplos y mejores prácticas para producción.

Índice

Este guía proporciona una visión general completa de los disponibles SDKs de Go para Ollama y compara sus conjuntos de características.

Exploraremos ejemplos prácticos de Go para llamar a los modelos Qwen3 y GPT-OSS alojados en Ollama, tanto mediante llamadas REST API crudas como mediante el cliente oficial de Go, incluyendo un manejo detallado de los modos de pensamiento y no pensamiento en Qwen3.

go y ollama

¿Por qué Ollama + Go?

Ollama expone un pequeño y pragmático API HTTP (normalmente en ejecución en http://localhost:11434) diseñado para cargas de trabajo de generación y chat, con soporte integrado para streaming y capacidades de gestión de modelos. La documentación oficial cubre exhaustivamente las estructuras de solicitud/respuesta de /api/generate y /api/chat y los semánticos de streaming.

Go es una excelente opción para construir clientes de Ollama debido a su fuerte biblioteca estándar para HTTP, excelente manejo de JSON, primitivos de concurrencia nativos y interfaces estáticamente tipadas que capturan errores en tiempo de compilación.

Hasta octubre de 2025, aquí están las opciones de SDK de Go que más probablemente considerará.


SDKs de Go para Ollama — ¿qué está disponible?

SDK / Paquete Estado y “propietario” Alcance (Generar/Chat/Streaming) Gestión de modelos (pull/list/etc.) Extras / Notas
github.com/ollama/ollama/api Paquete oficial dentro del repositorio de Ollama; utilizado por el CLI ollama mismo Cobertura completa mapeada al REST; streaming soportado Considerado el cliente Go canónico; API se asemeja estrechamente a la documentación.
LangChainGo (github.com/tmc/langchaingo/llms/ollama) Marco de la comunidad (LangChainGo) con módulo de LLM de Ollama Chat/Completar + streaming mediante abstracciones del marco Limitado (gestión de modelos no es el objetivo principal) Excelente si quiere cadenas, herramientas, almacenes vectoriales en Go; menos de un SDK crudo.
github.com/swdunlop/ollama-client Cliente de la comunidad Enfoque en chat; buenos experimentos de llamada de herramientas Parcial Diseñado para experimentar con llamadas de herramientas; no es una superficie completa 1:1.
Otros SDKs de la comunidad (p. ej., ollamaclient, terceros “go-ollama-sdk”) Comunidad Varía Varía Calidad y cobertura varían; evalúe por repositorio.

Recomendación: Para producción, prefiera github.com/ollama/ollama/api—se mantiene con el proyecto principal y se asemeja al API REST.


Qwen3 y GPT-OSS en Ollama: modo de pensamiento vs no pensamiento (lo que debe saber)

  • Modo de pensamiento en Ollama separa el “razonamiento” del modelo del resultado final cuando está habilitado. Ollama documenta un comportamiento de habilitar/deshabilitar el pensamiento de primera clase en los modelos admitidos.
  • (https://www.glukhov.org/es/post/2025/10/qwen3-30b-vs-gpt-oss-20b/ “Qwen3:30b vs GPT-OSS:20b: Detalles técnicos, comparación de rendimiento y velocidad”) admite conmutación dinámica: agregue /think o /no_think en mensajes del sistema/usuario para cambiar de modo por turno; la última instrucción gana.
  • GPT-OSS: los usuarios reportan que deshabilitar el pensamiento (p. ej., /set nothink o --think=false) puede ser poco confiable en gpt-oss:20b; planee filtrar/ocultar cualquier razonamiento que su interfaz no deba mostrar.

Parte 1 — Llamando a Ollama mediante REST crudo (Go, net/http)

Tipos compartidos

Primero, definamos los tipos comunes y funciones auxiliares que usaremos en nuestros ejemplos:

package main

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

// ---- Tipos de API de Chat ----

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

type ChatRequest struct {
	Model    string        `json:"model"`
	Messages []ChatMessage `json:"messages"`
	// Algunos servidores exponen el control del pensamiento como una bandera booleana.
	// Incluso si se omite, aún puede controlar Qwen3 mediante /think o /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"` // presente cuando el pensamiento está encendido
	} `json:"message"`
	Done bool `json:"done"`
}

// ---- Tipos de API de Generar ----

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"`           // texto final para no-stream
	Thinking  string `json:"thinking,omitempty"` // presente cuando el pensamiento está encendido
	Done      bool   `json:"done"`
}

// ---- Funciones auxiliares ----

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 devuelve un puntero a un valor booleano
func bptr(b bool) *bool { return &b }

Chat — Qwen3 con pensamiento ACTIVADO (y cómo desactivarlo)

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

	req := ChatRequest{
		Model:   "qwen3:8b-thinking", // cualquier etiqueta :*-thinking que haya extraído
		Think:   bptr(true),
		Stream:  bptr(false),
		Messages: []ChatMessage{
			{Role: "system", Content: "Eres un asistente preciso."},
			{Role: "user",   Content: "Explica la recursión con un ejemplo corto de 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("🧠 pensamiento:\n", out.Message.Thinking)
	fmt.Println("\n💬 respuesta:\n", out.Message.Content)
	return nil
}

// Para desactivar el pensamiento en la siguiente vuelta, haga:
// (a) establecer Think=false, y/o
// (b) agregar "/no_think" al mensaje más reciente del sistema/usuario (interruptor suave de Qwen3).
// Qwen3 respeta la última instrucción /think o /no_think en chats de múltiples turnos.
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: "Eres breve. /no_think"},
			{Role: "user",   Content: "Explica la recursión en una oración."},
		},
	}

	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out ChatResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	// Espere que el pensamiento esté vacío; aún así, maneje defensivamente.
	if out.Message.Thinking != "" {
		fmt.Println("🧠 pensamiento (inesperado):\n", out.Message.Thinking)
	}
	fmt.Println("\n💬 respuesta:\n", out.Message.Content)
	return nil
}

(El interruptor suave de Qwen3 /think y /no_think está documentado por el equipo de Qwen; la última instrucción gana en chats de múltiples turnos.)

Chat — GPT-OSS con pensamiento (y una advertencia)

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

	req := ChatRequest{
		Model:  "gpt-oss:20b",
		Think:  bptr(true),   // solicita razonamiento separado si se admite
		Stream: bptr(false),
		Messages: []ChatMessage{
			{Role: "user", Content: "¿Qué es la programación dinámica? Explica la idea central."},
		},
	}

	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out ChatResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	// Quirk conocido: deshabilitar el pensamiento puede no suprimir completamente el razonamiento en gpt-oss:20b.
	// Siempre filtre/oculte el pensamiento en la interfaz si no quiere mostrarlo.
	fmt.Println("🧠 pensamiento:\n", out.Message.Thinking)
	fmt.Println("\n💬 respuesta:\n", out.Message.Content)
	return nil
}

Los usuarios reportan que deshabilitar el pensamiento en gpt-oss:20b (p. ej., /set nothink o --think=false) puede ser ignorado—planee filtrado en el lado del cliente si es necesario.

Generar — Qwen3 y GPT-OSS

func generateQwen3() error {
	endpoint := "http://localhost:11434/api/generate"
	req := GenerateRequest{
		Model:  "qwen3:4b-thinking",
		Prompt: "En 2–3 oraciones, ¿para qué se utilizan los árboles B en bases de datos?",
		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("🧠 pensamiento:\n", out.Thinking)
	}
	fmt.Println("\n💬 respuesta:\n", out.Response)
	return nil
}

func generateGptOss() error {
	endpoint := "http://localhost:11434/api/generate"
	req := GenerateRequest{
		Model:  "gpt-oss:20b",
		Prompt: "Explica brevemente la retropropagación en redes neuronales.",
		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("🧠 pensamiento:\n", out.Thinking)
	}
	fmt.Println("\n💬 respuesta:\n", out.Response)
	return nil
}

Las formas de REST y el comportamiento de streaming provienen directamente de la referencia de la API de Ollama.


Parte 2 — Llamando a Ollama mediante el SDK oficial de Go (github.com/ollama/ollama/api)

El paquete oficial expone un Client con métodos que corresponden a la API REST. El CLI de Ollama mismo utiliza este paquete para comunicarse con el servicio, lo que lo hace la opción más segura para compatibilidad.

Instalación

go get github.com/ollama/ollama/api

Chat — Qwen3 (pensamiento ACTIVADO / DESACTIVADO)

package main

import (
	"context"
	"fmt"
	"log"

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

func chatWithQwen3Thinking(ctx context.Context, thinking bool) error {
	client, err := api.ClientFromEnvironment() // respeta OLLAMA_HOST si se establece
	if err != nil {
		return err
	}

	req := &api.ChatRequest{
		Model: "qwen3:8b-thinking",
		// Muchas construcciones de servidor exponen el pensamiento como una bandera de nivel superior;
		// además, puede controlar Qwen3 mediante /think o /no_think en mensajes.
		Think: api.Ptr(thinking),
		Messages: []api.Message{
			{Role: "system", Content: "Eres un asistente preciso."},
			{Role: "user",   Content: "Explica el ordenamiento por mezcla con un fragmento de código de Go corto."},
		},
	}

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

	if resp.Message.Thinking != "" {
		fmt.Println("🧠 pensamiento:\n", resp.Message.Thinking)
	}
	fmt.Println("\n💬 respuesta:\n", resp.Message.Content)
	return nil
}

func main() {
	ctx := context.Background()
	if err := chatWithQwen3Thinking(ctx, true); err != nil {
		log.Fatal(err)
	}
	// Ejemplo: no pensamiento
	if err := chatWithQwen3Thinking(ctx, false); err != nil {
		log.Fatal(err)
	}
}

Chat — GPT-OSS (maneje el razonamiento defensivamente)

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: "¿Qué es la memoización y cuándo es útil?"},
		},
	}
	var resp api.ChatResponse
	if err := client.Chat(ctx, req, &resp); err != nil {
		return err
	}
	// Si pretende ocultar el razonamiento, hágalo aquí independientemente de las banderas.
	if resp.Message.Thinking != "" {
		fmt.Println("🧠 pensamiento:\n", resp.Message.Thinking)
	}
	fmt.Println("\n💬 respuesta:\n", resp.Message.Content)
	return nil
}

Generar — Qwen3 y 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: "Resuma el rol de un árbol B en el índice.",
		Think:  api.Ptr(true),
	}
	var resp api.GenerateResponse
	if err := client.Generate(ctx, req, &resp); err != nil {
		return err
	}
	if resp.Thinking != "" {
		fmt.Println("🧠 pensamiento:\n", resp.Thinking)
	}
	fmt.Println("\n💬 respuesta:\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: "Explica el descenso de gradiente en términos simples.",
		Think:  api.Ptr(true),
	}
	var resp api.GenerateResponse
	if err := client.Generate(ctx, req, &resp); err != nil {
		return err
	}
	if resp.Thinking != "" {
		fmt.Println("🧠 pensamiento:\n", resp.Thinking)
	}
	fmt.Println("\n💬 respuesta:\n", resp.Response)
	return nil
}

La superficie del paquete oficial se asemeja a la documentación de REST y se actualiza junto con el proyecto principal.


Respuestas de streaming

Para streaming en tiempo real, establezca Stream: bptr(true) en su solicitud. La respuesta se entregará como fragmentos de JSON separados por nuevas líneas:

func streamChatExample() error {
	endpoint := "http://localhost:11434/api/chat"
	req := ChatRequest{
		Model:  "qwen3:8b-thinking",
		Think:  bptr(true),
		Stream: bptr(true), // Habilitar streaming
		Messages: []ChatMessage{
			{Role: "user", Content: "Explica el algoritmo de ordenamiento rápido paso a paso."},
		},
	}

	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
		}
		
		// Procese el pensamiento y el contenido a medida que llegan
		if chunk.Message.Thinking != "" {
			fmt.Print(chunk.Message.Thinking)
		}
		fmt.Print(chunk.Message.Content)
		
		if chunk.Done {
			break
		}
	}
	return nil
}

Con el SDK oficial, use una función de devolución de llamada para manejar los fragmentos de streaming:

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: "Explica los árboles de búsqueda binaria."},
		},
	}
	
	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
}

Trabajando con Qwen3 pensamiento vs no pensamiento (guía práctica)

  • Dos palancas:

    1. Una bandera booleana thinking soportada por la función de pensamiento de Ollama; y
    2. El interruptor suave de Qwen3 /think y /no_think en el mensaje del sistema/usuario más reciente. La instrucción más reciente gobierna la siguiente(s) vuelta(s).
  • Postura predeterminada: no pensamiento para respuestas rápidas; escalada a pensamiento para tareas que necesitan razonamiento paso a paso (matemáticas, planificación, depuración, análisis complejo de código).

  • Interfaz de streaming: cuando está habilitado el pensamiento, puede ver razonamiento/contenido intercalado en marcos de streaming—bufferice o renderice por separado y dé a los usuarios un interruptor “mostrar razonamiento”. (Consulte la documentación de la API para el formato de streaming.)

  • Conversaciones de múltiples turnos: Qwen3 recuerda el modo de pensamiento de las vueltas anteriores. Si quiere conmutarlo en medio de una conversación, use tanto la bandera como el interruptor suave para fiabilidad.

Notas para GPT-OSS

  • Trate el razonamiento como presente incluso si intentó deshabilitarlo; filtre en el cliente si su UX no debe mostrarlo.
  • Para aplicaciones de producción que usen GPT-OSS, implemente lógica de filtrado en el cliente que pueda detectar y eliminar patrones de razonamiento si es necesario.
  • Pruebe su variante específica de GPT-OSS exhaustivamente, ya que el comportamiento puede variar entre diferentes cuantizaciones y versiones.

Buenas prácticas y consejos para producción

Manejo de errores y tiempos de espera

Siempre implemente un manejo adecuado de tiempos de espera y recuperación de errores:

func robustChatRequest(ctx context.Context, model string, messages []api.Message) (*api.ChatResponse, error) {
	// Establezca un tiempo de espera razonable
	ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
	defer cancel()
	
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return nil, fmt.Errorf("creando cliente: %w", err)
	}
	
	req := &api.ChatRequest{
		Model:    model,
		Messages: messages,
		Options: map[string]interface{}{
			"temperature": 0.7,
			"num_ctx":     4096, // tamaño de la ventana de contexto
		},
	}
	
	var resp api.ChatResponse
	if err := client.Chat(ctx, req, &resp); err != nil {
		return nil, fmt.Errorf("solicitud de chat fallida: %w", err)
	}
	
	return &resp, nil
}

Piscina de conexiones y reutilización

Reutilice el cliente de Ollama entre solicitudes en lugar de crear uno nuevo cada vez:

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
}

Configuración del entorno

Use variables de entorno para una implementación flexible:

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

El SDK oficial respeta automáticamente OLLAMA_HOST mediante api.ClientFromEnvironment().

Monitoreo y registro

Implemente registro estructurado para sistemas de producción:

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
}

Conclusión

  • Para proyectos de Go, github.com/ollama/ollama/api es la opción más completa y lista para producción. Se mantiene junto con el proyecto principal de Ollama, se usa en el CLI oficial y proporciona una cobertura completa de la API con compatibilidad garantizada.

  • Para abstracciones de nivel superior, considere LangChainGo cuando necesite cadenas, herramientas, almacenes vectoriales y pipelines RAG—aunque intercambiará algo de control de nivel bajo por conveniencia.

  • Qwen3 le da un control limpio y confiable sobre el modo de pensamiento con tanto banderas como conmutadores de nivel de mensaje (/think, /no_think), lo que lo hace ideal para aplicaciones que necesiten tanto respuestas rápidas como razonamiento profundo.

  • Para GPT-OSS, siempre planee sanitizar la salida de razonamiento en el cliente cuando sea necesario, ya que la bandera de deshabilitación de pensamiento puede no ser respetada consistentemente.

  • En producción, implemente un manejo adecuado de errores, reutilización de conexiones, tiempos de espera y monitoreo para construir aplicaciones robustas impulsadas por Ollama.

La combinación de la fuerte tipificación de Go, excelente soporte de concurrencia y la API directa de Ollama lo hacen un stack ideal para construir aplicaciones impulsadas por IA, desde chatbots simples hasta sistemas RAG complejos.

Principales conclusiones

Aquí hay una referencia rápida para elegir su enfoque:

Caso de uso Enfoque recomendado ¿Por qué?
Aplicación de producción github.com/ollama/ollama/api Soporte oficial, cobertura completa de la API, mantenido con el proyecto principal
Pipeline de RAG/cadenas/herramientas LangChainGo Abstracciones de alto nivel, integraciones con almacenes vectoriales
Aprendizaje/experimentación REST crudo con net/http Transparencia total, sin dependencias, educativo
Prototipo rápido SDK oficial Equilibrio entre simplicidad y potencia
Interfaz de chat de streaming SDK oficial con devoluciones de llamada Soporte integrado de streaming, API limpia

Guía de selección de modelos:

  • Qwen3: Mejor para aplicaciones que requieren controlable modo de pensamiento, conversaciones confiables de múltiples turnos
  • GPT-OSS: Alto rendimiento pero requiere manejo defensivo de la salida de razonamiento
  • Otros modelos: Pruebe exhaustivamente; el comportamiento de pensamiento varía por familia de modelos

Referencias y lectura adicional

Documentación oficial

Alternativas del SDK para Go

Recursos específicos del modelo

Temas relacionados

Otros enlaces útiles