AIエージェント開発におけるトリレンマの解決アプローチ (エージェントと責務の分離)

はじめに

Google ADKを使ったPPTXスライド生成エージェントを開発している中で、トリレンマに遭遇したのでその内容と解決方法をまとめます。

開発しているエージェントには、ユーザーの指示を受けてスライドを生成する/runエンドポイントがあります。 しかし、エージェントが利用しようとした図形にスライド生成ツールが対応していない場合など、ユーザーの意図どおりにスライドを生成できないことがありました。

このとき、HTTPステータスと生成結果をどのように返すべきかを考える必要がありました。

トリレンマとは?

私がトリレンマという言葉に初めて出会ったのは、DDD(ドメイン駆動設計)をやりはじめたときでした。 DDDの文脈で語られるドメインモデル実装のトリレンマは、完全性、純粋性、性能のすべてを同時に満たすことが難しいという問題のことです。

それぞれ、次のような性質を指します。

性質 意味
完全性 業務上の判断がドメインモデルの中に漏れなく書かれていること
純粋性 外部の状態やI/Oに依存せず、入力から結果が決まること
性能 判断に必要なデータだけを取得すること

たとえば、ドメインモデルが判断の途中でRepositoryを呼び出す設計にすると、業務上の判断をドメインモデルに集約しつつ、必要なデータだけを取得できます。その一方で、外部I/Oに依存するため純粋性は失われます。 必要になりそうなデータをあらかじめすべて取得し、ドメインモデルへ渡す設計では、完全性と純粋性を保てます。しかし、実際には使わないデータまで取得する可能性があり、性能面で不利になります。 呼び出し側で必要なデータを取得しながら分岐する設計では、純粋性と性能を保てます。しかし、本来ドメインモデルにあるべき業務ルールが外側へ漏れ、完全性が失われます。

実装方法ごとのトレードオフを整理すると、次のようになります。

実装方法 完全性 純粋性 性能 特徴
ドメインモデルがRepositoryを呼び出す × 業務判断をドメインモデルに集約し、必要なデータだけ取得できる。一方で、外部I/Oに依存する
必要なデータをすべて取得してドメインモデルへ渡す × ドメインモデルを純粋に保てるが、不要なデータまで取得する可能性がある
呼び出し側でデータ取得と分岐を行う × 必要なデータだけ取得できるが、業務ルールがドメインモデルの外へ漏れる

このように、どの方法を選んでも一つの性質を諦める必要があるように見える状態が、DDDにおけるトリレンマです。

AI Agent開発で発生するトリレンマ

今回開発しているエージェントは、/runエンドポイントでPPTXファイルを生成して返します。

処理の大まかな流れは次のとおりです。

ユーザーからの指示
        ↓
エージェントがスライドを設計
        ↓
スライド生成ツールを実行
        ↓
PPTXファイルを返す

ところが、エージェントが指定した図形にスライド生成ツールが対応しておらず、生成処理に失敗することがありました。

このとき、どのようなレスポンスを返すべきかを考えたところ、三つの選択肢が出てきました。

選択肢 利用者価値 障害の識別性 生成品質 主な問題
HTTP 200とエラーメッセージを返す × × HTTP上は成功していても、ユーザーはスライドを受け取れない
スライドを生成できなければHTTP 500を返す × × 未対応図形とシステム障害が同じ500に混ざる
常に完全なスライドを生成できるようにする ○を目指せる 開発コストが高く、AIの性質上、完全な成功を保証できない

ここでいう「守りたいもの」は、次の三つです。

守りたいもの 意味
利用者価値 /runを呼び出せば、何らかのPPTXを受け取れること
障害の識別性 生成品質の問題と、技術的な障害を区別できること
生成品質 ユーザーの意図をできるだけ正確にスライドへ反映すること

200とエラーメッセージだけを返すと、利用者価値が下がります。 生成できなかったケースをすべて500にすると、障害の識別性が失われます。 常に完全な生成を目指す方法は理想的ですが、開発コストが高く、AIの不確実性も残ります。

今回の問題はDDDのトリレンマそのものではありません。しかし、複数の望ましい性質を一つの「成功か失敗か」という軸で扱った結果、どれかを諦めなければならないように見えた点がよく似ていました。

解決方法 : エージェントとスライドの状態を分ける

解決方法を考えるうえで外部フェローのkawasimaさんが開発しているsoutherのアイデアを活用しました。 southerでは業務上起こりうる結果と、DB接続失敗やタイムアウトなどの技術的な失敗を同じ戻り値に混ぜず、それぞれ異なる経路で扱っています。

たとえば、業務上「閲覧できない」という結果と、DBへの接続に失敗して「閲覧可否を判断できなかった」という状態は、同じ失敗ではありません。 前者は業務上起こりうる正当な結果ですが、後者は業務上の判断そのものが完了していない技術的な失敗です。

この考え方を参考にして、今回のエージェントでも状態を二つに分けました。

エージェントの実行状態
  → HTTPステータスで表す

スライドの生成状態
  → レスポンスJSONのstatusで表す

エージェントが例外で停止した場合は500

エージェントの実行中に未処理例外が発生し、結果を返すところまで到達できなかった場合はHTTP 500を返します。

{
  "error": {
    "code": "AGENT_EXECUTION_FAILED",
    "message": "エージェントの実行中に予期しない例外が発生しました",
    "trace_id": "..."
  }
}

500は、次のような技術的な失敗に限定します。

  • ADK上のエージェント実行が異常終了した
  • アプリケーションで未処理例外が発生した
  • PPTXファイルを保存できなかった
  • 必要な外部サービスを利用できなかった

この場合、エージェントはスライドの生成結果を判断するところまで到達していません。

それ以外は200とPPTXを返す

エージェントが最後まで実行できた場合はHTTP 200を返します。

完全に意図どおりのスライドを生成できなかった場合でも、エージェントは代替表現や簡略化を試し、必ず何らかのPPTXファイルを返します。

スライドの生成結果は、JSONのstatusで次のように表します。

completed
degraded

completedは、ユーザーの意図どおりにスライドを生成できた状態です。

{
  "status": "completed",
  "artifact": {
    "url": "..."
  },
  "warnings": []
}

未対応機能による代替や、要素の省略が発生していない完全成功を表します。

degradedは、何らかの問題が発生したものの、エージェントが別の方法を使って利用可能なスライドを生成できた状態です。 たとえば、次のようなケースです。

  • 未対応の図形を基本図形の組み合わせで再現した
  • 複雑な図を簡略化した
  • 図形として生成できない要素を画像として埋め込んだ
  • 一部の装飾を省略したが、伝えたい内容は維持できた
{
  "status": "degraded",
  "artifact": {
    "url": "..."
  },
  "warnings": [
    {
      "code": "UNSUPPORTED_SHAPE_REPLACED",
      "message": "未対応の図形を基本図形で置き換えました",
      "slide": 4
    }
  ]
}

degradedは単なる失敗ではありません。

最初に選んだ方法では生成できなかったものの、エージェントが問題を認識して別の方法へ切り替え、成果物を返せたことを意味します。

この設計の良いところ

利用するドメインに合わせて成功条件を変えられる

この設計では、PPTXが生成されたかどうかと、そのPPTXを利用側が受け入れられるかどうかを分けて考えられます。

私たちが扱っているスライドは、デザインやレイアウトが厳密に決められています。そのため、PPTXが生成されていても、degradedをそのまま採用できるとは限りません。 現在のユースケースでは、completedだけを利用可能な成果物として扱い、それ以外の状態であればAPIをもう一度呼び出して再生成できます。

status = completed
  → 生成されたPPTXを採用する

status = degraded
  → warningをもとに条件を修正して再生成する

一方で、多少デザインが変わっても内容が伝わればよいドメインでは、degradedのスライドも十分に利用できるかもしれません。 たとえば、社内でアイデアを共有するためのスライドであれば、未対応図形を基本図形へ置き換えても大きな問題にはならない可能性があります。

A2Aを利用している場合はどのように妥協してスライドを作ったのかの理由もレスポンスに含まれているため、その理由を使ってAI Agentに対して再依頼することで精度も高まるのではないかとも考えています。

おわりに

今回のスライド生成エージェントでは三つの選択肢で悩んでいました。

  • 200とエラーメッセージを返す
  • スライドを生成できなければ500を返す
  • 常に完全なスライドを返せるまでエージェントを改善する

しかし、どれか一つを選ぶのではなく、エージェントの実行状態とスライドの生成状態を分けることで解決しました。

最終的な契約は次のようになりました。

  • エージェントが予期しない例外で停止した場合は500を返す
  • エージェントが最後まで実行できた場合は200を返す
  • 200の場合は必ずPPTXファイルを返す
  • 意図どおりに生成できた場合はcompleted
  • 代替表現を使って利用可能なスライドを生成した場合はdegraded

AIを使う以上、すべての入力に対して完全な結果を保証することは難しいです。 しかし、どのような結果へ収束したのかを明示し、技術的な障害と生成品質の問題を分けることはできます。

スライドを完全に生成できなかった時点で処理を終えるのではなく、その失敗を次の推論の入力にして、別の方法で成果物へ収束させる。 この仕組みが、通常のツール呼び出しではなくAI Agentとして実装する価値の一つだと考えられます。

Page top