Produzir Resultados Estruturados com Agentes

Este passo do tutorial mostra-lhe como produzir outputs estruturados com um agente, onde o agente é construído com o serviço Azure OpenAI Chat Completion.

Importante

Nem todos os tipos de agentes suportam saídas estruturadas nativamente. O ChatClientAgent suporta saídas estruturadas quando usado com clientes de chat compatíveis.

Pré-requisitos

Para obter pré-requisitos e instalar pacotes NuGet, consulte a etapa Criar e executar um agente simples neste tutorial.

Defina um tipo para saídas estruturadas

Primeiro, defina um tipo que represente a estrutura do output que pretende do agente.

public class PersonInfo
{
    public string? Name { get; set; }
    public int? Age { get; set; }
    public string? Occupation { get; set; }
}

Criar o agente

Crie um ChatClientAgent usando o Azure AI Projects Client.

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.");

Advertência

DefaultAzureCredential é conveniente para o desenvolvimento, mas requer uma consideração cuidadosa na produção. Em produção, considere usar uma credencial específica (por exemplo, ManagedIdentityCredential) para evitar problemas de latência, sondagens não intencionais de credenciais e potenciais riscos de segurança provenientes de mecanismos de recurso.

Saídas estruturadas com RunAsync<T>

O RunAsync<T> método está disponível na AIAgent classe base. Aceita um parâmetro genérico de tipo que especifica o tipo de saída estruturada. Esta abordagem é aplicável quando o tipo de saídas estruturadas é conhecido no momento da compilação e é necessária uma instância tipificada de resultado. Suporta primitivas, arrays e tipos complexos.

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}");

Saídas estruturadas com ResponseFormat

As saídas estruturadas podem ser configuradas definindo a propriedade ResponseFormat de AgentRunOptions no momento da invocação, ou no momento da inicialização do agente para agentes que a suportam, como ChatClientAgent e Foundry Agent.

Esta abordagem é aplicável quando:

  • O tipo de saída estruturada não é conhecido em tempo de compilação.
  • O esquema é representado como JSON bruto.
  • As saídas estruturadas só podem ser configuradas no momento da criação do agente.
  • Apenas o texto JSON bruto é necessário sem desserialização.
  • É utilizada colaboração entre agentes.

Existem várias opções de ResponseFormat disponíveis:

  • Uma propriedade integrada ChatResponseFormat.Text: a resposta será texto sem formatação.
  • Uma propriedade interna ChatResponseFormat.Json : A resposta será um objeto JSON sem nenhum esquema específico.
  • Uma instância personalizada ChatResponseFormatJson : A resposta será um objeto JSON que está em conformidade com um esquema específico.

Observação

Primitivos e arrays não são suportados pela ResponseFormat abordagem. Se precisares de trabalhar com primitivas ou arrays, usa a RunAsync<T> abordagem ou cria um tipo de wrapper.

// 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}");

O ResponseFormat pode também ser especificado usando uma cadeia de esquema JSON bruta, o que é útil quando não existe um tipo de .NET correspondente disponível, como para agentes declarativos ou esquemas carregados a partir de configurações externas:

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()}");

Saídas estruturadas com streaming

Durante o streaming, a resposta do agente é transmitida como uma série de atualizações, e você só pode desserializar a resposta depois que todas as atualizações forem recebidas. Você deve reunir todas as atualizações em uma única resposta antes de desserializá-la.

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}");

Saídas estruturadas com agentes sem capacidade de gerar saídas estruturadas

Alguns agentes não suportam nativamente saídas estruturadas, seja porque não fazem parte do protocolo ou porque os agentes usam modelos de linguagem sem capacidades de saídas estruturadas. Uma abordagem possível é criar um agente decorador personalizado que envolva qualquer AIAgent e utilize uma chamada de LLM adicional através de um cliente de bate-papo para converter a resposta do agente em JSON estruturado.

Observação

Como esta abordagem depende de uma chamada adicional de LLM para transformar a resposta, a sua fiabilidade pode não ser suficiente para todos os cenários.

Para uma implementação de referência deste padrão que possa adaptar aos seus próprios requisitos, consulte o exemplo StructuredOutputAgent.

Sugestão

Consulte as amostras .NET para exemplos completos executáveis.

Exemplo de streaming

Sugestão

Consulte as amostras .NET para exemplos completos executáveis.

Este passo deste tutorial mostra-lhe como produzir resultados estruturados com um agente, onde o agente é construído no serviço de Conclusão de Chat da Azure OpenAI.

Importante

Nem todos os tipos de agentes suportam saídas estruturadas. A Agent suporta saídas estruturadas quando usado com clientes de chat compatíveis.

Pré-requisitos

Para obter pré-requisitos e instalar pacotes, consulte a etapa Criar e executar um agente simples neste tutorial.

Crie o agente com saídas estruturadas

É Agent construído sobre qualquer implementação de cliente de chat que suporte saídas estruturadas. O Agent usa a chave response_format no dicionário options para especificar o esquema de saída desejado.

Ao executar o agente, pode fornecer uma das seguintes opções:

  • Um modelo pidântico que define a estrutura da saída esperada.
  • Um mapeamento de esquema JSON (dict) quando queres JSON analisado sem definir uma classe modelo.

Podes passar o options dict em tempo de execução via agent.run(..., options={"response_format": ...}), ou defini-lo na altura da criação do agente através do default_options dict.

Vários formatos de resposta são suportados com base nos recursos subjacentes do cliente de chat.

O primeiro exemplo cria um agente que produz saídas estruturadas sob a forma de um objeto JSON que segue um esquema de modelo Pydantic.

Primeiro, defina um modelo Pydantic que represente a estrutura da saída que você deseja do agente:

from pydantic import BaseModel

class PersonInfo(BaseModel):
    """Information about a person."""
    name: str | None = None
    age: int | None = None
    occupation: str | None = None

Agora pode criar um agente usando o Cliente de Chat OpenAI do Azure:

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."
)

Agora podes executar o agente com alguma informação textual e especificar o formato estruturado das saídas usando a response_format chave no options ditado:

response = await agent.run(
    "Please provide information about John Smith, who is a 35-year-old software engineer.",
    options={"response_format": PersonInfo},
)

Para um formato de resposta de modelo Pydântico, a resposta do agente contém as saídas estruturadas na value propriedade como uma instância de modelo:

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")

Use um mapeamento de esquema JSON

Se já tens um esquema JSON como mapeamento Python, passa esse esquema diretamente como o valor response_format no ditado options. Neste modo, response.value contém o valor JSON analisado (tipicamente um dict ou list) em vez de uma instância do modelo 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']}")

Ao transmitir, agent.run(..., stream=True) devolve um ResponseStream. O finalizador incorporado do stream gere automaticamente a análise estruturada das saídas, por isso podes iterar para obter atualizações em tempo real e depois ligar get_final_response() para obter o resultado analisado.

# 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}")

A mesma regra aplica-se quando response_format é um mapeamento de esquema JSON: final_response.value contém JSON analisado em vez de uma instância de modelo Pydantic.

Se não precisares de processar atualizações individuais de streaming, podes saltar a iteração por completo — get_final_response() irá consumir automaticamente o stream:

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}")

Exemplo completo

# 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())

Passos seguintes