Agent管道详解-04]如何让LLM按照要求的结构输出数据?
针对IChatClient的结构化输出可以通过调用如下这些重载的GetResponseAsyncT扩展方法来完成。具体的实现很简单这些方法最终会利用指定或者默认的JsonSerializerOptions针对泛型参数T生成一个ChatResponseFormatJson对象并作为ChatOptions的ResponseFormat属性。这个ResponseFormat承载的JSON Schema将提供给LLM指导它按照定义的格式生成输出内容。当IChatClient接收到LLM的响应结果时利用匹配的JsonSerializerOptions对响应结果进行反序列化后封装成一个ChatResponseT对象返回给调用方。public static class ChatClientStructuredOutputExtensions { public static TaskChatResponseT GetResponseAsyncT( this IChatClient chatClient, IEnumerableChatMessage messages, ChatOptions? options null, bool? useJsonSchemaResponseFormat null, CancellationToken cancellationToken default(CancellationToken)); public static TaskChatResponseT GetResponseAsyncT( this IChatClient chatClient, string chatMessage, ChatOptions? options null, bool? useJsonSchemaResponseFormat null, CancellationToken cancellationToken default(CancellationToken)); public static TaskChatResponseT GetResponseAsyncT( this IChatClient chatClient, ChatMessage chatMessage, ChatOptions? options null, bool? useJsonSchemaResponseFormat null, CancellationToken cancellationToken default(CancellationToken)); public static TaskChatResponseT GetResponseAsyncT( this IChatClient chatClient, string chatMessage, JsonSerializerOptions serializerOptions, ChatOptions? options null, bool? useJsonSchemaResponseFormat null, CancellationToken cancellationToken default(CancellationToken)); public static TaskChatResponseT GetResponseAsyncT( this IChatClient chatClient, ChatMessage chatMessage, JsonSerializerOptions serializerOptions, ChatOptions? options null, bool? useJsonSchemaResponseFormat null, CancellationToken cancellationToken default(CancellationToken)); public static async TaskChatResponseT GetResponseAsyncT( this IChatClient chatClient, IEnumerableChatMessage messages, JsonSerializerOptions serializerOptions, ChatOptions? options null, bool? useJsonSchemaResponseFormat null, CancellationToken cancellationToken default(CancellationToken)); }1. 调用GetResponseAsync方法获取结构化输出在如下的演示程序中我们定义了一个描述个人基本信息的Profile类。我们利用OpenAIClient创建了一个IChatClient对象并调用了GetResponseAsyncProfile方法来从指定的一段文本中提取个人信息。在得到作为响应的ResponseProfile对象后利用Result属性提取反序列化响应内容生成的Profile对象调试断言表明这个Profile对象与我们预设的Profile对象是相等的。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.GetResponseAsyncProfile(chatMessage: prompt); Debug.Assert(profile response1.Result); public enum Gender { Male, Female, } class Profile:IEquatableProfile { 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对象。{ 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岁 } ] } ] }{ 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属性来获取结构化输出GetResponseAsyncT方法最终会利用指定或者默认的JsonSerializerOptions针对泛型参数T生成一个ChatResponseFormatJson对象并作为ChatOptions的ResponseFormat属性。当我们直接调用GetResponseAsync方法时ChatOptions的ResponseFormat属性返回的JSON Schema将作为调用LLM提示词的一部分用于指导LLM生成符合结构的输出。上面的演示程序与下面这段其实是完全等效的。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.ForJsonSchemaProfile() }; var response await chatClient.GetResponseAsync(chatMessage: prompt, options: options); var profile2 JsonSerializer.DeserializeProfile(response1.Messages.Last().Text, AIJsonUtilities.DefaultOptions); Debug.Assert(profile new Profile { Name 张三, Gender Gender.Male, Age 26 }); public enum Gender { Male, Female, } class Profile:IEquatableProfile { 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; } }标签: Agent, Agent管道, AI, MAF免责声明本内容来自平台创作者博客园系信息发布平台仅提供信息存储空间服务。好文要顶 关注我 收藏该文 微信分享Artech粉丝 - 10569 关注 - 0推荐博客加关注30« 上一篇 [MAF的Agent管道详解-03]连接LLM的IChatClient对象» 下一篇 [MAF的Agent管道详解-05]对话历史的持久化和输入输出的增强posted 2026-05-29 08:30 Artech 阅读(196) 评论(1) 收藏 举报