去年の11月、社内の技術ドキュメント検索システムを作っていたときのこと。上司から「このAIが古い情報を平気で返してくるんだけど、ファインチューニングすれば直るんじゃないか?」と言われた。当時の自分は「まあ、そうかもしれないですね」と曖昧に答えてしまった。
正直に言うと、その時点でRAGとファインチューニングの違いをちゃんと理解していなかった。なんとなく「ファインチューニング = モデルを賢くする」「RAG = 外部データを参照する」くらいの認識しかなかった。そこから2週間、両方を実際に試して比較した。このブログはそのときに学んだことの記録だ。
「古い情報問題」から始まった調査
まず、問題を整理した。うちのケースは:
- 社内Confluenceに約3,000ページのドキュメントがある
- 毎週更新される(APIの仕様変更、新機能の追加など)
- GPT-4を使ってQ&Aシステムを作りたい
ファインチューニングを最初に試してみた。GPT-3.5-turboで、Confluenceから抜いた500ペアの質問/回答データを使ってOpenAIのfine-tuning APIに流した。費用は約$40。3時間待って、できあがったモデルに「最新のデプロイ手順は?」と聞いたら、見事に3ヶ月前の手順を返してきた。
そりゃそうだ。ファインチューニングのデータを作った時点の情報しか知らないんだから。
ファインチューニングが実際に何をしているか
ファインチューニングは、モデルの重みを更新する。平たく言うと「モデルの脳みそそのものを書き換える」作業だ。
何が変わるかというと:
– 回答のスタイルや形式
– 特定ドメインの専門用語への慣れ
– タスク固有の出力パターン(例:「必ず箇条書きで答える」)
何が変わらないかというと:
– 学習データに含まれていない新しい事実
– トレーニング後に発生したイベント
– リアルタイムの情報
これを理解すると、ファインチューニングが向いているケースが見えてくる。「モデルの振る舞いを変えたい」ときだ。知識を増やしたいときではない。
例えば、うちのチームで実際に効果があったのは、カスタマーサポートのトーンを統一するケースだった。「もっと丁寧に、でも冗長にならず、技術的な質問には具体的なコードも添えて」というスタイルをファインチューニングで焼き込んだら、system promptだけで調整するよりも安定した出力が得られた。プロンプトで頑張るより、コストあたりのトークン消費が減ったのも意外な副産物だった。
RAGが解決すること(そして解決しないこと)
RAGは根本的にアーキテクチャの話だ。モデルを変えるのではなく、モデルが参照できる情報を動的に変える。
基本的な流れ:
# LangChainを使ったシンプルなRAGの実装例
# langchain==0.3.x、openai==1.x で動作確認済み
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import ConfluenceLoader
# Confluenceからドキュメントをロード
loader = ConfluenceLoader(
url="https://yourcompany.atlassian.net/wiki",
username="[email protected]",
api_key="your_api_key",
space_key="DEV",
)
docs = loader.load()
# ベクトルDBに保存(初回のみ)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
documents=docs,
embedding=embeddings,
persist_directory="./chroma_db"
)
# 質問応答チェーンを構築
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=vectorstore.as_retriever(
search_kwargs={"k": 5} # 上位5件を参照
),
return_source_documents=True, # 出典を返す(デバッグに超便利)
)
# クエリを実行
result = qa_chain.invoke({"query": "最新のデプロイ手順は?"})
print(result["result"])
# source_documentsを見れば、何を参照したかがわかる
for doc in result["source_documents"]:
print(f"出典: {doc.metadata.get('title', 'Unknown')}")
これでConfluenceが更新されるたびにベクトルDBを再インデックスすれば、常に最新の情報を返せる。うちでは毎日深夜にバッチで走らせている。
ただし、RAGにも限界がある。自分がやらかした失敗を話すと——
チャンクサイズを適当に設定したまま本番投入したら、回答の品質がひどかった。デフォルトの1000文字チャンクだと、コードブロックが途中で切れたり、文脈が断ち切られたりする。結局、RecursiveCharacterTextSplitterのchunk_sizeを500に下げ、chunk_overlapを100に設定しなおすまで2日ムダにした。チャンキング戦略はRAGの品質に直結する。ここを雑にやると後で絶対に泥沼にはまる。
あと、RAGは「検索ミス」に弱い。ユーザーの質問と、ドキュメントの書き方に乖離があると、正しいチャンクが取得されない。「デプロイ手順」と聞いているのに、ドキュメントには「リリース方法」と書いてあるだけだと、コサイン類似度的に遠くなる。HyDE(Hypothetical Document Embeddings)やクエリ拡張で改善できるけど、それはまた別の話。
コスト・運用コストで比べると
これが個人的に一番実感のある差だと思っている。
ファインチューニング
- 初期費用:データ準備(一番時間がかかる)+学習費用
- 更新のたびに再学習が必要
- OpenAIだと学習済みモデルを保管する費用もかかる($0.002/1K tokens at storage、2025年時点)
- データが古くなっても、「壊れた」とわかりにくい
うちの試算では、Confluenceの規模感(3,000ページ、毎週更新)だとファインチューニングの維持は現実的じゃなかった。毎週再学習するコストもそうだけど、「このモデル、ちゃんと最新情報を知っているっけ?」という不安が常につきまとう。
RAG
- 初期費用:ベクトルDB構築+インデックス作成
- 更新はインクリメンタルにできる(新しいドキュメントだけ追加)
- 何を参照したか追跡できる(デバッグ・監査に有利)
- インフラが増える(ベクトルDB、インデックスパイプライン)
Chromaをローカルで使えば安く済むけど、スケールするならPineconeやWeaviateが現実的。Pineconeは無料枠でかなりの量が入るので、プロトタイプ段階はそれで十分だった。
じゃあ、どっちを選ぶべきか
正直に言う。「ケースバイケース」と言うのは簡単だけど、それだと何も決まらない。自分の経験から言えば、判断基準はこうだ。
ファインチューニングを選ぶ場面:
モデルの「話し方」を変えたい
特定のフォーマット(JSON出力を絶対守らせるとか)、特定のトーン、特定のタスクへの特化。知識ではなく「癖」を変えたいとき。データが静的で、更新頻度が低い
医療分野の診断コード体系、法律用語の解釈方法、みたいに「1年に1回更新されるかどうか」程度のドメイン知識ならファインチューニングが向いている。推論コストを下げたい
RAGはリトリーバル+コンテキストウィンドウへの詰め込みでトークンが増える。ファインチューニングで「知識を焼き込んで」しまえば、プロンプトをシンプルにできる。(ただし、これが本当に効くかはケースによる——100%確信はない)
RAGを選ぶ場面:
データが頻繁に更新される
これは絶対RAG。毎週どころか毎日更新されるドキュメントをファインチューニングで追いかけるのは現実的じゃない。何を参照したか説明責任が必要
「このAIがこう言った根拠は?」と聞かれたとき、RAGなら出典を示せる。ファインチューニングされたモデルは「なぜそう知っているのか」を説明できない。コンプライアンスや内部監査が厳しい業界では重要。プロトタイプを早く作りたい
RAGは今日始めて今日動く。ファインチューニングのデータ準備は地味に時間がかかる。データ量が少ない
ファインチューニングは最低でも数百〜数千のサンプルが必要(質が高ければ少なくて済む場合もあるけど)。小規模なユースケースならRAGの方がコスパがいい。
現実的な選択:両方使う
一つ言い忘れていた。実は「どちらか」という二択じゃなくていい。
うちの最終的な構成は、ベースモデルにRAGを組み合わせた上で、回答フォーマットのコントロールだけをsystem promptでやっている。「ファインチューニングでフォーマットを固定して、RAGで知識を補う」というハイブリッドも選択肢としてある。ただ、複雑さが増すので、まずはどちらか一方から始めることをすすめる。
自分がいま新しいLLMアプリを作るとしたら、デフォルトの選択はRAGだ。理由は単純で、「間違いを修正しやすい」から。ファインチューニングで変な癖がついたモデルを直すのは、ベクトルDBのインデックスを更新するより何倍も面倒だった。
「でも精度がもっと欲しい」という話になったとき、初めてファインチューニングを検討する。その順番で考えると、無駄な学習コストを払わずに済む。
ここまで読んでわかったと思うけど、どちらの技術も「銀の弾丸」ではない(当たり前だけど)。うちのケースで一番時間を取られたのは実装ではなく、「どっちが適切か判断する」ことと「チャンキング戦略を最適化する」ことだった。
コードを書く前に、まず「自分が変えたいのはモデルの知識か、振る舞いか」を問いかけてみてほしい。その答えが出れば、技術の選択はほぼ自動的に決まる。
まとめ
今回は 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 の観点から大変有益です。疑問点があればコメント欄でお気軽にお知らせください。