🍵

エージェントは魔法じゃない:LLM時代の“不確実性”を武器に変える設計術

に公開
  • CursorやClineなどのコーディングエージェントは使っている
  • MCPサーバーも導入したことがある
  • OpenAIなどのLLMをAPI呼び出しを通して使ったこともある

といった状況の方が、「でも自分でエージェントを実装するってなると、いまいちピンと来ないんだよな・・・」と思っているときに役に立つ記事になればと思って筆を執りました。

ちょうど1ヶ月ほど前の私が同じ状況で、鳴り物入りでMastraのQuickstartをやってみたものの、自分の頭でエージェントの要件を考えて実装してみると、なんか違うな・・・?と思えてくる。そういう状況でした。

執筆時点でも私個人としてはエージェントの本番投入まではできていないものの、実務および個人開発それぞれでPoCの実装は進めており、周囲(エンジニア・非エンジニア問わず)に対して「エージェントはこれまでのLLM呼び出しとは何が異なるのか」「どのあたりがパワフルなのか」ある程度説明できるようになってきたところです。

本記事では、以下のような論点を軸に、エージェント時代のユーザー体験や要件定義などについて考察していきます。

  • エージェントはどんな要求を満たしてくれるのか?
    • エージェント前・以後で変わった「不確実性の扱い」
    • どんな要件がエージェントに向いているか?
  • エージェントとLLMの関わり
    • エージェントは意外とシンプルな仕組みでできている、という話
    • エージェント開発したいエンジニアのための学習ステップ
  • エージェントの流行に対して、「ある程度冷静で」いられるための考え方

前提:エージェントとは何か?

本記事ではあくまでMastraまたはそれに相当する任意のAgent FrameworkのDocsを一通り読んでいる方、をターゲットにしているため、堅苦しい基礎知識については触れず、抽象的な意見を述べていきます。

エージェントとは何か?を一言で表現するのは困難ですが、私は以下のように説明することが多いです。

「システムがLLMを呼び出すのではなく、LLMがシステムを呼び出す実装パターンである」

この説明だとFunction Callingと区別がつかないのですが、まず入口としてはこの説明で最低限だと考えています。
呼び出す主体が入れ替わることで、これまで実現が困難だったり面倒だった「不確実性が必要な要件」をある程度高い精度で実現可能になったのです。

また、本記事の後半で詳しく触れますがあくまでエージェントは完全なブラックボックスではなく実装パターンの一種と捉えるほうがシンプルです。LLMにToolのリストとInstructionを与えるだけで、適切な順番・内容・組み合わせでToolの呼び出しを決定してくれるというだけの話です。しかしながら、この「適切な順番・内容・組み合わせ」をLLMが決めるというのがミソで、これによって実現できる要件の不確実性が広がっていると私は考えています。

📚参考

エージェントはどんな要求を満たしてくれるのか?

エージェント前・以後で変わった「不確実性の扱い」

私が前節から主張している「不確実性」とは何のことか、説明していきます。

まず、そもそもWebシステム全体が、不確実性を徐々に確実性の高いものに変換していく装置であると考えます。

MastraのDocsでも述べられている定番のExampleである天気情報エージェントをベースに考えてみましょう。

このエージェントは「東京の天気は?」などと自然言語でチャットを投げると、東京の今の天気情報について同じく自然言語で返してくれるエージェントです。

内部的な処理の順番としては以下のようになります。

  1. ユーザーが「東京の天気は?」などと要望を入力
  2. エージェントが要望を解釈・呼び出すツールと引数を決定
  3. ツールの実行(天気取得APIの呼び出し)
  4. 実行結果を受け取ったエージェントが自然言語で出力

仮にこの「ユーザーが知りたい地域を入力すると、その地域の天気を知れるシステム」をエージェントを使わずに実装することを考えてみます。普通に考えると以下のようになるでしょう。

  1. ユーザーが、セレクトボックス等のあらかじめ決められた選択肢から地域を入力
  2. システム上で、選択肢を都市IDに変換(マスタなどから引く)
  3. 天気取得APIの呼び出し
  4. 実行結果をJSONからUIに組み上げて表示(表形式などがベターか)

この2つの流れにおいて、何が根本的に異なるかというと、私は「不確実性の扱い」だと考えています。

たとえば本例では「現在天気を知りたい地域がある」というユーザーの要望がありますが、この要望はとても不確実性が高いです。というのも、

  • 地域はどの粒度が知りたいのか(市区町村単位、都道府県単位、地方単位など)
  • どういった情報が知りたいのか(晴れ曇り雨だけなのか、温度湿度などもなのか、それ以上か)

などが決まっておらず、確実にシステムが遂行できる要望に落とし込むことがこのままでは困難です。

そこで旧来のシステムでは大きく分けて2つのアプローチで、この不確実性と向き合ってきました。

  1. ユーザーの入力をあらかじめ決められた選択肢に制限することによって、不確実性を排除する。
  2. 最終的に出力するUIをある程度広めの要望を含む内容にして、カバー率を上げる

ユーザーは「千代田区の天気知りたいな」と思っていても、UIが「都道府県」を選ばせることで、暗黙のうちに「じゃあ東京都を選べば良いんだな」と解釈して選択しています。つまり、UIを通してユーザーの不確実性高い要望を、システムにとって都合のいい、より確実性の高いものに落とし込んでいたわけです。

言い換えると、世の中の要望の大半は「システムで制限していても、まぁ仕方ないなと思うような要望」が占めていたともいえます。天気の例だと、どうしても千代田区の天気が知りたくて東京の天気だと不服なケースや、または「東京」じゃなくてどうしても「Tokyo」と入力したいケースなどはほとんどありませんよね。なので、都道府県から選択する仕様でも成立していたのです。

しかし、エージェントを用いることで、ユーザーの自然言語による多様な要望を直接受け入れ、LLMを介してシステムがその不確実性を解釈して適切な処理を行うことが可能になります。

エージェントに対して「千代田区の天気教えて」と入力したとき、エージェントは(ちゃんとInstructionすれば)「千代田区は東京都ですね。東京の天気であれば◯◯です。より細かい地域の情報が必要であれば他のサービスを検討ください」と返したり、またはToolが市区町村に対応していれば、市区町村レベルでの天気をしっかり求めて返してくれます。加えて、「Tokyoの天気は?」とか「What is the weather like in Tokyo?」でも答えてくれるはずです。
(余談ですが、i18nが面倒だから日英だけの対応にしておこう、といった発想も、システム都合で確実性を高める考え方ですね。LLMの登場で各国言語の垣根が低くなり、言語をまたぐ不確実性の吸収もより任せられそうです)

エージェント時代では、UXにおいてより多様な不確実性をシステムが受け入れることが可能になったことが分かっていただけたかと思います。そのために前段にLLMを配置する必要があり、前段にLLMを配置しつつシステムを動作させるための仕組みがエージェントパターンなのです。

エージェント時代のUI

前節の補足ですが、私は必ずしも「これからはUIが不要になる」と言いたいわけではありません。

「システム都合でより確実性の高いInputが欲しいがゆえに、不要に複雑だったり逆に情報の選択肢を狭めるUIを提供する必要がなくなってくる」と思っています。
逆に、AIへの不信感を下げたり、出力された情報をより魅力的に・明快に見せたりといったUIが求められてくるでしょう。

LLMによるStructured Outputが今後進化してくると、テキストではなくUI単位でAIとやり取りする世界線も近いかもしれません。そうなると今まで以上にリアルタイムでスムーズなUI体験を構築する必要が出てくるので、そういった意味でUIへの投資は引き続き必要なのではと考えています。

たとえば、私が個人開発しているテストメーカーは、穴埋めテストが作成編集できるWYSYWYGエディタが売りですが、LLMがテストの生成ができるようになっても、その生成したデータを表示するためのエディタというUIへの投資は引き続き重要です。

どんな要件がエージェントに向いているか?

これまでの内容を踏まえて、私が現状思っている、エージェントに向いている要件をリストアップします。

  • 複数のデータソースから適切な情報を引っ張ってくるレコメンドシステム。ただし、どのデータソースから取ってくるべきかが、ユーザーのプロンプトを見るまで確定しない
    • 要件でデータソースやそこからの取得ロジックを確定させてしまうとレコメンドの幅が狭まる。かといって全部のデータをプロンプトに食わせるとContext Windowや精度面で問題が予想される場合
  • 最終的にデータを生成するが、そのデータに多数の入力項目があり、その項目を適切に目的に応じて設定してくれる重要度が高く、かつ何を設定するべきかが難しい場合
    • たとえば私が個人開発しているテストメーカーでは、各テストに10個ほどのオプションが設定できますが、これを使いこなしてくれるかが1つのUX上の課題でした。ユーザーがテストの用途や配布先をどうしたいかAgentがヒアリングしたり、テストの生成元となる文献をPDF等で受け取ると適切に中身をパースし、最終的に適切なオプションをつけたテストを作ってくれる
  • データの生成後に分岐がある場合
    • テストメーカーにおいて、ユーザーが私が文献が一定以上のサイズだった場合はフォルダに複数のテストをまとめてほしい、といった要望があっても、AgentへのInstructionで「多かったらフォルダに入れてね」と言いつつフォルダの作成・格納ができるToolを提供すれば良い
  • 無理なときは無理と言ってほしい場合
    • プロンプトを見てから操作を始めるので、素っ頓狂な指示に対しては無理と回答することができる。事前にプロンプトを完全確定してから流す従来のアプローチでは、ユーザーの指示が素っ頓狂でも、プロンプトがリッチだと無理やり遂行しようとすることがあった

これまでであれば「それを要件にしちゃうと分岐のルール決めたり実装が面倒だな」と思ってほぼ無意識に選択肢から捨てていた多くの機能案が、エージェントの実装パターンによって実現可能になっていきそうだなと思っています。

一方で、機能は必要だから作るのであって作れるから作るわけではないので、エージェントを作れるようになることと目の前のプロダクトに埋め込みまくっていいかというと別なのが難しいところですね。ただエージェントのようなパラダイムシフトは”必要”という自己認知を超えてくる可能性がある(必要なのに必要と思っていない隙間を埋めてくる可能性がある)ので、キャッチアップ自体は続けたいところです。

エージェント開発に対する所感

エージェントは意外とシンプルな仕組み

エージェントを作ろうと思ってMastraなどのAgent Frameworkから入ると、ついついエージェントが魔法のように見えてしまうかもしれませんが、Web Frameworkなどのご多分に漏れず、エージェントも実際はシンプルな仕組みであると思ったほうが、ToolやInstructionの設計に活きてくるのでおすすめです。

以下のURLを開いてみてください。

https://chatgpt.com/share/6801f5f9-7654-800b-8944-f2505cadf79b

これは普通にWeb版のChatGPTに、以下のプロンプトを投げてみたときのレスポンスです。

中学3年生、理科の先生を探しています。

---

以上がユーザーの要望です。あなたに以下のツールを提供します。
最初に呼ぶべきツール名と、引数をJSONで教えて下さい。JSONで教えるだけでOKです。

---

Tools
・getTeachers 引数:subject_id: number 返り値:{id: number, name: string, avatar: string}[]
・getSubjectMasterTool 引数なし 返り値:{id: number, name: string}

面白いことに、これに対するChatGPTの返信は以下のようなJSONとなっています。

{
  "tool": "getSubjectMasterTool",
  "args": {}
}

ちょっと意地悪に、ToolsはgetTeachersから書いているにもかかわらず、「先生を探すためには科目をIDに置換する必要があるんだ」と上記のプロンプトだけで解釈し、まずは科目マスタを取得しようと動いていることがわかります(細かいことを言うと、本当は科目名を渡してIDを返すToolのほうがいいのですが、科目名の表記揺れリスクを低減するために、マスタ取得で実装する個人的Tipsがあるのですが、その意図も込で組んでくれていることがわかりますね)。

(原始的な)エージェントとは、実態は「あらかじめ規定されたToolの仕様群から呼び出すものを選んで、所定の形式のJSONで出力してあげるように厳密に指示されたプロンプト」でしかありません。

Mastraや、リークされているCursorなどのシステムプロンプトを読んでみてもいいと思いますが、Agentの実態は上記の例における「以上がユーザーの要望です。」の部分をFramework側で固定で決めておいたり、Toolsを実コード上で受け取ったら、DescriptionやSchemaも静的に算出できますから、それをプロンプトの所定の位置に所定の形式で埋め込んでおけば、今どきのLLMなら適切なJSONを吐いてくれるよね、というものでしかありません。
Function Callingを知っている人は、「なるほどFunction Callingの拡張可能バージョンがAgentか」みたいに理解しておいてもいいかもしれないです。

これ以上の説明は蛇足なので後はこの考えをもってAgent Frameworkの実装を読んでみるなどすると良いと思いますが、この辺を理解していると、MCPといったプロトコルもどうして嬉しいか?が自明になってくると思います。
また、ToolsにおいてSchemaやDescriptionをちゃんと丁寧に目的ベースで説明してあげないとAgentが意図したタイミングで読んでくれない、といった実際の悩みもこの概念で説明可能です。

エージェント用Toolの開発で気をつけること

不確実性を軸に考えたとき、エージェント用のToolは、ある意味人間に残された最後の不確実性調整の領域になります。
そのため、開発者はツールの設計において、どのように不確実性を吸収し、エージェントが適切に機能するかを考慮する必要があります。

たとえばデータを検索してレコメンドするAgentを実装しているとき、レコメンドの対象となるデータを取得するToolが、どの程度絞り込んだデータをどういった形式で返すかが問われます。
既存のアプリケーション開発のパラダイムにいると、ついついTool内でできるだけデータを削ろうとしてしまいがちですが、実際は、Toolが多様な検索パラメータ(RAG用の自然言語クエリなどはその代表例)を受け取ったり、多めの件数を返したり、レスポンスのスキーマに沢山の種類のデータを埋め込むなどして、Agent(LLM)が意思決定できる余白を多く設けるのがコツなようです。

Agent用のTool開発はさながら日頃のWeb開発におけるバックエンドAPI開発に近いように見えて、その実、UIという制約がないので無限のUIの可能性に対してPropsとReturnを決められるAPIを作るようなイメージに近くなります。

他方、私が素振りしている現状では、目的ベースでToolを使い分けられるように、あくまで目的という単位でToolを分けて、その中では自由度の高い用途に適合できるようにしておくようなイメージでバランスを取っています。

また、プチTipsとして、「DescriptionやSchemaにユーザーが使う単語を用いる」というものがあります。ToolsをAPIの一種だと思っているとUI側の単語を用いなくて良いように見えますが、実際はAgentが使うUIという側面があるためユーザー単語のほうが望ましいようです。

エージェントの流行に対して、「ある程度冷静で」いられるための考え方

最後に、もう方方で論じられているかもしれませんが、エージェント(LLM)の流行に対して冷静でいるために自分が考えていることを共有します。

それは、技術の進化を変わりゆくものと変わらなくなりつつあるものに分類して観察することです。
たとえばエージェントについては以下のように考えています。

変わりゆくもの

  • エージェントフレームワークの種類(Mastraすら半年後にはDeprecatedかもしれない)
  • エージェントフレームワークという概念(たとえばNext.jsがAgent Framework相当の機能を持ち始めたら絶対そっち使うと思う)
  • モデル(大規模言語モデルがAgentのような自律的システム操作にある程度合致しているのはある意味まぐれ。Agent操作に特化したモデルが開発されていってもおかしくない。法人からマネタイズしやすいためモデル提供企業側の経済合理性にも合致する)
  • ZodがToolのSchemaに使われていること(ZodのSchema表現力が、Toolの説明として適切な粒度になっているのか。最低限満たしているとは思うがAgent向けSchema Libraryが今後出てきてもおかしくない)
  • MCP(MCP、npxで実行できるおかげでサービス組み込みを待たずに個人で試せるからスケールしているプロトコルなのかなと思っている。なので引いて見たら、まだまだ実行環境も含めて過渡期だなという印象)
  • エージェントを使うUI(次のステップとして、自然言語の入力すらだるい、というフェーズが来そう。そうなるとエージェント呼び出しUIすら自動生成みたいな世界観が来ないだろうか?)
  • コンテキストウィンドウ及びそれに関連する無意識的な開発者側の常識(RAG相当の機能を内包し始めたらまたちょっと前提が変わりそう)
  • すでに出つつあるけど自前で管理するLLM系API用の専用プロキシサーバーみたいなのがそのうち主流になってAPPから直接API Callするのは非推奨になりそう

変わらなくなりつつあるもの

  • AIがシステムを呼び出せるようになったという事実(Structured Outputの安定性、速度もろもろ不可逆的な性能の進化)
  • AIへの抵抗感(まだまだ著作権回りについては課題が多いが、一方でそういった制約がない領域においてAIが関与することへのアレルギーはかなり減ってきている)
  • AIとシステムを連携させるうえでの感覚値のようなもの(◯◯みたいなMCPを作ると、▢▢みたいな課題が解けるのでは!といったLLMと現実世界を紐づける感覚)
  • Toolsそのものを開発するための基底となる技術力(むしろより重要度が高まってそう。これまでよりPrimitiveな技術力の重要性が増している感覚がある。絶対にテキストで代替できない複雑なUIとか、データストアに対する検索ロジックの引き出しとか)
  • セキュリティ、サンドボックス環境の重要性(Daytona - Secure Infrastructure for Running AI-Generated Code とか)

という感じで、個人的な感覚としては、「変わらなくなりつつあるもの」を見出し、磨き続けるためにある程度は「変わりゆくもの」も追わないといけないな、みたいな距離感でやってます。

まとめ

LLMの進歩が速すぎるのでいつ記事を書くか迷ったのですが、最近エージェントを触り始めて、新しいユーザー体験が作れそうな未来が見えてきそう!と思ったので書いてみました。参考になりましたら幸いです!

Discussion