当前位置: 首页 > news >正文

深入 .NET AI Agent 开发:利用 Microsoft.Agents.AI 提取思考、调用工具与执行脚本

效果图

一、整体架构概览

示例代码中,AIAgentService是核心服务,负责创建 OpenAI 兼容的聊天客户端并将其包装为AIAgent。关键步骤如下:

  1. 配置 OpenAI 客户端:支持自定义端点、超时、重试策略。
  2. 启用 / 禁用工具与技能:通过AISetting控制是否注入工具列表(Tools)和技能上下文提供者(AIContextProviders)。
  3. 发送消息并获取响应:支持流式(RunStreamingAsync)和非流式(RunAsync)两种模式。
  4. 在流式输出中解析结构化内容:通过判断update.Contents中的具体类型来捕获工具调用、工具结果以及普通文本,同时从原始响应中提取模型的思考过程。

二、提取思考过程(Reasoning)

许多大语言模型(如 OpenAI o1 系列)会在生成最终答案前输出一段内部的“思考链”(reasoning tokens)。Microsoft.Agents.AIAgentResponseUpdate对象在流式模式下提供了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中查找reasoningreasoning_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:表示模型请求调用某个函数,其中包含NameCallIdArguments(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 调用技能时会传入argumentsJsonElement?类型),脚本执行器将其序列化为 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框架的灵活性和扩展性:

http://www.jsqmd.com/news/1067238/

相关文章:

  • Manim物理模拟:别自己写欧拉了!
  • 古典密码 - 维吉尼亚密码破解
  • [APM32F4] 跟随 Fabrice Bellard 的脚步,把 MicroQuickJS 移植到 APM32F427
  • 2026 佛山传统企业升级|短视频矩阵赋能,加快数字化内容建设
  • 在线 AI 开发平台-MonkeyCode
  • 吉他选购指南2026年入门级1000-2000元横评:参数对比+真实评测
  • 每日一个开源项目(第138篇):OpenMontage - 把 AI 编程助手变成完整的视频制作团队
  • HAProxy 学习总结
  • CS16S:01
  • 低成本创业辅助软件客观梳理
  • 无损FLAC音乐怎么变成MP3?一篇讲清楚flac转mp3操作步骤
  • 安全审计系统有哪些?2026年5大安全审计软件功能详解,最新分享
  • 独立研究者开发的土耳其语“形态大脑“
  • Childhood,23款童年卡牌游戏复刻
  • RAG 检索质量从 60% 到 90%:混合检索 + 重排序的完整实践
  • 基于知识图谱的百科知识问答系统:Django+Neo4j 智能问答平台项目实战
  • 从Copilot到Agent——我的开发工作流正在被颠覆的技术文章大纲
  • 隐性隐私泄露
  • 全网最全!2026AI论文平台榜单(覆盖 99% 毕业论文需求)
  • 12.1.1 质量属性概念 (续) - 运行期质量属性
  • 92-Java 多线程编程
  • Milvus架构与核心原理
  • LangChain 实战:Structured Chat ReAct 智能体原理与实现(对比 ZeroShot ReAct)
  • 《B4501 [GESP202603 四级] 山之谷》
  • 轻集料混凝土哪家强?LC5.0型厂家这样选才靠谱
  • 基于 Harmony 7.0 应用的手写签名应用首页实现
  • API中转站搭建完整教程:从零部署专属New API服务为什么自建API中转站
  • 电商支付资损风险防控测试实战:从优惠叠加漏洞到大促零故障的完整路径
  • 车载开发|致远ZCAN设备Python ctypes完整封装库解析
  • .NET 高级开发 | 设计、实现一个事件总线框架