🧭

AIエージェントはどうやって作るのか?最初のステップの構造化出力を理解しよう

2025/01/05に公開

AIエージェントがトレンドになりつつありますが、どこから勉強したらよいかわからないという方はいませんか?

開発ツールとしてはLangChainなどが有名です。LangChainを使うことで、LLMをアプリケーションに統合する際によく使う処理を自作しなくても良くなります。一方で、最初からツールを使っても内部構造がよくわからない状態で終わってしまうかもしれないです。(自分はそうなりました)

そんな人に向けて、一番最初のステップである 基本概念「構造化出力」 をお伝えしたいと思い、記事を書きました。この記事では、AIエージェントの概念が生まれる流れと、そこに構造化出力が必要となる理由を説明しています。後半では、構造化出力を応用した技術について記載しています。

AIエージェントとは

生成AIツールとして一番有名なChatGPTの初期段階は、ユーザーの質問をそのままLLMに入力して、文章が出力されるだけのシンプルなものだったと思います。リリースした時点では従来のAIとは大きく異なる汎用性を持っているため衝撃を与えました。

一方で、一般ユーザーにとって、ChatGPTが何にとっても理想的な体験とは言い難いです。そこで出てきた概念がAIエージェントだと考えています。

ユーザーが求める体験は、ある作業についてツールを使うことで作業時間を節約したり、学習を効率的に行えたりすることです。ChatGPTではどんな分野においても汎用的に出力を得ることは理論上可能ですが、例えば、プロンプトのカスタマイズが必要ない方が理想的ですし、UI/UX面ではアプリを行き来をせずに一つのツール内で全ての作業が完結できた方が便利です。

そのため、生成AIを前提としてユーザーストーリーを再考することで、これまで実現できなかったユーザー体験を提供することができます。

(※この記事でのAIエージェントは、既存の技術を利用した短期的な実現の話で、最終形態である汎用人工知能レベルの定義は話に含めてないです。)

構造化出力の利点

構造化出力とは、LLMに自然言語のクエリと(JSON)スキーマ構造を与えると、クエリ内容を元にスキーマ構造に従った内容の(JSON)データが出力されるものです。以下に天気予報に関する構造化の例を示します。

入力例
{
 "query": "今日は、一日を通して晴れの予報です。最高気温は27℃、最低気温は18℃となるでしょう。風は穏やかで、過ごしやすい一日となりそうです。",
 "schema": {
   "weather": {"type": "string"},
   "max_temperature": {"type": "integer"},
   "min_temperature": {"type": "integer"}
 }
}
出力例
{
  "weather": "晴れ",
  "max_temperature": 27,
  "min_temperature": 18
}

OpenAI 構造化出力のガイド

テキスト → テキストの入出力は、ChatGPTクローンを作るだけなら十分ですが、アプリケーションや既存のフローへの組み込むために毎回オブジェクトに変換するのは、実装が大変です。

構造化出力によって、LLMをプログラミングコードの中のオブジェクトとして扱えるようになり、ロジックの最後だけでなく、任意のロジックの間に一つの関数として組み込みやすくなります。


構造化出力有無でのフローの違いのイメージ画像

構造化出力がなくても、出力テキストをJSONとして出力させようとすることはできますが、JSONをパースする面倒さ・出力精度自体の不安定さがあり、本番環境で運用するアプリケーションへの適用は不確実性が高かったです

例えば、テキストをいくつかのラベルに割り振りたい時には、キーワードを数値化して機械学習でモデルを作ったりする必要がありましたが、LLMの構造化出力を用いれば少しの時間で一定精度のラベル分けが実現可能です。
例: Vercel AI SDKを使えば、enum配列を定義するだけでラベルわけができます

2024年になってから主要なLLMであるGPT, Gemini, ClaudeのAPIへスキーマ忠実性の高い構造化出力がサポートがされるようになったことで、AIエージェントの実用性に現実味が帯びてきました。

個人の感想ですが、今年の後半でAIエージェントがビジネステーマとして話題になってきたのはこれらと時系列的に関係しているのではないかと思っています。

構造化出力からの発展

次に、構造化出力を発展させることで、AIエージェントのユーザー体験をさらに高めるための技術を紹介します。

並列関数呼び出し

まず、関数呼び出しとは、関数の入力として扱うスキーマ構造と関数を呼び出す判断基準を定義しておき、毎回呼び出すかどうかLLMに判断させるものです。

例として、AIチャットボットにWeb検索を組み込むケースが挙げられます。関数としてWeb検索を定義しておき、通常はAIが直接返答しますが、検索が必要だと判断した場合にのみWeb検索を行うようにできます。

関数呼び出しは、厳密には構造化出力と異なりますが、理解としてはスキーマ構造に従った出力をさせるという点で同じものとして捉えることができます

従来のアプリケーションとは異なり、LLMの出力は多くが数秒〜数十秒かかるため、一番のボトルネックはユーザー視点での体感速度の遅さです。

すべての関数呼び出しを順次的に処理していたらその数だけ待ち時間が発生してしまい、特定のフローを完了するのにどんなに早くても数十秒かかるのでは、ツールとして成り立ちません。並列関数呼び出しを用いることで、依存関係のない複数の処理を並列で呼び出すことができ、LLM出力速度の遅さを軽減できます。


並列関数呼び出しの有無による違いのイメージ図

外部ツールとしてのRAG

RAG(Retrieval-Augmented Generation) は、全部をプロンプトに入れるには大きすぎる本などのコンテンツを事前処理し、取り出しやすくDBに保存しておくことで、できるだけ必要十分な情報だけをプロンプトにコンテキストとして入れ込めるようにするための技術です。

Gemini以外のLLMは多くても数万語程度しか一度に入力することができませんし、目的に必要のない情報を毎回LLMに処理させてしまうとLLMの使用コストが高くなってしまいます。

これはAIエージェントに必須ではありませんが、AIエージェントの文脈でも同時に話に上がるケースを見かけますね。

RAGを抽象的に捉えると、上記のフロー図における外部ツールの一種とみなすことができます。他の外部ツールの例としては、Google検索APIなどが含まれます。

RAGなどの外部ツールはパラメータで呼び出せるので、アプリケーションの中の関数として捉えることができます。

したがって、構造化出力を使うことで任意の外部ツールを簡単にLLMと連携できるようになりました。

次のステップへのおすすめの記事

https://zenn.dev/loglass/articles/b9ee37737deb85

複数のエージェントの実装パターンの説明動画の内容がまとめられています。
すべてを使うわけではなく、ユースケースごとにどれが必要かは異なりますが、より具体的な実装イメージがつきやすいと思いました。

https://zenn.dev/pharmax/articles/ae19bafbcfeb23

こちらの記事は、実践からの教訓を抽出して共有されているため、デザインパターンとは別にエージェントの設計を考える上で参考になると思いました。

Discussion