深入 .NET AI Agent 开发:利用 Microsoft.Agents.AI 提取思考、调用工具与执行脚本
在基于 .NET 的 AI Agent 开发中,Microsoft.Agents.AI 提供了强大的抽象,使得构建能够调用工具、理解思考过程并执行自定义脚本的智能体变得直观。本文将基于实际代码片段,深入讲解如何利用该框架提取模型的思考过程(Reasoning)、处理工具调用(Tool Calls),以及如何安全地执行外部脚本作为 Agent 的 Skill。
效果图

一、整体架构概览
示例代码中,AIAgentService 是核心服务,负责创建 OpenAI 兼容的聊天客户端并将其包装为 AIAgent。关键步骤如下:
- 配置 OpenAI 客户端:支持自定义端点、超时、重试策略。
- 启用 / 禁用工具与技能:通过
AISetting控制是否注入工具列表(Tools)和技能上下文提供者(AIContextProviders)。 - 发送消息并获取响应:支持流式(
RunStreamingAsync)和非流式(RunAsync)两种模式。 - 在流式输出中解析结构化内容:通过判断
update.Contents中的具体类型来捕获工具调用、工具结果以及普通文本,同时从原始响应中提取模型的思考过程。
二、提取思考过程(Reasoning)
许多大语言模型(如 OpenAI o1 系列)会在生成最终答案前输出一段内部的“思考链”(reasoning tokens)。Microsoft.Agents.AI 的 AgentResponseUpdate 对象在流式模式下提供了 RawRepresentation,允许我们访问底层模型的原始更新。
代码中 GetReasoningTextAsync 方法展示了完整提取逻辑:
if (update.RawRepresentation is Microsoft.Extensions.AI.ChatResponseUpdate streamingChatCompletionUpdate
&& streamingChatCompletionUpdate.RawRepresentation is OpenAI.Chat.StreamingChatCompletionUpdate chatCompletionUpdate)
{
ref JsonPatch patch = ref chatCompletionUpdate.Patch;
var jsonPathBytes = Encoding.UTF8.GetBytes("$.choices");
var jsonPathSpan = new ReadOnlySpan<byte>(jsonPathBytes);
if (patch.TryGetJson(jsonPathSpan, out var data))
{
var jsonString = Encoding.UTF8.GetString(data.ToArray());
using var doc = JsonDocument.Parse(jsonString);
// 遍历 choices[].delta 查找 reasoning 或 reasoning_content 字段
}
}
原理:
StreamingChatCompletionUpdate.Patch是一个JsonPatch对象,包含了本次增量更新的原始 JSON 数据。- 通过
TryGetJson并指定 JSONPath$.choices,可以获取choices数组的完整片段。 - 遍历数组中的每个
choice,在delta中查找reasoning或reasoning_content字段(兼容不同模型),将字符串值拼接起来即为模型的思考过程。 - 注意处理
null值,用Replace("null", "\n")清理无关内容。
这样,即使框架的上层接口未直接暴露 reasoning,我们依然可以通过原始数据获取并单独回调,实现思考过程的实时展示。
三、处理工具调用(Tool Calls)
在 AIAgent 的流式响应循环中,update.Contents 是一系列 AIContent 派生对象,我们可以根据具体类型区分工具调用请求和工具执行结果。
代码片段:
await foreach (var update in aiAgent.RunStreamingAsync(msg))
{
foreach (var content in update.Contents)
{
switch (content)
{
case FunctionCallContent funcCall:
// 模型决定调用工具,输出工具名称、参数
aISetting.ToolStreameCallback.Invoke($"\n [工具调用] 名称:{funcCall.Name},调用ID:{funcCall.CallId},参数:{JsonConvert.SerializeObject(funcCall.Arguments)}");
break;
case FunctionResultContent funcResult:
// 工具执行完毕返回结果
aISetting.ToolStreameCallback.Invoke($"\n [工具返回] 调用ID:{funcResult.CallId},结果:{funcResult.Result}");
break;
case TextContent textContent:
// 普通文本输出(通常已由 update.Text 处理)
break;
}
// 处理可读文本
if (!string.IsNullOrEmpty(update.Text))
{
aISetting.StreameCallback.Invoke(update.Text);
resultText += update.Text;
}
}
}
关键点:
FunctionCallContent:表示模型请求调用某个函数,其中包含Name、CallId和Arguments(JSON 对象)。我们将其序列化后通过回调通知外部系统,以便记录或展示。FunctionResultContent:当工具执行完成后,Agent 会收到一个包含调用 ID 和结果的更新。同样通过回调传递结果,便于构建完整的对话记录。TextContent:通常与普通文本输出对应,但框架通常会将文本聚合到update.Text属性中,所以此处主要针对非文本类型做处理。
利用这种模式,我们可以在 Agent 执行过程中实时监控工具调用状态,进行日志记录或界面更新。
四、执行 Skill 脚本
在 Microsoft.Agents.AI 中,AgentFileSkill 允许我们将外部脚本(如 Python、Shell、PowerShell)注册为 Agent 的技能。PySubprocessScriptRunner 类展示了一个通用的脚本执行器,其核心方法是 StaticRunAsync。
1. 脚本类型与解释器选择
根据脚本文件扩展名动态决定启动进程的命令:
switch (Path.GetExtension(scriptFullPath).ToLowerInvariant())
{
case ".py":
startInfo = CreateStartInfo("python", $"\"{scriptFullPath}\"");
break;
case ".sh":
startInfo = CreateStartInfo("bash", $"\"{scriptFullPath}\"");
break;
case ".ps1":
// 根据操作系统选择 powershell 或 pwsh
break;
default:
startInfo = CreateStartInfo(scriptFullPath, string.Empty);
break;
}
2. 进程配置与编码处理
为避免跨平台输出乱码,CreateStartInfo 方法根据运行平台设置编码:
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Encoding outputEncoding = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Encoding.Default : Encoding.UTF8;
同时启用输入输出重定向,并禁用 Shell 执行,确保安全。
3. 向脚本传递参数
Agent 调用技能时会传入 arguments(JsonElement? 类型),脚本执行器将其序列化为 JSON 字符串,通过标准输入(stdin)传递给脚本:
string inputJson = JsonSerializer.Serialize(arguments);
await process.StandardInput.WriteAsync(inputJson);
process.StandardInput.Close();
这要求脚本必须能够从 stdin 读取 JSON 数据并自行解析。
4. 异步执行与结果处理
进程启动后立即开始异步读取标准输出和标准错误,并等待进程结束或取消:
process.OutputDataReceived += (s, e) => outputBuilder.AppendLine(e.Data);
process.ErrorDataReceived += (s, e) => errorBuilder.AppendLine(e.Data);
process.BeginOutputReadLine();
process.BeginErrorReadLine();
await process.WaitForExitAsync(cancellationToken);
若退出码非零,抛出异常并附带错误信息;否则,尝试将输出反序列化为 JSON 对象,失败时返回原始字符串。这样 Agent 就可以将脚本结果直接作为工具返回值参与后续对话。
五、总结
通过以上代码实践,我们可以看到 Microsoft.Agents.AI 框架的灵活性和扩展性:
- 思考过程提取:利用原始响应中的 JSON Patch 数据,轻松穿透抽象层获取模型内部推理细节。
- 工具调用监控:通过
update.Contents的多态类型判断,实现对工具调用请求和结果的精确拦截与展示。 - Skill 脚本执行:将外部脚本无缝集成为 Agent 的工具,通过子进程调用并传递 JSON 参数,实现了语言模型与本地代码的协同工作。
这些技术结合在一起,使得开发者能够构建出功能强大、可观测性高、且能充分利用现有代码资产的 AI Agent 系统。无论是要增强对话体验,还是整合复杂的业务逻辑,Microsoft.Agents.AI 都提供了坚实的基础。
六、代码开源地址
NetCoreKevin框架下的kevin.AI.AgentFramework模块,基于.NET构建的企业级SaaSAI智能体应用架构
项目地址:github:https://github.com/junkai-li/NetCoreKevin
Gitee: https://gitee.com/netkevin-li/NetCoreKevin
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)