この記事では、一見ばらばらに見える AI エンジニアリングのキーワードをつないで整理する。
fallback、Pydantic、semantic cache、prompt cache、context caching、checkpoint、RAGAS、DeepEval。
実はこれらは、すべて同じ問いに答えるためのものだ。不安定で、高価で、確率的に振る舞う LLM を、どうやって信頼でき、復旧でき、評価できるソフトウェアシステムの中に組み込むか。
0. なぜこれらの概念はよく一緒に語られるのか?
LLM アプリケーションを作り始めたばかりの頃、多くの人は仕組みをとてもシンプルに考える。
ユーザー入力 -> LLMを呼び出す -> 回答を返す
この形は demo には向いているが、本番運用には向いていない。
実際にリリースすると、すぐに次のような問題にぶつかる。
モデルの出力形式が壊れていたらどうするのか?
同じ質問が何度も来るなら、コストを節約できないか?
RAG は vector database だけで十分なのか?
長い context を毎回送っていたら、コストが膨らみすぎないか?
Agent が途中で失敗したとき、続きから再開できないか?
system prompt を変更したあと、本当に良くなったとどう判断するのか?
そこから自然に、次のようなエンジニアリング能力が必要になる。
信頼できる出力:モデル出力は検証でき、修復でき、必要なら degrade できること
コスト制御:同じ質問、同じ context、同じ tool call は再利用すること
タスク復旧:長時間タスク、Agent、多段階フローは checkpoint から再開できること
自動評価:システムが良くなったかを感覚だけで判断しないこと
言い換えると、LLM アプリケーションエンジニアリングの本質は「賢い prompt を書くこと」ではない。
従来のソフトウェアエンジニアリングが持つ信頼性、状態管理、cache、検証、観測、評価の力を使い、確率的に振る舞うモデルを、制御可能なプロダクトシステムとして包み込むこと。
1. まず全体像を持つ:LLM アプリケーションは一度の API call ではない
本番レベルの LLM アプリケーションは、むしろ次のような姿に近い。
ユーザーリクエスト
-> リクエストの正規化
-> exact cache / semantic cache
-> checkpoint の読み込み
-> RAG retrieval / tool call
-> prompt の組み立て
-> provider prompt cache / context cache
-> LLM 生成
-> Pydantic / JSON Schema 検証
-> fallback / repair / retry
-> 自動 eval
-> cache / checkpoint / trace への書き込み
-> 結果を返す
フローチャートにすると、こうなる。
この図の各モジュールには、それぞれ明確な役割がある。
| モジュール | 解決する問題 |
|---|---|
| Pydantic / JSON Schema | モデル出力を安全に業務システムへ渡せるか |
| Fallback | モデルが失敗したとき、システムをきれいに degrade できるか |
| Semantic Cache | 似た質問に対して過去の回答を再利用できるか |
| Prompt / Context Cache | 繰り返し使う長い入力のコストと latency を下げられるか |
| Checkpoint | 多段階タスクが失敗したあと、続きから再開できるか |
| RAGAS / DeepEval | システムの品質が本当に良くなったかをどう判断するか |
2. Pydantic と Fallback:モデル出力をシステムに入れる前に検査する
LLM の出力を、そのまま信頼できる戻り値として扱ってはいけない。
むしろ、外部 API のレスポンスに近いものとして考えたほうがよい。必要なフィールドが欠けているかもしれないし、enum に存在しない値が返ってくるかもしれない。型が合っていない、JSON として壊れている、あるいは内容が論理的に矛盾していることもある。
だから本番システムでは、prompt にただ次のように書くだけでは足りない。(実は、2023年に初心者だった頃の私も、これをやってしまっていた。)
JSON を出力してください。
必要なのは、硬い制約を作ることだ。
LLM 出力
-> JSON parse
-> Pydantic schema 検証
-> strict mode / validator / 業務ルール
-> 修復して retry
-> モデルを切り替える
-> 保守的に degrade する
-> 人による確認へ回す
OpenAI の Structured Outputs は、まさにこの考え方に近い。単にモデルに合法な JSON を出させるだけでなく、指定した schema にできるだけ沿った出力を得るための仕組みだ。Pydantic は Python アプリケーション層で、さらに強い検証を行うために使える。
簡単な例を見てみる。
from enum import Enum
from pydantic import BaseModel, Field, ConfigDict, ValidationError, field_validator
class Priority(str, Enum):
low = "low"
medium = "medium"
high = "high"
class TicketTriage(BaseModel):
model_config = ConfigDict(strict=True)
category: str = Field(min_length=1)
priority: Priority
summary: str = Field(min_length=10)
confidence: float = Field(ge=0, le=1)
@field_validator("category")
@classmethod
def category_must_be_known(cls, value: str) -> str:
allowed = {"billing", "technical", "account", "other"}
if value not in allowed:
raise ValueError(f"unknown category: {value}")
return value
def parse_llm_output(raw: dict) -> TicketTriage | None:
try:
return TicketTriage.model_validate(raw)
except ValidationError as e:
# エラーをそのまま握りつぶさず、repair / fallback フローに入れる
print(e)
return None
ここで大事なのは、「Pydantic を使えば万事解決」ということではない。モデルは間違えるものだが、その間違いはシステム側で捕まえなければならない。業務ロジックへそのまま流してはいけない。
典型的な fallback chain は、次のように設計できる。
第 1 層:現在のモデル出力が有効なら、そのまま使う
第 2 層:形式エラーなら、Pydantic のエラー情報をモデルに返して修復させる
第 3 層:修復に失敗したら、より強いモデルに切り替えて再生成する
第 4 層:それでも失敗したら、保守的なテンプレート回答を返す
第 5 層:高リスクの場面では、人による確認へ回す
なお、Pydantic はデフォルトである程度の型変換を行う。たとえば文字列 "123" を整数 123 に変換することがある。本当に厳格に検証したいなら strict mode を有効にし、型が合わない場合はその場でエラーにする必要がある。
3. Cache ファミリー:すべての Cache を一緒くたにしない
LLM アプリケーションにおける「cache」は、ひとつの概念ではない。複数の概念の集まりだ。
| 名前 | 何を cache するか | どの層で起きるか | モデルを skip するか | 典型的な用途 |
|---|---|---|---|---|
| Exact Cache | 完全に同じリクエストの結果 | アプリケーション層 | はい | FAQ、固定クエリ |
| Semantic Cache | 意味が近い質問への過去回答 | アプリケーション層 | 多くの場合ははい | 聞き方は違うが答えは同じ質問 |
| RAG Retrieval Cache | 検索された文書 chunk | retrieval 層 | いいえ | 重複する vector retrieval を減らす |
| Prompt / Context Cache | 長い prompt prefix に対するモデル側の計算 | モデル provider 層 | いいえ | 長い context のコストと latency を下げる |
| Tool Cache | tool や API call の結果 | tool 層 | 間接的に減らす | 検索、database、外部 API |
| Checkpoint | Agent の実行状態 | workflow 層 | cache ではない | 失敗復旧、途中再開 |
つまり、「RAG と聞いて vector database しか思い浮かばないのでは不十分」という話の要点は、vector database を否定することではない。vector database が解決するのは知識検索であって、回答の再利用、context の再利用、tool の再利用、タスク復旧までは解決しないということだ。
4. Semantic Cache:RAG の外側にある「意味の再利用」
Semantic Cache が問うているのは、次のことだ。
この質問は、過去にすでに聞かれていないか?
あるいは聞き方は違っても、意味としては十分に近くないか?
近いなら、過去の回答をそのまま再利用できないか?
たとえば、次の 2 つの質問を考える。
Q1: 会社の出張費精算ルールは何ですか?
Q2: 出張費はどうやって精算すればいいですか?
文字列としては違うが、答えは同じである可能性が高い。Semantic Cache は質問を embedding に変換し、vector similarity によって過去の質問を検索する。十分に similarity が高ければ、cache された回答をそのまま返す。
典型的な流れはこうだ。
ユーザーの質問
-> embedding
-> vector similarity search
-> similarity の高い過去質問に hit
-> tenant、権限、knowledge base version、timestamp を確認
-> 過去回答を返す
ここでは、誤 hit のリスクにかなり注意する必要がある。
たとえば次のような場合だ。
Q1: 2025 年の精算ルールは何ですか?
Q2: 2026 年の最新の精算ルールは何ですか?
意味は近いが、回答を再利用してよいとは限らない。本番では、次のような要素を cache key または filter 条件に入れる必要がある。
tenant_id
user_role
knowledge_base_version
locale
time_range
business_domain
semantic_threshold
RedisVL は、まさにこの位置づけで登場する。
RedisVL は暗記すべき理論概念ではなく、Semantic Cache を実装するための手段のひとつだ。より正確に階層化すると、次のようになる。
概念:Semantic Cache
実装のひとつ:RedisVL SemanticCache
その他の実装:GPTCache、LangChain cache、自前の Redis + embedding、pgvector など
RedisVL の SemanticCache は Redis 上に index を作成し、embedding model、similarity threshold、TTL などの設定と組み合わせて、LLM response の semantic cache を実現する。
5. Prompt Cache と Context Caching はどういう関係なのか?
ここが最も混同されやすい。
まず結論から言う。
Prompt Cache と Context Caching は、名前こそ違うが、基本的には同じ発想に基づく仕組みだ。
モデル提供側で、何度も使われる長い入力の prefix を再利用し、コストとレイテンシを下げる。
ただし、これらは最終的な回答をキャッシュする仕組みではない。
モデルによる生成処理を省略するものでもない。
キャッシュしているのは、「モデルが長い context を処理するときの計算の一部」だ。
provider によって呼び方は異なる。
| provider / 体系 | よく使われる呼び方 | 重点 |
|---|---|---|
| OpenAI | Prompt Caching | 長い prompt の重複 prefix を自動的に cache し、usage に cached_tokens を表示する |
| Anthropic | Prompt Caching |
cache_control に対応し、cache breakpoint を重視する |
| Google Gemini | Context Caching | implicit caching と explicit caching があり、長い context の再利用を重視する |
OpenAI の Prompt Caching は、長い prompt を扱う場面で自動的に働き、usage.prompt_tokens_details.cached_tokens によって cache hit した token を確認できる。
Anthropic の Prompt Caching はより明示的で、cache_control を使って cache breakpoint を指定できる。指定した breakpoint までの prompt prefix がすでに cache されているかを確認し、hit した場合は処理時間とコストを下げる。
Gemini のドキュメントでは Context Caching という表現が使われる。長い文書、長い動画、コードリポジトリ分析のように、大量の反復 context を短い質問から何度も参照する場面に向いている。
良い prompt の組み立て方は、次のような形だ。
安定した内容を前に置く:
system prompt
tool 定義
出力形式の説明
few-shot examples
長い文書 / 背景資料
動的な内容を後ろに置く:
現在のユーザー質問
現在時刻
この turn の tool result
一時的な parameter
良くない書き方は、動的な内容を毎回 system prompt の途中に挟み込むことだ。そうすると prefix hash が頻繁に変わり、cache hit しにくくなる。
cache breakpoint は、次のように理解するとよい。
prompt の先頭からここまでが、安定していて再利用できる内容。
この後ろの内容は毎回変わってもかまわない。
6. API call 層の cache breakpoint:コスト削減だけでなく、復旧にも効く
「cache breakpoint」には 2 つの意味があり、混同しやすい。
1 つ目は、provider 側の cache breakpoint だ。
役割:モデル provider に長い prompt prefix を再利用させる
目的:token コストを下げ、latency を短くする
例:Anthropic cache_control、Gemini explicit context cache
2 つ目は、application 側の checkpoint だ。
役割:アプリケーションフローの中で完了済みの高コストな step を保存する
目的:失敗後に最初からやり直さずに済ませる
例:RAG retrieval result、tool call result、validation result、Agent の現在 node
たとえば、複雑な Agent flow があるとする。
1. ユーザーが調査タスクを送信する
2. システムが資料を検索する
3. システムが検索 tool を呼び出す
4. システムが資料を要約する
5. システムがレポートを生成する
6. システムが形式を検証する
第 5 step で失敗した場合、理想は第 1 step からやり直すことではない。
checkpoint を読む
-> 前 4 step がすでに成功していると分かる
-> 第 5 step から続行する
だから API call 層では、ひとつの習慣を持つべきだ。高コストで、再利用でき、復旧に使える step が終わるたびに、構造化された結果を保存する。
7. 永続化された状態機械:Agent が途中で落ちたらどうするか?
Agent は、ただの while loop であるべきではない。
より安定した方法は、Agent を state machine として modeling することだ。
receive_request
-> classify_intent
-> retrieve_context
-> call_tools
-> generate_answer
-> validate_output
-> evaluate_trace
-> final_response
各 node には、明確な入力、明確な出力、明確な失敗戦略が必要になる。
checkpoint には、次のような情報を保存できる。
task_id
current_state
next_state
input_payload
node_outputs
tool_results
retry_count
error_message
model_name
prompt_version
trace_id
created_at
updated_at
Redis は checkpoint の保存に使える。特に低 latency、短期状態、タスク復旧には向いている。ただし、普通の Redis cache と信頼性のある永続状態は同じものではない。本番では次のような点を考える必要がある。
AOF / RDB persistence が必要か
database への長期保存が必要か
distributed lock が必要か
idempotency key が必要か
queue による async recovery が必要か
人による承認 node が必要か
タスクが非常に長く、step も多く、信頼性の要求が高い場合は、LangGraph や Temporal のような durable workflow の考え方を検討してもよい。LangGraph の persistence は graph state を checkpoint として保存するため、human-in-the-loop、memory、time travel debugging、障害復旧を支えられる。
判断の核になるのは、次の違いだ。
Cache が解決するのは、「コストを下げられるか、速くできるか」。
Checkpoint が解決するのは、「失敗後に続きから再開できるか」。
この 2 つは同じものではない。
8. 自動評価:Agent の各 step に KPI を持たせる
多くのチームは LLM アプリケーションを作るとき、最終回答を見て「なんとなく良さそう」と判断してしまう。これは危ない。
Agent のエラーは、最後の一文で起きるとは限らない。むしろ途中の step で起きることが多い。
retrieval が間違った文書を取ってくる
tool parameter を間違えて渡す
semantic cache が誤 hit する
モデル出力が schema に合わない
repair retry が意味を壊してしまう
Agent が余計な step を踏みすぎる
context に古い情報が混ざる
だから評価は層ごとに行う必要がある。
RAGAS は RAG や Agent/tool use に関連する指標を提供する。たとえば context precision、context recall、response relevancy、faithfulness、tool call accuracy、agent goal accuracy などだ。
DeepEval も RAG、Agentic、多 turn、安全性などの指標を提供している。Agentic 系の指標には task completion、argument correctness、tool correctness、step efficiency、plan adherence などが含まれる。
実践的な KPI set は、次のように設計できる。
cache 層:
exact_cache_hit_rate
semantic_cache_hit_rate
semantic_cache_false_hit_rate
prompt_cached_tokens
cache_eviction_rate
RAG 層:
context_precision
context_recall
context_relevancy
faithfulness
noise_sensitivity
モデル出力層:
schema_valid_rate
repair_success_rate
fallback_rate
json_parse_error_rate
business_rule_violation_rate
Agent 層:
task_completion_rate
tool_args_valid_rate
tool_success_rate
avg_steps_per_task
resume_success_rate
コストと performance 層:
cost_per_successful_task
p50_latency
p95_latency
retry_rate
timeout_rate
offline 評価だけでなく、online trace もつなげておくのが望ましい。
各リクエスト
-> prompt_version を記録する
-> model_version を記録する
-> retrieved_docs を記録する
-> tool_calls を記録する
-> cache_hit を記録する
-> validation_result を記録する
-> eval_score を記録する
こうしておけば prompt の変更、モデルの切り替え、retrieval strategy の調整を行ったあと、システムが本当に良くなったのか、それとも良くなったように見えているだけなのかを判断できる。
9. 本番レベルの LLM アプリケーション参考アーキテクチャ
システム全体は、いくつかの層に分けて考えられる。
入口層:
API Gateway
auth
rate limit
request normalization
cache 層:
exact cache
semantic cache
tool cache
retrieval cache
編成層:
workflow engine
state machine
checkpoint
retry / timeout / idempotency
知識層:
document loader
chunking
embedding
vector database
reranker
モデル層:
prompt builder
prompt/context cache friendly layout
model router
structured output
信頼性層:
Pydantic validation
business validators
repair loop
fallback policy
human review
評価・観測層:
tracing
RAGAS / DeepEval
cost metrics
latency metrics
quality dashboard
MVP から本番へ進化させるなら、次の順番で進めるとよい。
第 1 段階:動くものを作る
単一モデル呼び出し
基本 prompt
手動テスト
第 2 段階:出力を信頼できる形にする
JSON Schema / Pydantic
parse error retry
fallback answer
第 3 段階:知識を強化する
RAG
rerank
citation / source tracking
第 4 段階:コストを最適化する
exact cache
semantic cache
prompt/context cache
tool result cache
第 5 段階:タスクを信頼できるものにする
state machine
checkpoint
idempotency key
resume
第 6 段階:継続的に改善する
trace
eval dataset
RAGAS / DeepEval
A/B test
quality dashboard
大事なのは、最初からすべての component を入れることではない。それぞれの component が何の問題を解決するのかを理解しておくことだ。
10. まとめ
これらの概念を並べて見ると、役割はかなり明確になる。
RAG が解決すること:モデルがどの外部資料を知るべきか。
Semantic Cache が解決すること:過去の回答を再利用できるか。
Prompt / Context Cache が解決すること:繰り返し使う長い入力のコストと待ち時間を減らせるか。
Pydantic が解決すること:モデル出力を安全にシステムへ入れられるか。
Fallback が解決すること:モデルが失敗したとき、システムをきれいに degrade できるか。
Checkpoint が解決すること:タスクが失敗したあと、続きから再開できるか。
RAGAS / DeepEval が解決すること:システムが本当に良くなったとどう判断するか。
だから、LLM アプリケーションエンジニアリングの本質は「賢い prompt を書くこと」ではない。
より正確に言えば、それは次のような仕事だ。
従来のソフトウェアエンジニアリングが持つ信頼性、状態管理、cache、検証、観測、評価の力を使い、
確率的に振る舞うモデルを、制御可能なプロダクトシステムとして包み込むこと。
fallback、semantic cache、prompt cache、context caching、checkpoint、eval といった言葉を見たとき、それらを孤立した小技として捉えないほうがよい。
これらは本当は、ひとつの本番運用上の問いに一緒に答えている。
モデルは間違える。高い。遅い。途中で止まることもある。
それでも、システム全体をどうすれば信頼できるものにできるのか?