AIエージェント 低コスト大量バッチ処理(HBOパターン)
対象読者: Claude Code / AIエージェントを業務で使っているエンジニア。ベクトルDB・Ollama・ChromaDB・SBERT の基礎知識がある方。 本書の目的: AIエージェントの「推論コスト」と「計算コスト」を分離し、9時間・17タスク・4万件超のデータ処理を極小コストで完走させた技術体系を、再利用可能な形で伝える。
目次
第I部 技術概念と原理
- なぜ「推論」と「計算」を分離するのか
- 三層コストモデル — Opus / Haiku / ローカル推論
- HBOパターンの3原則
- タスク分類マトリクス — 何をどこに委託するか
- リソース競合の物理学 — なぜOllamaは共有できないか
第II部 用語辞典
第III部 システム構成
第IV部 歴史と背景
第V部 手順・マニュアル
第I部 技術概念と原理
1. なぜ「推論」と「計算」を分離するのか
AIエージェントに大量のバッチ処理を任せる場面を想像してほしい。4万件の特許をベクトル化する、1,200冊の書籍にCross-Encoderでリランキングをかける、570件のドキュメントをChromaDBに投入する——こうした処理を、最も高性能なモデル(Opus級)に全て任せるとどうなるか。
コストが処理量に比例して爆発する。
しかし冷静に考えると、4万件のベクトル化において「推論」が必要なのは最初の10分だけだ。どのスクリプトを使うか判断し、パラメータを決め、テスト実行して疎通を確認する——ここまでが「考える仕事」。残りの数時間は、同じAPIを同じパラメータで繰り返し叩くだけの「計算の仕事」である。
推論(考える) 計算(動かす)
┌──────────┐ ┌──────────────────────────┐
4万件の │ どのスクリ│ │ 同じAPIを40,000回叩く │
ベクトル化 │ プト? │ │ │
│ パラメータ│ │ → Ollamaで無料 │
│ は?テスト│ │ → 進捗をJSONで記録 │
│ OK? │ │ → エラーは3回リトライ │
└──────────┘ └──────────────────────────┘
↑ ここだけOpus ↑ ここはPython直接実行
(5分、高コスト) (6時間、コストゼロ)
この分離がHBOパターンの核心だ。推論にはOpus(高精度モデル)を使い、スクリプト作成にはHaiku(低コストモデル)を使い、実際の計算はOllamaやSBERTなどのローカル推論エンジンに任せる。タスクが10倍になっても推論コストはほぼ一定。増えるのは無料の計算時間だけだ。
コスト
^
| / 全部Opus
| /
| /
| / /-- 全部Haiku
|/ /
|/---------- HBO パターン
+------------------------------> 処理量
2. 三層コストモデル
HBOパターンは3つの層でコストを最適化する。
+---------+
| Opus | 計画・判断・エラー修復
| (最小限) | 使用量: セッション全体で1回
+----+----+
|
+------+------+
| Haiku | スクリプト作成・レポート生成
| (低コスト) | 使用量: タスクあたり1回
+------+------+
|
+---------+---------+
| Ollama / SBERT | ベクトル化・分類・スコアリング
| SQLite / ChromaDB | 使用量: 数万回(全て無料)
+-------------------+
各層の役割を明確にする:
| 層 | モデル/ツール | 役割 | コスト特性 |
|---|---|---|---|
| 計画層 | Opus | タスク設計、依存関係分析、エラー診断 | 高単価・最小量 |
| 生成層 | Haiku | Pythonスクリプト作成、MDレポート生成 | 低単価・中量 |
| 実行層 | Ollama / SBERT / SQLite | ベクトル化、LLM分類、DB操作 | 無料・大量 |
この構造が効く理由は、各層のスケーリング特性が異なるからだ。
- 計画層: タスク数に対して対数的(10タスクでも100タスクでも計画は1回)
- 生成層: タスク数に対して線形(タスクごとにスクリプト1本)
- 実行層: データ件数に対して線形(だがコストゼロ)
結果として、全体コストは処理量に対して対数的に増加する。
3. HBOパターンの3原則
原則1: 分離原則 — 「考える」と「動かす」を分ける
NG パターン HBO パターン
+----------------+ +----------------+
| Haiku Agent | | Haiku Agent |
| スクリプト | | スクリプト |---> .py作成
| 作成 + 実行 |---> 権限エラー | 作成のみ |
| | で停止 +----------------+
+----------------+ |
+------v------+
| 親プロセス |
| 直接実行 |---> 確実に完走
| (bash bg) |
+-------------+
Haikuエージェントはサブプロセスとして動作し、Bash実行権限が親プロセスと異なるスコープで制限されることがある。実際の運用では、11回のHaikuエージェント投入のうち3回が権限問題で実行フェーズでブロックされた。
対策: スクリプト「作成」(Write権限のみ)と「実行」(Bash権限)を分離する。Haikuにはスクリプトを書かせるだけにして、実行は親プロセスから直接行う。
適用判断基準:
- 実行に30秒以上かかるバッチ処理 → 必ず分離
- source .venv/bin/activate が必要 → 必ず分離
- SSH経由のリモート操作 → 必ず分離
- 単純なファイル読み書きのみ → 分離不要(Haikuに任せてOK)
原則2: 資源分離原則 — 計算リソースの帯域競合を避ける
ローカルLLMサーバー(Ollama等)は、複数プロセスから同時にリクエストを受けると応答速度が1/Nに低下する。これは推論がGPU/CPUを排他的に使用するためだ。
NG: 3プロセスがOllama争奪 OK: リソース分離
[分類] [embed] [score] [分類] [SBERT]
| | | | |
v v v v v
+-------------------+ +--------+ +--------+
| Ollama API | <-渋滞 | Ollama | | CPU |
| (実効速度 1/3) | | API | | (並列) |
+-------------------+ +--------+ +--------+
リソース競合マップ:
| リソース | 排他性 | 同時実行時の影響 |
|---|---|---|
| Ollama API | 準排他 | 応答速度が1/Nに低下 |
| SBERT (CPU) | 共有可 | メモリ依存、2並列まで実用的 |
| ChromaDB 書込 | 排他 | DB lockエラー(致命的) |
| SQLite 書込 | 排他 | ロック待ち |
| SSH | 共有可 | 問題なし |
最適スケジューリング: Ollamaを使うタスクは直列に、CPUを使うタスクは並列に。
Phase 1: [A: Ollamaベクトル化 37K件] ← Ollama独占
完了後
Phase 2: [G: Ollama分類] + [P: SBERT] + [Q: CrossEncoder]
Ollama CPU CPU ← 資源分離
Phase 3: [K: Ollama embed] + [M: Ollama scoring]
← Gより優先度低いが、G完了待たず並行投入
原則3: 段階的信頼原則 — まずテスト、次にバッチ
6時間のバッチ処理を開始して5時間後にカラム名エラーで全件失敗——これは避けたい。
Step 1: Haiku がスクリプト作成
|
Step 2: 親が --demo / --test で疎通確認 (5件)
| OK
Step 3: 親が全件バッチを run_in_background で起動
|
Step 4: progress.json を定期確認で監視
| エラー発生
Step 5: 親が直接修正して再起動 (Haikuに戻さない)
この原則で重要なのはStep 5だ。エラーが起きたとき、Haikuエージェントに修正を再委託するのではなく、親プロセス(Opus)が直接修正する。理由は、Haikuは元のコンテキストを持っていないため、修正に必要な文脈を再度説明するコストが、直接修正するコストを上回るからだ。
4. タスク分類マトリクス
すべてのタスクが同じパターンで実行できるわけではない。以下のマトリクスで最適な実行方式を選択する。
実行時間
短 (< 30min) 長 (30min - 数時間)
+------------------+------------------------+
Haiku | GKS修復 | 記事レビュー |
単独で | todo棚卸し | SSH経由サービス修復 |
完結 | (読み→分析→書き) | (診断→修正→検証) |
+------------------+------------------------+
Haiku | orgmemory同期 | DocFlow 570件embed |
作成 + | themes投入 | スコアリング 2,240件 |
親実行 | rerank構築 | 45K分類 |
| | 37K ベクトル化 |
+------------------+------------------------+
親プロセス | (該当なし) | SBERT 27K分析 |
直接のみ | | (venv依存が複雑) |
+------------------+------------------------+
判断フローチャート:
タスクを受け取る
|
+-- 実行時間 < 30秒? --> 親が直接実行
|
+-- 既存スクリプトがある?
| |
| +-- YES --> Haiku不要、親が直接実行
| |
| +-- NO --> Haikuにスクリプト作成を委託
|
+-- venvが必要?
| |
| +-- YES --> 親が直接実行(Haikuはvenv activateが不安定)
| |
| +-- NO --> Haikuに実行も委託可能
|
+-- Ollama / 外部APIを使う?
|
+-- YES --> run_in_background で起動(長時間)
|
+-- NO --> 通常実行
5. リソース競合の物理学
なぜOllamaに3プロセスを同時に投げると遅くなるのか。
ローカルLLMサーバーは、推論リクエストを受けると以下の処理を行う:
- トークン化: 入力テキストをトークンに変換(高速、並列可)
- KVキャッシュ確保: モデルの注意機構のためのメモリを確保(GPU VRAM排他)
- 推論実行: Transformerの各層を順に計算(GPU排他)
- デコード: 出力トークンを文字列に変換(高速、並列可)
ステップ2と3がボトルネックだ。GPUのVRAMとコンピュートユニットは物理的に1つしかない。複数リクエストが来ると、サーバーは以下のいずれかで対応する:
- キュー方式: リクエストをキューに入れ、1つずつ処理(Ollamaの標準動作)
- バッチ方式: 複数リクエストをまとめて1回の推論で処理(vLLMなど)
Ollamaはキュー方式のため、3プロセスが同時にリクエストすると実効速度は1/3ではなく、コンテキストスイッチのオーバーヘッドを含めて1/3〜1/4になる。
Embedding API(/api/embed)の場合:
nomic-embed-textは軽量(261MB)なので、推論自体は高速(0.05秒/件)。ボトルネックはネットワークレイテンシとリクエストのシリアライズだ。この場合、3並列でも実効速度の低下は1/2程度に収まる。
LLM生成API(/api/generate)の場合:
qwen3:8b(5GB)やqwen3:14b(9GB)は重い。1リクエストあたり1〜5秒かかり、並列時はGPUを完全に占有する。3並列では実効速度が1/3以下になる。
結論: Embedding APIは緩い並列を許容できるが、LLM生成APIは直列が最適。
第II部 用語辞典
6. 用語辞典
AIモデル・エージェント
| 用語 | 意味 |
|---|---|
| Opus | Anthropic Claude の最上位モデル。高精度な推論・計画・コード生成に使用。コスト高。 |
| Haiku | Claude の最軽量モデル。高速・低コスト。スクリプト生成やレポート作成に十分な品質。 |
| Ollama | ローカルでLLMを実行するオープンソースサーバー。API互換でGPU推論を提供。コストゼロ。 |
| nomic-embed-text | Ollama上で動作する軽量Embeddingモデル(768次元、261MB)。ベクトル化の主力。 |
| qwen3:8b / 14b | Alibaba開発のLLM。Ollama上で分類・スコアリングに使用。 |
| run_in_background | Claude Codeのバックグラウンド実行機能。長時間コマンドを非同期で実行し、完了通知を受け取る。 |
| subagent | Claude Code内で起動される子エージェント。特定のタスクを委託され、完了後に結果を返す。 |
ベクトルDB・検索
| 用語 | 意味 |
|---|---|
| ChromaDB | Pythonネイティブのベクトルデータベース。PersistentClientでローカルファイルに永続化。 |
| Embedding | テキストを固定長の数値ベクトルに変換すること。意味的な類似検索を可能にする。 |
| SBERT (Sentence-BERT) | Sentence-Transformersライブラリ。BERTベースの文埋め込みモデル。ローカルCPU実行可能。 |
| CrossEncoder | クエリとドキュメントのペアを同時にエンコードし、関連度スコアを出力するモデル。bi-encoderより高精度だが低速。 |
| Reranking | bi-encoderで粗く取得した候補を、CrossEncoderで精緻に再ランキングする二段階検索手法。 |
| FTS5 | SQLiteの全文検索拡張。BM25ランキングを提供。ベクトル検索との併用(ハイブリッド検索)が有効。 |
データベース・ストレージ
| 用語 | 意味 |
|---|---|
| SQLite | ファイルベースの軽量RDB。patent_search.db(45,121件)の基盤。 |
| DuckDB | 分析特化の列指向DB。書籍メタデータの管理に使用。 |
| progress.json | バッチ処理の進捗を記録するJSONファイル。中断→再開(resume)を可能にする。 |
| DB lock | SQLite/ChromaDBで複数プロセスが同時書込みすると発生するロック。致命的エラーの原因。 |
インフラ・デプロイ
| 用語 | 意味 |
|---|---|
| launchd / plist | macOSのサービス管理デーモン。plistファイルでサービス定義。launchctlで操作。 |
| Tailscale | WireGuardベースのVPN。リモートマシンへのセキュアな接続に使用。 |
| EINTR | Unix シグナルによるシステムコール中断。ファイルスキャン中にI/Oが重いと発生。リトライで対処。 |
第III部 システム構成
7. 全体アーキテクチャ
+---------------------------------------------------------------+
| Opus 親プロセス |
| (計画・判断・実行制御・エラー修復) |
| |
| +----------+ +----------+ +----------+ +----------+ |
| | Haiku | | Haiku | | Haiku | | Haiku | |
| | Agent 1 | | Agent 2 | | Agent 3 | | Agent N | |
| | (script | | (script | | (report | | (SSH | |
| | 作成) | | 作成) | | 生成) | | 修復) | |
| +----+-----+ +----+-----+ +----+-----+ +----+-----+ |
| | | | | |
| +----v-------------v-------------v-------------v-----------+ |
| | 親プロセス直接実行レイヤー | |
| | (bash run_in_background / progress.json監視) | |
| +----+-------------+-------------+-------------------------+ |
+-------+-------------+-------------+-----------------------------+
| | |
+----v----+ +----v----+ +----v-----+
| Ollama | | SBERT | | SQLite |
| Mac mini| | ローカル | | ChromaDB |
| (GPU) | | CPU | | (ファイル)|
| 無料 | | 無料 | | 無料 |
+---------+ +---------+ +----------+
コンポーネント間通信
Haiku Agent --[Write]--> Python Script (.py)
|
Parent Process --[Bash]--> python3 script.py --[HTTP]--> Ollama API
| |
+--[File I/O]--> ChromaDB |
| |
+--[File I/O]--> SQLite |
| |
+--[File I/O]--> progress.json|
| v
Parent Process --[Read]----> progress.json nomic-embed-text
| qwen3:8b / 14b
v
完了通知 → 次タスク起動
8. タスクフロー設計図
実際に実行した17タスクの依存関係と時系列:
時刻 0h 1h 2h 3h 4h 5h 6h 7h 8h 9h
|------|------|------|------|------|------|------|------|------|
[A] 特許ベクトル37K =====(39min)=>
[B] GKS ChromaDB ==(5min)=>
[C] todo棚卸し ===(1.5min)=>
[D] ランドスケープ ===(3min)=>
[E] Mac mini修復 ========(4min)=>
[F] watchdog+同期 ==(1min)=> (E完了後)
[G] 45K分類Phase1+2 =(即完了)= Phase3 ================================>
(IPC+keyword) (Ollama LLM、数時間)
[H] 記事レビュー =======(5min)=>
[I] NotebookLM 3NB ===(3min)=>
[J] GKS修復 =(1min)=>
[K] DocFlow 570 embed =======(13min)=>
[L+O] orgmemory+themes =====(5min)=>
[M'] scoring 3cat ========================>
[P] SBERT 27K ==(1.5min)=> (CPU, Ollama非使用)
[Q] rerank index =(2min)=> (CPU, Ollama非使用)
依存関係:
- E → F: Mac mini修復完了後にスクリプト同期
- A 完了 → K,M' 起動: Ollama帯域の解放
- P, Q: CPU専用のため、Ollamaタスクと並行可能
9. データフロー
[External DB CSV] --import--> [patent_search.db]
SQLite 45,121件
|
+---------------+---------------+
| | |
v v v
[batch_embed] [batch_categorize] [relevance_scoring]
Ollama embed Phase1: IPC rule Ollama qwen3:14b
nomic-embed Phase2: keyword スコア1-5判定
768次元 Phase3: Ollama LLM
| | |
v v v
[ChromaDB] [auto_category列] [scored_*.csv]
patent_search patent_search.db data/processed/
37,777 docs に直接UPDATE
[Obsidian vault] --> [layer2_embedder.py] --> [ChromaDB]
1,193 books MD nomic-embed-text book_insights: 1,193
41 themes MD book_summaries: 2,627
27 insights MD book_raw_text: 149,608
vault_themes_insights: 283
[docflow.db] --> [docflow_vectorize_v2.py] --> [ChromaDB]
570 docs MD nomic-embed-text docflow_docs: 570
[ChromaDB] --> [build_rerank_index.py] --> [rerank_index.json]
book_insights CrossEncoder 20 queries x top-50
1,193 docs BAAI/bge-reranker-base 701KB JSON
[bizc_27k.csv] --> [analyze_27k.py] --> [.npy] + [clusters.csv]
27,776 patents SentenceTransformer 384d embeddings
KMeans(30) 30 clusters
第IV部 歴史と背景
10. 技術選定の経緯
2023年 ChromaDB v0.3 リリース
| PythonネイティブのベクトルDB。pip installだけで動く手軽さが
| 個人プロジェクトでの採用を加速。
|
2024年 Ollama の普及
| ローカルLLM実行が「docker pullするだけ」の手軽さに。
| API互換により、OpenAI向けコードをローカルに移植可能に。
| embedding APIの追加で、ベクトル化もローカルで完結する世界が到来。
|
2024年 Claude Code(旧Claude CLI)登場
| AIエージェントがターミナルで直接コマンドを実行できるように。
| 「計画→コード生成→実行→検証」のループが1セッションで回る。
|
2025年 Haiku / Opus モデルの分化
| コスト差が明確になり「高精度モデルで計画、低コストモデルで実行」の
| パターンが経済的に合理的に。
|
2026年 本セッション — HBOパターンの確立
Opus(計画)+ Haiku(生成)+ Ollama/SBERT(実行)の
三層分離を体系化。9時間17タスクで実証。
なぜOllamaか: クラウドAPI(OpenAI等)は従量課金のため、4万件のベクトル化では$40〜$100のコストが発生する。Ollamaなら同じ処理がゼロコスト。品質差は検索用途では許容範囲。
なぜChromaDBか: Pinecone等のマネージドサービスは月額課金。ローカルファイルベースのChromaDBは無料で、SQLiteと同じ感覚で使える。個人・小チーム規模では最適解。
なぜSBERTか: Ollamaのnomic-embed-textは汎用だが、日本語特許の多言語クラスタリングにはparaphrase-multilingual-MiniLM-L12-v2(384次元)の方が精度が高い。CPU実行で145〜343件/秒と十分高速。
11. 失敗の系譜
このセッションで遭遇した6つの障害と、そこから得た教訓:
障害1: Haikuエージェントの権限ブロック
症状: HaikuエージェントがBash権限を拒否され、スクリプトを実行できない。
原因: サブプロセスとして動作するエージェントの権限スコープが親と異なる。
教訓: 原則1(分離原則)の確立。Haikuにはスクリプト作成のみ委託。
障害2: ChromaDB "Collection does not exist"
症状: collection.add()直後に「コレクションが存在しない」エラー。
原因: 先に起動した重複プロセスがChromaDBのSQLiteファイルをロック→破損。
教訓: バッチ起動前にps | grep <script>で重複プロセスを確認・kill。ChromaDBのパスを変えて完全クリーンから再開。
障害3: Ollama帯域競合による速度低下
症状: 3プロセスが同時にOllamaを叩き、1件あたりの処理時間が3倍に。 原因: Ollamaのキュー方式による排他的GPU使用。 教訓: 原則2(資源分離原則)の確立。Ollamaタスクは優先度順に直列化。CPUタスク(SBERT)は並行OK。
障害4: CSVカラム名の不一致
症状: KeyError: 'date' — DataFrameにdateカラムが無い。
原因: CSVのカラム名がfiling_dateだったが、スクリプトはdateを参照。
教訓: 原則3(段階的信頼原則)。--demoで5件テスト → カラム確認 → 全件実行。
障害5: macOS launchd Python バージョン不整合
症状: health-monitorがexit 1で即クラッシュ。
原因: plistが/usr/bin/python3(3.9)を指定していたが、スクリプトがPython 3.10+構文(int | None)を使用。
教訓: plistのPythonパスは/opt/homebrew/bin/python3を明示的に指定する。
障害6: EINTR(Interrupted System Call)
症状: watchdogがファイルスキャン中にInterruptedError。
原因: ~/DownloadsフォルダでChrome等のI/Oが重く、os.scandir()がシグナルで中断。
教訓: ファイル操作にはリトライラッパーを必ず実装する:
def glob_with_retry(path, pattern, retries=3):
for attempt in range(retries):
try:
return list(path.glob(pattern))
except InterruptedError:
if attempt == retries - 1:
return []
time.sleep(0.1)
第V部 手順・マニュアル
12. 9時間バッチを計画する10ステップ
前提条件
- Claude Code(Opus + Haiku使用可能)
- Mac / Linux マシン(Python 3.10+)
- Ollama サーバー(ローカルまたはリモート)
- ChromaDB(
pip install chromadb)
Step 1: 棚卸し — 既存Pythonスクリプトのリスト化
# プロジェクト内の全Pythonスクリプトを列挙
find ~/projects ~/virtual-company -name "*.py" \
-not -path "*/.venv/*" -not -path "*/__pycache__/*" \
-not -path "*/site-packages/*" | sort
探すべきもの:
- embed、vectorize、index、sync を含むスクリプト(ベクトル化系)
- score、classify、categorize を含むスクリプト(分類系)
- batch、pipeline、process を含むスクリプト(バッチ系)
Step 2: ギャップ分析 — データ量 vs ベクトル量
# SQLite件数 vs ChromaDB件数の差分が「鉱脈」
import sqlite3, chromadb
db = sqlite3.connect("data/patent_search.db")
total = db.execute("SELECT COUNT(*) FROM patents").fetchone()[0]
client = chromadb.PersistentClient(path="vectordb")
vectorized = sum(c.count() for c in client.list_collections())
gap = total - vectorized
print(f"Total: {total}, Vectorized: {vectorized}, Gap: {gap}")
# Gap > 0 なら、そこがバッチ処理の対象
Step 3: リソースマップ作成
+------------+-----------+------------------+
| リソース | 排他性 | 使用するタスク |
+------------+-----------+------------------+
| Ollama GPU | 準排他 | ベクトル化, 分類 |
| CPU | 共有可 | SBERT, 統計処理 |
| ChromaDB | 書込排他 | 全embed系 |
| SSH | 共有可 | リモート修復 |
+------------+-----------+------------------+
Step 4: 依存関係の整理
# 直列必須
Mac mini修復 --> スクリプト同期
# 並列可能
ベクトル化 || SBERT分析 || レポート生成
# リソース競合(擬似直列)
Ollama分類 ~~ Ollamaスコアリング(同時実行は非推奨)
Step 5: 段階的ファネルの設計
LLM分類タスクは、3段階ファネルでLLM呼び出しを最小化する:
Phase 1: ルールベース分類(IPC/CPCコードマッピング)
→ 即時、全件処理、LLM不要
→ 全体の30-40%をカバー
Phase 2: キーワードマッチング(正規表現)
→ 即時、残り件数の20-30%をカバー
Phase 3: LLM推論(Ollama)
→ 遅い(1-5秒/件)、残り30-40%のみ
Step 6: Haikuエージェントへの委託
# スクリプト作成のみをHaikuに委託
Agent(
model="haiku",
prompt="""
Create a Python script at ~/scripts/batch_embed.py that:
1. Reads from SQLite (path: ...)
2. Embeds using Ollama nomic-embed-text (URL: ...)
3. Stores in ChromaDB (path: ...)
4. Saves progress to .progress.json
5. Supports --resume flag
""",
run_in_background=True
)
Step 7: テストラン
# 5件で疎通確認
python3 scripts/batch_embed.py --test
# または
python3 scripts/batch_embed.py --limit 5
確認項目: - [ ] Ollamaに接続できるか - [ ] ChromaDBにデータが入るか - [ ] progress.jsonが生成されるか - [ ] カラム名は正しいか
Step 8: 全件バッチ起動
# run_in_backgroundで起動
python3 scripts/batch_embed.py --resume 2>&1
Claude Codeのrun_in_backgroundを使えば、完了時に自動通知を受け取れる。
Step 9: 定期監視
# progress.jsonで進捗確認
cat data/.progress.json
# {"last_id": 15000, "count": 15000, "total": 45121, "timestamp": "..."}
# プロセス生存確認
ps aux | grep batch_embed | grep -v grep
Step 10: 資源解放と次タスク投入
完了したタスクのプロセスが終了すると、Ollamaの帯域が解放される。この時点で次のOllamaタスクを投入する。
タスクA完了 → Ollama帯域解放 → タスクB起動
13. パターン別実装レシピ
Recipe A: Ollama Embed バッチ(最頻出)
#!/usr/bin/env python3
"""汎用Ollamaベクトル化バッチテンプレート"""
import json, os, sys, time, urllib.request, sqlite3
from pathlib import Path
import chromadb
OLLAMA_URL = os.environ.get("OLLAMA_HOST", "http://<サーバーIP>:11434")
PROGRESS = Path("data/.embed_progress.json")
def embed(text, model="nomic-embed-text"):
"""3回リトライ付きOllama embed"""
payload = json.dumps({"model": model, "input": text[:2000]}).encode()
req = urllib.request.Request(
f"{OLLAMA_URL}/api/embed", data=payload,
headers={"Content-Type": "application/json"})
for attempt in range(3):
try:
with urllib.request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read().decode())["embeddings"][0]
except Exception:
if attempt == 2: return None
time.sleep(2)
def save_progress(last_id, count, total):
PROGRESS.write_text(json.dumps({
"last_id": last_id, "count": count, "total": total,
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S")}))
def main():
resume = "--resume" in sys.argv
last_id = 0
if resume and PROGRESS.exists():
last_id = json.loads(PROGRESS.read_text()).get("last_id", 0)
db = sqlite3.connect("data/db.sqlite")
db.row_factory = sqlite3.Row
client = chromadb.PersistentClient(path="vectordb")
collection = client.get_or_create_collection("my_collection")
rows = db.execute(
"SELECT id, text, title FROM docs WHERE id > ? ORDER BY id",
(last_id,)).fetchall()
batch_ids, batch_docs, batch_metas, batch_embs = [], [], [], []
for i, row in enumerate(rows):
emb = embed(row["text"])
if emb is None: continue
batch_ids.append(str(row["id"]))
batch_docs.append(row["text"][:4000])
batch_metas.append({"title": row["title"] or ""})
batch_embs.append(emb)
if len(batch_ids) >= 50:
collection.upsert(
ids=batch_ids, documents=batch_docs,
metadatas=batch_metas, embeddings=batch_embs)
batch_ids, batch_docs, batch_metas, batch_embs = [], [], [], []
save_progress(row["id"], i + 1, len(rows))
if batch_ids: # 最後の端数
collection.upsert(
ids=batch_ids, documents=batch_docs,
metadatas=batch_metas, embeddings=batch_embs)
if __name__ == "__main__":
main()
Recipe B: 3段階ファネル分類
# Phase 1: ルールベース (即時)
IPC_MAP = {"H02J50": "WPT", "G06K19": "RFID", "H01Q": "ANTENNA", ...}
def classify_by_ipc(ipc_str):
for prefix, cat in sorted(IPC_MAP.items(), key=len, reverse=True):
if prefix.upper() in (ipc_str or "").upper():
return cat
return None
# Phase 2: キーワード (即時)
KEYWORD_MAP = {
"WPT": [r"wireless.?power", r"power.?transfer", r"rectenna"],
"RFID": [r"\bRFID\b", r"backscatter", r"transponder"],
}
def classify_by_keywords(title, abstract):
text = f"{title} {abstract}".lower()
scores = {cat: sum(1 for p in pats if re.search(p, text, re.I))
for cat, pats in KEYWORD_MAP.items()}
return max(scores, key=scores.get) if any(scores.values()) else None
# Phase 3: LLM (遅い、最終手段)
def classify_by_llm(title, abstract):
prompt = f"Classify: {title} {abstract[:300]}\nCategory:"
# Ollama API call...
Recipe C: SBERT ローカル分析
# CPU実行、Ollama不要
from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
texts = df["abstract"].fillna("").tolist()
embeddings = model.encode(texts, batch_size=128, show_progress_bar=True)
np.save("embeddings.npy", embeddings)
# KMeansクラスタリング
from sklearn.cluster import KMeans
km = KMeans(n_clusters=30, random_state=42, n_init=10)
df["cluster"] = km.fit_predict(embeddings)
Recipe D: CrossEncoder Rerank Index
from sentence_transformers import CrossEncoder
import chromadb, json
ce = CrossEncoder('BAAI/bge-reranker-base')
client = chromadb.PersistentClient(path="~/knowledge/db/chromadb")
col = client.get_collection("book_insights")
docs = col.get(include=["documents", "metadatas"])
queries = ["検索クエリ1", "検索クエリ2", ...]
index = {}
for query in queries:
pairs = [(query, doc[:512]) for doc in docs["documents"]]
scores = ce.predict(pairs)
ranked = sorted(enumerate(scores), key=lambda x: x[1], reverse=True)
index[query] = [{"id": docs["ids"][i], "score": float(s)}
for i, s in ranked[:50]]
with open("rerank_index.json", "w") as f:
json.dump(index, f, ensure_ascii=False)
14. トラブルシューティング辞典
| 症状 | 原因 | 対処 |
|---|---|---|
ChromaDB: Collection does not exist |
重複プロセスによるDB破損 | ps aux \| grep <script> で重複kill → ChromaDBディレクトリ削除 → 再実行 |
Ollama timeout (60s) |
他プロセスがGPU占有中 | 他のOllamaタスクの完了を待つ。またはtimeoutを120sに延長 |
KeyError: 'column_name' |
CSV/DBのカラム名不一致 | df.columns / PRAGMA table_info(table) で実際のカラム名を確認 |
exit code 127 (launchd) |
plist内のスクリプトパスが不正 | cat <plist> でProgramArgumentsを確認。フルパスに修正 |
exit code 1 (launchd) |
Pythonバージョン不整合 | plistのPythonパスを/opt/homebrew/bin/python3に変更 |
InterruptedError: EINTR |
I/O重負荷下でのシステムコール中断 | glob_with_retry()ラッパーを実装(3回リトライ) |
Permission denied (Haiku agent) |
サブプロセスの権限スコープ制限 | Haikuにはスクリプト作成のみ委託。実行は親から直接 |
DB readonly |
別プロセスがSQLite/ChromaDBをロック中 | 重複プロセスをkill。または別パスに新規DB作成 |
| Ollama応答が異常に遅い | 複数プロセスの帯域競合 | ps aux \| grep ollama で他プロセス確認。優先タスクを先に完走 |
requests.post がハングする |
Ollamaが別リクエスト処理中 | urllib.request に切り替え(timeout明示)。requestsは内部バッファリングでハングしやすい |
← 参照コンテンツ一覧に戻る本書は、AIエージェントによる9時間17タスクのバッチ処理セッションから抽出された技術体系です。個別の実装は変わっても、「推論と計算の分離」「リソース競合の管理」「段階的信頼の構築」という3原則は、あらゆるAIバッチ処理に適用できます。