Production de sorties structurées avec des agents

Cette étape du tutoriel vous montre comment produire des sorties structurées avec un agent, où l’agent est basé sur le service d’achèvement de conversation OpenAI Azure.

Important

Tous les types d’agents ne prennent pas en charge les sorties structurées en mode natif. Les ChatClientAgent sorties structurées sont prises en charge lorsqu'elles sont utilisées avec des clients de chat compatibles.

Prerequisites

Pour connaître les prérequis et l’installation des packages NuGet, consultez l’étape Créer et exécuter un agent simple dans ce tutoriel.

Définir un type pour les sorties structurées

Tout d’abord, définissez un type qui représente la structure de la sortie souhaitée de l’agent.

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

Créer l’agent

Créez un ChatClientAgent à l’aide du client Azure Projets AI.

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

Avertissement

DefaultAzureCredential est pratique pour le développement, mais nécessite une considération minutieuse en production. En production, envisagez d’utiliser des informations d’identification spécifiques (par exemple ManagedIdentityCredential) pour éviter les problèmes de latence, la détection involontaire des informations d’identification et les risques de sécurité potentiels liés aux mécanismes de secours.

Sorties structurées avec RunAsync<T>

La RunAsync<T> méthode est disponible sur la AIAgent classe de base. Il accepte un paramètre de type générique qui spécifie le type de sorties structurées. Cette approche s’applique lorsque le type de sorties structurées est connu au moment de la compilation et qu’une instance de résultat typée est nécessaire. Il prend en charge les primitives, les tableaux et les types complexes.

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

Sorties structurées avec ResponseFormat

Les sorties structurées peuvent être configurées en définissant la propriété ResponseFormat au moment de l'appel AgentRunOptions, ou lors de l’initialisation de l’agent pour les agents qui le supportent, tels que ChatClientAgent et l’agent Foundry.

Cette approche s’applique quand :

  • Le type de sorties structurées n’est pas connu au moment de la compilation.
  • Le schéma est représenté sous forme de JSON brut.
  • Les sorties structurées ne peuvent être configurées qu’au moment de la création de l’agent.
  • Seul le texte JSON brut est nécessaire sans désérialisation.
  • La collaboration entre agents est utilisée.

Différentes options ResponseFormat sont disponibles :

Note

Les primitives et les tableaux ne sont pas pris en charge par l’approche ResponseFormat . Si vous devez utiliser des primitives ou des tableaux, utilisez l’approche RunAsync<T> ou créez un type 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}");

Le ResponseFormat peut également être spécifié à l’aide d’une chaîne de schéma JSON brute, ce qui est utile lorsqu’il n’existe aucun type de .NET correspondant disponible, par exemple pour les agents déclaratifs ou les schémas chargés à partir de la configuration externe :

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

Sorties structurées avec streaming

Lors de la diffusion en continu, la réponse de l’agent est diffusée en tant que série de mises à jour et vous ne pouvez désérialiser la réponse qu’une fois que toutes les mises à jour ont été reçues. Vous devez assembler toutes les mises à jour en une seule réponse avant de la désérialiser.

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

Sorties structurées avec des agents dépourvus de capacités de sortie structurée

Certains agents ne prennent pas en charge les sorties structurées en mode natif, soit parce qu’ils ne font pas partie du protocole, soit parce que les agents utilisent des modèles de langage sans fonctionnalités de sortie structurée. Une approche possible consiste à créer un agent décoratif personnalisé qui encapsule tout AIAgent et utilise un appel LLM supplémentaire via un client de conversation pour convertir la réponse textuelle de l’agent en JSON structuré.

Note

Étant donné que cette approche s’appuie sur un appel LLM supplémentaire pour transformer la réponse, sa fiabilité peut ne pas suffire pour tous les scénarios.

Pour obtenir une implémentation de référence de ce modèle que vous pouvez adapter à vos propres exigences, consultez l’exemple StructuredOutputAgent.

Conseil / Astuce

Consultez les exemples .NET pour obtenir des exemples exécutables complets.

Exemple de diffusion en continu

Conseil / Astuce

Consultez les exemples .NET pour obtenir des exemples exécutables complets.

Cette étape du tutoriel vous montre comment produire des sorties structurées avec un agent, où l’agent est basé sur le service d’achèvement de conversation OpenAI Azure.

Important

Tous les types d’agents ne prennent pas en charge les sorties structurées. Les sorties de Agent structurées sont prises en charge lorsqu'elles sont utilisées avec des clients de chat compatibles.

Prerequisites

Pour connaître les prérequis et l’installation des packages, consultez l’étape Créer et exécuter un agent simple dans ce tutoriel.

Créer l’agent avec des sorties structurées

Le Agent repose sur n’importe quelle implémentation de client de conversation qui prend en charge les sorties structurées. Le Agent utilise la clé response_format dans le dictionnaire options pour spécifier le schéma de sortie souhaité.

Lors de l’exécution de l’agent, vous pouvez fournir les éléments suivants :

  • Modèle Pydantic qui définit la structure de la sortie attendue.
  • Mappage de schéma JSON (dict) lorsque vous souhaitez analyser JSON sans définir une classe de modèle.

Vous pouvez passer le dictionnaire pendant l'exécution via optionsagent.run(..., options={"response_format": ...}) ou le définir au moment de la création de l’agent via le dictionnaire default_options.

Différents formats de réponse sont pris en charge en fonction des fonctionnalités du client de conversation sous-jacentes.

Le premier exemple crée un agent qui produit des sorties structurées sous la forme d’un objet JSON conforme à un schéma de modèle Pydantic.

Tout d’abord, définissez un modèle Pydantic qui représente la structure de la sortie souhaitée à partir de l’agent :

from pydantic import BaseModel

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

Vous pouvez maintenant créer un agent à l’aide du client de conversation OpenAI 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."
)

Vous pouvez maintenant exécuter l’agent avec des informations textuelles et spécifier le format de sorties structurées à l’aide de la response_format clé dans le options dict :

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

Pour un format de réponse de modèle Pydantic, la réponse de l'agent contient les résultats structurés dans la propriété value comme une instance de modèle.

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

Utiliser un mappage de schéma JSON

Si vous avez déjà un schéma JSON en tant que mappage Python, transmettez ce schéma directement en tant que valeur response_format dans la dictée options. Dans ce mode, response.value contient la valeur JSON analysée (généralement une dict ou list) au lieu d’une instance de modèle 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']}")

Lors de la diffusion en continu, agent.run(..., stream=True) retourne un ResponseStream. Le finaliseur intégré du flux gère automatiquement l’analyse des sorties structurées. Vous pouvez donc itérer pour les mises à jour en temps réel, puis appeler get_final_response() pour obtenir le résultat analysé :

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

La même règle s’applique lorsque response_format est un mappage de schéma JSON : final_response.value contient du JSON analysé au lieu d’une instance de modèle Pydantic.

Si vous n’avez pas besoin de traiter les mises à jour de diffusion en continu individuelles, vous pouvez ignorer entièrement l’itération. get_final_response() Elle consomme automatiquement le flux :

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

Exemple complet

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

Étapes suivantes