
はじめに
LLMのAPIを叩いて何かを作ること自体は、ずいぶん手軽になりました。プロンプトを書いて実行すれば、それらしい出力が返ってきます。
ただ、「手元で動くもの」と「お客様に提供できる機能」の間には、かなりの距離があります。手元では良い感じの出力が出ていたのに、いざ幅広いデータで試すと精度が安定しない。精度は出たけれど処理が遅すぎる、あるいはコストが見合わない。本番のコードに載せ替えたら、なぜか検証時と結果が変わってしまう。このあたりで足踏みした経験のある方も多いのではないでしょうか。
そこで本記事では、LLMを使った機能開発を進める際の「何から手をつけて、どういう順番で進めるべきか」という進め方について、4つのステップに分けて整理してみます。
特定のフレームワークやサービスの使い方の話ではなく、AI機能を開発する際の「型」の話です。これから機能開発に着手する方や、一度作ってみたものの本番化で足踏みしている方の参考になれば幸いです。
開発フローの全体像
まずは全体像です。次の4ステップで進めるのが良いと考えています。
- 見極めと評価準備:AIで解けるタスクかを確かめ、精度を評価する仕組みを作る
- 評価と改善:目標ラインを設定し、達するまで評価と改善を繰り返す
- FIXと本番実装:構成を凍結し、本番用コードに載せ替えて再検証する
- リリース後の運用と継続的改善:実データで弱点を見つけ、ステップ2のサイクルに戻る
1〜3はリリースまでの一本道ですが、4だけは性質が違います。リリース後に実データを使ってステップ2の評価と改善に戻ってくる、大きなループになっています。
以降で、それぞれのステップを順に見ていきます。
1. 見極めと評価準備
AIで解けるタスクかの見極め
最初にやるべきは、解決したいタスクがそもそもAIで解けるものなのかを確認することです。前提として、そのタスクが「人間なら解決可能で、人間が解決手順を整理して説明できること」を満たしているかを考えます。人間が説明できない仕事は、AIにも任せられません。
そのうえで、利用可能な中で最も性能の良いモデルで、タスクが解決できるかを試します(動かすための最低限のコードとプロンプトは用意しておきます)。
- 最高性能のモデルでも解けない場合は、タスクそのものを見直します。AIに任せられそうな作業だけを切り出す、作業を細かく単純なものに分割してそれぞれをAIに解かせる、といったアプローチが有効です
- 解けた場合は、性能の劣るモデルでも解けるかを試して使えるモデル性能の下限を探り、予算やパフォーマンスの要求仕様に合うモデルの目星をつけておきます
※この時点では、少量のケースで「だいたい解けそうか」を人手で確認する程度で十分です。プロンプトの作り込みもまだしません。幅広いケースで解けるかどうかは、後のステップで検証します。
精度評価方法の構築
AIで解けそうだと確認できたら、より多くのデータで定量的に性能を評価する方法を用意します。必要なのは次の3つです。
- ベンチマークデータ:実際に扱うことになるデータのパターンを、できるだけ網羅するように幅広く選びます
- 正解の回答例:そのデータが入力されたとき、どんな出力であれば正解なのかの例を用意します
- スコア算出方法:AIの出力と回答例を突き合わせてスコアを出す仕組みです。データ読取のように正解が明確に決まるタスクなら正解率/適合率/再現率、文章生成のように出力が正解と一致するとは限らないタスクなら、ベクトル化とコサイン類似度や、LLMによる一致度評価が候補になります。「XXXが書かれていること」のような基準を予め設定し、出力が基準を満たすかをLLMに判定させる方法もあり、この場合は正解例がなくても評価できます
評価の仕組みが整ったら実際にスコアを算出してみて、そのスコアがAIの出力に対する人間の印象と一致するかを確認します。特に文章生成のようなクリエイティブ要素のあるタスクでは乖離が起こりやすく、例えば人間が「80点くらいかな」と感じる出力に評価スコアが50点しかつかないなら、評価基準が厳しすぎると考えて基準を緩めることを検討します。
2. 評価と改善
現状把握と目標ラインの設定
評価方法ができたら、まず最低限の実装の状態で評価を行い、現状を把握します。このとき精度だけでなく、処理速度、エラーの発生率、コストも併せて把握しておきます。
そのうえで、目標ラインを設定します。精度については、現状の実装の精度やタスクの難易度、モデルの性能を総合的にみた現実的な水準で、かつプロダクトとして必要とされる水準を満たすラインを引きます。処理速度やコストにも目標を設けます。精度だけを追求しても、処理速度が遅すぎる・コストが高すぎるのでは意味がありません。
※ LLMの出力は実行のたびに変化します(
temperature=0やtop_p=0.1のように確定的になる設定にしても変動します)。同じ条件で複数回評価を実行して平均を取ると、信頼できる結果になりやすいと思います。
評価結果の分析と改善策の実施
精度が目標に届かない場合は、原因の分析から入ります。精度が低かったデータについて、正解例や正解基準と照らし合わせて、出力のどこがどのようにできていないのかを人手で確認します。より高性能なモデルなら良い品質の出力ができるのであれば、モデル性能にも一因があると言えます。複数の工程からなるタスクを1つのプロンプトで実行している場合は、プロンプトを分割して実行し、どの工程に原因があるかを切り分けます。
原因が見えてきたら、改善策を実施します。
- できていなかった部分の是正案をプロンプトに盛り込み、同じ間違いを繰り返さないようにする
- より高性能なモデルに切り替える
- プロンプトを工程ごとに分割し、AIが行う1つ1つのタスクを単純化する
※ 高性能モデルへの変更やプロンプトの分割実行は、コスト増の原因になります。逆に「1つのモデルで精度が出たからOK」でもなく、より低コストのモデルで同等の性能が出せないかも確認し、コストと精度のトレードオフを常に意識することをおすすめします。
目標ラインに達するまで反復する ── そして深追いしない
改善策を実施したら再度評価を行い、効果が出ているかを確認します。効果が不十分なら分析からやり直し、目標ラインに達するまでこの評価と改善のサイクルを繰り返します。
ここで意識したいのは、目標ラインに達したら、それ以上数字を深追いしないことです。あらゆる入力に対して完璧な出力を返すようにモデルやプロンプトをチューニングするのは、そもそも困難です。リリース前に数字を追い込むよりも、そこそこの精度のあるものを早期にリリースし、ユーザーからのリアルなフィードバックに基づいて改善したほうが、得られる価値は大きいと思います。
3. FIXと本番実装
モデル・プロンプト・処理フローのFIX
目標に達したら、その結果を出した構成をそのまま凍結します。後の工程で精度が落ちたときに、原因が実装側にあるのか構成変更にあるのかを切り分けられるようにするためです。
固定する対象は、モデル名(できればエイリアスではなく、日付等が記載された特定のスナップショットを指定します)、推論パラメータ(temperature、top_p、max_tokens、seed など)、プロンプト全文、入出力のスキーマ、前後処理のロジックです。
併せて、FIX時点の評価スコア(以降のすべての比較の基準値になります)、その構成に至った理由と試したが不採用にした案、ベンチマークデータと評価スクリプト一式も残しておきます。評価一式は、機能の追加・修正の際に精度劣化していないかを確認する回帰テストとして、そのまま使い回します。
※ プロンプトはコードに直書きせず、バージョン管理できる形で外出ししておくと、後の改善サイクルが回しやすくなります。
本番実装用コードの仮作成と再検証
検証用コードと本番用コードでは、求められるものが違います。検証段階では1回動けばよいコードでも、本番では失敗すること前提の作りが必要になります。具体的には、エラーハンドリングとリトライ(タイムアウトやレート制限に対する指数バックオフ)、出力フォーマットのバリデーションと崩れていた場合の再実行、規定回数失敗したときの縮退動作、入力・出力・トークン数・処理時間のログ記録、レート制限と折り合いをつけた並列化、認証情報の管理とコスト集計の仕組みなどです。
まずは作り込みすぎず、通しで動くものを仮に作ります。次にその仮の実装をステップ1で用意した評価手法で評価し、検証時の結果が再現するかを確認します。見る観点は、精度がFIX時点のスコアと同水準か、処理速度(平均だけでなく遅い側も見ます)、並列実行時のレート制限への到達具合とエラー率、1件あたりと想定件数での月次コスト、異常系の入力(空、極端に長い、想定外の文字種)で落ちずに処理できるか、です。
※ 検証時と本番では、プロンプトへのデータの入り方(エスケープ、改行の扱い、文字数上限による切り詰め)が変わりやすく、これが精度劣化の典型的な原因になります。精度が再現しない場合は、モデルやプロンプトを疑う前に、まず実装の差分を潰します。
本番実装の完成
再検証で見つかった問題を修正し、運用に耐える状態に仕上げます。このとき、機能として動くことに加えて、リリース後に改善サイクルを回せる状態になっていることが完成条件になります。
- 監視とアラート:エラー率、処理時間、コストの急増を検知できるようにする
- 入出力ログの蓄積:どの入力で品質が悪かったかを後から追跡し、ベンチマークデータに追加できるようにする
- ユーザーからのフィードバック導線:出力に対する評価や修正内容を回収できるようにする
- 安全面の対処:個人情報のマスキング、プロンプトインジェクションへの対策、出力をそのまま外部に出す場合のフィルタ
- 段階的リリースの仕組みと運用手順の文書化:一部ユーザーへの先行公開や問題時に切り戻せる導線、障害時の対応やモデル更新時の再評価手順
ステップ2の最後に書いたとおり、リリース前に完璧を目指すよりも、そこそこの精度で早く出してユーザーの反応をもとに改善するほうが価値があります。そのための「早く出せる仕組み」と「改善を回せる仕組み」を、この工程で用意しておくイメージです。
4. リリース後の運用と継続的改善
リリースは検証の終わりではなく、実データでの検証の始まりです。
開発中のベンチマークデータは、あくまで自分たちが想定した入力パターンの集合でしかありません。実際のユーザーが入れてくるデータは想定を外れることが多く、リリース後に初めて分かる弱点があります。ステップ3で用意したログとフィードバック導線を使って、ステップ2の評価と改善のサイクルを本番データで回し続けます。
実データで弱点を見つけ、ベンチマークを育てる
まず、精度が低かったケース(ユーザーがやり直した、大幅に手直しした、フィードバックで低評価がついた入力)や、開発時のベンチマークに無かった想定外の入力パターンを、実データから拾い上げます。「精度が悪い」という報告だけでは改善できません。どの入力に対して、どんな出力が返り、期待されていた出力は何だったのか。この3点が揃う形で、ログとフィードバックを回収できるようにしておきます。
見つかった失敗ケースはベンチマークデータに追加し、評価セットを育てていきます。追加直後はスコアが下がりますが、これは評価が実態に近づいたということです。再評価の際は追加分だけでなく既存分も必ず一緒に評価します。特定ケースの対策で他が壊れる(デグレする)ことは頻繁に起こります。
※ ベンチマークデータが実データを反映して充実していくほど、評価の信頼性が上がり、改善のスピードも上がります。ここへの投資が、長期的には一番効いてくると感じています。
改善はリリース前と同じ手順で適用する
プロンプトを修正したら、リリース前と同じ回帰テストを通してから反映します。変更は一度にまとめず効果を切り分けられる単位で入れ、可能なら一部ユーザーで先に試してから全体に広げ、変更内容と前後のスコアを記録に残します。プロンプトの修正は1行の変更でも挙動が大きく変わるため、コード変更と同じ扱いで、レビューとテストを経て反映する運用にしておくのがよいと思います。
モデル更新とコストに追従する
LLMは提供側の都合でモデルが更新・提供終了されるため、追従は避けられないものとして手順化しておきます。新モデルが出たら、既存のベンチマークで旧モデルと精度・速度・コストを比較評価します。上位モデルだけでなく、より低コストのモデルで同等の精度が出ないかも都度確認します。モデルの価格性能比は継続的に改善されているため、リリース時点の最適解が半年後も最適とは限りません。使用中モデルの提供終了期限を把握し、期限前に移行検証の時間を確保しておくことも必要です。
※ モデルを差し替えると、旧モデル向けにチューニングしたプロンプトが最適でなくなることがあります。モデル変更時は、プロンプトの見直しもセットで考えます。
コストについても、実際の利用量に基づく月次コストを定点観測し、想定より高い場合はプロンプトの短縮、キャッシュの活用、モデルのダウングレード、処理の分割方法の見直しなどを検討します。
おわりに
4つのステップを並べてきましたが、通してみると、特別なことは何もしていません。「目標を決め、評価し、改善する」という当たり前のサイクルを、AI機能開発の文脈に置き直しただけとも言えます。
しかしLLMを使った開発では、この当たり前が思いのほか難しいこともあります。
出力が実行のたびに変わるため「(一時的な)良くなった気がする」で判断しがちですし、手元では動いてしまうぶん、評価の仕組みを作る前に作り込みを始めてしまいがちです。だからこそ、作り込む前に評価する仕組みを用意しておくことが、堅実に開発を進めるために重要になってきます。
もう1つの難しさは、やめ時が分かりにくいことです。あらゆる入力に対して完璧な出力を返すようにチューニングすることは、そもそもできません。一方で、リリースすればユーザーのフィードバックという社内の検証では得られない貴重なデータを得られます。完璧を目指して社内で磨き続けるより、目標に達したら深追いせず早く出し、ユーザーのフィードバックに応えていくほうが、結果的に良いものになるはずです。
これから機能開発に着手する方や、本番化の手前で足踏みしている方にとって、進め方を考えるきっかけになれば幸いです。