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.
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.

¿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 | Sí | 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
/thinko/no_thinken 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 nothinko--think=false) puede ser poco confiable engpt-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:
- Una bandera booleana
thinkingadmitida por la característica de pensamiento de Ollama; y - El interruptor suave de Qwen3
/thinky/no_thinken el mensaje del sistema/usuario más reciente. La instrucción más reciente gobierna la próxima(s) vuelta(s).
- Una bandera booleana
-
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/apies 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
- Referencia de la API de Ollama — Documentación completa de la API REST
- Paquete oficial de Ollama para Go — Documentación del SDK para Go
- Repositorio de GitHub de Ollama — Código fuente e issues
Alternativas al SDK de Go
- Integración de Ollama en LangChainGo — Para aplicaciones basadas en cadenas
- swdunlop/ollama-client — Cliente de la comunidad con llamadas de herramientas
- xyproto/ollamaclient — Otra opción de la comunidad
Recursos específicos del modelo
- Documentación de Qwen — Información oficial del modelo Qwen
- Información de GPT-OSS — Detalles del modelo GPT-OSS
Temas relacionados
- Construir aplicaciones RAG con Go — Ejemplos de LangChainGo
- Paquete de contexto de Go — Esencial para tiempos de espera y cancelación
- Mejores prácticas para el cliente HTTP de Go — Documentación de la biblioteca estándar
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.
- Instalar y configurar Ollama
- Hoja de trucos de Ollama
- Hoja de trucos de Go
- Reclasificación de documentos de texto con Ollama y modelo de incrustación Qwen3 - en Go
- Restringir LLMs con Salida Estructurada: Ollama, Qwen3 y Python o Go
- Comparación de LLMs: Qwen3:30b vs GPT-OSS:20b
- Problemas de salida estructurada de Ollama GPT-OSS