还在手写JSON调教大模型?.NET 9有新玩法
引言
.NET 9 迎来了一项备受期待的功能更新:对JSON Schema的原生支持。这一新增功能极大地简化了JSON Schema的生成与使用。JSON Schema作为一种描述JSON数据结构的标准格式,能够帮助我们有效地验证数据结构和类型。尤其在与大语言模型(LLM)进行交互的场景中,它扮演着至关重要的角色,可以精确定义模型输入与输出的数据格式,从而确保通信的准确性和可靠性。

.NET 9 的新利器:JsonSchemaExporter
让我们从一个简单的例子开始。在.NET 9中,我们可以利用 System.Text.Json 命名空间下的新工具 JsonSchemaExporter,轻松地将一个C#类转换为对应的JSON Schema。
首先,我们定义一个名为 Book 的C#类。这个类包含三个属性:Title、Author 和 PublishYear。其中,Title 是一个必需的字符串属性,Author 是一个可空字符串属性,而 PublishYear 是一个整数。
// 定义一个名为 Book 的类
public class Book
{
// 必须的字符串属性 Title
public required string Title { get; set; }
// 可选的字符串属性 Author,允许为 null
public string? Author { get; set; }
// 整数属性 PublishYear
public int PublishYear { get; set; }
}
接下来,在主程序中,我们只需调用 JsonSchemaExporter.GetJsonSchemaAsNode 方法,并传入我们的 Book 类型,即可生成其JSON Schema。
class Program
{
static void Main()
{
// 使用 JsonSchemaExporter 为 .NET 类型生成 JSON schema
var schema = JsonSchemaExporter.GetJsonSchemaAsNode(
JsonSerializerOptions.Default,
typeof(Book)
);
// 输出生成的 JSON schema
Console.WriteLine(schema);
}
}
运行上述代码,我们将得到以下JSON Schema输出:
{
"type": [
"object",
"null"
],
"properties": {
"Title": {
"type": [
"string",
"null"
]
},
"Author": {
"type": [
"string",
"null"
]
},
"PublishYear": {
"type": "integer"
}
},
"required": [
"Title"
]
}
这个特性使得我们能够以标准化的JSON Schema形式来表示一个.NET类型,这对于实现远程过程调用(RPC)或与OpenAI、Google Gemini等AI服务进行集成时非常有用。
实战:结合OpenAI实现结构化输出
我们知道,从 gpt-4o-0806 开始,OpenAI便支持了通过JSON Schema来约束模型的输出格式。这个功能极大地便利了开发者,尤其是在需要模型返回复杂数据结构时。然而,手动编写和维护这些JSON Schema既繁琐又容易出错。现在,借助.NET 9的 JsonSchemaExporter,这个过程变得前所未有的简单。
注意:以下示例需要依赖
Azure.AI.OpenAINuGet包 2.2.0 或更高版本。
让我们来看一个实际的例子。假设我们需要让大模型分析一段关于篮子里放球、取球的描述,并返回一个包含各种颜色球类数量的结构化数据。
首先,定义我们期望的输出数据结构 BallCounts 类:
public class BallCounts
{
public string? Think { get; init; } // 模型的思考过程
public int Red { get; init; }
public int Blue { get; init; }
public int Yellow { get; init; }
public int Green { get; init; }
public int Confidence { get; init; } // 模型对答案的置信度
}
然后,在调用模型时,我们可以将这个 BallCounts 类型动态生成JSON Schema,并直接传递给 ChatCompletionOptions 的 ResponseFormat。
// 初始化OpenAI客户端
OpenAIClient api = new AzureOpenAIClient(new Uri($"https://{Util.GetPassword("azure-ai-resource")}.openai.azure.com/"), new AzureKeyCredential(Util.GetPassword("azure-ai-key")));
ChatClient cc = api.GetChatClient("gpt-4o");
// 准备请求
var result = await cc.CompleteChatAsync(
[
new SystemChatMessage("你是人工智能助理"),
new UserChatMessage("""
有个空篮子,放里面放1个红色球,再往里面放1个蓝色球,再把红色球和黄色球拿出来,再放2个绿色球,请问这个篮子里面有几个球?分别是什么颜色?
"""),
],
new ChatCompletionOptions()
{
Temperature = 0,
// 关键点:动态生成并设置JSON Schema
ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(
nameof(BallCounts),
BinaryData.FromBytes(
JsonSerializer.SerializeToUtf8Bytes(
JsonSchemaExporter.GetJsonSchemaAsNode(
JsonSerializerOptions.Default,
typeof(BallCounts),
new JsonSchemaExporterOptions()
{
// 确保非可空引用类型不被视为可空
TreatNullObliviousAsNonNullable = true
}
)
)
)
)
});
// 反序列化并输出结果
var ballCountsResult = JsonSerializer.Deserialize<BallCounts>(result.Value.Content[0].Text, JsonSerializerOptions.Default);
Console.WriteLine(JsonSerializer.Serialize(ballCountsResult, new JsonSerializerOptions { WriteIndented = true }));
模型返回的将是严格符合我们C#类结构的JSON:
{
"Think": "Let's break down the steps:\n\n1. Start with an empty basket.\n2. Add 1 red ball. (Basket: 1 red)\n3. Add 1 blue ball. (Basket: 1 red, 1 blue)\n4. Remove the red ball. (Basket: 1 blue)\n5. Remove the yellow ball. (Since there is no yellow ball in the basket, this step doesn't change anything. Basket: 1 blue)\n6. Add 2 green balls. (Basket: 1 blue, 2 green)\n\nSo, the basket contains 3 balls: 1 blue and 2 green.",
"Red": 0,
"Blue": 1,
"Yellow": 0,
"Green": 2,
"Confidence": 100
}
随心所欲,轻松扩展
这个方法的强大之处在于其灵活性。如果我们想调整输出结构,比如增加两种颜色(紫色和橙色),并将 Confidence 属性改为布尔类型的 Sure,我们 只需要修改C#类定义 即可。
public class BallCounts
{
public string? Think { get; init; }
public int Red { get; init; }
public int Blue { get; init; }
public int Yellow { get; init; }
public int Green { get; init; }
public int Purple { get; init; } // 新增
public int Orange { get; init; } // 新增
public bool Sure { get; init; } // 修改
}
我们无需改动任何调用逻辑,只需重新运行程序,模型就会自动适应新的 BallCounts 结构,输出的JSON会神奇地包含所有新的颜色属性,并且 Sure 属性也被正确地处理为布尔值。
{
"Think": "Let's break down the steps:\n\n1. Start with an empty basket.\n2. Add 1 red ball.\n3. Add 1 blue ball.\n4. Remove the red ball.\n5. Remove the yellow ball (though there was no yellow ball added, so this step doesn't change the count).\n6. Add 2 green balls.\n\nAfter these steps, the basket contains:\n- 1 blue ball\n- 2 green balls\n\nTotal: 3 balls.",
"Red": 0,
"Blue": 1,
"Yellow": 0,
"Green": 2,
"Purple": 0,
"Orange": 0,
"Sure": true
}
可见,模型完美地适应了新的数据契约。这种开发体验简直不要太方便!
对比:新方法 vs. 传统方法
有趣的是,在OpenAI官方的.NET SDK文档中,关于结构化响应的示例 (openai-dotnet/README.md) 并没有采用 JsonSchemaExporter,而是手动编写了一大段JSON Schema字符串。这可能是因为官方文档旨在展示其库的原始能力,而非特定于.NET 9的便捷特性。
这是官网的示例代码,我们可以看到它相当繁琐:
// ...
ChatCompletionOptions options = new()
{
ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(
jsonSchemaFormatName: "math_reasoning",
jsonSchema: BinaryData.FromBytes("""
{
"type": "object",
"properties": {
"steps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": { "type": "string" },
"output": { "type": "string" }
},
"required": ["explanation", "output"],
"additionalProperties": false
}
},
"final_answer": { "type": "string" }
},
"required": ["steps", "final_answer"],
"additionalProperties": false
}
"""u8.ToArray()),
jsonSchemaIsStrict: true)
};
// ...
与我们前面的方法相比,这种硬编码Schema的方式不仅工作量大,而且在需求变更时极难维护。而.NET 9的 JsonSchemaExporter 真正实现了“定义一次,处处使用”的优雅编程范式。
值得一提的是,据我了解,Google Gemini、Ollama等其他主流模型服务提供商也已支持JSON Schema格式化。这意味着您学到的这项技巧具有广泛的适用性。
总结
.NET 9中引入的 JsonSchemaExporter 无疑是.NET开发者与大语言模型协作时的一大福音。它将繁琐、易错的JSON Schema手动编写过程,转变为基于C#类型定义的自动化、强类型、可维护的流程。

这项功能特别好,它是将基于概率、本质不稳定的语言模型进行工程化,提升其输出结果稳定性和可靠性的关键手段。强烈建议您在自己的项目中,尤其是在与大模型交互时,全面拥抱JSON Schema来定义数据契约。借助.NET 9,我们现在可以无比轻松地生成和使用JSON Schema,从而显著提高代码的可读性、可维护性和整体开发效率。
感谢阅读,欢迎评论点赞!
欢迎加入「.NET骚操作」技术交流群:495782587
还在手写JSON调教大模型?.NET 9有新玩法的更多相关文章
- Kubernetes实战指南(三十三):都0202了,你还在手写k8s的yaml文件?
目录 1. k8s的yaml文件到底有多复杂 2. 基于图形化的方式自动生成yaml 2.1 k8s图形化管理工具Ratel安装 2.2 使用Ratel创建生成yaml文件 2.2.1 基本配置 2. ...
- 大数据分析新玩法之Kusto宝典 - 新书发布,免费发行
我很高兴地跟大家分享,我在元旦期间编写的一本新书今天上线,并且免费发行,大家可以随时通过 https://kusto.book.xizhang.com 这个地址访问,也可以下载 PDF 的版本, 这本 ...
- C#9.0 终于来了,带你一起解读Pattern matching 和 nint 两大新特性玩法
一:背景 1. 讲故事 上一篇跟大家聊到了Target-typed new 和 Lambda discard parameters,看博客园和公号里的阅读量都达到了新高,甚是欣慰,不管大家对新特性是多 ...
- Odoo 的库存管理与OpenERP之前的版本有了很大的不同,解读Odoo新的WMS模块中的新特性
原文来自:http://shine-it.net/index.php/topic,16409.0.html 库存移动(Stock Move)新玩法Odoo的库存移动不仅仅是存货在两个“存货地点”之间的 ...
- 3. 懂了这些,方敢在简历上说会用Jackson写JSON
你必须非常努力,才能看起来毫不费力.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众 ...
- 还在用Json完成Ajax,改用Beetl吧
原文链接:https://blog.csdn.net/xiandafu/article/details/44216905 作者:Beetl作者,闲大赋 浏览器通过AJAX,服务器返回json数据,无刷 ...
- 2. 妈呀,Jackson原来是这样写JSON的
没有人永远18岁,但永远有人18岁.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众 ...
- 华为高级研究员谢凌曦:下一代AI将走向何方?盘古大模型探路之旅
摘要:为了更深入理解千亿参数的盘古大模型,华为云社区采访到了华为云EI盘古团队高级研究员谢凌曦.谢博士以非常通俗的方式为我们娓娓道来了盘古大模型研发的"前世今生",以及它背后的艰难 ...
- 文心大模型api使用
文心大模型api使用 首先,我们要获取硅谷社区的连个key 复制两个api备用 获取Access Token 获取access_token示例代码 之后就会输出 作文创作 作文创作:作文创作接口基于文 ...
- 千亿参数开源大模型 BLOOM 背后的技术
假设你现在有了数据,也搞到了预算,一切就绪,准备开始训练一个大模型,一显身手了,"一朝看尽长安花"似乎近在眼前 -- 且慢!训练可不仅仅像这两个字的发音那么简单,看看 BLOOM ...
随机推荐
- 【笔记】Git|将git仓库中所有的 commit 合成一个,清空所有 git 提交记录
在对代码进行开源时,我们往往并不希望代码开发过程中的提交记录被其他人看到,因为提交的过程中往往会涵盖一些敏感信息.因此会存在 将仓库中所有 commit 合成一个 的需求. 直觉上,往往会用 reba ...
- AI法律助手:打造普惠法律服务的未来
当法律服务遇见人工智能,普通人的维权之路将不再艰难 当法律服务成为奢侈品,AI或许是唯一出路 2025年的一个深夜,我刷着手机,一条新闻让我停下了滑动的手指: "某平台家装工人因合同纠纷讨薪 ...
- 【TensorRT 10 C++ inference example】最新版本TensorRT c++ api的推理部署教程
TensorRT是英伟达推出的部署框架,我的工作经常需要封装我的AI算法和模型给到桌面软件使用,那么tensorRT对我来说就是不二之选.TensorRT和cuda深度绑定,在c++版本的推理教程 ...
- python正则表达式中re.M,re.S,re.I的作用
参考:https://www.cnblogs.com/feifeifeisir/p/10627474.html 正则表达式可以包含一些可选标志修饰符来控制匹配的模式.修饰符被指定为一个可选的标志.多个 ...
- .NET外挂系列:4. harmony 中补丁参数的有趣玩法(上)
一:背景 1. 讲故事 前面几篇我们说完了 harmony 的几个注入点,这篇我们聚焦注入点可接收的几类参数的解读,非常有意思,在.NET高级调试 视角下也是非常重要的,到底是哪些参数,用一张表格整理 ...
- 推荐一个Elasticsearch ES可视化客户端工具:ES-King,支持win、mac、linux
ES-King:开源免费,一个现代.实用的ES GUI客户端,支持多平台. 下载地址:https://github.com/Bronya0/ES-King 我之前开源的kafka客户端kafka-ki ...
- AtCoder Beginner Contest 369 补题记录
A - 369 题意: 给定A和B,求有多少个x可以和A,B构成等差数列 思路: 分三种情况讨论 A == B 则x不得不与A和B想等 x位于A和B中间 只有B - A 为偶数才有这种情况存在 x位于 ...
- Linux服务器(CentOS/Ubuntu)接口Bond模式详解、配置指南及交换机对应接口的配置示例
以下是关于Linux服务器(CentOS/Ubuntu)与交换机对接的接口Bond模式详解.配置指南及交换机配置示例(思科/华为/华三) 的全面说明: 一.Linux Bonding 模式对比 模式 ...
- ArkUI-X与Android消息通信
平台桥接用于客户端(ArkUI)和平台(Android或iOS)之间传递消息,即用于ArkUI与平台双向数据传递.ArkUI侧调用平台的方法.平台调用ArkUI侧的方法.本文主要介绍Android平台 ...
- 在MySQL中悲观锁及乐观锁的应用
本文由 ChatMoney团队出品 在数据库管理系统中,锁机制是保证数据一致性和并发控制的重要手段.MySQL,作为广泛使用的数据库系统之一,提供了多种锁策略来处理并发访问时可能引发的数据不一致性问题 ...