「サーバーのアクセスログや障害レポートが大量にあり、毎日手作業で目視確認するのが限界になってきた」
そんな悩みを抱えるインフラエンジニアや情報システム担当者は多いはずです。
この記事では、OllamaのローカルLLMで複数のドキュメントをバッチ処理する方法を、curlによる基本確認からシェルスクリプト・Pythonの並列実装まで段階的に解説します。
機密情報を外部に一切送らず、社内ネットワーク内で完結するLLM自動処理パイプラインを構築できます。
この記事のポイント
・OllamaのREST APIで stream: false を指定すれば、1回のcurl呼び出しで要約テキストを取得できる
・シェルスクリプトのforループとjqで複数テキストファイルをまとめて安全にバッチ処理できる
・PythonのThreadPoolExecutorで並列実行し、大量ドキュメントを高速に処理できる
・タイムアウト・リトライ設計と失敗ファイルのログ記録で本番バッチを安定稼働させる方法も解説する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
ローカル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%超に達し、レスポンス遅延が発生した。
`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 全ファイルの処理が完了しました。
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
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件)
バッチ処理でよくあるトラブルと対処法
本番環境でバッチ処理を動かすと、いくつか典型的なトラブルに直面する。対処法をまとめておく。タイムアウトが頻発する場合
入力テキストが長すぎる、またはサーバー負荷が高い場合にタイムアウトが起きる。対処は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
失敗ファイルを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のバッチ処理を2日間のハンズオンで実装する
「スクリプトは書けても、本番環境への組み込みで詰まる」という声をよく聞きます。実機GPU環境でOllamaのAPIを直接叩きながら、curlからPython並列処理まで一気に実装したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。
・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら

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