LLMアプリケーション開発の実践:プロンプトエンジニアリングからエージェントまで
LLMを活用したアプリケーション開発の実践的手法を解説。プロンプト設計の体系的アプローチから、Function Calling、AIエージェント構築、本番運用のベストプラクティスまで。
はじめに:LLMアプリケーション開発の現状
2023年以降、LLM(大規模言語モデル)を活用したアプリケーション開発は急速に進化しています。単純なチャットボットから、複雑なワークフローを自律的に実行するAIエージェントまで、その応用範囲は拡大し続けています。
しかし、「ChatGPTのAPIを叩けばいい」という単純な話ではありません。本番品質のLLMアプリケーションを構築するには、体系的なアプローチが必要です。
本記事では、プロンプトエンジニアリングの基礎から、Function Calling、AIエージェント、本番運用まで、実践的な開発手法を解説します。
プロンプトエンジニアリングの体系的アプローチ
プロンプトの構成要素
効果的なプロンプトは、以下の要素で構成されます:
1. System Prompt(システムプロンプト)
- AIの役割・ペルソナの定義
- 振る舞いのルール・制約
- 出力フォーマットの指定
2. Context(文脈)
- タスクに必要な背景情報
- 参照すべきドキュメント
- 過去の会話履歴
3. Task(タスク)
- 具体的な指示
- 期待する出力の例
- 制約条件
プロンプト設計のベストプラクティス
明確で具体的な指示を与える
❌ 悪い例:
「この文章を要約して」
✅ 良い例:
「以下の文章を、重要なポイントを3つの箇条書きにまとめてください。
各ポイントは50文字以内で、専門用語は使わず平易な表現にしてください。」
役割を明示する
あなたは経験豊富なテクニカルライターです。
技術的な内容を、非エンジニアにもわかりやすく説明することが得意です。
出力フォーマットを指定する
以下のJSON形式で出力してください:
{
"summary": "要約文",
"key_points": ["ポイント1", "ポイント2", "ポイント3"],
"sentiment": "positive" | "negative" | "neutral"
}
Few-shot例を提供する
例1:
入力:「昨日の会議は長かったが、重要な決定ができた」
出力:{"sentiment": "positive", "reason": "重要な決定ができたという成果がある"}
例2:
入力:「プロジェクトが遅延している」
出力:{"sentiment": "negative", "reason": "遅延はネガティブな状況"}
では、以下の入力を分析してください:
Chain of Thought(思考の連鎖)
複雑なタスクでは、ステップバイステップの思考を促すことで精度が向上します。
以下の問題を解いてください。
まず、問題を理解し、解法を段階的に考え、最後に答えを出してください。
問題:[問題文]
Step 1: 問題の理解
Step 2: 必要な情報の整理
Step 3: 解法の検討
Step 4: 計算・推論
Step 5: 答えの確認
Function Calling / Tool Useの実装
Function Callingとは
Function Calling(Tool Use)は、LLMに「いつ」「どの」関数を呼び出すべきかを判断させ、適切なパラメータを生成させる機能です。
ユースケース
- 外部APIの呼び出し
- データベースへのクエリ
- 計算処理の実行
- ファイル操作
実装パターン
基本的なFunction定義
const tools = [
{
type: "function",
function: {
name: "get_weather",
description: "指定した都市の現在の天気を取得します",
parameters: {
type: "object",
properties: {
city: {
type: "string",
description: "都市名(例:東京、大阪)"
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "温度の単位"
}
},
required: ["city"]
}
}
}
];
実行フロー
1. ユーザー入力 + Tool定義 → LLM
2. LLMがTool呼び出しを判断 → tool_calls返却
3. アプリがToolを実行 → 結果取得
4. 結果をLLMに返却 → 最終回答生成
設計のポイント
1. 関数の粒度
- 1つの関数は1つの責務に
- 複雑な処理は複数の関数に分割
- 再利用可能な設計
2. パラメータ設計
- 明確なdescriptionを記述
- enumで選択肢を制限
- requiredを適切に設定
3. エラーハンドリング
- Tool実行失敗時の処理
- LLMへのエラー伝達方法
- リトライ戦略
AIエージェントの設計
エージェントとは
AIエージェントとは、目標を達成するために、自律的に判断・行動を繰り返すAIシステムです。単発のLLM呼び出しとは異なり、複数のステップを経て複雑なタスクを遂行します。
エージェントのアーキテクチャパターン
パターン1:ReAct(Reasoning + Acting)
思考(Thought)→ 行動(Action)→ 観察(Observation)のループを繰り返します。
Thought: ユーザーは東京の明日の天気を知りたい
Action: get_weather(city="東京", date="tomorrow")
Observation: 晴れ、最高気温25度、最低気温18度
Thought: 天気情報が取得できた。ユーザーにわかりやすく伝えよう
Action: respond("明日の東京は晴れで、最高気温25度、最低気温18度の予報です")
パターン2:Plan and Execute
最初に計画を立て、その後ステップごとに実行します。
Plan:
1. ユーザーの要件を確認
2. 必要なデータを収集
3. データを分析
4. レポートを作成
5. ユーザーに提示
Execute Step 1: [実行]
Execute Step 2: [実行]
...
パターン3:Multi-Agent(マルチエージェント)
複数の専門エージェントが協調して動作します。
Orchestrator Agent
├── Research Agent(調査担当)
├── Analyst Agent(分析担当)
├── Writer Agent(執筆担当)
└── Reviewer Agent(レビュー担当)
エージェント実装の注意点
1. ループの制御
- 最大ステップ数の設定
- 無限ループの検出・回避
- タイムアウトの設定
2. エラーからの回復
- 失敗時のリトライ
- 代替手段の実行
- ユーザーへのエスカレーション
3. コスト管理
- API呼び出し回数の監視
- 各ステップのトークン数把握
- 予算上限の設定
評価とテスト
LLMアプリケーションのテストの難しさ
従来のソフトウェアテストとは異なり、LLMの出力は非決定的です。同じ入力でも異なる出力が返る可能性があります。
評価の種類
1. オフライン評価
事前に用意したデータセットで評価します。
// 評価データセット例
const evalDataset = [
{
input: "AIとは何ですか?",
expectedTopics: ["人工知能", "機械学習", "コンピュータ"],
minLength: 100,
maxLength: 500
},
// ...
];
評価指標
- 正確性:期待するキーワード・トピックを含むか
- 一貫性:同じ質問に対する回答のばらつき
- 安全性:有害なコンテンツを含まないか
- 形式準拠:指定したフォーマットに従っているか
2. LLM-as-a-Judge
別のLLMを使って回答品質を評価します。
以下の質問と回答を評価してください。
質問:{question}
回答:{answer}
評価基準:
1. 正確性(1-5)
2. 網羅性(1-5)
3. わかりやすさ(1-5)
JSON形式で評価結果を出力してください。
3. A/Bテスト
本番環境で異なるバージョンを比較します。
- プロンプトのバリエーション比較
- モデルバージョンの比較
- パラメータ設定の比較
本番運用のベストプラクティス
可観測性(Observability)
1. ログ収集
// 記録すべき項目
{
requestId: "uuid",
timestamp: "2024-01-15T10:30:00Z",
userId: "user-123",
model: "gpt-4o",
promptTokens: 1500,
completionTokens: 500,
latencyMs: 2300,
input: "...", // 必要に応じて
output: "...", // 必要に応じて
toolCalls: [...],
error: null
}
2. メトリクス監視
- レイテンシ(P50, P95, P99)
- エラー率
- トークン使用量
- コスト
3. トレーシング
LangSmith、Langfuse、Phoenix等のツールで、LLM呼び出しの詳細をトレース。
コスト最適化
1. モデル選択の最適化
- タスクに応じた適切なモデル選択
- 簡単なタスクは小さいモデルで
- ルーティングによる動的モデル選択
2. キャッシング
- 同一クエリのキャッシュ
- セマンティックキャッシュ(類似クエリ)
- Embedding結果のキャッシュ
3. プロンプト最適化
- 不要なトークンの削減
- 動的なプロンプト構築
- Few-shot例の最適化
セキュリティ
1. プロンプトインジェクション対策
// 入力のサニタイズ
function sanitizeInput(input: string): string {
// 危険なパターンの検出・除去
// システムプロンプトの上書き試行を検出
// ...
}
2. 出力のフィルタリング
- 機密情報の検出・マスキング
- 有害コンテンツのフィルタリング
- PII(個人識別情報)の除去
3. レート制限
- ユーザーごとのリクエスト制限
- 異常なパターンの検出
- 不正利用の防止
実装例:カスタマーサポートボット
最後に、これまでの内容を統合した実装例を示します。
システム構成
User → API Gateway → Support Bot Agent
├── FAQ Search Tool
├── Order Lookup Tool
├── Ticket Creation Tool
└── Human Escalation Tool
システムプロンプト例
あなたはECサイト「〇〇ショップ」のカスタマーサポートAIです。
【役割】
- お客様の問い合わせに親切・丁寧に対応する
- 問題解決のために必要なツールを活用する
- 解決できない場合は人間のオペレーターにエスカレーションする
【対応ルール】
1. まず、お客様の問い合わせ内容を正確に理解する
2. FAQ検索で解決できる場合は、FAQの情報を基に回答する
3. 注文に関する問い合わせは、注文番号を確認して詳細を調べる
4. 解決が難しい場合や、お客様が人間の対応を希望する場合はエスカレーションする
【禁止事項】
- 個人情報を不必要に聞き出さない
- 確証のない情報を伝えない
- 他社の批判をしない
【出力形式】
- 丁寧な敬語を使用
- 長文は避け、簡潔に
- 必要に応じて箇条書きを活用
ツール定義
const tools = [
{
name: "search_faq",
description: "FAQデータベースから関連する情報を検索",
parameters: { query: "string" }
},
{
name: "lookup_order",
description: "注文番号から注文詳細を取得",
parameters: { orderId: "string" }
},
{
name: "create_ticket",
description: "サポートチケットを作成",
parameters: {
category: "string",
description: "string",
priority: "low|medium|high"
}
},
{
name: "escalate_to_human",
description: "人間のオペレーターにエスカレーション",
parameters: { reason: "string" }
}
];
まとめ
LLMアプリケーション開発は、単なるAPI呼び出しを超えた、体系的なエンジニアリングが求められます。
重要なポイント
- プロンプトエンジニアリングは「職人芸」ではなく「エンジニアリング」
- Function Callingで外部システムと連携
- エージェントで複雑なタスクを自律的に遂行
- 評価・テストの仕組みを最初から組み込む
- 本番運用を見据えた可観測性・セキュリティ
LLMアプリケーション開発についてのご相談は、Algoboaまでお気軽にお問い合わせください。設計から実装、運用まで一貫してサポートいたします。