6ヶ月前、チームでカスタマーサポートbotを本番リリースした。そのbotは自信満々に「返品期間は60日です」とユーザーに答え続けた。実際は30日だった。2023年の製品ドキュメントでファインチューニングしたモデルで、ポリシーが変更されていたことを誰も——自分も含めて——確認していなかった。300件のサポートチケットが飛んできて、同僚がQuarterlyのレビューで恥をかいた。
そこからRAGで作り直して、今は安定して動いている。
この経験があるから、「RAGとファインチューニングはケースバイケースです」だけで終わるブログ記事を読むたびに少しイライラする。どちらを選ぶかには具体的な結果が伴う。比較表より、その結果から学んだことの方がよほど役に立つ。
ということで、2つのプロダクトで3つのLLM機能を作り、18ヶ月ほどこのテーマに向き合ってきた経験から書く。
ファインチューニングは見た目より正当化が難しい
ファインチューニングは、モデルに特定の振る舞いをさせたい時の自然な選択肢に見える。自社データで訓練して、ドメイン知識を焼き込む。最初の自分もそう思っていた。
社内コードレビューアシスタントを作った時がその典型だった。18ヶ月分のPRコメント——シニアエンジニアたちの知見が詰まったやつ——があった。これは良い訓練データになると確信していた。
OpenAIのファインチューニングAPI(当時はgpt-3.5-turbo、2024年半ば)を使い、4回ほどの訓練を経てアウトプットがチームのレビュースタイルに近づいてきた。短く直接的なコメント。無駄な褒め言葉なし。社内スタイルガイドへのリンク付き。チームの反応も悪くなかった。
で、そこに新しいエンジニアが3人入ってきて、スタイルガイドも更新された。突然、ファインチューニングしたモデルが古い慣習——明示的に廃止したやつ——を教え始めた。再訓練にはコストがかかり、数日間のイテレーションが必要になる。さらに厄介なのは、新しい訓練データのキュレーション自体がゼロコストじゃないということ。
ここで気づいた本質的な問題:ファインチューニングはモデルに「どう答えるか」を教えるのであって、「何を知っているか」ではない。「特定のトーンで話す」とか「常にJSONの特定フィールドでアウトプットする」という用途なら、複雑さは正当化される。でも「製品について正確に答える」という用途なら、データの陳腐化という上り坂を登り続けることになる。
もう一つ見落としていたこと:評価の問題が思ったより大変だった。ファインチューニングしたモデルが本当に改善されているかどうか、どうやって判断する?評価インフラに、訓練自体とほぼ同じくらいの時間を使ってしまった。それがなければ、基本的に勘で進めることになる。
ここから学んだこと: ファインチューニングが有効なのは、問題が「振る舞い」に関するもの——一貫したフォーマット、トーン、タスク構造——で、その振る舞いが頻繁に変わらない場合。情報が更新され続ける用途には、最初からRAGの方が合っている。
RAGも銀の弾丸ではなかった
返品ポリシーの失敗の後、RAGが全ての答えだという確信を持ちすぎた。少し恥ずかしいが、2ヶ月くらい、あらゆるアーキテクチャ議論で「RAG使えばいい」と言い続けた——RAGが本当に苦手なユースケースに出会うまで。
契約書要約ツールを作っていた。契約書をチャンクに分割してベクターストアに保存し、関連する条項を取得してモデルに要約させる設計。シンプルに見えた。取得部分は動いた。問題は、法的文書には複雑な相互参照がある——「第4.2条(b)で定義されるとおり」——のに、チャンク戦略がその定義を参照元の条項から切り離していたこと。モデルは不完全なコンテキストで答えていたが、それが不完全だとすら認識できていなかった。
RAGの品質はチャンクと取得の戦略で決まる。これをちゃんと説明している入門チュートリアルはほとんどない。最終的には、大きめのチャンク、オーバーラップ、そしてCohereのrerank APIを使った再ランキングのハイブリッドアプローチに移行した。改善はしたが、そこに到達するまで数週間かかった。
日本語特有の落とし穴もある。英語コーパス中心で学習したembeddingモデルを日本語テキストにそのまま使うと、精度が落ちることがある。text-embedding-3-smallは日本語でもそれなりに動くが、日本語文書が大量にあるプロジェクトではintfloat/multilingual-e5-largeを試す価値はある。自分で直接比較したわけではないので、ユースケースによって変わるとは思うが、頭に入れておいて損はない。
それからレイテンシ。すべてのクエリがベクターDBを叩き、類似検索して、チャンクを取得して、コンテキストウィンドウに詰め込み、そこからLLMの推論が走る。自分の環境(Pinecone + GPT-4o)では、モデルの推論時間に加えて800ms〜1.2sが上乗せされた。非同期タスクなら問題ない。インタラクティブに見せたいものだと、体感できる遅さになる。
# 最初に使っていた取得ロジック——再ランキングなし版
from pinecone import Pinecone
from openai import OpenAI
pc = Pinecone(api_key="...")
index = pc.Index("contracts-v2")
client = OpenAI()
def retrieve_and_answer(query: str, top_k: int = 5) -> str:
# クエリをembedding化
query_embedding = client.embeddings.create(
input=query,
model="text-embedding-3-small"
).data[0].embedding
# シンプルな上位K件取得——再ランキングなし
results = index.query(
vector=query_embedding,
top_k=top_k,
include_metadata=True
)
# 取得したチャンクからコンテキストを構築
context = "\n\n---\n\n".join(
r["metadata"]["text"] for r in results["matches"]
)
# モデルが参照できるのはここに入れたコンテキストだけ
# 重要な相互参照がチャンクをまたいで分断されていると、それは見えない
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": "提供されたコンテキストのみをもとに回答してください。"
},
{
"role": "user",
"content": f"コンテキスト:\n{context}\n\n質問: {query}"
}
]
)
return response.choices[0].message.content
このtop_k=5の単純な取得を、20件の候補取得から上位5件に再ランキングする2段階アプローチに変えた。その変更だけで、他のどの改善より回答精度が上がった。
ここから学んだこと: RAGは、データが変化する場合、ソース帰属が必要な場合、大規模なドキュメントコーパスを扱う場合に強い。ただし取得パイプラインは本物のエンジニアリング作業だ。その工数を最初から見積もりに入れておくこと。
両方を組み合わせるのが意味を持つケース
両方で失敗を経験してから、組み合わせて使っているチームのケースに注目し始めた。そこで「なるほど」と思えるパターンが見えてきた。
ファインチューニングで振る舞いとスタイルを、RAGで知識を扱う。モデルが「何を知っているか」と「どう答えるか」を分離するイメージだ。
具体例:社内プラットフォームのドキュメントアシスタントを作った。モデルに必要だったのは3つ——(a)特定の構造化フォーマットで常に答えること、(b)スプリントごとに変わるドキュメントに追随すること、(c)幻覚するより「分からない」と正直に言うこと。
適切な不確実性を表現した整形済み回答の例でファインチューニングした小さめのモデルを、クエリ時にドキュメントを取得するRAGパイプラインに組み込んだ。
結果はどちらか単体より良かった。APIの仕様を幻覚しなくなった(コンテキストに正しい情報があるから)。アウトプットのフォーマットが安定した(それを訓練で入れたから)。プロンプト単体では安定させるのが難しい「分からない」の挙動が、ファインチューニング後は信頼できるものになった。
# 組み合わせた実装——シンプルにまとめたバージョン
def answer_with_finetuned_rag(query: str) -> str:
# 関連ドキュメントを取得
docs = retrieve_docs(query, top_k=5)
context = format_context(docs)
# ファインチューニング済みモデルで一貫性のある回答を生成
# ft:gpt-3.5-turbo-0125:our-org:docs-assistant:abc123 — 自分たちのチェックポイント
response = client.chat.completions.create(
model="ft:gpt-3.5-turbo-0125:our-org:docs-assistant:abc123",
messages=[
{
"role": "system",
"content": (
"あなたはドキュメントアシスタントです。"
"提供されたコンテキストのみから回答してください。"
"コンテキストに回答がない場合は、その旨を明示してください。"
)
},
{
"role": "user",
"content": f"ドキュメントコンテキスト:\n{context}\n\n質問: {query}"
}
],
temperature=0.2 # フォーマットの一貫性を高めるため低めに設定
)
return response.choices[0].message.content
ただ、これが常に正当化されるとは言わない。ファインチューニング済みモデルを運用するということは、そのチェックポイントを自分たちで管理するということ。OpenAIがベースモデルを廃止すれば、再訓練が必要になる。それは本物の運用コストだ。
ここから学んだこと: 組み合わせが複雑さに見合うのは、プロンプトだけでは解決できない明確な振る舞いの問題があり、かつ知識ベースが動的に更新される——その両方が成立する場合のみ。どちらか一方が当てはまらないなら、シンプルな方を選ぶこと。
実際のところ、どちらを選ぶべきか
全部経験した上で、今の自分の判断フレームワークはかなりシンプルになった。3つの質問を順番に答える。
1. 再訓練のサイクルより速くデータが変わるか?
「はい」ならRAGが必要。これはほとんどのプロダクトのユースケースに当てはまる——サポートbot、ドキュメントアシスタント、チームが更新し続けるコンテンツDBを扱うもの全部。
2. プロンプトでは直せない一貫した振る舞いの問題があるか?
具体的に言うと、複数のシステムプロンプトのバリエーションと少数ショット例を試した上でも、アウトプットが安定しない、ということ。「はい」ならファインチューニングを検討する価値がある。本格的なプロンプトエンジニアリングをまだ試していないなら、そちらを先にやること——イテレーションが速くてコストが低い。
3. 性能の差が運用コストに見合うか?
古いベースチェックポイントでファインチューニングしたモデルは、最新のフラッグシップモデルよりトークン単価が安いことがある。1日に何百万回もAPIを叩くなら、再訓練コストを含めても元が取れる計算になることはある。社内ツールを作っているほとんどのチームには当てはまらないが。
正直に言う。自分が見ているほとんどのアプリケーション——これは一般論ではなく、実際に目にしたプロダクトの話として——には、RAGが正しいスタート地点だ。データの鮮度問題だけで、知識検索系のユースケースからファインチューニングは外れる。ファインチューニングに手を出す前に、チャンク戦略、embeddingモデルの選択、取得品質に力を注ぐべきだ。
例外は、プロンプトでは指定しにくい振る舞いが必要な場合——強く制約された出力スキーマ、特殊な推論パターン、安定させる必要のあるトーン要件。その場合はファインチューニングが意味を持つ。
チームの誰かが「ドキュメントをモデルにファインチューニングしよう」と言い出したら——これを一度以上聞いたことがある——「更新された時どうする?」と聞いてみるといい。大抵はそこで整理がつく。
まとめ
今回は RAG vs ファインチューニング: LLMアプリケーションでの使い分け完全ガイ について詳しく解説しました。以下に本記事の重要なポイントをまとめます。
重要なポイント
- 基本概念:core concept を正しく理解することが、production ready な実装への近道です。
- 実装方法:Python や TypeScript など主要な programming language でのimplementation が可能で、official SDK や library を活用することで開発効率が向上します。
- パフォーマンス:response latency や throughput を考慮したsystem design が重要です。load testing と monitoring を組み合わせて最適化しましょう。
- コスト管理:API usage cost を最小化するため、caching strategy や rate limiting の実装を検討してください。
- セキュリティ:API key management、input validation、output sanitization など、security best practices を必ず遵守してください。
次のステップ
まずは small scale な prototype を作成して動作を確認し、staging environment でのテストを経てから production deployment を進めることをお勧めします。
GitHub や official documentation、Stack Overflow などのリソースも積極的に活用してください。open source community forum や issue tracker への参加も、knowledge sharing の観点から大変有益です。疑問点があればコメント欄でお気軽にお知らせください。