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

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

目次

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

Ollama 上でホストされている Qwen3 および GPT-OSS モデルを呼び出すための実用的な Go の例を、raw REST API 呼び出しおよび 公式の Go クライアントの両方を通じて探求します。Qwen3 の思考モードおよび非思考モードの詳細な処理も含まれます。

go and ollama

なぜ Ollama + Go なのか?

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

Go は、HTTP に対する強力な標準ライブラリサポート、優れた JSON ハンドリング、ネイティブの並行性プリミティブ、コンパイル時にエラーを検出する静的型付けインターフェースにより、Ollama クライアントの構築 に最適な選択肢です。

2025年10月時点では、以下が Go SDK のオプション です。


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

SDK / パッケージ ステータス & “所有者” スコープ (Generate/Chat/Streaming) モデル管理 (pull/list/etc.) その他の注意点
github.com/ollama/ollama/api 公式 パッケージ; Ollama リポジトリ内にあり、ollama CLI 自身で使用されている 完全 なカバレッジ; REST にマッピング; ストリーミングがサポートされている はい 公式の Go クライアントと見なされる; API はドキュメントに近い。
LangChainGo (github.com/tmc/langchaingo/llms/ollama) コミュニティフレームワーク (LangChainGo) に Ollama LLM モジュールが含まれている チャット/コンプリーション + ストリーミングをフレームワーク抽象化を通じて 限定的 (モデル管理は主な目的ではない) チェーン、ツール、ベクトルストアを 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 ドキュメント は、サポートされているモデル全体にわたって 思考の有効/無効 の一等級の動作を記載しています。
  • (https://www.glukhov.org/ja/post/2025/10/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 に表示すべきでない場合は フィルタリング/非表示 するように計画しています。

第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 はブール値へのポインタを返します
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) 最新のシステム/ユーザーのメッセージに "/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: "再帰を一文で説明してください。"},
		},
	}

	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: "2~3文で、データベースで B-Tree が使用される目的を説明してください。",
		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 が最新のシステム/ユーザーのメッセージに含まれています。最新の 指令が次のターン(s)を支配します。
  • デフォルトの姿勢: 非思考 で迅速な返答; 思考 に昇格してステップバイステップの推論が必要なタスク (数学、計画、デバッグ、複雑なコード分析) に。

  • ストリーミング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 クライアントを作成する代わりに、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 の組み合わせは、AI パワードのアプリケーションを構築するための理想的なスタックです—単純なチャットボットから複雑な RAG システムまで。

主なポイント

選択肢を選ぶ際のクイックリファレンスです:

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

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

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

参考文献 & 補足資料

公式ドキュメント

Go SDK の代替手段

モデル固有のリソース

関連トピック

その他の役に立つリンク