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 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 | Sí | 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 engpt-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:
- Una bandera booleana
thinking
soportada por la función de pensamiento de Ollama; y - 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).
- Una bandera booleana
-
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
- 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 del SDK para Go
- Integración de Ollama en LangChainGo — Para aplicaciones basadas en cadenas
- Cliente de Ollama de swdunlop — Cliente de la comunidad con llamadas de herramientas
- Otro cliente de la comunidad: 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 sobre GPT-OSS — Detalles del modelo GPT-OSS
Temas relacionados
- Construyendo 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
- Instalación y configuración de Ollama
- Hoja de trucos de Ollama
- Hoja de trucos de Go
- Cómo maneja Ollama las solicitudes en paralelo
- Reclasificación de documentos de texto con Ollama y el modelo de incrustación Qwen3 — en Go
- Comparación de ORMs para PostgreSQL en Go: GORM vs Ent vs Bun vs sqlc
- Limitando LLMs con salida estructurada: Ollama, Qwen3 y Python o Go
- Usando Ollama en código de Python
- Comparación de LLMs: Qwen3:30b vs GPT-OSS:20b
- Problemas de salida estructurada en Ollama GPT-OSS