データ処理におけるGeminiとClaudeの使い分け戦略
これは株式会社TimeTree Advent Calendar 2025の6日目の記事です。
こんにちは。TimeTreeの伊藤です。
最近はもっぱらLLMを使った業務効率化や機能開発に取り組んでいます。
特に「Claude」と「Gemini」の2つのモデルは、同じLLMの枠組みながら、それぞれ性格がけっこう違っています。色々と試していく中で「どちらか一方を使う」というより どう組み合わせるか が重要になってきたなと感じてきました。
本記事では、「社内やWeb上にある非構造化テキストから、ドメイン固有の情報を抽出・検証して、統一フォーマットのJSONとして蓄積する」 というバッチ処理を通じて、
- 両モデルを実際に業務で回してみて分かった「使い心地」の違い
- コストと精度を両立させるための設計と設定の工夫
- ハルシネーションや出力崩れが出たときにどう抑え込んでいったか
といったあたりをまとめます。
1. 背景:大量データ処理における課題
やりたかったことはシンプルで、要件だけを書くとだいたいこんな感じです。
「Web上の非構造化テキスト(ニュース、記事、告知ページなど)や、社内に蓄積されたテキストから、後続処理で使いたい属性情報(名称・日付・場所・参照URLなど)を抜き出し、統一フォーマットのJSONとして蓄積する」
どこに保存するかはケースによって変わります。
プロダクト側のDBかもしれないし、スプレッドシートかもしれません。共通しているのは、
- どこから取った情報か(情報ソース)をはっきりさせたい
- 後続のバッチやアプリケーションから扱いやすいように、構造と型を揃えたい
というニーズでした。
当初は、指示従順性が非常に高い Claude Sonnet 4.5 を中心に実装を進めていました。
ClaudeはSystem Promptに「このJSONスキーマで出力して」と指示したり、構造化出力ツールを使用すれば確実に守ってくれますし、ハルシネーション(事実と異なる生成)も比較的少なく、精度面では十分な手応えがありました。
ただ、処理対象が数千件規模のテキストデータになってくると、次のような課題が目立ち始めます。
- APIコスト: 高性能モデルで全件処理すると、ランニングコストが無視できない水準になる
- 処理速度: 丁寧な推論を行うモデルは、バッチ処理として見るとレイテンシが気になる
そこで、コストパフォーマンスと処理速度に優れた Gemini 2.5 pro をパイプラインに組み込む検討を始めました。
以降は、GeminiとClaudeをどう役割分担させていったか、その過程も含めて書いていきます。
2. 技術選定の紆余曲折:Geminiでの「検索+構造化」一発狙いの失敗
最終的には「Gemini + Claude」というハイブリッド構成に落ち着いているのですが、その前に一度大きく設計を変えています。
Geminiで「検索+JSON出力」を一気にやろうとした
最初の設計では、Geminiを使って 「外部の情報源から必要なデータを検索しつつ、その結果をそのまま構造化JSONにして返す」 という一体型の処理を狙っていました。
ざっくり言うと、
- Grounding with Google Search(外部検索と連携してリアルタイム情報を取ってくる機能)がある
- Structured Outputsで構造化出力もできる
ので、「検索も整形もまとめて1リクエストで済ませたい」 という発想です。
しかし、当時使っていた Gemini 2.5 Pro を実際に試してみると、この2つを同時に満たすのは難しいという制約が見えてきました。
- Structured Outputsを有効にすると、検索ツールが使えない (併用ができない)
- Grounding with Google Searchを使うと、レスポンスがMarkdown混じりのテキストになりやすく、JSONとしてそのまま扱いにくい
結果として、「外部の情報ソースを参照しつつ、きれいなJSONだけ返してほしい」 という要件が、素直な形では実現できないことが分かりました。
※補足:この記事で書いている実験は 2.5 世代の経験がベースですが、後述のとおり Gemini 3 Preview 以降では「構造化出力」が使えるようになり、JSONを安定して出す手段が増えています。
Claudeの各種ツールの組み合わせ活用
そこで方針を変えるきっかけになったのが、ClaudeのWeb search tool, Web fetch tool(beta), Structured outputs(関数呼び出し)を組み合わせられるという点でした。
- Claudeも外部の検索エンジンと連携できる
- さらにStructured outputsを併用できるので、「検索したうえで、指定したSchemaどおりのJSONを返す」 という構成が取りやすい
という特徴があります。
そこで、役割を次のように切り分けることにしました。
- Gemini: 大量のテキストを高速・低コストに処理し、「条件に合いそうなレコード」をざっくり抽出(Groundingは使わない)
- Claude: Geminiが抽出した候補に対してWeb検索を行い、一次情報源を確認しながら、厳密なJSON形式で最終データを生成
つまり、最初にやろうとしていた 「検索+構造化」 の仕事を、2つのモデルに役割分担させる方向に舵を切りました。
3. Gemini導入のメリットと、そこで出た課題
メリット:大量データの一次処理に向いたバランス
Geminiは、今回のような 「数千件規模のテキストを一括で処理する」バッチ処理 との相性が良いと感じました。
- ある程度シンプルなタスク(候補抽出・ラベリング・分類など)を大量に投げられる
- Batch APIを使えば、通常APIよりも安い料金体系で利用できる
- 「数秒〜数分で返ってくれば十分」というバッチでは、レイテンシもそこまで問題にならない
この段階では、以下のような処理をGeminiに任せています。
- 記事本文+メタデータをまとめて投入
- 「条件に合いそうなレコードかどうか」をざっくり判定
- 候補っぽいものだけを次のフェーズに渡す
一次抽出はGemini、最終確定はClaude という構成の「一次抽出側」です。
課題1:「良かれと思って」行われた解釈が致命的
一方で、Geminiを使っていて特に気になったのが、数値への深読みでした。
たとえば、次のようなデータを渡したとします。
{
"url": "example.com/archives/20221201/news.html",
"content": "来年(2025年)の開催が決定しました!"
}
この場合に期待する挙動は、「本文に書いてある 2025 年の情報を採用する」ことです。
しかし実際には、URLパスに含まれる 20221201 に引っ張られてしまい、「日付: 2022-12-01」 と誤って解釈してしまう出力がありました。
逆もあり、2025年の開催なのだからURLがexample.com/archives/20251201/news.htmlになって出力されてしまうこともありました。
こういった挙動もあり、「それっぽい数字」に意味づけしにいく傾向が見えてきました。
特にURLは改変されることはpromptに明示しても防ぐことは難しく
- 「Inputとして与えられたURLをそのまま返す」
- 「入力中の数値は「値」ではなく文字列として扱い、解釈を行わない」
といったルールをプロンプト側で明示することで、ある程度挙動が落ち着きました。
Tips: キー名は短く、意味の説明はプロンプト側に寄せる
上記とは別に、大量のデータを一度に投げるときのトークン節約として効いたのが、入力JSONのキー名を短くする工夫です。
// 意味のあるキー名
[
{
"title": "...",
"date": "...",
"source_url": "..."
}
]
// 記号化したキー名
[
{
"t": "...",
"d": "...",
"u": "..."
}
]
プロンプト側で、「t はタイトル、d は日付フィールド、u は元データのURLです」とだけ説明しておき、
各レコードには短いキーだけを使うようにします。
レコード数が多いと、キー名の長さがそのままトークン数に効いてきます。
このやり方だと、
- 1レコードあたり数トークンずつ節約できる
- 100〜1000レコード単位のバッチでは、入力トークンの削減効果が馬鹿にならない
という形で、「ユーザープロンプトに意味を書いて、JSON側は極力記号に寄せる」設計がコスト面で効いてきました。
課題2:思考ループと数値の繰り返し
もうひとつ、実務でかなり困ったのが 「出力がループして壊れる」 問題です。
特に、
- 0 に近い temperature
- 思考設定がデフォルト
という設定にしていたときに、数値を含むタイトルの繰り返しが発生しやすくなりました。
イメージとしては、
七五三, 七五三, 七五三, 七五三, 七五三, 七五三, 七五三…
のように、同じフレーズが延々と出力され、最終的には max_tokens まで到達してしまい、JSONですらなくなる パターンです。構造化出力していてもこの現象が発生しました。
これに対しては、設定面で次のような調整をしました。
-
temperature を 0 → 1 付近まで上げる
- geminiでは完全に決め打ちの出力を求めるよりも、ランダム性があったほうが同じトークンを繰り返すループに入りにくかった。
-
思考用トークンの上限をしっかりと設定する
- 「たくさん考えてほしい」と思って無制限にしているとループの発生率も上がり、処理時間が長くなる原因にもなります。
- 「ここまで考えたら出力に移る」という上限を設けると余分な思考が発生しにくくなりました。(今回の実験では4096に設定しています。)
結果として、
- ループしてJSONが壊れる頻度がほぼ0に。
- 「出力が途中で崩れたのでパースできない」というケースもほぼ0に。
という形で、「プロンプト」だけでなく「パラメータ設定」もかなり効いていると感じました。
課題3:Markdownコードブロック問題と構造化出力
さらに、システム組み込みの観点で効いてきたのがMarkdownのコードブロック問題です。
Gemini 2.5 世代をテキストモードで使っていたときは、「JSONで出力してください」とお願いすると、かなりの頻度でこう返ってきました。
geminiの感想つらつら
```json
{
"records": [...]
}
```
人間が眺めるぶんには読みやすいのですが、システム側ではそのまま JSON.parse() したいので、
- コードブロックのバッククォートを削る
- 前後の説明テキストを削る
といった前処理が必要になります。
ここは割り切って、レスポンスを正規表現などで前処理してからJSONとして扱う レイヤーを挟めばいいだけなのですが、組み込むには少し安定性にかけて不安な部分がありました。
一方で、Gemini 3 Preview でStructured OutputとGrounding with Google Searchが併用して使えるようになりました。
そのため、新しく実装するなら構造化出力を前提に組むほうがシンプルで、上記のような正規表現レイヤーは歴史的経緯、という位置づけになりつつあります。新しいモデルを使いましょう!!!
4. プロンプトエンジニアリングの比較:ClaudeとGemini
同じタスクでも、ClaudeとGeminiでは刺さるプロンプトの作り方がけっこう違うと感じました。
ここでは、「ドメイン固有データの抽出・検証」というタスクで実際に効いた設計方針をまとめます。
Claude向け:作業手順書を渡す
Claudeはもともと「慎重によく考える」タイプのモデルですが、実際に触ってみると、
- ふんわりした「専門家ロール」を与える
というよりも - 作業手順書+ツールの入出力仕様をそのまま渡す
くらいのほうが、安定して動いてくれました。
ここでいう「作業手順書」は、だいたい次のような要素を含んだSystem Promptです。
- どんな入力が来るか(例:
{ "date": ..., "titles": [...] }のようなJSONの形と例) - どんな順番で何を確認するか(1→2→3…と決定的なプロセスで書く)
- どの条件なら採用し、どの条件なら捨てるのか(IN/OUTの境界)
- どのツールを、どんなスキーマで呼び出すか(フィールド名・型・配列か単体か)
- 最終的に「見えてよい出力」は何だけか(例:ツール呼び出し1発だけ など)
たとえば、
「ある日付の候補タイトルが、本当にその日に日本で行われる“出来事”なのかを一次情報源ベースで検証し、
確認できた場合だけscrutiny_titlesという構造化ツールを使用し出力を行ってください。」
といったタスクでは、
- 日付は入力JSONを真とする(URLや記事の公開日で上書きしない)
- 一次情報源で確認できない場合は
verified_events: []にする -
source_urlはイベント個別ページのHTTPS URLだけを許可(一覧ページ・トップページは禁止) - 最終出力は
scrutiny_titles({ "scrutiny_titles": [...] })だけで、説明文は一切禁止
…といった形で、プロセスとToolの入出力仕様をかなり細かく「契約」として書いておくと、そのとおりに守ってくれる確率がかなり高くなりました。。
同じように、日本語の文字数カウントのようなタスクでも、
- 何を1カウントとみなすか(結合文字や絵文字をどう扱うか)
- この入力なら結果はいくつになる、という具体例
をSystem Prompt側にきちんと並べておくと、「専門家っぽいロールを演じさせる」よりもずっと安定して、同じルールで数えてくれるようになります。
また、Claudeに対しては 「やってほしくないこと」を否定形で並べるより、「やるべきこと」を肯定形で指定する ほうが、挙動が安定しやすいと感じています。
- 「URLに含まれる数字を日付だと勘違いしないでください」
+ 「日付は本文や一次情報源に明示されている値のみを使用してください」
のように、禁止事項の列挙ではなく「この条件を満たすものだけを採用する」と書くイメージで、作業手順書を組み立てるのがポイントでした。
Gemini向け:役割とFew-shotをセットで渡す
Geminiについても、最初は「役割+Few-shot」が効くな、というところから入ったのですが、
実際に運用してみると 「やること」と同じくらい「やらないこと」をハッキリ書く のが重要だと感じました。
特に今回のようなバッチ処理では、次の3点をセットでSystem Promptに書いておくと安定しました。
- 役割の範囲をかなり狭く定義する
- JSONの入出力仕様(キー設計・圧縮方針)をプロンプト側で宣言しておく
- 数字はただの文字列として扱い、計算や推論は「専門外」としてやらないと明示する
役割は「これだけやる人」に絞る
たとえば、
「大量のタイトルをクラスタリングして代表タイトルを返す“だけ”を担当するアシスタントです。
Web検索や日付の解釈、統計的な推定は行いません。」
のように、やる作業とやらない作業をまとめて宣言しておきます。
ここで「専門家ロール」を盛りすぎるよりも、「あなたの仕事はこの1ステップ」という作業指示に寄せた方が素直に動く印象でした。
JSONのキー定義と圧縮方針をプロンプト側に寄せる
本文でも書いたように、レコード数が多いときは キー名を圧縮するだけでもトークン節約効果が大きい です。
Gemini向けのプロンプトでは、最初にこんな感じで宣言しておきます。
# Input
JSON array of objects: [ { "t": "<title>" } ].
- "t" は元のタイトル文字列です。タイトル中の数字も全て文字列として扱います。
- どのような意味の数字であっても、加算・減算・比較などの計算は行いません。
出力側も、同じように 圧縮したキー名と厳密なフォーマットを決めておきます。
# Output Format
- 返すのは JSON 配列のみです(コードブロックや説明文は禁止)。
- 各要素は { "t": "<代表タイトルや結合タイトル>" } という形式にしてください。
- 数字は全て文字列として出力し、日付変換やフォーマット変更は行わないでください。
- 専門外の処理(数値計算・日付推定・意味推論など)は一切行わないでください。
こうしておけば、
- 意味づけは System Promptに書けば理解してくれるので入力/出力のJSONはキーを短く保てる
- 数値に対して「計算したり、日付として解釈したりする」余地が減る
という形で、トークン節約とハルシネーション抑制を同時に狙えます。
「専門外のことはしない」をハッキリ書く
特に数字まわりで変な挙動を防ぐために効いたのが、
- 「数字はすべて 文字列 として扱う」
- 「日付の推定・変換・端数処理などの 計算タスクは専門外」
- 「統計・集計・四則演算・日付差分の計算は行わない」
といった “やらないことリスト”を明記する ことでした。
これを書いておくと、
- URL中の
20221201を勝手に日付として解釈する - 題名に含まれた数字から「これは価格だろう」と推測する
- 文字数タスクではないのに長さを数え始める
といった「気を利かせすぎた推論」がかなり減ります。
Few-shotは「境界ケース」を多めに
Few-shot自体の考え方は従来どおりで、2〜3例ほど入れておくと出力のブレが減ります。
ここで意識したのは、「きれいな例」だけでなく “どんなものは絶対にいらないか / どんなものは積極的に残したいか” の感覚をざっくり共有しておく ことです。
- 「こういうクラスのものはまとめて除外」
- 「こういうクラスのものはまとめて残す」
という カテゴリレベルの例示 を入れておくと、
Gemini側のフィルタリングの感覚をこちらの意図に寄せやすく、出力の安定感も増しました。
- どこまで拾うか(網羅性)のライン
- どこからは「専門外」として手を出さないか
の感覚を、Gemini側にも共有しやすくなります。
まとめると、Geminiに対しては、
- 役割を「この処理だけ」のレベルまで絞る
- JSONのキー設計(圧縮)と意味づけをSystem Prompt側で宣言する
- 数字は文字列として扱い、計算・推論・日付変換など「専門外のことはやらない」と明記する
- Few-shotでは、OKなケースとNGなケースの両方を例として見せる
というセットでプロンプトを組むと、今回のかなり効率よく安定して動いてくれました。
5. Batch API活用:非同期前提で設計してコストを落とす
Gemini, Claudeのコスト最適化で効いたのが Batch API です。
Batch APIの位置づけ
Batch APIは、ざっくり言うと
- リアルタイムレスポンスは不要
- 代わりに、まとめて投げておいて、あとから結果を取りにいく
という使い方に特化したエンドポイントです。
料金体系も、通常APIより割安に設定されています。
今回のような「数千件を日次で処理するが、数分〜数十分後に結果が返ってくればよい」タスクであれば絶対にBatch APIを利用すべきだと感じました。
料金の細かい計算まではここでは触れませんが、すべてを高価なモデルだけで処理していた頃と比べると、プロンプトキャッシュも含めとんでもなくコストを圧縮できている感覚があります。
定期的に回すバッチ処理であれば、Batch API前提で設計する価値は十分あると感じています。
6. ハイブリッド構成の最終形:どこまでをGeminiに任せるか
最終的に、次のような役割分担がしっくりきました。
フェーズ1:Geminiで広く集める
- 入力:記事本文、URL、その他のメタ情報を含むJSON
- 出力:
- 「条件に合いそうなレコード」のリスト
- 必要に応じたグルーピングやスコアリング
この段階では、多少のノイズは許容する代わりに、取りこぼしを少なくすることを重視しています。
フェーズ2:Claudeで精密検査と確定
- 入力:Geminiが「候補レコード」と判定したもの
- 役割:
- Web検索などを行い、一次情報源・公式な情報源を探す
- 名称・日付・場所などが一次情報と整合しているか照合する
- 最終的なJSONレコードとして確定させる
ここでは逆に、ノイズをできるだけ削り、後続のバッチやプロダクト側の処理にそのまま流せる形に整えることを重視しています。
コストと品質のバランス
この構成にしてからは、ざっくり言うと、
- コスト:すべてをClaudeだけで処理していた頃と比べて、かなり小さいオーダーに収まっている
- 品質:
- 無差別な収集ではなく、厳選した情報だけをソースに
- 構造面は各種ツールで確実に整形しながら
- 検索ツールの活用でリアルタイムな情報を収集できるようになった
「どこまでをGeminiに任せるか」「どこからClaudeに渡すか」はプロダクトごとに違うと思いますが、「一次抽出は安いモデルで、最終確定は従順なモデルで」という設計の方向性自体は、他のユースケースでも流用しやすいパターンだと感じています。
7. まとめ:モデルの“性格”を前提にした設計をする
今回を携わってきたことから、いちばん大きかった学びは、
「プロンプトを工夫する」だけでなく、「モデルの性格を前提にしたアーキテクチャを組む」ことが効く
ということでした。
ざっくり整理すると、こんなイメージです。
-
Gemini
- 大量データの一次処理や候補抽出に向く
- 役割とFew-shotをしっかり渡すと安定しやすい
- キー名短縮でトークンを節約しつつ、前処理やルール明示で過剰な“深読み”を抑えると扱いやすい
-
Claude
- Tool Useによる構造化出力が安定している
- 思考の手順を先に渡すと、検証・照合作業を任せやすい
- 「最後の1マイル」を整える役として、システムに組み込みやすい
LLMをシステムに組み込むとき、つい「単体で何でもやってほしい」と考えがちですが、
実務的には、
- モデルごとの得意・不得意
- コスト/速度/安定性のバランス
- どこでハルシネーションや出力崩れを受け止めるか
といった観点で役割分担を考えたほうが、結果的に落ち着いた構成になると感じています。
AIで何かしよう!と言われ、「大量の非構造化データをさばきたい」「でもコストも怖いし、AIが安定しないし」と悩まれている方の参考になれば幸いです。
ここまで読んでいただきありがとうございました!!!
参考リンク
TimeTreeのエンジニアによる記事です。メンバーのインタビューはこちらで発信中! note.com/timetree_inc/m/m4735531db852
Discussion