0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Microsoft FoundryでAgentic AIエージェントを作ってみた

0
Last updated at Posted at 2026-05-21

はじめに

最近、エージェンティックAI(Agentic AI)を業務アプリに組み込む仕事が増えています。この記事では、Microsoft Foundry Agent Service で実際に作った構成と、詰まった点をまとめます。

1. エージェンティックAI(Agentic AI)とは

エージェンティックAIは、質問に答えるだけのチャットボットじゃなくて、目的に向かって「考える」「必要な情報を集める」「ツールやAPIを呼ぶ」「次の行動を決める」AIアプリケーションです。エージェントを「LLMを使ってユーザー要求を推論し、自律的なアクションで達成するAIアプリケーション」と説明しています。

たとえば受付業務なら、ユーザーに返事をするだけじゃ足りないんです。必要項目を順番に聞き取って、足りない情報があれば追加で質問して、例外条件に当たれば通常フローを止めて、最後は後続システムが読める構造化データに変換しないといけません。

なので、エージェンティックAIのポイントは、きれいな文章を返すことよりも、業務上のゴールに向けて状態を持ちながら進むことです。

2. エージェンティックAIを構築する基盤

エージェンティックAI向けの基盤は、各社でかなり色が違います。2026年5月21日時点で確認した範囲だと、代表的な選択肢は次のように整理できます。

プラットフォーム 特徴 向いているケース
Salesforce Agentforce Salesforce Platform、Data Cloud、Flow、MuleSoft、Apex と深く統合されたエージェント基盤。ローコード / コード実装で営業、カスタマーサポート、コマースなどの業務エージェントを作れる。 Salesforce CRM を中心に業務データと顧客接点が集まっている組織
Microsoft Foundry Agent Service AIエージェントの構築、デプロイ、スケーリングを扱うマネージド基盤。Foundryポータルでプロンプトエージェントを作ることも、SDK / REST API でコードベースのエージェントを動かすこともできる。Microsoft Entra、Azure、Microsoft 365 との接続が強い。 Azure / Microsoft 365 / Teams / Entra を使う企業、または WebアプリからAPI経由でエージェントを呼びたいケース
Google Gemini Enterprise Agent Platform / Vertex AI Agent Builder Agent Studio、Agent Development Kit、Agent Garden、Model Garden、Agent Runtime、Agent Identity / Gateway などを含む、構築 / 拡張 / 統制 / 最適化の一体型基盤。Gemini だけでなく 200 以上の基盤モデルにアクセスできる。 Google Cloud、Vertex AI、Gemini、BigQuery などを中心にAI・データ基盤を作っているケース
Anthropic Claude Managed Agents / Claude Agent SDK Claude を自律型エージェントとして動かすためのマネージドインフラとSDK。ファイル操作、コマンド実行、Web検索、MCP、セッション、イベントストリームなどが用意されている。 長時間実行、非同期タスク、コード・ファイル・調査系エージェントを Claude 中心に作りたいケース
Amazon Bedrock Agents / AgentCore 基盤モデル、データソース、ソフトウェアアプリケーション、ユーザー会話をオーケストレーションし、アクショングループやナレッジベースを使ってAPI呼び出しやRAGを行う。Bedrock がメモリ、監視、暗号化、権限、API呼び出しを管理する。 AWS 上のシステム、IAM、Lambda、Knowledge Bases、Bedrockモデルカタログを中心に構築するケース
OpenAI Agents SDK / Responses API 指示、ツール、引き継ぎ、ガードレール、トレース、セッションを組み合わせてエージェンティックなアプリケーションを作る軽量なSDK。Responses API と組み合わせて、アプリ内にエージェントループを組み込みやすい。 OpenAIモデルを中心に、コード主導で軽くエージェントのオーケストレーションを組みたいケース

どれが一番良いというより、既存の業務データ、ID管理、クラウド、デプロイ先、ユーザーが普段いる場所で選ぶのが現実的です。

3. Microsoft Foundry を選んだメリット

今回 Microsoft Foundry を選んだ理由は、モデルそのものだけじゃなくて、エージェントを業務アプリに組み込むための周辺機能がそろっているからです。

まず、Foundry Agent Service はプロンプトエージェントだけじゃなくて、Agent Framework、LangGraph、自前コードで作ったホスト型エージェントも扱えます。最初はポータルで指示(Instructions)を詰めて、必要になったら SDK / REST API でバックエンドに組み込む、という段階的な作り方ができます。

次に、企業向け連携が強いです。公開済みエージェントは安定したエンドポイントとして呼び出せて、Microsoft 365 Copilot や Teams への配布、Entra Agent Registry、OpenResponses / Activity Protocol / Invocations / A2A protocol などの接続口が用意されています。Webアプリから呼ぶだけじゃなく、将来的に Microsoft 365 側へ置く選択肢も残せるのは便利です。

あと、安全性と監査性を気にする業務用途だと、Microsoft Entra によるID管理、RBAC、ネットワーク分離、データ所在地、ガードレール、バージョン管理といった基盤機能が効いてきます。エージェンティックAIは一度作って終わりじゃなくて、指示、ツール、評価、権限を継続的に調整するものなので、運用に近い部分まで基盤側に寄せられるのは有効です。

この記事では、その Foundry Agent Service を使って、エージェントを作り、Playground で検証し、TypeScript のバックエンドから呼び出して、アプリケーションで使える構造化レスポンスまで扱う流れを紹介します。

内容は2026年5月21日時点の公式ドキュメントと、実装中に得た知見をもとにしています。Foundry 周辺のAPIは変化が速いので、実際に手を動かすときは公式ドキュメントも合わせて確認してください。

この記事で扱うこと

この記事の中心は、Microsoft Foundry Agent Service の使い方です。具体的には、次の流れを扱います。
この記事で扱うこと — Microsoft Foundry Agent Service を使ったエージェント開発の流れ

事例として、患者から症状を聞き取って、医師に渡すための情報を整理するAI問診エージェントを使います。ただし、問診エージェントそのものが主題ではありません。ここでは、Foundry Agent を業務アプリに組み込むための具体例として扱います。

Foundry Agent の設計単位

Foundry Agent を作るときは、最初に「このエージェントは何を判断して、どこまで実行してよいのか」を決めます。チャットの文章だけ考えるのではなく、業務上の入力、判断、出力、禁止事項を指示(Instructions)に落とし込むのが大事です。

たとえば業務受付エージェントなら、次のような責務を持たせます。

  • ユーザーの入力言語を検出して、会話中はその言語で応答する
  • 必要項目を一度に聞かず、順番に確認する
  • エージェントが判断してはいけない領域を明確にする
  • 画像やファイルがあれば、事実ベースで扱う
  • 例外条件を検知したら専用のJSONイベントを返す
  • 必要項目がそろったら完了JSONイベントを返す

この記事では、具体例として医療問診を少しだけ使います。カスタマーサポートなら「問い合わせ分類」「必要情報の聞き取り」「返金可否の判断」、社内申請なら「申請内容の不足確認」「承認ルールの確認」「申請データのJSON化」みたいに置き換えられます。

ポイントは、エージェントの自然言語出力と、アプリケーション制御用の構造化された値を分けることです。画面側の分岐をエージェントの言い回しに依存させると不安定になるので、完了、警告、承認待ちなどはJSONイベントとして返す設計にします。

アプリケーション連携の全体構成

Foundry Agent をWebアプリから呼び出す場合、構成は比較的シンプルです。

アプリケーション連携の全体構成 — Webアプリから Foundry Agent を呼び出す構成

主な技術構成:

レイヤ 技術
エージェント実行基盤 Microsoft Foundry Agent Service
モデル gpt-4o 系のデプロイ
バックエンド Node.js 20, Fastify, TypeScript
Foundry 認証 DefaultAzureCredential
会話状態 Foundry の previous_response_id
翻訳 Azure AI Translator REST API(必要な場合)
フロントエンド React 18, Vite, DaisyUI

ステップ 1. Microsoft Foundry プロジェクトを作る

まずは Microsoft Foundry でプロジェクトを作ります。

大まかな流れはこんな感じです。

  1. Azure サブスクリプションを用意する
  2. Microsoft Foundry でプロジェクトを作成する
  3. モデルをデプロイする
  4. Agents 画面でプロンプトエージェントを作る
  5. 指示(Instructions)を設定する
  6. Playground で動作確認する

    プロジェクトを作ったら、次にモデルをデプロイします。gpt-4o 系を選んでおくと、テキスト + 画像のマルチモーダル入力が一通り扱えるので便利です。

Microsoft Learn のクイックスタートでも、Foundry の基本フローは「モデルの応答を試す」「定義済みプロンプトでエージェントを作る」「エージェントと複数ターンの会話をする」という順番で説明されています。

今回は、Foundry 側でプロンプトエージェントを作って、Fastifyバックエンドから /openai/v1/responses を呼び出す形にしました。

ステップ 2. エージェントの指示を業務仕様として書く

エージェンティックAIで一番大事なのは、プロンプトを「ふわっとした人格設定」じゃなくて、業務ルールとして書くことです。

指示(Instructions)には、少なくとも次の要素を入れます。

あなたは <業務名> のエージェントです。
ユーザーから必要な情報を聞き取り、次の処理に渡せる形に整理します。

役割:
- 何を聞き取るか
- どの順番で確認するか
- いつ完了とみなすか
- どの形式で結果を返すか

厳守ルール:
- やってはいけない判断
- 使ってはいけない表現
- 人間に確認すべき条件
- JSON など、アプリケーションが扱う出力形式

問診エージェントの例なら、「診断しない」「薬を勧めない」「必要項目がそろったら日本語の要約をJSONで返す」みたいな制約を書きます。カスタマーサポートなら、「返金を確定しない」「本人確認前に個人情報を出さない」「チケット作成用JSONを返す」のように置き換えられます。

Foundry Agent は指示(Instructions)を中心に動くので、ここを業務仕様として書けば書くほど、Playground での検証とアプリ連携が安定します。

ステップ 3. アプリ用のJSONイベントを設計する

Foundry Agent をアプリに組み込むとき、自然言語の返答だけで画面を分岐させると不安定になります。なので、ユーザー向けの文章とは別に、アプリケーションが読むためのJSONイベントを出す設計にします。

たとえば「情報収集が完了した」ことを画面に伝えたい場合、エージェントの自然文に正規表現をかけるのではなく、次のようなイベントを返します。

以下の内容で受け付けました。画面のボタンで確認してください。

{"event":"collection_complete","summary":{"category":"...","details":"...","needsHumanReview":true}}

問診の例だと、これを intake_complete として使いました。

責務はこんなふうに分けます。

担当 責務
エージェント 情報収集、ユーザー向け要約、構造化JSONの出力
バックエンド JSONイベントの分類、会話状態の保持、必要な後処理
画面 確認ボタン、警告表示、画面遷移

この分離にすると、フロントエンドはエージェントの言い回しに依存しなくて済みます。

ステップ 4. 例外フローもJSONイベントにする

業務アプリだと、完了だけじゃなくて、例外フローも構造化しておくと扱いやすいです。

たとえば「人間の確認が必要」「危険な入力を検知した」「権限が足りない」みたいな状態です。

{"event":"human_review_required","category":"safety","message":"担当者による確認が必要です。"}

問診の例だと、強い胸痛や呼吸困難なんかを検知したときに red_flag イベントを返して、画面側で警告表示に切り替えます。大事なのは、例外時の画面表示をエージェントの自由文に任せないことです。

ステップ 5. TypeScript から Foundry Agent を呼び出す

Microsoft Foundry のクイックスタートでは SDK と REST API の両方が紹介されています。今回はリクエストボディとヘッダーを明示的に制御しやすいREST方式で、素の fetch から呼び出しました。

実装の核はこんな形です。

import { DefaultAzureCredential } from "@azure/identity";

const SCOPE = "https://ai.azure.com/.default";
const credential = new DefaultAzureCredential();

async function getToken(): Promise<string> {
  const token = await credential.getToken(SCOPE);
  if (!token) throw new Error("Failed to acquire Entra token");
  return token.token;
}

export async function runFoundryAgent(args: {
  projectEndpoint: string;
  agentName: string;
  agentVersion: string;
  userText: string;
  imageUrl?: string;
  previousResponseId?: string;
}) {
  const token = await getToken();
  const url = `${args.projectEndpoint}/openai/v1/responses`;

  const body: Record<string, unknown> = {
    input: [
      {
        role: "user",
        content: [
          { type: "input_text", text: args.userText },
          ...(args.imageUrl ? [{ type: "input_image", image_url: args.imageUrl }] : []),
        ],
      },
    ],
    agent_reference: {
      name: args.agentName,
      version: args.agentVersion,
      type: "agent_reference",
    },
  };

  if (args.previousResponseId) {
    body.previous_response_id = args.previousResponseId;
  }

  const res = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    throw new Error(`Foundry responses ${res.status}: ${await res.text()}`);
  }

  return res.json();
}

大事なのは 3 点です。

  • Entraトークンのスコープは https://ai.azure.com/.default
  • エージェント呼び出しには agent_reference を渡す
  • 複数ターンの文脈は previous_response_id でつなぐ

ローカル開発だと az login、Azure 上ではマネージドID(Managed Identity)に寄せるのが自然です。

ステップ 6. previous_response_id で会話状態を持たせる

チャットアプリを作ると、毎回全履歴を送る実装にしがちです。でも Foundry の Responses API では、前回の response.id を次のリクエストに previous_response_id として渡すことで、サーバー側に保存された応答履歴を参照できます。

この実装では、セッションストアにこの値だけ保存しています。

export type Session = {
  id: string;
  previousResponseId?: string;
  transcript: TranscriptEntry[];
  imageUrls: string[];
  completed: boolean;
  needsHumanReview?: { category: string; message: string };
};

各ターンの流れは次のようになります。

1. フロントエンドが /api/agent/message に送信
2. Fastify が session.previousResponseId を読む
3. Foundry Agent に previous_response_id を渡す
4. Foundry から `response.id` と `output_text` を受け取る
5. `response.id` を `session.previousResponseId` に保存する

こうすると、バックエンド側で巨大な会話履歴を毎回組み立てなくて済むようになりました。

ステップ 7. エージェント出力からJSONイベントを取り出す

ユーザー向けの文章とJSONイベントを同じターンで返す場合、応答全体はJSONではありません。

なので、バックエンドで末尾の JSON センチネルだけを切り出します。

単純に text.lastIndexOf("{") で切ると、JSON内のネストしたオブジェクトで壊れます。この実装では、末尾の } から逆向きに走査して、文字列リテラルの中を無視しながら対応する { を探す実装にしました。

function splitTrailingJson(text: string): { prefix: string; json: string } | null {
  const trimmed = text.trimEnd();
  if (!trimmed.endsWith("}")) return null;

  let depth = 0;
  let inString = false;
  let start = -1;

  for (let i = trimmed.length - 1; i >= 0; i--) {
    const ch = trimmed[i];

    if (inString) {
      if (ch === '"' && trimmed[i - 1] !== "\\") inString = false;
      continue;
    }

    if (ch === '"') {
      inString = true;
      continue;
    }

    if (ch === "}") depth++;
    if (ch === "{") {
      depth--;
      if (depth === 0) {
        start = i;
        break;
      }
    }
  }

  if (start < 0) return null;

  return {
    prefix: trimmed.slice(0, start).trim(),
    json: trimmed.slice(start).trim(),
  };
}

分類処理は、最終的にこの 3 種類に落としました。

type AgentEvent = "message" | "collection_complete" | "human_review_required";

画面はこの event だけを見ます。エージェントの自然言語の細かい言い回しには依存しません。

ステップ 8. Fastifyのルートでエージェントを包む

Fastify 側では、/api/agent/start でセッションを作って、/api/agent/message で Foundry Agent に転送します。

app.post("/api/agent/message", async (req, reply) => {
  const { sessionId, text, imageDataUrl } = messageSchema.parse(req.body);

  const session = getSession(sessionId);
  if (!session) return reply.code(404).send({ error: "session_not_found" });
  if (session.completed) return reply.code(409).send({ error: "session_completed" });
  if (session.needsHumanReview) return reply.code(409).send({ error: "human_review_required" });

  const result = await runFoundryAgent({
    userText: text,
    imageUrl: imageDataUrl,
    previousResponseId: session.previousResponseId,
  });

  session.previousResponseId = result.responseId;

  if (result.event === "human_review_required") {
    session.needsHumanReview = {
      category: result.payload?.category ?? "safety",
      message: result.payload?.message ?? "担当者による確認が必要です。",
    };
  }

  return {
    sessionId,
    event: result.event,
    text: result.text,
    payload: result.payload,
  };
});

ここで大事なのは、collection_complete が来た瞬間にセッションを完了扱いにしないことです。

画面の「確認」ボタンが押されたときだけ /api/agent/confirm を呼んで確定します。こうしておけば、「修正」ボタンが押された場合は同じ会話のままエージェントに戻せます。

ステップ 9. 修正フローはエージェントの文脈に任せる

collection_complete の後にユーザーが「修正」を押した場合、画面側で修正会話を持ちすぎると複雑になります。

たとえば多言語対応だと、ユーザーが英語やインドネシア語で話していると、画面文言とエージェントの応答言語がずれる可能性があります。

なので、修正ボタンはエージェントに "edit" という文字列だけを送る設計にしました。

await sendMessage({
  sessionId,
  text: "edit",
});

previous_response_id で会話文脈が残っているので、エージェントは直前までの会話を参照できます。指示(Instructions)側に「完了イベント後に edit が来たら、修正項目を聞く」と書いておけば、画面側は修正文言を持たずに済みます。

これも、画面とエージェントの責務分離です。

ステップ 10. 必要なら翻訳や後処理をバックエンドに置く

Foundry Agent の出力をそのまま画面に出すだけなら簡単ですが、業務アプリだと後処理が必要になることが多いです。

たとえば、多言語入力を受け付けつつ、社内処理や担当者向けの要約は日本語に固定したい場合があります。この場合、クライアント側で発話を正規表現解析するより、エージェントが出した構造化出力をバックエンドで翻訳・補正する方が安定します。

方針はシンプルです。

クライアントで自然言語処理(NLP)をしない。
エージェントが出したJSON要約をバックエンドで処理する。

問診の例なら、エージェントには患者の言語で会話してもらいつつ、要約は日本語で返すように指示(Instructions)へ書いておきます。

{
  "event": "collection_complete",
  "summary": {
    "chiefComplaint": "のどの痛み",
    "duration": "3日前から",
    "associated": "発熱あり",
    "medications": "なし",
    "allergies": "なし",
    "hasImage": false
  }
}

さらに必要であれば、Azure AI Translator で日本語化を補助します。

const url = `${translatorEndpoint}/translate?api-version=3.0&to=ja`;

const res = await fetch(url, {
  method: "POST",
  headers: {
    "Ocp-Apim-Subscription-Key": translatorKey,
    "Ocp-Apim-Subscription-Region": translatorRegion,
    "Content-Type": "application/json",
  },
  body: JSON.stringify([{ text: rawSummary.chiefComplaint }]),
});

エージェントの構造化出力と Translator の補助を組み合わせると、ユーザー体験は多言語、業務側の記録は日本語に固定できます。

ステップ 11. Playground でテストするシナリオ

Foundry Agent は作って終わりではありません。指示(Instructions)を変更したら、必ず Playground で複数シナリオをテストします。

テスト観点はこんなふうに整理できます。

観点 期待結果
通常入力 必要な情報を順番に聞き、最後に collection_complete を返す
多言語入力 ユーザーの言語で会話し、業務用の要約は指定言語で返す
情報不足 一度に全部聞かず、足りない項目だけ追加質問する
例外入力 human_review_required などの例外イベントを返す
修正依頼 既存の会話文脈を使って修正項目を聞き直す
画像入力 画像から見える事実だけを扱い、過剰な推測をしない

医療問診の事例では、通常の問診、英語・インドネシア語入力、緊急ワード、修正依頼、画像付き相談をテストしました。大事なのは、成功パターンだけじゃなくて、例外フローと修正フローを Playground で先に潰しておくことです。


Playground 上で応答の中身を直接確認できるので、JSONイベントがちゃんと末尾に出ているか、自然文とJSONが混ざっていないかを目視チェックできます。バックエンドの splitTrailingJson を書く前に、ここでエージェントの出力形式を安定させておくのが楽です。

詰まった点

実装中に詰まったところを 4 つまとめておきます。

1. SDK より REST のほうが早い場面があった

公式 SDK は便利ですが、短期間のプロトタイプ実装だとエンドポイントやプレビューAPIの差分で詰まることがあります。今回は /openai/v1/responses の呼び出しを素の fetch に寄せたことで、リクエストボディとヘッダーを完全に制御できました。

これは SDK を使わない方が良い、という意味ではありません。まず公式クイックスタート通りに動かして、詰まったらREST方式を確認できるようにしておく、という判断でした。

2. 自然言語を画面分岐条件にしない

「合っていますか?」という文言を正規表現で検知してボタンを出す実装は、すぐ壊れます。

エージェントは言い回しを変えます。多言語ならなおさらです。

画面の状態遷移は、必ず event のような構造化された値で扱うのがいいです。

3. クライアントで業務内容を推測しない

最初はフロントエンドでユーザー発話を解析して、画面側のメモを段階的に埋めようとしていました。でも、多言語対応を入れた瞬間に不安定になりました。

最終的には、エージェントが出した collection_complete.summary だけを信頼する記録として扱うようにしました。

4. エージェントと画面の責務を分ける

エージェントに画面上の確認まで任せると、患者が二重確認をさせられます。逆に画面側が会話を持ちすぎると、多言語対応が崩れます。

この実装では、こんな境界に落ち着きました。

エージェント: 聞き取る、要約する、JSONイベントを返す
バックエンド: イベントを分類する、会話状態を保持する、翻訳する
画面: ボタンを出す、警告を出す、次画面へ進める

事例: 医療問診での安全ルール

最後に、事例として使っている医療問診では、安全ルールを指示(Instructions)と画面の両方に入れました。

実装で入れたルールは少なくともこんな感じです。

  • AI は診断しない
  • AI は薬や治療を勧めない
  • 画像については見える事実だけを述べる
  • 緊急ワードは専用のJSONイベントにする
  • 119番の案内を画面側で明確に表示する
  • 医師向け引き継ぎカードにはAI生成である注意書きを表示する
  • 最終判断は医師が行う前提にする

これは医療以外の業務でも同じです。返金、契約、個人情報、社内承認なんかの、人間の確認が必要な領域では、エージェントの指示(Instructions)だけじゃなく、バックエンドのイベント処理と画面表示まで含めて設計します。

まとめ

Microsoft Foundry Agent Service を使うと、プロンプトエージェントを素早く作って、指示(Instructions)、モデル、会話状態、ツール連携を Foundry 側に寄せられます。

ただ、実際のアプリに組み込むときは、エージェントの自然言語出力をそのまま画面制御に使わないのが大事でした。

特に効いた設計はこの 4 つです。

  • previous_response_id で会話状態を Foundry に持たせる
  • collection_completehuman_review_required をJSONイベントにする
  • 画面はイベントだけで状態遷移する
  • 安全ルールを指示(Instructions)と画面の両方に入れる

エージェンティックAIは「賢いチャット」じゃなくて、アプリケーションの中で役割を持った実行単位として設計すると扱いやすくなります。Microsoft Foundry は、そのエージェントを作って、検証して、API経由でアプリに組み込み、運用に近い形へ持っていくための土台になってくれます。

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?