[MAF的Agent管道详解-04]如何让LLM按照要求的结构输出数据?

针对IChatClient的结构化输出可以通过调用如下这些重载的GetResponseAsync<T>扩展方法来完成。具体的实现很简单,这些方法最终会利用指定或者默认的JsonSerializerOptions针对泛型参数T生成一个ChatResponseFormatJson对象,并作为ChatOptionsResponseFormat属性。这个ResponseFormat承载的JSON Schema将提供给LLM指导它按照定义的格式生成输出内容。当IChatClient接收到LLM的响应结果时,利用匹配的JsonSerializerOptions对响应结果进行反序列化后,封装成一个ChatResponse<T>对象返回给调用方。

csharp 复制代码
public static class ChatClientStructuredOutputExtensions
{
    public static Task<ChatResponse<T>> GetResponseAsync<T>(
        this IChatClient chatClient, 
        IEnumerable<ChatMessage> messages, 
        ChatOptions? options = null, bool? useJsonSchemaResponseFormat = null, 
        CancellationToken cancellationToken = default(CancellationToken));
    public static Task<ChatResponse<T>> GetResponseAsync<T>(
        this IChatClient chatClient, 
        string chatMessage, 
        ChatOptions? options = null, 
        bool? useJsonSchemaResponseFormat = null, 
        CancellationToken cancellationToken = default(CancellationToken));
    public static Task<ChatResponse<T>> GetResponseAsync<T>(
        this IChatClient chatClient, 
        ChatMessage chatMessage, 
        ChatOptions? options = null,
        bool? useJsonSchemaResponseFormat = null, 
        CancellationToken cancellationToken = default(CancellationToken));
    public static Task<ChatResponse<T>> GetResponseAsync<T>(
        this IChatClient chatClient, 
        string chatMessage, 
        JsonSerializerOptions serializerOptions, 
        ChatOptions? options = null, 
        bool? useJsonSchemaResponseFormat = null, 
        CancellationToken cancellationToken = default(CancellationToken));
    public static Task<ChatResponse<T>> GetResponseAsync<T>(
        this IChatClient chatClient, 
        ChatMessage chatMessage, 
        JsonSerializerOptions serializerOptions, 
        ChatOptions? options = null, 
        bool? useJsonSchemaResponseFormat = null, 
        CancellationToken cancellationToken = default(CancellationToken));
    public static async Task<ChatResponse<T>> GetResponseAsync<T>(
        this IChatClient chatClient, 
        IEnumerable<ChatMessage> messages, 
        JsonSerializerOptions serializerOptions, 
        ChatOptions? options = null, 
        bool? useJsonSchemaResponseFormat = null, 
        CancellationToken cancellationToken = default(CancellationToken));
}

1. 调用GetResponseAsync方法获取结构化输出

在如下的演示程序中,我们定义了一个描述个人基本信息的Profile类。我们利用OpenAIClient创建了一个IChatClient对象,并调用了GetResponseAsync<Profile>方法来从指定的一段文本中提取个人信息。在得到作为响应的Response<Profile>对象后,利用Result属性提取反序列化响应内容生成的Profile对象,调试断言表明这个Profile对象与我们预设的Profile对象是相等的。

csharp 复制代码
using dotenv.net;
using Microsoft.Extensions.AI;
using OpenAI;
using System.ClientModel;
using System.Diagnostics;
using System.Text.Json;

DotEnv.Load();
var model = Environment.GetEnvironmentVariable("MODEL")!;
var apiKey = Environment.GetEnvironmentVariable("API_KEY")!;
var openAIUrl = Environment.GetEnvironmentVariable("OPENAI_URL")!;
var openAIClient = new OpenAIClient(
    credential: new ApiKeyCredential(key: apiKey),
    options: new OpenAIClientOptions
    {
        Endpoint = new Uri(openAIUrl)
    });

var chatClient = openAIClient.GetResponsesClient().AsIChatClient(defaultModelId: model);
var profile = new Profile { Name = "张三" , Gender = Gender.Male, Age = 26 };
var prompt = "从下面内容中提取有效的个人信息:我叫张三,男,今年26岁";
var response1 = await chatClient.GetResponseAsync<Profile>(chatMessage: prompt);
Debug.Assert(profile == response1.Result);

public enum Gender
{
    Male,
    Female,
}
class Profile:IEquatable<Profile>
{
    public string? Name { get; set; }
    public Gender Gender { get; set; }
    public int Age { get; set; }
    public bool Equals(Profile? other)
    { 
        if(other is null) return false;
        return Name == other.Name && Gender == other.Gender && Age == other.Age;
    }
}

对于程序涉及的LLM调用,如下的两段JSON为发送的请求和接收的响应。可以看出针对Profile类型的JSON Schema被包含在发送给LLM的请求中,而LLM生成的响应内容则被成功地反序列化成了一个Profile对象。

json 复制代码
{
  "model": "gpt-5.2-chat",
  "text": {
    "format": {
      "type": "json_schema",
      "name": "Profile",
      "schema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "gender": {
            "type": "string",
            "enum": [
              "Male",
              "Female"
            ]
          },
          "age": {
            "type": "integer"
          }
        },
        "additionalProperties": false,
        "required": [
          "name",
          "gender",
          "age"
        ]
      }
    }
  },
  "input": [
    {
      "type": "message",
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "从下面内容中提取有效的个人信息:我叫张三,男,今年26岁"
        }
      ]
    }
  ]
}
json 复制代码
{
  "id": "resp_0962768f225127bc006a002eb6441881978b7b0150d57cdff9",
  "object": "response",
  "created_at": 1778396854,
  "status": "completed",
  "background": false,
  "completed_at": 1778396858,
  "content_filters": [
    {
      "blocked": false,
      "source_type": "prompt",
      "content_filter_raw": [],
      "content_filter_results": {
        "hate": {
          "filtered": false,
          "severity": "safe"
        },
        "sexual": {
          "filtered": false,
          "severity": "safe"
        },
        "violence": {
          "filtered": false,
          "severity": "safe"
        },
        "self_harm": {
          "filtered": false,
          "severity": "safe"
        }
      },
      "content_filter_offsets": {
        "start_offset": 0,
        "end_offset": 585,
        "check_offset": 0
      }
    },
    {
      "blocked": false,
      "source_type": "completion",
      "content_filter_raw": [],
      "content_filter_results": {
        "hate": {
          "filtered": false,
          "severity": "safe"
        },
        "sexual": {
          "filtered": false,
          "severity": "safe"
        },
        "violence": {
          "filtered": false,
          "severity": "safe"
        },
        "self_harm": {
          "filtered": false,
          "severity": "safe"
        }
      },
      "content_filter_offsets": {
        "start_offset": 0,
        "end_offset": 824,
        "check_offset": 0
      }
    }
  ],
  "error": null,
  "frequency_penalty": 0.0,
  "incomplete_details": null,
  "instructions": null,
  "max_output_tokens": null,
  "max_tool_calls": null,
  "model": "gpt-5.2-chat",
  "output": [
    {
      "id": "rs_0962768f225127bc006a002eb6c400819798b1da517e1a1eda",
      "type": "reasoning",
      "summary": []
    },
    {
      "id": "msg_0962768f225127bc006a002eba09b48197b34ee13d7b475432",
      "type": "message",
      "status": "completed",
      "content": [
        {
          "type": "output_text",
          "annotations": [],
          "logprobs": [],
          "text": "{\"name\":\"张三\",\"gender\":\"Male\",\"age\":26}"
        }
      ],
      "role": "assistant"
    }
  ],
  "parallel_tool_calls": true,
  "presence_penalty": 0.0,
  "previous_response_id": null,
  "prompt_cache_key": null,
  "prompt_cache_retention": null,
  "reasoning": {
    "effort": "medium",
    "summary": null
  },
  "safety_identifier": null,
  "service_tier": "default",
  "store": true,
  "temperature": 1.0,
  "text": {
    "format": {
      "type": "json_schema",
      "description": null,
      "name": "Profile",
      "schema": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "gender": {
            "type": "string",
            "enum": [
              "Male",
              "Female"
            ]
          },
          "age": {
            "type": "integer"
          }
        },
        "additionalProperties": false,
        "required": [
          "name",
          "gender",
          "age"
        ]
      },
      "strict": true
    },
    "verbosity": "medium"
  },
  "tool_choice": "auto",
  "tools": [],
  "top_logprobs": 0,
  "top_p": 0.85,
  "truncation": "disabled",
  "usage": {
    "input_tokens": 69,
    "input_tokens_details": {
      "cached_tokens": 0
    },
    "output_tokens": 217,
    "output_tokens_details": {
      "reasoning_tokens": 192
    },
    "total_tokens": 286
  },
  "user": null,
  "metadata": {}
}

2. 通过设置ChatOptions的ResponseFormat属性来获取结构化输出

GetResponseAsync<T>方法最终会利用指定或者默认的JsonSerializerOptions针对泛型参数T生成一个ChatResponseFormatJson对象,并作为ChatOptionsResponseFormat属性。当我们直接调用GetResponseAsync方法时,ChatOptionsResponseFormat属性返回的JSON Schema将作为调用LLM提示词的一部分,用于指导LLM生成符合结构的输出。上面的演示程序与下面这段其实是完全等效的。

csharp 复制代码
using dotenv.net;
using Microsoft.Extensions.AI;
using OpenAI;
using System.ClientModel;
using System.Diagnostics;
using System.Text.Json;

DotEnv.Load();
var model = Environment.GetEnvironmentVariable("MODEL")!;
var apiKey = Environment.GetEnvironmentVariable("API_KEY")!;
var openAIUrl = Environment.GetEnvironmentVariable("OPENAI_URL")!;
var openAIClient = new OpenAIClient(
    credential: new ApiKeyCredential(key: apiKey),
    options: new OpenAIClientOptions
    {
        Endpoint = new Uri(openAIUrl)
    });

var chatClient = openAIClient.GetResponsesClient().AsIChatClient(defaultModelId: model);
var options = new ChatOptions
{
    ResponseFormat = ChatResponseFormat.ForJsonSchema<Profile>()
};
var response = await chatClient.GetResponseAsync(chatMessage: prompt, options: options);
var profile2 = JsonSerializer.Deserialize<Profile>(response1.Messages.Last().Text, AIJsonUtilities.DefaultOptions);
Debug.Assert(profile == new Profile { Name = "张三", Gender = Gender.Male, Age = 26 });

public enum Gender
{
    Male,
    Female,
}
class Profile:IEquatable<Profile>
{
    public string? Name { get; set; }
    public Gender Gender { get; set; }
    public int Age { get; set; }
    public bool Equals(Profile? other)
    { 
        if(other is null) return false;
        return Name == other.Name && Gender == other.Gender && Age == other.Age;
    }
}
相关推荐
李姆斯7 小时前
给 AI Agent 造好用的锤子:复杂系统的 Tool 抽象设计
aigc·agent
HIT_Weston8 小时前
92、【Agent】【OpenCode】edit 工具提示词
人工智能·agent·opencode
Swift社区8 小时前
推动AI领导力:构建全栈开放的智能生态
人工智能·ai
不会编程的懒洋洋9 小时前
VisionPro 中 几何相交工具 Geometry-Intersection
图像处理·笔记·c#·视觉检测·机器视觉·visionpro
@蔓蔓喜欢你10 小时前
团队协作工具:提升开发效率的利器
人工智能·ai
阿部多瑞 ABU10 小时前
ADRO实战:用渐进式诱导“聊出”TATP完整合成路线——某国产大模型红队测试实录
安全·ai
ToBeTuring11 小时前
openclaw和claude code的配置文件参考
ai·claude code·openclaw
不会编程的懒洋洋11 小时前
VisionPro 中 图像预处理工具
图像处理·笔记·c#·视觉检测·visionpro
Hexian258012 小时前
SpringAI+RAG
java·spring·ai