MAF快速入门(19)给Agent Skill添加脚本执行能力
大家好,我是Edison。
最近我一直在跟着圣杰的《.NET+AI智能体开发进阶》课程学习MAF开发多智能体工作流,我强烈推荐你也上车跟我一起出发!
上一篇,我们初步学习了Agent Skill的基本概念以及在MAF中实现了一个Agent Skill的案例。本篇,我们来了解下Agent Skill如何集成脚本执行能力。
1 Agent Skill中的执行脚本
根据上一篇我们知道,Agent Skill就是大模型随时翻阅的说明文档,它还可以包含一些资源文件,而脚本就是其中的一种重要资源。
skill/ ├── SKILL.md # 必需:指令 + 元数据 ├── scripts/ # 可选:可执行代码 ├── references/ # 可选:文档资料 └── assets/ # 可选:模板、资源文件
scripts子目录
这些脚本会被放在其所属skill目录下的scripts子目录下,不管它是BASH脚本,PowerShell脚本,又或者是Python/C#代码,只要是Agent所在客户/服务端能够执行的都可以。
总结,Skills不仅让Agent知道处理规则(SKILL.md + references),还能让Agent直接上手处理(scripts)。
工具 vs 技能
工具(Tool)是一种能力,而技能(Skill)是一种知识。比如,自己开发一个PowerShellTools,给模型提供一些run shell的能力,我们通过注册到 ChatOptions.Tools 完成加载。
又如,自己定义一个系统运维Skill,给模型提供一些系统运维的知识,我们通过注册到AIContextProviders完成加载。
知识指导行为,而非工具限定行为。
2 快速开始:MAF中集成Shell执行能力
假设我们有这样一个需求:某企业开发了一个IT助手Agent,它运行在每个员工的笔记本电脑上,可以回答用户关于系统方面的一些问题并加以分析给出建议。
例如,很多员工发现自己的笔记本电脑最近越来越慢了,又或者发现内存一直高负载 等等,在以前员工会直接联系IT部的某位员工,而现在IT部将自身的运维经验封装为了一个Skill并通过Agent直接回复员工。
以终为始,我们的Agent应用的解决方案长这样子:这是一个.NET控制台应用程序,其中有一个skills目录,存放了系统运维领域的知识,这是IT部呕心沥血整理的“宝典”。

系统运维Skill
首先,我们创建这个SKILL.md,内容如下:
--- name: system-ops description: 系统运维诊断技能。适用于系统健康检查、磁盘空间分析、进程资源监控、故障排查等系统运维场景。包含可执行的诊断脚本。 --- # 系统运维(System Operations) ## 可用诊断脚本 以下脚本位于本技能的 `scripts/` 目录,可通过 `run_shell` 工具执行: | 脚本 | 用途 | 执行命令 | |------|------|--------| | check-system-info.ps1 | 获取系统基本信息(OS、CPU、内存) | | check-disk-usage.ps1 | 检查磁盘使用情况和剩余空间 | | check-top-processes.ps1 | 查看 CPU/内存占用 Top 进程 | ## 运维检查流程 1. **基础检查**:先执行 `check-system-info.ps1` 获取系统概况 2. **针对性诊断**:根据用户问题,选择性执行磁盘或进程检查脚本 3. **分析报告**:综合脚本输出和故障排查指南,给出诊断结论和建议 4. **故障排查**:如需深入排查,参考 [references/troubleshooting-guide.md](references/troubleshooting-guide.md) ## 告警阈值 | 指标 | 正常 | 警告 | 严重 | |------|------|------|------| | CPU 使用率 | <70% | 70-90% | >90% | | 内存使用率 | <80% | 80-95% | >95% | | 磁盘使用率 | <70% | 70-90% | >90% | | 单进程 CPU | <30% | 30-60% | >60% |
由于该Skill还定义了引用的资源,所以我们还需要添加:
(1)参考文件:故障自查指南
说明:这是一个markdown文件。
# 故障排查指南 ## CPU 持续高负载 1. 通过 `check-top-processes.ps1` 识别高占用进程 2. 判断是否为预期行为(如编译、数据处理) 3. 如为异常进程,建议: - 记录进程信息(PID、启动时间、命令行) - 联系应用负责人确认 - 必要时可终止进程:`Stop-Process -Id` ## 内存不足 1. 检查物理内存使用率(通过 `check-system-info.ps1`) 2. 识别内存大户(通过 `check-top-processes.ps1`) 3. 常见原因: - 内存泄漏:进程内存持续增长 - 缓存过多:数据库或应用缓存未限制 4. 缓解措施: - 重启内存泄漏进程 - 调整应用缓存配置 - 考虑扩容 ## 磁盘空间不足 1. 通过 `check-disk-usage.ps1` 确认各盘使用率 2. 清理建议(按优先级): - 清理临时文件 - 清理日志文件(保留近 7 天) - 清理 NuGet 包缓存:`dotnet nuget locals all --clear` 3. 长期方案:配置日志轮转、扩容磁盘
(2)脚本文件:PowerShell脚本
说明:这里主要在Windows系统中执行脚本,所以选择了PowerShell。
脚本1:获取系统基本信息
# check-system-info.ps1 — 获取系统基本信息
Write-Host "=== 系统基本信息 ==="
Write-Host "计算机名: $env:COMPUTERNAME"
Write-Host "操作系统: $([System.Runtime.InteropServices.RuntimeInformation]::OSDescription)"
Write-Host "处理器架构: $([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)"
Write-Host "逻辑处理器数: $([Environment]::ProcessorCount)"
$os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction SilentlyContinue
if ($os) {
$totalMemGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
$freeMemGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
$usedMemGB = [math]::Round($totalMemGB - $freeMemGB, 2)
$memUsagePercent = [math]::Round(($usedMemGB / $totalMemGB) * 100, 1)
Write-Host ""
Write-Host "=== 内存信息 ==="
Write-Host "总内存: ${totalMemGB} GB"
Write-Host "已使用: ${usedMemGB} GB ($memUsagePercent%)"
Write-Host "可用: ${freeMemGB} GB"
}
Write-Host ""
Write-Host "=== 系统运行时间 ==="
$uptime = (Get-Date) - (Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime
Write-Host "已运行: $($uptime.Days) 天 $($uptime.Hours) 小时 $($uptime.Minutes) 分钟"
脚本2:检查系统磁盘使用信息
# check-disk-usage.ps1 — 检查磁盘使用情况
Write-Host "=== 磁盘使用情况 ==="
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
$totalGB = [math]::Round($_.Size / 1GB, 2)
$freeGB = [math]::Round($_.FreeSpace / 1GB, 2)
$usedGB = [math]::Round($totalGB - $freeGB, 2)
$usagePercent = if ($totalGB -gt 0) { [math]::Round(($usedGB / $totalGB) * 100, 1) } else { 0 }
$status = if ($usagePercent -gt 90) { "🔴 严重" }
elseif ($usagePercent -gt 70) { "🟡 警告" }
else { "🟢 正常" }
Write-Host ""
Write-Host "驱动器 $($_.DeviceID)"
Write-Host " 总容量: ${totalGB} GB"
Write-Host " 已使用: ${usedGB} GB ($usagePercent%)"
Write-Host " 可用: ${freeGB} GB"
Write-Host " 状态: $status"
}
脚本3:查看资源占用Top10信息
# check-top-processes.ps1 — 查看资源占用 Top 进程
param(
[int]$Top = 10
)
Write-Host "=== CPU 占用 Top $Top 进程 ==="
Get-Process | Sort-Object CPU -Descending | Select-Object -First $Top |
Format-Table -Property @{N='进程名';E={$_.ProcessName}},
@{N='PID';E={$_.Id}},
@{N='CPU(s)';E={[math]::Round($_.CPU, 2)}},
@{N='内存(MB)';E={[math]::Round($_.WorkingSet64/1MB, 1)}} -AutoSize |
Out-String | Write-Host
Write-Host ""
Write-Host "=== 内存占用 Top $Top 进程 ==="
Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First $Top |
Format-Table -Property @{N='进程名';E={$_.ProcessName}},
@{N='PID';E={$_.Id}},
@{N='内存(MB)';E={[math]::Round($_.WorkingSet64/1MB, 1)}},
@{N='CPU(s)';E={[math]::Round($_.CPU, 2)}} -AutoSize |
Out-String | Write-Host
自定义ShellTools
最近MAF发布了1.0.0-rc2,支持了Agent Skill,但其尚未开放原生的脚本执行能力,虽然在GitHub的demo中我已经看到了脚本执行的demo。但貌似已经revert了,因此我们再静静等候一阵。
这里,我们通过自定义一个ShellTools来实现PowerShell的脚本执行能力:
public class ShellTools { // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // run_shell — 一个 Shell 工具做一切(含安全护栏) // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 🛡️ 安全护栏 1:危险命令黑名单 private static string[] dangerousPatterns = [ "rm -rf /", "rm -rf /*", // 删除根目录 "sudo ", // 提权 "shutdown", "reboot", // 系统操作 "> /dev/", // 设备写入 ":(){ :|:& };:", // Fork bomb "mkfs.", // 格式化 "dd if=", // 磁盘覆写 "format ", // Windows 格式化 "del /f /s /q", // Windows 递归删除 ]; [Description("执行 Shell 命令。通过操作系统原生 Shell 执行命令(Windows 用 cmd,Linux/Mac 用 bash)。包含安全护栏:危险命令阻止、输出截断(50KB)、超时控制(60秒)。")] public static string RunShell( [Description("要执行的 Shell 命令。例如:'pwsh -File /path/to/script.ps1' 或 'dir'")] string command, [Description("命令执行的工作目录(可选)。如果不指定,使用当前目录。")] string? workingDirectory = null) { try { // 🛡️ 安全护栏 1:危险命令检查 if (dangerousPatterns.Any(d => command.Contains(d, StringComparison.OrdinalIgnoreCase))) { return "❌ 安全拦截:检测到危险命令,已阻止执行。"; } // 🔑 跨平台 Shell 适配: // Windows → cmd /c "command" (原生命令提示符) // Linux/Mac → bash -c "command" (原生 Bash) // // 为什么不直接用 pwsh? // SKILL.md 中的命令已经是 "pwsh -File ..." // 如果 RunShell 也用 pwsh -Command 包裹 → pwsh 套 pwsh(冗余嵌套) // 用原生 shell 分发 → pwsh -File 直接执行,零嵌套 var isWindows = OperatingSystem.IsWindows(); var processInfo = new ProcessStartInfo { FileName = isWindows ? "cmd" : "bash", Arguments = isWindows ? $"/c {command}" : $"-c \"{command.Replace("\"", "\\\"")}\"", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; // 设置工作目录 if (!string.IsNullOrWhiteSpace(workingDirectory) && Directory.Exists(workingDirectory)) { processInfo.WorkingDirectory = workingDirectory; } using var process = Process.Start(processInfo); if (process == null) { return "❌ 无法启动 Shell 进程"; } var stdout = process.StandardOutput.ReadToEnd(); var stderr = process.StandardError.ReadToEnd(); // 🛡️ 安全护栏 3:超时控制(60秒) if (!process.WaitForExit(60_000)) { process.Kill(entireProcessTree: true); return "❌ 命令执行超时(60秒),已强制终止。"; } var result = new StringBuilder(); if (!string.IsNullOrWhiteSpace(stdout)) { result.AppendLine(stdout.Trim()); } if (!string.IsNullOrWhiteSpace(stderr)) { result.AppendLine($"⚠️ stderr: {stderr.Trim()}"); } if (process.ExitCode != 0) { result.AppendLine($"⚠️ 退出码: {process.ExitCode}"); } var output = result.Length > 0 ? result.ToString() : "(命令执行成功,无输出)"; // 🛡️ 安全护栏 2:输出截断(50KB) const int maxOutputLength = 50_000; if (output.Length > maxOutputLength) { output = output[..maxOutputLength] + "\n... (输出已截断,超过 50KB 上限)"; } return output; } catch (Exception ex) { return $"❌ 执行失败: {ex.Message}"; } } }
这个ShellTools还考虑了必要的安全性,对于一些危险命令,会主动进行阻止,还对超时60s的操作进行了命令挂起。
智能体调用Agent Skill
这里,让我们一步一步来实现在MAF中让智能体调用集成脚本执行的Skill。
(1)创建SkillsProvider:从文件中发现和加载Skills
var skillsProvider = new FileAgentSkillsProvider( skillPath: Path.Combine(Directory.GetCurrentDirectory(), "skills"), options: new FileAgentSkillsProviderOptions { // 🔑 自定义提示词:引导模型加载技能后使用 run_shell 执行脚本 SkillsInstructionPrompt = """ 你可以使用以下技能获取领域知识和操作指引。 每个技能提供专业指令、参考文档和可执行脚本。{0} 工作流程: 1. 当用户任务匹配技能描述时,使用 `load_skill` 加载该技能的完整指令 2. 技能指令中会标明可用脚本及其执行命令 3. 使用 `run_shell` 工具执行技能中标注的命令 4. 需要时使用 `read_skill_resource` 读取参考资料 重要原则:先加载知识,再执行操作。 """ } ); Console.WriteLine("✅ FileAgentSkillsProvider 创建成功(知识层)"); Console.WriteLine("📂 自动注册工具: load_skill, read_skill_resource");
(2)创建Agent:注入SkillsProvider让其有Skill调用能力
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions { Name = "SkillsBashAgent", ChatOptions = new() { Instructions = "你是一个专业的系统运维助手。请用中文回答所有问题。", // 🔑 能力层:仅注册一个 run_shell 工具 Tools = [AIFunctionFactory.Create(ShellTools.RunShell)], }, // 🔑 知识层:通过 AIContextProviders 注入 Skills AIContextProviders = [skillsProvider], }); Console.WriteLine("✅ 技能 Agent 创建成功");
下面,我们就来测试一下这个Agent:
测试用例1
用户:帮我检查一下当前系统的整体健康状态,包括 CPU、内存和磁盘使用情况。
Agent 会:
1. 识别属于 system-ops 领域 → load_skill("system-ops")
2. 从 SKILL.md 获取脚本执行命令
3. 依次调用 run_shell 执行诊断脚本
4. 根据告警阈值分析结果
测试代码如下:
var session = await agent.CreateSessionAsync(); Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); Console.WriteLine(" 🔍 测试 1:系统健康检查"); Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); Console.WriteLine(); var question1 = "帮我检查一下当前系统的整体健康状态,包括 CPU、内存和磁盘使用情况。"; Console.WriteLine($"👤 用户: {question1}"); Console.WriteLine(); var response1 = await agent.RunAsync(question1, session); Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); Console.WriteLine($"🤖 Agent: {response1.Text}"); Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); Console.WriteLine(); Console.WriteLine();
测试结果如下图所示:

测试用例2
危险脚本操作命令,验证Agent不会执行这些命令。
var dangerousTestCases = new[] { ("rm -rf /", "删除根目录"), ("sudo apt-get install malware", "提权操作"), ("shutdown -s -t 0", "关机命令"), }; foreach (var (cmd, desc) in dangerousTestCases) { var result = ShellTools.RunShell(cmd); Console.WriteLine($"❌ 测试: {desc}"); Console.WriteLine($" 命令: {cmd}"); Console.WriteLine($" 结果: {result}"); Console.WriteLine(); } // 测试正常命令 var normalResult = ShellTools.RunShell("Get-Date"); Console.WriteLine($"✅ 测试: 正常命令"); Console.WriteLine($" 命令: Get-Date"); Console.WriteLine($" 结果: {normalResult.Trim()}"); Console.WriteLine(); Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); Console.WriteLine("🛡️ 安全护栏验证完成:危险命令被正确拦截,正常命令正常执行。");
测试结果如下图所示:

可以看到,安全防护栏正常工作,防止危险命令被Agent执行。
3 小结
本文介绍了Agent Skill的脚本执行,脚本 和 工具的对比,最后通过一个MAF中Agent Skill 结合 PowerShell脚本执行 的系统运维案例介绍如何集成脚本执行,相信会对你有所帮助。
示例源码
GitHub: https://github.com/EdisonTalk/MAFD
参考资料
圣杰,《.NET + AI 智能体开发进阶》(推荐指数:★★★★★)
Microsoft Learn,《Agent Framework Tutorials》

原文地址: https://www.cveoy.top/t/topic/qF8i 著作权归作者所有。请勿转载和采集!