图片

简介

在 DeepSeek-R1 或 Qwen-2.5-Max 等推理模型中,模型会在生成最终答案之前输出一段“思考过程”(Reasoning Content)。

目前的 .NET OpenAI SDK (OpenAI 包) 尚未完全原生支持将 reasoning_content 字段映射到强类型对象中。本文将演示如何通过 GetRawPagesAsync 和 SseParser 手动解析 Server-Sent Events (SSE) 数据流,从而实时获取并展示模型的思考过程。

核心方案

  1. JSON Patch 启用功能:使用 ChatCompletionOptions.Patch 注入 enable_thinking 参数。

  2. 底层 SSE 解析:绕过 SDK 的强类型封装,直接处理原始流数据。

  3. 双流分离:分别提取 reasoning_content (思考) 和 content (回答)。

关键代码实现

1. 初始化与配置

首先配置客户端,并使用 JSON Patch 启用思考模式(以 Qwen 为例):

// 配置 OpenAI 客户端
var clientOptions = new OpenAIClientOptions
{
    Endpoint = new Uri(Keys.QwenEndpoint)
};

var openAIClient = new OpenAIClient(new ApiKeyCredential(Keys.QwenApiKey), clientOptions);
var chatClient = openAIClient.GetChatClient("deepseek-v3.2-exp");

// 启用思考模式 (关键步骤)
var options = new ChatCompletionOptions();
#pragma warning disable SCME0001
options.Patch.Set("$.enable_thinking"u8, true);
#pragma warning restore SCME0001

2. SSE 解析逻辑

这是核心部分。我们需要处理原始的 SSE 数据包,手动解析 JSON 并提取字段。

using System.Net.ServerSentEvents;
using System.Text.Json;

// 解析 SSE 页面并分流处理
void ProcessSsePage(ClientResult page, StringBuilder reasoning, StringBuilder answer)
{
    var contentStream = page.GetRawResponse().ContentStream;
    if (contentStream isnull) return;

    // 使用 .NET 内置的 SseParser
    var parser = SseParser.Create(contentStream);

    foreach (var item in parser.Enumerate())
    {
        if (string.IsNullOrWhiteSpace(item.Data)) continue;
        
        // 解析 JSON
        if (!TryParseJsonMessage(item.Data, outvar message)) continue;

        // 提取内容
        ProcessDeltaContent(message, reasoning, answer);
    }
}

void ProcessDeltaContent(JsonElement message, StringBuilder reasoning, StringBuilder answer)
{
    // ... 省略部分校验代码 ...
    if (!message.TryGetProperty("choices", outvar choices)) return;
    var delta = choices[0].GetProperty("delta");

    // 1. 提取思考内容
    if (delta.TryGetProperty("reasoning_content", outvar rProp))
    {
        var text = rProp.GetString();
        if (!string.IsNullOrEmpty(text))
        {
            Console.ForegroundColor = ConsoleColor.Gray;
            Console.Write(text); // 实时输出思考
            reasoning.Append(text);
        }
    }

    // 2. 提取回答内容
    if (delta.TryGetProperty("content", outvar cProp))
    {
        var text = cProp.GetString();
        if (!string.IsNullOrEmpty(text))
        {
            Console.ResetColor();
            Console.Write(text); // 实时输出回答
            answer.Append(text);
        }
    }
}

3. 执行流式请求

使用 GetRawPagesAsync() 获取底层数据流:

var messages = new ChatMessage[] { new UserChatMessage("如何将大象装进冰箱?") };

// 发起流式请求
var chatUpdates = chatClient.CompleteChatStreamingAsync(messages, options);

// 遍历原始 SSE 页面
await foreach (var page in chatUpdates.GetRawPagesAsync())
{
    ProcessSsePage(page, reasoningContent, answerContent);
}

总结

虽然 SDK 暂时未跟上模型 API 的更新速度,但通过 .NET 强大的底层网络处理能力(如 SseParser),我们依然可以优雅地解决问题。

最佳实践:

  • 关注 SDK 的 GetRawPagesAsync 方法,它是处理非标准 API 响应的逃生舱。

  • 使用 System.Net.ServerSentEvents 处理流式数据,避免手写复杂的流解析逻辑。


如需获取文章配套完整代码,可扫码咨询领取。👇

图片

 

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐