OllamaでドキュメントをローカルLLMバッチ処理する方法|シェルスクリプトとPythonで社内文書・ログを自動要約する手順

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)ローカルLLM > OllamaでドキュメントをローカルLLMバッチ処理する方法|シェルスクリプトとPythonで社内文書・ログを自動要約する手順
「社内の議事録や日報をAIで自動要約させたいが、OpenAIのAPIに送るのはセキュリティポリシー上できない」
「サーバーのアクセスログや障害レポートが大量にあり、毎日手作業で目視確認するのが限界になってきた」

そんな悩みを抱えるインフラエンジニアや情報システム担当者は多いはずです。
この記事では、OllamaのローカルLLMで複数のドキュメントをバッチ処理する方法を、curlによる基本確認からシェルスクリプト・Pythonの並列実装まで段階的に解説します。
機密情報を外部に一切送らず、社内ネットワーク内で完結するLLM自動処理パイプラインを構築できます。

この記事のポイント

・OllamaのREST APIで stream: false を指定すれば、1回のcurl呼び出しで要約テキストを取得できる
・シェルスクリプトのforループとjqで複数テキストファイルをまとめて安全にバッチ処理できる
・PythonのThreadPoolExecutorで並列実行し、大量ドキュメントを高速に処理できる
・タイムアウト・リトライ設計と失敗ファイルのログ記録で本番バッチを安定稼働させる方法も解説する


OllamaでドキュメントをローカルLLMバッチ処理する方法|シェルスクリプトとPythonで社内文書・ログを自動要約する手順

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

ローカルLLMのバッチ処理が必要になる業務シナリオ

ローカルLLMを使ったバッチ処理が実際の現場でどういう場面で求められるか、整理しておきたい。

最も多いのは「機密文書の自動要約」だ。議事録・日報・障害レポート・コードレビューコメントのような社内文書を、クラウドAIに送らずに要約・分類したい、という要件は多くの企業で出てくる。知人のインフラエンジニアが所属する製造業の現場では、毎日300件以上の設備ログをOllamaに要約させて異常兆候を抽出するパイプラインを構築し、担当者の確認作業を8割削減できたという話を聞いた。

次に多いのは「繰り返しテキスト処理の自動化」だ。
カスタマーサポートの問い合わせメールをカテゴリ分類する、技術ドキュメントを英語→日本語に翻訳する、コードのdiffからchangelogを自動生成する、といった定型化できるテキスト処理は全てバッチLLMの候補になる。

社内でのChatGPT利用制限や情報漏洩リスクへの対応策としてローカルLLMを選択した組織では、次のステップとして「既存ワークフローへの組み込み」が求められる。バッチ処理はその中核になる技術だ。

この記事では「毎日決まったディレクトリにテキストファイルが溜まり、LLMで処理して結果を保存する」という最もシンプルなパターンを軸に進める。

前提環境の確認とOllamaのAPI動作チェック

バッチスクリプトを書く前に、まずOllamaが正常に動作しているかを確認する。

Ubuntu ServerへのOllamaインストールと初期設定が完了していること、およびsystemdサービスとして常時起動している状態を前提とする。以下のcurlコマンドで稼働確認を行う。

# Ollamaの稼働確認(インストール済みモデル一覧を取得) $ curl -s http://localhost:11434/api/tags | jq '.models[].name' # 出力例 "llama3.3:70b-instruct-q4_0" "mistral:7b-instruct-q8_0" "gemma3:9b-instruct-q8_0"

モデルが一覧に表示されれば準備完了だ。
バッチ処理では応答速度が重要なので、スクリプトを本格運用する前に単発で要約の品質と速度を確認しておく。以下のコマンドで動作を確かめる。

# stream: false で1回のレスポンスとして受け取る(バッチ処理の基本) $ curl -s http://localhost:11434/api/generate \ -H "Content-Type: application/json" \ -d '{ "model": "mistral:7b-instruct-q8_0", "prompt": "以下のテキストを200字以内で要約してください。\n\n障害報告:2026年7月16日午前3時、Webサーバーのメモリ使用率が95%を超え、レスポンスが遅延した。原因はバッチジョブの多重起動だった。", "stream": false, "options": {"num_predict": 400, "temperature": 0.3} }' | jq -r '.response' # 出力例 2026年7月16日午前3時、バッチジョブの多重起動によりWebサーバーメモリが95%超に達し、レスポンス遅延が発生した。

`stream: false` がバッチ処理の基本だ。これを指定しないとSSE形式でチャンクが返ってきてシェルスクリプトでの扱いが複雑になる。
`temperature: 0.3` は出力の揺れを抑えるための設定で、要約・分類タスクは創作タスクより低い値にするのが鉄則だ。

モデル選定についてはOllama対応モデルの比較と使い分けで詳しく解説している。バッチ処理では速度重視でq4_0量子化モデルを選ぶのが現実的だ。

シェルスクリプトで複数ファイルをループ処理する手順

最も手軽なのはBashのforループだ。ディレクトリ内のテキストファイルを1件ずつ読み込み、Ollamaに要約させて別ディレクトリに保存する。

1. ディレクトリ構造の準備

入力ファイルと出力ファイルを分けて管理する。

$ mkdir -p /opt/llm-batch/input $ mkdir -p /opt/llm-batch/output $ ls /opt/llm-batch/input/ report_20260715.txt report_20260714.txt report_20260713.txt

2. バッチ処理シェルスクリプトの作成

以下のスクリプトを `/opt/llm-batch/summarize.sh` として保存する。

#!/bin/bash INPUT_DIR="/opt/llm-batch/input" OUTPUT_DIR="/opt/llm-batch/output" MODEL="mistral:7b-instruct-q8_0" OLLAMA_URL="http://localhost:11434/api/generate" for filepath in "$INPUT_DIR"/*.txt; do filename=$(basename "$filepath" .txt) outfile="$OUTPUT_DIR/${filename}_summary.txt" content=$(cat "$filepath") echo "[$(date '+%H:%M:%S')] 処理中: $filename" # jq -n でJSONを安全に組み立て(改行・特殊文字を自動エスケープ) result=$(curl -s "$OLLAMA_URL" \ -H "Content-Type: application/json" \ -d "$(jq -n \ --arg model "$MODEL" \ --arg prompt "以下のテキストを200字以内で要約してください。\n\n${content}" \ '{model: $model, prompt: $prompt, stream: false, options: {num_predict: 400, temperature: 0.3}}' )" | jq -r '.response') echo "$result" > "$outfile" echo "[$(date '+%H:%M:%S')] 完了: $outfile" done echo "全ファイルの処理が完了しました。"

3. スクリプトを実行して動作確認する

$ chmod +x /opt/llm-batch/summarize.sh $ /opt/llm-batch/summarize.sh # 出力例 [14:23:01] 処理中: report_20260715 [14:23:08] 完了: /opt/llm-batch/output/report_20260715_summary.txt [14:23:09] 処理中: report_20260714 [14:23:17] 完了: /opt/llm-batch/output/report_20260714_summary.txt [14:23:18] 処理中: report_20260713 [14:23:25] 完了: /opt/llm-batch/output/report_20260713_summary.txt 全ファイルの処理が完了しました。

`jq -n --arg` でJSONを組み立てているのが安全性のポイントだ。ファイルの内容にダブルクォート・バックスラッシュ・改行が含まれていても、jqが自動でエスケープするためJSONが壊れない。

1ファイルあたりの処理時間はモデルサイズと入力テキスト長に依存する。7Bモデルのq8_0で200字要約なら概ね5~10秒が目安になる。

Pythonスクリプトでバッチ処理を実装する手順

シェルスクリプトは手軽だが、エラーハンドリングとログ管理が複雑になってくる。ファイル数が多く本番運用するなら、Pythonで実装したほうが保守性が高い。

1. 必要なライブラリの確認

標準ライブラリのみで動作するが、HTTPクライアントとしてrequestsを使う。

$ pip3 install requests # バージョン確認 $ python3 -c "import requests; print(requests.__version__)" 2.32.3

2. シーケンシャルバッチスクリプトの実装

#!/usr/bin/env python3 import requests from pathlib import Path from datetime import datetime OLLAMA_URL = "http://localhost:11434/api/generate" MODEL = "mistral:7b-instruct-q8_0" INPUT_DIR = Path("/opt/llm-batch/input") OUTPUT_DIR = Path("/opt/llm-batch/output") OUTPUT_DIR.mkdir(parents=True, exist_ok=True) def summarize(filepath: Path) -> str: content = filepath.read_text(encoding="utf-8") payload = { "model": MODEL, "prompt": f"以下のテキストを200字以内で要約してください。\n\n{content}", "stream": False, "options": {"num_predict": 400, "temperature": 0.3} } resp = requests.post(OLLAMA_URL, json=payload, timeout=180) resp.raise_for_status() return resp.json()["response"] def main(): files = sorted(INPUT_DIR.glob("*.txt")) print(f"対象ファイル数: {len(files)}") for fp in files: out_path = OUTPUT_DIR / f"{fp.stem}_summary.txt" print(f"[{datetime.now().strftime('%H:%M:%S')}] 処理中: {fp.name}") try: summary = summarize(fp) out_path.write_text(summary, encoding="utf-8") print(f"[{datetime.now().strftime('%H:%M:%S')}] 完了: {out_path.name}") except Exception as e: print(f"[ERROR] {fp.name}: {e}") if __name__ == "__main__": main()

3. スクリプトを実行する

$ python3 /opt/llm-batch/summarize.py # 出力例 対象ファイル数: 3 [14:45:01] 処理中: report_20260715.txt [14:45:09] 完了: report_20260715_summary.txt [14:45:09] 処理中: report_20260714.txt [14:45:18] 完了: report_20260714_summary.txt [14:45:18] 処理中: report_20260713.txt [14:45:26] 完了: report_20260713_summary.txt

`temperature: 0.3` と `num_predict: 400` を明示していることで、出力の揺れと途中切れを防いでいる。単純な要約タスクは temperature を低めにするほど再現性が高くなり、品質が安定する。

ThreadPoolExecutorで並列処理してスループットを上げる方法

シーケンシャル処理では100ファイルの処理に数十分かかる。Ollamaは複数の並列リクエストを受け付けるので、Pythonのスレッドプールで同時処理数を増やすと大幅に高速化できる。

1. 並列処理対応スクリプトの実装

#!/usr/bin/env python3 import requests from pathlib import Path from datetime import datetime from concurrent.futures import ThreadPoolExecutor, as_completed OLLAMA_URL = "http://localhost:11434/api/generate" MODEL = "mistral:7b-instruct-q8_0" INPUT_DIR = Path("/opt/llm-batch/input") OUTPUT_DIR = Path("/opt/llm-batch/output") MAX_WORKERS = 3 # 同時処理数(VRAMに応じて調整) OUTPUT_DIR.mkdir(parents=True, exist_ok=True) def summarize_file(filepath: Path): content = filepath.read_text(encoding="utf-8") payload = { "model": MODEL, "prompt": f"以下のテキストを200字以内で要約してください。\n\n{content}", "stream": False, "options": {"num_predict": 400, "temperature": 0.3} } resp = requests.post(OLLAMA_URL, json=payload, timeout=300) resp.raise_for_status() return filepath, resp.json()["response"] def main(): files = sorted(INPUT_DIR.glob("*.txt")) print(f"対象ファイル数: {len(files)}, 並列数: {MAX_WORKERS}") errors = [] with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: futures = {executor.submit(summarize_file, fp): fp for fp in files} for future in as_completed(futures): try: fp, result = future.result() out_path = OUTPUT_DIR / f"{fp.stem}_summary.txt" out_path.write_text(result, encoding="utf-8") print(f"[{datetime.now().strftime('%H:%M:%S')}] 完了: {out_path.name}") except Exception as e: fp = futures[future] print(f"[ERROR] {fp.name}: {e}") errors.append(fp.name) if errors: print(f"\n失敗ファイル ({len(errors)}件): {', '.join(errors)}") else: print("\n全ファイルの処理が正常に完了しました。") if __name__ == "__main__": main()

2. 並列数(MAX_WORKERS)の目安

`MAX_WORKERS` の最適値はVRAMとモデルサイズに依存する。

・7B q8_0モデル、VRAM 8GB → MAX_WORKERS=2
・7B q4_0モデル、VRAM 8GB → MAX_WORKERS=3~4
・7B q8_0モデル、VRAM 24GB → MAX_WORKERS=4~6

増やしすぎるとOllamaがリクエストをキューイングし、かえってスループットが落ちる場合がある。まず2で試してVRAM使用率を `nvidia-smi` で監視しながら少しずつ上げていくのが安全な進め方だ。

結果をJSON形式で構造化して保存する方法

要約テキストだけでなく、処理日時・ファイル名・モデル名・処理時間をセットで保存すると、後から分析や監査がしやすくなる。

JSONL(1行=1レコードのJSON)形式が、追記が簡単でjqやPandasでの集計にも適している。

import json, time from pathlib import Path from datetime import datetime import requests RESULTS_FILE = Path("/opt/llm-batch/results.jsonl") OLLAMA_URL = "http://localhost:11434/api/generate" MODEL = "mistral:7b-instruct-q8_0" def summarize_with_meta(filepath: Path) -> dict: content = filepath.read_text(encoding="utf-8") payload = { "model": MODEL, "prompt": f"以下のテキストを200字以内で要約してください。\n\n{content}", "stream": False, "options": {"num_predict": 400, "temperature": 0.3} } start = time.time() resp = requests.post(OLLAMA_URL, json=payload, timeout=300) resp.raise_for_status() data = resp.json() return { "file": filepath.name, "model": MODEL, "processed_at": datetime.now().isoformat(), "elapsed_sec": round(time.time() - start, 2), "input_chars": len(content), "summary": data["response"], "eval_count": data.get("eval_count", 0) } # JSONL形式で追記保存 with RESULTS_FILE.open("a", encoding="utf-8") as f: result = summarize_with_meta(Path("/opt/llm-batch/input/report_20260715.txt")) f.write(json.dumps(result, ensure_ascii=False) + "\n") print(f"処理時間: {result['elapsed_sec']}秒 / 出力トークン: {result['eval_count']}")

# 保存したJSONLをjqで集計(全ファイルの平均処理時間) $ cat /opt/llm-batch/results.jsonl | jq '.elapsed_sec' | \ awk '{sum+=$1; count++} END {printf "平均処理時間: %.1f秒 (%d件)\n", sum/count, count}' # 出力例 平均処理時間: 7.3秒 (50件)

`eval_count` はOllamaが出力したトークン数で、`eval_duration`(ナノ秒)と組み合わせれば tokens/sec を計算できる。モデルやハードウェアごとのスループット比較に活用できる。

バッチ処理でよくあるトラブルと対処法

本番環境でバッチ処理を動かすと、いくつか典型的なトラブルに直面する。対処法をまとめておく。

タイムアウトが頻発する場合

入力テキストが長すぎる、またはサーバー負荷が高い場合にタイムアウトが起きる。
対処は2つある。1つはrequestsの `timeout` を長めに設定すること(180~300秒)、もう1つは入力テキストを分割してリクエストを小さくすることだ。

# 長文テキストを1,000字単位に分割して処理する def split_text(text: str, chunk_size: int = 1000) -> list: return [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)] # モデルのコンテキスト長を確認(num_ctx) $ curl -s http://localhost:11434/api/show \ -H "Content-Type: application/json" \ -d '{"name": "mistral:7b-instruct-q8_0"}' | jq '.parameters' | grep num_ctx

メモリ不足でOllamaがクラッシュする場合

複数のモデルが同時にロードされているとVRAMを圧迫する。バッチ処理の前に不要なモデルをアンロードしてから実行する。

# ロード済みモデルの確認 $ curl -s http://localhost:11434/api/ps | jq '.models[].name' # 不要なモデルをアンロード(keep_alive=0を送信) $ curl -s http://localhost:11434/api/generate \ -H "Content-Type: application/json" \ -d '{"model": "llama3.3:70b-instruct-q4_0", "prompt": "", "keep_alive": 0}' > /dev/null # アンロード後のVRAM確認 $ nvidia-smi --query-gpu=memory.used,memory.free --format=csv,noheader

出力が途中で切れる場合

`num_predict` を明示していない場合、Ollamaのデフォルト値(モデルにより異なる)で出力が打ち切られることがある。200字要約なら `num_predict: 400` を常に指定しておけば切れることはない。

また、入力テキストがモデルの `num_ctx`(デフォルト多くは2048トークン)を超えている場合も末尾が切り落とされる。長い文書はチャンク分割が必須だ。

エラーハンドリングとリトライ設計で安定稼働させる方法

本番のバッチ処理では、一時的なエラーに対する自動リトライを組み込むことで安定性が大幅に上がる。

タイムアウト・接続エラーはリトライ対象、HTTPエラー(400系)はリトライしない、という設計が基本だ。

import time import requests from pathlib import Path from datetime import datetime FAILED_LOG = Path("/opt/llm-batch/failed.txt") def summarize_with_retry(filepath, payload, url, max_retries=3, backoff_base=5): for attempt in range(max_retries): try: resp = requests.post(url, json=payload, timeout=300) resp.raise_for_status() return resp.json()["response"] except (requests.Timeout, requests.ConnectionError) as e: wait = backoff_base * (2 ** attempt) print(f"[RETRY {attempt+1}/{max_retries}] {filepath.name}: {e}. {wait}秒後に再試行") time.sleep(wait) except requests.HTTPError as e: # 400系はリトライしない print(f"[HTTP ERROR] {filepath.name}: {e}") raise raise RuntimeError(f"{filepath.name}: {max_retries}回リトライ後も失敗") def log_failure(filepath: Path, error: str): with FAILED_LOG.open("a", encoding="utf-8") as f: f.write(f"{datetime.now().isoformat()},{filepath.name},{error}\n")

# 失敗ログを確認 $ cat /opt/llm-batch/failed.txt 2026-07-16T14:30:11,report_20260710.txt,ReadTimeout: HTTPConnectionPool... # 失敗ファイルだけ再処理する $ while IFS=',' read -r ts filename err; do python3 /opt/llm-batch/summarize.py --file "/opt/llm-batch/input/$filename" done < /opt/llm-batch/failed.txt

exponential backoffとは、リトライのたびに待機時間を倍にしていく方式だ(1回目=5秒、2回目=10秒、3回目=20秒)。Ollamaが一時的に高負荷でも、短時間で回復すれば次のリトライで成功できる。

失敗ファイルをCSVログに記録しておくと、後から原因分析と再処理が手軽にできる。バッチの規模が大きくなるほど、このログ設計が運用の要になる。

まとめ:OllamaバッチLLM処理の実践チェックリスト

OllamaのREST APIを使ったドキュメントバッチ処理の要点をまとめる。
項目 実装のポイント コマンド・設定例
API動作確認 curl /api/tagsでモデル一覧を確認する curl -s http://localhost:11434/api/tags | jq '.models[].name'
ストリーム無効化 バッチ処理は必ずstream: falseを指定する "stream": false
出力長の制御 num_predictを明示して出力切れを防ぐ "num_predict": 400
温度設定 要約・分類タスクはtemperature低めに設定する "temperature": 0.3
シェルスクリプト jq -nでJSONを安全に組み立てるforループ jq -n --arg prompt "$content" '{...}'
Python並列処理 ThreadPoolExecutorでMAX_WORKERS=2~4から開始 concurrent.futures.ThreadPoolExecutor
結果の保存 JSONL形式で処理メタ情報ごと保存する json.dumps(result, ensure_ascii=False) + "\n"
リトライ設計 タイムアウトはexponential backoffでリトライする time.sleep(backoff_base * 2 ** attempt)
モデル管理 バッチ前に不要モデルをアンロードしてVRAMを確保する "keep_alive": 0
機密情報を外部に送らずにLLM処理を自動化できる環境を構築できれば、社内の情報処理ワークフローを大きく変えられる。まずは10ファイル程度の小規模バッチから試して、モデルと並列数の組み合わせを手元の環境で調整していくのが安全な進め方だ。

ローカルLLMのバッチ処理を2日間のハンズオンで実装する

「スクリプトは書けても、本番環境への組み込みで詰まる」という声をよく聞きます。実機GPU環境でOllamaのAPIを直接叩きながら、curlからPython並列処理まで一気に実装したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。

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

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

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

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

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

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

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

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

登録10秒/合わなければ解除3秒 / 詳細はこちら

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

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。