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 API HTTP pragmático (normalmente ejecutándose 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. Para ver cómo Ollama compara con vLLM, Docker Model Runner, LocalAI y proveedores en la nube — incluyendo cuándo elegir cada uno — ve LLM Hosting: Local, Self-Hosted & Cloud Infrastructure Compared.

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


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 Cubrimiento completo mapeado al REST; soporte para streaming Considerado el cliente oficial de Go; API se asemeja estrechamente a la documentación.
LangChainGo (github.com/tmc/langchaingo/llms/ollama) Marco de 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 deseas cadenas, herramientas, almacenes de vectores en Go; menos de un SDK crudo.
github.com/swdunlop/ollama-client Cliente de la comunidad Enfocado 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 (por ejemplo, ollamaclient, SDK de terceros “go-ollama-sdk”) Comunidad Varía Varía La calidad y el cubrimiento varían; evalúa por repositorio.

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


Qwen3 & GPT-OSS en Ollama: modo pensamiento vs no pensamiento (lo que debes 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 primer orden en los modelos compatibles.
  • (https://www.glukhov.org/es/llm-performance/benchmarks/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: agrega /think o /no_think en mensajes del sistema/usuario para cambiar los modos por turno; la última instrucción gana.
  • GPT-OSS: los usuarios reportan que deshabilitar el pensamiento (por ejemplo, /set nothink o --think=false) puede ser poco confiable en gpt-oss:20b; planifica para filtrar/ocultar cualquier razonamiento que tu UI no deba mostrar.

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

Tipos compartidos

Primero, definamos los tipos comunes y las 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 puedes 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 encendido (y cómo apagarlo)

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

	req := ChatRequest{
		Model:   "qwen3:8b-thinking", // cualquier etiqueta :*-thinking que hayas 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 en 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 apagar el pensamiento en la próxima vuelta, haz lo siguiente:
// (a) establece Think=false, y/o
// (b) agrega "/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 vueltas.
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 frase."},
		},
	}

	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out ChatResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	// Espera que el pensamiento esté vacío; aún así, maneja 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 vueltas.)

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 filtra/oculta el pensamiento en la interfaz de usuario si no deseas 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 (por ejemplo, /set nothink o --think=false) puede ser ignorado—planifica para 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 frases, ¿para qué se usan 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 mejor opción para compatibilidad.

Instalar

go get github.com/ollama/ollama/api

Chat — Qwen3 (pensamiento encendido / apagado)

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 está establecido
	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, puedes 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 corto en Go."},
		},
	}

	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 pensar
	if err := chatWithQwen3Thinking(ctx, false); err != nil {
		log.Fatal(err)
	}
}

Chat — GPT-OSS (maneja 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 planeas ocultar el razonamiento, hazlo 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 & 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 REST y se actualiza junto con el proyecto principal.


Respuestas de streaming

Para streaming en tiempo real, establece Stream: bptr(true) en tu solicitud. La respuesta se entregará como fragmentos 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), // Habilita 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
		}
		
		// Procesa el pensamiento y el contenido conforme 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, usa 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 admitida por la característica 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 próxima(s) vuelta(s).
  • Postura por defecto: no pensar para respuestas rápidas; escalar a pensar para tareas que requieren razonamiento paso a paso (matemáticas, planificación, depuración, análisis complejo de código).

  • Interfaz de usuario de streaming: cuando el pensamiento está habilitado, puedes ver razonamiento/contenido intercalados en los marcos de streaming—bufferiza o renderiza por separado y proporciona a los usuarios un interruptor “mostrar razonamiento”. (Ver documentación de la API para el formato de streaming.)

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

Notas para GPT-OSS

  • Trata el razonamiento como presente incluso si intentaste deshabilitarlo; filtra en el cliente si tu UX no debe mostrarlo.
  • Para aplicaciones de producción que usan GPT-OSS, implementa lógica de filtrado en el cliente que pueda detectar y eliminar patrones de razonamiento si es necesario.
  • Prueba tu variante específica del modelo GPT-OSS exhaustivamente, ya que el comportamiento puede variar entre diferentes cuantizaciones y versiones.

Mejores prácticas y consejos para producción

Manejo de errores y tiempos de espera

Siempre implementa 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) {
	// Establece 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

Reutiliza 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

Usa 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

Implementa 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, considera LangChainGo cuando necesites cadenas, herramientas, almacenes de vectores y pipelines RAG—aunque intercambiarás algo de control de nivel inferior por comodidad.

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

  • Para GPT-OSS, siempre planifica sanitizar la salida de razonamiento en el lado del cliente cuando sea necesario, ya que la bandera de deshabilitar el pensamiento puede no ser respetada consistentemente.

  • En producción, implementa un manejo adecuado de errores, pooling de conexiones, tiempos de espera y monitoreo para construir aplicaciones robustas con Ollama.

La combinación del fuerte tipado de Go, excelente soporte de concurrencia y la API directa de Ollama hace que sea un stack ideal para construir aplicaciones con IA — desde chatbots simples hasta sistemas RAG complejos.

Resumen clave

Aquí tienes una referencia rápida para elegir tu enfoque:

Caso de uso Enfoque recomendado ¿Por qué?
Aplicación en producción github.com/ollama/ollama/api Soporte oficial, cubrimiento completo de la API, mantenido con el proyecto principal
Pipeline de RAG/cadenas/herramientas LangChainGo Abstracciones de alto nivel, integraciones con almacenes de vectores
Aprendizaje/experimentación REST crudo con net/http Transparencia completa, sin dependencias, educativo
Prototipado rápido SDK oficial Equilibrio entre simplicidad y poder
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 requieran controlable modo de pensamiento, conversaciones confiables de múltiples vueltas
  • GPT-OSS: Buena rendimiento pero requiere manejo defensivo de salida de razonamiento
  • Otros modelos: Prueba exhaustivamente; el comportamiento de pensamiento varía según familia de modelos

Referencias y lectura adicional

Documentación oficial

Alternativas al SDK de Go

Recursos específicos del modelo

Temas relacionados

Otros enlaces útiles

Para una comparación más amplia de Ollama con otras infraestructuras locales y en la nube de LLM, consulta nuestro LLM Hosting: Local, Self-Hosted & Cloud Infrastructure Compared.