惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
A
Arctic Wolf
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Threatpost
P
Proofpoint News Feed
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Vulnerabilities – Threatpost
V
V2EX
Webroot Blog
Webroot Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Privacy & Cybersecurity Law Blog
P
Privacy International News Feed
T
Tor Project blog
P
Proofpoint News Feed
T
Tailwind CSS Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Vercel News
Vercel News
Security Archives - TechRepublic
Security Archives - TechRepublic
MongoDB | Blog
MongoDB | Blog
T
Troy Hunt's Blog
Google DeepMind News
Google DeepMind News
NISL@THU
NISL@THU
C
CERT Recently Published Vulnerability Notes
W
WeLiveSecurity
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
GbyAI
GbyAI
Y
Y Combinator Blog
T
Threat Research - Cisco Blogs
S
Security Affairs
Google Online Security Blog
Google Online Security Blog
S
Securelist
Spread Privacy
Spread Privacy
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
C
Cybersecurity and Infrastructure Security Agency CISA
爱范儿
爱范儿
H
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Scott Helme
Scott Helme
The Cloudflare Blog
T
The Blog of Author Tim Ferriss
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
B
Blog RSS Feed
AWS News Blog
AWS News Blog

博客园 - Artech

[MAF预定义的AIContextProvider-05]CompactionProvider——采用多种策略压缩对话历史 [MAF预定义的AIContextProvider-04]Mem0Provider——长期记忆基于的云端解决方案 [MAF预定义的AIContextProvider-03]ChatHistoryMemoryProvider——赋予Agent从经验中学习的能力 [MAF预定义的AIContextProvider-02]AgentSkillsProvider——将Agent Skills引入MAF [MAF预定义的AIContextProvider-01]TextSearchProvider——RAG在MAF中的实现 [MAF预定义ChatClient中间件-09]MessageInjectingChatClient-赋予工具消息注入的能力 [MAF预定义ChatClient中间件-08]OpenTelemetryChatClient-实现链路跟踪和性能监控 [MAF预定义ChatClient中间件-07]PerServiceCallChatHistoryPersistingChatClient——基于ReAct循环的一步一存档 [MAF预定义ChatClient中间件-06]利用ImageGeneratingChatClient开发专业图片生成Agent [MAF预定义ChatClient中间件-05]动态修改ChatOptions和请求消息 [MAF预定义ChatClient中间件-04]ReducingChatClient——精减对话历史又不丢失基本语义 [MAF预定义ChatClient中间件-03]CachingChatClient——利用缓存省钱省时间 [MAF预定义ChatClient中间件-02]FunctionInvokingChatClient——实现ReAct循环和人机交互的大功臣 [MAF预定义ChatClient中间件-01]LoggingChatClient——在调用LLM前后输出日志 [MAF的Agent管道详解-07]利用AIAgent中间件构建Agent管道 [MAF的Agent管道详解-06]ChatClientAgent对IChatClient和输入输出增强管道的整合 [MAF的Agent管道详解-06]ChatClientAgent对IChatClient和输入输出增强管道的整合 - Artech [MAF的Agent管道详解-05]对话历史的持久化和输入输出的增强 [MAF的Agent管道详解-03]连接LLM的IChatClient对象 [MAF的Agent管道详解-02]IChatClient管道如何完美连接大模型? [MAF的Agent管道详解-01]塑智能体边界,从AIAgent抽象类开始 [对比学习LangChain和MAF-04]针对消息的设计 [对比学习LangChain和MAF-03]完全不同的Agent设计哲学 [对比学习LangChain和MAF-02]基本编程模式的差异(下篇) [对比学习LangChain和MAF-01]基本编程模式的差异(上篇) 这是一篇测试文章 我所理解的Python元模型 除了按值和引用,方法参数的第三种传递方式 方法的三种调用形式 可以调用Null的实例方法吗? 自定义Key类型的字典无法序列化的N种解决方案 为什么ASP.NET Core的路由处理器可以使用一个任意类型的Delegate 深入解析ASP.NET Core MVC的模块化设计[下篇] 深入解析ASP.NET Core MVC的模块化设计[上篇] ASP.NET Core MVC应用模型的构建[4]: Action的选择 ASP.NET Core MVC应用模型的构建[3]: Controller的收集 ASP.NET Core MVC应用模型的构建[2]: 定制应用模型 ASP.NET Core MVC应用模型的构建[1]: 应用的蓝图 基于HTTP2/3的流模式消息交换如何实现? 编写高效的代码,你应该了解Array、Memory、ReadOnlySequence . . . WebAssembly核心编程[4]: Memory WebAssembly核心编程[3]: Module 与 Instance WebAssembly核心编程[2]:类型系统 WebAssembly核心编程[1]:wasm模块实例化的N种方式 WebAssembly入门笔记[4]:利用Global传递全局变量 WebAssembly入门笔记[3]:利用Table传递引用
[MAF的Agent管道详解-04]如何让LLM按照要求的结构输出数据?
Artech · 2026-05-29 · via 博客园 - Artech

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

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对象是相等的。

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对象。

{
  "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属性来获取结构化输出

GetResponseAsync<T>方法最终会利用指定或者默认的JsonSerializerOptions针对泛型参数T生成一个ChatResponseFormatJson对象,并作为ChatOptionsResponseFormat属性。当我们直接调用GetResponseAsync方法时,ChatOptionsResponseFormat属性返回的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.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;
    }
}