0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

エージェント定義と実行時コンテキストを分離する新機能 Structured Inputs の設計メモ

0
Last updated at Posted at 2026-05-12

Structured Inputs とは

OpenAI のモデルには、Structured Outputs という便利な機能がありますが、Microsoft Foundry Agent Service には、それとは対になるような Structured Inputs(構造化入力) という仕組みが実装されました。

Structured Outputs が「モデルから返される出力を JSON Schema などで構造化する」ための機能だとすれば、Structured Inputs は「エージェントに渡す入力や実行時の設定を構造化し、エージェント定義・ツールリソース・応答パラメーターを動的に差し替える」ための機能です。

エージェント定義の中に {{variableName}} のようなプレースホルダを置いておき、実行時に structured_inputsで値を渡すことで、同じエージェントVersion のまま instructionstool resources を差し替えられるという仕組みです。

これはマルチテナント SaaS 型エージェントや、部署別ナレッジを扱う RAG エージェントなどで効果を発揮します。

✨ 生成 AI による図説

structuredinput.jpg

エージェント定義側では、Handlebars 風のテンプレート書式でプレースホルダを書きます。

{{userName}}
{{departmentName}}
{{vectorStoreId}}
{{mcpServerUrl}}

そして Responses API 実行時に structured_inputs として値を渡します。

{
  "structured_inputs": {
    "userName": "源実朝",
    "departmentName": "contoso-history-lab",
    "vectorStoreId": "vs_contoso_202605",
    "mcpServerUrl": "https://contoso.example.com/mcp"
  }
}

これにより、エージェントを再作成したり、エージェントVersion を大量に分岐したりせずに、実行時のコンテキストに応じて instructionstool の参照先を変えることができます。

これはまさに、Context Engineering における 実行時コンテキスト注入 のための機能と言えるでしょう。

何を実行時に差し替えられるのか

Structured Inputs の対象として以下が整理されています。

Category Property 使いどころ
Instructions Agent instructions エージェントの基本方針をユーザー、部署、業務に合わせる
Instructions Response instructions 1 回の Responses API リクエストごとにシステムプロンプトを変える
Instructions System/developer message content 会話に実行時コンテキストを注入する
File Search vector_store_ids 部署別、顧客別、Tier 別の vector store を切り替える
Code Interpreter container 既存 container を実行時に指定する
Code Interpreter container.file_ids 分析対象ファイルをリクエストごとに差し替える
MCP server_label MCP Server の識別子を切り替える
MCP server_url テナント / プロジェクト ごとの MCP endpoint に接続する
MCP headers 認証 token や プロジェクト ID をリクエストコンテキストで渡す

特に vector_store_idsfile_ids のような配列フィールドでは、空文字の値が実行時に自動的に取り除かれます。例えば primary_vector_store_idsecondary_vector_store_idoptional_vector_store_id のようにテンプレートスロットを多めに用意しておき、使わないものは空にする のような作り方ができます。

Structured Inputs がある世界、ない世界

Structured Inputs があると我々のエージェント開発ライフがどう変わるかをもう少し具体例で見てみましょう。

一見単なるプロンプトパラメータに見えるかもしれませんが、実際にはエージェントの増殖を抑え、実行時コンテキストを安全に注入するための設計パターンとして効果を発揮することが分かると思います。

例1: 部署別 RAG Agent

まずはよくある部署別 RAG エージェントです。営業部、サポート部、法務部で参照すべきナレッジベースが違うとします。

観点 Structured Inputs がない世界 Structured Inputs がある世界
エージェント定義 sales-agentsupport-agentlegal-agent を別々に作る 共通エージェントに departmentNamevector_store_id を渡す
プロンプト管理 似た instructions を 3 箇所にコピー instructions は 1 箇所、部署名だけ実行時注入
RAG 参照先 エージェント定義ごとに固定 リクエストごとに承認済み vector_store_ids を指定
変更時の影響 プロンプト修正を全エージェントに横展開 共通定義を直せばよい
事故りやすい点 どれか 1 エージェントだけ古いプロンプト / 古い vector store のまま残る 実行時コンテキスト解決部分を集中的に監査しやすい

Structured Inputs を使うと、少なくとも「部署名」「tier」「language」「vector_store_id」の違いだけでエージェントを増やす必要はなくなります。

{
  "structured_inputs": {
    "departmentName": "sales",
    "customerTier": "enterprise",
    "language": "ja-JP",
    "vector_store_id": "vs_sales_enterprise_ja_202605"
  }
}

Python SDK 実装

pip install azure-ai-projects==2.1.0

実装から利用までのステップは以下となります。

1. FileSearchTool 定義にプレースホルダをセット

FileSearchTool 定義にプレースホルダをセットすることができます。共通ナレッジベースを固定し、部署別ベクトルストアを {{vector_store_id}} としましょう。

import os
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
  FileSearchTool,
  PromptAgentDefinition,
  StructuredInputDefinition,
)
from azure.identity import DefaultAzureCredential

project = AIProjectClient(
  endpoint=os.environ["PROJECT_ENDPOINT"],
  credential=DefaultAzureCredential(),
)
openai = project.get_openai_client()

# 共通ナレッジベースと部署別ベクトルストア
tool = FileSearchTool(
  vector_store_ids=["vs_base_kb", "{{vector_store_id}}"]
)

Structured Input は単にプロンプトに埋め込むだけではなく、ツール設定・ツール引数側にも結び付けられるという点が重要です。

2. StructuredInputDefinition 定義

StructuredInputDefinition クラスの説明は以下。

プロパティ 意味 使いどころ
description str 入力値の説明。意味を理解するための説明文。 tenant_id が「テナント識別子」なのか、「表示名」なのかを明確にする。
default_value Any 実行時に値が渡されなかった場合に使われる既定値。 省略可能な入力にデフォルト値を持たせたい場合に使う。
schema dict[str, Any] 入力値の JSON Schema。値の型や形式を定義する。 {"type": "string"}{"type": "array"} などで入力形式を明示する。
required bool エージェント実行時にこの入力が必須かどうか。 auth_tokentenant_id のように、欠けると実行できない値は True にする。
agent = project.agents.create_version(
  agent_name="department-rag-agent",
  definition=PromptAgentDefinition(
    model=os.environ.get("MODEL_DEPLOYMENT", "gpt-5.4-mini"),
    instructions=(
      "あなたは {{departmentName}} 向けの承認済みナレッジベースを検索するアシスタントです。"
      "顧客 tier は {{customerTier}} です。"
      "回答は {{language}} で、実務向けに簡潔にまとめてください。"
    ),
    tools=[tool],
    structured_inputs={
      "departmentName": StructuredInputDefinition(
        description="Department name",
        required=True,
        schema={"type": "string"},
      ),
      "language": StructuredInputDefinition(
        description="Response language",
        required=True,
        schema={"type": "string"},
      ),
      "customerTier": StructuredInputDefinition(
        description="Customer tier",
        required=True,
        schema={"type": "string"},
      ),
      "vector_store_id": StructuredInputDefinition(
        description="Vector store ID for department knowledge",
        required=True,
        schema={"type": "string"},
      ),
    },
  ),
)

3. structured_inputs に値を格納

conversation = openai.conversations.create()
response = openai.responses.create(
  conversation=conversation.id,
  input="この顧客に説明すべき Foundry Agent Service のポイントを教えてください。",
  extra_body={
    "agent_reference": {"name": agent.name, "type": "agent_reference"},
    "structured_inputs": {
      "departmentName": "sales",
      "customerTier": "enterprise",
      "language": "ja-JP",
      "vector_store_id": "vs_sales_enterprise_ja_202605",
    },
  },
)

print(response.output_text)

もちろん、法務部だけ別の承認フローが必要、医療データだけ監査境界が違う、という場合はエージェントそのものを分けるべきです。そこは無理に共通化しない方がよく、単なる参照先やコンテキストの違いでエージェントが増えているのであれば Structured Inputs の利用が効果的です。

例2: マルチテナント SaaS エージェント

次に SaaS 型エージェントです。

顧客テナントとに MCP Server や API endpoint が違うケースを考えます。

観点 Structured Inputs がない世界 Structured Inputs がある世界
テナント登録 テナントごとにエージェント/ tool 設定を作る テナント登録情報から実行時コンテキストを解決して渡す
MCP 接続 server_url がエージェント定義に固定される server_url をリクエストごとに差し替える
認証ヘッダー エージェント/ツール 側の設定更新が必要 短寿命 token を headers に渡せる
credential rotation テナント数分の設定更新が重い token broker / policy check 側に寄せられる
運用 テナントが増えるほどエージェントインベントリが膨らむ エージェント定義は少なく、テナントコンテキストは外で管理

Python SDK 実装

1. MCPTool 定義

MCPTool 定義は 1 つに固定したまま、実行時にテナントごとのコンテキスト・MCP 接続先・認証ヘッダー・テナント ID をまとめて注入。

import os
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import MCPTool, PromptAgentDefinition, StructuredInputDefinition
from azure.identity import DefaultAzureCredential

project = AIProjectClient(
    endpoint=os.environ["PROJECT_ENDPOINT"],
    credential=DefaultAzureCredential(),
)
openai = project.get_openai_client()

# 1. プレースホルダをセット
tool = MCPTool(
    server_label="{{server_label}}",
    server_url=mcp_server_url,
    require_approval="never",
    headers={
        "Authorization": "{{auth_token}}",
        "X-Tenant-ID": "{{tenant_id}}",
    },
)

この部分、とても強力で、例えば以下のように単一の MCP Tool に対して複数のエージェント名をヘッダとして送信して、MCP Server 側で FROM 側のエージェントに応じて処理を分けることもできます。これによってメタデータによるコンテキストの汚染が無くなります。

    headers={
        "Authorization": "{{auth_token}}",
        "X-Agent-Name": "{{agent_name}}",
    },

2. StructuredInputDefinition 定義

# 2. StructuredInputDefinition 定義
agent = project.agents.create_version(
    agent_name="tenant-aware-mcp-agent",
    definition=PromptAgentDefinition(
        model="gpt-5.4-mini",
        instructions="あなたは {{tenant_name}} のテナント向けアシスタントです。",
        tools=[tool],
        structured_inputs={
            "tenant_name": StructuredInputDefinition(
                description="Tenant display name", required=True, schema={"type": "string"}
            ),
            "server_label": StructuredInputDefinition(
                description="MCP server label", required=True, schema={"type": "string"}
            ),
            "server_url": StructuredInputDefinition(
                description="MCP server URL", required=True, schema={"type": "string"}
            ),
            "auth_token": StructuredInputDefinition(
                description="Short-lived MCP auth token", required=True, schema={"type": "string"}
            ),
            "tenant_id": StructuredInputDefinition(
                description="Tenant identifier", required=True, schema={"type": "string"}
            ),
        },
    ),
)

ここでは MCP Server の接続情報を固定していません。固定値ではなく、次の値をすべてプレースホルダーにしています。

プレースホルダー 役割
{{server_label}} Foundry 側で MCP Server を識別するラベル
{{server_url}} 実際に接続する MCP Server URL
{{auth_token}} MCP Server に渡す短命トークン
{{tenant_id}} MCP Server 側でテナントを識別するためのヘッダー

つまり、MCP Server 接続をテナントごとに実行時切り替えできる構成です。ただし MCPTool.server_url だけは、プレースホルダ化 ({{server_url}}) するとバリデーションエラーが出てしまいました。

instructions="あなたは {{tenant_name}} のテナント向けアシスタントです。"

さらに、上記プレースホルダはエージェントに対して、今回の実行は Contoso AI Labs というテナントの文脈で振る舞え と伝える部分です。ここで重要なのは、テナントに応じて振る舞うことと、テナントの認可を行うことは別だという点です。エージェントは tenant_name を見て、回答のコンテキストや説明の粒度をテナント向けに調整できます。

# 3. `structured_inputs` に値を格納
conversation = openai.conversations.create()
response = openai.responses.create(
    conversation=conversation.id,
    input="このテナントの承認済みプロジェクト一覧を取得してください。",
    extra_body={
        "agent_reference": {"name": agent.name, "type": "agent_reference"},
        "structured_inputs": {
            "tenant_name": "Contoso AI Labs",
            "server_label": "contoso-projects",
            "server_url": "https://api.contoso.example/mcp/kamakura",
            "auth_token": "Bearer <short-lived-token>",
            "tenant_id": "tenant-contoso-001",
        },
    },
)

print(response.output_text)

この設計にすると、エージェントはテナントごとのコンテキストを理解して振る舞える一方で、認可の判断はアプリケーション、ID、ポリシー側に残すことができます。責務分離の観点でも非常に自然な設計と言えます。

例3: リクエストごとのファイル分析エージェント

Code Interpreter を使うエージェントでも効果を発揮します。ユーザーが毎回違う CSV / Excel をアップロードし、そのファイルだけを分析させるようなケースです。

観点 Structured Inputs がない世界 Structured Inputs がある世界
File binding File ID をエージェントや container に固定しがち analysis_file_id を request ごとに渡す
再利用性 ファイルごとにエージェント/ container 管理が複雑化 同じエージェント定義を使い回せる
データ分離 古い File ID が残ると怖い リクエスト単位で対象 file を明示できる
UI 連携 アップロード後のエージェント作成/更新が必要になりがち アップロード -> File ID 解決 -> レスポンス作成で済む

Python SDK 実装

import os
from io import BytesIO

from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
  AutoCodeInterpreterToolParam,
  CodeInterpreterTool,
  PromptAgentDefinition,
  StructuredInputDefinition,
)
from azure.identity import DefaultAzureCredential


project = AIProjectClient(
  endpoint=os.environ["PROJECT_ENDPOINT"],
  credential=DefaultAzureCredential(),
)
openai = project.get_openai_client()

csv_file = BytesIO(b"x\n1\n2\n3\n")
csv_file.name = "numbers.csv"
uploaded = openai.files.create(purpose="assistants", file=csv_file)

tool = CodeInterpreterTool(
  container=AutoCodeInterpreterToolParam(file_ids=["{{analysis_file_id}}"])
)

agent = project.agents.create_version(
  agent_name="code-interp-structured",
  definition=PromptAgentDefinition(
    model=os.environ.get("MODEL_DEPLOYMENT", "gpt-5.4-mini"),
    instructions="あなたはデータ分析を支援するアシスタントです。",
    tools=[tool],
    structured_inputs={
      "analysis_file_id": StructuredInputDefinition(
        description="File ID for the code interpreter",
        required=True,
        schema={"type": "string"},
      ),
    },
  ),
)

conversation = openai.conversations.create()
response = openai.responses.create(
  conversation=conversation.id,
  input="numbers.csv を読み取り、x 列の合計を返してください。",
  extra_body={
    "agent_reference": {"name": agent.name, "type": "agent_reference"},
    "structured_inputs": {"analysis_file_id": uploaded.id},
  },
  tool_choice="required",
)

print(response.output_text)

ファイル分析エージェントでは「このリクエストではどのファイルを見てよいのか」「前の顧客のファイルを参照していないか」「アップロード後のファイル ID をどこで渡すか」が重要になります。Structured Inputs があると、リクエストコンテキストとツールリソースの対応が明示的になります。

メリットの差のまとめ

ここまでをまとめると以下のようになります。

領域 ない世界 ある世界
エージェント数 コンテキストの数だけ増えやすい 共通定義に寄せやすい
プロンプト修正 複数エージェントへ横展開 共通 instructions を更新
RAG 参照先 エージェントに固定されやすい リクエストごとに承認済み ID を注入
ツール接続 テナント / プロジェクトごとに固定設定 server_url / headers を実行時指定
監査 どのエージェントがどの設定か追いづらい 実行時に渡した部署名、顧客 Tier、参照先 ID などをログに残しやすい
変更耐性 顧客や部署を追加するたびにエージェントやツール設定を増やす必要がある 顧客や部署に応じて使う設定を決める処理を足せば済むケースが増える

Structured Inputs の価値は、ちょっとプロンプトを差し替えられるようなレベルではなく、エージェントを増やさずに、本番運用で必要なコンテキスト差分を扱いやすくするところにあります。

応用: Handlebars が使えるのが地味に強い

Structured Inputs は単純な文字列置換だけではなく、Handlebars のヘルパーも使えます。単純な変数置換以外の完全な Handlebars テンプレート構文をサポートします。 条件、ループ、およびその他の組み込みヘルパーを使用して、1 つのエージェント定義内に動的命令ロジックを作成できます。

Helper Syntax 用途
Conditional {{#if value}}...{{else}}...{{/if}} 値の有無や boolean で分岐する
Negation {{#unless value}}...{{/unless}} false の場合だけ表示する
Loop {{#each array}}{{this}}{{/each}} array を繰り返し展開する
Last item check {{#unless @last}}, {{/unless}} 最後以外に区切り文字を出す

例えば、応答方針を実行時で切り替えるならこんな感じです。

あなたは {{departmentName}} 向けのアシスタントです

{{#if includeComplianceNotes}}
規制対象の内容については必ずコンプライアンス上の注意事項を含めてください
{{else}}
ユーザーが詳細を求めない限りコンプライアンス上の注意事項は簡潔にしてください
{{/if}}

{{#if allowedTools}}
ユーザーは以下の承認済みツールを使用できます
{{#each allowedTools}}
- {{this}}
{{/each}}
{{/if}}

Structured Inputs で効率的エージェント開発ライフを楽しもう!

参考

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?