.NET+AI | MEAI | 获取推理内容新姿势
MEAI 最近更新了 10.4.0 版本,引入了TextReasoningContent 类型用于表述推理内容,基于该类型,可以更方便的获取推理字段。
以下内容选自我精心打造的《.NET+AI | 智能体开发进阶》课程,如需系统学习,不妨阅读原文了解详情。
启用推理模型与获取思考过程
在 07-meai-chatoptions-extension.ipynb[1] 中,我们介绍了 ChatOptions 的扩展点 RawRepresentationFactory,可以用来配置推理模型的特有参数。
本节课将深入展示如何:
-
• 🧠 启用推理模型的思考模式
-
• 📤 非流式获取推理内容(Reasoning Content)
-
• 🔄 流式获取推理内容(Streaming Reasoning Content)
什么是推理内容(Reasoning Content)?
国内的 DeepSeek、千问 AI 等推理模型支持混合思考模式,能够输出:
-
1. 推理过程(Reasoning Content):模型的内部思考过程
-
2. 最终答案(Message Content):面向用户的最终回答
这两部分内容分开返回,帮助开发者:
-
• ✅ 理解模型的推理逻辑
-
• ✅ 调试和优化提示词
-
• ✅ 在 UI 中展示"思考中..."效果
课程目标
-
• 掌握如何通过
RawRepresentationFactory启用推理模式 -
• 学习使用 JsonPatch 动态设置提供商参数
-
• 实现流式和非流式两种方式获取推理内容
推理内容流转示意图
enable_thinking=true
是
否
用户请求
IChatClient
启用推理?
推理模型
生成推理内容
生成最终答案
流式响应?
StreamingChatResponseUpdate
OfType
OfType
response.Messages.Single
OfType
OfType
关键概念:
-
• 📤 推理内容(Reasoning Content):模型内部思考,不直接展示给最终用户
-
• 💬 消息内容(Message Content):最终回答,面向用户
步骤 1:环境准备
首先导入必要的帮助类和依赖包:
#!import ../helper/AIClientHelper.cs
using Microsoft.Extensions.AI;
using OpenAI.Chat;
Console.WriteLine("✅ 依赖包加载完成");
步骤 2:启用推理模式
要启用推理模式,需要通过 RawRepresentationFactory 设置底层提供商的参数。
工作原理
代码实现
阿里百炼平台提供了enable_thinking参数,用于控制在使用混合思考(回复前既可思考也可不思考)模型时,是否开启思考模式。
-
• 千问系列模型:适用于 Qwen3 、Qwen3-Omni-Flash、Qwen3-VL模型,默认不开启思考模式。阿里百炼千问模型API文档[2]
-
• DeepSeek模型:适用于 deepseek-v3.2、deepseek-v3.2-exp与deepseek-v3.1。默认不开启思考模式。deepseek-v3.2是DeepSeek推出的首个将思考融入工具使用的模型,同时支持思考模式与非思考模式的工具调用。阿里百炼DeepSeek模型API文档[3]
关于阿里百炼平台更多推理模型的默认行为和参数设置,可参考阿里百炼-深度思考[4]
对于DeepSeek 平台,deepseek-chat 和 deepseek-reasoner 都已经升级为 DeepSeek-V3.2。deepseek-chat 对应 DeepSeek-V3.2 的非思考模式,deepseek-reasoner 对应 DeepSeek-V3.2 的思考模式,意味着无需手动设置
enable_thinking参数即可使用对应模式。更多信息请参考DeepSeek-API文档[5]。
下面使用阿里百炼平台创建一个带有推理模式的 ChatOptions:
#pragma warning disable SCME0001 // RawRepresentationFactory 使用警告
// 创建启用推理模式的 ChatOptions
var reasoningOptions = new ChatOptions()
{
RawRepresentationFactory = (client) =>
{
// 创建底层 OpenAI SDK 的 ChatCompletionOptions
var options = new ChatCompletionOptions();
// 使用 JsonPatch 设置 enable_thinking 参数
options.Patch.Set("$.enable_thinking"u8, true);
return options;
}
};
Console.WriteLine("✅ 推理模式配置完成");
new
{
配置说明 = "enable_thinking=true",
用途 = "启用模型推理过程输出",
适用模型 = "DeepSeek, 千问等推理模型"
}.Display();
代码说明:
-
1.
#pragma warning disable SCME0001:禁用编译器警告,因为RawRepresentationFactory是高级功能 -
2.
RawRepresentationFactory:工厂方法,用于创建底层 SDK 的选项对象 -
3.
ChatCompletionOptions:OpenAI SDK 的原生配置类 -
4.
Patch.Set():使用 JsonPatch 设置参数,u8后缀表示 UTF-8 字节数组 -
5.
enable_thinking:推理模型的特有参数,用于启用思考输出
步骤 3:创建推理模型客户端
使用 AIClientHelper 创建支持推理的 AI 客户端。本示例使用 DeepSeek 的 deepseek-chat 模型:
// 使用阿里百炼平台的deepseek-v3.2-exp模型(支持推理模式)
var qwenClient = AIClientHelper.GetQwenClient(true);
var chatClient = qwenClient.GetChatClient("deepseek-v3.2-exp").AsIChatClient();
Console.WriteLine("✅ DeepSeek 推理模型客户端创建完成");
new
{
提供商 = "DeepSeek",
模型 = "deepseek-v3.2-exp",
功能 = "支持推理内容输出"
}.Display();
步骤 4:非流式获取推理内容
使用 GetResponseAsync 获取完整的响应,包括推理内容和最终答案。
Console.WriteLine("⏱️ 开始发送请求...\n");
// 发送请求,传入启用推理的 ChatOptions
var response = await chatClient.GetResponseAsync("将大象装进冰箱需要几步?", reasoningOptions);
Console.WriteLine("✅ 响应接收完成\n");
// 以下两种方式均可提取大模型的回复内容:
var message = response.Messages.Single();
message.Display();
var text = message.Contents.OfType<TextContent>().Single();
text.Display();
// 显示基本响应信息
new
{
消息内容 = response.Text,
Token使用 = response.Usage?.TotalTokenCount,
完成原因 = response.FinishReason
}.Display();
提取推理内容
要获取推理内容,可以直接在响应消息中提取 TextReasoningContent 类型的内容:
⚠️ 重要:: 推理内容仅在启用 enable_thinking 时才会返回
// 提取并显示推理内容
var reasoning = message.Contents.OfType<TextReasoningContent>().Single();
reasoning.Display();
步骤 5:流式获取推理内容
在实际应用中,我们通常希望实时展示推理过程,提升用户体验。
推荐做法是直接从 StreamingChatResponseUpdate.Contents 中提取 TextReasoningContent。
流式响应的工作流程
实现流式推理内容获取
推荐方式是直接读取 TextReasoningContent,更加直观且类型安全:
推荐方式:提取 TextReasoningContent 类型的内容
// 1. 获取流式响应
var streamingUpdates = chatClient.GetStreamingResponseAsync(
"将大象装进冰箱需要几步?",
options: reasoningOptions
);
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("🧠 模型推理过程(实时流式输出):\n");
List<ChatResponseUpdate> updates = [];
await foreach (var update in streamingUpdates)
{
// 实时显示推理内容
if (update.Contents.OfType<TextReasoningContent>().Any()){
var reasoningContent = update.Contents.OfType<TextReasoningContent>().Single();
Console.Write(reasoningContent.Text);
}
updates.Add(update);
}
// 汇总显示完整推理内容
var reasoningContents = updates.SelectMany(u => u.Contents).OfType<TextReasoningContent>().Select(r => r.Text);
string reasoningText = string.Concat(reasoningContents);
// 汇总显示完整回复内容
var msgContents = updates.SelectMany(u => u.Contents).OfType<TextContent>().Select(c => c.Text);
string msgText = string.Concat(msgContents);
new
{
完整推理 = reasoningText,
完整回复 = msgText
}.Display();
备注:
TextReasoningContent本质上也是通过从底层响应中提取 JsonPatch 实现的,只是封装成了更友好的 API,避免了直接操作 JsonPatch 的复杂性和潜在错误。具体实现思路,见下列章节的兼容方式示例。
兼容方式(不推荐):先转换再从 JsonPatch 增量更新中提取
以下方式可用于兼容旧代码或排查底层响应结构,不建议作为新项目主方案:
JsonPatch 快速入门
在使用 RawRepresentationFactory 之前,我们需要了解 JsonPatch 的基本概念。
什么是 JsonPatch?
JsonPatch 是一种用于描述 JSON 文档修改操作的标准格式(RFC 6902),在 OpenAI SDK 中用于动态设置请求参数。
在 OpenAI 和 Azure.AI.OpenAI SDK 中,ChatCompletionOptions.Patch 是一个 JsonPatch 对象,用于设置 AI 提供商的特有参数。
JsonPatch 基本用法
// 1. 创建 ChatCompletionOptions
var options = new ChatCompletionOptions();
// 2. 使用 Patch.Set() 设置参数
// 语法:Patch.Set(路径, 值)
options.Patch.Set("$.enable_thinking"u8, true);
options.Patch.Set("$.temperature"u8, 0.7);
options.Patch.Set("$.max_tokens"u8, 1000);
JsonPath 路径语法
|
路径 |
说明 |
示例 |
|---|---|---|
$ |
根对象 |
$.enable_thinking |
. |
属性访问 |
$.choices[0].delta |
[] |
数组索引 |
$.choices[0] |
常用推理模型参数
|
参数 |
类型 |
说明 |
示例值 |
|---|---|---|---|
enable_thinking |
bool |
启用推理模式 |
true |
thinking.type |
string |
思考类型(DeepSeek) |
"enabled" |
enable_search |
bool |
启用联网搜索(千问) |
true |
⚠️ 注意:
-
•
u8后缀表示 UTF-8 字节数组字面量,用于高性能字符串处理 -
• 不同 AI 提供商的参数名称可能不同,需参考官方文档
Console.WriteLine("⏱️ 开始流式请求...\n");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("🧠 模型推理过程(实时流式输出):\n");
#pragma warning disable SCME0001,OPENAI001 // JsonPatch 使用警告
// 1. 获取流式响应
var streamingUpdates = chatClient.GetStreamingResponseAsync(
"将大象装进冰箱需要几步?",
options: reasoningOptions
);
// 2. 转换为 OpenAI SDK 的流式更新对象
var openAIUpdates = streamingUpdates.AsOpenAIStreamingChatCompletionUpdatesAsync();
// 3. 遍历流式更新,提取推理内容
await foreach (var update in openAIUpdates)
{
// 使用 JsonPatch 从更新中提取 reasoning_content
if (update.Patch.TryGetValue("$.choices[0].delta.reasoning_content"u8, out string reasoningContentChunk))
{
Console.Write(reasoningContentChunk);
}
}
Console.WriteLine("\n\n✅ 流式推理内容接收完成");
代码说明:
-
• ⚠️
AsOpenAIStreamingChatCompletionUpdatesAsync()+Patch.TryGetValue()属于兼容写法,不推荐作为首选方案。 -
• ✅ 推荐优先使用
update.Contents.OfType<TextReasoningContent>()直接获取推理内容。
|
API |
说明 |
用途 |
|---|---|---|
GetStreamingResponseAsync() |
MEAI 流式响应接口 |
返回 |
update.Contents.OfType<TextReasoningContent>() |
强类型提取方式(推荐) |
直接获取推理内容片段 |
update.Contents.OfType<TextContent>() |
强类型提取方式(推荐) |
获取最终回答内容片段 |
AsOpenAIStreamingChatCompletionUpdatesAsync() |
OpenAI SDK 转换扩展(兼容) |
仅用于兼容旧代码或排查底层字段 |
Patch.TryGetValue() |
JsonPatch 读取方法(兼容) |
从底层增量中读取指定路径值 |
兼容方式的 JsonPath 路径示例
{
"choices": [
{
"delta": {
"reasoning_content": "首先,需要打开冰箱门...", // ← 目标字段
"content": "将大象装进冰箱需要三步..."
}
}
]
}
-
•
$: 根对象 -
•
choices[0]: 第一个选择(数组索引) -
•
delta: 增量更新对象 -
•
reasoning_content: 推理内容字段
同时获取推理内容和最终答案
在实际应用中,通常需要同时展示推理过程和最终回答:
#pragma warning disable SCME0001 // JsonPatch 使用警告
Console.WriteLine("⏱️ 开始完整流式请求...\n");
var streamingUpdates2 = chatClient.GetStreamingResponseAsync(
"用简洁的方式说明如何将大象装进冰箱",
options: reasoningOptions
);
var openAIUpdates2 = streamingUpdates2.AsOpenAIStreamingChatCompletionUpdatesAsync();
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("🧠 推理过程:\n");
bool hasReasoningContent = false;
bool hasMessageContent = false;
await foreach (var update in openAIUpdates2)
{
// 提取推理内容
if (update.Patch.TryGetValue("$.choices[0].delta.reasoning_content"u8, out string reasoningChunk))
{
if (!hasReasoningContent)
{
hasReasoningContent = true;
}
Console.Write(reasoningChunk);
}
// 提取消息内容
if (update.Patch.TryGetValue("$.choices[0].delta.content"u8, out string messageChunk))
{
if (!hasMessageContent)
{
Console.WriteLine("\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("💬 最终回答:\n");
hasMessageContent = true;
}
Console.Write(messageChunk);
}
}
Console.WriteLine("\n\n✅ 完整流式响应接收完成");
输出示例:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🧠 推理过程:
首先,这是一个经典的脑筋急转弯问题。实际上,将大象装进冰箱是不可能的物理操作...
我应该按照问题的字面意思,给出简洁的步骤说明。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💬 最终回答:
将大象装进冰箱需要三步:
1. 打开冰箱门
2. 将大象放进去
3. 关上冰箱门
完整工作流程对比
非流式 vs 流式
|
特性 |
非流式(GetResponseAsync) |
流式(GetStreamingResponseAsync) |
|---|---|---|
| 用户体验 |
等待完整响应后一次性显示 |
实时显示推理过程和回答 |
| 延迟感知 |
延迟明显(需等待全部完成) |
延迟低(逐步输出) |
| 适用场景 |
后台处理、批量任务 |
对话界面、实时展示 |
| 推理内容提取 | message.Contents.OfType<TextReasoningContent>() |
update.Contents.OfType<TextReasoningContent>() |
| 实现复杂度 |
简单 |
简单(强类型提取) |
完整流程图
流式
发送请求
接收流式更新
ChatResponseUpdate
OfType
实时显示推理
OfType
实时显示回答
非流式
发送请求
等待完整响应
response.Messages.Single
OfType
显示推理内容
OfType
显示最终答案
最佳实践与注意事项
✅ 最佳实践
-
1. 选择合适的模式
-
• 对话应用 → 使用流式,实时展示思考过程
-
• 后台任务 → 使用非流式,简化处理逻辑
-
2. 推理内容的用途
-
-
• 📊 调试提示词:查看模型理解是否正确
-
• 🎓 教学场景:展示 AI 推理逻辑
-
• 🔍 审计追踪:记录模型决策过程
-
-
3. 成本优化
-
-
• 推理内容会消耗额外的 Token(输入 Token)
-
• 生产环境中,可根据需要选择性启用
-
• 使用缓存策略减少重复推理
⚠️ 注意事项
问题
说明
解决方案
模型支持 并非所有模型都支持推理模式
使用 DeepSeek、千问等明确支持的模型
参数差异 不同提供商参数名可能不同
参考官方文档,使用正确的参数名
类型提取 TextReasoningContent可能不存在
使用
OfType<TextReasoningContent>().SingleOrDefault()并判空Token 计费 推理内容计入 Token 使用量
关注 API 计费,合理使用推理模式
🔍 常见问题
Q: 为什么取不到
TextReasoningContent?-
• 检查是否设置了
enable_thinking=true -
• 确认使用的模型支持推理模式
-
• 使用
SingleOrDefault()并添加空值判断,避免Single()异常
Q: 流式响应中只有推理内容,没有最终答案?
-
• 检查 JsonPath 路径是否正确
-
• 确认同时提取了
reasoning_content和content
Q: 如何在 UI 中展示"思考中..."效果?
-
• 使用流式响应,当接收到
reasoning_content时显示"正在思考..." -
• 当接收到
content时切换为显示最终答案
扩展示例:不同推理模型的参数配置
DeepSeek Reasoner(专用推理模型)
DeepSeek 提供了专门的推理模型
deepseek-reasoner,使用不同的参数配置:var deepseekReasonerOptions = new ChatOptions() { RawRepresentationFactory = (client) => { var options = new ChatCompletionOptions(); // DeepSeek Reasoner 使用 thinking.type 参数 options.Patch.Set("$.thinking.type"u8, "enabled"); return options; } }; var reasonerClient = AIClientHelper.GetDefaultChatClient("DeepSeek", "deepseek-reasoner"); var response = await reasonerClient.GetResponseAsync("分析量子计算的未来发展", deepseekReasonerOptions);千问推理模型(Qwen)
千问模型支持推理 + 联网搜索:
var qwenOptions = new ChatOptions() { RawRepresentationFactory = (client) => { var options = new ChatCompletionOptions(); options.Patch.Set("$.enable_thinking"u8, true); options.Patch.Set("$.enable_search"u8, true); // 启用联网搜索 return options; } }; var qwenClient = AIClientHelper.GetDefaultChatClient("Qwen", "qwen-max"); var response = await qwenClient.GetResponseAsync("2025年最新的AI技术趋势", qwenOptions);参数对比:
提供商
推理参数
额外功能
DeepSeek (reasoner)
thinking.type专用推理模型
千问
enable_thinking+
enable_search推理 + 联网搜索
本节小结
本节课我们学习了如何在 Microsoft.Extensions.AI 中启用推理模型并获取推理内容:
核心知识点
-
1. JsonPatch 基础
-
• ✅ 使用
Patch.Set()设置底层提供商参数 -
• ✅ JsonPath 语法:
$.property,$.array[0].field -
• ✅
u8后缀表示 UTF-8 字节数组字面量
-
-
2. 启用推理模式
-
-
• ✅ 通过
RawRepresentationFactory创建ChatCompletionOptions -
• ✅ 设置
enable_thinking=true启用推理输出 -
• ✅ 不同模型参数可能不同(参考官方文档)
-
-
3. 非流式获取推理内容(推荐)
-
-
• ✅ 使用
GetResponseAsync获取完整响应 -
• ✅ 从
response.Messages中读取message.Contents -
• ✅
OfType<TextReasoningContent>()提取推理内容
-
-
4. 流式获取推理内容(推荐)
-
-
• ✅ 使用
GetStreamingResponseAsync获取流式响应 -
• ✅ 从
update.Contents中直接提取强类型内容 -
• ✅
OfType<TextReasoningContent>()获取推理片段,OfType<TextContent>()获取答案片段
-
-
5. 兼容方式(不推荐)
-
-
• ℹ️ 可通过
AsOpenAIStreamingChatCompletionUpdatesAsync()+Patch.TryGetValue()读取底层字段 -
• ℹ️ 建议仅用于兼容旧代码或调试底层响应
实际应用场景
-
• 💬 对话应用:实时展示 AI 思考过程,提升用户体验
-
• 🔍 调试工具:查看模型推理逻辑,优化提示词
-
• 📊 审计日志:记录 AI 决策过程,满足合规要求
引用链接
[1]07-meai-chatoptions-extension.ipynb: ./07-meai-chatoptions-extension.ipynb[2]阿里百炼千问模型API文档: https://bailian.console.aliyun.com/?tab=api#/api/?type=model&url=2712576&userCode=okjhlpr5[3]阿里百炼DeepSeek模型API文档: https://bailian.console.aliyun.com/?tab=api#/api/?type=model&url=2868565&userCode=okjhlpr5[4]阿里百炼-深度思考: https://bailian.console.aliyun.com/?tab=doc#/doc/?type=model&url=2870973[5]DeepSeek-API文档: https://api-docs.deepseek.com/zh-cn/ -
更多推荐
所有评论(0)