話題のAIエージェントを作ってみた

1. はじめに


NTT西日本の相浦です。

NTT西日本公式の技術ブログが始まりました。記念すべき第一回ということで、最近の私の「やってみた」を共有したいと思います。

最近よく耳にするAIエージェント。でも、「結局どういう場面で役立つの?」と思う方も多いのではないでしょうか。 本記事では、自治体業務の一つである粗大ごみ収集申込の受付を例にして、対話型で申込受付をしてくれるAIエージェントのサンプルを作ってみたのでご紹介します。

作ってみて気づいた工夫ポイントや「ここは意外と難しい」というところも交えつつ、「自分の業務に当てはめられるかも」と思っていただける内容を目指します。

2. 対象読者


本記事が想定する対象読者は以下の通りです。

  • AIエージェントに興味があり、具体的な実装事例を知りたい方
  • 「自分の業務もAIエージェント化できるのでは?」と感じてみたい方

3. 背景


AIエージェントとは何か

「AIエージェント」という言葉を耳にする機会が増えました。簡単に言えば、LLM(大規模言語モデル)がツールを選び、状況に応じて自律的に動く仕組みのことです。

LangChainの創設者Harrison Chase氏は次のように説明しています。

AIエージェントとは、LLMがアプリケーションの制御方法を決定するシステムのこと。重要なのは「どの程度エージェンティックか」という観点でとらえること。(参考: LangChain Blog

つまり「LLMが会話を進めながら、必要に応じてツールを呼ぶ」仕組みだとイメージしていただければ十分です。

例えばLangChain公式サンプルには、ユーザが「天気は?」と聞くとLLMが「天気APIを呼ぶ」と判断して答えるチャットボットがあります。(参考: LangChain Agents Tutorial

今回の題材

こうしたデモは面白いですが、「実際の業務でどう役立つのか?」はまだ見えにくいです。そこで、今回は自治体業務の一つ、粗大ごみ収集の申込受付を題材に、対話で必要情報を集めて予約まで進めるエージェントを試作しています。

4. 「粗大ごみ収集の申込受付エージェント」を設計する


今回の題材は、自治体業務の一つである粗大ごみ収集の申込受付です。エージェント化するにあたり、必要な要素を整理しました。

申込受付では何をする必要があるのか?

粗大ごみ収集の申込受付では、単に「予約を受け付ける」だけではなく、いくつかの確認ややり取りが必要になります。具体的には次のような内容です。

  • 申込者情報の確認:氏名・住所・電話番号といった、誰が申し込んでいるのかを特定する情報を集める。
  • 収集希望内容の確認:どの品目を、何個、いつ、どこで回収してほしいのかを聴き取る。
  • FAQ対応:「この品目は回収できる?」「年末年始は対応している?」など、利用者からの質問に答える。
  • 料金見積:品目と数量に基づいて料金を計算し、利用者に提示する。

つまり、必要な情報を漏れなく収集し、寄り道的な質問にも答えながら、最終的に予約を確定することが申込受付の役割です。

設計のポイント

AIエージェントとは「LLMが会話を進めながら、必要に応じてツールを呼ぶ」ものだとすると、LLMに与える役割、呼ぶことのできるツール群は何か、を考える必要があります。

LLMに与える役割
エージェントの目的は、申込に必要な情報を揃えること。そのため、LLMには不足情報を把握し、次に聞く質問を考える役割を与えます。

ツール群
LLMに使ってほしい機能を関数として与えます。今回はresolve_date(日付正規化)、check_collectible(収集可能日か判定)、estimate_fee(料金見積)、rag_search(FAQ対応)、reserve(予約確定)を与えます。

どこまでLLMに任せるか

AIエージェントでは、LLMが様々な判断を行うことになります。一方、LLMの判断が必ずしも設計者の意図通りになることを保証することはできません。例えば、「申込者には必要なすべての情報を入力してほしい」と考えていても、LLMは「これだけの情報があれば十分だろう」と勝手に判断することもあります。

そのため今回の実装では、

  • LLM: 柔軟な誘導(質問生成、ツール選択)
  • プログラム: 受付確定や必須情報の充足判定

という役割分担にしました。すべてLLMに任せることもできますが、LLM+ルールのハイブリッド型にすることで「自然な会話」と「確実性」を両立します。

なお、プログラムが必須情報の充足判定を行うには、いまどの情報まで収集したかという情報を構造化して保持する仕組み必要です。そのため、PydanticモデルGarbageRequestを用意し、プログラムが扱いやすいJSON形式で情報を管理します。

5. 実装


実装のポイントです。詳細コードはGitHubに掲載するので、ここでは処理の流れとLLMに与えた指示を紹介します。

プログラムの流れ

LLMへの指示

LLMには2つの役割を与えます。

不足情報を把握

収集すべき情報は申込情報スキーマGarbageRequestの中で、氏名・住所・電話番号...と指定されています。LLMにはユーザの発話からこれらの情報を抽出する役割を与えます。

# 会話から申込情報を抽出するためのプロンプト

# システムプロンプト
あなたは自治体窓口のAIです。ユーザ発話から粗大ごみ申込情報を抽出し、
必ず JSON のみで出力します。説明文やコードブロックは禁止。
{GarbageRequestで指定されたフォーマット}
不明は null のままでよい。電話は数字のみ。time_slot は『午前/午後』。
相対日付は日本時間の本日 {本日の日付をプログラムで取得して埋め込む} 基準で YYYY-MM-DD に正規化する。

# ユーザプロンプト
{ユーザの発話を入れる}

次に聞く質問を考える

LLMにはもう一つ、次に聞く質問を作る役割も与えます。出力には必ず、

  • 現在のステータス: [ASK](不足情報を聞く) / [REVIEW](申込内容をまとめて確認) / [ANSWER](FAQへの回答)
  • 次に聞く質問
  • 現在までに収集した申込情報一覧(JSON形式)

を含めるように指示します。こうすることで、後でLLMの出力を使ってプログラムに判断を仰ぐことができます。

# 次に聞く質問を作るためのプロンプト

# システムプロンプト
あなたは自治体の粗大ごみ申込アシスタントです。目的は申込に必要な情報を揃えること。
【対話方針】
- ユーザが『可否の質問(例:◯/◯は回収できますか?)』をした場合は、まず可能な範囲で **即答** する。
  ・日曜/1月1日/12月31日などの一律NGは、住所が未取得でも『不可』と即答してよい。
  ・それ以外は、住所と希望日が揃ったら `check_collectible` で判定する。住所が無い場合は、可否に必要な **最小限の質問(住所)** のみを先に聞く。
- 情報に不足があれば、優先度に従い“1項目だけ”丁寧に質問する。ただし、状況に応じて順番を入れ替えてもよい。(優先度: name > address > phone > item_description > quantity > preferred_date > time_slot > pickup_location)。
- ユーザが相対/曖昧な日付(例:来週水曜、明後日 等)を述べたときは resolve_date で YYYY-MM-DD に正規化し、  そのターンは必ず [ASK] で『この日付(YYYY-MM-DD)でよろしいですか?』と確認してから次へ進む。
- preferred_date と address が揃ったら check_collectible を使う。NGなら代替案を簡潔に提案して [ASK]。
- item_description と quantity が揃ったら estimate_fee で概算料金を出し、[REVIEW] に金額を含める。
- 制度/ルール等のFAQは rag_search を使って短く回答し、その後は不足収集に戻る(通常は[ASK])。
【出力形式(厳守)】
1) 先頭行に [ASK] / [REVIEW] / [ANSWER] のいずれか
2) ユーザに見せる本文(丁寧・簡潔)
3) request の最新状態を JSON で同梱:
[REQUEST_JSON]
{これまでに収集した申込情報一覧}
[/REQUEST_JSON]
【利用可能ツール】{ツール一覧とそれぞれの使い方}
【状況コンテキスト】{直近の会話模様}
【これまでのツール実行ログ】{agent_scratchpad}

# ユーザプロンプト
{ユーザの入力}

6. デモ


ここでは、実際にエージェントを動かし、次のようなシナリオで想定通りの挙動になるかを確認します。

①曖昧な日付の正規化:「来週の水曜日に、ソファを一脚回収に来てもらえますか?」という依頼に対し、エージェントが具体的な日付を返し、確認を促すか。
②不足情報の収集:エージェントが順に質問を行い、申込に必要な情報の収集が進むか。
③予約確認と確定:申込に必要な情報が集まった時、エージェントがユーザに対して確認を促すか。ユーザの確認がOKだった場合に受付を完了するか。
④寄り道質問:ユーザが途中で「年末年始は回収可能か?」「料金はいくらになるか?」と質問したとき、適切なツールを使って質問に回答できるか。


では、実際のエージェントの動作をシナリオに沿って追ってみます。左側のパネルには常に現在の申込情報が表示され、進行状況が一目でわかります。

⓪初期画面

新規スレッド作成直後は空欄。ここからユーザの入力に応じて値が埋まっていきます。

①曖昧な日付の正規化

「来週の水曜日に、ソファを一脚回収に来てもらえますか?」と依頼すると、item_description: ソファ, quantity: 1が抽出され、日付もresolve_date2025-09-03に変換されます。

曖昧な日付を正規化し、確認の質問を返す。

②不足情報の収集

不足情報は優先度順に一つずつ質問され、回答が左側のパネルに反映されます。

氏名→住所→電話番号...と順番に埋めていく。

③予約確認と確定

全項目が揃うと[REVIEW]タグで申込内容をまとめ、ユーザが了承すればreserveが実行されます。

最終確認後に予約番号と回収日を提示。

④寄り道質問

「年末年始は回収できる?」「料金は?」といった質問にも即応可能です。check_collectibleestimate_feeを呼び出し、回答後は不足情報収集に戻ります。

デモまとめ

  • 初期入力から曖昧日付の正規化までを自動で処理
  • 不足情報は一問一答で収集
  • すべて揃えば最終確認の後、予約を確定
  • FAQ的な寄り道質問にも対応

自然な会話の流れで申し込み完了まで進めることを確認できました。

7. オープンソースモデルでの追加実験


GPT-4oで一通り動作を確認したので、「オープンソースモデルでも再現できるか?」を試しました。対象は以下の3モデルです。

  • Bedrock (AWS)
    • meta/llama-3-1-8b-instruct
  • 独自デプロイ (EC2 - Lambda - API Gateway経由の利用)
    • google/gemma-3-12b-it
    • RedHatAI/gemma-3-27b-it-quantized.w8a8

プロンプトとツール設計は共通とし、LLM呼び出し部分と出力JSON制約のみ差し替えています。

前章のシナリオ(①曖昧な日付の正規化、②不足情報の収集、③予約確認と確定、④寄り道質問)をもとに検証しましたが、結果としてGPT-4oのように最後まで通せたモデルはなく、いずれも途中で課題が明らかになりました。

meta/llama-3-1-8b-instruct

①曖昧な日付の正規化

「来週の水曜日に、ソファを一脚回収に来てもらえますか?」と依頼すると、入力文をそのままオウム返ししてきます。LLMがプロンプトの指示を正しく理解せず、またresolve_dateツールも使っていないようです。

依頼文をそのまま返すのみ。

AWS公式には「tool use対応」とありますが、redditでは「ツール呼び出しが不安定」との報告があり、安定性に課題があると確認できました。

冒頭でつまづいたので、ここで中断です。

google/gemma-3-12b-it

①曖昧な日付の正規化

「来週の水曜日に、ソファを一脚回収に来てもらえますか?」の依頼に対し、適切にツールを使い、具体的な日付で確認を促します。

②不足情報の収集

その後も順に質問を行い、情報収集を行っています。

ただし「電電太郎」という名前は「デンデンタロウ」と読むつもりで入力しましたが、「デントウ太郎」と解釈されたみたいです。カタカナでの入力促すなどの工夫が必要です。

会話を続けます。一度は名前の訂正を試みますが、左側パネルは修正されません。

その後、電話番号を入力した後から挙動が怪しくなります。一度確認したはずの回収日を、再確認されます。さらに「はい、あっています。」と答えても、同じ質問を繰り返すようになりました。左側パネルのpreferred_dateには日付が入っているため、プログラムは確認済と認識していますが、LLMは未確認と誤認しています。ここでシナリオは中断となります。

なお別の会話では、名前の訂正をきっかけに申込情報全体をリセットする事象も見られました。リセットに留まらず、申込内容を「ソファ」から「テレビ」に書き換えるなど、情報を改ざんしています。今回の実験から、情報保持の安定性に弱点があることがわかりました。

RedHatAI/gemma-3-27b-it-quantized.w8a8

①曖昧な日付の正規化

「来週の水曜日に、ソファを一脚回収に来てもらえますか?」の依頼に対し、適切にツールを使い、具体的な日付で確認を促します。ご丁寧にツールを使うことを宣言しています。

②不足情報の収集

続いて名前を入力しますが、何度入力しても同じ質問を繰り返されます。「電電太郎」を名前と認識していないようです。これ以上進まないので中断します。

今度は違う名前でシナリオを試します。「山田太郎」は名前と認識するようです。その後も不足情報の収集が続きます。

③予約確認と確定

予約確認で問題が起きます。まず回収場所の確認をされますが、「マンションのごみ捨て場に置きます」という入力は無視されます。諦めて「はい」と答えると、申込内容の再確認をせずに受付を完了してしまいました。

結果と課題

追加実験を通じ、モデル毎の得手不得手がわかります。

  • GPT-4o: 幅広く安定
  • llama-3-1-8b: ツール呼び出し不安定
  • gemma-3-12b: 情報保持不安定
  • gemma-3-27b: 日本語固有名詞に弱い、指示をスキップすることがある

特にgemma系列は「固有名詞処理の弱さ」が共通の課題でした。

クラウドモデルは安定している一方、コスト・セキュリティの制約があります。そのためにオープンソースモデルを使いたいというニーズを聞くことが多いです。

ただし、今回の実験ではオープンソースモデルをそのまま適用するのは難しく、モデルをタスクごとに使い分ける設計LoRA/DPOなどの調整など、追加の調整が必要になることがわかりました。

8. まとめ


実業務を想定してエージェントを設計したら動作した

粗大ごみ収集の申込という具体タスクを想定し、受付に必要な情報収集→曖昧情報の補正→ツール連携→最終確認の一連の流れをエージェントとして実装しました。寄り道質問(料金・収集可否)や日付正規化など、実際に想定される状況にも対応できています。

「なんでもLLM」にしない設計が肝心

LLMは会話の誘導や判断に強い一方、確実性が求められる箇所はプログラムでガードし、ルール化できる処理はツール(関数)化して呼び分けるのが安全です。今回も、情報の充足判定や予約の意思決定はコード側で判断しており、LLMの柔軟さ x 決定性の担保を両立しています。

最後に

今回はLangChainベースで単一エージェントを構成しましたが、最近は「こんな小難しいプログラムを書かなくても」エージェントを作れるようになってきています。例えば、ローコード基盤Difyがニュースで取り上げられることが増えています。これを使えばほとんどコードを書かずにエージェントを構築できます。エージェントを使うのも作るのも、どんどん身近になってきています

ただし、エージェントは「魔法」ではありません。要件定義と設計の積み重ねがあってこそ、現場で役立つ実用品として作ることができます。皆さんも、まずは身近な業務から、「ここはエージェント化できるかも」と考える一歩を踏み出してみてはいかがでしょうか?


執筆者

相浦 大司(NTT西日本 技術革新部)

AI技術の社内相談役として、プロトタイプ開発や業務適用の検討に従事。 JDLA Deep Learning for ENGINEER(2024#1)取得。

商標

  • AWS, Amazon Bedrock, Amazon EC2, AWS Lambda, Amazon API Gatewayは、Amazon Web Services, Inc. またはその関連会社の商標または登録商標です。
  • GPT-4oは、OpenAI, Inc.の商標または登録商標です。
  • Llamaは、Meta Platforms, Inc.の商標または登録商標です。
  • Gemmaは、Google LLCの商標または登録商標です。
  • LangChainは、LangChain, Inc.の商標または登録商標です。
  • Difyは、LangGenius, Inc.の商標または登録商標です。

© NTT WEST, Inc.