既存プロダクトに Amazon Bedrock AgentCore runtime でLLM ワークフロー基盤を後付けした設計判断
2026-10-05 | Author : 田中 健太郎 (株式会社うるる), 石岡 陸
1. はじめに
1-1. NJSS と、今回作った機能
私たちは、入札情報速報サービス「NJSS」を開発・運営しています。NJSS は、全国の官公庁・自治体が公示する入札・落札情報を集約して提供するサービスです。全国 9,000 機関を超える発注機関の入札・落札情報を収集し、年間 290 万件以上の入札情報、累計 2,100 万件以上 (2026 年 6 月末時点) の落札情報を掲載しており、非常に多くのユーザーが同時に利用するサービスです。
この NJSS に、2026 年 6 月下旬、「AI リサーチ機能」を β 版としてリリースしました。チャット形式の画面に「神奈川県の清掃業務」のようにエリアと業務内容を自然な言葉で入力すると、LLM を使ったワークフローが入札市場の分析レポートを生成する機能です。案件トレンド・傾向を見る、落札状況・価格帯を見る、発注機関の傾向を見る、市場全体を調べる、という 4 つの調査モードを備えています。
その後、同じ基盤の上で動く「提案書作成アシスト機能」も β 版としてリリースしました。本記事ではこの機能固有のワークフローは扱いませんが、「AI リサーチ機能」と呼び出し経路・実行環境を共用することに関わる設計判断については後述します。
builders.flash メールメンバー登録
builders.flash メールメンバー登録で、毎月の最新アップデート情報とともに、AWS を無料でお試しいただけるクレジットコードを受け取ることができます。
1-2. アーキテクチャを決定づけた設計条件
利用者が抱えていた課題は、入札市場の傾向を手軽に調べる手段がなく、参入の判断が手探りになっていることでした。具体的には、どの機関がどのような案件を出しているのか、価格帯や競合の状況はどうなっているのかといった情報を、自分で案件データを集計しながら調べる必要がありました。
そこで、蓄積した案件データをLLMで分析・要約し、レポートとして返す機能を構想しました。このレポート生成は、LLM を一度呼び出して結果を返すだけでは完結しません。単発の LLM 呼び出しなら既存の PHP で構築されたバックエンドシステムでも問題なく行えましたが、今回必要だったのは、分岐やループを含むワークフローを組み立てることができ、その実行状態も扱える環境でした。
弊社では AI 関連の機能は主に Python で開発していたため、ワークフローの実装には利用実績のあった LangGraph を採用しました。その結果、システム全体は、既存の PHP バックエンドと、LangGraph で実装したワークフローを動かす Python 実行環境を組み合わせる構成になります。この 2 つの環境をどのように役割分担させ、接続するかを検討するにあたり、アーキテクチャ上の設計判断を左右した条件は次の 4 点でした。
- 既存システムの制約 : 既存バックエンドはLaravel (PHP) のアプリケーションで、PHP-FPM 上で動いている
- スケール要件 : 想定される同時利用者数ピーク時においても、既存バックエンドの通常の API リクエストへの影響を抑える
- UX 要件 : LLM のレスポンスはユーザーへストリーミング表示する
- 実行要件 : 一部のワークフローは完了まで数分かかるため、ブラウザの接続が切れた後も処理を継続し、ユーザーがあとから結果を参照できるようにする
以上を踏まえ、具体的なアーキテクチャの検討を進めました。
1-3. 本記事で扱うこと
本記事では、まず第 2 章でアーキテクチャの全体像を示します。このアーキテクチャには、前節で整理した設計条件を踏まえて社内で合意した、次の5つの設計判断を反映しています。
- LLM ワークフローの実行環境として Amazon Bedrock AgentCore runtime を選んだ (3-1)
- LLM ワークフローの出力をストリーミング配信する API を、既存 PHP バックエンドとは別サービスとして構築した (3-2)
- 会話履歴と LLM ワークフローの実行状態を Amazon DynamoDB に保存した (3-3)
- LLM アプリケーション基盤を「汎用 (複数プロダクト共通)」ではなく「対象プロダクト特化」とした (3-4)
- 呼び出し経路の異なる 2 つの LLM 機能について、1 つの実行環境を共有する構成にした (3-5)
第 3 章ではそれぞれの節でこれらの設計判断に至った背景について解説します。第 4 章では開発を通して得た知見を、第 5 章では今後の展望を紹介します。
本記事では、LLM が次の処理を動的に選ぶものをエージェント、あらかじめ決めた経路に沿って分岐・ループするものをワークフローと呼び分けます。今回実装したのは主に後者です。AgentCore runtime はエージェント専用の基盤ではなく、フレームワークにもモデルにも縛られない実行基盤なので、ワークフローの実行環境としても使えます。
2. アーキテクチャ概要
2-1. 全体構成
AI リサーチ機能では、生成途中の出力をブラウザへストリーミング配信します。その通信方式として、SSE (Server-Sent Events) を採用しました。SSE は、HTTP 接続を維持しながら、サーバーからブラウザへイベントを一方向に送信する仕組みです。この SSE による配信経路を含めた、AI リサーチ機能の全体構成を図に示します。(図 1 : AI リサーチ機能のアーキテクチャ全体構成)
各コンポーネントの用途
上記の図について、構成要素を「何の課題に対して置いたか」という観点で並べると、以下のようになります。
|
コンポーネント
|
何の課題に対して置いた
|
|---|---|
|
FastAPI (AWS Fargate)
|
大量の SSE 接続を維持するために、既存バックエンドとは別の実行モデルで受ける層 |
|
Amazon Bedrock AgentCore runtime
|
呼び出し元のコネクションから独立して、時間のかかる LLM 処理を走り切らせる実行基盤 |
|
Amazon DynamoDB
|
実行環境がセッションごとに立ち上がっては消えるため、コネクション管理が不要なデータストア |
|
Amazon S3
|
LLM ワークフローの実行状態 (LangGraph のチェックポイント) が DynamoDB の 1 アイテム上限を超える場合のオフロード先 |
|
既存バックエンド API (Laravel / PHP-FPM)
|
ユーザーの権限とデータのスコープを既存基盤に一元化するための呼び出し先 |
|
Amazon CloudWatch
|
LLM ワークフローの実行をトレースとして追跡し、実行時間やトークン使用量などの詳細を確認するための出力 |
2-2. リクエストの流れ
AIリサーチ機能の 1 回の実行を、番号付きで追います。
① ブラウザからのリクエストが、既存バックエンドで取得した認証情報を添えて FastAPI に届く
② FastAPI が既存バックエンドの API に問い合わせ、認証と認可を済ませる
③ FastAPI が AgentCore runtime を同期呼び出しする (SSE に対応する形で呼ぶ)
④ AgentCore runtime 上で LangGraph のワークフローが実行される
⑤ ワークフローが既存バックエンドの API を呼び出し、業務データを取得する。あわせて利用枠 (機能を使える回数の上限) を予約し、正常終了で確定、失敗時は取り消す
⑥ ワークフローが LLM を呼び出し、分析・生成を行う
⑦ ワークフローが実行状態を保存し、生成結果と会話履歴を書き込む。実行状態が 1 アイテムの上限を超える場合は S3 にオフロードされる
⑧ 生成結果が SSE でブラウザにストリーミングされる
⑨ ①のコネクションが切れても、④〜⑦は完走する。結果はデータストアに残る
⑩ トレースは CloudWatch に送られる
②を独立した段として書いているのは、この構成の性格をよく表しているからです。認証と認可の判断は既存基盤に一元化しており、新しく置いた層はそれを持ちません。詳しくは 3-4. で説明します。
そしてこのうち⑨が、このアーキテクチャの性格を最もよく表しています。ユーザーがブラウザを閉じて SSE の接続が切れても、AgentCore runtime 上の実行は最後まで走り切り、結果はデータストアに残ります。なぜこの性質を要件の中心に置いたのかは、3-1. で詳しく説明します。
なお、先ほどの全体構成図には別経路として、提案書作成アシスト機能が既存 PHP から AgentCore runtime を直接起動する流れを破線で描いています。FastAPI を経由するのは SSE を必要とする機能だけです。呼び出し経路を分けた理由は 3-2. で、呼び出し経路の異なる2つの機能で実行環境を共有した理由は 3-5. で説明します。
3. 設計判断の詳細
本章では、1-3. で述べた 5 つの設計判断について、順番に背景を解説していきます。
3-1. LLM ワークフローの実行環境
第 1 章で書いたとおり、LangGraph で書いたワークフローは既存バックエンドの外で動かす必要がありました。では、どこで動かすのが良いでしょうか。
実行環境に求めたこと
LLM ワークフローは、応答が返ってくるまでに時間がかかります。機能によっては数分に達します。ユーザーはチャットから処理を開始するので、応答を待たずにブラウザを閉じることもあります。それでも、あとから結果を確認できるようにしたいと考えていました。ここで問題になるのが、フロントエンドからのリクエストを受けたプロセスの中で LLM を実行すると、ブラウザが閉じてコネクションが切れた時点で、実行も打ち切られてしまう点です。やるとすれば、リクエストを受けたプロセスからバックグラウンド実行の仕組みを起動する形を、自前で作る必要があります。
さらに、こうした時間のかかる LLM 処理は、今後も機能として複数出てくることが分かっていました。機能ごとに個別のジョブ実行の仕組みを作ることは避けたいと考えました。そのため、呼び出し元のコネクションから独立して LLM ワークフローを走り切らせる、共通の実行基盤が必要でした。
AgentCore runtimeを選んだ理由
AgentCore runtime はフレームワークにもモデルにも縛られないため、LangGraph で書いた実装をそのまま持ち込めます。セッション (ユーザーとの一連のやり取り) ごとの分離や、時間のかかる実行にも対応しています。
特に良いと思ったのが課金モデルです。LLM を呼ぶワークロードには特有の性質があります。処理時間の大半が「LLM の応答を待っている時間」だということです。ワークフローの実行時間のほとんどで、CPU はほぼ仕事をしていません。AgentCore runtime の課金は、セッションが生きている間の CPU とメモリの消費量に対して行われますが、CPU は実際に消費した分だけが対象になります (AgentCore runtime では 2026 年 8 月に Instances という compute type も GA しましたが、本稿では microVMs という compute type を利用しており、ここでの課金体系についての記述も microVMs に対してのものです) 。待ち時間の多い今回のワークロードには、この課金モデルがよくマッチすると考えました。
以上のことから、AgentCore runtime を採用しました。
コネクション切断後の実行
フロントエンドからのリクエストを受ける層 (私たちの構成では FastAPI) から AgentCore runtime への呼び出しは同期です。呼び出し元の処理が途切れた後の AgentCore runtime 側の挙動は、ドキュメントには明示されていません。私たちはリリース前の試験で、ブラウザを閉じてコネクションが切れた後も実行が継続し、結果が残ることを確認し、この挙動を前提に構成を決めました。生成結果と会話履歴の書き込みは、ワークフローの中で行っています。この前提が崩れて実行が途中で失われた場合も、利用枠が消費されたままになることは避けています (後述) 。
なお、接続からの独立そのものを要件とする場合は、非同期タスク管理が公式の方式です。ただしこれは先に応答を返す方式のため、ストリーミングが要件であった今回は採用していません。
生成結果と利用枠の管理
「コネクションが切れても完走する」という性質は、処理を FastAPI 側と AgentCore runtime 側のどちらに置くかの判断基準になりました。私たちは、この経路では、生成結果の書き込みと利用枠 (機能を使える回数の上限) の管理を、すべて AgentCore runtime 側で行うことにしました。最後まで走り切る側に、確実に実行したい処理を寄せる、という考え方です。 このうち利用枠の管理は、単純なカウントアップではなく、「予約 → 確定 / 取消」の 3 つの操作に分けています。
|
操作
|
タイミング
|
挙動
|
|---|---|---|
|
予約
|
実行前 |
既存バックエンドの内部 API に予約を立てる。予約には期限 (TTL) を設定。上限超過はエラー |
|
確定
|
正常終了時 |
予約を確定する |
|
取消
|
例外発生時 |
予約を戻す |
ワークフローの実行は失敗しえます。LLM や外部 API の呼び出しがエラーになることもあれば、ワークフローの途中で例外が起きることもあります。そこで、AI リサーチ機能では、失敗したのに利用枠だけが消費されているという状態を防ぐ設計としました。予約に TTL を設定しているのは、確定も取消も届かないまま実行が失われた場合に、枠が永久に埋まったままになることを防ぐためです。
3-2. ストリーミング API の分離
PHP-FPM で SSE を受けることの懸念
最初に検討したのは、フロントエンドからの受け口を既存の PHP バックエンドに追加する形です。
その場合に懸念したのは、LLM ワークフローの応答時間の長さと、想定される同時利用者数の多さでした。LLM のワークフローやチャットを使う機能は、今後さらに増えることが見込まれました。応答に時間のかかる処理を、多くの利用者が同時に呼ぶ。こうした要件を汎用的に受けられる仕組みを用意しておきたい。SSE の受け口を考えるときも、この前提で選択肢を見ていました。
既存バックエンドは PHP-FPM で動いています。PHP-FPM はリクエストごとにワーカープロセスを 1 つ占有します。そのため SSE では、接続している間ずっと、ワーカーが 1 つ占有され続けます。具体的には、クライアントがリクエストを送ってから、サーバーが最後のイベントを送り終えるまでの間です。その大半は LLM の応答を待っているだけの時間ですが、待っている間もワーカーは解放されません。まず問題になるのは、通常のリクエストへの影響です。ワーカープールを通常のリクエストと共有していると、SSE の接続が枠を使い切ったときに、SSE とは無関係なリクエストまで順番待ちになります。ただしこれは、SSE 専用のワーカープールを分けることで避けられます。
しかし、プールを分けても残る問題があります。1 接続あたり 1 プロセスとそのメモリを占有し続けるという性質は、プールをどう分けても変わりません。LLM の応答を待つだけのために接続の数だけプロセスとメモリが必要になるため、効率がよくありません。通常のリクエストへの影響は切り離せても、同時接続数の上限はプロセス数とメモリの量で決まったままです。このように、PHP-FPM のプロセスモデルには、長時間接続を大量に維持するワークロードには向かない特徴がありました。
FastAPI への分離
そこで、SSE を受ける層は既存バックエンドの外に置くことにしました。FastAPI が採用している ASGI (Python の非同期サーバー規格) では、1 プロセスで多数の接続を並行に保持でき、接続ごとにプロセスを占有しません。接続の本数がボトルネックになる層には、こちらの実行モデルの方が適していました。ただしこれは、接続を保持している間にイベントループをブロックしないことが前提です。私たちの構成では、外部への呼び出しは非同期の HTTP クライアントで行い、同期の SDK を経由するものは別スレッドで実行することで対応しています。
この層は、FastAPI で実装した独立したサービスです。既存バックエンドと同じく Amazon ECS 上で、AWS Fargate を使って動かしています。既存バックエンドとは別のサービスなので、PHP-FPM のワーカープールを共有することもありません。
SSE を使わない機能の呼び出し
一方で、この FastAPI を LLM ワークフロー機能すべての入口にはしていません。FastAPI は SSE のために置いた層です。SSE を使わない機能は、既存バックエンドから AgentCore runtime を直接起動しています (3-5. で触れます) 。
ただし、直接呼び出しを許容するにあたって、相互依存を避けるためのルールを 1 つ置きました。
- 既存バックエンドから AgentCore runtime を直接呼ぶことは許容する
- ただし、既存バックエンドから呼ばれるワークフローは、既存バックエンドを呼び返さない
呼んでよい向きを最初に決めておくことで、依存関係が循環しないようにしています。
ストリーミング実装の注意点
SSE でストリーミングするにあたり、イベントの区切り方、部分テキストの結合、エラーやツール呼び出しの表現といった、データプロトコルの設計が必要になります。
私たちは、ここに Vercel AI SDK を利用しました。フロントエンドは Nuxt で構築しており、Vue 向けの実装が提供されているため、それをそのまま使えました。サーバー側は、SDK が定めるストリームのプロトコルに沿ってイベントを組み立てて送信することで対応しました。
もう 1 つ、SSE 対応はアプリケーションの実装だけでは終わりません。経路上のタイムアウト設定も見る必要があります。ストリームの途中には、最初のトークンが返るまでの待ちや、時間のかかるツールの実行など、データが流れない時間が生じます。その無通信時間で接続が閉じないよう、私たちは Application Load Balancer のアイドルタイムアウトを既定値より長く設定しました。
3-3. 会話履歴と実行状態の保存
保存対象と保持期間
ユーザーがブラウザを閉じたあとでも結果を参照できるようにするには、生成結果と会話履歴を、取り出せる形で保持する必要があります。
会話履歴を見返したり、会話のタイトルを変えたり、不要になったものを削除したりといった機能は、最初のリリースでは必須要件ではありませんでした。ただ、あとから対応できる形にはしておきたいと考えていました。
そしてもう 1 つ、実行環境はセッションごとに立ち上がり、セッションが終われば消える、という実行モデル上の性質があります。
AgentCore memory を採用しなかった理由
まず検討したのは、マネージドな記憶機能である AgentCore memory です。検討の材料になったのは 2 点でした。
1 つは、会話データの保持期限です。AgentCore memory は会話のデータを無期限には保持できません。会話ごとに有効期限を設けること自体は問題ありませんが、ユーザーが会話履歴を閲覧できる期間に制約を設けたくはありませんでした。AgentCore memory で対応するなら、期限が切れる前に別の永続層へ退避させる仕組みが必要になります。
もう 1 つは長期記憶です。セッションをまたいでユーザーの好みや重要な事実を自動で抽出し、次のやり取りに活かせる仕組みで、魅力的ではありました。ただ、実際に機能へ組み込むにはさらに検討が必要で、直近の要件で必要になるものでもなかったため、今回は優先度を下げました。
上記 2 点から、今回は AgentCore memory は見送り、データストアを自分たちで用意することにしました。
DynamoDB を選んだ理由
自前で持つとして、次はデータストアの選択です。当初は RDB を使うつもりでいました。ただ、先に触れた実行モデルでは、寿命の短い環境が同時セッション数だけ並びます。RDB はコネクションを維持する前提のデータストアなので、Amazon RDS Proxy などの導入をあわせて検討する必要がありました。
今回保存する会話履歴は構造が単純だったので、サーバーレスな実行環境からもシンプルな構成で使える DynamoDB を選びました。加えて実装面の後押しとして、AWS 向けのチェックポイントライブラリ (langgraph-checkpoint-aws の DynamoDBSaver) がそのまま使えることがありました。圧縮、TTL、S3 へのオフロードもオプションで設定でき、実行状態の保存まわりを自前で実装せずに済みます。
保存構成
|
何を持つか
|
どこに持つか
|
用途
|
|---|---|---|
|
会話履歴
|
DynamoDB |
ユーザーに見せる |
|
ワークフローの実行状態 (LangGraph のチェックポイント)
|
DynamoDB (TTL つき) |
次のやり取りに引き継ぐ |
|
上限を超えた実行状態
|
S3 にオフロード |
DynamoDB の 400 KB 上限対策 |
実行状態を持つ必要があるのは、AI リサーチ機能が対話しながら必要な情報を集める形になっているからです。分析に必要な項目が埋まるまで聞き返す、いわゆるスロットフィリングの形です。項目が揃えば分析を行い、レポートを返します。別の切り口で見たい場合は、続けて別のレポートを作成することもできます。こうしたやり取りの途中の状態を、サーバー側で保持しています。
この実行状態は、ユーザーに見せる会話履歴とは別物として分けています。会話履歴は残し続けますが、実行状態はやり取りが終われば不要になるため、TTL を設定して一定期間で自動的に削除されるようにしています。
また、ワークフローの実行状態は大きくなりえます。DynamoDB の 1 アイテムの上限は 400 KB ですが、ワークフローの状態はこれを超えることがあるため、超えた分は S3 にオフロードする設定にしています (圧縮も有効化しています) 。DynamoDBSaver ではデフォルトで 350 KB 以上の場合に S3 にオフロードされるため、これを活用しています。保存先の切り替えはライブラリが管理するため、アプリケーション側に分岐は出ません。
3-4. LLMアプリケーション基盤の適用範囲
当初の構想
構想の初期に立ち返ります。私たちは、入札案件や行政情報を参照するサービスを複数運営しています。そのため行政データは社内共通の API として整備し、各サービスがそこから取得する形にしています。
そこで考えていたのは、この行政データを扱う汎用のエージェントです。API と同じように、複数のプロダクトから呼び出して使える形にできないか、と考えていました。それに伴い、エージェントの利用に必要な認証も、複数プロダクトに対応できる汎用の基盤を用意する想定でいました。
検討した選択肢
|
選択肢
|
内容
|
|---|---|
|
A. 社内横断 (複数プロダクト共通)
|
複数プロダクトから使える。汎用的な認証基盤と、汎用的なデータフォーマットが必要になる |
|
B. 対象プロダクト特化 (採用)
|
NJSS に閉じる。既存基盤の権限モデルをそのまま使える |
汎用エージェントを見送った理由
いくつかの LLM 機能を実際に開発していくなかで、分かったことがあります。
検討した機能のほとんどが、プロダクト固有のユーザーの情報に依存したのです。そしてユーザーが持っている情報は、システムごとに違います。データを配信する API のように、複数のプロダクトから汎用的に使われる見込みは、実際にはありませんでした。
汎用的な構成にすれば、汎用的なデータフォーマットや入力書式を用意することになり、特化した構成に比べて複雑になります。汎用化のニーズの見込みが低い状況では、それはオーバースペックだと判断しました。そこで、対象プロダクトに特化させることを決めました。
認証・認可の構成
この結果、権限の設計は次のように単純な形に落ち着きました。
フロントエンドは既存のバックエンドで認証を済ませ、その認証情報を FastAPI の新しいエンドポイントへ送ります。FastAPI はまず既存バックエンドの API に問い合わせて認証と認可を確認し、それを済ませてから AgentCore runtime を呼び出します (2-2. の②と③) 。LLM ワークフローが既存バックエンドの API を呼ぶ際も、そのユーザーの情報を利用します。利用枠の残りがあるかどうかの判断 (3-1.) は、既存バックエンドの API が返します。認可が必要なデータも、既存バックエンドの API を通して取得する構成にしています。このように認証と権限の管理は既存バックエンド側に寄せる構成にしました。
ただし、会話の所有権については、まず既存バックエンドで認証した上で、リクエストごとにユーザーが当該会話の所有者であることを FastAPI 側で検証してから AgentCore runtime を呼び出す設計にしています。会話の識別子は FastAPI 側で生成・保持し、これを AgentCore runtime のセッション ID として渡しています。
3-5. 複数機能での実行環境の共有
この AgentCore runtime には、現在 2 つの機能が載っています。1 つはここまで見てきた AIリサーチ機能で、SSE で対話的に返します。もう 1 つは提案書作成アシスト機能で、案件の資料を読んで要件を整理し、提案書の土台までを生成する LLM ワークフロー機能です。こちらは SSE を使わないため、フロントエンドは従来どおり既存 PHP のエンドポイントにアクセスし、既存 PHP が AgentCore runtime を呼び出す構成にしています。呼び出し元も呼ばれ方も違うものが、同じ実行環境の上で動いています。
2 つの機能には共通のロジックがあるため、同じリポジトリで管理しています。本来は、機能の責務ごとに実行環境を分けるのがよいと考えています。責務が分かれていれば、実行環境ごとの権限やリソースの管理も、トレースを起点にした評価・改善も、機能単位で行いやすくなるからです。それでも現時点では、同じエンドポイントにしてパラメーターで処理を振り分ける構成を選び、分けるのは先送りにしました。理由は 4 つです。
- 実行環境を分けると管理するリソースが増え、同じコードベースから複数のエンドポイントへデプロイし分ける仕組みも必要になる
- ログの出力と切り分けが適切に行えていれば、1 つにまとめても運用に支障はなかった
- 将来分ける必要が出てきたら、そのときに分けられる。不可逆な選択ではない
- AgentCore runtime のクォータは AWS アカウントごとのリージョン単位なので、実行環境を分けてもセッション枠の緩和にはならない (後述)
以上のことから、いまは 1 つにまとめておき、分けるのは必要になったときでよいと判断しました。
まとめること自体に積極的な利点があるわけではなく、デプロイし分ける仕組みを作る手間との兼ね合いでこうしているにすぎません。そのため、以下の時が来たら分けることを検討するつもりです。
- 機能ごとに権限を最小限に絞る必要が出た時。いま各ワークフローが触るツールとデータは限られているため、まとめて管理してもリスクは限定的だと考えています。ただし、LLM が動的にツールを選ぶ形へ広げていくと、アクセスする範囲が事前に確定しにくくなります。そうなれば、機能ごとに権限を絞りたくなると考えています。
- 機能ごとの品質指標を継続的に追い、改善のサイクルを機能単位で回すようになった時。いまはスパンの属性で絞り込めば足りています (4-1.) 。
4. 開発を通して得た知見
4-1.トレースはゼロコード計装で対応
LLMワークフローのトレースは、OpenTelemetry のゼロコード計装 (zero-code instrumentation) で取得しています。プロセスの起動コマンドを opentelemetry-instrument で包むだけで、各 LangGraph ノードに手動でスパン生成コードを書かずに済みました。検索用にエージェント名やユーザー ID といった属性をスパンに追加していますが、それ以外の実装は不要でした。アプリケーションコード側の対応としてはそれだけで、 Amazon CloudWatch の生成 AI オブザーバビリティにおいて、LangGraph のノードをそのままスパンとして確認し、実行経路を追えるようになりました。
トレース画面には、その実行で通ったノードが順に並びます。ノードごとの所要時間も出るため、どこで時間がかかっているかがそのまま分かります。こうした受け皿が揃っていたため、LangGraph の実装を持ち込む作業はスムーズに進みました。
※ 図 2 : CloudWatch 生成 AI オブザーバビリティのトレース画面 (レイテンシーが 1 秒を超えるスパンに絞り込んだ状態)
4-2. アクティブセッション数とアイドルタイムアウト
AgentCore runtime には、アクティブセッション数のクォータがあります。このクォータは AWS アカウントごとのリージョンレベルで適用されます。
私たちは、リリース前の負荷試験で当時のデフォルトのクォータでは不足することが確認できたため、引き上げを申請しました (このデフォルト値は 2026 年 7 月に引き上げられています) 。あわせて、アイドルセッションのタイムアウトを既定の 15 分から 5 分に短縮し、アイドル状態のセッションが枠を占有し続けることを抑えました。
注意点として、待ち時間に CPU の課金が発生しないことと、セッションが残り続けることによるコストは、別の問題です。CPU を使っていない待ち時間でも、セッションが生きている限りメモリの課金は続き、クォータの枠も消費し続けます。アイドルタイムアウトの短縮は、その両方に効果がありました。
5. 基盤としての現在地
5-1. 今後の LLM ワークフローを載せる基盤ができた
この基盤の上では現在 2 つの機能が動いており、AI リサーチ機能は 2026 年 6 月下旬のリリース以降、トラブルなく稼働しています。
あとから追加した提案書作成アシスト機能では、この基盤を活かすことで、ワークフローの開発に集中できました。
LLM ワークフローを利用する機能は今後も開発していく予定です。それらも同じ基盤の上に構築していきます。
さらに、あらかじめ決めた経路をたどるワークフローだけでなく、LLM が手順やツールを動的に選ぶ、いわゆるエージェントの形をとる機能も考えています。その場合、これまで実装してきたツール群はそのまま使い、ストリーミング配信の層も活用する想定です。一方で、機能が扱える範囲が広がるぶん、権限の設計と実行環境の分け方は改めて考える必要があると考えています。
5-2. まとめ
最後に、5 つの判断を振り返ります。
- LLM ワークフローの実行環境として Amazon Bedrock AgentCore runtime を選んだ (3-1.)
- LLM ワークフローの出力をストリーミング配信する API を、既存 PHP バックエンドとは別サービスとして構築した (3-2.)
- 会話履歴と LLM ワークフローの実行状態を DynamoDB に保存した (3-3.)
- LLM アプリケーション基盤を「汎用 (複数プロダクト共通) 」ではなく「対象プロダクト特化」とした (3-4.)
- 呼び出し経路の異なる 2 つの LLM 機能について、1 つの実行環境を共有する構成にした (3-5.)
既存プロダクトに LLM ワークフローを後付けしようとしている方の、判断の材料になれば幸いです。
筆者プロフィール
田中 健太郎
株式会社うるる
Govtech事業本部 開発部 AI活用課 課長
入札情報検索サービスの開発を経て、2022 年にうるるへ入社。現在は AI活用課の責任者として、AI を活用して事業を発展させることをミッションに、AI 機能の企画や設計を担当しています。今回の入札情報速報サービス「NJSS」の AI リサーチ機能の開発では、全体のアーキテクチャ設計を担当しました。
趣味は料理。直近は鶏雑炊に凝っており、次は燻製に手を出そうかと画策中です。
石岡 陸
アマゾンウェブサービスジャパン合同会社
ソリューションアーキテクト
Web 業界のお客様を中心に、クラウドや生成 AI の活用を支援しています。現在は特に AI エージェントを実際の業務やプロダクトでどう活かすかに関心があります。本記事に関してはアーキテクチャディスカッションや設計判断の整理支援等を行いました。
趣味はゲームと開発で、仕事以外の時間はゲームをしながら AI エージェントを回しています。最近は旅行用の装備を自作したりもしています。