本教程步骤演示如何使用代理生成结构化输出,其中代理是基于 Azure OpenAI 聊天完成服务构建的。
重要
并非所有代理类型都支持原生结构化输出。 与兼容的聊天客户端一起使用时,支持 ChatClientAgent 结构化输出。
先决条件
有关先决条件和安装 NuGet 包,请参阅本教程中的 “创建并运行简单代理 ”步骤。
定义结构化输出的类型
首先,定义一个类型,该类型表示要从代理输出的结构。
public class PersonInfo
{
public string? Name { get; set; }
public int? Age { get; set; }
public string? Occupation { get; set; }
}
创建代理
使用 Azure AI Projects Client 创建 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 的结构化输出
可以通过在调用时设置 ResponseFormat 属性 AgentRunOptions,或在支持该功能的代理(例如 ChatClientAgent 和 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}");
也可以使用原始 JSON 架构字符串指定 ResponseFormat,这在没有可用的相应.NET类型时非常有用,例如对于从外部配置加载的声明性代理或架构:
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 内容,并通过聊天客户端使用额外的 LLM 调用将代理的文本响应转换为结构化 JSON。
注释
由于此方法依赖于额外的 LLM 调用来转换响应,因此其可靠性可能不足以满足所有方案。
有关可适应自己要求的此模式的参考实现,请参阅 StructuredOutputAgent 示例。
小窍门
有关完整的可运行示例,请参阅 .NET 示例。
流式处理示例
小窍门
有关完整的可运行示例,请参阅 .NET 示例。
在本教程步骤中,您将学习如何使用代理生成结构化输出,其中代理是基于 Azure OpenAI 聊天完成服务构建的。
重要
并非所有代理类型都支持结构化输出。 与兼容的聊天客户端一起使用时,支持 Agent 结构化输出。
先决条件
有关先决条件和安装包,请参阅本教程中的 “创建并运行简单代理 ”步骤。
使用结构化输出创建代理
它是 Agent 基于支持结构化输出的任何聊天客户端实现构建的。
Agent使用response_format字典中的options键指定所需的输出模式。
运行代理时,可以提供以下任一项:
- 一个 Pydantic 模型,用于定义预期输出的结构。
- 想要获得解析后的 JSON 而不定义模型类时的 JSON 架构映射 (
dict)。
可以在运行时通过 options 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键在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 字典中的 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 架构映射时: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())