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

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

Indice

Questo documento fornisce un overview completo dei disponibili Go SDKs per Ollama 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—incluso un dettagliato trattamento 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 generate e chat, con supporto integrato per lo streaming e le capacità di gestione dei modelli. La documentazione ufficiale copre in modo approfondito le strutture di richiesta/risposta /api/generate e /api/chat e i semantici di 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 in fase di compilazione.

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


SDK Go per Ollama — cosa è disponibile?

SDK / Package Status & “owner” Scope (Generate/Chat/Streaming) Model mgmt (pull/list/etc.) Extras / Notes
github.com/ollama/ollama/api Pacchetto ufficiale all’interno del repository Ollama; utilizzato dal CLI ollama stesso Copertura completa mappata al REST; supporto per lo streaming Considerato il client Go canonico; l’API specchio della documentazione.
LangChainGo (github.com/tmc/langchaingo/llms/ollama) Framework comunitario (LangChainGo) con modulo LLM Ollama Chat/Completion + streaming tramite astrazioni del framework Limitato (gestione dei modelli non è lo scopo principale) Ottimo se desideri catene, strumenti, magazzini vettoriali in Go; meno di un SDK grezzo.
github.com/swdunlop/ollama-client Client comunitario Focus su chat; buone esperimenti di chiamata degli strumenti Parziale Costruito per sperimentare con la chiamata degli strumenti; non una superficie completa.
Altri SDK comunitari (es. ollamaclient, terze parti “go-ollama-sdk”) Comunità Varia Varia Qualità e copertura variano; valutare per repo.

Raccomandazione: 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/post/2025/10/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

Prima, 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) imposta Think=false, e/o
// (b) aggiungi "/no_think" al messaggio di sistema/utente più recente (switch soft di Qwen3).
// Qwen3 rispetta l'ultima istruzione /think o /no_think in chat a più turni.
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; tuttavia gestisci difensivamente.
	if out.Message.Thinking != "" {
		fmt.Println("🧠 pensiero (inaspettato):\n", out.Message.Thinking)
	}
	fmt.Println("\n💬 risposta:\n", out.Message.Content)
	return nil
}

(Il switch soft di Qwen3 /think e /no_think è documentato dalla squadra Qwen; l’ultima istruzione vince in chat a più turni.)

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),   // richiedi 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: disattivare il pensiero potrebbe non sopprimire completamente il ragionamento su gpt-oss:20b.
	// Filtra/separa 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 disattivare il pensiero su gpt-oss:20b (es. /set nothink o --think=false) potrebbe essere ignorato—pianifica il filtraggio client-side se necessario.

Generate — 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 la retropropagazione 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 il SDK Go ufficiale (github.com/ollama/ollama/api)

Il pacchetto ufficiale espone un Client con metodi che corrispondono all’API REST. L’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",
		// Molti server espongono il pensiero come flag a livello superiore;
		// inoltre, 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 di codice 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 (gestisci 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
}

Generate — 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 albero B 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 discenso del gradiente 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 delimitati 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 di quicksort passo dopo 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 mentre 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 comando soft switch di Qwen3 /think e /no_think nell’ultimo messaggio 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 dopo passo (matematica, pianificazione, debug, analisi complessa del codice).

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

  • Conversazioni a più turni: Qwen3 ricorda la modalità di pensiero dai turni precedenti. Se desideri commutare durante la conversazione, utilizza sia il flag che il comando soft-switch per affidabilità.

Note per GPT-OSS

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

Linee guida per le migliori pratiche e suggerimenti per la produzione

Gestione degli errori e timeout

Implementa sempre una corretta gestione 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
}

Piscina 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 flessibile distribuzione:

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 a livello superiore, considera LangChainGo quando hai bisogno di catene, strumenti, magazzini vettoriali e pipeline RAG—sebbene tu possa sacrificare un po’ di controllo a basso livello per la convenienza.

  • Qwen3 ti dà un controllo pulito e affidabile sulla 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 disattivazione del pensiero potrebbe non essere rispettato in modo coerente.

  • In produzione, implementa una corretta gestione degli errori, la piscina di connessioni, i timeout e il monitoraggio per costruire applicazioni robuste alimentate da Ollama.

La combinazione della forte tipizzazione di Go, del supporto eccellente alla concorrenza e dell’API semplice di Ollama lo rende un stack ideale per costruire applicazioni alimentate da AI—dai semplici chatbot ai sistemi RAG complessi.

Principali takeaway

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 completa dell’API, mantenuto con il progetto principale
Pipeline RAG/catene/strumenti LangChainGo Astrazioni a livello superiore, 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 allo streaming, API pulita

Guida alla selezione del modello:

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

Riferimenti e ulteriore lettura

Documentazione ufficiale

Alternative per il Go SDK

Risorse specifiche per i modelli

Argomenti correlati