本文提供的方法適用於 DeepSeek 和豆包等模型
前置博客:
- Microsoft Agent Framework 與 DeepSeek 對接
- C# Microsoft Agent Framework 與 豆包 對接
核心原理是從 AgentResponseUpdate 裏面的 RawRepresentation 獲取 reasoning_content 字段
核心代碼如下
AIAgent agent = ...;
IEnumerable<ChatMessage> messages = ...;
AgentSession? session = ...;
AgentRunOptions? options = ...;
await foreach (AgentResponseUpdate agentRunResponseUpdate in agent.RunStreamingAsync(messages, session, options, cancellationToken))
{
var contentIsEmpty = string.IsNullOrEmpty(agentRunResponseUpdate.Text);
if (contentIsEmpty && agentRunResponseUpdate.RawRepresentation is Microsoft.Extensions.AI.ChatResponseUpdate streamingChatCompletionUpdate)
{
if (streamingChatCompletionUpdate.RawRepresentation is StreamingChatCompletionUpdate chatCompletionUpdate)
{
#pragma warning disable SCME0001 // Patch 屬性是實驗性內容
ref JsonPatch patch = ref chatCompletionUpdate.Patch;
if (patch.TryGetJson("$.choices[0].delta"u8, out var data))
{
var jsonElement = JsonElement.Parse(data.Span);
if (jsonElement.TryGetProperty("reasoning_content", out var reasoningContent))
{
// 拿到的 reasoningContent 就是思考內容
}
}
#pragma warning restore SCME0001
}
}
我將這段代碼封裝為擴展方法,方便上層業務使用,代碼如下
using System.ClientModel.Primitives;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text.Json;
using OpenAI.Chat;
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
namespace Microsoft.Agents.AI.Reasoning;
public static class ReasoningAIAgentExtension
{
public static IAsyncEnumerable<ReasoningAgentRunResponseUpdate> RunReasoningStreamingAsync(this AIAgent agent, ChatMessage message,
AgentSession? session = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default)
{
return RunReasoningStreamingAsync(agent, [message], session, options, cancellationToken);
}
public static async IAsyncEnumerable<ReasoningAgentRunResponseUpdate> RunReasoningStreamingAsync(this AIAgent agent, IEnumerable<ChatMessage> messages,
AgentSession? session = null,
AgentRunOptions? options = null,
[EnumeratorCancellation]
CancellationToken cancellationToken = default)
{
bool? isThinking = null;
bool isFirstOutputContent = true;
await foreach (AgentResponseUpdate agentRunResponseUpdate in agent.RunStreamingAsync(messages, session, options, cancellationToken))
{
var contentIsEmpty = string.IsNullOrEmpty(agentRunResponseUpdate.Text);
if (contentIsEmpty && agentRunResponseUpdate.RawRepresentation is Microsoft.Extensions.AI.ChatResponseUpdate streamingChatCompletionUpdate)
{
if (streamingChatCompletionUpdate.RawRepresentation is StreamingChatCompletionUpdate chatCompletionUpdate)
{
#pragma warning disable SCME0001 // Patch 屬性是實驗性內容
ref JsonPatch patch = ref chatCompletionUpdate.Patch;
if (patch.TryGetJson("$.choices[0].delta"u8, out var data))
{
var jsonElement = JsonElement.Parse(data.Span);
if (jsonElement.TryGetProperty("reasoning_content", out var reasoningContent))
{
// 拿到的 reasoningContent 就是思考內容
}
}
#pragma warning restore SCME0001
}
}
if (!contentIsEmpty)
{
var responseUpdate = new ReasoningAgentRunResponseUpdate(agentRunResponseUpdate);
if (isFirstOutputContent)
{
responseUpdate.IsFirstOutputContent = true;
}
if (isThinking is true && isFirstOutputContent)
{
responseUpdate.IsThinkingEnd = true;
}
isFirstOutputContent = false;
isThinking = false;
yield return responseUpdate;
}
}
}
}
用到的輔助類 ReasoningAgentRunResponseUpdate 代碼如下
namespace Microsoft.Agents.AI.Reasoning;
public class ReasoningAgentRunResponseUpdate : AgentResponseUpdate
{
public ReasoningAgentRunResponseUpdate(AgentResponseUpdate origin) : base(origin.Role, origin.Contents)
{
Origin = origin;
AdditionalProperties = origin.AdditionalProperties;
AuthorName = origin.AuthorName;
CreatedAt = origin.CreatedAt;
MessageId = origin.MessageId;
RawRepresentation = origin.RawRepresentation;
ResponseId = origin.ResponseId;
ContinuationToken = origin.ContinuationToken;
AgentId = origin.AgentId;
}
public AgentResponseUpdate Origin { get; }
public string? Reasoning { get; set; }
/// <summary>
/// 是否首次輸出內容,前面輸出的都是內容
/// </summary>
/// 僅內容輸出,無思考的首次內容輸出:
/// - IsFirstOutputContent = true
/// - IsFirstThinking = false
/// - IsThinkingEnd = false
/// 有思考,完成思考後的首次內容輸出:
/// - IsFirstOutputContent = true
/// - IsFirstThinking = false
/// - IsThinkingEnd = true
public bool IsFirstOutputContent { get; set; }
/// <summary>
/// 思考的首次輸出
/// </summary>
public bool IsFirstThinking { get; set; }
/// <summary>
/// 是否思考結束
/// </summary>
public bool IsThinkingEnd { get; set; }
}
業務層使用示例:
ChatClientAgent aiAgent = ...;
ChatMessage message = new ChatMessage(ChatRole.User, "請講一個笑話");
await foreach (var agentRunResponseUpdate in aiAgent.RunReasoningStreamingAsync(message))
{
if (agentRunResponseUpdate.IsFirstThinking)
{
Console.WriteLine("思考:");
}
if (agentRunResponseUpdate.Reasoning is not null)
{
Console.Write(agentRunResponseUpdate.Reasoning);
}
if (agentRunResponseUpdate.IsThinkingEnd)
{
Console.WriteLine();
Console.WriteLine("--------");
}
var text = agentRunResponseUpdate.Text;
if (!string.IsNullOrEmpty(text))
{
Console.Write(text);
}
}
輸出效果如下:
思考:
用户讓我講一個笑話,那得選個輕鬆有趣、貼近生活的,別太複雜。首先,得考慮不同年齡段都能聽懂,最好有點小反轉,這樣笑點更足。
比如可以從日常場景入手,像去超市、和朋友互動,或者寵物相關的?寵物的笑話通常很受歡迎,因為可愛又有反差感。
哦,想到一個關於貓和主人的:
主人發現貓最近總偷喝魚缸裏的水,於是教訓它:“你再偷喝,我就把你和魚一起燉了!”
第二天,主人發現魚缸裏的魚全沒了,貓蹲在旁邊舔爪子,抬頭一臉無辜:“主人,我幫你提前處理了食材~”
這個有小反轉,貓的邏輯很可愛,應該挺逗的。或者換個更接地氣的,比如關於吃飯的:
昨天去吃火鍋,服務員問我要辣的還是不辣的,我説:“微辣就行,別太辣。”
服務員:“好的,我們這邊微辣是‘外地人眼中的特辣’,特辣是‘本地人眼中的微辣’,您要哪個?”
我:“……給我來個‘外地人眼中的微辣’的微辣吧。”
服務員愣了一下:“好的,清水鍋加一片辣椒!”
這個也很真實,很多地方的微辣都很辣,有共鳴感。不過還是寵物的那個更可愛,可能用户會更喜歡。再調整一下,讓語言更自然:
有隻貓特別愛偷喝魚缸裏的水,主人每次看見都批評它:“再偷喝,我就把你和魚一起燉了!”
結果第二天起牀,主人發現魚缸裏的魚全不見了,貓正蹲在旁邊舔嘴巴,看見主人過來,還一臉邀功地説:“主人你看!我提前把配菜都處理好了??”
對,這樣更生動,有表情符號也更活潑。或者再短一點的?比如冷笑話?不過可能用户想要正常的笑話,不是冷的。那就選這個貓的吧,應該不錯。
--------
有隻貓特別愛偷喝魚缸裏的水,主人每次看見都批評它:“再偷喝,我就把你和魚一起燉了!”
結果第二天起牀,主人發現魚缸裏的魚全不見了,貓正蹲在旁邊舔嘴巴,看見主人過來,還一臉邀功地説:“主人你看!我提前把配菜都處理好了”
本文代碼放在 github 和 gitee 上,可以使用如下命令行拉取代碼。我整個代碼倉庫比較龐大,使用以下命令行可以進行部分拉取,拉取速度比較快
先創建一個空文件夾,接着使用命令行 cd 命令進入此空文件夾,在命令行裏面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin fc4cf4f485ea3e2268a67c0c6900827d9803d9b3
以上使用的是國內的 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令行繼續輸入以下代碼,將 gitee 源換成 github 源進行拉取代碼。如果依然拉取不到代碼,可以發郵件向我要代碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin fc4cf4f485ea3e2268a67c0c6900827d9803d9b3
獲取代碼之後,進入 SemanticKernelSamples/LadelallkeacheWhikurwearqobakaju 文件夾,即可獲取到源代碼
更多技術博客,請參閲 博客導航