這個教學步驟會教你如何用代理產生結構化輸出,而代理是建立在 Azure OpenAI 聊天完成服務上。
這很重要
並非所有代理類型都原生支援結構化輸出。 當與相容的聊天客戶端使用時,支援 ChatClientAgent 結構化輸出。
先決條件
如需必要條件和安裝 NuGet 套件,請參閱本教學課程中的 建立並執行簡單代理程式 步驟。
定義結構化輸出的型別
首先,定義一個代表你希望代理人輸出結構的型別。
public class PersonInfo
{
public string? Name { get; set; }
public int? Age { get; set; }
public string? Occupation { get; set; }
}
建立代理程式
使用 Azure AI 專案客戶端建立一個 ChatClientAgent。
using System;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
AIAgent agent = new AIProjectClient(
new Uri("<your-foundry-project-endpoint>"),
new DefaultAzureCredential())
.AsAIAgent(
model: "gpt-4o-mini",
name: "HelpfulAssistant",
instructions: "You are a helpful assistant.");
警告
DefaultAzureCredential 開發方便,但在生產過程中需謹慎考量。 在生產環境中,建議使用特定的憑證(例如 ManagedIdentityCredential),以避免延遲問題、意外的憑證探測,以及備援機制帶來的安全風險。
使用 RunAsync<T 來實現結構化輸出>
RunAsync<T> 方法在 AIAgent 基底類別中可用。 它接受一個通用型別參數,用以指定結構化輸出型態。
當結構化輸出類型在編譯時已知,且需要型別化的結果實例時,此方法適用。 它支援原語、陣列和複雜型態。
AgentResponse<PersonInfo> response = await agent.RunAsync<PersonInfo>("Please provide information about John Smith, who is a 35-year-old software engineer.");
Console.WriteLine($"Name: {response.Result.Name}, Age: {response.Result.Age}, Occupation: {response.Result.Occupation}");
結構化輸出與回應格式設定
結構化輸出可以在調用時透過設定 ResponseFormat 屬性來配置,或是在支援此功能的代理(例如 AgentRunOptions 和 Foundry 代理)的初始化時進行配置。
此方法適用於以下情況:
- 結構化輸出的類型在編譯時尚未可知。
- 該結構以原始 JSON 表示。
- 結構化輸出只能在代理建立時設定。
- 只需原始 JSON 文字,無需反序列化即可。
- 採用代理間協作。
各種 ResponseFormat 選項可用。
- 內建 ChatResponseFormat.Text 屬性:回應會是純文字。
- 內建 ChatResponseFormat.Json 屬性:回應會是沒有任何特定結構描述的 JSON 物件。
- 自訂 ChatResponseFormatJson 執行個體:回應會是符合特定結構描述的 JSON 物件。
備註
此 ResponseFormat 方法不支援原始元素與陣列。 如果你需要處理原件或陣列,可以用這種 RunAsync<T> 方法或建立包裝型別。
// Instead of using List<string> directly, create a wrapper type:
public class MovieListWrapper
{
public List<string> Movies { get; set; }
}
using System.Text.Json;
using Microsoft.Extensions.AI;
AgentRunOptions runOptions = new()
{
ResponseFormat = ChatResponseFormat.ForJsonSchema<PersonInfo>()
};
AgentResponse response = await agent.RunAsync("Please provide information about John Smith, who is a 35-year-old software engineer.", options: runOptions);
PersonInfo personInfo = JsonSerializer.Deserialize<PersonInfo>(response.Text, JsonSerializerOptions.Web)!;
Console.WriteLine($"Name: {personInfo.Name}, Age: {personInfo.Age}, Occupation: {personInfo.Occupation}");
ResponseFormat 也可以用原始 JSON schema 字串來指定,當沒有對應的 .NET 類型可用時,例如宣告式代理或從外部設定載入的 schema,這很有用:
string jsonSchema = """
{
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" },
"occupation": { "type": "string" }
},
"required": ["name", "age", "occupation"]
}
""";
AgentRunOptions runOptions = new()
{
ResponseFormat = ChatResponseFormat.ForJsonSchema(JsonElement.Parse(jsonSchema), "PersonInfo", "Information about a person")
};
AgentResponse response = await agent.RunAsync("Please provide information about John Smith, who is a 35-year-old software engineer.", options: runOptions);
JsonElement result = JsonSerializer.Deserialize<JsonElement>(response.Text);
Console.WriteLine($"Name: {result.GetProperty("name").GetString()}, Age: {result.GetProperty("age").GetInt32()}, Occupation: {result.GetProperty("occupation").GetString()}");
結構化輸出與串流
串流時,代理程式回應會串流為一系列更新,而且只有在收到所有更新後,您才能還原序列化回應。 您必須將所有更新整合為一個回應才能進行反序列化。
using System.Text.Json;
using Microsoft.Extensions.AI;
AIAgent agent = new AIProjectClient(
new Uri("<your-foundry-project-endpoint>"),
new DefaultAzureCredential())
.AsAIAgent(new ChatClientAgentOptions()
{
Name = "HelpfulAssistant",
ChatOptions = new()
{
ModelId = "gpt-4o-mini",
Instructions = "You are a helpful assistant.",
ResponseFormat = ChatResponseFormat.ForJsonSchema<PersonInfo>()
}
});
> [!WARNING]
> `DefaultAzureCredential` is convenient for development but requires careful consideration in production. In production, consider using a specific credential (e.g., `ManagedIdentityCredential`) to avoid latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
IAsyncEnumerable<AgentResponseUpdate> updates = agent.RunStreamingAsync("Please provide information about John Smith, who is a 35-year-old software engineer.");
AgentResponse response = await updates.ToAgentResponseAsync();
PersonInfo personInfo = JsonSerializer.Deserialize<PersonInfo>(response.Text)!;
Console.WriteLine($"Name: {personInfo.Name}, Age: {personInfo.Age}, Occupation: {personInfo.Occupation}");
結構化輸出,代理卻沒有結構化輸出能力
有些代理本身不支援結構化輸出,可能是因為這不在協議中,或是代理使用的語言模型沒有結構化輸出功能。 一種可能的方法是建立一個自訂的裝飾代理,將任何 AIAgent 包裝起來,並透過聊天客戶端使用額外的大型語言模型的呼叫,將代理的文字回應轉換成結構化的 JSON。
備註
由於此方法依賴額外的大型語言模型呼叫來轉換回應,因此其可靠性可能不足以應對所有情境。
若想參考此模式的實作,可依照自身需求調整,請參考 StructuredOutputAgent 範例。
小提示
完整可執行範例請參見 .NET 樣本。
串流範例
小提示
完整可執行範例請參見 .NET 樣本。
這個教學步驟會教你如何用代理產生結構化輸出,而代理是建立在 Azure OpenAI 聊天完成服務上。
這很重要
並非所有代理類型都支援結構化輸出。 當與相容的聊天客戶端使用時,支援 Agent 結構化輸出。
先決條件
如需必要條件和安裝套件,請參閱本教學課程中的 建立並執行簡式代理程式 步驟。
建立帶有結構化輸出的代理
Agent 建立在任何支援結構化輸出的聊天客戶端應用之上。
使用 Agent 使用 response_format 作為在 options 的字典中指定想要的輸出結構的鍵值。
在執行代理程式時,你可以提供以下選項之一:
- 一個定義預期輸出結構的 Pydantic 模型。
- 當你想要解析 JSON 但不定義模型類別時,可以用 JSON 架構映射
dict()。
你可以在執行時透過 options來傳遞 agent.run(..., options={"response_format": ...}) dict,或在代理建立時透過 default_options dict 設定。
根據基礎聊天用戶端功能,支援各種回應格式。
第一個範例建立一個代理,產生結構化輸出,以符合 Pydantic 模型結構的 JSON 物件形式呈現。
首先,定義一個 Pydantic 模型,該模型代表您想要從代理程式輸出的結構:
from pydantic import BaseModel
class PersonInfo(BaseModel):
"""Information about a person."""
name: str | None = None
age: int | None = None
occupation: str | None = None
現在你可以使用 Azure OpenAI 聊天客戶端建立代理程式:
import os
from agent_framework.openai import OpenAIChatCompletionClient
from azure.identity import AzureCliCredential
# Create the agent using Azure OpenAI Chat Client
agent = OpenAIChatCompletionClient(
model=os.environ["AZURE_OPENAI_CHAT_COMPLETION_MODEL"],
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
credential=AzureCliCredential(),
).as_agent(
name="HelpfulAssistant",
instructions="You are a helpful assistant that extracts person information from text."
)
現在你可以用文字資訊執行代理,並用 response_format dict 中的 options 鍵指定結構化輸出格式:
response = await agent.run(
"Please provide information about John Smith, who is a 35-year-old software engineer.",
options={"response_format": PersonInfo},
)
對於 Pydantic 模型回應格式,代理回應在 value 屬性中包含作為模型實例的結構化輸出。
if response.value:
person_info = response.value
print(f"Name: {person_info.name}, Age: {person_info.age}, Occupation: {person_info.occupation}")
else:
print("No structured data found in response")
使用 JSON 架構映射
如果你已經有 JSON 架構作為 Python 映射,請直接將該架構作為 response_format dict 中的 options 值傳遞。 在此模式下,response.value 包含解析後的 JSON 值(通常是 dict 或 list),而不是 Pydantic 模型實例。
person_info_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"occupation": {"type": "string"},
},
"required": ["name", "age", "occupation"],
}
response = await agent.run(
"Please provide information about John Smith, who is a 35-year-old software engineer.",
options={"response_format": person_info_schema},
)
if response.value:
person_info = response.value
print(f"Name: {person_info['name']}, Age: {person_info['age']}, Occupation: {person_info['occupation']}")
串流時,agent.run(..., stream=True) 回傳ResponseStream. 串流內建的終結器會自動處理結構化輸出的解析,因此你可以即時迭代更新,然後呼叫 get_final_response() 以取得解析結果:
# Stream updates in real time, then get the structured result
stream = agent.run(query, stream=True, options={"response_format": PersonInfo})
async for update in stream:
print(update.text, end="", flush=True)
# get_final_response() returns the AgentResponse with the parsed value
final_response = await stream.get_final_response()
if final_response.value:
person_info = final_response.value
print(f"Name: {person_info.name}, Age: {person_info.age}, Occupation: {person_info.occupation}")
同樣的規則適用於 response_format 是 JSON schema 映射: final_response.value 包含解析後的 JSON 而非 Pydantic 模型實例。
如果你不需要處理個別串流更新,可以完全跳過迭代—— get_final_response() 它會自動消耗整個串流:
stream = agent.run(query, stream=True, options={"response_format": PersonInfo})
final_response = await stream.get_final_response()
if final_response.value:
person_info = final_response.value
print(f"Name: {person_info.name}, Age: {person_info.age}, Occupation: {person_info.occupation}")
完整範例
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework.openai import OpenAIChatClient
from pydantic import BaseModel
"""
OpenAI Responses Client with Structured Outputs Example
This sample demonstrates using structured outputs capabilities with OpenAI Responses Client,
showing Pydantic model integration for type-safe response parsing and data extraction.
"""
class OutputStruct(BaseModel):
"""A structured outputs model for testing purposes."""
city: str
description: str
async def non_streaming_example() -> None:
print("=== Non-streaming example ===")
agent = OpenAIChatClient().as_agent(
name="CityAgent",
instructions="You are a helpful agent that describes cities in a structured format.",
)
query = "Tell me about Paris, France"
print(f"User: {query}")
result = await agent.run(query, options={"response_format": OutputStruct})
if structured_data := result.value:
print("Structured Outputs Agent:")
print(f"City: {structured_data.city}")
print(f"Description: {structured_data.description}")
else:
print(f"Failed to parse response: {result.text}")
async def streaming_example() -> None:
print("=== Streaming example ===")
agent = OpenAIChatClient().as_agent(
name="CityAgent",
instructions="You are a helpful agent that describes cities in a structured format.",
)
query = "Tell me about Tokyo, Japan"
print(f"User: {query}")
# Stream updates in real time using ResponseStream
stream = agent.run(query, stream=True, options={"response_format": OutputStruct})
async for update in stream:
if update.text:
print(update.text, end="", flush=True)
print()
# get_final_response() returns the AgentResponse with structured outputs parsed
result = await stream.get_final_response()
if structured_data := result.value:
print("Structured Outputs (from streaming with ResponseStream):")
print(f"City: {structured_data.city}")
print(f"Description: {structured_data.description}")
else:
print(f"Failed to parse response: {result.text}")
async def main() -> None:
print("=== OpenAI Responses Agent with Structured Outputs ===")
await non_streaming_example()
await streaming_example()
if __name__ == "__main__":
asyncio.run(main())