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

OllamaをGoで統合する: SDKガイド、例、およびプロダクションでのベストプラクティス

目次

このガイドでは、利用可能な Go SDK for Ollama の包括的な概要を提供し、それらの機能セットを比較します。

Qwen3 および GPT-OSS モデルが Ollama 上にホストされている場合、raw REST API calls および official Go client を介して Go で呼び出すための実用的な例を紹介します。Qwen3 における思考モードおよび非思考モードの詳細な処理も含まれます。

go and ollama

なぜ Ollama + Go なのか?

Ollama は、通常 http://localhost:11434 で動作する小さな、実用的な HTTP API を公開しており、generate および chat のワークロード向けに設計されています。ストリーミングをサポートし、モデル管理機能も備えています。公式ドキュメントでは /api/generate および /api/chat のリクエスト/レスポンス構造およびストリーミングセマンティクスが詳細に説明されています。

Go は、HTTP に対する強力な標準ライブラリ、優れた JSON ハンドリング、ネイティブな並行性プリミティブ、およびコンパイル時にエラーを検出する静的型付けインターフェースを備えているため、Ollama クライアントの構築 に最適な選択肢です。Ollama が vLLM、Docker Model Runner、LocalAI およびクラウドプロバイダーとどのように比較されているか、それぞれを選択する際の条件については、LLM Hosting: Local, Self-Hosted & Cloud Infrastructure Compared を参照してください。

2025 年 10 月時点では、以下が Go SDK のオプション として最も考慮すべきものです。


Ollama 用の Go SDK — 何が利用可能か?

SDK / パッケージ ステータス & “owner” スコープ (Generate/Chat/Streaming) モデル管理 (pull/list/etc.) エクストラ / メモ
github.com/ollama/ollama/api Ollama リポジトリ内の公式パッケージ。ollama CLI 自身で使用 REST へのフルカバレッジ、ストリーミングをサポート はい 公式の Go クライアント と見なされ、API はドキュメントに密接に反映されている。
LangChainGo (github.com/tmc/langchaingo/llms/ollama) Ollama LLM モジュールを備えたコミュニティフレームワーク (LangChainGo) チャット/コンプリーション + フレームワーク抽象化を介したストリーミング 限定的 (モデル管理は主目的ではない) チェーン、ツール、ベクトルストアを含む Go が望ましい場合に適している。
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 のドキュメント は、サポートされているモデルにわたって 思考の有効/無効 という一等級の動作を記載しています。
  • (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: ユーザーは gpt-oss:20b において 思考の無効化 (例: /set nothink または --think=false) が信頼性に欠けると報告しています。UI が表示しない場合、フィルタリング/非表示 するように計画してください。

第1部 — raw 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"`
	// 一部のサーバーは思考制御をブールフラグとして公開しています。
	// 省略しても、Qwen3 は /think または /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"` // 思考が有効な場合に表示されます
	} `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"`           // 非ストリームの場合の最終テキスト
	Thinking  string `json:"thinking,omitempty"` // 思考が有効な場合に表示されます
	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 は boolean 値へのポインタを返します
func bptr(b bool) *bool { return &b }

チャット — Qwen3思考 ON (および OFF にする方法)

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

	req := ChatRequest{
		Model:   "qwen3:8b-thinking", // 抜き出した :*-thinking タグを持つ任意のモデル
		Think:   bptr(true),
		Stream:  bptr(false),
		Messages: []ChatMessage{
			{Role: "system", Content: "あなたは正確なアシスタントです。"},
			{Role: "user",   Content: "再帰を短い 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("🧠 思考:\n", out.Message.Thinking)
	fmt.Println("\n💬 回答:\n", out.Message.Content)
	return nil
}

// 次のターンで思考を OFF にするには:
// (a) Think=false を設定し、または
// (b) 最新の system/user メッセージに "/no_think" を追加します (Qwen3 のソフトスイッチ)。
// Qwen3 は、マルチターンチャットで最新の /think または /no_think 指令を優先します。
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: "あなたは簡潔です。/no_think"},
			{Role: "user",   Content: "再帰を1文で説明してください。"},
		},
	}

	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out ChatResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	// 思考が空であることを期待します; それでも防御的に処理します。
	if out.Message.Thinking != "" {
		fmt.Println("🧠 思考 (予期しない):\n", out.Message.Thinking)
	}
	fmt.Println("\n💬 回答:\n", out.Message.Content)
	return nil
}

(Qwen3 の /think および /no_think ソフトスイッチは Qwen チームによって文書化されており、マルチターンチャットでは 最新 の指示が優先されます。)

チャット — GPT-OSS と思考 (および注意点)

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

	req := ChatRequest{
		Model:  "gpt-oss:20b",
		Think:  bptr(true),   // サポートされている場合、分離された推論を要求します
		Stream: bptr(false),
		Messages: []ChatMessage{
			{Role: "user", Content: "動的プログラミングとは何か。コアアイデアを説明してください。"},
		},
	}

	raw, err := httpPostJSON(endpoint, req)
	if err != nil {
		return err
	}
	var out ChatResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return err
	}
	// 知られているクセ: gpt-oss:20b 上で思考を無効化しても、推論が完全に抑止されない場合があります。
	// UI に表示したくない場合は、常にクライアントサイドのフィルタリングを実施してください。
	fmt.Println("🧠 思考:\n", out.Message.Thinking)
	fmt.Println("\n💬 回答:\n", out.Message.Content)
	return nil
}

gpt-oss:20b において、思考の無効化 (例: /set nothink または --think=false) が無視される場合があります。必要に応じてクライアントサイドのフィルタリングを計画してください。

生成 — Qwen3 および GPT-OSS

func generateQwen3() error {
	endpoint := "http://localhost:11434/api/generate"
	req := GenerateRequest{
		Model:  "qwen3:4b-thinking",
		Prompt: "データベースで B-Tree が使用される目的を2~3文で説明してください。",
		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("🧠 思考:\n", out.Thinking)
	}
	fmt.Println("\n💬 回答:\n", out.Response)
	return nil
}

func generateGptOss() error {
	endpoint := "http://localhost:11434/api/generate"
	req := GenerateRequest{
		Model:  "gpt-oss:20b",
		Prompt: "ニューラルネットワークにおけるバックプロパゲーションを簡単に説明してください。",
		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("🧠 思考:\n", out.Thinking)
	}
	fmt.Println("\n💬 回答:\n", out.Response)
	return nil
}

REST の形状およびストリーミングの動作は、Ollama API リファレンスから直接取得されます。


第2部 — 公式の Go SDK (github.com/ollama/ollama/api) を介して Ollama を呼び出す

公式のパッケージは、REST API に対応するメソッドを持つ Client を公開しています。Ollama CLI 自身 はこのパッケージを使用してサービスにアクセスしており、互換性において最も安全な選択肢です。

インストール

go get github.com/ollama/ollama/api

チャット — Qwen3 (思考 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() // OLLAMA_HOST が設定されている場合に尊重します
	if err != nil {
		return err
	}

	req := &api.ChatRequest{
		Model: "qwen3:8b-thinking",
		// 複数のサーバー構築では、思考をトップレベルのフラグとして公開しています;
		// さらに、Qwen3 を /think または /no_think でメッセージで制御できます。
		Think: api.Ptr(thinking),
		Messages: []api.Message{
			{Role: "system", Content: "あなたは正確なアシスタントです。"},
			{Role: "user",   Content: "マージソートを短い Go のスニペットで説明してください。"},
		},
	}

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

	if resp.Message.Thinking != "" {
		fmt.Println("🧠 思考:\n", resp.Message.Thinking)
	}
	fmt.Println("\n💬 回答:\n", resp.Message.Content)
	return nil
}

func main() {
	ctx := context.Background()
	if err := chatWithQwen3Thinking(ctx, true); err != nil {
		log.Fatal(err)
	}
	// 例: 非思考
	if err := chatWithQwen3Thinking(ctx, false); err != nil {
		log.Fatal(err)
	}
}

チャット — GPT-OSS (推論を防御的に処理する)

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: "メモ化とは何か、いつ役立つのか?"},
		},
	}
	var resp api.ChatResponse
	if err := client.Chat(ctx, req, &resp); err != nil {
		return err
	}
	// 表示しない場合は、フラグに関係なくここですべてフィルタリングしてください。
	if resp.Message.Thinking != "" {
		fmt.Println("🧠 思考:\n", resp.Message.Thinking)
	}
	fmt.Println("\n💬 回答:\n", resp.Message.Content)
	return nil
}

生成 — 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: "B-Tree がインデクシングにおいて果たす役割を要約してください。",
		Think:  api.Ptr(true),
	}
	var resp api.GenerateResponse
	if err := client.Generate(ctx, req, &resp); err != nil {
		return err
	}
	if resp.Thinking != "" {
		fmt.Println("🧠 思考:\n", resp.Thinking)
	}
	fmt.Println("\n💬 回答:\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: "勾配降下法を簡単に説明してください。",
		Think:  api.Ptr(true),
	}
	var resp api.GenerateResponse
	if err := client.Generate(ctx, req, &resp); err != nil {
		return err
	}
	if resp.Thinking != "" {
		fmt.Println("🧠 思考:\n", resp.Thinking)
	}
	fmt.Println("\n💬 回答:\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), // ストリーミングを有効にする
		Messages: []ChatMessage{
			{Role: "user", Content: "クイックソートアルゴリズムをステップバイステップで説明してください。"},
		},
	}

	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
		}
		
		// 思考およびコンテンツが届くたびに処理
		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: "二分木検索を説明してください。"},
		},
	}
	
	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 を最新の system/user メッセージに含めること。最新の 指令が次のターンを支配します。
  • デフォルトの姿勢: 非思考 で迅速な回答; 思考 に切り替えて、ステップバイステップの推論が必要なタスク (数学、計画、デバッグ、複雑なコード分析) に使用してください。

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

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

GPT-OSS に関するノート

  • 思考を無効にした場合でも、推論が存在していると扱うべきです; UX に表示しない場合 は、クライアントサイドでフィルタリングしてください。
  • GPT-OSS を使用する本番アプリケーション では、必要に応じて推論パターンを検出・削除するクライアントサイドのフィルタリングロジックを実装してください。
  • 特定の GPT-OSS モデルバリアントを徹底的にテストしてください。動作は異なる量子化とバージョンによって異なる可能性があります。

ベストプラクティスと本番のヒント

エラーハンドリングとタイムアウト

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

func robustChatRequest(ctx context.Context, model string, messages []api.Message) (*api.ChatResponse, error) {
	// 適切なタイムアウトを設定
	ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
	defer cancel()
	
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return nil, fmt.Errorf("クライアントの作成: %w", err)
	}
	
	req := &api.ChatRequest{
		Model:    model,
		Messages: messages,
		Options: map[string]interface{}{
			"temperature": 0.7,
			"num_ctx":     4096, // コンテキストウィンドウサイズ
		},
	}
	
	var resp api.ChatResponse
	if err := client.Chat(ctx, req, &resp); err != nil {
		return nil, fmt.Errorf("チャットリクエストの失敗: %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 の包括的なカバレッジと保証された互換性を提供します。

  • より高いレベルの抽象化 を必要とする場合は、LangChainGo を考慮してください。チェーン、ツール、ベクトルストア、RAG パイプラインが必要な場合に適していますが、低レベルの制御を若干犠牲にします。

  • Qwen3 は、フラグとメッセージレベルのトグル (/think/no_think) を介して 思考モード にクリーンで信頼性の高い制御を提供するため、迅速な応答と深い推論が必要なアプリケーションに最適です。

  • GPT-OSS では、必要に応じて クライアントサイドで推論出力をクリーンアップ するように計画してください。思考無効化フラグが常に尊重されない可能性があります。

  • 本番環境 では、適切なエラーハンドリング、接続プーリング、タイムアウト、モニタリングを実装して、堅牢な Ollama パワードアプリケーションを構築してください。

Go の強力な型付け、優れた並行性サポート、Ollama の直感的な API は、単純なチャットボットから複雑な RAG システムまで、AI パワードアプリケーションの構築に理想的なスタックです。

キーの要点

選択方法の簡単なリファレンスです:

使用ケース 推奨されるアプローチ なぜ
本番アプリケーション github.com/ollama/ollama/api 公式サポート、完全な API カバレッジ、コアプロジェクトと連携してメンテナンス
RAG/チェーン/ツールパイプライン LangChainGo 高レベルの抽象化、ベクトルストアとの統合
学習/実験 net/http による raw REST 完全な透明性、依存関係なし、教育目的
クイックプロトタイピング 公式 SDK 簡単さと強力さのバランス
ストリーミングチャットUI 公式 SDK とコールバック 埋め込みストリーミングサポート、クリーンな API

モデル選択のガイドライン:

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

参考文献とさらなる読書

公式ドキュメント

Go SDK の代替

モデル固有のリソース

関連トピック

その他の有用なリンク

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