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

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

Índice

Esta guía ofrece una vista general completa de los SDKs de Go para Ollama y compara sus conjuntos de funciones.

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

go and ollama

¿Por qué Ollama + Go?

Ollama expone una API HTTP pequeña y pragmática (que normalmente funciona en http://localhost:11434) diseñada para cargas de trabajo de generate y chat, con soporte de streaming integrado y capacidades de gestión de modelos. La documentación oficial cubre a fondo las estructuras de solicitud/respuesta de /api/generate y /api/chat y las semánticas de streaming.

Go es una excelente elección para construir clientes de Ollama debido a su fuerte soporte en la biblioteca estándar para HTTP, su excelente manejo de JSON, sus primitivas nativas de concurrencia y sus interfaces estáticamente tipadas que capturan errores en tiempo de compilación. Para ver cómo Ollama se compara con vLLM, Docker Model Runner, LocalAI y los proveedores de la nube, incluyendo cuándo elegir cada uno, consulta Alojamiento de LLM: Infraestructura local, autoalojada y en la nube comparada.

A octubre de 2025, estas son las opciones de SDK de Go que probablemente considerarás.


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

SDK / Paquete Estado y “propietario” Alcance (Generate/Chat/Streaming) Gestión de modelos (pull/list/etc.) Extras / Notas
github.com/ollama/ollama/api Paquete Oficial dentro del repositorio de Ollama; usado por la propia CLI ollama Cobertura completa mapeada a REST; streaming soportado Sí Considerado el cliente Go canónico; la API refleja de cerca la documentación.
LangChainGo (github.com/tmc/langchaingo/llms/ollama) Marco comunitario (LangChainGo) con módulo de LLM de Ollama Chat/Completion + streaming mediante abstracciones del marco Limitado (la gestión de modelos no es el objetivo principal) Excelente si quieres cadenas, herramientas y almacenes vectoriales en Go; menos un SDK crudo.
github.com/swdunlop/ollama-client Cliente comunitario Enfoque en chat; buenas experimentaciones de tool-calling Parcial Construido para experimentar con la llamada a herramientas; no es una superficie completa 1:1.
Otros SDKs comunitarios (p. ej., ollamaclient, “go-ollama-sdk` de terceros) Comunitario Varía Varía La calidad y la cobertura varían; evalúa por repositorio.

Recomendación: Para producción, prefiere github.com/ollama/ollama/api—es mantenido junto con el proyecto central y refleja la API REST.


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

  • El modo de pensamiento en Ollama separa el “razonamiento” del modelo de la salida final cuando está habilitado. Nuestra hoja de trucos de Ollama documenta las banderas de activar/desactivar el pensamiento (--think, --hidethinking) en los modelos admitidos.
  • (https://www.glukhov.org/es/llm-performance/benchmarks/qwen3-30b-vs-gpt-oss-20b/ “Qwen3:30b vs GPT-OSS:20b: Detalles técnicos, rendimiento y comparación de velocidad”) admite conmutación dinámica: agrega /think o /no_think en los mensajes de sistema/usuario para cambiar de modo turno a turno; la instrucción más reciente prevalece.
  • GPT-OSS: los usuarios reportan que desactivar el pensamiento (p. ej., /set nothink o --think=false) puede no ser confiable en gpt-oss:20b; planea filtrar/ocultar cualquier razonamiento que tu interfaz de usuario no deba mostrar.

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

Tipos compartidos

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

package main

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

// ---- Tipos de la 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 las etiquetas /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á activado
	} `json:"message"`
	Done bool `json:"done"`
}

// ---- Tipos de la API de 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"`           // texto final para no-streaming
	Thinking  string `json:"thinking,omitempty"` // presente cuando el pensamiento está activado
	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 hayas obtenido
		Think:   bptr(true),
		Stream:  bptr(false),
		Messages: []ChatMessage{
			{Role: "system", Content: "Eres un asistente preciso."},
			{Role: "user",   Content: "Explica la recursión con un breve ejemplo 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
}

// Desactiva el pensamiento para el siguiente turno mediante:
// (a) establecer Think=false, y/o
// (b) agregar "/no_think" al mensaje de sistema/usuario más reciente (conmutación suave de Qwen3).
// Qwen3 respeta la última instrucción de /think o /no_think en chats multiturno.
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 conciso. /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
	}
// Esperamos que el pensamiento esté vacío; aun así, mánelo de forma defensiva.
	if out.Message.Thinking != "" {
		fmt.Println("🧠 pensamiento (inesperado):\n", out.Message.Thinking)
	}
	fmt.Println("\n💬 respuesta:\n", out.Message.Content)
	return nil
}

(El conmutador suave /think y /no_think de Qwen3 está documentado por el equipo de Qwen; la instrucción última prevalece en chats multiturno.)

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 está soportado
		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: desactivar el pensamiento puede no suprimir por completo el razonamiento en gpt-oss:20b.
	// Filtra u oculta siempre el pensamiento en la UI si no quieres mostrarlo.
	fmt.Println("🧠 pensamiento:\n", out.Message.Thinking)
	fmt.Println("\n💬 respuesta:\n", out.Message.Content)
	return nil
}

Los usuarios informan que desactivar el pensamiento en gpt-oss:20b (p. ej., /set nothink o --think=false) puede ser ignorado: planifica un filtrado en el cliente si es necesario.

Generate — 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 usan los B-Trees en las 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 las 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 REST y el comportamiento de streaming provienen directamente de la referencia de la API de Ollama.


Parte 2 — Llamar 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. La propia CLI de Ollama utiliza este paquete para comunicarse con el servicio, lo que lo convierte en la apuesta más segura para la 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 está definido
	if err != nil {
		return err
	}

	req := &api.ChatRequest{
		Model: "qwen3:8b-thinking",
		// Muchas versiones del servidor exponen el pensamiento como una bandera de nivel superior;
		// adicionalmente, puedes controlar Qwen3 mediante /think o /no_think en los mensajes.
		Think: api.Ptr(thinking),
		Messages: []api.Message{
			{Role: "system", Content: "Eres un asistente preciso."},
			{Role: "user",   Content: "Explica el ordenamiento de fusión (merge sort) con un breve fragmento de 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: sin pensamiento
	if err := chatWithQwen3Thinking(ctx, false); err != nil {
		log.Fatal(err)
	}
}

Chat — GPT-OSS (manejo defensivo del razonamiento)

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 pretendes 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
}

Generate — 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: "Resume el papel de un B-Tree en la indexación.",
		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 refleja la documentación REST y se actualiza junto con el proyecto central.


Respuestas con streaming

Para el streaming en tiempo real, establece Stream: bptr(true) en tu solicitud. La respuesta se entregará en fragmentos de JSON delimitados por saltos de línea:

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 quicksort 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 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, utiliza una función callback 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
}

Trabajo con pensamiento vs no-pensamiento en Qwen3 (guía práctica)

  • Dos palancas:

    1. Una bandera booleana thinking soportada por la función de pensamiento de Ollama; y
    2. Los comandos de conmutación suave /think y /no_think de Qwen3 en el mensaje de sistema/usuario más reciente. La instrucción más reciente gobierna los próximos turnos.
  • Postura por defecto: no-pensamiento para respuestas rápidas; escala a pensamiento para tareas que requieren razonamiento paso a paso (matemáticas, planificación, depuración, análisis complejo de código).

  • UIs de streaming: cuando el pensamiento está habilitado, es posible que veas razonamiento/contenido entrelazado en los fotogramas de streaming; búferízalos o renderízalos por separado y ofrece a los usuarios un conmutador de “mostrar razonamiento”. (Consulta la documentación de la API para el formato de streaming.)

  • Conversaciones multirrrno: Qwen3 recuerda el modo de pensamiento de los turnos anteriores. Si deseas alternarlo en medio de la conversación, usa tanto la bandera como el comando de conmutación suave para mayor fiabilidad.

Notas para GPT-OSS

  • Trata el razonamiento como presente incluso si intentaste desactivarlo; filtra en el cliente si tu experiencia de usuario 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 a fondo tu variante específica del modelo GPT-OSS, ya que el comportamiento puede variar entre distintas cuantizaciones y versiones.

Mejores prácticas y consejos para producción

Manejo de errores y temporizaciones

Implementa siempre un manejo adecuado de temporizaciones y recuperación de errores:

func robustChatRequest(ctx context.Context, model string, messages []api.Message) (*api.ChatResponse, error) {
	// Establece una temporización razonable
	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, // tamaño de la ventana de contexto
		},
	}
	
	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
}

Pooling y reutilización de conexiones

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 un 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 en Go, github.com/ollama/ollama/api es la opción más completa y lista para producción. Se mantiene junto con el proyecto central de Ollama, es usado por la CLI oficial y ofrece una cobertura completa de la API con compatibilidad garantizada.

  • Para abstracciones de nivel superior, considera LangChainGo cuando necesites cadenas, herramientas, almacenes vectoriales y pipelines RAG, aunque sacrificarás algo de control de bajo nivel a cambio de conveniencia.

  • Qwen3 te ofrece un control limpio y fiable sobre el modo de pensamiento con ambas banderas y conmutaciones a nivel de mensaje (/think, /no_think), lo que lo hace ideal para aplicaciones que necesitan tanto respuestas rápidas como razonamiento profundo.

  • Para GPT-OSS, planifica siempre sanitizar la salida de razonamiento en el cliente cuando sea necesario, ya que la bandera para desactivar el pensamiento puede no respetarse de manera consistente.

  • En producción, implementa un manejo adecuado de errores, pooling de conexiones, temporizaciones y monitoreo para construir aplicaciones impulsadas por Ollama de manera robusta.

La combinación de la tipificación fuerte de Go, su excelente soporte de concurrencia y la API sencilla de Ollama lo convierten en una pila ideal para construir aplicaciones impulsadas por inteligencia artificial, desde chatbots simples hasta sistemas RAG complejos.

Puntos clave

Aquí tienes una referencia rápida para elegir tu 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 central
Pipeline 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
Prototipado rápido SDK oficial Equilibrio entre simplicidad y potencia
UI de chat con streaming SDK oficial con callbacks Soporte de streaming integrado, API limpia

Guía para la selección de modelos:

  • Qwen3: Mejor para aplicaciones que requieren un modo de pensamiento controlable, conversaciones multirruno fiables
  • GPT-OSS: Fuerte rendimiento pero requiere un manejo defensivo de la salida de pensamiento/razonamiento
  • Otros modelos: Prueba a fondo; el comportamiento del pensamiento varía según la familia del modelo

Referencias y lecturas adicionales

Documentación oficial

Alternativas de SDK de Go

Recursos específicos de modelos

Temas relacionados

Otros enlaces útiles

Para una comparación más amplia de Ollama con otras infraestructuras locales y en la nube para LLM, consulta nuestra Alojamiento de LLM: Infraestructura local, autoalojada y en la nube comparada.

Suscribirse

Recibe nuevas publicaciones sobre sistemas, infraestructura e ingeniería de IA.