Semantic Kernel 过滤器中间件-详解

一、过滤器类型(SK 1.78.0)

过滤器类型接口作用时机
函数调用过滤器 IFunctionInvocationFilter 函数执行前后
Prompt 渲染过滤器 IPromptRenderFilter Prompt 渲染后,调用 LLM 前
自动函数调用过滤器 IAutoFunctionInvocationFilter 自动函数调用流程中

 

过滤器(Filter)是 Semantic Kernel 的中间件机制,允许在函数调用的前后执行自定义逻辑,用于日志记录、监控、安全控制等。

项目引用

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <NoWarn>$(NoWarn);NU5104</NoWarn>
    </PropertyGroup>

    <ItemGroup>
        <Compile Remove="Registry\**" />
        <EmbeddedResource Remove="Registry\**" />
        <None Remove="Registry\**" />
    </ItemGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.SemanticKernel" Version="1.78.0" />
        <PackageReference Include="Microsoft.SemanticKernel.Connectors.Sqlite" Version="1.51.0-preview" />
        <PackageReference Include="Microsoft.SemanticKernel.Plugins.Core" Version="1.78.0-preview" />
        <PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" Version="1.78.0-alpha" />
        <PackageReference Include="Microsoft.SemanticKernel.Plugins.OpenApi" Version="1.78.0" />
        <PackageReference Include="Microsoft.SemanticKernel.PromptTemplates.Handlebars" Version="1.78.0" />
        <PackageReference Include="Microsoft.SemanticKernel.Plugins.Web" Version="1.78.0-alpha" />
        <PackageReference Include="Qdrant.Client" Version="1.18.1" />
        <PackageReference Include="Sdcb.DashScope" Version="2.0.0" />
        <!-- ✅ 新增:限流需要 -->
        <PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" />

        <!-- ✅ 新增:重试需要 -->
        <PackageReference Include="Polly" Version="8.4.0" />
    </ItemGroup>

    <ItemGroup>
        <ProjectReference Include="..\Services\Services.csproj" />
    </ItemGroup>

</Project>
View Code

1、IFunctionInvocationFilter、Token认证过滤器

token过滤器

    /// <summary>
    /// 认证过滤器 - 简化版(不依赖 IUserContext)
    /// </summary>
    public class AuthenticationFilter : IFunctionInvocationFilter
    {
        private readonly Func<string, Task<bool>> _validateToken;
        private readonly Dictionary<string, string[]> _pluginPermissions;

        /// <summary>
        /// 创建认证过滤器
        /// </summary>
        /// <param name="validateToken">验证 Token 的委托</param>
        /// <param name="pluginPermissions">插件权限映射(插件名 -> 允许的角色列表)</param>
        public AuthenticationFilter(
            Func<string, Task<bool>> validateToken,
            Dictionary<string, string[]>? pluginPermissions = null)
        {
            _validateToken = validateToken;
            _pluginPermissions = pluginPermissions ?? new();
        }

        /// <summary>
        /// 在函数执行前调用
        /// </summary>
        /// <param name="context"></param>
        /// <param name="next"></param>
        /// <returns></returns>
        /// <exception cref="UnauthorizedAccessException"></exception>
        public async Task OnFunctionInvocationAsync(
            FunctionInvocationContext context,
            Func<FunctionInvocationContext, Task> next)
        {
            // 1️⃣ 从上下文获取 Token
            var token = GetTokenFromContext(context);

            // 2️⃣ 验证 Token
            if (string.IsNullOrEmpty(token) || !await _validateToken(token))
            {
                throw new UnauthorizedAccessException(
                    $"❌ 未授权的函数调用:{context.Function.PluginName}.{context.Function.Name}");
            }

            // 3️⃣ 检查插件权限(如果配置了)
            if (_pluginPermissions.Count > 0)
            {
                var userRole = GetUserRoleFromToken(token);

                if (_pluginPermissions.TryGetValue(context.Function.PluginName, out var requiredRoles))
                {
                    if (!requiredRoles.Contains(userRole))
                    {
                        throw new UnauthorizedAccessException(
                            $"❌ 用户角色 [{userRole}] 无权限访问插件 [{context.Function.PluginName}]");
                    }
                }
            }

            Console.WriteLine($"🔐 认证通过:{context.Function.PluginName}.{context.Function.Name}");

            // 4️⃣ 继续执行
            await next(context);

            // 5️⃣ 执行后:记录审计日志
            Console.WriteLine($"✅ 函数完成:{context.Function.PluginName}.{context.Function.Name}");
        }

        private string GetTokenFromContext(FunctionInvocationContext context)
        {
            // 从 Kernel.Data 获取 Token
            if (context.Kernel.Data.TryGetValue("auth_token", out var token))
            {
                return token?.ToString() ?? "";
            }
            return "";
        }

        private string GetUserRoleFromToken(string token)
        {
            // 简化:从 Token 中提取角色
            // 实际项目应该解析 JWT 或调用用户服务
            if (token.Contains("admin")) return "admin";
            if (token.Contains("user")) return "user";
            return "guest";
        }
    }
View Code

入口

using ConsoleApp1;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Newtonsoft.Json;
using Polly;
using Polly.Retry;
using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Threading.RateLimiting;

namespace ConsoleApp4
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("╔════════════════════════════════════════╗");
            Console.WriteLine("║    SK 认证过滤器使用示例               ║");
            Console.WriteLine("╚════════════════════════════════════════╝\n");

            // 1️⃣ 创建 Kernel
            var kernel = CreateKernelWithAuth();

            // 2️⃣ 注册插件
            var weatherPlugin = new WeatherPlugin();
            var weatherPluginName = "Weather";
            var plugin = KernelPluginFactory.CreateFromObject(weatherPlugin, weatherPluginName); 
            kernel.Plugins.Add(plugin);

            var weatherHistoryPlugin = new WeatherHistoryPlugin();
            var WeatherHistoryPluginName = "WeatherHistory";
            var HistoryPlugin = KernelPluginFactory.CreateFromObject(weatherHistoryPlugin, WeatherHistoryPluginName);
            kernel.Plugins.Add(HistoryPlugin);
            // ========== 场景 1: 有效 Token ==========
            Console.WriteLine("═══════════════════════════════════════");
            Console.WriteLine("场景 1: 使用有效 Token,角色:user");
            Console.WriteLine("═══════════════════════════════════════\n");

            kernel.Data["auth_token"] = "valid_token_user";

            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: weatherPluginName,
                    functionName: "GetWeather",
                    arguments: new KernelArguments { ["city"] = "苏州" });

                Console.WriteLine($"\n✅ user尝试访问GetWeather,结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"\n❌ user尝试访问GetWeather,认证失败:{ex.Message}");
            }
            // 
            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: weatherPluginName,
                    functionName: "GetWeatherByDate",
                    arguments: new KernelArguments { ["city"] = "苏州", ["dt"]=DateTime.Now.AddDays(-3).ToString("yyyy-MM-dd") });

                Console.WriteLine($"\n✅ user尝试访问GetWeatherByDate,结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"\n❌  user尝试访问GetWeatherByDate,认证失败:{ex.Message}");
            }
            //尝试访问Admin专属的插件
            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: WeatherHistoryPluginName,
                    functionName: "GetWeatherHistory",
                    arguments: new KernelArguments { ["city"] = "苏州" });

                Console.WriteLine($"\n✅ user尝试访问GetWeatherHistory,结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"\n❌ user尝试访问GetWeatherHistory,认证失败:{ex.Message}");
            }

            // ========== 场景 2: 使用有效 Token,角色:guest  ==========
            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("场景 2: 使用有效 Token,角色:guest");
            Console.WriteLine("═══════════════════════════════════════\n");

            ///guest 尝试访问 GetWeather
            kernel.Data["auth_token"] = "valid_token_guest";

            try
            {
                var result = await kernel.InvokeAsync(
                     pluginName: weatherPluginName,
                    functionName: "GetWeather",
                    arguments: new KernelArguments { ["city"] = "苏州" });

                Console.WriteLine($"\n✅ guest 尝试访问 GetWeather:结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"\n❌ guest 尝试访问 GetWeather:认证失败:{ex.Message}");
            }

            // ========== 场景 3: 无 Token 无效token ==========
            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("场景 3: 不提供 Token");
            Console.WriteLine("═══════════════════════════════════════\n");

            kernel.Data.Remove("auth_token");

            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: weatherPluginName,
                    functionName: "GetWeather",
                    arguments: new KernelArguments { ["city"] = "苏州" });

                Console.WriteLine($"\n✅ 不提供 Token,结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"\n❌ 不提供 Token,认证失败:{ex.Message}");
            }
        }

        static Kernel CreateKernelWithAuth()
        {
            var builder = Kernel.CreateBuilder();
             
            builder.AddOpenAIChatCompletion(
              modelId: ConstParm.modelId,
              apiKey: ConstParm.apiKey,
              endpoint: new Uri(ConstParm.endpoint));

            // 配置日志
            builder.Services.AddLogging(b => b
                .AddConsole()
                .SetMinimumLevel(LogLevel.Information));

            var kernel = builder.Build();

            // ========== 注册认证过滤器 ==========

            // 模拟 Token 验证服务
            Func<string, Task<bool>> validateToken = async (token) =>
            {
                // 模拟网络请求验证 Token
                await Task.Delay(10);

                // 简单规则:包含 "valid" 的 Token 有效
                return token.Contains("valid");
            };

            // 配置插件权限(可选)
            var pluginPermissions = new Dictionary<string, string[]>();
            //Weather插件可以被admin 和 user 两种角色访问
            pluginPermissions.Add("Weather", new[] { "admin", "user" });
            //WeatherHistory插件只能被 admin 角色访问
            pluginPermissions.Add("WeatherHistory", new[] { "admin" });


            kernel.FunctionInvocationFilters.Add(new AuthenticationFilter(
                validateToken,
                pluginPermissions));

            return kernel;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public class WeatherPlugin
    {
        [KernelFunction("GetWeather"), Description("获取指定城市当天的天气预报")]
        public async Task<string> GetWeather([Description("城市名称")] string city)
        {
            await Task.Delay(10);//模拟查询天气预报
            return JsonConvert.SerializeObject( new 
            {
                City = city,
                Temperature = 25.5,
                Condition = "",
                UpdatedAt = DateTime.Now
            });
        }

        [KernelFunction("GetWeatherByDate"), Description("获取指定日期及城市的天气预报")]
        public async Task<string> GetWeatherByDate([Description("城市名称")] string city,DateTime dt)
        {
            await Task.Delay(10);//模拟查询天气预报
            return JsonConvert.SerializeObject(new
            {
                City = city,
                Temperature = 25.5,
                Condition = "",
                UpdatedAt =dt
            });
        }
    }

     
    public class WeatherHistoryPlugin
    {
        [KernelFunction("GetWeatherHistory"), Description("获取指定城市近一周的天气情况(管理员专用)")]
        public async Task<string> GetWeatherHistory([Description("城市名称")] string cityName)
        {
            await Task.Delay(10);//模拟查询天气预报
            return JsonConvert.SerializeObject(new List<string>() { "8.1日晴", "8.2日晴", "8.3日晴", "8.4日晴", "8.5日晴", "8.6日晴", "8.7日阴" });
        } 
    }
}
View Code

2、IFunctionInvocationFilter、日志过滤器

日志过滤器

    // ============================================
    // ✅ 日志过滤器(彩色控制台输出)
    // ============================================
    public class LoggingFilter : IFunctionInvocationFilter
    {
        private readonly ILogger<LoggingFilter> _logger;
        private readonly Stopwatch _stopwatch;

        public LoggingFilter(ILogger<LoggingFilter> logger)
        {
            _logger = logger;
            _stopwatch = new Stopwatch();
        }

        public async Task OnFunctionInvocationAsync(
            FunctionInvocationContext context,
            Func<FunctionInvocationContext, Task> next)
        {
            var functionName = $"{context.Function.PluginName}.{context.Function.Name}";
            var arguments = string.Join(", ", context.Arguments.Select(kvp => $"{kvp.Key}={kvp.Value}"));

            // 📝 开始执行函数 - 彩色输出
            _logger.LogInformation("📝 开始执行函数:{FunctionName}({Arguments})", functionName, arguments);
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.Write($"📝 开始执行函数:{functionName}(");
            Console.ForegroundColor = ConsoleColor.White;
            Console.Write($"{arguments}");
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WriteLine(")");
            Console.ResetColor();

            _stopwatch.Restart();

            try
            {
                // 执行函数
                await next(context);

                _stopwatch.Stop();
                var result = context.Result?.GetValue<object>()?.ToString() ?? "null";

                // ✅ 函数执行成功 - 彩色输出
                _logger.LogInformation(
                    "✅ 函数执行成功:{FunctionName},耗时 {ElapsedMs}ms,结果:{Result}",
                    functionName,
                    _stopwatch.ElapsedMilliseconds,
                    Truncate(result, 100));

                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write($"✅ 函数执行成功:{functionName},");
                Console.ForegroundColor = ConsoleColor.DarkGreen;
                Console.Write($"耗时 {_stopwatch.ElapsedMilliseconds}ms,");
                Console.ForegroundColor = ConsoleColor.Gray;
                Console.WriteLine($"结果:{Truncate(result, 100)}");
                Console.ResetColor();
            }
            catch (Exception ex)
            {
                _stopwatch.Stop();

                // ❌ 函数执行失败 - 彩色输出
                _logger.LogError(
                    ex,
                    "❌ 函数执行失败:{FunctionName},耗时 {ElapsedMs}ms,错误:{Error}",
                    functionName,
                    _stopwatch.ElapsedMilliseconds,
                    ex.Message);

                Console.ForegroundColor = ConsoleColor.Red;
                Console.Write($"❌ 函数执行失败:{functionName},");
                Console.ForegroundColor = ConsoleColor.DarkRed;
                Console.Write($"耗时 {_stopwatch.ElapsedMilliseconds}ms,");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine($"错误:{ex.Message}");
                Console.ResetColor();

                throw;
            }
        }

        private string Truncate(string text, int maxLength)
        {
            if (string.IsNullOrEmpty(text) || text.Length <= maxLength)
                return text;
            return text[..maxLength] + "...";
        }
    }
View Code

入口处和Token入口一致,只需增加注册日志过滤器

image

using ConsoleApp1;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Newtonsoft.Json;
using Polly;
using Polly.Retry;
using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Threading.RateLimiting;

namespace ConsoleApp4
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("╔════════════════════════════════════════╗");
            Console.WriteLine("║    SK 认证过滤器使用示例               ║");
            Console.WriteLine("╚════════════════════════════════════════╝\n");

            // 1️⃣ 创建 Kernel
            var kernel = CreateKernelWithAuth();

            // 2️⃣ 注册插件
            var weatherPlugin = new WeatherPlugin();
            var weatherPluginName = "Weather";
            var plugin = KernelPluginFactory.CreateFromObject(weatherPlugin, weatherPluginName); 
            kernel.Plugins.Add(plugin);

            var weatherHistoryPlugin = new WeatherHistoryPlugin();
            var WeatherHistoryPluginName = "WeatherHistory";
            var HistoryPlugin = KernelPluginFactory.CreateFromObject(weatherHistoryPlugin, WeatherHistoryPluginName);
            kernel.Plugins.Add(HistoryPlugin);
            // ========== 场景 1: 有效 Token ==========
            Console.WriteLine("═══════════════════════════════════════");
            Console.WriteLine("场景 1: 使用有效 Token,角色:user");
            Console.WriteLine("═══════════════════════════════════════\n");

            kernel.Data["auth_token"] = "valid_token_user";

            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: weatherPluginName,
                    functionName: "GetWeather",
                    arguments: new KernelArguments { ["city"] = "苏州" });

                Console.WriteLine($"\n✅ user尝试访问GetWeather,结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"\n❌ user尝试访问GetWeather,认证失败:{ex.Message}");
            }
            // 
            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: weatherPluginName,
                    functionName: "GetWeatherByDate",
                    arguments: new KernelArguments { ["city"] = "苏州", ["dt"]=DateTime.Now.AddDays(-3).ToString("yyyy-MM-dd") });

                Console.WriteLine($"\n✅ user尝试访问GetWeatherByDate,结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"\n❌  user尝试访问GetWeatherByDate,认证失败:{ex.Message}");
            }
            //尝试访问Admin专属的插件
            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: WeatherHistoryPluginName,
                    functionName: "GetWeatherHistory",
                    arguments: new KernelArguments { ["city"] = "苏州" });

                Console.WriteLine($"\n✅ user尝试访问GetWeatherHistory,结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"\n❌ user尝试访问GetWeatherHistory,认证失败:{ex.Message}");
            }

            // ========== 场景 2: 使用有效 Token,角色:guest  ==========
            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("场景 2: 使用有效 Token,角色:guest");
            Console.WriteLine("═══════════════════════════════════════\n");

            ///guest 尝试访问 GetWeather
            kernel.Data["auth_token"] = "valid_token_guest";

            try
            {
                var result = await kernel.InvokeAsync(
                     pluginName: weatherPluginName,
                    functionName: "GetWeather",
                    arguments: new KernelArguments { ["city"] = "苏州" });

                Console.WriteLine($"\n✅ guest 尝试访问 GetWeather:结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"\n❌ guest 尝试访问 GetWeather:认证失败:{ex.Message}");
            }

            // ========== 场景 3: 无 Token 无效token ==========
            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("场景 3: 不提供 Token");
            Console.WriteLine("═══════════════════════════════════════\n");

            kernel.Data.Remove("auth_token");

            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: weatherPluginName,
                    functionName: "GetWeather",
                    arguments: new KernelArguments { ["city"] = "苏州" });

                Console.WriteLine($"\n✅ 不提供 Token,结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"\n❌ 不提供 Token,认证失败:{ex.Message}");
            }
        }

        static Kernel CreateKernelWithAuth()
        {
            var builder = Kernel.CreateBuilder();
             
            builder.AddOpenAIChatCompletion(
              modelId: ConstParm.modelId,
              apiKey: ConstParm.apiKey,
              endpoint: new Uri(ConstParm.endpoint));

            // 配置日志
            builder.Services.AddLogging(b => b
                .AddConsole()
                .SetMinimumLevel(LogLevel.Information));

            var kernel = builder.Build();

            // ========== 注册认证过滤器 ==========

            // 模拟 Token 验证服务
            Func<string, Task<bool>> validateToken = async (token) =>
            {
                // 模拟网络请求验证 Token
                await Task.Delay(10);

                // 简单规则:包含 "valid" 的 Token 有效
                return token.Contains("valid");
            };

            // 配置插件权限(可选)
            var pluginPermissions = new Dictionary<string, string[]>();
            //Weather插件可以被admin 和 user 两种角色访问
            pluginPermissions.Add("Weather", new[] { "admin", "user" });
            //WeatherHistory插件只能被 admin 角色访问
            pluginPermissions.Add("WeatherHistory", new[] { "admin" });

            // ========== 注册TOKEN过滤器 ==========
            kernel.FunctionInvocationFilters.Add(new AuthenticationFilter(
                validateToken,
                pluginPermissions));

            // ========== 注册日志过滤器 ==========
            var logger = kernel.Services.GetRequiredService<ILogger<LoggingFilter>>();
            kernel.FunctionInvocationFilters.Add(new LoggingFilter(logger));

            return kernel;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public class WeatherPlugin
    {
        [KernelFunction("GetWeather"), Description("获取指定城市当天的天气预报")]
        public async Task<string> GetWeather([Description("城市名称")] string city)
        {
            await Task.Delay(10);//模拟查询天气预报
            return JsonConvert.SerializeObject( new 
            {
                City = city,
                Temperature = 25.5,
                Condition = "",
                UpdatedAt = DateTime.Now
            });
        }

        [KernelFunction("GetWeatherByDate"), Description("获取指定日期及城市的天气预报")]
        public async Task<string> GetWeatherByDate([Description("城市名称")] string city,DateTime dt)
        {
            await Task.Delay(10);//模拟查询天气预报
            return JsonConvert.SerializeObject(new
            {
                City = city,
                Temperature = 25.5,
                Condition = "",
                UpdatedAt =dt
            });
        }
    }

     
    public class WeatherHistoryPlugin
    {
        [KernelFunction("GetWeatherHistory"), Description("获取指定城市近一周的天气情况(管理员专用)")]
        public async Task<string> GetWeatherHistory([Description("城市名称")] string cityName)
        {
            await Task.Delay(10);//模拟查询天气预报
            return JsonConvert.SerializeObject(new List<string>() { "8.1日晴", "8.2日晴", "8.3日晴", "8.4日晴", "8.5日晴", "8.6日晴", "8.7日阴" });
        } 
    }
}
View Code

执行结果彩色部分为日志过滤器生成

image

3、IFunctionInvocationFilter、限流过滤器

接合上述Token过滤器和日志过滤器,现在增加限流过滤器,执行过程如下:

用户调用 kernel.InvokeAsync()
    ↓
┌─────────────────────────────────────────┐
│  1. AuthenticationFilter (认证)         │ ← 验证 Token 和权限
└─────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────┐
│  2. LoggingFilter (日志 - 前)           │ ← 记录开始时间
└─────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────┐
│  3. RateLimitingFilter (限流)           │ ← 检查频率限制
└─────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────┐
│  4. 实际函数执行                         │
└─────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────┐
│  5. LoggingFilter (日志 - 后)           │ ← 记录结果和耗时
└─────────────────────────────────────────┘
    ↓
返回结果

注册限流过滤器

image

            // ========== 注册TOKEN过滤器 ==========
            kernel.FunctionInvocationFilters.Add(new AuthenticationFilter(
                validateToken,
                pluginPermissions));

            // ========== 注册日志过滤器 ==========
            var logger = kernel.Services.GetRequiredService<ILogger<LoggingFilter>>();
            kernel.FunctionInvocationFilters.Add(new LoggingFilter(logger));



            // ========== 注册限流过滤器 ==========
            var rateLimiter = new RateLimitingFilter(requestsPerMinute: 3); // 全局限流:每分钟 3 次

            // 按插件限流(可选)
            rateLimiter.AddPluginLimit("Weather", 4);         // Weather 插件:每分钟 4 次 、WeatherHistory 插件:默认为全局:每分钟 3 次
            kernel.FunctionInvocationFilters.Add(rateLimiter);
View Code

限流过滤器

// ============================================
// ✅ 限流过滤器 
// ============================================
public class RateLimitingFilter : IFunctionInvocationFilter
{
    private readonly RateLimiter _rateLimiter;
    private readonly Dictionary<string, RateLimiter> _pluginLimiters;

    /// <summary>
    /// 全局限流
    /// </summary>
    public RateLimitingFilter(int requestsPerMinute = 60)
    {
        _rateLimiter = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
        {
            PermitLimit = requestsPerMinute,
            Window = TimeSpan.FromMinutes(1),
            QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
            QueueLimit = 0  // ⚠️ 关键:队列限制设为 0,不等待
        });
        _pluginLimiters = new();
    }

    /// <summary>
    /// 按插件限流
    /// </summary>
    public void AddPluginLimit(string pluginName, int requestsPerMinute)
    {
        _pluginLimiters[pluginName] = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
        {
            PermitLimit = requestsPerMinute,
            Window = TimeSpan.FromMinutes(1),
            QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
            QueueLimit = 0  // ⚠️ 关键:队列限制设为 0,不等待
        });
    }

    public async Task OnFunctionInvocationAsync(
        FunctionInvocationContext context,
        Func<FunctionInvocationContext, Task> next)
    {
        // 选择限流器(插件级别优先)
        var limiter = _pluginLimiters.TryGetValue(context.Function.PluginName, out var pluginLimiter)
            ? pluginLimiter
            : _rateLimiter;

        // ✅ 修复:使用 AttemptAcquire 立即返回,不等待
        using var lease = limiter.AttemptAcquire(1);

        if (!lease.IsAcquired)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"🚫 触发限流:{context.Function.PluginName}.{context.Function.Name}");
            Console.ResetColor();

            throw new RateLimitExceededException(
                $"🚫 函数调用频率超限:{context.Function.PluginName}.{context.Function.Name}");
        }

        Console.ForegroundColor = ConsoleColor.DarkYellow;
        Console.WriteLine($"🚦 限流通过:{context.Function.Name}");
        Console.ResetColor();

        await next(context);
    }
}

/// <summary>
/// 限流异常
/// </summary>
public class RateLimitExceededException : Exception
{
    public RateLimitExceededException(string message) : base(message)
    {

    }
}
View Code

程序入口

using ConsoleApp1;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Newtonsoft.Json;
using Polly;
using Polly.Retry;
using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Threading.RateLimiting;

namespace ConsoleApp4
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("╔════════════════════════════════════════╗");
            Console.WriteLine("║    SK 认证过滤器使用示例               ║");
            Console.WriteLine("╚════════════════════════════════════════╝\n");

            // 1️⃣ 创建 Kernel
            var kernel = CreateKernelWithAuth();

            // 2️⃣ 注册插件
            var weatherPlugin = new WeatherPlugin();
            var weatherPluginName = "Weather";
            var plugin = KernelPluginFactory.CreateFromObject(weatherPlugin, weatherPluginName); 
            kernel.Plugins.Add(plugin);

            var weatherHistoryPlugin = new WeatherHistoryPlugin();
            var WeatherHistoryPluginName = "WeatherHistory";
            var HistoryPlugin = KernelPluginFactory.CreateFromObject(weatherHistoryPlugin, WeatherHistoryPluginName);
            kernel.Plugins.Add(HistoryPlugin);
            // ========== 场景 1: 有效 Token ==========循环调用 6 次,第 6 次会触发限流==============
            Console.WriteLine("═══════════════════════════════════════");
            Console.WriteLine("场景 1: 使用有效 Token,角色:user");
            Console.WriteLine("═══════════════════════════════════════\n");

            kernel.Data["auth_token"] = "valid_token_user";
            for (int i = 1; i <= 6; i++)
            {
                Console.WriteLine($"\n--- 第 {i} 次调用 ---\n");
                try
                {
                    var result = await kernel.InvokeAsync(
                        pluginName: weatherPluginName,
                        functionName: "GetWeather",
                        arguments: new KernelArguments { ["city"] = "苏州" });

                    Console.WriteLine($"\n✅ user尝试访问GetWeather,结果:{result.GetValue<string>()}");
                }
                catch (UnauthorizedAccessException ex)
                {
                    Console.WriteLine($"❌ 第 {i} 次调用,认证失败:{ex.Message}");
                }
                catch (RateLimitExceededException ex)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine($"🚫 第 {i} 次调用,触发限流:{ex.Message}");
                    Console.ResetColor();
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"❌ 第 {i} 次调用,其他错误:{ex.Message}");
                }
            }
        }

        static Kernel CreateKernelWithAuth()
        {
            var builder = Kernel.CreateBuilder();
             
            builder.AddOpenAIChatCompletion(
              modelId: ConstParm.modelId,
              apiKey: ConstParm.apiKey,
              endpoint: new Uri(ConstParm.endpoint));

            // 配置日志
            builder.Services.AddLogging(b => b
                .AddConsole()
                .SetMinimumLevel(LogLevel.Information));

            var kernel = builder.Build();

            // ========== 注册认证过滤器 ==========

            // 模拟 Token 验证服务
            Func<string, Task<bool>> validateToken = async (token) =>
            {
                // 模拟网络请求验证 Token
                await Task.Delay(10);

                // 简单规则:包含 "valid" 的 Token 有效
                return token.Contains("valid");
            };

            // 配置插件权限(可选)
            var pluginPermissions = new Dictionary<string, string[]>();
            //Weather插件可以被admin 和 user 两种角色访问
            pluginPermissions.Add("Weather", new[] { "admin", "user" });
            //WeatherHistory插件只能被 admin 角色访问
            pluginPermissions.Add("WeatherHistory", new[] { "admin" });

            // ========== 注册TOKEN过滤器 ==========
            kernel.FunctionInvocationFilters.Add(new AuthenticationFilter(
                validateToken,
                pluginPermissions));

            // ========== 注册日志过滤器 ==========
            var logger = kernel.Services.GetRequiredService<ILogger<LoggingFilter>>();
            kernel.FunctionInvocationFilters.Add(new LoggingFilter(logger));



            // ========== 注册限流过滤器 ==========
            var rateLimiter = new RateLimitingFilter(requestsPerMinute: 3); // 全局限流:每分钟 3 次

            // 按插件限流(可选)
            rateLimiter.AddPluginLimit("Weather", 4);         // Weather 插件:每分钟 4 次 、WeatherHistory 插件:默认为全局:每分钟 3 次
            kernel.FunctionInvocationFilters.Add(rateLimiter);

            return kernel;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public class WeatherPlugin
    {
        [KernelFunction("GetWeather"), Description("获取指定城市当天的天气预报")]
        public async Task<string> GetWeather([Description("城市名称")] string city)
        {
            await Task.Delay(10);//模拟查询天气预报
            return JsonConvert.SerializeObject( new 
            {
                City = city,
                Temperature = 25.5,
                Condition = "",
                UpdatedAt = DateTime.Now
            });
        }

        [KernelFunction("GetWeatherByDate"), Description("获取指定日期及城市的天气预报")]
        public async Task<string> GetWeatherByDate([Description("城市名称")] string city,DateTime dt)
        {
            await Task.Delay(10);//模拟查询天气预报
            return JsonConvert.SerializeObject(new
            {
                City = city,
                Temperature = 25.5,
                Condition = "",
                UpdatedAt =dt
            });
        }
    }

     
    public class WeatherHistoryPlugin
    {
        [KernelFunction("GetWeatherHistory"), Description("获取指定城市近一周的天气情况(管理员专用)")]
        public async Task<string> GetWeatherHistory([Description("城市名称")] string cityName)
        {
            await Task.Delay(10);//模拟查询天气预报
            return JsonConvert.SerializeObject(new List<string>() { "8.1日晴", "8.2日晴", "8.3日晴", "8.4日晴", "8.5日晴", "8.6日晴", "8.7日阴" });
        } 
    }
}
View Code

image

 执行结果

image

4、自动函数调用监控过滤器、自动函数调用限制过滤器、Prompt 渲染过滤器(敏感词过滤)、总项目代码

自动函数调用监控过滤器:目的是为了监控LLM调用函数的全过程,形成日志

自动函数调用限制过滤器:目的是防止LLM大量调用函数,或者一些诸如删除数据的敏感函数!

Prompt 渲染过滤器:目的是将密码,AI的ApiKey等敏感信息加以处理,防止机密信息泄露

4.1、项目引用

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <NoWarn>$(NoWarn);NU5104</NoWarn>
    </PropertyGroup>

    <ItemGroup>
        <Compile Remove="Registry\**" />
        <EmbeddedResource Remove="Registry\**" />
        <None Remove="Registry\**" />
    </ItemGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.10" />
        <PackageReference Include="Microsoft.SemanticKernel" Version="1.78.0" />
        <PackageReference Include="Microsoft.SemanticKernel.Connectors.Sqlite" Version="1.51.0-preview" />
        <PackageReference Include="Microsoft.SemanticKernel.Plugins.Core" Version="1.78.0-preview" />
        <PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" Version="1.78.0-alpha" />
        <PackageReference Include="Microsoft.SemanticKernel.Plugins.OpenApi" Version="1.78.0" />
        <PackageReference Include="Microsoft.SemanticKernel.PromptTemplates.Handlebars" Version="1.78.0" />
        <PackageReference Include="Microsoft.SemanticKernel.Plugins.Web" Version="1.78.0-alpha" />
        <PackageReference Include="Qdrant.Client" Version="1.18.1" />
        <PackageReference Include="Sdcb.DashScope" Version="2.0.0" />
        <!-- ✅ 新增:限流需要 -->
        <PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" />

        <!-- ✅ 新增:重试需要 -->
        <PackageReference Include="Polly" Version="8.4.0" />
    </ItemGroup>

    <ItemGroup>
        <ProjectReference Include="..\Services\Services.csproj" />
    </ItemGroup>

</Project>
View Code

4.2、main入口

using ConsoleApp1;
using Grpc.Core;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Newtonsoft.Json;
using Polly;
using Polly.Retry;
using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Threading.RateLimiting;

namespace ConsoleApp4
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("╔════════════════════════════════════════╗");
            Console.WriteLine("║ SK 8 个过滤器完整演示                   ║");
            Console.WriteLine("╚════════════════════════════════════════╝\n");

            // 1️⃣ 创建 Kernel(包含所有 8 个过滤器)
            var kernel = CreateKernelWithAuth();

            // 2️⃣ 注册插件
            var weatherPlugin = new WeatherPlugin();
            kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(weatherPlugin, pluginName: "Weather"));

            var emailPlugin = new EmailPlugin();
            kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(emailPlugin, pluginName: "Email"));

            var adminPlugin = new AdminPlugin();
            kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(adminPlugin, pluginName: "Admin"));

            // ========== 场景 1: 认证过滤器 - 无 Token ==========
            Console.WriteLine("═══════════════════════════════════════");
            Console.WriteLine("场景 1: 认证过滤器 - 无 Token");
            Console.WriteLine("═══════════════════════════════════════\n");

            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: "Weather",
                    functionName: "GetWeather",
                    arguments: new KernelArguments { ["city"] = "苏州" });

                Console.WriteLine($"✅ 结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"❌ 认证失败:{ex.Message}");
            }

            // ========== 场景 2: 认证过滤器 - 有效 Token ==========
            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("场景 2: 认证过滤器 - 有效 Token");
            Console.WriteLine("═══════════════════════════════════════\n");

            kernel.Data["auth_token"] = "valid_token_user";

            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: "Weather",
                    functionName: "GetWeather",
                    arguments: new KernelArguments { ["city"] = "苏州" });

                Console.WriteLine($"✅ 结果:{result.GetValue<string>()}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"❌ 失败:{ex.Message}");
            }

            // ========== 场景 3: 缓存过滤器 - 相同参数 ==========
            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("场景 3: 缓存过滤器 - 相同参数(缓存命中)");
            Console.WriteLine("═══════════════════════════════════════\n");

            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: "Weather",
                    functionName: "GetWeather",
                    arguments: new KernelArguments { ["city"] = "苏州" });

                Console.WriteLine($"✅ 结果:{result.GetValue<string>()}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"❌ 失败:{ex.Message}");
            }

            // ========== 场景 4: Prompt 渲染过滤器 - 敏感词 ==========
            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("场景 4: Prompt 渲染过滤器 - 敏感词过滤");
            Console.WriteLine("═══════════════════════════════════════\n");

            kernel.Data["auth_token"] = "valid_token_admin";

            var settings = new Microsoft.SemanticKernel.Connectors.OpenAI.OpenAIPromptExecutionSettings
            {
                FunctionChoiceBehavior = Microsoft.SemanticKernel.FunctionChoiceBehavior.Auto(),
                Temperature = 0.7,
                MaxTokens = 1000
            };

            try
            {
                Console.WriteLine("🤖 用户:我的密码是 123456,请保存\n");
                Console.Write("🤖 AI: ");
                await foreach (var chunk in kernel.InvokePromptStreamingAsync(
                    "我的密码是 123456,请保存",
                    new(settings)))
                {
                    Console.Write(chunk.ToString());
                }
                Console.WriteLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"❌ 错误:{ex.Message}");
            }

            // ========== 场景 5: 限流 + 重试过滤器 - 循环调用 ==========
            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("场景 5: 限流 + 重试过滤器 - 循环调用 6 次");
            Console.WriteLine("═══════════════════════════════════════\n");

            kernel.Data["auth_token"] = "valid_token_user";

            for (int i = 1; i <= 6; i++)
            {
                Console.WriteLine($"\n--- 第 {i} 次调用 ---\n");

                try
                {
                    var result = await kernel.InvokeAsync(
                        pluginName: "Weather",
                        functionName: "GetWeather",
                        arguments: new KernelArguments { ["city"] = $"城市{i}" });

                    Console.WriteLine($"✅ 第 {i} 次调用成功");
                }
                catch (UnauthorizedAccessException ex)
                {
                    Console.WriteLine($"❌ 认证失败:{ex.Message}");
                } 
                catch (Exception ex)
                {
                    Console.WriteLine($"❌ 失败:{ex.Message}");
                }
            }

            // ========== 场景 6: 自动函数调用监控 + 限制过滤器 ==========
            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("场景 6: 自动函数调用监控 + 限制过滤器");
            Console.WriteLine("═══════════════════════════════════════\n");

            kernel.Data["auth_token"] = "valid_token_admin";

            try
            {
                Console.WriteLine("🤖 用户:请查询苏州、北京、上海、广州、深圳、杭州、南京的天气\n");
                Console.Write("🤖 AI: ");
                await foreach (var chunk in kernel.InvokePromptStreamingAsync(
                    "请查询苏州、北京、上海、广州、深圳、杭州、南京的天气",
                    new(settings)))
                {
                    Console.Write(chunk.ToString());
                }
                Console.WriteLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"❌ 错误:{ex.Message}");
            }

            // ========== 场景 7: 权限控制 - 用户访问管理员插件 ==========
            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("场景 7: 认证过滤器 - 权限控制");
            Console.WriteLine("═══════════════════════════════════════\n");

            kernel.Data["auth_token"] = "valid_token_user";

            try
            {
                var result = await kernel.InvokeAsync(
                    pluginName: "Admin",
                    functionName: "GetStats",
                    arguments: new KernelArguments());

                Console.WriteLine($"✅ 结果:{result.GetValue<string>()}");
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine($"❌ 权限不足:{ex.Message}");
            }

            // ========== 场景 8: 自动调用黑名单函数 ==========
            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("场景 8: 自动函数调用限制 - 黑名单函数");
            Console.WriteLine("═══════════════════════════════════════\n");

            kernel.Data["auth_token"] = "valid_token_admin";

            try
            {
                Console.WriteLine("🤖 用户:请删除所有数据\n");
                Console.Write("🤖 AI: ");
                await foreach (var chunk in kernel.InvokePromptStreamingAsync(
                    "请删除所有数据",
                    new(settings)))
                {
                    Console.Write(chunk.ToString());
                }
                Console.WriteLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"❌ 错误:{ex.Message}");
            }

            Console.WriteLine("\n═══════════════════════════════════════");
            Console.WriteLine("✅ 所有场景演示完成!");
            Console.WriteLine("═══════════════════════════════════════\n");
        }

        static Kernel CreateKernelWithAuth()
        {
            var builder = Kernel.CreateBuilder();

            builder.AddOpenAIChatCompletion(
                modelId: ConstParm.modelId,
                apiKey: ConstParm.apiKey,
                endpoint: new Uri(ConstParm.endpoint));

            builder.Services.AddLogging(b => b
                .AddConsole()
                .SetMinimumLevel(LogLevel.Information));

            builder.Services.AddMemoryCache();

            var kernel = builder.Build();

            // ========== 1️⃣ 认证过滤器 ==========
            Func<string, Task<bool>> validateToken = async (token) =>
            {
                await Task.Delay(10);
                return token.Contains("valid");
            };

            var pluginPermissions = new Dictionary<string, string[]>
            {
                ["Weather"] = new[] { "admin", "user" },
                ["Email"] = new[] { "admin", "user" },
                ["Admin"] = new[] { "admin" }
            };

            kernel.FunctionInvocationFilters.Add(new AuthenticationFilter(
                validateToken,
                pluginPermissions));

            // ========== 2️⃣ 日志过滤器 ==========
            var logger = kernel.Services.GetRequiredService<ILogger<LoggingFilter>>();
            kernel.FunctionInvocationFilters.Add(new LoggingFilter(logger));

            // ========== 3️⃣ Prompt 渲染过滤器 ==========
            var sensitiveWords = new List<string>
        {
            "密码", "password", "Token", "token", "sk-", "密钥", "secret"
        };

            kernel.PromptRenderFilters.Add(new PromptSanitizerFilter(sensitiveWords));

            // ========== 4️⃣ 缓存过滤器 ==========
            var memoryCache = kernel.Services.GetRequiredService<IMemoryCache>();
            kernel.FunctionInvocationFilters.Add(new CacheFilter(memoryCache, TimeSpan.FromMinutes(5)));

            // ========== 5️⃣ 重试过滤器 ==========
            kernel.FunctionInvocationFilters.Add(new RetryFilter(
                maxRetries: 3,
                delay: TimeSpan.FromSeconds(1)));

            // ========== 6️⃣ 限流过滤器 ==========
            var rateLimiter = new RateLimitingFilter(requestsPerMinute: 5);
            rateLimiter.AddPluginLimit("Weather", 5);
            kernel.FunctionInvocationFilters.Add(rateLimiter);

            // ========== 7️⃣ 自动函数调用监控过滤器 ==========
            kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionMonitoringFilter());

            // ========== 8️⃣ 自动函数调用限制过滤器 ==========
            kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionCallLimitFilter(
                maxCallsPerConversation: 5,
                blockedFunctions: new[] { "delete", "remove", "clear" }
            ));

            return kernel;
        }
    } 
    // ============================================
    // ✅ 插件类
    // ============================================
    public class WeatherPlugin
    {
        [KernelFunction("GetWeather"), Description("获取指定城市当天的天气预报")]
        public async Task<string> GetWeather([Description("城市名称")] string city)
        {
            await Task.Delay(10);
            return JsonConvert.SerializeObject(new
            {
                City = city,
                Temperature = 25.5,
                Condition = "",
                UpdatedAt = DateTime.Now
            });
        }
    }

    public class EmailPlugin
    {
        [KernelFunction("SendEmail"), Description("发送邮件到指定邮箱")]
        public async Task<string> SendEmail(
            [Description("收件人邮箱")] string to,
            [Description("邮件主题")] string subject,
            [Description("邮件内容")] string body)
        {
            await Task.Delay(10);
            return $"✅ 邮件已发送到 {to}";
        }
    }

    public class AdminPlugin
    {
        [KernelFunction("DeleteData"), Description("删除所有数据(危险操作)")]
        public async Task<string> DeleteData()
        {
            await Task.Delay(10);
            return "❌ 数据已删除(模拟)";
        }

        [KernelFunction("GetStats"), Description("获取系统统计信息")]
        public async Task<string> GetStats()
        {
            await Task.Delay(10);
            return JsonConvert.SerializeObject(new
            {
                TotalUsers = 1000,
                ActiveUsers = 500,
                Timestamp = DateTime.Now
            });
        }
    }
}
View Code

 4.3、所有过滤器

using HandlebarsDotNet;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.RateLimiting;
using System.Threading.Tasks;

namespace ConsoleApp4
{
    // ============================================
    // ✅ 认证过滤器
    // ============================================
    public class AuthenticationFilter : IFunctionInvocationFilter
    {
        private readonly Func<string, Task<bool>> _validateToken;
        private readonly Dictionary<string, string[]> _pluginPermissions;

        public AuthenticationFilter(
            Func<string, Task<bool>> validateToken,
            Dictionary<string, string[]>? pluginPermissions = null)
        {
            _validateToken = validateToken;
            _pluginPermissions = pluginPermissions ?? new();
        }

        public async Task OnFunctionInvocationAsync(
            FunctionInvocationContext context,
            Func<FunctionInvocationContext, Task> next)
        {
            var token = GetTokenFromContext(context);

            if (string.IsNullOrEmpty(token) || !await _validateToken(token))
            {
                throw new UnauthorizedAccessException(
                    $"❌ 未授权的函数调用:{context.Function.PluginName}.{context.Function.Name}");
            }

            if (string.IsNullOrEmpty(context.Function.PluginName))
            {
                await next(context);
                return;
            }

            if (_pluginPermissions.Count > 0)
            {
                var userRole = GetUserRoleFromToken(token);

                if (_pluginPermissions.TryGetValue(context.Function.PluginName, out var requiredRoles))
                {
                    if (!requiredRoles.Contains(userRole))
                    {
                        throw new UnauthorizedAccessException(
                            $"❌ 用户角色 [{userRole}] 无权限访问插件 [{context.Function.PluginName}]");
                    }
                }
            }

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine($"🔐 认证通过:{context.Function.PluginName}.{context.Function.Name}");
            Console.ResetColor();
            await next(context);
        }

        private string GetTokenFromContext(FunctionInvocationContext context)
        {
            if (context.Kernel.Data.TryGetValue("auth_token", out var token))
            {
                return token?.ToString() ?? "";
            }
            return "";
        }

        private string GetUserRoleFromToken(string token)
        {
            if (token.Contains("admin")) return "admin";
            if (token.Contains("user")) return "user";
            return "guest";
        }
    }

    // ============================================
    // ⭐ 自动函数调用限制过滤器
    // ============================================
    public class AutoFunctionCallLimitFilter : IAutoFunctionInvocationFilter
    {
        private readonly int _maxCallsPerConversation;
        private readonly HashSet<string> _blockedFunctions;
        private int _callCount = 0;

        public AutoFunctionCallLimitFilter(
            int maxCallsPerConversation = 10,
            string[]? blockedFunctions = null)
        {
            _maxCallsPerConversation = maxCallsPerConversation;
            _blockedFunctions = new HashSet<string>(blockedFunctions ?? Array.Empty<string>());
        }

        public async Task OnAutoFunctionInvocationAsync(
            AutoFunctionInvocationContext context,
            Func<AutoFunctionInvocationContext, Task> next)
        {
            // 检查调用次数
            if (_callCount >= _maxCallsPerConversation)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"⚠️  已达到最大函数调用次数限制 ({_maxCallsPerConversation})");
                Console.ResetColor();

                context.Result = new FunctionResult(
                    context.Function,
                    "已达到最大调用次数限制,请简化请求");
                return; // 阻止调用
            }

            // 检查黑名单函数
            if (_blockedFunctions.Any(f => context.Function.Name.ToLower().Contains(f)))
            {
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine($"⚠️  函数 {context.Function.Name} 被禁止自动调用");
                Console.ResetColor();

                context.Result = new FunctionResult(
                    context.Function,
                    $"函数 {context.Function.Name} 需要手动调用");
                return;
            }

            _callCount++;
            Console.ForegroundColor = ConsoleColor.DarkCyan;
            Console.WriteLine($"📞 自动函数调用 ({_callCount}/{_maxCallsPerConversation}): {context.Function.PluginName}.{context.Function.Name}");
            Console.ResetColor();

            await next(context);
        }
    }


    // ============================================
    // ✅ 日志过滤器(彩色控制台输出)
    // ============================================
    public class LoggingFilter : IFunctionInvocationFilter
    {
        private readonly ILogger<LoggingFilter> _logger;
        private readonly Stopwatch _stopwatch;

        public LoggingFilter(ILogger<LoggingFilter> logger)
        {
            _logger = logger;
            _stopwatch = new Stopwatch();
        }

        public async Task OnFunctionInvocationAsync(
            FunctionInvocationContext context,
            Func<FunctionInvocationContext, Task> next)
        {
            var functionName = $"{context.Function.PluginName}.{context.Function.Name}";
            var arguments = string.Join(", ", context.Arguments.Select(kvp => $"{kvp.Key}={kvp.Value}"));

            // 📝 开始执行函数 - 彩色输出
            _logger.LogInformation("📝 开始执行函数:{FunctionName}({Arguments})", functionName, arguments);
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.Write($"📝 开始执行函数:{functionName}(");
            Console.ForegroundColor = ConsoleColor.White;
            Console.Write($"{arguments}");
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WriteLine(")");
            Console.ResetColor();

            _stopwatch.Restart();

            try
            {
                // 执行函数
                await next(context);

                _stopwatch.Stop();
                var result = context.Result?.GetValue<object>()?.ToString() ?? "null";

                // ✅ 函数执行成功 - 彩色输出
                _logger.LogInformation(
                    "✅ 函数执行成功:{FunctionName},耗时 {ElapsedMs}ms,结果:{Result}",
                    functionName,
                    _stopwatch.ElapsedMilliseconds,
                    Truncate(result, 100));

                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write($"✅ 函数执行成功:{functionName},");
                Console.ForegroundColor = ConsoleColor.DarkGreen;
                Console.Write($"耗时 {_stopwatch.ElapsedMilliseconds}ms,");
                Console.ForegroundColor = ConsoleColor.Gray;
                Console.WriteLine($"结果:{Truncate(result, 100)}");
                Console.ResetColor();
            }
            catch (Exception ex)
            {
                _stopwatch.Stop();

                // ❌ 函数执行失败 - 彩色输出
                _logger.LogError(
                    ex,
                    "❌ 函数执行失败:{FunctionName},耗时 {ElapsedMs}ms,错误:{Error}",
                    functionName,
                    _stopwatch.ElapsedMilliseconds,
                    ex.Message);

                Console.ForegroundColor = ConsoleColor.Red;
                Console.Write($"❌ 函数执行失败:{functionName},");
                Console.ForegroundColor = ConsoleColor.DarkRed;
                Console.Write($"耗时 {_stopwatch.ElapsedMilliseconds}ms,");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine($"错误:{ex.Message}");
                Console.ResetColor();

                throw;
            }
        }

        private string Truncate(string text, int maxLength)
        {
            if (string.IsNullOrEmpty(text) || text.Length <= maxLength)
                return text;
            return text[..maxLength] + "...";
        }
    }
    
    // ============================================
    // ✅ 限流过滤器 
    // ============================================
    public class RateLimitingFilter : IFunctionInvocationFilter
    {
        private readonly RateLimiter _rateLimiter;
        private readonly Dictionary<string, RateLimiter> _pluginLimiters;

        /// <summary>
        /// 全局限流
        /// </summary>
        public RateLimitingFilter(int requestsPerMinute = 60)
        {
            _rateLimiter = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
            {
                PermitLimit = requestsPerMinute,
                Window = TimeSpan.FromMinutes(1),
                QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
                QueueLimit = 0  // ⚠️ 关键:队列限制设为 0,不等待
            });
            _pluginLimiters = new();
        }

        /// <summary>
        /// 按插件限流
        /// </summary>
        public void AddPluginLimit(string pluginName, int requestsPerMinute)
        {
            _pluginLimiters[pluginName] = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
            {
                PermitLimit = requestsPerMinute,
                Window = TimeSpan.FromMinutes(1),
                QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
                QueueLimit = 0  // ⚠️ 关键:队列限制设为 0,不等待
            });
        }

        public async Task OnFunctionInvocationAsync(
            FunctionInvocationContext context,
            Func<FunctionInvocationContext, Task> next)
        {  
            // ⭐ 修复:跳过没有 PluginName 的函数
            if (string.IsNullOrEmpty(context.Function.PluginName))
            {
                await next(context);
                return;
            }
            // 选择限流器(插件级别优先)
            var limiter = _pluginLimiters.TryGetValue(context.Function.PluginName, out var pluginLimiter)
                ? pluginLimiter
                : _rateLimiter;

            // ✅ 修复:使用 AttemptAcquire 立即返回,不等待
            using var lease = limiter.AttemptAcquire(1);

            if (!lease.IsAcquired)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"🚫 触发限流:{context.Function.PluginName}.{context.Function.Name}");
                Console.ResetColor();

                throw new RateLimitExceededException(
                    $"🚫 函数调用频率超限:{context.Function.PluginName}.{context.Function.Name}");
            }

            Console.ForegroundColor = ConsoleColor.DarkYellow;
            Console.WriteLine($"🚦 限流通过:{context.Function.Name}");
            Console.ResetColor();

            await next(context);
        }
    }

    /// <summary>
    /// 限流异常
    /// </summary>
    public class RateLimitExceededException : Exception
    {
        public RateLimitExceededException(string message) : base(message)
        {

        }
    }


    // ============================================
    // ✅ 重试过滤器
    // ============================================
    public class RetryFilter : IFunctionInvocationFilter
    {
        private readonly int _maxRetries;
        private readonly TimeSpan _delay;

        public RetryFilter(int maxRetries = 3, TimeSpan? delay = null)
        {
            _maxRetries = maxRetries;
            _delay = delay ?? TimeSpan.FromSeconds(1);
        }

        public async Task OnFunctionInvocationAsync(
            FunctionInvocationContext context,
            Func<FunctionInvocationContext, Task> next)
        {
            Exception? lastException = null;

            for (int i = 0; i < _maxRetries; i++)
            {
                try
                {
                    Console.ForegroundColor = ConsoleColor.DarkCyan;
                    Console.WriteLine($"🔄 重试 {i + 1}/{_maxRetries}");
                    Console.ResetColor();

                    await next(context);
                    return; // 成功,退出
                }
                catch (HttpOperationException ex) when (IsRetryable(ex))
                {
                    lastException = ex;

                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine($"⚠️  可重试错误:{ex.StatusCode},等待 {_delay.TotalSeconds * (i + 1)}s 后重试...");
                    Console.ResetColor();

                    if (i < _maxRetries - 1)
                    {
                        // 指数退避
                        await Task.Delay(_delay * (i + 1));
                    }
                }
            }

            throw lastException!;
        }

        private bool IsRetryable(HttpOperationException ex)
        {
            return ex.StatusCode is
                HttpStatusCode.TooManyRequests or      // 429 限流
                HttpStatusCode.InternalServerError or  // 500 服务器错误
                HttpStatusCode.ServiceUnavailable;     // 503 服务不可用
        }
    }



    // ============================================
    // ⭐ Prompt 渲染过滤器(敏感词过滤)
    // ============================================
    public class PromptSanitizerFilter : IPromptRenderFilter
    {
        private readonly List<string> _sensitiveWords;

        public PromptSanitizerFilter(List<string> sensitiveWords)
        {
            _sensitiveWords = sensitiveWords;
        }

        public async Task OnPromptRenderAsync(
            PromptRenderContext context,
            Func<PromptRenderContext, Task> next)
        {
            // 渲染 Prompt
            await next(context);

            // 处理渲染后的 Prompt
            var originalPrompt = context.RenderedPrompt;
            var sanitizedPrompt = Sanitize(originalPrompt);

            if (originalPrompt != sanitizedPrompt)
            {
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine($"\n⚠️  Prompt 已净化,移除敏感词");
                Console.WriteLine($"   原始:{Truncate(originalPrompt, 100)}");
                Console.WriteLine($"   净化:{Truncate(sanitizedPrompt, 100)}");
                Console.ResetColor();

                context.RenderedPrompt = sanitizedPrompt;
            }
        }

        private string Sanitize(string prompt)
        {
            var result = prompt;
            foreach (var word in _sensitiveWords)
            {
                result = result.Replace(word, new string('*', word.Length), StringComparison.OrdinalIgnoreCase);
            }
            return result;
        }

        private string Truncate(string text, int maxLength)
        {
            if (string.IsNullOrEmpty(text) || text.Length <= maxLength)
                return text;
            return text[..maxLength] + "...";
        }
    }


    // ============================================
    // ⭐ 自动函数调用监控过滤器
    // ============================================
    public class AutoFunctionMonitoringFilter : IAutoFunctionInvocationFilter
    {
        private int _invocationCount = 0;

        public async Task OnAutoFunctionInvocationAsync(
            AutoFunctionInvocationContext context,
            Func<AutoFunctionInvocationContext, Task> next)
        {
            _invocationCount++;

            Console.ForegroundColor = ConsoleColor.DarkCyan;
            Console.WriteLine($"\n🤖 [监控 {_invocationCount}] LLM 选择调用函数:{context.Function.PluginName}.{context.Function.Name}");
            Console.ResetColor();

            Console.ForegroundColor = ConsoleColor.Gray;
            Console.WriteLine($"   参数:{string.Join(", ", context.Arguments.Select(kvp => $"{kvp.Key}={kvp.Value}"))}");
            Console.ResetColor();

            // 记录开始时间
            var stopwatch = Stopwatch.StartNew();

            await next(context);

            stopwatch.Stop();

            // 记录结果和耗时
            Console.ForegroundColor = ConsoleColor.Gray;
            Console.WriteLine($"   结果:{context.Result?.GetValue<object>()?.ToString() ?? "null"}");
            Console.WriteLine($"   耗时:{stopwatch.ElapsedMilliseconds}ms");
            Console.ResetColor();
        }
    }

    /// <summary>
    /// 缓存过滤器
    /// </summary>
    public class CacheFilter : IFunctionInvocationFilter
    {
        private readonly IMemoryCache _cache;
        private readonly TimeSpan _expiration;

        public CacheFilter(IMemoryCache cache, TimeSpan? expiration = null)
        {
            _cache = cache;
            _expiration = expiration ?? TimeSpan.FromMinutes(5);
        }

        public async Task OnFunctionInvocationAsync(
            FunctionInvocationContext context,
            Func<FunctionInvocationContext, Task> next)
        {
            // ⭐ 修复:跳过没有 PluginName 的函数
            if (string.IsNullOrEmpty(context.Function.PluginName))
            {
                await next(context);
                return;
            }

            // 生成缓存键
            var cacheKey = GenerateCacheKey(context);

            // 尝试从缓存获取
            if (_cache.TryGetValue<string>(cacheKey, out var cachedResult))
            {
                Console.WriteLine($"💾 缓存命中:{cacheKey}");
                context.Result = new FunctionResult(context.Function, cachedResult);
                return; // 跳过实际执行
            }

            // 执行函数
            await next(context);

            // 缓存结果
            if (context.Result != null)
            {
                _cache.Set(cacheKey, context.Result.ToString(), _expiration);
            }
        }

        private string GenerateCacheKey(FunctionInvocationContext context)
        {
            var args = string.Join("_", context.Arguments.Select(a => $"{a.Key}={a.Value}"));
            return $"{context.Function.PluginName}_{context.Function.Name}_{args}";
        }
    }
}
View Code

4.4、运行效果

 4.4.1、Token认证

image

 4.4.2、日志过滤器

image

 4.4.3、缓存过滤器

image

 4.4.4、Prompt 过滤器

image

 4.4.5、限流过滤器

image

 4.4.6、LLM调用监控

image

 4.4.7、LLM黑名单函数

image

4.5、总结

8 个过滤器注册及触发场景总结

序号过滤器注册位置触发场景
1 AuthenticationFilter FunctionInvocationFilters 每次函数调用,验证 Token 和权限
2 LoggingFilter FunctionInvocationFilters 每次函数调用,记录日志和耗时
3 CacheFilter FunctionInvocationFilters 相同参数调用,缓存命中跳过执行
4 PromptSanitizerFilter PromptRenderFilters InvokePromptAsync 调用,过滤敏感词
5 RetryFilter FunctionInvocationFilters 429/500/503 错误,自动重试 3 次
6 RateLimitingFilter FunctionInvocationFilters 超过频率限制,抛出 429 异常
7 AutoFunctionMonitoringFilter AutoFunctionInvocationFilters LLM 自动选择函数,记录调用详情
8 AutoFunctionCallLimitFilter AutoFunctionInvocationFilters LLM 自动选择函数,限制次数和黑名单

posted @ 2026-08-05 18:08  天才卧龙  阅读(8)  评论(0)    收藏  举报