Bedrock Agents で多段ステップタスクを実行する ── Action Groups と Trace
対象読者:ch07 の Tool Use ループを書いたが、本番運用の難しさ(リトライ・観測・並列性)に直面しはじめた読者 難易度:★★★☆☆(実装ハンズオン) 対象バージョン:
@aws-sdk/client-bedrock-agent-runtimev3 系 /@aws-sdk/client-bedrock-agentv3 系 想定読了時間:約 45 分 関連章:第 7 章(Tool Use)/ 第 10 章(AgentCore への移行)
手書き Tool Use ループの限界
ch07 で書いた Tool Use ループを思い出してほしい。
// ch07 で書いた素朴な Tool Use ループ(抜粋)
for (let i = 0; i < 5; i++) {
const res = await client.send(new ConverseCommand({ /* ... */ }));
if (res.stopReason !== "tool_use") return messages;
// assistant.content の tool_use ブロックを集めて、自前で実行して、結果を push して、また回す …
}
「東京の今の気温は?」のような 単発の関数呼び出し にはこれで十分だった。1 回ツールを叩いて、結果を整形して返す。シンプルで読みやすい。
ところが、helpdesk-ai で社員からこう聞かれたらどうなるか。
先月の私のリモートワーク勤怠を集計して、有給を 3 日(来週の月・水・金で)申請して。
これを 1 つのリクエストで処理しようとすると、最低でも次のステップが必要になる。
get_employee_id(slack_user_id)── 社員 ID を引くget_attendance(employee_id, month="2026-05")── 先月の勤怠を集計get_pto_balance(employee_id)── 有給残数を確認submit_pto_request(employee_id, dates=[...])── 3 件まとめて申請- 結果を要約して社員に返す
5 ステップ。途中で「有給残数が足りなかったら?」のような分岐も入ってくる。手書きの for ループでこれを書こうとした瞬間に、コードはあっという間に肥大化する。中間結果(observation)の蓄積、暴走防止のループ上限、session ID と会話履歴の管理、終了判定、リトライ方針、各 step の rationale をどう残すか ── これらは「LLM エージェント」を作るたびに毎回現れる共通課題だ。AWS マネージドな ReAct ループ がほしくなる。それが Bedrock Agents の入口になる。
本章のゴール:helpdesk-ai に Bedrock Agents を導入し、社員の自然言語リクエストを「人事 API への複数ステップ呼び出し」に落とし込む。Action Groups で関数群を定義し、Trace で実行履歴を観測できる状態にする。
対象バージョン:AWS SDK v3(
@aws-sdk/client-bedrock-agentv3、@aws-sdk/client-bedrock-agent-runtimev3)、Bedrock Agents 2026 年 6 月時点
Bedrock Agents の動作原理
Bedrock Agents は、内部で 3 段階のフロー を回している。AWS 公式ドキュメントの整理に沿って読み解こう。
3 段階フロー
flowchart TD
UserInput[User Input] --> Pre[1\. Pre-processing]
Pre -->|Sanitized prompt| Orch[2\. Orchestration<br/>ReAct ループ]
Orch -->|Final answer| Post[3\. Post-processing]
Post --> Response[User Response]
subgraph Orch[2\. Orchestration ReAct ループ]
direction TB
Rationale[FM が rationale 生成<br/>'次に何をすべきか'] --> Decide{呼ぶべき<br/>ものは?}
Decide -->|Action Group| Action[Lambda / RoC<br/>を呼ぶ]
Decide -->|Knowledge Base| KB[KB.Retrieve<br/>を呼ぶ]
Decide -->|終了| End[最終応答]
Action --> Obs[Observation<br/>を蓄積]
KB --> Obs
Obs --> Rationale
end
- Pre-processing:入力の文脈化、分類、バリデーション。プロンプトインジェクションっぽい入力をここで弾く。
- Orchestration:ReAct ループの本体。
- FM が現在の状況を解釈し、rationale(次の手順の根拠)を生成
- Action Group の関数呼び出しか、Knowledge Base クエリかを判断
- 結果(observation)を得て、プロンプトに足してまた FM を回す
- 終了条件まで繰り返す
- Post-processing:最終応答の整形(デフォルトでは無効)
各ステップの プロンプトテンプレート は Bedrock コンソールの「Advanced prompts」から編集でき、Lambda parser で FM の出力を解釈し直すこともできる。実務では、最初はデフォルトで動かすのが定石。
「手書きと何が違うのか」
ch07 の for ループは上の Orchestration を アプリ側で実装 していたものだ。Bedrock Agents は次を AWS 側に押し付ける。
| 関心事 | 手書き Tool Use | Bedrock Agents |
|---|---|---|
| ループ管理 | 自分で for を書く | Agents が管理 |
| rationale の保存 | 自分で messages に積む | Trace に自動記録 |
| KB 連携 | 自分で Retrieve を呼ぶ | Action Group と同列で扱える |
| 会話履歴 | 自分で配列を維持 | sessionId で AWS が保持 |
| インジェクション対策 | 自分で書く | Pre-processing で 1 段クッション |
代わりに Agent をリソースとして作る(CreateAgent → PrepareAgent → CreateAgentAlias)という前準備が要る。
Action Groups ── Agent にツールを持たせる
Agent が実行できる関数(ツール)は、Action Group という単位で束ねる。Action Group は次の 2 つを定義する。
- スキーマ(どんな関数がどんな引数で呼べるか)
- Executor(実際にその関数を実行する場所)
スキーマの 2 種類
flowchart LR
AG[Action Group] --> Schema{スキーマ}
Schema --> Func[Function detail schema<br/>関数名・説明・パラメータを<br/>コンソール / JSON で定義]
Schema --> OpenAPI[OpenAPI schema<br/>既存 API を OpenAPI 3.0 で記述<br/>インライン or S3 URI]
AG --> Executor{Executor}
Executor --> Lambda[Lambda function<br/>Bedrock が直接呼び出す<br/>resource-based policy 必須]
Executor --> RoC[Return of Control<br/>InvokeAgent の応答に<br/>呼ぶべき関数を返す]
- Function detail schema:「関数名 + 説明 + パラメータ」をコンソールまたは JSON で直接定義。最大 11 functions / action group、5 parameters / function。
requireConfirmation: ENABLEDでユーザー確認を必須化できる(インジェクション対策に有効) - OpenAPI schema:既存 API を OpenAPI 3.0 ドキュメントで与える。インライン or S3 URI。既存 REST API を Agent に生やしたい ときに便利
helpdesk-ai では人事 API を Lambda 経由で叩くので、本章は Function detail schema + Lambda Executor に絞る。
Executor の 2 種類
| Executor | 動作 | 向く用途 |
|---|---|---|
| Lambda function | Bedrock が Lambda を直接呼び出す。resource-based policy で bedrock.amazonaws.com からの呼び出しを許可する必要 | サーバーレスで完結する社内ツール |
| Return of Control(RoC) | Lambda を経由せず、InvokeAgent の応答に「呼ぶべきアクション + パラメータ」が返ってくる。アプリ側で実行 | VPC 内の私的 API、Lambda 化しにくい既存システム |
RoC の発想転換:Bedrock 側で実行せず、自分のアプリで実行した結果を次の InvokeAgent に sessionState.returnControlInvocationResults として返す。オンプレ DB やレガシー業務システムを叩きたいときの逃げ道だ。
Lambda 関数の入出力契約
Action Group の Executor を Lambda にするとき、Lambda は Bedrock 規定の構造 で入力を受け、規定の構造で出力を返す。
受け取る入力(Function schema 時)
{
"messageVersion": "1.0",
"agent": {
"name": "helpdesk-agent",
"id": "AGENT123",
"alias": "ALIAS456",
"version": "1"
},
"sessionId": "session-...",
"actionGroup": "hr-actions",
"function": "get_attendance",
"parameters": [
{ "name": "employee_id", "type": "string", "value": "E-0421" },
{ "name": "month", "type": "string", "value": "2026-05" }
],
"sessionAttributes": {},
"promptSessionAttributes": {}
}
parameters は 配列で 渡ってくる点に注意。{ employee_id: "E-0421", month: "2026-05" } のようなオブジェクトではない。
返す出力
{
"messageVersion": "1.0",
"response": {
"actionGroup": "hr-actions",
"function": "get_attendance",
"functionResponse": {
"responseBody": {
"TEXT": { "body": "出勤 18 / リモート 4 / 有休 0 / 残業 12h" }
}
}
},
"sessionAttributes": {},
"promptSessionAttributes": {}
}
返すボディは原則テキスト(TEXT.body)で、FM が次の rationale を組み立てる材料にする。JSON を返したいなら、文字列化して body に入れるのが安全。
helpdesk-ai の Action Group「hr-actions」を実装する
ここからコードに落とす。シナリオは次の 2 関数。
get_attendance(employee_id, month)── 指定月の勤怠サマリを返すsubmit_pto_request(employee_id, dates)── 有給申請を送る
Lambda 関数本体(src/handlers/hr-actions.ts)
import type { Handler } from "aws-lambda";
// Bedrock Agents から受け取る入力型(Function schema 用)
interface AgentEvent {
messageVersion: "1.0";
agent: { id: string; name: string; alias: string; version: string };
sessionId: string;
actionGroup: string;
function: string;
parameters: { name: string; type: string; value: string }[];
sessionAttributes?: Record<string, string>;
promptSessionAttributes?: Record<string, string>;
}
interface AgentResponse {
messageVersion: "1.0";
response: {
actionGroup: string;
function: string;
functionResponse: {
responseBody: { TEXT: { body: string } };
};
};
sessionAttributes?: Record<string, string>;
promptSessionAttributes?: Record<string, string>;
}
// parameters 配列 → オブジェクト変換ヘルパ
const toArgs = (params: AgentEvent["parameters"]): Record<string, string> =>
Object.fromEntries(params.map((p) => [p.name, p.value]));
// 人事 API(架空)の呼び出し。実体は別 Lambda or 社内 API
async function fetchAttendance(employeeId: string, month: string) {
// 実装は省略。社内 HR API を fetch する想定
return {
employee_id: employeeId,
month,
workday: 18,
remote: 4,
pto_used: 0,
overtime_hours: 12,
};
}
async function submitPto(employeeId: string, dates: string[]) {
// 実装は省略
return { request_id: "PTO-2026-0421-001", status: "submitted", dates };
}
export const handler: Handler<AgentEvent, AgentResponse> = async (event) => {
const args = toArgs(event.parameters);
let body: string;
try {
switch (event.function) {
case "get_attendance": {
const r = await fetchAttendance(args.employee_id, args.month);
body = `${r.month} の勤怠サマリ: 出勤 ${r.workday} 日 / リモート ${r.remote} 日 / 有給消化 ${r.pto_used} 日 / 残業 ${r.overtime_hours}h`;
break;
}
case "submit_pto_request": {
// dates は "2026-06-08,2026-06-10,2026-06-12" のような CSV で渡ってくる前提
const dates = (args.dates ?? "").split(",").map((s) => s.trim()).filter(Boolean);
const r = await submitPto(args.employee_id, dates);
body = `申請を受理しました(ID: ${r.request_id}, 日付: ${r.dates.join(", ")})`;
break;
}
default:
body = `unknown function: ${event.function}`;
}
} catch (e) {
// Agent には「失敗した事実」をテキストで返す。スタックトレースは渡さない
body = `関数 ${event.function} の実行に失敗: ${(e as Error).message}`;
}
return {
messageVersion: "1.0",
response: {
actionGroup: event.actionGroup,
function: event.function,
functionResponse: { responseBody: { TEXT: { body } } },
},
sessionAttributes: event.sessionAttributes,
promptSessionAttributes: event.promptSessionAttributes,
};
};
ポイントを 3 つ。
parametersは配列で来るので、必ずtoArgsのような変換ヘルパで扱う- 例外は呑む。投げると Agent 側で「Failure trace」になる。エラーをテキストで返した方が、FM は次の手を考えやすい
- 複数値は Function schema のパラメータ型に配列がないので、CSV 文字列で渡すのが定石(
type: "string")
Lambda の resource-based policy
Bedrock が Lambda を呼び出すには、Lambda 側で bedrock.amazonaws.com からの lambda:InvokeFunction を許可する必要がある。CDK なら次の 1 行。
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as iam from "aws-cdk-lib/aws-iam";
const hrFn = new lambda.Function(this, "HrActionsFn", { /* ... */ });
hrFn.addPermission("AllowBedrockInvoke", {
principal: new iam.ServicePrincipal("bedrock.amazonaws.com"),
action: "lambda:InvokeFunction",
sourceArn: `arn:aws:bedrock:${this.region}:${this.account}:agent/*`,
});
sourceArn で Agent ARN の prefix を絞れば、別 Agent からの誤呼び出しを防げる。この 1 行を忘れて「Lambda が呼ばれない」と詰まる人が非常に多い ので最初に書くこと。
Agent を作成する
Agent は control plane(@aws-sdk/client-bedrock-agent)でリソースとして作る。CDK の CfnAgent を使う方法と、SDK で直接 CreateAgentCommand を叩く方法がある。ここでは SDK 版を示す。
SDK で Agent を作る(infra/create-agent.ts)
import {
BedrockAgentClient,
CreateAgentCommand,
CreateAgentActionGroupCommand,
PrepareAgentCommand,
CreateAgentAliasCommand,
} from "@aws-sdk/client-bedrock-agent";
const region = process.env.AWS_REGION ?? "us-east-1";
const bedrockMgmt = new BedrockAgentClient({
region,
maxAttempts: 5,
retryMode: "adaptive",
});
const AGENT_ROLE_ARN = process.env.AGENT_ROLE_ARN!; // bedrock:InvokeModel 等を許可した IAM Role
const HR_LAMBDA_ARN = process.env.HR_LAMBDA_ARN!;
// 1) Agent を作る
const createRes = await bedrockMgmt.send(
new CreateAgentCommand({
agentName: "helpdesk-agent",
agentResourceRoleArn: AGENT_ROLE_ARN,
foundationModel: "us.anthropic.claude-sonnet-4-5",
instruction: [
"あなたは社内ヘルプデスクのアシスタントです。",
"勤怠・有給に関する質問は hr-actions の関数を使って答えてください。",
"申請を行う前に、必ず社員に確認文を提示してから実行してください。",
"回答は簡潔に、箇条書きで。",
].join("\n"),
idleSessionTTLInSeconds: 1800,
}),
);
const agentId = createRes.agent!.agentId!;
// 2) Action Group を紐付ける(Function detail schema + Lambda Executor)
await bedrockMgmt.send(
new CreateAgentActionGroupCommand({
agentId,
agentVersion: "DRAFT",
actionGroupName: "hr-actions",
actionGroupExecutor: { lambda: HR_LAMBDA_ARN },
functionSchema: {
functions: [
{
name: "get_attendance",
description:
"指定した社員 ID と月(YYYY-MM)の勤怠サマリを返します。出勤日数・リモート日数・有給消化・残業時間を含みます。",
parameters: {
employee_id: { type: "string", required: true, description: "社員 ID(例: E-0421)" },
month: { type: "string", required: true, description: "対象月 YYYY-MM 形式" },
},
requireConfirmation: "DISABLED",
},
{
name: "submit_pto_request",
description:
"指定日付の有給休暇申請を送ります。複数日付は CSV 文字列で指定してください(例: 2026-06-08,2026-06-10)。",
parameters: {
employee_id: { type: "string", required: true, description: "社員 ID" },
dates: {
type: "string",
required: true,
description: "申請日。複数日は CSV(YYYY-MM-DD,YYYY-MM-DD,...)",
},
},
// 申請系は人間に確認を取る
requireConfirmation: "ENABLED",
},
],
},
}),
);
// 3) Prepare(編集中の DRAFT を実行可能にビルド)
await bedrockMgmt.send(new PrepareAgentCommand({ agentId }));
// 4) Alias を切る(本番呼び出しはこの aliasId を使う)
const aliasRes = await bedrockMgmt.send(
new CreateAgentAliasCommand({
agentId,
agentAliasName: "prod",
}),
);
const agentAliasId = aliasRes.agentAlias!.agentAliasId!;
console.log({ agentId, agentAliasId });
ポイントを 2 つ。
PrepareAgentが必須。DRAFT への変更は Prepare して初めて反映される。忘れると古い設定で動き続ける- 書き込み系の関数には
requireConfirmation: ENABLEDを付ける。「全社員に有給 365 日を申請して」とインジェクションで仕込まれても、確認なしで実行されなくなる
IAM Role(AGENT_ROLE_ARN の中身)
Agent が引き受ける IAM Role には、最低限こうしたポリシーが要る。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InvokeFoundationModel",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": "arn:aws:bedrock:*::foundation-model/anthropic.claude-sonnet-4-5*"
}
]
}
KB を紐付ける場合は bedrock:Retrieve、Guardrail なら bedrock:ApplyGuardrail も足す。IAM 設計の本格化は ch14 に譲り、ここでは最小限で進む。
InvokeAgent で呼び出す
リソースが揃ったら、ランタイム(@aws-sdk/client-bedrock-agent-runtime)で InvokeAgent を叩く。レスポンスは EventStream(async iterable)で、completion の各イベントに chunk・trace・returnControl のいずれかが入ってくる。
src/handlers/agent.ts
import {
BedrockAgentRuntimeClient,
InvokeAgentCommand,
} from "@aws-sdk/client-bedrock-agent-runtime";
import { randomUUID } from "node:crypto";
const region = process.env.AWS_REGION ?? "us-east-1";
const bedrockAgent = new BedrockAgentRuntimeClient({
region,
maxAttempts: 5,
retryMode: "adaptive",
});
const AGENT_ID = process.env.AGENT_ID!;
const AGENT_ALIAS_ID = process.env.AGENT_ALIAS_ID!;
export async function askHelpdesk(
inputText: string,
sessionId = `session-${randomUUID()}`,
) {
const res = await bedrockAgent.send(
new InvokeAgentCommand({
agentId: AGENT_ID,
agentAliasId: AGENT_ALIAS_ID,
sessionId,
inputText,
enableTrace: true, // Trace を観測したいので必ず true
}),
);
let answer = "";
const traces: unknown[] = [];
for await (const event of res.completion ?? []) {
if (event.chunk?.bytes) {
answer += new TextDecoder().decode(event.chunk.bytes);
}
if (event.trace) {
traces.push(event.trace.trace);
}
if (event.returnControl) {
// RoC を使う場合はここでアプリ側が関数を実行し、
// 次の InvokeAgent で sessionState.returnControlInvocationResults を返す
throw new Error("RoC は本章のスコープ外(hr-actions は Lambda Executor)");
}
}
return { answer, traces, sessionId };
}
// 使い方
const { answer, sessionId } = await askHelpdesk(
"社員 ID E-0421 の先月の勤怠を集計して、来週月・水・金(2026-06-08, 06-10, 06-12)に有給を申請して。",
);
console.log(answer);
// 同じ sessionId を渡せば会話が継続する
const next = await askHelpdesk("申請の状況を確認して。", sessionId);
console.log(next.answer);
sessionIdを保持 すれば、会話履歴を AWS 側が orchestration プロンプトに自動 augment するenableTrace: trueはデバッグ・監視に必須。本番では Trace を CloudWatch Logs か OpenTelemetry に流す(ch13 で詳述)
Trace ── 実行履歴を観測する
enableTrace: true でレスポンスに付随する trace フィールドは、Agent が内部で何をしたかを ステップ単位で全部 教えてくれる。デバッグでも本番監視でも、これが唯一の窓になる。
Trace の 5 種類
| Trace 種別 | 含まれる情報 |
|---|---|
| PreProcessingTrace | 前処理(入力カテゴリ判定)の入出力、FM への送信プロンプトと生応答 |
| OrchestrationTrace | rationale / invocation input(Lambda or KB への引数)/ observation(戻り値) の流れ。最重要 |
| PostProcessingTrace | 後処理(応答整形)の入出力。デフォルト無効なので空のことが多い |
| GuardrailTrace | Guardrail が介入した場合の判定結果。ch11 で活用 |
| FailureTrace | 失敗理由。Lambda の TimeoutError、resource-based policy 不足など |
OrchestrationTrace の中身(抜粋)
{
"orchestrationTrace": {
"rationale": {
"text": "ユーザーは先月の勤怠集計と有給申請を求めている。まず get_attendance を呼ぶ。"
},
"invocationInput": {
"actionGroupInvocationInput": {
"actionGroupName": "hr-actions",
"function": "get_attendance",
"parameters": [
{ "name": "employee_id", "value": "E-0421" },
{ "name": "month", "value": "2026-05" }
]
}
},
"observation": {
"actionGroupInvocationOutput": {
"text": "2026-05 の勤怠サマリ: 出勤 18 日 / リモート 4 日 / 有給消化 0 日 / 残業 12h"
}
}
}
}
「FM がなぜそのツールを選んだのか(rationale)」と「実際に返ってきた値(observation)」が ステップ単位でログに残る。ch07 の手書きループでは自分で蓄積する必要があった情報が、Trace に全部流れてくる。
デバッグと本番監視
const { traces } = await askHelpdesk(/* ... */);
// 開発時はファイルに出して目視
import { writeFile } from "node:fs/promises";
await writeFile("./trace.json", JSON.stringify(traces, null, 2));
「期待した関数が呼ばれない」「毎回 KB を引いてしまう」「ループが 3 回で打ち切られる」── こうした不可解な挙動は、ほぼ Trace を読むと原因が見える。console.log で追うのではなく、Trace を読む文化 を最初から作るのが正解だ。
Production では Trace を 構造化したまま CloudWatch Logs / OpenTelemetry に流す。平均ステップ数、Action Group / KB のレイテンシ、FailureTrace 発生率、GuardrailTrace 介入率 ── 派生できるメトリクスは多い。Trace を流し続けないと Agent の振る舞いは可視化できない。具体実装は ch13「観測を設計する」で扱う。
Multi-Agent Collaboration ── helpdesk-ai を 3 つに分ける
ここまでは Action Group「hr-actions」だけだった。だが現実の helpdesk-ai には、人事だけでなく IT サポート と 経費精算 も来る。これを 1 つの Agent に詰め込むと、instruction が肥大化して破綻する。
2024-12 GA の Multi-Agent Collaboration は、Supervisor + Collaborators の階層モデルで分割を可能にする。
flowchart TD
User[社員からの質問] --> Sup[Supervisor Agent<br/>helpdesk-supervisor]
Sup -->|勤怠・有給・人事| HR[Sub-agent: hr-agent<br/>Action Group: hr-actions]
Sup -->|VPN・端末・SaaS| IT[Sub-agent: it-agent<br/>Action Group: it-actions<br/>+ KB: it-troubleshoot]
Sup -->|精算・申請・規程| Exp[Sub-agent: expense-agent<br/>Action Group: expense-actions<br/>+ KB: expense-policy]
HR --> Sup
IT --> Sup
Exp --> Sup
Sup --> Reply[最終応答]
- Supervisor agent がユーザー入力を解釈し、適切な collaborator にルーティングする
- 各 collaborator は 独自に action group / knowledge base / guardrail を持つ
- Collaborator は並列に走らせられる
helpdesk-ai 想定の運用フロー:「先月の勤怠を集計して有給を申請して、ついでに新しい MacBook の交換も申請したい」のような 複合質問 を Supervisor が分解 → 勤怠は hr-agent、MacBook 交換は it-agent に振り分け → 並列実行して結果を統合。
注意点:
- Supervisor を 先に保存 してから collaborator を関連付ける(順序が決まっている)
- 役割の重複は最小化する(IT と人事で同じツールを持たない)
- Supervisor の instruction には「ルーティングだけ」「業務処理はしない」と明示する
ch10 で AgentCore に移行するとき、この階層モデルは AgentCore Runtime + Strands Agents SDK のような他フレームワークでも組み直せる。Multi-Agent はモデル化の道具であって、Bedrock Agents 専売ではない。
Inline Agents ── ランタイムで Agent を動的構築する
2024-11 GA の Inline Agents は、CreateAgent でリソース化せずに、InvokeInlineAgent API で その場で Agent を組み立てて即実行 する。
import {
BedrockAgentRuntimeClient,
InvokeInlineAgentCommand,
} from "@aws-sdk/client-bedrock-agent-runtime";
import { randomUUID } from "node:crypto";
const bedrockAgent = new BedrockAgentRuntimeClient({ region: "us-east-1" });
// テナント A は人事 API のみ、テナント B は人事 + 経費 という構成を動的に切り替え
async function askForTenant(tenantId: string, inputText: string) {
const actionGroups = [
{
actionGroupName: "hr-actions",
actionGroupExecutor: { lambda: process.env.HR_LAMBDA_ARN! },
functionSchema: { /* ... 前述と同じ ... */ },
},
];
if (tenantId === "tenant-b") {
actionGroups.push({
actionGroupName: "expense-actions",
actionGroupExecutor: { lambda: process.env.EXPENSE_LAMBDA_ARN! },
functionSchema: { /* ... */ },
});
}
const res = await bedrockAgent.send(
new InvokeInlineAgentCommand({
foundationModel: "us.anthropic.claude-sonnet-4-5",
instruction: `あなたは ${tenantId} 向けのヘルプデスクアシスタントです。…`,
sessionId: `session-${randomUUID()}`,
inputText,
actionGroups,
}),
);
for await (const event of res.completion ?? []) {
if (event.chunk?.bytes) {
process.stdout.write(new TextDecoder().decode(event.chunk.bytes));
}
}
}
使いどころ
| ユースケース | なぜ Inline が向く |
|---|---|
| マルチテナント SaaS | テナント別に有効ツール・KB・guardrail を変える。リソースを 1000 個作らずに済む |
| A/B 実験 | instruction や FM を動的に切り替えて差分を観測 |
| ロールベース | 管理者と一般ユーザーで使える関数を変える |
helpdesk-ai は リソース化した Agent で始めて、テナント別に instruction を変えたくなった段階で Inline に切り替える、という二段構えがおすすめ。
Bedrock Agents の限界 ── 次章への接続
ここまで読むと「Bedrock Agents 便利だな」と感じるはずだ。実際、PoC とプロトタイプまでは Bedrock Agents で十分 に到達できる。
だが、Production 運用に持ち込もうとした瞬間、足りないものが見えてくる。
| 本番要件 | Bedrock Agents 単体での状態 |
|---|---|
| Memory(cross-session の長期記憶) | session ID ベースの短期会話履歴のみ。「先月この社員はこう申請した」のような蓄積はない |
| Identity(誰がこの Agent を叩いたか) | IAM ロールベース。Slack ユーザーや Okta ユーザーとの紐付けは自前 |
| Observability(メトリクス・ダッシュボード) | Trace は出るが、可視化・アラートは自前で組む |
| Gateway(ツール群の集中管理) | Action Group 単位の管理にとどまる。MCP エンドポイントとして公開はできない |
| Code Interpreter / Browser | サンドボックスで Python を走らせる、Web をクロールするは含まれない |
| 実行ウィンドウ | リクエストごとの短時間呼び出し前提。長時間タスクには向かない |
| Evaluations | LLM-as-a-Judge は別途 Model Evaluation を組む必要(ch12) |
これらを埋めるのが ch10 で扱う AgentCore(2025-10 GA)だ。Bedrock Agents の「マネージドな ReAct ループ」から、Production 向けインフラへの進化 として読み解いていく。
なお、Action Group + KB + Guardrails の組み合わせは 評価が難しい(ステップ単位の正しさ、ツール選択の妥当性、最終応答の品質を分離する必要がある)。これは ch12「Day 1 から評価を組み込む」で AgentCore Evaluations を使って解く。Trace を Production の観測にどう繋ぐかは ch13 で具体実装を示す。本章は Agent を動かすところまで、次章以降で本番化していく という分担になる。
章末まとめ
- 手書きの Tool Use ループは単発タスクには良いが、多段ステップになると ループ管理・rationale 保存・会話履歴・終了判定 が肥大化する。Bedrock Agents はこれをマネージドな ReAct ループとして抽象化する
- Agent は Pre-processing → Orchestration(rationale → action → observation のループ)→ Post-processing の 3 段階で動く
- Action Group は「Function detail schema / OpenAPI schema」× 「Lambda / Return of Control」の組合せ。Lambda には必ず
bedrock.amazonaws.com向けの resource-based policy を付ける(最頻出の詰まりポイント)- 書き込み系の関数には
requireConfirmation: ENABLEDを付けて、プロンプトインジェクションでの暴発を防ぐenableTrace: true+ Trace の読書 が Agent デバッグの基本。OrchestrationTrace の rationale / invocationInput / observation を読めば、ほぼ全ての不可解な挙動の原因がわかる- Multi-Agent Collaboration で Supervisor + Collaborators に分割すると、helpdesk-ai のような複合ドメインを綺麗に切り出せる
- Inline Agents はマルチテナント SaaS や A/B 実験に向く。リソース化せずランタイムで構築できる
- 本番運用には Memory・Identity・Observability・実行ウィンドウ が足りない。次章では AgentCore でこれらを埋める