Structured Inputs とは
OpenAI のモデルには、Structured Outputs という便利な機能がありますが、Microsoft Foundry Agent Service には、それとは対になるような Structured Inputs(構造化入力) という仕組みが実装されました。
Structured Outputs が「モデルから返される出力を JSON Schema などで構造化する」ための機能だとすれば、Structured Inputs は「エージェントに渡す入力や実行時の設定を構造化し、エージェント定義・ツールリソース・応答パラメーターを動的に差し替える」ための機能です。
エージェント定義の中に {{variableName}} のようなプレースホルダを置いておき、実行時に structured_inputsで値を渡すことで、同じエージェントVersion のまま instructions や tool resources を差し替えられるという仕組みです。
これはマルチテナント SaaS 型エージェントや、部署別ナレッジを扱う RAG エージェントなどで効果を発揮します。
✨ 生成 AI による図説
エージェント定義側では、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 を大量に分岐したりせずに、実行時のコンテキストに応じて instructions や tool の参照先を変えることができます。
これはまさに、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_ids や file_ids のような配列フィールドでは、空文字の値が実行時に自動的に取り除かれます。例えば primary_vector_store_id、secondary_vector_store_id、optional_vector_store_id のようにテンプレートスロットを多めに用意しておき、使わないものは空にする のような作り方ができます。
Structured Inputs がある世界、ない世界
Structured Inputs があると我々のエージェント開発ライフがどう変わるかをもう少し具体例で見てみましょう。
一見単なるプロンプトパラメータに見えるかもしれませんが、実際にはエージェントの増殖を抑え、実行時コンテキストを安全に注入するための設計パターンとして効果を発揮することが分かると思います。
例1: 部署別 RAG Agent
まずはよくある部署別 RAG エージェントです。営業部、サポート部、法務部で参照すべきナレッジベースが違うとします。
| 観点 | Structured Inputs がない世界 | Structured Inputs がある世界 |
|---|---|---|
| エージェント定義 |
sales-agent、support-agent、legal-agent を別々に作る |
共通エージェントに departmentName と vector_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_token や tenant_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 で効率的エージェント開発ライフを楽しもう!
参考
