OllamaのAPIをGolangから使う方法|net/httpでチャット・ストリーミング・埋め込みをLinuxで実装する手順

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > ローカルLLM > OllamaのAPIをGolangから使う方法|net/httpでチャット・ストリーミング・埋め込みをLinuxで実装する手順
「PythonのOllamaライブラリは知っているが、自社の運用ツールはGoで書いているため統合方法が分からない」
「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システムの土台になる
・エラーハンドリングとタイムアウト設定を組み込んで本番向けの品質に仕上げる


OllamaのAPIをGolangから使う方法|net/httpでチャット・ストリーミング・埋め込みをLinuxで実装する手順

「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

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

`go mod init` でモジュールが初期化されます。外部ライブラリを追加しないので、go.sumファイルは生成されません。

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 3などに変更できます。
モデルの特性については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

取得したベクターをpgvectorやQdrantに格納すれば、Goで書いたRAGシステムの土台になります。
テキストのバッチ処理が必要な場合は、`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":[...]}

curlで `{"models":[...]}` が返るのにGoコードでエラーが出る場合は、Goコード内のURLが `http://localhost:11434` または `http://127.0.0.1:11434` になっているか確認してください。

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/chatstream:false/json.NewDecoder().Decode()で1回デコード
チャット(ストリーミング)POST /api/chatstream:true/bufio.Scannerで行ごとに読み取ってデコード
テキスト埋め込みPOST /api/embednomic-embed-textなどのEmbeddingモデルを指定
エラーハンドリング共通共通http.Clientにタイムアウト設定/StatusCode確認/Context組み込み

OllamaのAPI活用をハンズオンで体験する

OllamaのREST APIをGoや各種言語から呼び出す構成を、実際のLinux環境で試してみたい方も多いはずです。実機GPU環境で手を動かしながら習得したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。

>> ローカルAIマスターセミナーの詳細を確認する
ローカルLLMの構築・運用に関する関連記事もあわせて参考にしてください。

・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。