1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【要約】12-factor-agents:本番環境で動くLLMアプリケーションを構築するための原則

1
Last updated at Posted at 2025-10-18

前提

本記事はAI(Claude 4.5 sonnet)を使用して作成しました。

以下のGithubから取得できるREADME.mdをAIに提供して、内容を要約してもらうように指示を出してます。

とはいえ、要約をAIに出してもらった後のレビューは筆者にて実施しています。体裁とか、なんか文章変じゃないか?と思うところは適宜修正・加筆をしています(修正点は、ほぼ無かった)。

口調については大分話し口調のためラフな内容に読めますが、まあ自分的にはそっちの方が読みやすいかなと思った次第です。そういう口調で記載するようにユーザプロンプトを調整しました。

はじめに

AIエージェント作ってみたけど、なんか本番に出せるレベルじゃないんだよなぁ…って経験ありませんか?

実は、「プロンプト渡して、ツール一式渡して、ゴールまでループさせればOK!」っていう作り方じゃ、本番で使えるエージェントって作れないんです。

今回紹介する12ファクターエージェントは、あの有名な12ファクターアプリの精神を受け継いで、「ちゃんと本番環境で動くAIエージェントってどう作るの?」っていう問いに答えてくれる方法論です。

なんでこの方法論が生まれたのか

作者のDexさん、めちゃくちゃ色んなエージェントフレームワーク試したらしいんですよ。crew/langchainみたいなプラグアンドプレイ系から、smolagentsみたいなミニマリスト系、langraphやgriptapeみたいな「本番環境用」って謳ってるやつまで。

Griptapeは、本番環境向けを謳うAIエージェントフレームワークの一つです。著者が検証した多数のエージェントフレームワークの中で、LangGraphと並んで「本番環境用」として位置づけられているフレームワークとして言及されています。

smolagentsは、ミニマリスト系(最小限の機能に絞った)AIエージェントフレームワークの一つです。12ファクターエージェントの作者が検証した様々なエージェントフレームワークの中で、シンプルさを重視した設計アプローチを取るフレームワークとして紹介されています。

で、100人以上のSaaS開発者(ほとんど技術系の創業者)と話した結果、みんなこういう道を辿ってることが分かったんです:

  1. よし、エージェント作るぞ!
  2. プロダクト設計して、UX考えて、どんな問題解決するか決めて
  3. 早く動かしたいから、フレームワーク選んでさっそく作り始める
  4. うん、70-80%くらいのクオリティには到達したな
  5. あれ…でも顧客向け機能として出すには80%じゃ全然足りないぞ?
  6. 結局フレームワークのプロンプトとかフローとか、全部リバースエンジニアリングしないと80%超えられない…
  7. もういいや、ゼロから作り直そう

そう、優れたエージェントって実はほとんどが普通のソフトウェアで、適切な箇所にLLMを散りばめることで「魔法のような体験」を作ってるんですよね。

そもそもエージェントって何なの?ソフトウェアの歴史から紐解く

ソフトウェアは有向グラフだった

昔、プログラムってフローチャートで表現してましたよね。あれ、実はソフトウェアって本質的に有向グラフ(DG)なんだってことを示してたんです。

DAGオーケストレーターの時代

20年くらい前から、AirflowとかPrefectとかDagsterみたいなDAGオーケストレーターが流行り出しました。

これらは同じグラフパターンを使いながら、可観測性とかモジュール性とか、リトライ機能とか管理機能とかを追加してくれてたんです。

エージェントの魅力的な約束

で、エージェントの何がすごいって、DAGを捨てられるってことなんですよ。

今までみたいにエンジニアが全部のステップとエッジケースをコーディングするんじゃなくて、エージェントにゴールと選択肢を渡すだけ。あとはLLMがリアルタイムで判断して、勝手にパスを見つけてくれる。

コード書く量が減るし、エラーからも回復できるし、LLMが思いもよらない解決策を見つけてくれるかもしれない。夢みたいな話ですよね?

エージェントのループ構造

エージェントって基本的に3ステップのループで動いてます:

  1. LLMが次に何するか決めて、構造化されたJSON(「ツール呼び出し」)を出力
  2. 決まったコードがそのツール呼び出しを実行
  3. 結果をコンテキストウィンドウに追加
  4. 「完了」って判断されるまで繰り返し
initial_event = {"message": "..."}
context = [initial_event]
while True:
  next_step = await llm.determine_next_step(context)
  context.append(next_step)

  if (next_step.intent === "done"):
    return next_step.final_answer

  result = await execute_step(next_step)
  context.append(result)

シンプルでしょ?でもこれだけじゃ足りないんです。

なんで12ファクターエージェントが必要なの?

さっきのループだけだと、期待通りに動かないんですよね。

数百のライブラリを調べて分かったこと

Dexさんが数百のAIライブラリ調査して、数十人の創業者と仕事した結果、こういう結論に至ったそうです:

  1. エージェントを優れたものにする核心的な要素がある
  2. フレームワークに全乗っかりして、グリーンフィールドで全部書き直すのは逆効果かも
  3. エージェントを良くする原則があって、フレームワーク使えばそれらは大体手に入る
  4. でも、高品質なAIソフトウェアを顧客に届ける一番速い方法は、エージェント構築のモジュール化された概念を取り出して、既存プロダクトに組み込むこと
  5. こういうモジュール化された概念って、AI経験がなくても腕のいいソフトウェアエンジニアなら定義・適用できる

LLMがどれだけ指数関数的に強力になっても、LLMソフトウェアをより信頼性高く、スケーラブルで、保守しやすくするエンジニアリング技術は存在し続ける

どこから始めればいい?

12ファクターエージェントの良いところは、全部一気に実装しなくていいってことです。今困ってることから段階的に始めましょう。

課題別のスタートガイド

今困ってること まずここから
プロンプトがうまくいかない Factor 2(プロンプト管理)
人間のチェックが必要 Factor 7(ツール呼び出しで人間と連絡)
デバッグが大変 Factor 5(状態統一)+ Factor 3(コンテキスト管理)
信頼性が心配 Factor 6(起動/一時停止/再開)+ Factor 8(制御フロー)
パフォーマンスが悪い Factor 10(小さく焦点絞ったエージェント)
統合が難しい Factor 11(どこからでも起動)

段階的に進める

  1. ステップ1: 一番痛いところから着手
  2. ステップ2: 関連するファクター追加
  3. ステップ3: 全体のアーキテクチャ最適化
  4. 継続改善: フィードバック見ながら反復

12の原則を詳しく見ていこう

Factor 1: 自然言語をツール呼び出しに変換する

ユーザーが普通に話した言葉を、構造化されたツール呼び出しに直接変換します。これがエージェントがちゃんと動く基本パターンです。

こんな感じ:

「Jeffに750ドルの支払いリンク作って」
↓
{
  "function": "create_payment_link",
  "parameters": {
    "amount": 750,
    "customer": "cust_128934ddasf9",
    "memo": "サービスの支払い"
  }
}

なんでこれが大事?

  • 曖昧さがなくなる
  • デバッグもテストもラクラク
  • 動作が予測できる
  • 監査証跡も残せる

Factor 2: プロンプトは自分で管理しよう

プロンプトエンジニアリングをフレームワークに丸投げしちゃダメです。プロンプトをちゃんとしたコードとして扱って、バージョン管理して、テストして、改善していきましょう。

何が良いの?

  • 指示を完全にコントロールできる
  • テストもバージョン管理もできる
  • 実際の動きを見ながら高速でイテレーション回せる
  • エージェントが何やってるか透明性が保てる
  • ブラックボックス化を避けられる

実践的なヒント:

  • プロンプトをコードリポジトリで管理する
  • A/Bテストで効果測定
  • モデルごとに最適化

Factor 3: コンテキストウィンドウも自分で管理する

標準的なメッセージフォーマットだけに頼っちゃダメ。最大限の効果を出すためにコンテキストをエンジニアリングしましょう。コンテキストウィンドウって、LLMとのメインインターフェースなんです。

最適化するポイント:

  • トークン効率: 無駄なトークンは削る
  • 情報密度: 大事な情報を優先的に配置
  • LLMの理解しやすさ: モデルが分かりやすい形式にする
  • 人間が読みやすいか: 開発者がデバッグしやすい構造にする

カスタムフォーマットの例:

  • 時系列じゃなくて重要度順に並べる
  • エラー情報は圧縮して入れる
  • 実行トレースをコンパクトに表現

Factor 4: ツールってただの構造化出力

ツールって複雑に考える必要ないんです。LLMが吐き出す構造化されたJSON、それだけ。で、そのJSONが決まったコードをキックする。これだけでLLMの判断とアプリの動作がきれいに分離できます。

実装例:

if (nextStep.intent === 'create_payment_link') {
  await stripe.paymentlinks.create(nextStep.parameters);
} else if (nextStep.intent === 'wait_for_approval') {
  // 止めて人間の判断を待つ
  await pauseAndWaitForHuman();
} else {
  // 知らないツール呼び出しが来たら処理
  handleUnknownTool(nextStep);
}

覚えておくべきこと:

  • ツールはLLMの出力フォーマットってだけ
  • 実行ロジックは普通のコード
  • バリデーションと型チェックを忘れずに

Factor 5: 実行状態とビジネス状態は一緒に管理

実行状態(今どのステップ?待ってる?)とビジネス状態(何が起きた?)を分けて考えない。一緒に管理しちゃいましょう。

何が嬉しい?

  • 真実は一箇所に: 状態管理が一箇所で済む
  • 保存も復元も簡単: シリアライゼーションがラク

シリアライゼーション(直列化)とは、プログラム内のデータ構造やオブジェクトの状態を、保存や送信が可能な形式(バイト列や文字列など)に変換するプロセスです。この文脈では、AIエージェントの実行状態とビジネス状態を統合管理することで、状態の保存と復元(シリアライゼーション)が簡単になることを指しています。

  • 履歴が丸見え: 何が起きたか完全に追える
  • 復旧もフォークも簡単: エラーからの回復や分岐がラク

実装イメージ:

interface UnifiedState {
  // ビジネス状態
  customer: Customer;
  paymentLinks: PaymentLink[];

  // 実行状態
  currentStep: string;
  waitingFor?: 'human_approval' | 'payment_completion';
  executionHistory: Step[];
}


Factor 6: 起動/一時停止/再開をシンプルなAPIで

エージェントって、起動して、止めて、再開するのが簡単じゃないとダメなんです。これで中断に強い、信頼できるワークフローが作れます。

大事な機能:

  • 起動(Launch): 新しい実行スタート
  • 一時停止(Pause): 長時間の処理とか人間の判断を待つ
  • 再開(Resume): 止まったとこから続ける

使いどころ:

  • 人間の承認待ち
  • 外部APIの非同期レスポンス待ち
  • 長時間バッチ処理
  • エラー後の手動介入

Factor 7: 人間とのやりとりもツール呼び出しで

人間とのやりとりを特別扱いしない。ただのツール呼び出しとして扱います。LLMにテキストか構造化データか選ばせるんじゃなくて、常に構造化出力使いましょう。

人間とのやりとりを構造化:

{
  "intent": "request_human_input",
  "question": "この支払い承認する?",
  "context": {
    "amount": 750,
    "recipient": "Jeff"
  }
}

これでできること:

  • いろんなタイプの人間とのやりとりに明確な指示
  • エージェント→人間から始まるワークフロー
  • 複数人の調整
  • マルチエージェント通信
  • 人間の返事待ってる間の状態管理がクリア

Factor 8: 制御フローは自分でコントロール

自分のユースケースに合わせてカスタムの制御構造を作りましょう。単純なループじゃ足りないことも多いんです。

カスタム制御フローの例:

  • 人間の返事待つためにループから抜ける
  • 特定のツール呼び出しの後で違う処理パスに分岐
  • エラー時の独自リトライロジック
  • OODA(Observe, Orient, Decide, Act)ループ実装

考え方:

  • フレームワークの制御フローに縛られない
  • ビジネスロジックに最適な制御構造を設計
  • 収束条件をちゃんと実装

Factor 9: エラーはコンテキストウィンドウに圧縮

エラーが起きたら、エージェントのループ壊しちゃダメ。役立つコンテキストに圧縮してLLMに渡しましょう。

実装のやり方:

try {
  await executeTool(step);
} catch (error) {
  const compactedError = {
    type: 'tool_execution_error',
    tool: step.tool,
    summary: error.message,
    recoveryHints: getRecoveryHints(error)
  };
  context.append(compactedError);
  // エージェントのループ続けてLLMに自己修復させる
}

何が良い?

  • 信頼性アップ
  • エージェントが失敗から学習できる
  • 自己修復能力
  • より頑丈なワークフロー

Factor 10: 小さく焦点絞ったエージェントを作る

一個のことをちゃんとやるエージェントを作りましょう。LLMがどれだけ賢くなっても、焦点絞ったエージェントの方がモノリシックなやつよりずっと良いです。

何が良い?

  • デバッグしやすい
  • テストしやすい
  • 保守しやすい
  • 個別にスケールできる
  • 責任が明確

設計の考え方:

  • 単一責任の原則(SRP)をエージェントにも適用
  • 複雑なタスクは小さいエージェントに分割
  • エージェント間のインターフェースを明確に

Factor 11: どこからでも起動、ユーザーがいる場所で対応

エージェントって、どんなインターフェースからでも起動できないとダメなんです。ユーザーを一個のやり方に縛り付けちゃダメ。

起動できる場所の例:

  • Webhook: 外部システムからのイベント
  • Cron: 定期実行
  • Slack: チャットから
  • Email: メール経由
  • API: プログラムから呼び出し
  • UI: WebやモバイルアプリのUI

なんで大事?

  • ユーザーの柔軟性
  • 既存ワークフローへの統合
  • マルチチャネル対応

Factor 12: エージェントをステートレスリデューサー(関数)にする

エージェントを、今の状態とイベントを受け取って新しい状態を返す純粋な関数として設計しましょう。

リデューサーパターン:

function agentReducer(
  currentState: State,
  event: Event
): State {
  // 副作用なし、決まった変換
  const nextState = computeNextState(currentState, event);
  return nextState;
}

この関数型アプローチの良さ:

  • テストしやすい: 入力と出力が明確
  • 理解しやすい: 動きが予測できる
  • 並列実行できる: 状態の競合がない
  • タイムトラベルデバッグ: いつでも任意の時点に戻れる
  • ロールバック簡単: 前の状態に戻すのがラク

企業で使うとこんなに嬉しい

🔒 セキュリティとコンプライアンス

  • 重要な操作には人間の承認(Human-in-the-loop)
  • 構造化された状態で完全な監査証跡
  • コントロールされた実行環境
  • コンプライアンス要件に対応

📊 可観測性

  • エージェントの判断プロセスが全部見える
  • 構造化されたログとトレース
  • 統一された状態管理でデバッグがラク
  • リアルタイムモニタリング

⚡ 信頼性

  • エラーハンドリングがしっかりしてる
  • 一時停止/再開で中断に強い
  • ミッションクリティカルな操作も決定論的に実行
  • 自己修復できる

🔧 保守性

  • プロンプトもツールもバージョン管理
  • コンポーネントがテストできる
  • 必要に応じて進化するモジュラー設計
  • 責任が明確に分離されてる

📈 スケーラビリティ

  • ステートレス設計で水平スケーリング
  • シンプルで分かりやすいAPI
  • 個別にデプロイ・スケールできる
  • リソース効率が良い

🤝 統合

  • 既存システムとスムーズに連携
  • 全部書き直す必要なし
  • ユーザーがいつも使ってる場所で対応
  • 段階的に導入できる

おまけ:第13の原則

Factor 13: 必要そうなコンテキストは先に取得

LLMに必要な時に情報取りに行かせるんじゃなくて、必要になりそうな情報は全部先に取っておきましょう。

何が良い?

  • レスポンスが速くなる
  • 実行がより確実
  • コンテキストに一貫性

実際のプロジェクトでの使われ方

この方法論って理論じゃなくて、実際の本番環境での経験から生まれたものなんです:

  • 顧客向けAIエージェントの構築とデプロイ経験
  • 主要エージェントフレームワーク全部テスト済み
  • 数百人の技術系創業者との協力
  • 本番での失敗と成功から学んだこと

関連プロジェクト

  • got-agents/agents: この方法論でOSSエージェント構築
  • kubechain: Kubernetesで分散エージェントを動かすフレームワーク
  • HumanLayer: 人間承認システム

まとめ

12ファクターエージェントは、LLMアプリを本番で使えるレベルにするための実践ガイドです。

大事なポイント:

  1. フレームワークに全乗せしない: 全部任せちゃダメ
  2. モジュール化された概念: 小さい原則を既存プロダクトに組み込む
  3. 段階的に実装: 全部一気にやる必要なし
  4. 実践的: 実際の本番環境での経験ベース

LLMがどれだけ進化しても、こういうエンジニアリングの基本原則がLLMソフトウェアをより信頼性高く、スケーラブルで、保守しやすくしてくれます。

一番速いのは、エージェント構築のモジュール化された概念を取り出して、既存プロダクトに組み込むことです

リソースとコミュニティ

このプロジェクトはオープンソースで、コミュニティで活発にメンテナンスされてます。フィードバックや貢献大歓迎です。一緒により良いやり方を見つけていきましょう!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?