Создание структурированных выходных данных с помощью агентов

На этом шаге руководства показано, как создавать структурированные выходные данные с агентом, где агент построен на службе завершения чата OpenAI Azure.

Это важно

Не все типы агентов поддерживают структурированные выходные данные в собственном коде. Объект ChatClientAgent поддерживает структурированные выходные данные при использовании с совместимыми чат-клиентами.

Необходимые условия

Предварительные требования и установка пакетов NuGet см. в разделе "Создание и запуск простого агента " в этом руководстве.

Определение типа структурированных выходных данных

Сначала определите тип, представляющий структуру выходных данных, которые требуется от агента.

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

Создать агента

Создайте ChatClientAgent с помощью клиента Azure AI Projects.

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 Agent.

Этот подход применим, если:

  • Структурированный тип выходных данных не известен во время компиляции.
  • Схема представлена как необработанный 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, которая полезна при отсутствии соответствующего типа .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.

На этом шаге руководства показано, как создавать структурированные выходные данные с агентом, где агент построен на службе завершения чата OpenAI Azure.

Это важно

Не все типы агентов поддерживают структурированные выходные данные. Объект Agent поддерживает структурированные выходные данные при использовании с совместимыми чат-клиентами.

Необходимые условия

Для ознакомления с предварительными требованиями и установкой пакетов см. шаг "Создание и запуск простого агента" в этом руководстве.

Создание агента со структурированными выходными данными

Основанная на платформе любого клиента чата Agent, который поддерживает структурированные выходные данные. Ключ Agent в дикте response_format используется options для указания требуемой выходной схемы.

При запуске агента можно указать следующее:

  • Модель Pydantic, определяющая структуру ожидаемых выходных данных.
  • Сопоставление схемы JSON (dict) при необходимости анализа JSON без определения класса модели.

Вы можете передать options словарь во время выполнения через agent.run(..., options={"response_format": ...}), или задать его во время создания агента с помощью default_options словаря.

Поддерживаются различные форматы ответов на основе базовых возможностей клиента чата.

В первом примере создается агент, который создает структурированные выходные данные в виде объекта JSON, соответствующего схеме модели Pydantic.

Сначала определите модель Pydantic, представляющую структуру выходных данных, которые требуется от агента:

from pydantic import BaseModel

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

Теперь вы можете создать агент с помощью клиента openAI Chat 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."
)

Теперь агент можно запустить с некоторыми текстовыми сведениями и указать структурированный формат выходных данных с помощью 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())

Структурированные выходные данные

Агенты Go поддерживают структурированный вывод через параметр agent.WithStructuredOutput. Определите структуру Go, и фреймворк автоматически сгенерирует JSON-схему и десериализует ответ.

Определение типа вывода

type PersonInfo struct {
    Name       string `json:"name"`
    Age        int    `json:"age"`
    Occupation string `json:"occupation"`
}

Запрос структурированных выходных данных

Используйте универсальный вспомогательный метод для вызова агента и десериализации ответа:

import (
    "context"
    "fmt"

    "github.com/microsoft/agent-framework-go/agent"
)

func runFor[T any](ctx context.Context, a *agent.Agent, message string, opts ...agent.Option) (T, error) {
    var v T
    opts = append(opts, agent.WithStructuredOutput(&v), agent.Stream(false))
    for _, err := range a.RunText(ctx, message, opts...) {
        if err != nil {
            return v, err
        }
    }
    return v, nil
}

person, err := runFor[PersonInfo](ctx, a,
    "Please provide information about John Smith, who is a 35-year-old software engineer.")
fmt.Println("Name:", person.Name)
fmt.Println("Age:", person.Age)

Указание формата ответа на уровне агента

Вы также можете задать формат отклика в конфигурации агента, чтобы все запуски производили структурированные выходные данные:

import "github.com/microsoft/agent-framework-go/agent/format/jsonformat"

a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
    Instructions: "You are a helpful assistant.",
    Config: agent.Config{
        RunOptions: []agent.Option{
            agent.WithResponseFormat(jsonformat.MustFor[PersonInfo]()),
        },
    },
})

Подсказка

См. полный пример, чтобы увидеть полностью рабочий пример.

Дальнейшие действия