Ollama用Goクライアント:SDK比較とQwen3/GPT-OSSの例

OllamaとGoの統合:SDKガイド、例、および本番環境でのベストプラクティス

目次

このガイドでは、利用可能な Ollama 用の Go SDK の概要を網羅的に示し、その機能セットを比較します。

ここでは、Ollama上でホストされている Qwen3 および GPT-OSS モデルを呼び出すための実践的なGoの例を探ります。生のREST API呼び出し と 公式Goクライアント の両方の方法を含め、Qwen3における思考モード(thinking)と非思考モード(non-thinking)の詳細な処理について解説します。

go and ollama

なぜ Ollama + Go なのか?

Ollamaは 小型で実用的なHTTP API(通常は http://localhost:11434 で動作)を公開しており、generate(テキスト生成)と chat(チャット)ワークロード向けに設計されています。ストリーミング対応やモデル管理機能が組み込まれています。公式ドキュメントでは、/api/generate と /api/chat のリクエスト/レスポンス構造やストリーミングのセマンティクスが十分にカバーされています。

Go は、HTTPに対する強力な標準ライブラリサポート、優れたJSON処理、ネイティブの並行処理プリミティブ、そしてコンパイル時にエラーを検出する静的型付きインターフェースを持っているため、Ollamaクライアントの構築 に最適な選択です。 OllamaがvLLM、Docker Model Runner、LocalAI、クラウドプロバイダーと比較してどのように位置づけられ、いつそれぞれを選択すべきかを把握するには、LLMホスティング: ローカル、セルフホスト、クラウドインフラストラクチャの比較 をご覧ください。

2025年10月時点では、最も検討する可能性が高い Go SDKの選択肢 は以下の通りです。


Ollama 用の Go SDK — 何がありますか?

SDK / パッケージ ステータスおよび「所有者」 スコープ (Generate/Chat/Streaming) モデル管理 (pull/list/その他) 追加情報 / 備考
github.com/ollama/ollama/api Ollamaリポジトリ内の 公式 パッケージ。ollama CLI自体で使用 RESTにマッピングされた 完全 なカバレッジ。ストリーミング対応 はい 正統な Goクライアントと見なされています。APIはドキュメントに密接に対応しています。
LangChainGo (github.com/tmc/langchaingo/llms/ollama) Ollama LLMモジュールを備えたコミュニティフレームワーク (LangChainGo) フレームワークの抽象化による Chat/Completion + ストリーミング 限定的 (モデル管理は主要目標ではない) Goでチェーン、ツール、ベクトルストアを使いたい場合に最適。生のSDKというよりは高レベルな抽象化です。
github.com/swdunlop/ollama-client コミュニティ製クライアント チャットに焦点。優れた ツール呼び出し の実験 部分的 ツール呼び出しの実験用に構築。1:1で全機能をカバーしていません。
その他のコミュニティ製SDK (例: ollamaclient、サードパーティ製の “go-ollama-sdk`) コミュニティ 変動 変動 品質とカバレッジはリポジトリにより異なります。各リポジトリを個別に評価してください。

推奨事項: プロダクション環境では、github.com/ollama/ollama/api を優先してください。コアプロジェクトと並行してメンテンナンスが行われており、REST APIをミラーリングしています。


Ollama での Qwen3 & GPT-OSS: 思考 vs 非思考 (知るべきこと)

  • Ollamaにおける思考モード は、有効化されている場合、モデルの「推論」を最終的な出力から分離します。弊社の Ollama チートシート では、対応するモデル全体での 思考の有効化/無効化 フラグ (--think, --hidethinking) が記載されています。
  • (https://www.glukhov.org/ja/llm-performance/benchmarks/qwen3-30b-vs-gpt-oss-20b/ “Qwen3:30b vs GPT-OSS:20b: 技術的な詳細、パフォーマンスおよび速度の比較”) は動的な切り替えをサポートしています:システム/ユーザーメッセージに /think または /no_think を追加することで、ターンごとにモードを切り替えることができます。最新 の指示が優先されます。
  • GPT-OSS: ユーザーは、思考を 無効化 しても(例: /set nothink または --think=false)、gpt-oss:20b では信頼できない場合があると報告しています。UIに表示すべきでない推論部分を フィルタリング/非表示 にする準備をしてください。

Part 1 — 生のREST 経由で Ollama を呼び出す (Go, net/http)

共通タイプ

まず、例全体で使用する共通のタイプとヘルパー関数を定義しましょう:

package main

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

// ---- Chat API Types ----

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

type ChatRequest struct {
	Model    string        `json:"model"`
	Messages []ChatMessage `json:"messages"`
	// Some servers expose thinking control as a boolean flag.
	// Even if omitted, you can still control Qwen3 via /think or /no_think tags.
	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"` // present when thinking is on
	} `json:"message"`
	Done bool `json:"done"`
}

// ---- Generate API Types ----

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"`           // final text for non-stream
	Thinking  string `json:"thinking,omitempty"` // present when thinking is on
	Done      bool   `json:"done"`
}

// ---- Helper Functions ----

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 returns a pointer to a boolean value
func bptr(b bool) *bool { return &b }

Chat — Qwen3 with thinking ON (and how to turn it OFF)

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

	req := ChatRequest{
		Model:   "qwen3:8b-thinking", // any :*-thinking tag you have pulled
		Think:   bptr(true),
		Stream:  bptr(false),
		Messages: []ChatMessage{
			{Role: "system", Content: "You are a precise assistant."},
			{Role: "user",   Content: "Explain recursion with a short Go example."},
		},
	}

	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("🧠 thinking:\n", out.Message.Thinking)
	fmt.Println("\n💬 answer:\n", out.Message.Content)
	return nil
}

// Turn thinking OFF for the next turn by:
// (a) setting Think=false, and/or
// (b) adding "/no_think" to the most-recent system/user message (Qwen3 soft switch).
// Qwen3 honors the latest /think or /no_think instruction in multi-turn chats.
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: "You are brief. /no_think"},
			{Role: "user",   Content: "Explain recursion in one sentence."},
		},
	}

	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out ChatResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	// Expect thinking to be empty; still handle defensively.
	if out.Message.Thinking != "" {
		fmt.Println("🧠 thinking (unexpected):\n", out.Message.Thinking)
	}
	fmt.Println("\n💬 answer:\n", out.Message.Content)
	return nil
}

(Qwen3の /think と /no_think ソフトスイッチはQwenチームによってドキュメント化されています。マルチターンチャットでは、最後 の指示が優先されます。)

Chat — GPT-OSS with thinking (and a caveat)

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

	req := ChatRequest{
		Model:  "gpt-oss:20b",
		Think:  bptr(true),   // request separated reasoning if supported
		Stream: bptr(false),
		Messages: []ChatMessage{
			{Role: "user", Content: "What is dynamic programming? Explain the core idea."},
		},
	}

	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out ChatResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	// Known quirk: disabling thinking may not fully suppress reasoning on gpt-oss:20b.
	// Always filter/hide thinking in UI if you don't want to surface it.
	fmt.Println("🧠 thinking:\n", out.Message.Thinking)
	fmt.Println("\n💬 answer:\n", out.Message.Content)
	return nil
}

ユーザーは、gpt-oss:20b での思考の 無効化(例: /set nothink または --think=false)が無視されることがあると報告しています—必要に応じてクライアント側のフィルタリングを計画してください。

Generate — Qwen3 and GPT-OSS

func generateQwen3() error {
	endpoint := "http://localhost:11434/api/generate"
	req := GenerateRequest{
		Model:  "qwen3:4b-thinking",
		Prompt: "In 2–3 sentences, what are B-Trees used for in databases?",
		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("🧠 thinking:\n", out.Thinking)
	}
	fmt.Println("\n💬 answer:\n", out.Response)
	return nil
}

func generateGptOss() error {
	endpoint := "http://localhost:11434/api/generate"
	req := GenerateRequest{
		Model:  "gpt-oss:20b",
		Prompt: "Briefly explain backpropagation in neural networks.",
		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("🧠 thinking:\n", out.Thinking)
	}
	fmt.Println("\n💬 answer:\n", out.Response)
	return nil
}

RESTの構造やストリーミングの動作は、Ollama APIリファレンスに直接基づいています。


Part 2 — 公式 Go SDK (github.com/ollama/ollama/api) 経由で Ollama を呼び出す

公式パッケージは、REST APIに対応するメソッドを備えた Client を公開しています。Ollama CLI自体 がこのパッケージを使用してサービスと通信するため、互換性を確保するには最も安全な選択です。

インストール

go get github.com/ollama/ollama/api

Chat — Qwen3 (thinking 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() // honors OLLAMA_HOST if set
	if err != nil {
		return err
	}

	req := &api.ChatRequest{
		Model: "qwen3:8b-thinking",
		// Many server builds expose thinking as a top-level flag;
		// additionally, you can control Qwen3 via /think or /no_think in messages.
		Think: api.Ptr(thinking),
		Messages: []api.Message{
			{Role: "system", Content: "You are a precise assistant."},
			{Role: "user",   Content: "Explain merge sort with a short Go snippet."},
		},
	}

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

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

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

Chat — GPT-OSS (handle reasoning defensively)

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: "What is memoization and when is it useful?"},
		},
	}
	var resp api.ChatResponse
	if err := client.Chat(ctx, req, &resp); err != nil {
		return err
	}
	// If you intend to hide reasoning, do it here regardless of flags.
	if resp.Message.Thinking != "" {
		fmt.Println("🧠 thinking:\n", resp.Message.Thinking)
	}
	fmt.Println("\n💬 answer:\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: "Summarize the role of a B-Tree in indexing.",
		Think:  api.Ptr(true),
	}
	var resp api.GenerateResponse
	if err := client.Generate(ctx, req, &resp); err != nil {
		return err
	}
	if resp.Thinking != "" {
		fmt.Println("🧠 thinking:\n", resp.Thinking)
	}
	fmt.Println("\n💬 answer:\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: "Explain gradient descent in simple terms.",
		Think:  api.Ptr(true),
	}
	var resp api.GenerateResponse
	if err := client.Generate(ctx, req, &resp); err != nil {
		return err
	}
	if resp.Thinking != "" {
		fmt.Println("🧠 thinking:\n", resp.Thinking)
	}
	fmt.Println("\n💬 answer:\n", resp.Response)
	return nil
}

公式パッケージのインターフェースはRESTドキュメントをミラーリングしており、コアプロジェクトと並行して更新されます。


ストリーミング応答

リアルタイムストリーミングを行うには、リクエストで Stream: bptr(true) を設定します。応答は、行区切りJSONチャンクとして配信されます:

func streamChatExample() error {
	endpoint := "http://localhost:11434/api/chat"
	req := ChatRequest{
		Model:  "qwen3:8b-thinking",
		Think:  bptr(true),
		Stream: bptr(true), // Enable streaming
		Messages: []ChatMessage{
			{Role: "user", Content: "Explain quicksort algorithm step by step."},
		},
	}

	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
		}
		
		// Process thinking and content as they arrive
		if chunk.Message.Thinking != "" {
			fmt.Print(chunk.Message.Thinking)
		}
		fmt.Print(chunk.Message.Content)
		
		if chunk.Done {
			break
		}
	}
	return nil
}

公式SDKでは、ストリーミングチャンクを処理するためにコールバック関数を使用します:

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: "Explain binary search trees."},
		},
	}
	
	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
}

Qwen3の思考 vs 非思考 との التعامل (実践的なガイダンス)

  • 2つのレバー:

    1. Ollamaの思考機能がサポートするブール型の thinking フラグ、そして
    2. 最新のシステム/ユーザーメッセージ内のQwen3の ソフトスイッチ コマンド /think と /no_think。最新 の指示が次のターンを支配します。
  • デフォルトの姿勢: 素早い返信には 非思考、ステップバイステップの推論を必要とするタスク(数学、計画、デバッグ、複雑なコード分析)には 思考 に切り替えます。

  • ストリーミングUI: 思考が有効な場合、ストリームされたフレーム内で推論とコンテンツが交互に現れる可能性があります。バッファリングするか個別にレンダリングし、ユーザーに「推論を表示する」トグルを提供してください。(ストリーミング形式についてはAPIドキュメントを参照してください。)

  • マルチターン会話: Qwen3は以前のターンからの思考モードを記憶しています。会話の途中で切り替える場合は、信頼性のためにフラグとソフトスイッチコマンドの両方を使用してください。

GPT-OSS への注意事項

  • 無効化を試みたとしても、推論が存在するとみなしてください。UXで表示すべきでない場合は、クライアント側でフィルタリング してください。
  • GPT-OSSを使用するプロダクションアプリケーションでは、必要に応じて推論パターンを検出し取り除くことができるクライアント側フィルタリングロジックを実装してください。
  • 行動は異なる量子化やバージョン間で異なり得るため、特定のGPT-OSSモデルバリアントを徹底的にテストしてください。

ベストプラクティスとプロダクション向けヒント

エラー処理とタイムアウト

常に適切なタイムアウト処理とエラーリカバリーを実装してください:

func robustChatRequest(ctx context.Context, model string, messages []api.Message) (*api.ChatResponse, error) {
	// Set a reasonable timeout
	ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
	defer cancel()
	
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return nil, fmt.Errorf("creating client: %w", err)
	}
	
	req := &api.ChatRequest{
		Model:    model,
		Messages: messages,
		Options: map[string]interface{}{
			"temperature": 0.7,
			"num_ctx":     4096, // context window size
		},
	}
	
	var resp api.ChatResponse
	if err := client.Chat(ctx, req, &resp); err != nil {
		return nil, fmt.Errorf("chat request failed: %w", err)
	}
	
	return &resp, nil
}

接続プーリングと再利用

各リクエストごとに新しいクライアントを作成するのではなく、Ollamaクライアントをリクエスト間で再利用してください:

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
}

環境設定

柔軟なデプロイメントのために環境変数を使用してください:

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

公式SDKは、api.ClientFromEnvironment() を介して OLLAMA_HOST を自動的に尊重します。

モニタリングとログ出力

プロダクションシステムには、構造化ログを実装してください:

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
}

結論

  • Goプロジェクト にとって、github.com/ollama/ollama/api は最も包括的でプロダクションレディな選択です。Ollamaコアプロジェクトと並行してメンテンナンスされており、公式CLIで使用されており、保証された互換性を持つ包括的なAPIカバレッジを提供します。

  • 高レベルな抽象化 が必要な場合は、チェーン、ツール、ベクトルストア、および RAGパイプライン が必要な場合に LangChainGo を検討してください。ただし、利便性の代わりに一部の低レベル制御を犠牲にすることになります。

  • Qwen3 は、フラグとメッセージレベルのトグル (/think, /no_think) の両方によって、思考モード をクリーンかつ信頼可能に制御できるため、高速な応答と深い推論の両方を必要とするアプリケーションに最適です。

  • GPT-OSS の場合、思考無効化フラグが一貫して尊重されない可能性があるため、必要に応じてクライアント側で 推論出力のサニタイズ を常に計画してください。

  • プロダクション環境 においては、堅牢なOllama搭載アプリケーションを構築するために、適切なエラー処理、接続プーリング、タイムアウト、モニタリングを実装してください。

Goの強い型付け、優れた並行処理サポート、Ollamaの素直なAPIの組み合わせは、シンプルなチャットボットから 複雑なRAGシステム まで、AI搭載アプリケーションの構築のための理想的なスタックです。

重要なポイント

アプローチを選択するためのクイックリファレンスです:

使用ケース 推奨アプローチ 理由
プロダクションアプリケーション github.com/ollama/ollama/api 公式サポート、完全なAPIカバレッジ、コアプロジェクトと並行メンテンナンス
RAG/チェーン/ツール パイプライン LangChainGo 高レベルな抽象化、ベクトルストアとの統合
学習/実験 net/http による生のREST 完全な透明性、依存関係なし、教育的
クイックプロトタイピング 公式SDK シンプルさとパワーのバランス
ストリーミングチャットUI コールバック付き公式SDK ビルトインのストリーミングサポート、クリーンなAPI

モデル選択のガイダンス:

  • Qwen3: 制御可能な思考モード、信頼性の高いマルチターン会話が必要なアプリケーションに最適
  • GPT-OSS: 強力なパフォーマンスだが、思考/推論出力の防御的処理が必要
  • その他のモデル: 徹底的にテストしてください。思考の行動はモデルファミリーによって異なります

参考文献および追加情報

公式ドキュメント

Go SDK の代替案

モデル固有のリソース

関連トピック

その他の役立つリンク

Ollamaと他のローカルおよびクラウドLLMインフラストラクチャのより広範な比較については、LLMホスティング: ローカル、セルフホスト、クラウドインフラストラクチャの比較 をチェックしてください。

購読する

システム、インフラ、AIエンジニアリングの新記事をお届けします。