Client Go per Ollama: confronto tra SDK e esempi con Qwen3/GPT-OSS

Integra Ollama con Go: guida all'SDK, esempi e best practice per la produzione.

Indice

Questo documento fornisce un’overview completa degli SDK Go per Ollama disponibili e confronta i loro set di funzionalità.

Esploreremo esempi pratici in Go per chiamare i modelli Qwen3 e GPT-OSS ospitati su Ollama, entrambi tramite chiamate REST API grezze e il client Go ufficiale, inclusi dettagliati trattamenti dei modi di pensiero e non-pensiero in Qwen3.

go e ollama

Perché Ollama + Go?

Ollama espone un piccolo, pragmatico HTTP API (tipicamente in esecuzione su http://localhost:11434) progettato per carichi di lavoro di generazione e chat, con supporto integrato per lo streaming e le capacità di gestione dei modelli. La documentazione ufficiale copre in modo completo /api/generate e /api/chat strutture richiesta/risposta e semantica dello streaming.

Go è un’ottima scelta per costruire client Ollama grazie al forte supporto della libreria standard per HTTP, eccellente gestione JSON, primitive di concorrenza native e interfacce staticamente tipizzate che catturano gli errori al momento della compilazione. Per vedere come Ollama si confronta con vLLM, Docker Model Runner, LocalAI e fornitori di cloud — incluso quando scegliere ciascuno — vedi LLM Hosting: Local, Self-Hosted & Cloud Infrastructure Compared.

Fino ad ottobre 2025, ecco le opzioni SDK Go che probabilmente considererai.


SDK Go per Ollama — cosa è disponibile?

SDK / Package Stato e “proprietario” Ambito (Generazione/Chat/Streaming) Gestione modelli (pull/list/etc.) Extra / Note
github.com/ollama/ollama/api Pacchetto ufficiale all’interno del repository Ollama; utilizzato dal CLI ollama stesso Copertura completa mappata al REST; supporto streaming Considerato il client Go canonico; API specchio della documentazione.
LangChainGo (github.com/tmc/langchaingo/llms/ollama) Framework della comunità (LangChainGo) con modulo LLM Ollama Chat/Completion + streaming tramite astrazioni del framework Limitato (gestione modelli non obiettivo principale) Ottimo se desideri catene, strumenti, magazzini vettoriali in Go; meno di un SDK raw.
github.com/swdunlop/ollama-client Client della comunità Focalizzato su chat; buone esplorazioni di chiamata a strumenti Parziale Costruito per sperimentare con le chiamate a strumenti; non una superficie completa.
Altri SDK della comunità (es. ollamaclient, terze parti “go-ollama-sdk”) Comunità Varia Varia Qualità e copertura variano; valutare per repo.

Consiglio: Per produzione, preferisci github.com/ollama/ollama/api—è mantenuto con il progetto principale e specchia l’API REST.


Qwen3 & GPT-OSS su Ollama: pensiero vs non-pensiero (cosa sapere)

  • Modalità di pensiero in Ollama separa il “ragionamento” del modello dall’output finale quando abilitata. Ollama documenta un comportamento abilita/disabilita pensiero di prima classe su tutti i modelli supportati.
  • (https://www.glukhov.org/it/llm-performance/benchmarks/qwen3-30b-vs-gpt-oss-20b/ “Qwen3:30b vs GPT-OSS:20b: Dettagli tecnici, confronto di prestazioni e velocità”) supporta il commutatore dinamico: aggiungi /think o /no_think nei messaggi di sistema/utente per commutare i modi turno per turno; l’ultima istruzione vince.
  • GPT-OSS: gli utenti segnalano che disabilitare il pensiero (es. /set nothink o --think=false) può essere non affidabile su gpt-oss:20b; pianifica di filtrare/nascondere qualsiasi ragionamento che la tua interfaccia non dovrebbe mostrare.

Parte 1 — Chiamare Ollama tramite REST grezzo (Go, net/http)

Tipi condivisi

Per prima cosa, definiamo i tipi comuni e le funzioni helper che useremo nei nostri esempi:

package main

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

// ---- Tipi API Chat ----

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

type ChatRequest struct {
	Model    string        `json:"model"`
	Messages []ChatMessage `json:"messages"`
	// Alcuni server espongono il controllo del pensiero come flag booleano.
	// Anche se omesso, puoi comunque controllare Qwen3 tramite /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 quando il pensiero è attivo
	} `json:"message"`
	Done bool `json:"done"`
}

// ---- Tipi API 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"`           // testo finale per non-stream
	Thinking  string `json:"thinking,omitempty"` // presente quando il pensiero è attivo
	Done      bool   `json:"done"`
}

// ---- Funzioni helper ----

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 restituisce un puntatore a un valore booleano
func bptr(b bool) *bool { return &b }

Chat — Qwen3 con pensiero ON (e come disattivarlo)

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

	req := ChatRequest{
		Model:   "qwen3:8b-thinking", // qualsiasi tag :*-thinking che hai scaricato
		Think:   bptr(true),
		Stream:  bptr(false),
		Messages: []ChatMessage{
			{Role: "system", Content: "Sei un assistente preciso."},
			{Role: "user",   Content: "Spiega la ricorsione con un breve esempio in 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("🧠 pensiero:\n", out.Message.Thinking)
	fmt.Println("\n💬 risposta:\n", out.Message.Content)
	return nil
}

// Per disattivare il pensiero per il prossimo turno:
// (a) impostare Think=false, e/o
// (b) aggiungere "/no_think" al messaggio di sistema/utente più recente (Qwen3 soft switch).
// Qwen3 rispetta l'ultima istruzione /think o /no_think in chat multi-turn.
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: "Sei breve. /no_think"},
			{Role: "user",   Content: "Spiega la ricorsione in 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
	}
	// Aspettativa: pensiero vuoto; comunque gestire difensivamente.
	if out.Message.Thinking != "" {
		fmt.Println("🧠 pensiero (inaspettato):\n", out.Message.Thinking)
	}
	fmt.Println("\n💬 risposta:\n", out.Message.Content)
	return nil
}

(Il soft switch di Qwen3 /think e /no_think è documentato dalla squadra Qwen; l’ultima istruzione vince in chat multi-turn.)

Chat — GPT-OSS con pensiero (e un avvertimento)

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

	req := ChatRequest{
		Model:  "gpt-oss:20b",
		Think:  bptr(true),   // richiede ragionamento separato se supportato
		Stream: bptr(false),
		Messages: []ChatMessage{
			{Role: "user", Content: "Cos'è la programmazione dinamica? Spiega l'idea centrale."},
		},
	}

	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out ChatResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	// Nota conosciuta: disabilitare il pensiero potrebbe non sopprimere completamente il ragionamento su gpt-oss:20b.
	// Filtra sempre il pensiero nell'interfaccia utente se non si desidera mostrarlo.
	fmt.Println("🧠 pensiero:\n", out.Message.Thinking)
	fmt.Println("\n💬 risposta:\n", out.Message.Content)
	return nil
}

Gli utenti segnalano che disabilitare il pensiero su gpt-oss:20b (es. /set nothink o --think=false) potrebbe essere ignorato — pianifica il filtraggio client-side se necessario.

Genera — Qwen3 e GPT-OSS

func generateQwen3() error {
	endpoint := "http://localhost:11434/api/generate"
	req := GenerateRequest{
		Model:  "qwen3:4b-thinking",
		Prompt: "In 2–3 frasi, a cosa servono gli alberi B nei database?",
		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("🧠 pensiero:\n", out.Thinking)
	}
	fmt.Println("\n💬 risposta:\n", out.Response)
	return nil
}

func generateGptOss() error {
	endpoint := "http://localhost:11434/api/generate"
	req := GenerateRequest{
		Model:  "gpt-oss:20b",
		Prompt: "Spiega brevemente il backpropagation nei reti neurali.",
		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("🧠 pensiero:\n", out.Thinking)
	}
	fmt.Println("\n💬 risposta:\n", out.Response)
	return nil
}

Le forme REST e il comportamento dello streaming provengono direttamente dalla documentazione dell’API Ollama.


Parte 2 — Chiamare Ollama tramite l’SDK Go ufficiale (github.com/ollama/ollama/api)

Il pacchetto ufficiale espone un Client con metodi che corrispondono all’API REST. Il CLI Ollama stesso utilizza questo pacchetto per comunicare con il servizio, il che lo rende la scelta più sicura per la compatibilità.

Installazione

go get github.com/ollama/ollama/api

Chat — Qwen3 (pensiero ON / OFF)

package main

import (
	"context"
	"fmt"
	"log"

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

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

	req := &api.ChatRequest{
		Model: "qwen3:8b-thinking",
		// Molte build del server espongono il pensiero come flag a livello superiore;
		// in aggiunta, puoi controllare Qwen3 tramite /think o /no_think nei messaggi.
		Think: api.Ptr(thinking),
		Messages: []api.Message{
			{Role: "system", Content: "Sei un assistente preciso."},
			{Role: "user",   Content: "Spiega il merge sort con un breve frammento Go."},
		},
	}

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

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

func main() {
	ctx := context.Background()
	if err := chatWithQwen3Thinking(ctx, true); err != nil {
		log.Fatal(err)
	}
	// Esempio: non-pensiero
	if err := chatWithQwen3Thinking(ctx, false); err != nil {
		log.Fatal(err)
	}
}

Chat — GPT-OSS (gestire il ragionamento difensivamente)

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: "Cos'è la memoizzazione e quando è utile?"},
		},
	}
	var resp api.ChatResponse
	if err := client.Chat(ctx, req, &resp); err != nil {
		return err
	}
	// Se intendi nascondere il ragionamento, fallo qui indipendentemente dai flag.
	if resp.Message.Thinking != "" {
		fmt.Println("🧠 pensiero:\n", resp.Message.Thinking)
	}
	fmt.Println("\n💬 risposta:\n", resp.Message.Content)
	return nil
}

Genera — 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: "Riassumi il ruolo di un B-Tree nell'indicizzazione.",
		Think:  api.Ptr(true),
	}
	var resp api.GenerateResponse
	if err := client.Generate(ctx, req, &resp); err != nil {
		return err
	}
	if resp.Thinking != "" {
		fmt.Println("🧠 pensiero:\n", resp.Thinking)
	}
	fmt.Println("\n💬 risposta:\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: "Spiega il gradiente discenso in termini semplici.",
		Think:  api.Ptr(true),
	}
	var resp api.GenerateResponse
	if err := client.Generate(ctx, req, &resp); err != nil {
		return err
	}
	if resp.Thinking != "" {
		fmt.Println("🧠 pensiero:\n", resp.Thinking)
	}
	fmt.Println("\n💬 risposta:\n", resp.Response)
	return nil
}

La superficie del pacchetto ufficiale specchia le documentazioni REST e viene aggiornata insieme al progetto principale.


Risposte in streaming

Per le risposte in tempo reale, imposta Stream: bptr(true) nella tua richiesta. La risposta verrà consegnata come frammenti JSON separati da newline:

func streamChatExample() error {
	endpoint := "http://localhost:11434/api/chat"
	req := ChatRequest{
		Model:  "qwen3:8b-thinking",
		Think:  bptr(true),
		Stream: bptr(true), // Abilita lo streaming
		Messages: []ChatMessage{
			{Role: "user", Content: "Spiega l'algoritmo quicksort passo passo."},
		},
	}

	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
		}
		
		// Processa pensiero e contenuto man mano che arrivano
		if chunk.Message.Thinking != "" {
			fmt.Print(chunk.Message.Thinking)
		}
		fmt.Print(chunk.Message.Content)
		
		if chunk.Done {
			break
		}
	}
	return nil
}

Con l’SDK ufficiale, utilizza una funzione callback per gestire i frammenti dello 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: "Spiega gli alberi di ricerca 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
}

Lavorare con Qwen3 pensiero vs non-pensiero (guida pratica)

  • Due leve:

    1. Un flag booleano thinking supportato dalla funzionalità di pensiero di Ollama; e
    2. Il soft switch di Qwen3 /think e /no_think nel messaggio più recente di sistema/utente. L’ultima istruzione governa il prossimo turno(i).
  • Postura predefinita: non-pensiero per risposte rapide; passa a pensiero per compiti che richiedono ragionamento passo passo (matematica, pianificazione, debug, analisi complessa del codice).

  • Interfacce UI in streaming: quando il pensiero è abilitato, potresti vedere ragionamento e contenuto intercalati in frame in streaming — bufferizzali o renderizzali separatamente e fornisci agli utenti un “mostra ragionamento” toggle. (Vedi documentazione API per il formato dello streaming.)

  • Conversazioni a più turni: Qwen3 ricorda la modalità di pensiero dei turni precedenti. Se desideri commutare durante una conversazione, usa entrambi il flag e il comando soft-switch per affidabilità.

Note per GPT-OSS

  • Tratta il ragionamento come presente anche se hai provato a disabilitarlo; filtra sul client se la tua interfaccia utente non dovrebbe mostrarlo.
  • Per applicazioni di produzione che utilizzano GPT-OSS, implementa logica di filtraggio client-side che possa rilevare e rimuovere i pattern di ragionamento se necessario.
  • Testa la tua variante specifica del modello GPT-OSS in modo approfondito, poiché il comportamento può variare tra diverse quantizzazioni e versioni.

Linee guida per le migliori pratiche e suggerimenti per la produzione

Gestione degli errori e dei timeout

Implementa sempre una gestione corretta dei timeout e del recupero degli errori:

func robustChatRequest(ctx context.Context, model string, messages []api.Message) (*api.ChatResponse, error) {
	// Imposta un timeout ragionevole
	ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
	defer cancel()
	
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return nil, fmt.Errorf("creando client: %w", err)
	}
	
	req := &api.ChatRequest{
		Model:    model,
		Messages: messages,
		Options: map[string]interface{}{
			"temperature": 0.7,
			"num_ctx":     4096, // dimensione della finestra di contesto
		},
	}
	
	var resp api.ChatResponse
	if err := client.Chat(ctx, req, &resp); err != nil {
		return nil, fmt.Errorf("richiesta chat fallita: %w", err)
	}
	
	return &resp, nil
}

Pools di connessioni e riutilizzo

Riutilizza il client Ollama tra le richieste invece di crearne uno nuovo ogni volta:

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
}

Configurazione dell’ambiente

Utilizza le variabili d’ambiente per una distribuzione flessibile:

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

L’SDK ufficiale rispetta automaticamente OLLAMA_HOST tramite api.ClientFromEnvironment().

Monitoraggio e logging

Implementa il logging strutturato per i sistemi di produzione:

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
}

Conclusione

  • Per i progetti Go, github.com/ollama/ollama/api è la scelta più completa e pronta per la produzione. È mantenuto insieme al progetto principale Ollama, utilizzato dal CLI ufficiale e fornisce una copertura API completa con compatibilità garantita.

  • Per astrazioni di livello superiore, considera LangChainGo quando hai bisogno di catene, strumenti, magazzini vettoriali e pipeline RAG — pur sacrificando alcuni controlli di basso livello per la comodità.

  • Qwen3 ti dà un controllo pulito e affidabile sul modalità di pensiero con entrambi i flag e i commutatori a livello di messaggio (/think, /no_think), rendendolo ideale per applicazioni che necessitano sia di risposte rapide che di ragionamento approfondito.

  • Per GPT-OSS, pianifica sempre di sanificare l’output del ragionamento sul client quando necessario, poiché il flag di disabilitazione del pensiero potrebbe non essere sempre rispettato.

  • In produzione, implementa una corretta gestione degli errori, pooling delle connessioni, timeout e monitoraggio per costruire applicazioni Ollama-powered robuste.

La combinazione di una forte tipizzazione Go, eccellente supporto alla concorrenza e dell’API semplice di Ollama lo rende un stack ideale per costruire applicazioni AI — dalle semplici chatbot a complessi sistemi RAG.

Punti chiave

Ecco un riferimento rapido per scegliere il tuo approccio:

Caso d’uso Approccio consigliato Perché
Applicazione in produzione github.com/ollama/ollama/api Supporto ufficiale, copertura API completa, mantenuto con il progetto principale
Pipeline RAG/catene/strumenti LangChainGo Astrazioni di alto livello, integrazioni con magazzini vettoriali
Apprendimento/sperimentazione REST grezzo con net/http Trasparenza completa, nessuna dipendenza, educativo
Prototipazione rapida SDK ufficiale Equilibrio tra semplicità e potenza
Interfaccia utente chat in streaming SDK ufficiale con callback Supporto integrato per lo streaming, API pulita

Guida alla selezione dei modelli:

  • Qwen3: Migliore per applicazioni che richiedono controllo della modalità di pensiero, conversazioni multi-turn affidabili
  • GPT-OSS: Buone prestazioni ma richiede gestione difensiva dell’output del ragionamento
  • Altri modelli: Testa a fondo; il comportamento del pensiero varia per famiglia di modelli

Riferimenti e ulteriore lettura

Documentazione ufficiale

Alternative SDK Go

Risorse specifiche per i modelli

Argomenti correlati

Per un confronto più ampio tra Ollama e altre infrastrutture locali e cloud LLM, consulta la nostra LLM Hosting: Local, Self-Hosted & Cloud Infrastructure Compared.