「GoからOllamaのREST APIを呼び出す方法が分からず、ストリーミング応答の処理をどう書けばよいか分からない」
そんな悩みを抱えるGoエンジニアやLinuxサーバー管理者は多いはずです。この記事では、GolangのネイティブHTTPクライアント(net/http・encoding/json)だけを使い、OllamaのAPIを呼び出してチャット・ストリーミング応答処理・テキスト埋め込みを実装する手順を解説します。
外部ライブラリへの依存なしで実装できるため、既存のGoプロジェクトにそのまま組み込めます。Ubuntu Server上にOllamaが構築済みであることを前提に進めます。まだ準備が済んでいない場合は、先にUbuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイドを参照してください。
この記事のポイント
・GoのネイティブHTTPクライアントだけでOllamaのチャットAPIを呼び出せる(外部ライブラリ不要)
・ストリーミング応答はbufio.Scannerで1行ずつ読み取ってJSONをデコードするのが定石
・Embedding取得はPOST /api/embedで実現し、Goで書いたRAGシステムの土台になる
・エラーハンドリングとタイムアウト設定を組み込んで本番向けの品質に仕上げる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
GoからOllamaのAPIを使う基本的な仕組み
OllamaはデフォルトでHTTP REST APIをlocalhost:11434で公開しています。同じサーバー上の任意のプロセスからAPIを呼べる設計です。
GoからOllamaを操作するときに使う主なエンドポイントは3つです。
・POST /api/chat:チャット形式の推論。会話履歴を配列で渡す
・POST /api/generate:シングルターンのシンプルな推論
・POST /api/embed:テキストをベクターに変換するEmbedding取得 GoからこれらのエンドポイントにHTTPリクエストを送る仕組みは、curlで手動実行するときと同じです。
リクエストボディをJSONにエンコードし、レスポンスをデコードするだけです。
PythonのようにOllama公式ライブラリは使わず、`net/http` と `encoding/json` だけで実装できます。 GoとOllamaを組み合わせる典型的なユースケースを挙げます。
・Linux運用ツールへのAI判断ロジックの組み込み
・ログ解析スクリプトへの自動要約機能の追加
・社内CLIツールへのチャット機能の埋め込み
いずれも、既存のGoコードベースに最小限の変更で追加できます。
Ubuntu ServerにGoをインストールする
Goがまだインストールされていない環境向けに、Ubuntu Serverへの導入手順を説明します。1. 最新のGoバイナリをダウンロードする
aptのパッケージは古いバージョンが配布されることがあるため、公式サイトからバイナリを直接取得します。実際の作業時は go.dev で最新バージョンを確認してからURLを変更してください。
# Goバイナリをダウンロードする(バージョンは実際の最新版に変更する) $ wget https://go.dev/dl/go1.22.4.linux-amd64.tar.gz # /usr/local に展開する(既存のGoディレクトリを削除してから展開する) $ sudo rm -rf /usr/local/go $ sudo tar -C /usr/local -xzf go1.22.4.linux-amd64.tar.gz
2. PATHを設定してgoコマンドを有効にする
# ~/.profileにPATHを追加する $ echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.profile # 現在のシェルに反映する $ source ~/.profile # バージョンを確認する $ go version go version go1.22.4 linux/amd64
3. 作業ディレクトリを作成してGoモジュールを初期化する
# プロジェクトディレクトリを作成する $ mkdir -p ~/ollama-go-client $ cd ~/ollama-go-client # Goモジュールを初期化する $ go mod init ollama-go-client go: creating new go.mod: module ollama-go-client
Golangでチャットリクエストを送る(非ストリーミング)
まず最もシンプルな形として、ストリーミングなしのチャットリクエストを実装します。`stream: false` を指定すると、推論が完了した時点でレスポンス全体がJSONで返ります。
短い質問への回答や、バッチ処理で複数ファイルを順に処理するケースに向いています。
1. チャットリクエストの構造体とHTTPクライアントを定義する
// chat_simple.go package main import ( "bytes" "encoding/json" "fmt" "log" "net/http" ) type ChatRequest struct { Model string `json:"model"` Messages []Message `json:"messages"` Stream bool `json:"stream"` } type Message struct { Role string `json:"role"` Content string `json:"content"` } type ChatResponse struct { Message Message `json:"message"` } func main() { req := ChatRequest{ Model: "llama3.3", Messages: []Message{ {Role: "user", Content: "LinuxのiノードをPATH: 30字以内で説明してください"}, }, Stream: false, } body, err := json.Marshal(req) if err != nil { log.Fatal("JSONエンコードに失敗しました: ", err) } resp, err := http.Post( "http://localhost:11434/api/chat", "application/json", bytes.NewBuffer(body), ) if err != nil { log.Fatal("APIリクエストに失敗しました: ", err) } defer resp.Body.Close() var chatResp ChatResponse if err := json.NewDecoder(resp.Body).Decode(&chatResp); err != nil { log.Fatal("レスポンスのデコードに失敗しました: ", err) } fmt.Println(chatResp.Message.Content) }
2. ファイルを実行して動作を確認する
# コンパイルして実行する $ go run chat_simple.go iノードはファイルのメタデータ(所有者・パーミッション・タイムスタンプ・ブロック位置)を管理するデータ構造です。 # ビルドして実行ファイルを生成する場合 $ go build -o chat_simple chat_simple.go $ ./chat_simple iノードはファイルのメタデータ(所有者・パーミッション・タイムスタンプ・ブロック位置)を管理するデータ構造です。
モデルの特性についてはLlama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイントを参照してください。
Golangでストリーミング応答を処理する
`stream: false` の場合、推論が完了するまでHTTPレスポンスが返ってきません。30秒以上かかるような長い推論では、プログラムが止まっているように見えます。
`stream: true` にすると、トークンが生成されるたびに即座にレスポンスが流れてくるため、CLIツールや対話型UIに適しています。 ストリーミング応答の各行はJSONオブジェクトになっています。
`done: false` の行には `message.content` にトークンが入り、`done: true` の行が最後のメタデータ行です。
`bufio.Scanner` で1行ずつ読み取り、各行をデコードするのが基本的な処理方法です。
1. ストリーミング対応のチャットを実装する
// chat_stream.go package main import ( "bufio" "bytes" "encoding/json" "fmt" "log" "net/http" "os" ) type ChatRequest struct { Model string `json:"model"` Messages []Message `json:"messages"` Stream bool `json:"stream"` } type Message struct { Role string `json:"role"` Content string `json:"content"` } type StreamResponse struct { Message Message `json:"message"` Done bool `json:"done"` } func main() { req := ChatRequest{ Model: "llama3.3", Messages: []Message{ {Role: "user", Content: "Linuxのプロセス管理で重要なコマンドを5つ教えてください"}, }, Stream: true, } body, err := json.Marshal(req) if err != nil { log.Fatal("JSONエンコードに失敗しました: ", err) } resp, err := http.Post( "http://localhost:11434/api/chat", "application/json", bytes.NewBuffer(body), ) if err != nil { log.Fatal("APIリクエストに失敗しました: ", err) } defer resp.Body.Close() scanner := bufio.NewScanner(resp.Body) for scanner.Scan() { line := scanner.Bytes() var streamResp StreamResponse if err := json.Unmarshal(line, &streamResp); err != nil { continue } fmt.Fprint(os.Stdout, streamResp.Message.Content) if streamResp.Done { fmt.Println() break } } if err := scanner.Err(); err != nil { log.Fatal("ストリームの読み取りに失敗しました: ", err) } }
2. 実行して出力がリアルタイムに流れることを確認する
$ go run chat_stream.go 1. ps:動作中のプロセスを一覧表示する 2. top:CPU・メモリ使用率をリアルタイムで確認する 3. kill:プロセスにシグナルを送信して停止する ...(トークンが順次表示される)
ターミナル上でChatGPTのような出力体験を再現できます。
長い応答が予想される場合は `scanner.Buffer(make([]byte, 1024*1024), 1024*1024)` でスキャナーのバッファを拡張してください。
Golangでテキスト埋め込み(Embedding)を取得する
OllamaのEmbedding APIを使うと、テキストを浮動小数点の配列(ベクター)に変換できます。類似テキスト検索や社内ドキュメントのRAGシステムをGoから構築するときに使います。
RAGシステムの設計については社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢も参考になります。 Embedding取得のエンドポイントは `POST /api/embed` です。
`model` にEmbedding専用モデル(nomic-embed-textなど)を指定して、`input` にテキストを渡します。
レスポンスには `embeddings` キーに二次元配列(バッチ処理も可能)が入っています。
1. Embedding取得の実装
// embed.go package main import ( "bytes" "encoding/json" "fmt" "log" "net/http" ) type EmbedRequest struct { Model string `json:"model"` Input string `json:"input"` } type EmbedResponse struct { Embeddings [][]float64 `json:"embeddings"` } func main() { req := EmbedRequest{ Model: "nomic-embed-text", Input: "Linuxのiノードとは何か", } body, err := json.Marshal(req) if err != nil { log.Fatal("JSONエンコードに失敗しました: ", err) } resp, err := http.Post( "http://localhost:11434/api/embed", "application/json", bytes.NewBuffer(body), ) if err != nil { log.Fatal("APIリクエストに失敗しました: ", err) } defer resp.Body.Close() var embedResp EmbedResponse if err := json.NewDecoder(resp.Body).Decode(&embedResp); err != nil { log.Fatal("レスポンスのデコードに失敗しました: ", err) } if len(embedResp.Embeddings) > 0 { vec := embedResp.Embeddings[0] fmt.Printf("次元数: %d\n", len(vec)) fmt.Printf("先頭3要素: %.6f, %.6f, %.6f\n", vec[0], vec[1], vec[2]) } }
2. Embeddingモデルを取得して実行する
# nomic-embed-textモデルを取得する $ ollama pull nomic-embed-text pulling manifest... pulling model... 100% # embed.goを実行する $ go run embed.go 次元数: 768 先頭3要素: 0.051234, -0.023456, 0.098765
テキストのバッチ処理が必要な場合は、`Input` フィールドを `[]string` に変更してリクエストを送れます。
エラーハンドリングと実務向けの改善
これまでのサンプルコードはシンプルさを優先していますが、実務で使うには改善が必要な点があります。最低限組み込んでおきたい3点を説明します。
1. タイムアウトを設定する
`http.DefaultClient` はデフォルトでタイムアウトがありません。長い推論でプロセスが永続的に止まらないよう、`http.Client` にタイムアウトを設定します。
import "time" // タイムアウト付きのHTTPクライアントを作る client := &http.Client{ Timeout: 120 * time.Second, } // PostではなくNewRequestとclient.Doを組み合わせる httpReq, err := http.NewRequest("POST", "http://localhost:11434/api/chat", bytes.NewBuffer(body)) if err != nil { log.Fatal("リクエストの作成に失敗しました: ", err) } httpReq.Header.Set("Content-Type", "application/json") resp, err := client.Do(httpReq) if err != nil { log.Fatal("APIリクエストに失敗しました: ", err) }
2. HTTPステータスコードを確認する
推論が失敗した場合、OllamaはHTTP 500などのエラーコードを返します。`resp.StatusCode` の確認をリクエスト直後に入れておくと、エラーの原因が明確になります。
import "io" // ステータスコードが200以外ならエラーの詳細を表示して終了する if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) log.Fatalf("APIエラー (ステータスコード: %d): %s", resp.StatusCode, string(bodyBytes)) }
3. コンテキストキャンセルを組み込む
Ctrl+Cで中断できるCLIツールにするには、`context.WithCancel` を使います。`http.NewRequestWithContext` にキャンセル可能なコンテキストを渡すと、シグナルを受け取った時点で推論を中断できます。
Goの `os/signal` パッケージで `SIGINT` と `SIGTERM` を捕捉して `cancel()` を呼び出す構成が定石です。
よくあるトラブルと対処法
GoからOllamaのAPIを呼び出すときに典型的に発生するトラブルを整理します。1. connection refusedエラーが発生する
Ollamaが起動していないか、ホスト名やポート番号が違う場合に発生します。まずcurlでAPIが応答するかを確認します。
# Ollamaのプロセス状態を確認する $ systemctl status ollama # curlでAPIが応答するか確認する $ curl http://localhost:11434/api/tags {"models":[...]}
2. ストリーミング応答のデコードに失敗する
非ストリーミングのつもりで `stream: true` を指定したまま `json.Decoder.Decode` を1回だけ実行すると、最初の行だけデコードして残りが切り捨てられた状態になります。`stream: true` の場合は必ず `bufio.Scanner` で全行を読み切る実装にしてください。
逆に `stream: false` のレスポンスを `bufio.Scanner` で読もうとすると1行で終わるため、`done: true` のループ終了条件が機能しません。ストリーミングの有無とデコード方法を対応させるのが重要です。
3. モデルが見つからない(404 Not Found)
指定したモデル名がOllamaに存在しない場合に発生します。# 利用可能なモデルの一覧を確認する $ ollama list NAME ID SIZE MODIFIED llama3.3:latest abc123def456 43 GB 3 days ago nomic-embed-text:latest xyz789aaa111 274 MB 1 week ago # 一覧にないモデルはpullする $ ollama pull llama3.3
`llama3.3` と `llama3.3:latest` は同じですが、`llama3.3:70b-instruct-q4_0` のように特定の量子化タグを指定した場合はその名前で指定します。
4. bufio.Scannerがデータを読み取れない(ErrTooLong)
`bufio.Scanner` のデフォルトバッファサイズ(64KB)を超える行が来ると `ErrTooLong` が発生します。長い応答が予想される場合は `Scanner` を作成した直後にバッファを拡張してください。
scanner := bufio.NewScanner(resp.Body) // 1MBまでのバッファに拡張する scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
まとめ
この記事では、GolangのネイティブHTTPクライアントを使ってOllamaのAPIを呼び出し、チャット・ストリーミング応答処理・Embeddingを実装する方法を解説しました。外部ライブラリへの依存なしで実装できるため、既存のGoプロジェクトにそのまま統合できます。
20年以上のLinux実務経験から言うと、Goのシンプルな並行処理モデルはOllamaへの同時リクエスト処理との相性がよく、CLIツールや内部サービスの実装言語として有力な選択肢です。
| 機能 | エンドポイント | 主なポイント |
|---|---|---|
| チャット(非ストリーミング) | POST /api/chat | stream:false/json.NewDecoder().Decode()で1回デコード |
| チャット(ストリーミング) | POST /api/chat | stream:true/bufio.Scannerで行ごとに読み取ってデコード |
| テキスト埋め込み | POST /api/embed | nomic-embed-textなどのEmbeddingモデルを指定 |
| エラーハンドリング共通 | 共通 | http.Clientにタイムアウト設定/StatusCode確認/Context組み込み |
OllamaのAPI活用をハンズオンで体験する
OllamaのREST APIをGoや各種言語から呼び出す構成を、実際のLinux環境で試してみたい方も多いはずです。実機GPU環境で手を動かしながら習得したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。
・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:OllamaでApache/NginxアクセスログをAI解析する方法|不審アクセスの自動検出と日次レポート生成をLinuxサーバーで実現する手順
- この記事の属するカテゴリ:ローカルLLMへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます