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.
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.
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 | Sì | 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 sugpt-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:
- Un flag booleano
thinking
supportato dalla funzionalità di pensiero di Ollama; e - Il comando soft switch di Qwen3
/think
e/no_think
nell’ultimo messaggio di sistema/utente. L’ultima istruzione governa il prossimo turno(i).
- Un flag booleano
-
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
- Ollama API reference — Documentazione completa dell’API REST
- Official Ollama Go package — Documentazione del SDK Go
- Ollama GitHub repository — Codice sorgente e problemi
Alternative per il Go SDK
- LangChainGo Ollama integration — Per applicazioni basate su catena
- swdunlop/ollama-client — Client comunitario con chiamata a strumenti
- xyproto/ollamaclient — Un’altra opzione comunitaria
Risorse specifiche per i modelli
- Qwen documentation — Informazioni ufficiali sul modello Qwen
- GPT-OSS information — Dettagli sul modello GPT-OSS
Argomenti correlati
- Costruire applicazioni RAG con Go — Esempi di LangChainGo
- Go context package — Essenziale per i timeout e l’annullamento
- Best practice per il client HTTP in Go — Documentazione della libreria standard
Altri link utili
- Installare e configurare Ollama
- Ollama cheatsheet
- Go Cheatsheet
- Come Ollama gestisce le richieste parallele
- Riordinare documenti testuali con Ollama e Qwen3 Embedding model - in Go
- Confronto tra ORMs per PostgreSQL in Go: GORM vs Ent vs Bun vs sqlc
- Limitare gli LLM con Output Strutturato: Ollama, Qwen3 & Python o Go
- Utilizzare Ollama nel codice Python
- Confronto tra LLM: Qwen3:30b vs GPT-OSS:20b
- Problemi di Output Strutturato in Ollama GPT-OSS