2. エージェント構築の基礎
第1章では、最初のLLM呼び出しを行い、基本的なリクエスト-レスポンスの流れを確認しました。次に理解すべきなのは、私たちが実際に何を構築しているのかです。それが エージェント型AIシステム です。この章では、本書全体で使う基礎概念を整理します。
この章の終わりまでに、次を理解できるようになります:
- AIシステムを「エージェント型」にするものは何か(そしてなぜ重要なのか)
- LangChain や LangGraph のようなフレームワークが存在する理由
- LLMが内部で実際にどう動いているのか(そしてそれがなぜエージェント設計に影響するのか)
- LLM利用の経済性(トークン、コスト、モデル選定)
- エージェントシステム向けの効果的なプロンプトの書き方
これは概念中心の章です。ハンズオンのコーディングには第3章で戻ります。しかし、これらの概念は、エージェントを構築する際に行う設計判断を理解するうえで不可欠です。
2.1) エージェント型AIとは?(チャットボット vs エージェント)
多くの人が「AIアプリケーション」と聞いて思い浮かべるのはチャットボットです。質問するとAIが答えて、会話が終わる。しかし エージェント型AI は本質的に異なります。エージェントはただ応答するだけではなく、目標を達成するために自律的に行動します。
中核となる違い
まずは明確な定義から始めましょう:
チャットボット: ユーザー入力に対して生成テキストで応答するシステムです。やり取りは純粋に会話的です。チャットボットには、次のメッセージを生成する以外に、世界に対して行動を起こす能力がありません。
エージェント: 次を行えるシステムです:
- 環境を 知覚する(例: 注文状況を読む、在庫を確認する)
- 実行すべき行動を 決定する(例: チケット作成、メール送信、エスカレーションのどれを選ぶか)
- その決定に基づいて 行動する(例: APIを呼ぶ、データベースを更新する)
- 目標を達成するまで 反復する(例: 注文を確認 → チケット作成 → 確認 → 完了)
重要な違いは 自律性と行動 です。チャットボットは受動的で、エージェントは能動的です。
具体例: カスタマーサポート
この違いを実例で見てみましょう。
チャットボットのアプローチ:
ユーザー: "注文番号 #12345 の返金をお願いします"
チャットボット: "返金をご希望とのこと承知しました。処理するには、次の手順を実施してください:
1. example.com/refunds の返金ポータルにアクセスしてください
2. 注文番号を入力してください
3. 理由を選択してください
4. フォームを送信してください
5. 手動レビューのため24〜48時間お待ちください
ほかにお手伝いできることはありますか?"チャットボットは 情報 を提供しますが、行動は起こしません。ユーザーがすべての作業を行う必要があります。
エージェントのアプローチ:
ユーザー: "注文番号 #12345 の返金をお願いします"
エージェント(内部推論):
1. ユーザーは注文番号 #12345 の返金を希望している
2. この注文が存在することを確認する必要がある
3. [get_order_details(order_id="12345") を呼び出す]
4. 注文が見つかった、返金対象
5. [create_refund_ticket(order_id="12345", reason="customer_request") を呼び出す]
6. チケットを作成: TICKET-789
エージェント: "注文番号 #12345 の返金チケット TICKET-789 を作成しました。
返金チームが3〜5営業日以内に処理します。
まもなくメールで確認をお送りします。"エージェントは 行動 しました。注文を検証し、チケットを作成し、結果を確認しました。手動の手順なしでユーザーの問題が解決しています。
開発においてなぜ重要か
この違いを理解すると、システム設計の方針が変わります:
チャットボット開発:
- 応答品質と会話フローに注力する
- 主な関心事: 役に立つ正確なテキストを生成すること
- シンプルなアーキテクチャ: プロンプト → LLM → 応答
- 外部統合は不要
エージェント開発:
- 意思決定と行動実行に注力する
- 主な関心事: 正しい行動の選択、エラー処理、状態の維持
- 複雑なアーキテクチャ: 知覚 → 推論 → 行動選択 → 実行 → 検証
- ツール統合、エラー処理、状態管理が必要
自律性のスペクトラム
すべてのエージェントが同じレベルで自律的というわけではありません。スペクトラムがあります:
レベル1: 支援付きアクション
- エージェントが行動を提案し、ユーザーが各行動を承認する
- 例: 「返金チケットを作成できます。進めてもよいですか?」
- ハイリスクな操作における最も安全なアプローチ
レベル2: 制約付き自律性
- エージェントが事前定義された制約の範囲内で行動する
- 例: チケット作成やメール送信はできるが、500ドル超の返金処理はできない/決済システムへ直接アクセスできない
- 本番システムで最も一般的(効率と安全のバランス)
レベル3: 完全自律
- エージェントが目標達成のために独立して行動する
- 例: 人の介入なしに返金ワークフロー全体を処理する
- 強固なガードレールと監視が必要
エージェントの主要な特性
まとめると、エージェント型AIシステムには次の 中核 特性があります:
- ツール利用(Tool-Using): 関数、API、外部サービスを呼び出せる(これがないと単なるチャットボットです)
- 目標指向(Goal-Directed): ただ応答するのではなく、特定の成果へ向かって動く
- マルチステップ(Multi-Step): 複雑なタスクを一連の行動に分解する
- 適応的(Adaptive): 中間結果に基づいて振る舞いを調整する
- ステートフル(Stateful): 複数回のやり取りにわたって文脈を維持する
最初の2つが 必須 です。ツールと目標がなければ、エージェントではありません。残りは、良いエージェントと優れたエージェントを分ける 品質要因 です。
これで、エージェントが 何で、そして なぜ 強力なのかが理解できました。
2.2) なぜLangChainとLangGraphなのか?
「なぜフレームワークが必要なのか?OpenAI APIを直接呼べばよいのでは?」と思うかもしれません。フレームワークが存在する理由と、どんな問題を解決するのかを見ていきましょう。
エージェント開発の複雑さ
生のAPI呼び出しでシンプルなチャットボットを作るのは簡単です:
import openai
response = openai.chat.completions.create(
model="gpt-5-mini",
messages=[{"role": "user", "content": "こんにちは!"}]
)
print(response.choices[0].message.content)これは基本的な用途なら十分に機能します。しかしエージェントを作ろうとした瞬間に、複雑さが爆発します:
課題1: マルチステップのワークフロー エージェントは次を行う必要があります:
- ナレッジベースから関連ドキュメントを取得する
- ユーザー意図に基づいて呼び出すツールを決定する
- ツールを実行してエラーを処理する
- 結果を整形してユーザーへ応答する
各ステップには、慎重なオーケストレーション、エラー処理、状態管理が必要です。
課題2: プロバイダー抽象化 次のような場合はどうでしょう:
- OpenAIからAnthropicやGoogleへ切り替えたい
- タスクに応じて異なるモデルを使いたい
- 主要モデルが失敗したら安いモデルへフォールバックしたい
生のAPI呼び出しでは、プロバイダーごとに大きなコード書き換えが必要になります。
課題3: 会話メモリ エージェントは文脈を覚えている必要があります:
- 会話内の過去メッセージ
- 以前のクエリで取得したドキュメント
- ツール呼び出しの中間結果
この状態を手動で管理するのはミスが起きやすく、面倒です。
課題4: ツール統合 エージェントは次を行う必要があります:
- スキーマ付きで利用可能なツールを定義する
- LLMにどのツールを呼ぶか選ばせる
- LLM出力からツール引数をパースする
- 検証付きで安全にツールを実行する
- ツールエラーとリトライロジックを扱う
これには大量のボイラープレートコードが必要で、さらに各ステップでセキュリティ上の配慮が必要 になります。
課題5: 複雑なルーティング 実際のエージェントには条件分岐ロジックが必要です:
- 「ユーザーが返金について聞いたら、ポリシードキュメントを取得する」
- 「ユーザーが返金を望むなら、チケットを作成する」
- 「質問が関係ないなら、丁寧に断る」
これをif-elseで実装すると、すぐに保守不能になります。
LangChainが提供するもの
LangChain はLLMアプリケーションを構築するためのフレームワークです。次を提供します:
1. モデル抽象化
異なるLLMプロバイダー(OpenAI、Anthropic、Googleなど)向けの統一インターフェースです。
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
# 同じインターフェース、異なるプロバイダー
openai_llm = ChatOpenAI(model="gpt-5-mini")
anthropic_llm = ChatAnthropic(model="claude-4-5-sonnet")
# どちらも同じメッセージ形式で .invoke() を使う
response = openai_llm.invoke([{"role": "user", "content": "こんにちは"}])アプリケーションロジックを書き換えずにプロバイダーを切り替えられます。
2. 合成可能なチェーン(Composable Chains)(LCEL)
LangChain Expression Language(LangChain式言語)— コンポーネントをパイプラインに接続するための構文です。
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# | 演算子(Unixパイプのような)でパイプラインを合成する
chain = prompt | llm | output_parser
# 1回の呼び出しでパイプライン全体を実行する
result = chain.invoke({"input": "user question"})ステップ間で手動でデータを受け渡しすることなく、複雑なワークフローを構築できます。LCELは第6章で学びます。
3. 会話メモリ
ステートフルな会話のためのメッセージ履歴管理です。
from langchain_core.chat_history import InMemoryChatMessageHistory
# メッセージ履歴ヘルパー
history = InMemoryChatMessageHistory()
history.add_user_message("こんにちは")
history.add_ai_message("こんにちは!")
# 必要なときにメッセージを取り出す
messages = history.messages
response = llm.invoke(messages)リストを手動で管理する代わりに、ヘルパークラスでメッセージ保存を抽象化できます。この段階では、履歴はまだ明示的にモデルへ渡します。 第8章でこれをチェーンへ統合します。LangGraph(第15章以降)では、組み込みの状態管理によりこれがさらに簡単になります。
4. ツール統合
Python関数をLLMに公開するためのデコレータベースのシステムです。
from langchain_core.tools import tool
@tool
def create_ticket(order_id: str, reason: str) -> str:
"""注文に対するサポートチケットを作成します。"""
# ここに実装
return f"{order_id} のチケットを作成しました"
# LangChain がスキーマ生成とLLM統合を処理します任意のPython関数を、ツール対応LLMやエージェントが発見して呼び出せるツールに変換できます。JSONスキーマを手書きする必要はありません。
5. ドキュメントローダーとベクトルストア
さまざまなソースからドキュメントを読み込み、検索可能な埋め込みとして保存するための、すぐに使えるコンポーネントです。
from langchain_community.document_loaders import TextLoader
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
# ドキュメントを読み込む
docs = TextLoader("support_docs.txt").load()
# 検索可能なインデックスを作成する
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(docs, embeddings)
# 関連ドキュメントを取得する
results = vectorstore.similarity_search("返金ポリシー")パース、埋め込み、リトリーバルをゼロから実装する代わりに、事前構築済みのローダーとベクトルストアを使ってRAGシステムを構築できます。
LangGraphが提供するもの
LangGraph は、複雑なエージェントワークフローのためにLangChainを拡張します。次を提供します:
1. 明示的な状態管理
変数に散らばらせるのではなく、エージェントのデータを単一の型付きスキーマにすべて定義します。
from typing import TypedDict, Optional
from langchain_core.messages import BaseMessage
from langchain_core.documents import Document
class AgentState(TypedDict):
messages: list[BaseMessage]
retrieved_docs: list[Document]
current_step: Optional[str]
# すべてのエージェントデータはここに集約される - デバッグ時に1か所を確認できるデータがどこにあり、ステップ間でどう流れるのかを探し回る必要がなくなります。状態の変更は明示的で、ノードは state から読み取り、更新を返します。IDEはフィールド名を補完し、型チェッカーが実行前にエラーを検出します。
2. グラフベースのワークフロー
オーケストレーションコードを書くのではなく、ステップ(ノード)とその接続(エッジ)を宣言してワークフローを構築します。
graph = StateGraph(AgentState)
# ノード(ワークフロー内のステップ)を定義する
graph.add_node("retrieve", retrieve_docs)
graph.add_node("answer", generate_answer)
# エッジ(ステップ間の遷移)を定義する
graph.add_edge("retrieve", "answer")構造——「retrieveが実行され、その後answerが実行される」——を定義し、実行はLangGraphが処理します。ステップ間で状態を受け渡しするオーケストレーションコードを書く必要はありません。順序付けや分岐などの制御フローは、グラフ構造そのものとして宣言されます。
3. 条件付きルーティング
ワークフロー内でどの経路を進むかを、実行時に意思決定します。
from langchain_core.messages import BaseMessage
def route_request(state):
last_message = state["messages"][-1].content
if "refund" in last_message:
return "create_ticket"
else:
return "answer_question"
graph.add_conditional_edges(
"classify",
route_request,
{
"create_ticket": "create_ticket",
"answer_question": "answer_question",
}
)重要なポイント: ルーティング関数は、次のノード名("create_ticket" または "answer_question")を返します。
なぜ重要か: 意思決定ロジックと実行が分離されます。ルーティング規則を変えたい?1つの関数を編集するだけです。可能な経路をすべて見たい?グラフ定義を見ればよいです。どの経路が選ばれたかデバッグしたい?実行トレースを確認すればよく、ネストした関数呼び出しを掘り返す必要はありません。
4. チェックポイントと永続化
各ステップ後に状態が自動的にチェックポイントされ、クラッシュ復旧や一時停止-再開ワークフローが可能になります。
from langgraph.checkpoint.sqlite import SqliteSaver
checkpointer = SqliteSaver("agent_state.db")
graph = graph.compile(checkpointer=checkpointer)
# 各ステップで状態が自動保存される
result = graph.invoke(
input_state,
config={"configurable": {"thread_id": "user-123"}}
)各ステップの後、LangGraphは現在の状態を永続ストレージへチェックポイントします。プロセスがクラッシュしても、同じスレッドIDに関連付けられた最新チェックポイントから実行を再開できます。
チェックポイントは、条件付きルーティングや割り込みと組み合わせることで、人間の承認待ちなどの一時停止-再開ワークフローを実現します。
それぞれのフレームワークを使うべき場面
LangChainを使う場面:
- シンプルなチェーン(プロンプト → LLM → パーサー)を構築する
- RAGシステムを実装する
- メッセージベースの文脈を扱う(チャット履歴を明示的に渡す)
- LLMプロバイダー間の抽象化を行う
LangGraphを使う場面:
- マルチステップのエージェントワークフローを構築する
- 条件付きルーティングロジックを実装する
- ステップ間の複雑な状態を管理する
- チェックポイントとクラッシュ復旧が必要
2.3) LLMの仕組み(エージェント構築者向け)
効果的なエージェントを作るには、LLMが実際にどう動くのかを理解する必要があります。ここで扱うのはトランスフォーマーの数学ではなく、エージェントシステム設計の判断を形作るためのメンタルモデルです。
中核メカニズム: トークン予測
基本的な洞察はこれです: LLMはデータベースのように事実を「知っている」わけではありません。次のトークンを予測します。
具体例で分解します。
入力: "The capital of France is"
起きていると思いがちなこと:
- LLMが知識ベースでフランスの首都を「検索」する
- LLMが答え「Paris」を「取得」する
- LLMが「Paris」を返す
実際に起きていること:
- LLMが入力をトークンに変換する:
["The", "capital", "of", "France", "is"] - LLMが「次に来る可能性のあるすべてのトークン」に対する確率分布を計算する
- 最も可能性が高い次トークン:
"Paris"(確率が最大) - LLMが分布からサンプリングする(通常は最大確率を選ぶ)
- LLMが「Paris」を返す
LLMは「Parisが首都だ」と「知っている」のではありません。入力パターンに対して「Paris」が最もあり得る次トークンだと予測しているのです。
(注: トークナイズや確率は概念的説明であり、モデルやトークナイザーによって異なります。)
これがエージェントにとってなぜ重要か
このトークン予測モデルは、エージェント設計に重大な含意を持ちます:
含意1: LLMはハルシネーションを起こす
LLMはトークンを予測する(事実を取得するわけではない)ため、もっともらしいが誤った情報を生成し得ます。
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke("アトランティスの首都はどこですか?")
print(response.content)
# 注: 実際の出力は変わる可能性があります。
# 現代のモデルはアトランティスが架空であることを認識し、回答を拒否するかもしれません。
# 重要なポイント:
# 明示的なファクトチェックがなければ、LLMはもっともらしい誤情報を生成し得ます。あり得る(過去の、または制約のない)出力:
アトランティスの首都はエーテリアです。プラトンの記録によれば、エーテリアは島の最東端に位置する港町であり、その名称は「天に届く」と信じられていたことに由来します。アトランティスは架空であるにもかかわらず、LLMはもっともらしい答えを生成します。エージェントにとっては、次を意味します:
- LLM出力を盲目的に信頼しない
- 権威あるソースで事実を検証する
- ガードレールを実装する
含意2: 文脈がすべて
LLMが見えるのは、あなたが与えたトークンだけです。以前の会話を明示的に含めない限り、その記憶はありません。
# 1回目の呼び出し
response1 = llm.invoke("私の名前はアリスです")
print(response1.content) # 「はじめまして、アリスさん!」
# 2回目の呼び出し(別のinvoke)
response2 = llm.invoke("私の名前は何ですか?")
print(response2.content) # 「あなたの名前にアクセスできません...」2回目の呼び出しには、1回目の文脈がありません。エージェントにとっては、次を意味します:
- 会話履歴を管理する必要がある
- コンテキストウィンドウの制限が重要
- 状態管理が重要
含意3: プロンプトはクエリではなく指示
LLMはトークンを予測するため、プロンプトの言い回しが出力品質に劇的に影響します。
# 弱いプロンプト(クエリ風)
response = llm.invoke("返金ポリシー")
# 出力: "返金ポリシーについて何を知りたいですか?"
# 強いプロンプト(指示風)
response = llm.invoke(
"あなたはカスタマーサポート担当者です。返金ポリシーを明確かつ簡潔に説明してください。"
)
# 出力: "返金ポリシーでは30日以内の返品が可能です..."エージェントにとっては、次を意味します:
- プロンプトが主要な制御機構である
- プロンプトエンジニアリングは中核スキル
- システムメッセージがエージェントの振る舞いを設定する
含意4: 構造化出力にはガイダンスが必要
LLMは自然に自由形式のテキストを生成します。構造化出力(JSONや特定フォーマット)を得るには明示的な指示が必要です。
# 構造ガイダンスなし
response = llm.invoke("次の文から注文IDを抽出してください: '注文 #12345 の返金をお願いします'")
print(response.content)
# 出力: 「注文IDは12345です」(プレーンテキストで形式が不安定)
# 構造ガイダンスあり
response = llm.invoke(
'注文IDを抽出し、形式 {"order_id": "..."} のJSONオブジェクトのみを返してください\n\n'
"Text: '注文 #12345 の返金をお願いします'"
)
print(response.content)
# 出力: {"order_id": "12345"}(構造化され、パース可能)エージェントにとっては、次を意味します:
- スキーマで出力形式を制約する
- 出力形式を明示的に指定する
- LLM応答を検証してパースする
自由形式テキストは人間向けに最適化されています。エージェントには機械可読性を確保するための明示的な整形が必要です。
決定性、確率性、そして現代のLLM
LLMは本質的に 確率的なシステム です。決定的なルールを実行するのではなく、最もあり得る次トークンを予測してテキストを生成します。その結果、同じ入力が常に同じ出力を保証するわけではありません。
以前のモデルでは、開発者は temperature のようなパラメータを使ってこのランダム性を明示的に制御していました。値を低くするとより予測可能な出力になり、高くすると多様性や創造性が促進されました。
多くの現代の 推論志向(reasoning-oriented)モデル は、temperature や top_p のようなパラメータを公開しなくなっています。代わりに、安定した構造化推論を優先するために、デコーディングやサンプリング戦略を内部で管理します。しかし、これはこれらのモデルが完全に決定的であることを 意味しません。
これらのモデルであっても、同一の出力を保証しません。出力は次の理由で変動し得ます:
- 内部サンプリング: モデルが異なる推論経路をたどり、構造、詳細、言い回しが異なる出力になる場合があります。
- モデル更新: プロバイダーは通知なしに継続的にモデルを更新するため、同じプロンプトでも時間とともに異なる応答になる可能性があります。
- 安全フィルター: コンテンツモデレーションにより、ある場合は直接回答し、別の場合は婉曲表現・拒否・言い換えになることがあります。
- ツールポリシー: エージェントシステムでは、同じ入力に対してモデルが異なるツールを呼び出す(あるいはツールを呼ばない)ことがあり、実行経路が変わります。
エージェント構築者にとっての意味:
重要なのはパラメータ調整ではなく、予測可能性 です。LLM出力は、明示的に制約しない限り、言い回しや構造、さらには結論さえ変わり得る前提でエージェントシステムを設計すべきです。
このことは、エージェントシステムにおけるいくつかの具体的な設計原則につながります:
- ロジック判断を厳密な文言に依存させない — 制御フローは、モデル出力の特定フレーズ照合ではなく、構造化シグナル(スキーマ、enum、フラグ)に依存させるべきです。
- 境界で構造を強制する — LLM出力がコードで消費される場面では、スキーマ、バリデータ、厳格なフォーマットで制約し、プログラムが自由文を「解釈」しなくて済むようにします。
- 重要なものは必ず検証する — お金、権限、不可逆なアクションに影響する事実は、モデル単体を信頼せず、ツールや外部システムで確認する必要があります。
- LLM出力は提案であって決定ではない — モデルは何をすべきかを提案しますが、いつ・どのように行動するかを決めるのはシステムです。
現代のエージェントシステムでは、信頼性はパラメータ調整ではなく システム設計 から生まれます。タスクが重要であればあるほど、モデルの自由度を下げ、エージェント側でより強い構造を強制すべきです。
重要なポイント: 一貫したLLM出力を期待するのではなく、スキーマ、検証、ツール統合で信頼性の高いエージェントを構築しましょう。
次のセクションでは、トークンベースの処理が持つ経済的な含意を見ていきます。
2.4) トークン: 基本リソース
トークンはLLMが処理する基本単位です。トークンを理解することは不可欠です。なぜなら、エージェントシステムにおいてトークンが 何が可能か(制約)と 何が高いか(コスト)の両方を定義するからです。
トークンとは?
トークン(token) とは、LLMが推論し生成する最小のテキスト単位です。
言語や文脈によって、トークンは次を表すことがあります:
- 単語(
agent) - 単語の一部(
calculat,ion) - 数字や記号(
#,123) - 句読点や空白
トークンは文字でも単語でもありません。トークナイザーによって作られる、モデル固有の単位です。
モデルに送る情報も、モデルが生成する情報も、すべてトークンで測られます:
- システム指示
- ユーザーメッセージ
- 取得ドキュメント
- ツールの説明
- モデル出力
トークンはLLMインタラクションにおける基本的な 通貨 です。
システム制約としてのトークン
トークンはコストであるだけでなく、1回のリクエストでエージェントができることに対する ハードな上限 でもあります。
各LLMには コンテキストウィンドウ(context window) があり、一度に処理できる最大トークン数が固定されています。
例のシナリオ: あなたのカスタマーサポートエージェントには次が必要です:
- システム指示: 200トークン
- 直近10メッセージ: 約2,000トークン
- 取得したヘルプ記事3本: 約1,500トークン
- 生成レスポンス: 約200トークン
- 合計: 3,900トークン
モデルのコンテキストウィンドウが4,000トークンなら、97.5%が埋まっています。もう1つ長いメッセージが来たら、システムは動かなくなります。
(現代のモデルは通常128K+トークンのコンテキストウィンドウを持ちますが、原理は同じです。文脈は有限であり、この上限を前提に設計しなければなりません。)
上限を超えると何が起きるか:
- 古いメッセージが落ちる → エージェントが以前の文脈を忘れ、会話の連続性が壊れる
- 取得ドキュメントが切り詰められる → 重要情報が失われ、誤答につながる
- リクエスト自体が失敗する → システムがまったく応答できない
追加料金を払って容量を増やすことはできません。 コンテキストウィンドウはハードな制限です。1リットルのボトルに2リットルを入れようとするようなものです。
だからこそ、長時間稼働するエージェントは、何を文脈に残し、何を残さないかを能動的に管理する必要があります。トークン管理は最適化の細部ではなく、アーキテクチャの中核課題です。
トークンが直接影響するもの
コンテキストウィンドウの上限に加えて、トークンは2つの重要な設計判断を左右します:
1. メモリ戦略: 全履歴 vs 要約
会話履歴をすべて保持すれば詳細は保たれますが、トークン使用量が継続的に増えます。
一般的な代替策が メモリ要約(memory summarization) です:
- 古いメッセージをコンパクトな要約に置き換える
- 意図を保ちながらトークンコストを削減する
このトレードオフは次へ影響します:
- コスト
- 精度
- 長期的なエージェントの一貫性
したがって、メモリ設計はトークン管理の問題です。
2. RAGのチャンクサイズとリトリーバル戦略
検索拡張生成(RAG: Retrieval-Augmented Generation)では、検索前にドキュメントをチャンクに分割します。
-
大きいチャンク
- 検索呼び出し回数が少ない
- リクエストあたりのトークンコストが高い
- 無関係な文脈が増える
-
小さいチャンク
- トークンコストが低い
- 精度が高い
- 重要情報を取りこぼすリスクがある
チャンクサイズは重要な設計判断であり、コストと回答品質の両方に直接影響します。
トークンの経済性
トークンコストを理解すると、コスト効率の良いシステムを構築できます。
一般的な価格(2026年):
| モデル | 入力(100万トークンあたり) | 出力(100万トークンあたり) |
|---|---|---|
| GPT-5 | $1.25 | $10.00 |
| GPT-5-mini | $0.25 | $2.00 |
| Claude 4.5 Sonnet | $3.00 | $15.00 |
| Gemini 3 Pro | $2.00 | $12.00 |
| Gemini 3 Flash | $0.50 | $3.00 |
重要な洞察: 出力トークンは入力トークンの4〜8倍のコストがかかります。つまり、生成が制御されていない場合、本番システムにおける最大のコスト要因になることが多いのです。
ざっくりコスト見積もり:
GPT-5-miniで、入力500トークン・出力50トークンの典型的リクエストの場合:
- 入力: (500 / 1,000,000) × $0.25 = $0.000125
- 出力: (50 / 1,000,000) × $2.00 = $0.0001
- 合計: 約$0.000225/リクエスト
1日10,000リクエストなら: 約$67.5/月
実務でのコスト最適化
戦略1: タスクの複雑さにモデルを合わせる
単純なタスクには小さく安いモデルを使います:
from langchain_openai import ChatOpenAI
# 複雑な推論向けの高価なモデル
complex_llm = ChatOpenAI(model="gpt-5")
# 単純なタスク向けの安価なモデル
simple_llm = ChatOpenAI(model="gpt-5-nano")
def get_llm_for_task(task_type):
if task_type == "complex_reasoning":
return complex_llm
else:
return simple_llm戦略2: 文脈サイズと品質のバランスを取る
必要な文脈だけを含めます:
# 効率的: 関連チャンクのみを含める
relevant_chunks = retrieve_top_k(user_question, k=3) # ~500 tokens
prompt = f"文脈: {relevant_chunks}\n\n質問: {user_question}"重要: 文脈の削減を攻めすぎると回答精度が下がることがあります。コスト削減と品質のバランスを取りましょう。
戦略3: 出力長を制御する
モデルが生成する量を制限します:
# コスト制御
llm = ChatOpenAI(model="gpt-5-mini", max_tokens=100)
response = llm.invoke(messages)
# 出力は最大100トークン重要なポイント: トークン管理はコスト削減だけではありません。ハードなリソース制約の中で、信頼性が高くスケーラブルなエージェントシステムを設計することです。
次のセクションでは、モデルの状況を整理し、タスクごとに適切なモデルを選ぶ方法を学びます。
2.5) モデルの状況を理解する
エージェントに適したLLMを選ぶには、次のバランスが必要です:
- コンテキストウィンドウ: モデルが処理できるテキスト量はどれくらいか?
- コスト: リクエストあたりの費用はいくらか?
- レイテンシ: モデルの応答速度はどれくらいか?
- 能力: モデルはどれくらい推論できるか?
2026年時点のモデル状況を俯瞰しましょう。
主要なモデルファミリー
OpenAI GPTモデル
| モデル | コンテキストウィンドウ | 入力コスト | 出力コスト | レイテンシ | 適した用途 |
|---|---|---|---|---|---|
| GPT-5 | 400K tokens | $1.25 / 1M | $10.00 / 1M | ~2–4s | 複雑な推論、高度なコード |
| GPT-5-mini | 400K tokens | $0.25 / 1M | $2.00 / 1M | ~1.5–3s | 一般タスク、チャット、要約 |
| GPT-5-nano | 400K tokens | $0.05 / 1M | $0.40 / 1M | ~1–2s | 分類、抽出 |
Anthropic Claudeモデル
| モデル | コンテキストウィンドウ | 入力コスト | 出力コスト | レイテンシ | 適した用途 |
|---|---|---|---|---|---|
| Claude Opus 4.5 | 200K tokens | $5.00 / 1M | $25.00 / 1M | ~2–4s | 深い推論、分析 |
| Claude Sonnet 4.5 | 200K tokens | $3.00 / 1M | $15.00 / 1M | ~1.5–3s | バランスの良い性能、コーディング |
Google Geminiモデル
| モデル | コンテキストウィンドウ | 入力コスト | 出力コスト | レイテンシ | 適した用途 |
|---|---|---|---|---|---|
| Gemini 3.0 Pro | 1M tokens | $2.00 / 1M | $12.00 / 1M | ~3–5s | 巨大コンテキスト、リサーチ |
| Gemini 3.0 Flash | 1M tokens | $0.50 / 1M | $3.00 / 1M | ~1–2s | 高スループットアプリケーション |
プロバイダー固有の特徴
OpenAI:
- 適した用途: 汎用エージェント、幅広いマルチドメインのワークフロー、function calling
- 強み: 多用途な推論、強力な開発者ツールとエコシステム、頻繁なモデル更新
Anthropic:
- 適した用途: 安全性が重要なワークフロー、構造化された長めの推論
- 強み: 深い分析、方法論的な出力、強いアラインメントを伴う拡張思考
Google:
- 適した用途: 巨大コンテキストの取り込みとマルチモーダルタスク
- 強み: 大規模ドキュメント分析、マルチモーダル理解、高スループット処理
タスクにモデルを合わせる
タスクの複雑さ、文脈サイズ、レイテンシ要件に基づいて選びます:
タスクの複雑さ別
単純タスク → コスト最適化モデル(GPT-5-nano、GPT-5-mini):
- 意図分類、感情分析、キーワード抽出、簡単な整形
- 選ぶ場面: コストが最優先
中程度のタスク → バランスモデル(GPT-5-mini):
- 質問応答、要約、ツール選択
- 選ぶ場面: コストと品質のバランスが必要
複雑なタスク → 性能重視モデル(GPT-5、Claude Sonnet、Gemini Pro):
- マルチステップ推論、コード生成、詳細な分析
- 選ぶ場面: 推論能力が重要で、コストが許容できる
最高難度 → プレミアムモデル(Claude Opus):
- 極めて複雑な推論、ミッションクリティカルな判断
- 選ぶ場面: 精度が最優先で、コストは二次
レイテンシ要件別
リアルタイム(体感レイテンシ~1秒以下) → 高速モデル(GPT-5-nano、Gemini Flash):
- ユーザー向けチャット
- インタラクティブアプリケーション
準リアルタイム(1〜3秒) → 多くのモデル:
- 標準的なエージェントタスク
バッチ(>3秒) → 高能力モデル:
- バックグラウンド分析
コンテキストウィンドウの考慮
標準タスク: 主要モデルはすべて200K+トークンをサポートしており、ほとんどのエージェントワークフローに十分です。
特殊ケース:
- 400Kトークンが必要: GPT-5ファミリー(全文書分析、大規模コードベース)
- 1Mトークンが必要: Geminiモデル(本丸ごと、巨大なドキュメント集合)
実務的な助言: 大きなコンテキストウィンドウがあっても、選択的リトリーバル(RAG)のほうが通常は良い結果になります。
重要なポイント
- 単一の「最良」モデルはない → モデルごとに得意分野が異なる
- タスクの複雑さにモデルを合わせる → 単純タスクに過剰に支払わない
- コンテキストウィンドウ ≠ より良い → 選択的リトリーバルを使う
- レイテンシがUXに影響する → インタラクティブタスクでは応答時間を考慮する
実務では: 多くのエージェントは複数モデルを使います。単純タスクには安価なモデル、複雑な推論には高能力モデル。これは後の章で実装します。
次のセクションでは、効果的なプロンプトでモデルの振る舞いを制御する方法を学びます。
2.6) エージェントシステムのためのプロンプト基礎
プロンプトは、LLMの振る舞いを制御するための主要インターフェースです。エージェントでは効果的なプロンプトが重要です。エージェントが正しい意思決定をし、適切なツールを呼び出し、信頼できる出力を生成できるかどうかを左右します。
プロンプトの構造
完全なプロンプトには3つの要素があります:
1. システムメッセージ(役割と制約) エージェントのペルソナ、能力、境界を定義します。
2. 文脈(関連情報) タスクを完了するために必要な情報を提供します。
3. 指示(具体的タスク) 何をすべきかを正確に伝えます。
実例で見てみましょう:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-5-mini")
response = llm.invoke([
# システムメッセージ: 役割と制約を定義する
{
"role": "system",
"content": """あなたはTechCorpのカスタマーサポート担当者です。
あなたの能力:
- 返金ポリシーに関する質問への回答
- サポートチケットの作成
- トラブルシューティングのガイダンス提供
あなたの制約:
- TechCorp製品に関する質問にのみ回答すること
- 返金のタイムラインについて約束しないこと
- 常に丁寧でプロフェッショナルであること"""
},
# ユーザーメッセージ: 文脈 + 指示
{
"role": "user",
"content": """文脈: 顧客は2026-01-15にノートパソコンのモデルX500を購入しました。
本日は2026-02-20です。返金ポリシーでは30日以内の返品が可能です。
指示: 顧客は返金を希望しています。何と伝えるべきですか?"""
}
])
print(response.content)出力:
ノートパソコンモデルX500の返金をご希望とのこと承知しました。残念ながら、
ご購入日は1月15日で本日は2月20日のため、30日間の返品期間を過ぎています。
ただし、保証サービスや交換など別の選択肢について確認できるよう、
サポートチケットを作成することは可能です。進めてもよろしいでしょうか?システムメッセージ: エージェントの振る舞いを設定する
システムメッセージは、エージェントの人格と能力を定義する場所です。これはエージェントのプロンプト設計で最も重要な部分です。
弱いシステムメッセージ:
system_message = "あなたは親切なアシスタントです。"強いシステムメッセージ:
system_message = """あなたはTechCorpのカスタマーサポート担当者です。
ROLE:
あなたは返金依頼、製品に関する質問、技術的な問題について顧客を支援します。
CAPABILITIES:
- 提供されたドキュメントを用いて質問に回答する
- 必要に応じてサポートチケットを作成する
- 手順に沿ったトラブルシューティングを提供する
CONSTRAINTS:
- TechCorp製品に関する質問にのみ回答する
- 分からない場合は分からないと言い、決して推測しない
- ドキュメントを使う場合は常にソースを引用する
- 特定のタイムラインや結果を決して約束しない
TONE:
プロフェッショナルで、共感的で、解決志向。
"""強いバージョンがより良く機能する理由:
- 能力が明示される → エージェントができることを理解する
- 制約が明確 → 避けるべきことを理解する
- トーンが定義される → 一貫した人格になる
- 具体的な指示 → 曖昧さが減る
指示の明確さ: 具体的にする
LLMは指示に忠実に従います。曖昧な指示は不安定な結果を生みます。
曖昧な指示:
instruction = "顧客の返金対応を手伝ってください。"具体的な指示:
instruction = """顧客の依頼を分析し、次を判断してください:
1. 注文は返金対象ですか?(購入日と返金ポリシーを照合)
2. 対象である場合: 返金手順を説明する
3. 対象でない場合: 理由を説明し代替案を提示する
次の形式で回答してください:
- Eligibility: [YES/NO]
- Reason: [簡潔な説明]
- Next Steps: [顧客が次に行うべきこと]
"""文脈管理: 必要なものを提供する
エージェントは意思決定のために文脈が必要ですが、多すぎる文脈はトークンを浪費し、モデルを混乱させます。
過剰な文脈(無駄):
context = f"""
会社沿革: TechCorp は1995年に設立されました...
製品カタログ: 私たちは...を含む500以上の製品を販売しています...
返金ポリシー: {refund_policy_text}
配送ポリシー: {shipping_policy_text}
保証ポリシー: {warranty_policy_text}
顧客履歴: {full_customer_history}
"""
# 5000+ tokens, most irrelevant選択的な文脈(効率的):
context = f"""
Relevant Policy: {refund_policy_text}
Order Details: {order_details}
"""
# 200 tokens, all relevant出力フォーマット: 応答を構造化する
エージェントでは、自由形式テキストではなく、構造化出力(JSONや特定フォーマット)が必要になることがよくあります。
非構造化出力(パースが難しい):
response = llm.invoke([
{"role": "system", "content": "あなたはサポート担当者です。"},
{"role": "user", "content": "この返金依頼に対してチケットを作成すべきですか?"}
])
print(response.content)
# 出力: "はい、チケットを作成すべきだと思います。理由は..."
# 問題: パースが難しく、形式が不安定構造化出力(パースが簡単):
response = llm.invoke([
{"role": "system", "content": """あなたはサポート担当者です。
必ず次のJSON形式で回答してください:
{
"action": "CREATE_TICKET" or "ANSWER_QUESTION" or "ESCALATE",
"reason": "簡潔な説明",
"response_text": "顧客に伝える内容"
}"""},
{"role": "user", "content": "顧客は注文 #12345 の返金を希望しています(購入から40日経過)。"}
])
print(response.content)出力:
{
"action": "CREATE_TICKET",
"reason": "注文が30日間の返金期限を過ぎているため、手動レビューが必要です",
"response_text": "返金依頼を確認するためのサポートチケットを作成しました。担当チームが24時間以内にご連絡します。"
}第7章では、堅牢な構造化出力のためにPydanticスキーマを使います。
Few-shot例: 言うだけではなく見せる
複雑なタスクでは、長い指示よりも例を示すほうが効果的です。
ゼロショット(指示のみ):
prompt = """顧客メッセージから注文ID、製品名、問題を抽出してください。
顧客メッセージ: "ノートパソコンX500の注文 #12345 の電源が入りません"
"""
# モデルが形式に苦戦する可能性があるFew-shot(例あり):
prompt = """顧客メッセージから注文ID、製品名、問題を抽出してください。
Example 1:
Input: "ノートパソコンX500の注文 #12345 の電源が入りません"
Output: {"order_id": "12345", "product": "laptop X500", "issue": "won't turn on"}
Example 2:
Input: "注文 67890 - スマホが充電できません"
Output: {"order_id": "67890", "product": "phone", "issue": "not charging"}
次のメッセージから抽出してください:
Input: "My tablet order #11111 has a cracked screen"
Output:
"""モデルは例からパターンを学び、一貫して適用します。
エージェント向けのプロンプトエンジニアリング・パターン
パターン1: Chain of Thought(推論)
複雑な判断では、「ステップごとに考える」よう依頼します:
prompt = """サポートチケットを作成すべきか判断する必要があります。
次をステップごとに考えてください:
1. 顧客は何を求めていますか?
2. 既存のドキュメントで回答できますか?
3. 手動介入が必要ですか?
4. どのアクションを取るべきですか?
Customer message: "I want a refund for order #12345 but I lost the receipt"
Reasoning:
"""モデルが推論を明示するため、意思決定がより透明になり、信頼性も高まります。
パターン2: 制約付き生成(安全性)
モデルが出力できる候補を制限します:
prompt = """顧客の意図を分類してください。次の選択肢のうち、EXACTLY ONE のみで回答してください:
- REFUND_REQUEST
- PRODUCT_QUESTION
- TECHNICAL_ISSUE
- OFF_TOPIC
Customer message: "How do I reset my password?"
Classification:
"""これにより、想定外の出力を防ぎます。出力候補を明示的に制約することで:
- パースエラーを防ぐ(常に定義済み4種のいずれか)
- 意図しないアクションを防ぐ(未定義アクションの実行を防止)
- デバッグを簡単にする(出力空間が限定され、問題を追いやすい)
パターン3: セルフクリティーク(品質)
モデルに自分の出力を検証して改善させます:
prompt = """顧客への応答を生成し、その後で批評してください。
Customer message: "I want a refund"
Step 1 - Generate response:
[Your response here]
Step 2 - Critique:
- Is this response accurate?
- Is it helpful?
- Does it follow company policy?
- What could be improved?
Step 3 - Final response (incorporating critique):
[Improved response here]
"""このマルチステップのアプローチは、次の理由で高品質な出力になりやすいです:
- 早期にエラーを検出: 最終化前にモデルが自身の推論を見直す
- トーンと明確さを改善: 自己反省により不明確・不適切な言い回しを見つけやすい
- ポリシー遵守を担保: 批評ステップでガイドラインへの準拠を検証する
よくあるプロンプトのミス
ミス1: モデルが何かを「知っている」と思い込む
LLMはリアルタイム情報や暗黙の文脈にアクセスできません。必要なデータは常に明示的に提供してください。
# 悪い例: モデルが現在日付を知っている前提
prompt = "Is this order eligible for refund? Order #12345"
# 良い例: 必要な情報をすべて提供する
prompt = f"""Is this order eligible for refund?
Order #12345
purchased {purchase_date}
Today: {current_date}
Policy: 30-day returns
"""ミス2: 曖昧な指示
「handle」「process」「deal with」のような曖昧な動詞は解釈の余地が大きすぎます。必要な正確なアクションを明示してください。
# 悪い例: "handle" とは何を意味するのか?
prompt = "Handle this refund request"
# 良い例: 明示的なアクション
prompt = "Determine if this refund request is eligible. If yes, create a ticket. If no, explain why."ミス3: 文脈の詰め込みすぎ
無関係な情報を含めるとトークンを浪費し、レイテンシが増え、モデルを混乱させる可能性があります。特定タスクに必要なものだけを取得してください。
# 悪い例: 10,000トークンの文脈の大半が無関係
prompt = f"""
{entire_knowledge_base}
Question: What's the refund policy?
"""
# 良い例: 関連セクションのみ取得する
prompt = f"""
{refund_policy_section}
Question: What's the refund policy?
"""ミス4: 不整合なフォーマット
出力形式が揺れると、下流のコードが壊れます。特に構造化データでは、期待する正確な形式を常に指定してください。
# 悪い例: ときどきJSON、ときどきプレーンテキスト
prompt = "Respond with your decision"
# 良い例: 常に形式を指定する
prompt = 'Respond in JSON format: {"decision": "...", "reason": "..."}'重要なポイント
エージェント向けの効果的なプロンプトには次が必要です:
- 明確なシステムメッセージ → 役割、能力、制約を定義する
- 具体的な指示 → モデルに何をすべきか正確に伝える
- 選択的な文脈 → 関連情報のみ提供する
- 構造化出力 → 形式を明示的に指定する
- Few-shot例 → 望むパターンを示す
- 推論プロンプト → ステップごとの思考を求める
プロンプトは練習で上達するスキルです。本書を通して、これらのパターンが実際のエージェントシステムでどのように適用されるかを見ていきます。
章のまとめ
これで、エージェント型AIシステムを構築するための基礎概念を理解できました:
エージェント型AI vs チャットボット:
- エージェントは目標達成のために自律的に行動する
- エージェントはツールを使い、マルチステップで意思決定する
- エージェントは状態を維持し、結果に応じて適応する
フレームワークが重要な理由:
- LangChainはモデル抽象化、チェーン、メモリ、ツール、RAGを提供する
- LangGraphは状態管理、ルーティング、チェックポイントを追加する
- フレームワークはボイラープレートを減らし、複雑なワークフローを可能にする
LLMのメカニクス:
- LLMはトークンを予測し、事実を取得するわけではない
- 文脈は暗黙ではなく明示的である
- プロンプトはクエリではなく指示である
- 構造化出力にはガイダンスが必要
- コンテキストウィンドウは有限である
トークンの経済性:
- 出力トークンは入力トークンの4〜8倍のコスト
- モデル選択は10〜100倍のコスト差を生む
- 選択的な文脈はコストを劇的に下げる
- 監視と予算設定が暴走コストを防ぐ
モデル選定:
- タスクの複雑さにモデルを合わせる
- 文脈要件とレイテンシ制約を考慮する
- コスト最適化のためにマルチモデル構成を使う
- まず安く始め、必要ならアップグレードする
プロンプトの基礎:
- システムメッセージがエージェントの振る舞いを定義する
- 具体的な指示が信頼できる結果を生む
- 選択的な文脈が品質を上げ、コストを下げる
- 構造化出力がパースと検証を可能にする
- Few-shot例がパターンを効果的に教える